import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,441 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
const logger = Log.repository.getLogger("Marionette");
Cu.import("chrome://marionette/content/error.js");
XPCOMUtils.defineLazyModuleGetter(
this, "setInterval", "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(
this, "clearInterval", "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyGetter(this, "service", () => {
let service;
try {
service = Cc["@mozilla.org/accessibilityService;1"].getService(
Ci.nsIAccessibilityService);
} catch (e) {
logger.warn("Accessibility module is not present");
} finally {
return service;
}
});
this.EXPORTED_SYMBOLS = ["accessibility"];
/**
* Number of attempts to get an accessible object for an element.
* We attempt more than once because accessible tree can be out of sync
* with the DOM tree for a short period of time.
*/
const GET_ACCESSIBLE_ATTEMPTS = 100;
/**
* An interval between attempts to retrieve an accessible object for an
* element.
*/
const GET_ACCESSIBLE_ATTEMPT_INTERVAL = 10;
this.accessibility = {
get service() {
return service;
}
};
/**
* Accessible states used to check element"s state from the accessiblity API
* perspective.
* Note: if gecko is built with --disable-accessibility, the interfaces are not
* defined. This is why we use getters instead to be able to use these
* statically.
*/
accessibility.State = {
get Unavailable() {
return Ci.nsIAccessibleStates.STATE_UNAVAILABLE;
},
get Focusable() {
return Ci.nsIAccessibleStates.STATE_FOCUSABLE;
},
get Selectable() {
return Ci.nsIAccessibleStates.STATE_SELECTABLE;
},
get Selected() {
return Ci.nsIAccessibleStates.STATE_SELECTED;
}
};
/**
* Accessible object roles that support some action.
*/
accessibility.ActionableRoles = new Set([
"checkbutton",
"check menu item",
"check rich option",
"combobox",
"combobox option",
"entry",
"key",
"link",
"listbox option",
"listbox rich option",
"menuitem",
"option",
"outlineitem",
"pagetab",
"pushbutton",
"radiobutton",
"radio menu item",
"rowheader",
"slider",
"spinbutton",
"switch",
]);
/**
* Factory function that constructs a new {@code accessibility.Checks}
* object with enforced strictness or not.
*/
accessibility.get = function (strict = false) {
return new accessibility.Checks(!!strict);
};
/**
* Component responsible for interacting with platform accessibility
* API.
*
* Its methods serve as wrappers for testing content and chrome
* accessibility as well as accessibility of user interactions.
*/
accessibility.Checks = class {
/**
* @param {boolean} strict
* Flag indicating whether the accessibility issue should be logged
* or cause an error to be thrown. Default is to log to stdout.
*/
constructor(strict) {
this.strict = strict;
}
/**
* Get an accessible object for an element.
*
* @param {DOMElement|XULElement} element
* Element to get the accessible object for.
* @param {boolean=} mustHaveAccessible
* Flag indicating that the element must have an accessible object.
* Defaults to not require this.
*
* @return {Promise: nsIAccessible}
* Promise with an accessibility object for the given element.
*/
getAccessible(element, mustHaveAccessible = false) {
if (!this.strict) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
if (!accessibility.service) {
reject();
return;
}
let acc = accessibility.service.getAccessibleFor(element);
if (acc || !mustHaveAccessible) {
// if accessible object is found, return it;
// if it is not required, also resolve
resolve(acc);
} else {
// if we require an accessible object, we need to poll for it
// because accessible tree might be
// out of sync with DOM tree for a short time
let attempts = GET_ACCESSIBLE_ATTEMPTS;
let intervalId = setInterval(() => {
let acc = accessibility.service.getAccessibleFor(element);
if (acc || --attempts <= 0) {
clearInterval(intervalId);
if (acc) {
resolve(acc);
} else {
reject();
}
}
}, GET_ACCESSIBLE_ATTEMPT_INTERVAL);
}
}).catch(() => this.error(
"Element does not have an accessible object", element));
};
/**
* Test if the accessible has a role that supports some arbitrary
* action.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if an actionable role is found on the accessible, false
* otherwise.
*/
isActionableRole(accessible) {
return accessibility.ActionableRoles.has(
accessibility.service.getStringRole(accessible.role));
}
/**
* Test if an accessible has at least one action that it supports.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if the accessible has at least one supported action,
* false otherwise.
*/
hasActionCount(accessible) {
return accessible.actionCount > 0;
}
/**
* Test if an accessible has a valid name.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if the accessible has a non-empty valid name, or false if
* this is not the case.
*/
hasValidName(accessible) {
return accessible.name && accessible.name.trim();
}
/**
* Test if an accessible has a {@code hidden} attribute.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if the accesible object has a {@code hidden} attribute,
* false otherwise.
*/
hasHiddenAttribute(accessible) {
let hidden = false;
try {
hidden = accessible.attributes.getStringProperty("hidden");
} finally {
// if the property is missing, error will be thrown
return hidden && hidden === "true";
}
}
/**
* Verify if an accessible has a given state.
* Test if an accessible has a given state.
*
* @param {nsIAccessible} accessible
* Accessible object to test.
* @param {number} stateToMatch
* State to match.
*
* @return {boolean}
* True if |accessible| has |stateToMatch|, false otherwise.
*/
matchState(accessible, stateToMatch) {
let state = {};
accessible.getState(state, {});
return !!(state.value & stateToMatch);
}
/**
* Test if an accessible is hidden from the user.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if element is hidden from user, false otherwise.
*/
isHidden(accessible) {
while (accessible) {
if (this.hasHiddenAttribute(accessible)) {
return true;
}
accessible = accessible.parent;
}
return false;
}
/**
* Test if the element's visible state corresponds to its accessibility
* API visibility.
*
* @param {nsIAccessible} accessible
* Accessible object.
* @param {DOMElement|XULElement} element
* Element associated with |accessible|.
* @param {boolean} visible
* Visibility state of |element|.
*
* @throws ElementNotAccessibleError
* If |element|'s visibility state does not correspond to
* |accessible|'s.
*/
assertVisible(accessible, element, visible) {
if (!accessible) {
return;
}
let hiddenAccessibility = this.isHidden(accessible);
let message;
if (visible && hiddenAccessibility) {
message = "Element is not currently visible via the accessibility API " +
"and may not be manipulated by it";
} else if (!visible && !hiddenAccessibility) {
message = "Element is currently only visible via the accessibility API " +
"and can be manipulated by it";
}
this.error(message, element);
}
/**
* Test if the element's unavailable accessibility state matches the
* enabled state.
*
* @param {nsIAccessible} accessible
* Accessible object.
* @param {DOMElement|XULElement} element
* Element associated with |accessible|.
* @param {boolean} enabled
* Enabled state of |element|.
*
* @throws ElementNotAccessibleError
* If |element|'s enabled state does not match |accessible|'s.
*/
assertEnabled(accessible, element, enabled) {
if (!accessible) {
return;
}
let win = element.ownerDocument.defaultView;
let disabledAccessibility = this.matchState(
accessible, accessibility.State.Unavailable);
let explorable = win.getComputedStyle(element)
.getPropertyValue("pointer-events") !== "none";
let message;
if (!explorable && !disabledAccessibility) {
message = "Element is enabled but is not explorable via the " +
"accessibility API";
} else if (enabled && disabledAccessibility) {
message = "Element is enabled but disabled via the accessibility API";
} else if (!enabled && !disabledAccessibility) {
message = "Element is disabled but enabled via the accessibility API";
}
this.error(message, element);
}
/**
* Test if it is possible to activate an element with the accessibility
* API.
*
* @param {nsIAccessible} accessible
* Accessible object.
* @param {DOMElement|XULElement} element
* Element associated with |accessible|.
*
* @throws ElementNotAccessibleError
* If it is impossible to activate |element| with |accessible|.
*/
assertActionable(accessible, element) {
if (!accessible) {
return;
}
let message;
if (!this.hasActionCount(accessible)) {
message = "Element does not support any accessible actions";
} else if (!this.isActionableRole(accessible)) {
message = "Element does not have a correct accessibility role " +
"and may not be manipulated via the accessibility API";
} else if (!this.hasValidName(accessible)) {
message = "Element is missing an accessible name";
} else if (!this.matchState(accessible, accessibility.State.Focusable)) {
message = "Element is not focusable via the accessibility API";
}
this.error(message, element);
}
/**
* Test that an element's selected state corresponds to its
* accessibility API selected state.
*
* @param {nsIAccessible} accessible
* Accessible object.
* @param {DOMElement|XULElement}
* Element associated with |accessible|.
* @param {boolean} selected
* The |element|s selected state.
*
* @throws ElementNotAccessibleError
* If |element|'s selected state does not correspond to
* |accessible|'s.
*/
assertSelected(accessible, element, selected) {
if (!accessible) {
return;
}
// element is not selectable via the accessibility API
if (!this.matchState(accessible, accessibility.State.Selectable)) {
return;
}
let selectedAccessibility = this.matchState(accessible, accessibility.State.Selected);
let message;
if (selected && !selectedAccessibility) {
message = "Element is selected but not selected via the accessibility API";
} else if (!selected && selectedAccessibility) {
message = "Element is not selected but selected via the accessibility API";
}
this.error(message, element);
}
/**
* Throw an error if strict accessibility checks are enforced and log
* the error to the log.
*
* @param {string} message
* @param {DOMElement|XULElement} element
* Element that caused an error.
*
* @throws ElementNotAccessibleError
* If |strict| is true.
*/
error(message, element) {
if (!message || !this.strict) {
return;
}
if (element) {
let {id, tagName, className} = element;
message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
}
throw new ElementNotAccessibleError(message);
}
};

1348
testing/marionette/action.js Normal file

File diff suppressed because it is too large Load diff

104
testing/marionette/addon.js Normal file
View file

@ -0,0 +1,104 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("chrome://marionette/content/error.js");
this.EXPORTED_SYMBOLS = ["addon"];
this.addon = {};
// from https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonManager#AddonInstall_errors
addon.Errors = {
[-1]: "ERROR_NETWORK_FAILURE: A network error occured.",
[-2]: "ERROR_INCORECT_HASH: The downloaded file did not match the expected hash.",
[-3]: "ERROR_CORRUPT_FILE: The file appears to be corrupt.",
[-4]: "ERROR_FILE_ACCESS: There was an error accessing the filesystem.",
[-5]: "ERROR_SIGNEDSTATE_REQUIRED: The addon must be signed and isn't.",
};
function lookupError(code) {
let msg = addon.Errors[code];
return new UnknownError(msg);
}
/**
* Install a Firefox addon.
*
* If the addon is restartless, it can be used right away. Otherwise a
* restart is required.
*
* Temporary addons will automatically be uninstalled on shutdown and
* do not need to be signed, though they must be restartless.
*
* @param {string} path
* Full path to the extension package archive.
* @param {boolean=} temporary
* True to install the addon temporarily, false (default) otherwise.
*
* @return {Promise: string}
* Addon ID.
*
* @throws {UnknownError}
* If there is a problem installing the addon.
*/
addon.install = function (path, temporary = false) {
return new Promise((resolve, reject) => {
let file = new FileUtils.File(path);
let listener = {
onInstallEnded: function (install, addon) {
resolve(addon.id);
},
onInstallFailed: function (install) {
reject(lookupError(install.error));
},
onInstalled: function (addon) {
AddonManager.removeAddonListener(listener);
resolve(addon.id);
}
};
if (!temporary) {
AddonManager.getInstallForFile(file, function (aInstall) {
if (aInstall.error !== 0) {
reject(lookupError(aInstall.error));
}
aInstall.addListener(listener);
aInstall.install();
});
} else {
AddonManager.addAddonListener(listener);
AddonManager.installTemporaryAddon(file);
}
});
};
/**
* Uninstall a Firefox addon.
*
* If the addon is restartless it will be uninstalled right away.
* Otherwise, Firefox must be restarted for the change to take effect.
*
* @param {string} id
* ID of the addon to uninstall.
*
* @return {Promise}
*/
addon.uninstall = function (id) {
return new Promise(resolve => {
AddonManager.getAddonByID(id, function (addon) {
addon.uninstall();
resolve();
});
});
};

View file

@ -0,0 +1,322 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("chrome://marionette/content/error.js");
this.EXPORTED_SYMBOLS = ["assert"];
const isFennec = () => AppConstants.platform == "android";
const isB2G = () => AppConstants.MOZ_B2G;
const isFirefox = () => Services.appinfo.name == "Firefox";
/** Shorthands for common assertions made in Marionette. */
this.assert = {};
/**
* Asserts that Marionette has a session.
*
* @param {GeckoDriver} driver
* Marionette driver instance.
* @param {string=} msg
* Custom error message.
*
* @return {string}
* Session ID.
*
* @throws {InvalidSessionIDError}
* If |driver| does not have a session ID.
*/
assert.session = function (driver, msg = "") {
assert.that(sessionID => sessionID,
msg, InvalidSessionIDError)(driver.sessionId);
return driver.sessionId;
};
/**
* Asserts that the current browser is Firefox Desktop.
*
* @param {string=} msg
* Custom error message.
*
* @throws {UnsupportedOperationError}
* If current browser is not Firefox.
*/
assert.firefox = function (msg = "") {
msg = msg || "Only supported in Firefox";
assert.that(isFirefox, msg, UnsupportedOperationError)();
};
/**
* Asserts that the current browser is Fennec, or Firefox for Android.
*
* @param {string=} msg
* Custom error message.
*
* @throws {UnsupportedOperationError}
* If current browser is not Fennec.
*/
assert.fennec = function (msg = "") {
msg = msg || "Only supported in Fennec";
assert.that(isFennec, msg, UnsupportedOperationError)();
};
/**
* Asserts that the current browser is B2G.
*
* @param {string=} msg
* Custom error message.
*
* @throws {UnsupportedOperationError}
* If the current browser is not B2G.
*/
assert.b2g = function (msg = "") {
msg = msg || "Only supported in B2G";
assert.that(isB2G, msg, UnsupportedOperationError)();
};
/**
* Asserts that the current |context| is content.
*
* @param {string} context
* Context to test.
* @param {string=} msg
* Custom error message.
*
* @return {string}
* |context| is returned unaltered.
*
* @throws {UnsupportedOperationError}
* If |context| is not content.
*/
assert.content = function (context, msg = "") {
msg = msg || "Only supported in content context";
assert.that(c => c.toString() == "content", msg, UnsupportedOperationError)(context);
};
/**
* Asserts that the current browser is a mobile browser, that is either
* B2G or Fennec.
*
* @param {string=} msg
* Custom error message.
*
* @throws {UnsupportedOperationError}
* If the current browser is not B2G or Fennec.
*/
assert.mobile = function (msg = "") {
msg = msg || "Only supported in Fennec or B2G";
assert.that(() => isFennec() || isB2G(), msg, UnsupportedOperationError)();
};
/**
* Asserts that |obj| is defined.
*
* @param {?} obj
* Value to test.
* @param {string=} msg
* Custom error message.
*
* @return {?}
* |obj| is returned unaltered.
*
* @throws {InvalidArgumentError}
* If |obj| is not defined.
*/
assert.defined = function (obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be defined`;
return assert.that(o => typeof o != "undefined", msg)(obj);
};
/**
* Asserts that |obj| is a finite number.
*
* @param {?} obj
* Value to test.
* @param {string=} msg
* Custom error message.
*
* @return {number}
* |obj| is returned unaltered.
*
* @throws {InvalidArgumentError}
* If |obj| is not a number.
*/
assert.number = function (obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be finite number`;
return assert.that(Number.isFinite, msg)(obj);
};
/**
* Asserts that |obj| is an integer.
*
* @param {?} obj
* Value to test.
* @param {string=} msg
* Custom error message.
*
* @return {number}
* |obj| is returned unaltered.
*
* @throws {InvalidArgumentError}
* If |obj| is not an integer.
*/
assert.integer = function (obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be an integer`;
return assert.that(Number.isInteger, msg)(obj);
};
/**
* Asserts that |obj| is a positive integer.
*
* @param {?} obj
* Value to test.
* @param {string=} msg
* Custom error message.
*
* @return {number}
* |obj| is returned unaltered.
*
* @throws {InvalidArgumentError}
* If |obj| is not a positive integer.
*/
assert.positiveInteger = function (obj, msg = "") {
assert.integer(obj, msg);
msg = msg || error.pprint`Expected ${obj} to be >= 0`;
return assert.that(n => n >= 0, msg)(obj);
};
/**
* Asserts that |obj| is a boolean.
*
* @param {?} obj
* Value to test.
* @param {string=} msg
* Custom error message.
*
* @return {boolean}
* |obj| is returned unaltered.
*
* @throws {InvalidArgumentError}
* If |obj| is not a boolean.
*/
assert.boolean = function (obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be boolean`;
return assert.that(b => typeof b == "boolean", msg)(obj);
};
/**
* Asserts that |obj| is a string.
*
* @param {?} obj
* Value to test.
* @param {string=} msg
* Custom error message.
*
* @return {string}
* |obj| is returned unaltered.
*
* @throws {InvalidArgumentError}
* If |obj| is not a string.
*/
assert.string = function (obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be a string`;
return assert.that(s => typeof s == "string", msg)(obj);
};
/**
* Asserts that |obj| is an object.
*
* @param {?} obj
* Value to test.
* @param {string=} msg
* Custom error message.
*
* @return {Object}
* |obj| is returned unaltered.
*
* @throws {InvalidArgumentError}
* If |obj| is not an object.
*/
assert.object = function (obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be an object`;
return assert.that(o =>
Object.prototype.toString.call(o) == "[object Object]", msg)(obj);
};
/**
* Asserts that |prop| is in |obj|.
*
* @param {?} prop
* Own property to test if is in |obj|.
* @param {?} obj
* Object.
* @param {string=} msg
* Custom error message.
*
* @return {?}
* Value of |obj|'s own property |prop|.
*
* @throws {InvalidArgumentError}
* If |prop| is not in |obj|, or |obj| is not an object.
*/
assert.in = function (prop, obj, msg = "") {
assert.object(obj, msg);
msg = msg || error.pprint`Expected ${prop} in ${obj}`;
assert.that(p => obj.hasOwnProperty(p), msg)(prop);
return obj[prop];
};
/**
* Asserts that |obj| is an Array.
*
* @param {?} obj
* Value to test.
* @param {string=} msg
* Custom error message.
*
* @return {Object}
* |obj| is returned unaltered.
*
* @throws {InvalidArgumentError}
* If |obj| is not an Array.
*/
assert.array = function (obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be an Array`;
return assert.that(Array.isArray, msg)(obj);
};
/**
* Returns a function that is used to assert the |predicate|.
*
* @param {function(?): boolean} predicate
* Evaluated on calling the return value of this function. If its
* return value of the inner function is false, |error| is thrown
* with |message|.
* @param {string=} message
* Custom error message.
* @param {Error=} error
* Custom error type by its class.
*
* @return {function(?): ?}
* Function that takes and returns the passed in value unaltered, and
* which may throw |error| with |message| if |predicate| evaluates
* to false.
*/
assert.that = function (
predicate, message = "", error = InvalidArgumentError) {
return obj => {
if (!predicate(obj)) {
throw new error(message);
}
return obj;
};
};

192
testing/marionette/atom.js Normal file
View file

@ -0,0 +1,192 @@
// Copyright 2011-2014 Software Freedom Conservancy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
this.EXPORTED_SYMBOLS = ["atom"];
this.atom = {};
atom.clearElement = function (element, window){return function(){function g(a){throw a;}var h=void 0,i=!0,k=null,l=!1;function n(a){return function(){return this[a]}}function o(a){return function(){return a}}var p,q=this;
function aa(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function r(a){return a!==h}function ba(a){var b=aa(a);return"array"==b||"object"==b&&"number"==typeof a.length}function t(a){return"string"==typeof a}function w(a){return"function"==aa(a)}function ca(a){a=aa(a);return"object"==a||"array"==a||"function"==a}var da="closure_uid_"+Math.floor(2147483648*Math.random()).toString(36),ea=0,fa=Date.now||function(){return+new Date};
function x(a,b){function c(){}c.prototype=b.prototype;a.$=b.prototype;a.prototype=new c};function ga(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function ha(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}function ia(a){if(!ja.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(ka,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(la,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(ma,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(na,"&quot;"));return a}var ka=/&/g,la=/</g,ma=/>/g,na=/\"/g,ja=/[&<>\"]/;
function oa(a,b){for(var c=0,d=ha(""+a).split("."),e=ha(""+b).split("."),f=Math.max(d.length,e.length),j=0;0==c&&j<f;j++){var m=d[j]||"",s=e[j]||"",O=RegExp("(\\d*)(\\D*)","g"),E=RegExp("(\\d*)(\\D*)","g");do{var u=O.exec(m)||["","",""],v=E.exec(s)||["","",""];if(0==u[0].length&&0==v[0].length)break;c=((0==u[1].length?0:parseInt(u[1],10))<(0==v[1].length?0:parseInt(v[1],10))?-1:(0==u[1].length?0:parseInt(u[1],10))>(0==v[1].length?0:parseInt(v[1],10))?1:0)||((0==u[2].length)<(0==v[2].length)?-1:(0==
u[2].length)>(0==v[2].length)?1:0)||(u[2]<v[2]?-1:u[2]>v[2]?1:0)}while(0==c)}return c}var pa=2147483648*Math.random()|0,qa={};function ra(a){return qa[a]||(qa[a]=(""+a).replace(/\-([a-z])/g,function (a,c){return c.toUpperCase()}))};var sa,ta;function ua(){return q.navigator?q.navigator.userAgent:k}var va,wa=q.navigator;va=wa&&wa.platform||"";sa=-1!=va.indexOf("Mac");ta=-1!=va.indexOf("Win");var xa=-1!=va.indexOf("Linux"),ya,za="",Aa=/rv\:([^\);]+)(\)|;)/.exec(ua());ya=za=Aa?Aa[1]:"";var Ba={};var Ca=window;function Da(a,b){for(var c in a)b.call(h,a[c],c,a)}function Ea(a){var b=[],c=0,d;for(d in a)b[c++]=a[d];return b};function y(a,b){this.code=a;this.message=b||"";this.name=Fa[a]||Fa[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}x(y,Error);
var Fa={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
y.prototype.toString=function(){return"["+this.name+"] "+this.message};function Ga(a){this.stack=Error().stack||"";a&&(this.message=""+a)}x(Ga,Error);Ga.prototype.name="CustomError";function Ha(a,b){b.unshift(a);Ga.call(this,ga.apply(k,b));b.shift()}x(Ha,Ga);Ha.prototype.name="AssertionError";function Ia(a,b,c){if(!a){var d=Array.prototype.slice.call(arguments,2),e="Assertion failed";if(b)var e=e+(": "+b),f=d;g(new Ha(""+e,f||[]))}}function Ja(a,b){g(new Ha("Failure"+(a?": "+a:""),Array.prototype.slice.call(arguments,1)))};function z(a){return a[a.length-1]}var Ka=Array.prototype;function A(a,b){if(t(a))return!t(b)||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1}function La(a,b){for(var c=a.length,d=t(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(h,d[e],e,a)}function Ma(a,b){for(var c=a.length,d=Array(c),e=t(a)?a.split(""):a,f=0;f<c;f++)f in e&&(d[f]=b.call(h,e[f],f,a));return d}
function Na(a,b,c){for(var d=a.length,e=t(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return i;return l}function Oa(a,b,c){for(var d=a.length,e=t(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&!b.call(c,e[f],f,a))return l;return i}function Pa(a,b){var c;a:{c=a.length;for(var d=t(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(h,d[e],e,a)){c=e;break a}c=-1}return 0>c?k:t(a)?a.charAt(c):a[c]}function Qa(a){return Ka.concat.apply(Ka,arguments)}
function Ra(a){if("array"==aa(a))return Qa(a);for(var b=[],c=0,d=a.length;c<d;c++)b[c]=a[c];return b}function Sa(a,b,c){Ia(a.length!=k);return 2>=arguments.length?Ka.slice.call(a,b):Ka.slice.call(a,b,c)};var Ta;Ba["1.9.1"]||(Ba["1.9.1"]=0<=oa(ya,"1.9.1"));function Ua(a,b){var c;c=(c=a.className)&&"function"==typeof c.split?c.split(/\s+/):[];var d=Sa(arguments,1),e;e=c;for(var f=0,j=0;j<d.length;j++)0<=A(e,d[j])||(e.push(d[j]),f++);e=f==d.length;a.className=c.join(" ");return e};function B(a,b){this.x=r(a)?a:0;this.y=r(b)?b:0}B.prototype.toString=function(){return"("+this.x+", "+this.y+")"};function Va(a,b){this.width=a;this.height=b}Va.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};Va.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};Va.prototype.scale=function (a){this.width*=a;this.height*=a;return this};var C=3;function Wa(a){return a?new Xa(D(a)):Ta||(Ta=new Xa)}function Ya(a,b){Da(b,function (b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in Za?a.setAttribute(Za[d],b):0==d.lastIndexOf("aria-",0)?a.setAttribute(d,b):a[d]=b})}var Za={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",rowspan:"rowSpan",valign:"vAlign",height:"height",width:"width",usemap:"useMap",frameborder:"frameBorder",maxlength:"maxLength",type:"type"};
function F(a){return a?a.parentWindow||a.defaultView:window}function $a(a,b,c){function d(c){c&&b.appendChild(t(c)?a.createTextNode(c):c)}for(var e=2;e<c.length;e++){var f=c[e];ba(f)&&!(ca(f)&&0<f.nodeType)?La(ab(f)?Ra(f):f,d):d(f)}}function bb(a){return a&&a.parentNode?a.parentNode.removeChild(a):k}
function G(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}
function cb(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?db(a,b):!c&&G(e,b)?-1*eb(a,b):!d&&G(f,a)?eb(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=D(a);c=d.createRange();c.selectNode(a);c.collapse(i);d=d.createRange();d.selectNode(b);d.collapse(i);
return c.compareBoundaryPoints(q.Range.START_TO_END,d)}function eb(a,b){var c=a.parentNode;if(c==b)return-1;for(var d=b;d.parentNode!=c;)d=d.parentNode;return db(d,a)}function db(a,b){for(var c=b;c=c.previousSibling;)if(c==a)return-1;return 1}
function fb(a){var b,c=arguments.length;if(c){if(1==c)return arguments[0]}else return k;var d=[],e=Infinity;for(b=0;b<c;b++){for(var f=[],j=arguments[b];j;)f.unshift(j),j=j.parentNode;d.push(f);e=Math.min(e,f.length)}f=k;for(b=0;b<e;b++){for(var j=d[0][b],m=1;m<c;m++)if(j!=d[m][b])return f;f=j}return f}function D(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function gb(a,b){var c=[];return hb(a,b,c,i)?c[0]:h}
function hb(a,b,c,d){if(a!=k)for(a=a.firstChild;a;){if(b(a)&&(c.push(a),d)||hb(a,b,c,d))return i;a=a.nextSibling}return l}var ib={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},jb={IMG:" ",BR:"\n"};function kb(a,b,c){if(!(a.nodeName in ib))if(a.nodeType==C)c?b.push((""+a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in jb)b.push(jb[a.nodeName]);else for(a=a.firstChild;a;)kb(a,b,c),a=a.nextSibling}
function ab(a){if(a&&"number"==typeof a.length){if(ca(a))return"function"==typeof a.item||"string"==typeof a.item;if(w(a))return"function"==typeof a.item}return l}function lb(a,b){for(var a=a.parentNode,c=0;a;){if(b(a))return a;a=a.parentNode;c++}return k}function Xa(a){this.v=a||q.document||document}p=Xa.prototype;p.ea=n("v");p.z=function (a){return t(a)?this.v.getElementById(a):a};
p.da=function (a,b,c){var d=this.v,e=arguments,f=e[1],j=d.createElement(e[0]);f&&(t(f)?j.className=f:"array"==aa(f)?Ua.apply(k,[j].concat(f)):Ya(j,f));2<e.length&&$a(d,j,e);return j};p.createElement=function (a){return this.v.createElement(a)};p.createTextNode=function (a){return this.v.createTextNode(a)};p.qa=function(){return this.v.parentWindow||this.v.defaultView};
function mb(a){var b=a.v,a="CSS1Compat"==b.compatMode?b.documentElement:b.body,b=b.parentWindow||b.defaultView;return new B(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)}p.appendChild=function (a,b){a.appendChild(b)};p.removeNode=bb;p.contains=G;var H={};H.ya=function(){var a={Oa:"http://www.w3.org/2000/svg"};return function (b){return a[b]||k}}();H.ma=function (a,b,c){var d=D(a);if(!d.implementation.hasFeature("XPath","3.0"))return k;try{var e=d.createNSResolver?d.createNSResolver(d.documentElement):H.ya;return d.evaluate(b,a,e,c,k)}catch(f){"NS_ERROR_ILLEGAL_VALUE"!=f.name&&g(new y(32,"Unable to locate an element with the xpath expression "+b+" because of the following error:\n"+f))}};
H.ka=function (a,b){(!a||1!=a.nodeType)&&g(new y(32,'The result of the xpath expression "'+b+'" is: '+a+". It should be an element."))};H.Ia=function (a,b){var c=function(){var c=H.ma(b,a,9);return c?c.singleNodeValue||k:b.selectSingleNode?(c=D(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectSingleNode(a)):k}();c===k||H.ka(c,a);return c};
H.Na=function (a,b){var c=function(){var c=H.ma(b,a,7);if(c){for(var e=c.snapshotLength,f=[],j=0;j<e;++j)f.push(c.snapshotItem(j));return f}return b.selectNodes?(c=D(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectNodes(a)):[]}();La(c,function (b){H.ka(b,a)});return c};var nb,ob="",pb=/Firefox\/([0-9.]+)/.exec(ua());nb=ob=pb?pb[2]||pb[1]:"";var qb=k,rb=function(){var a=q.Components;if(!a)return l;try{if(!a.classes)return l}catch(b){return l}var c=a.classes,a=a.interfaces,d=c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator),e=c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo).version;qb=function (a){return 0<=d.Ka(e,""+a)};return i}();var I="StopIteration"in q?q.StopIteration:Error("StopIteration");function J(){}J.prototype.next=function(){g(I)};J.prototype.r=function(){return this};function sb(a){if(a instanceof J)return a;if("function"==typeof a.r)return a.r(l);if(ba(a)){var b=0,c=new J;c.next=function(){for(;;){b>=a.length&&g(I);if(b in a)return a[b++];b++}};return c}g(Error("Not implemented"))};function K(a,b,c,d,e){this.o=!!b;a&&L(this,a,d);this.depth=e!=h?e:this.q||0;this.o&&(this.depth*=-1);this.za=!c}x(K,J);p=K.prototype;p.p=k;p.q=0;p.ha=l;function L(a,b,c,d){if(a.p=b)a.q="number"==typeof c?c:1!=a.p.nodeType?0:a.o?-1:1;"number"==typeof d&&(a.depth=d)}
p.next=function(){var a;if(this.ha){(!this.p||this.za&&0==this.depth)&&g(I);a=this.p;var b=this.o?-1:1;if(this.q==b){var c=this.o?a.lastChild:a.firstChild;c?L(this,c):L(this,a,-1*b)}else(c=this.o?a.previousSibling:a.nextSibling)?L(this,c):L(this,a.parentNode,-1*b);this.depth+=this.q*(this.o?-1:1)}else this.ha=i;(a=this.p)||g(I);return a};
p.splice=function (a){var b=this.p,c=this.o?1:-1;this.q==c&&(this.q=-1*c,this.depth+=this.q*(this.o?-1:1));this.o=!this.o;K.prototype.next.call(this);this.o=!this.o;for(var c=ba(arguments[0])?arguments[0]:arguments,d=c.length-1;0<=d;d--)b.parentNode&&b.parentNode.insertBefore(c[d],b.nextSibling);bb(b)};function tb(a,b,c,d){K.call(this,a,b,c,k,d)}x(tb,K);tb.prototype.next=function(){do tb.$.next.call(this);while(-1==this.q);return this.p};function ub(a,b){var c=D(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,k))?c[b]||c.getPropertyValue(b):""}function vb(a,b){return ub(a,b)||(a.currentStyle?a.currentStyle[b]:k)||a.style&&a.style[b]}
function wb(a){for(var b=D(a),c=vb(a,"position"),d="fixed"==c||"absolute"==c,a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=vb(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return k}
function xb(a){var b=new B;if(1==a.nodeType)if(a.getBoundingClientRect)a=a.getBoundingClientRect(),b.x=a.left,b.y=a.top;else{var c=mb(Wa(a));var d,e=D(a),f=vb(a,"position"),j=e.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==f&&(d=e.getBoxObjectFor(a))&&(0>d.screenX||0>d.screenY),f=new B(0,0),m=(e?9==e.nodeType?e:D(e):document).documentElement;if(a!=m)if(a.getBoundingClientRect)d=a.getBoundingClientRect(),a=mb(Wa(e)),f.x=d.left+a.x,f.y=d.top+a.y;else if(e.getBoxObjectFor&&!j)d=e.getBoxObjectFor(a),
a=e.getBoxObjectFor(m),f.x=d.screenX-a.screenX,f.y=d.screenY-a.screenY;else{d=a;do f.x+=d.offsetLeft,f.y+=d.offsetTop,d!=a&&(f.x+=d.clientLeft||0,f.y+=d.clientTop||0),d=d.offsetParent;while(d&&d!=a);for(d=a;(d=wb(d))&&d!=e.body&&d!=m;)f.x-=d.scrollLeft,f.y-=d.scrollTop}b.x=f.x-c.x;b.y=f.y-c.y}else c=w(a.pa),d=a,a.targetTouches?d=a.targetTouches[0]:c&&a.pa().targetTouches&&(d=a.pa().targetTouches[0]),b.x=d.clientX,b.y=d.clientY;return b}
function yb(a){var b=a.offsetWidth,c=a.offsetHeight;return!r(b)&&a.getBoundingClientRect?(a=a.getBoundingClientRect(),new Va(a.right-a.left,a.bottom-a.top)):new Va(b,c)};function M(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}var zb={"class":"className",readonly:"readOnly"},Ab=["checked","disabled","draggable","hidden"];function Bb(a,b){var c=zb[b]||b,d=a[c];if(!r(d)&&0<=A(Ab,c))return l;if(c="value"==b)if(c=M(a,"OPTION")){var e;c=b.toLowerCase();if(a.hasAttribute)e=a.hasAttribute(c);else try{e=a.attributes[c].specified}catch(f){e=l}c=!e}c&&(d=[],kb(a,d,l),d=d.join(""));return d}
var Cb="async,autofocus,autoplay,checked,compact,complete,controls,declare,defaultchecked,defaultselected,defer,disabled,draggable,ended,formnovalidate,hidden,indeterminate,iscontenteditable,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,paused,pubdate,readonly,required,reversed,scoped,seamless,seeking,selected,spellcheck,truespeed,willvalidate".split(","),Db="BUTTON,INPUT,OPTGROUP,OPTION,SELECT,TEXTAREA".split(",");
function Eb(a){var b=a.tagName.toUpperCase();return!(0<=A(Db,b))?i:Bb(a,"disabled")?l:a.parentNode&&1==a.parentNode.nodeType&&"OPTGROUP"==b||"OPTION"==b?Eb(a.parentNode):i}var Fb="text,search,tel,url,email,password,number".split(",");function Gb(a){return M(a,"TEXTAREA")?i:M(a,"INPUT")?0<=A(Fb,a.type.toLowerCase()):Hb(a)?i:l}
function Hb(a){function b(a){return"inherit"==a.contentEditable?(a=Ib(a))?b(a):l:"true"==a.contentEditable}return!r(a.contentEditable)?l:r(a.isContentEditable)?a.isContentEditable:b(a)}function Ib(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return M(a)?a:k}function Jb(a,b){b=ra(b);return ub(a,b)||Kb(a,b)}
function Kb(a,b){var c=a.currentStyle||a.style,d=c[b];!r(d)&&w(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?r(d)?d:k:(c=Ib(a))?Kb(c,b):k}function Lb(a){if(w(a.getBBox))try{var b=a.getBBox();if(b)return b}catch(c){}if("none"!=vb(a,"display"))a=yb(a);else{var b=a.style,d=b.display,e=b.visibility,f=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";a=yb(a);b.display=d;b.position=f;b.visibility=e}return a}
function Mb(a,b){function c(a){if("none"==Jb(a,"display"))return l;a=Ib(a);return!a||c(a)}function d(a){var b=Lb(a);return 0<b.height&&0<b.width?i:Na(a.childNodes,function (a){return a.nodeType==C||M(a)&&d(a)})}function e(a){var b=Ib(a);if(b&&"hidden"==Jb(b,"overflow")){var c=Lb(b),d=xb(b),a=xb(a);return d.x+c.width<a.x||d.y+c.height<a.y?l:e(b)}return i}M(a)||g(Error("Argument to isShown must be of type Element"));if(M(a,"OPTION")||M(a,"OPTGROUP")){var f=lb(a,function (a){return M(a,"SELECT")});return!!f&&
Mb(f,i)}if(M(a,"MAP")){if(!a.name)return l;f=D(a);f=f.evaluate?H.Ia('/descendant::*[@usemap = "#'+a.name+'"]',f):gb(f,function (b){var c;if(c=M(b))8==b.nodeType?b=k:(c="usemap","style"==c?(b=ha(b.style.cssText).toLowerCase(),b=";"==b.charAt(b.length-1)?b:b+";"):(b=b.getAttributeNode(c),b=!b?k:0<=A(Cb,c)?"true":b.specified?b.value:k)),c=b=="#"+a.name;return c});return!!f&&Mb(f,b)}return M(a,"AREA")?(f=lb(a,function (a){return M(a,"MAP")}),!!f&&Mb(f,b)):M(a,"INPUT")&&"hidden"==a.type.toLowerCase()||M(a,
"NOSCRIPT")||"hidden"==Jb(a,"visibility")||!c(a)||!b&&0==Nb(a)||!d(a)||!e(a)?l:i}function Nb(a){var b=1,c=Jb(a,"opacity");c&&(b=Number(c));(a=Ib(a))&&(b*=Nb(a));return b};function N(){this.w=Ca.document.documentElement;this.ua=k;var a=D(this.w).activeElement;a&&Ob(this,a)}N.prototype.z=n("w");function Ob(a,b){a.w=b;a.ua=M(b,"OPTION")?lb(b,function (a){return M(a,"SELECT")}):k}
function Pb(a,b,c,d,e,f){function j(a,c){var d={identifier:a,screenX:c.x,screenY:c.y,clientX:c.x,clientY:c.y,pageX:c.x,pageY:c.y};m.changedTouches.push(d);if(b==Qb||b==Rb)m.touches.push(d),m.targetTouches.push(d)}var m={touches:[],targetTouches:[],changedTouches:[],altKey:l,ctrlKey:l,shiftKey:l,metaKey:l,relatedTarget:k,scale:0,rotation:0};j(c,d);r(e)&&j(e,f);Sb(a.w,b,m)}rb&&rb&&(rb?qb(4):oa(nb,4));rb&&(rb?qb(4):oa(nb,4));function P(a,b,c){this.J=a;this.S=b;this.T=c}P.prototype.create=function (a){a=D(a).createEvent("HTMLEvents");a.initEvent(this.J,this.S,this.T);return a};P.prototype.toString=n("J");function Q(a,b,c){P.call(this,a,b,c)}x(Q,P);
Q.prototype.create=function (a,b){var c=D(a),d=F(c),c=c.createEvent("MouseEvents"),e=1;this==Tb&&(e=b.wheelDelta/-40);this==Ub&&(e=b.wheelDelta);c.initMouseEvent(this.J,this.S,this.T,d,e,0,0,b.clientX,b.clientY,b.ctrlKey,b.altKey,b.shiftKey,b.metaKey,b.button,b.relatedTarget);return c};function Vb(a,b,c){P.call(this,a,b,c)}x(Vb,P);
Vb.prototype.create=function (a,b){var c=D(a),d=F(c),e=b.charCode?0:b.keyCode,c=c.createEvent("KeyboardEvent");c.initKeyEvent(this.J,this.S,this.T,d,b.ctrlKey,b.altKey,b.shiftKey,b.metaKey,e,b.charCode);this.J==Wb&&b.preventDefault&&c.preventDefault();return c};function Xb(a,b,c){P.call(this,a,b,c)}x(Xb,P);
Xb.prototype.create=function (a,b){function c(b){var c=Ma(b,function (b){return{identifier:b.identifier,screenX:b.screenX,screenY:b.screenY,clientX:b.clientX,clientY:b.clientY,pageX:b.pageX,pageY:b.pageY,target:a}});c.item=function (a){return c[a]};return c}var d=D(a),e=F(d),f=c(b.changedTouches),j=b.touches==b.changedTouches?f:c(b.touches),m=b.targetTouches==b.changedTouches?f:c(b.targetTouches),d=d.createEvent("MouseEvents");d.initMouseEvent(this.J,this.S,this.T,e,1,0,0,b.clientX,b.clientY,b.ctrlKey,
b.altKey,b.shiftKey,b.metaKey,0,b.relatedTarget);d.touches=j;d.targetTouches=m;d.changedTouches=f;d.scale=b.scale;d.rotation=b.rotation;return d};
var Yb=new P("change",i,l),Zb=new Q("click",i,i),$b=new Q("contextmenu",i,i),ac=new Q("dblclick",i,i),bc=new Q("mousedown",i,i),cc=new Q("mousemove",i,l),dc=new Q("mouseout",i,i),ec=new Q("mouseover",i,i),fc=new Q("mouseup",i,i),Tb=new Q("DOMMouseScroll",i,i),Ub=new Q("MozMousePixelScroll",i,i),Wb=new Vb("keypress",i,i),Rb=new Xb("touchmove",i,i),Qb=new Xb("touchstart",i,i);function Sb(a,b,c){b=b.create(a,c);"isTrusted"in b||(b.La=l);a.dispatchEvent(b)};function gc(a){if("function"==typeof a.L)return a.L();if(t(a))return a.split("");if(ba(a)){for(var b=[],c=a.length,d=0;d<c;d++)b.push(a[d]);return b}return Ea(a)};function hc(a,b){this.n={};this.ta={};var c=arguments.length;if(1<c){c%2&&g(Error("Uneven number of arguments"));for(var d=0;d<c;d+=2)this.set(arguments[d],arguments[d+1])}else a&&this.aa(a)}p=hc.prototype;p.ia=0;p.L=function(){var a=[],b;for(b in this.n)":"==b.charAt(0)&&a.push(this.n[b]);return a};function ic(a){var b=[],c;for(c in a.n)if(":"==c.charAt(0)){var d=c.substring(1);b.push(a.ta[c]?Number(d):d)}return b}
p.set=function (a,b){var c=":"+a;c in this.n||(this.ia++,"number"==typeof a&&(this.ta[c]=i));this.n[c]=b};p.aa=function (a){var b;if(a instanceof hc)b=ic(a),a=a.L();else{b=[];var c=0,d;for(d in a)b[c++]=d;a=Ea(a)}for(c=0;c<b.length;c++)this.set(b[c],a[c])};p.r=function (a){var b=0,c=ic(this),d=this.n,e=this.ia,f=this,j=new J;j.next=function(){for(;;){e!=f.ia&&g(Error("The map has changed since the iterator was created"));b>=c.length&&g(I);var j=c[b++];return a?j:d[":"+j]}};return j};function jc(a){this.n=new hc;a&&this.aa(a)}function kc(a){var b=typeof a;return"object"==b&&a||"function"==b?"o"+(a[da]||(a[da]=++ea)):b.substr(0,1)+a}p=jc.prototype;p.add=function (a){this.n.set(kc(a),a)};p.aa=function (a){for(var a=gc(a),b=a.length,c=0;c<b;c++)this.add(a[c])};p.contains=function (a){return":"+kc(a)in this.n.n};p.L=function(){return this.n.L()};p.r=function(){return this.n.r(l)};function lc(){N.call(this);Gb(this.z())&&Bb(this.z(),"readOnly");this.Ha=new jc}x(lc,N);var mc={};function R(a,b,c){ca(a)&&(a=a.c);a=new nc(a);if(b&&(!(b in mc)||c))mc[b]={key:a,shift:l},c&&(mc[c]={key:a,shift:i})}function nc(a){this.code=a}R(8);R(9);R(13);R(16);R(17);R(18);R(19);R(20);R(27);R(32," ");R(33);R(34);R(35);R(36);R(37);R(38);R(39);R(40);R(44);R(45);R(46);R(48,"0",")");R(49,"1","!");R(50,"2","@");R(51,"3","#");R(52,"4","$");R(53,"5","%");R(54,"6","^");R(55,"7","&");R(56,"8","*");
R(57,"9","(");R(65,"a","A");R(66,"b","B");R(67,"c","C");R(68,"d","D");R(69,"e","E");R(70,"f","F");R(71,"g","G");R(72,"h","H");R(73,"i","I");R(74,"j","J");R(75,"k","K");R(76,"l","L");R(77,"m","M");R(78,"n","N");R(79,"o","O");R(80,"p","P");R(81,"q","Q");R(82,"r","R");R(83,"s","S");R(84,"t","T");R(85,"u","U");R(86,"v","V");R(87,"w","W");R(88,"x","X");R(89,"y","Y");R(90,"z","Z");R(ta?{c:91,e:91,opera:219}:sa?{c:224,e:91,opera:17}:{c:0,e:91,opera:k});
R(ta?{c:92,e:92,opera:220}:sa?{c:224,e:93,opera:17}:{c:0,e:92,opera:k});R(ta?{c:93,e:93,opera:0}:sa?{c:0,e:0,opera:16}:{c:93,e:k,opera:0});R({c:96,e:96,opera:48},"0");R({c:97,e:97,opera:49},"1");R({c:98,e:98,opera:50},"2");R({c:99,e:99,opera:51},"3");R({c:100,e:100,opera:52},"4");R({c:101,e:101,opera:53},"5");R({c:102,e:102,opera:54},"6");R({c:103,e:103,opera:55},"7");R({c:104,e:104,opera:56},"8");R({c:105,e:105,opera:57},"9");R({c:106,e:106,opera:xa?56:42},"*");R({c:107,e:107,opera:xa?61:43},"+");
R({c:109,e:109,opera:xa?109:45},"-");R({c:110,e:110,opera:xa?190:78},".");R({c:111,e:111,opera:xa?191:47},"/");R(144);R(112);R(113);R(114);R(115);R(116);R(117);R(118);R(119);R(120);R(121);R(122);R(123);R({c:107,e:187,opera:61},"=","+");R({c:109,e:189,opera:109},"-","_");R(188,",","<");R(190,".",">");R(191,"/","?");R(192,"`","~");R(219,"[","{");R(220,"\\","|");R(221,"]","}");R({c:59,e:186,opera:59},";",":");R(222,"'",'"');lc.prototype.X=function (a){return this.Ha.contains(a)};function oc(a){return pc(a||arguments.callee.caller,[])}
function pc(a,b){var c=[];if(0<=A(b,a))c.push("[...circular reference...]");else if(a&&50>b.length){c.push(qc(a)+"(");for(var d=a.arguments,e=0;e<d.length;e++){0<e&&c.push(", ");var f;f=d[e];switch(typeof f){case "object":f=f?"object":"null";break;case "string":break;case "number":f=""+f;break;case "boolean":f=f?"true":"false";break;case "function":f=(f=qc(f))?f:"[fn]";break;default:f=typeof f}40<f.length&&(f=f.substr(0,40)+"...");c.push(f)}b.push(a);c.push(")\n");try{c.push(pc(a.caller,b))}catch(j){c.push("[exception trying to get caller]\n")}}else a?
c.push("[...long stack...]"):c.push("[end]");return c.join("")}function qc(a){if(rc[a])return rc[a];a=""+a;if(!rc[a]){var b=/function ([^\(]+)/.exec(a);rc[a]=b?b[1]:"[Anonymous]"}return rc[a]}var rc={};function sc(a,b,c,d,e){this.reset(a,b,c,d,e)}sc.prototype.oa=k;sc.prototype.na=k;var tc=0;sc.prototype.reset=function (a,b,c,d,e){"number"==typeof e||tc++;d||fa();this.N=a;this.Fa=b;delete this.oa;delete this.na};sc.prototype.va=function (a){this.N=a};function S(a){this.Ga=a}S.prototype.Y=k;S.prototype.N=k;S.prototype.ba=k;S.prototype.ra=k;function uc(a,b){this.name=a;this.value=b}uc.prototype.toString=n("name");var vc=new uc("WARNING",900),wc=new uc("CONFIG",700);S.prototype.getParent=n("Y");S.prototype.va=function (a){this.N=a};function xc(a){if(a.N)return a.N;if(a.Y)return xc(a.Y);Ja("Root logger has no level set.");return k}
S.prototype.log=function (a,b,c){if(a.value>=xc(this).value){a=this.Ca(a,b,c);b="log:"+a.Fa;q.console&&(q.console.timeStamp?q.console.timeStamp(b):q.console.markTimeline&&q.console.markTimeline(b));q.msWriteProfilerMark&&q.msWriteProfilerMark(b);for(b=this;b;){var c=b,d=a;if(c.ra)for(var e=0,f=h;f=c.ra[e];e++)f(d);b=b.getParent()}}};
S.prototype.Ca=function (a,b,c){var d=new sc(a,""+b,this.Ga);if(c){d.oa=c;var e;var f=arguments.callee.caller;try{var j;var m;c:{for(var s=["window","location","href"],O=q,E;E=s.shift();)if(O[E]!=k)O=O[E];else{m=k;break c}m=O}if(t(c))j={message:c,name:"Unknown error",lineNumber:"Not available",fileName:m,stack:"Not available"};else{var u,v,s=l;try{u=c.lineNumber||c.Ma||"Not available"}catch(md){u="Not available",s=i}try{v=c.fileName||c.filename||c.sourceURL||m}catch(nd){v="Not available",s=i}j=s||
!c.lineNumber||!c.fileName||!c.stack?{message:c.message,name:c.name,lineNumber:u,fileName:v,stack:c.stack||"Not available"}:c}e="Message: "+ia(j.message)+'\nUrl: <a href="view-source:'+j.fileName+'" target="_new">'+j.fileName+"</a>\nLine: "+j.lineNumber+"\n\nBrowser stack:\n"+ia(j.stack+"-> ")+"[end]\n\nJS stack traversal:\n"+ia(oc(f)+"-> ")}catch(kd){e="Exception trying to expose exception! You win, we lose. "+kd}d.na=e}return d};var yc={},zc=k;
function Ac(a){zc||(zc=new S(""),yc[""]=zc,zc.va(wc));var b;if(!(b=yc[a])){b=new S(a);var c=a.lastIndexOf("."),d=a.substr(c+1),c=Ac(a.substr(0,c));c.ba||(c.ba={});c.ba[d]=b;b.Y=c;yc[a]=b}return b};function Bc(){}x(Bc,function(){});Ac("goog.dom.SavedRange");x(function (a){this.Ja="goog_"+pa++;this.Aa="goog_"+pa++;this.la=Wa(a.ea());a.R(this.la.da("SPAN",{id:this.Ja}),this.la.da("SPAN",{id:this.Aa}))},Bc);function T(){}function Cc(a){if(a.getSelection)return a.getSelection();var a=a.document,b=a.selection;if(b){try{var c=b.createRange();if(c.parentElement){if(c.parentElement().document!=a)return k}else if(!c.length||c.item(0).document!=a)return k}catch(d){return k}return b}return k}function Dc(a){for(var b=[],c=0,d=a.D();c<d;c++)b.push(a.A(c));return b}T.prototype.F=o(l);T.prototype.ea=function(){return D(this.b())};T.prototype.qa=function(){return F(this.ea())};
T.prototype.containsNode=function (a,b){return this.u(Ec(Fc(a),h),b)};function U(a,b){K.call(this,a,b,i)}x(U,K);function V(){}x(V,T);V.prototype.u=function (a,b){var c=Dc(this),d=Dc(a);return(b?Na:Oa)(d,function (a){return Na(c,function (c){return c.u(a,b)})})};V.prototype.insertNode=function (a,b){if(b){var c=this.b();c.parentNode&&c.parentNode.insertBefore(a,c)}else c=this.g(),c.parentNode&&c.parentNode.insertBefore(a,c.nextSibling);return a};V.prototype.R=function (a,b){this.insertNode(a,i);this.insertNode(b,l)};function Gc(a,b,c,d,e){var f;if(a&&(this.f=a,this.i=b,this.d=c,this.h=d,1==a.nodeType&&"BR"!=a.tagName&&(a=a.childNodes,(b=a[b])?(this.f=b,this.i=0):(a.length&&(this.f=z(a)),f=i)),1==c.nodeType))(this.d=c.childNodes[d])?this.h=0:this.d=c;U.call(this,e?this.d:this.f,e);if(f)try{this.next()}catch(j){j!=I&&g(j)}}x(Gc,U);p=Gc.prototype;p.f=k;p.d=k;p.i=0;p.h=0;p.b=n("f");p.g=n("d");p.M=function(){return this.ha&&this.p==this.d&&(!this.h||1!=this.q)};p.next=function(){this.M()&&g(I);return Gc.$.next.call(this)};"ScriptEngine"in q&&"JScript"==q.ScriptEngine()&&(q.ScriptEngineMajorVersion(),q.ScriptEngineMinorVersion(),q.ScriptEngineBuildVersion());function Hc(){}Hc.prototype.u=function (a,b){var c=b&&!a.isCollapsed(),d=a.a;try{return c?0<=this.l(d,0,1)&&0>=this.l(d,1,0):0<=this.l(d,0,0)&&0>=this.l(d,1,1)}catch(e){g(e)}};Hc.prototype.containsNode=function (a,b){return this.u(Fc(a),b)};Hc.prototype.r=function(){return new Gc(this.b(),this.j(),this.g(),this.k())};function Ic(a){this.a=a}x(Ic,Hc);p=Ic.prototype;p.C=function(){return this.a.commonAncestorContainer};p.b=function(){return this.a.startContainer};p.j=function(){return this.a.startOffset};p.g=function(){return this.a.endContainer};p.k=function(){return this.a.endOffset};p.l=function (a,b,c){return this.a.compareBoundaryPoints(1==c?1==b?q.Range.START_TO_START:q.Range.START_TO_END:1==b?q.Range.END_TO_START:q.Range.END_TO_END,a)};p.isCollapsed=function(){return this.a.collapsed};
p.select=function (a){this.Z(F(D(this.b())).getSelection(),a)};p.Z=function (a){a.removeAllRanges();a.addRange(this.a)};p.insertNode=function (a,b){var c=this.a.cloneRange();c.collapse(b);c.insertNode(a);c.detach();return a};
p.R=function (a,b){var c=F(D(this.b()));if(c=(c=Cc(c||window))&&Jc(c))var d=c.b(),e=c.g(),f=c.j(),j=c.k();var m=this.a.cloneRange(),s=this.a.cloneRange();m.collapse(l);s.collapse(i);m.insertNode(b);s.insertNode(a);m.detach();s.detach();if(c){if(d.nodeType==C)for(;f>d.length;){f-=d.length;do d=d.nextSibling;while(d==a||d==b)}if(e.nodeType==C)for(;j>e.length;){j-=e.length;do e=e.nextSibling;while(e==a||e==b)}c=new Kc;c.G=Lc(d,f,e,j);"BR"==d.tagName&&(m=d.parentNode,f=A(m.childNodes,d),d=m);"BR"==e.tagName&&
(m=e.parentNode,j=A(m.childNodes,e),e=m);c.G?(c.f=e,c.i=j,c.d=d,c.h=f):(c.f=d,c.i=f,c.d=e,c.h=j);c.select()}};p.collapse=function (a){this.a.collapse(a)};function W(a){this.a=a}x(W,Ic);function Fc(a){var b=D(a).createRange();if(a.nodeType==C)b.setStart(a,0),b.setEnd(a,a.length);else if(X(a)){for(var c,d=a;(c=d.firstChild)&&X(c);)d=c;b.setStart(d,0);for(d=a;(c=d.lastChild)&&X(c);)d=c;b.setEnd(d,1==d.nodeType?d.childNodes.length:d.length)}else c=a.parentNode,a=A(c.childNodes,a),b.setStart(c,a),b.setEnd(c,a+1);return new W(b)}
W.prototype.Z=function (a,b){var c=b?this.g():this.b(),d=b?this.k():this.j(),e=b?this.b():this.g(),f=b?this.j():this.k();a.collapse(c,d);(c!=e||d!=f)&&a.extend(e,f)};function Mc(a){this.a=a}x(Mc,Hc);Ac("goog.dom.browserrange.IeRange");function Nc(a){var b=D(a).body.createTextRange();if(1==a.nodeType)b.moveToElementText(a),X(a)&&!a.childNodes.length&&b.collapse(l);else{for(var c=0,d=a;d=d.previousSibling;){var e=d.nodeType;if(e==C)c+=d.length;else if(1==e){b.moveToElementText(d);break}}d||b.moveToElementText(a.parentNode);b.collapse(!d);c&&b.move("character",c);b.moveEnd("character",a.length)}return b}p=Mc.prototype;p.O=k;p.f=k;p.d=k;p.i=-1;p.h=-1;
p.s=function(){this.O=this.f=this.d=k;this.i=this.h=-1};
p.C=function(){if(!this.O){var a=this.a.text,b=this.a.duplicate(),c=a.replace(/ +$/,"");(c=a.length-c.length)&&b.moveEnd("character",-c);c=b.parentElement();b=b.htmlText.replace(/(\r\n|\r|\n)+/g," ").length;if(this.isCollapsed()&&0<b)return this.O=c;for(;b>c.outerHTML.replace(/(\r\n|\r|\n)+/g," ").length;)c=c.parentNode;for(;1==c.childNodes.length&&c.innerText==(c.firstChild.nodeType==C?c.firstChild.nodeValue:c.firstChild.innerText)&&X(c.firstChild);)c=c.firstChild;0==a.length&&(c=Oc(this,c));this.O=
c}return this.O};function Oc(a,b){for(var c=b.childNodes,d=0,e=c.length;d<e;d++){var f=c[d];if(X(f)){var j=Nc(f),m=j.htmlText!=f.outerHTML;if(a.isCollapsed()&&m?0<=a.l(j,1,1)&&0>=a.l(j,1,0):a.a.inRange(j))return Oc(a,f)}}return b}p.b=function(){this.f||(this.f=Pc(this,1),this.isCollapsed()&&(this.d=this.f));return this.f};p.j=function(){0>this.i&&(this.i=Qc(this,1),this.isCollapsed()&&(this.h=this.i));return this.i};
p.g=function(){if(this.isCollapsed())return this.b();this.d||(this.d=Pc(this,0));return this.d};p.k=function(){if(this.isCollapsed())return this.j();0>this.h&&(this.h=Qc(this,0),this.isCollapsed()&&(this.i=this.h));return this.h};p.l=function (a,b,c){return this.a.compareEndPoints((1==b?"Start":"End")+"To"+(1==c?"Start":"End"),a)};
function Pc(a,b,c){c=c||a.C();if(!c||!c.firstChild)return c;for(var d=1==b,e=0,f=c.childNodes.length;e<f;e++){var j=d?e:f-e-1,m=c.childNodes[j],s;try{s=Fc(m)}catch(O){continue}var E=s.a;if(a.isCollapsed())if(X(m)){if(s.u(a))return Pc(a,b,m)}else{if(0==a.l(E,1,1)){a.i=a.h=j;break}}else{if(a.u(s)){if(!X(m)){d?a.i=j:a.h=j+1;break}return Pc(a,b,m)}if(0>a.l(E,1,0)&&0<a.l(E,0,1))return Pc(a,b,m)}}return c}
function Qc(a,b){var c=1==b,d=c?a.b():a.g();if(1==d.nodeType){for(var d=d.childNodes,e=d.length,f=c?1:-1,j=c?0:e-1;0<=j&&j<e;j+=f){var m=d[j];if(!X(m)&&0==a.a.compareEndPoints((1==b?"Start":"End")+"To"+(1==b?"Start":"End"),Fc(m).a))return c?j:j+1}return-1==j?0:j}e=a.a.duplicate();f=Nc(d);e.setEndPoint(c?"EndToEnd":"StartToStart",f);e=e.text.length;return c?d.length-e:e}p.isCollapsed=function(){return 0==this.a.compareEndPoints("StartToEnd",this.a)};p.select=function(){this.a.select()};
function Rc(a,b,c){var d;d=d||Wa(a.parentElement());var e;1!=b.nodeType&&(e=i,b=d.da("DIV",k,b));a.collapse(c);d=d||Wa(a.parentElement());var f=c=b.id;c||(c=b.id="goog_"+pa++);a.pasteHTML(b.outerHTML);(b=d.z(c))&&(f||b.removeAttribute("id"));if(e){a=b.firstChild;e=b;if((d=e.parentNode)&&11!=d.nodeType)if(e.removeNode)e.removeNode(l);else{for(;b=e.firstChild;)d.insertBefore(b,e);bb(e)}b=a}return b}p.insertNode=function (a,b){var c=Rc(this.a.duplicate(),a,b);this.s();return c};
p.R=function (a,b){var c=this.a.duplicate(),d=this.a.duplicate();Rc(c,a,i);Rc(d,b,l);this.s()};p.collapse=function (a){this.a.collapse(a);a?(this.d=this.f,this.h=this.i):(this.f=this.d,this.i=this.h)};function Sc(a){this.a=a}x(Sc,Ic);Sc.prototype.Z=function (a){a.collapse(this.b(),this.j());(this.g()!=this.b()||this.k()!=this.j())&&a.extend(this.g(),this.k());0==a.rangeCount&&a.addRange(this.a)};function Tc(a){this.a=a}x(Tc,Ic);Tc.prototype.l=function (a,b,c){return Ba["528"]||(Ba["528"]=0<=oa(ya,"528"))?Tc.$.l.call(this,a,b,c):this.a.compareBoundaryPoints(1==c?1==b?q.Range.START_TO_START:q.Range.END_TO_START:1==b?q.Range.START_TO_END:q.Range.END_TO_END,a)};Tc.prototype.Z=function (a,b){a.removeAllRanges();b?a.setBaseAndExtent(this.g(),this.k(),this.b(),this.j()):a.setBaseAndExtent(this.b(),this.j(),this.g(),this.k())};function X(a){var b;a:if(1!=a.nodeType)b=l;else{switch(a.tagName){case "APPLET":case "AREA":case "BASE":case "BR":case "COL":case "FRAME":case "HR":case "IMG":case "INPUT":case "IFRAME":case "ISINDEX":case "LINK":case "NOFRAMES":case "NOSCRIPT":case "META":case "OBJECT":case "PARAM":case "SCRIPT":case "STYLE":b=l;break a}b=i}return b||a.nodeType==C};function Kc(){}x(Kc,T);function Ec(a,b){var c=new Kc;c.K=a;c.G=!!b;return c}p=Kc.prototype;p.K=k;p.f=k;p.i=k;p.d=k;p.h=k;p.G=l;p.fa=o("text");p.W=function(){return Y(this).a};p.s=function(){this.f=this.i=this.d=this.h=k};p.D=o(1);p.A=function(){return this};function Y(a){var b;if(!(b=a.K)){b=a.b();var c=a.j(),d=a.g(),e=a.k(),f=D(b).createRange();f.setStart(b,c);f.setEnd(d,e);b=a.K=new W(f)}return b}p.C=function(){return Y(this).C()};p.b=function(){return this.f||(this.f=Y(this).b())};
p.j=function(){return this.i!=k?this.i:this.i=Y(this).j()};p.g=function(){return this.d||(this.d=Y(this).g())};p.k=function(){return this.h!=k?this.h:this.h=Y(this).k()};p.F=n("G");p.u=function (a,b){var c=a.fa();return"text"==c?Y(this).u(Y(a),b):"control"==c?(c=Uc(a),(b?Na:Oa)(c,function (a){return this.containsNode(a,b)},this)):l};p.isCollapsed=function(){return Y(this).isCollapsed()};p.r=function(){return new Gc(this.b(),this.j(),this.g(),this.k())};p.select=function(){Y(this).select(this.G)};
p.insertNode=function (a,b){var c=Y(this).insertNode(a,b);this.s();return c};p.R=function (a,b){Y(this).R(a,b);this.s()};p.ga=function(){return new Vc(this)};p.collapse=function (a){a=this.F()?!a:a;this.K&&this.K.collapse(a);a?(this.d=this.f,this.h=this.i):(this.f=this.d,this.i=this.h);this.G=l};function Vc(a){a.F()?a.g():a.b();a.F()?a.k():a.j();a.F()?a.b():a.g();a.F()?a.j():a.k()}x(Vc,Bc);function Wc(){}x(Wc,V);p=Wc.prototype;p.a=k;p.m=k;p.Q=k;p.s=function(){this.Q=this.m=k};p.fa=o("control");p.W=function(){return this.a||document.body.createControlRange()};p.D=function(){return this.a?this.a.length:0};p.A=function (a){a=this.a.item(a);return Ec(Fc(a),h)};p.C=function(){return fb.apply(k,Uc(this))};p.b=function(){return Xc(this)[0]};p.j=o(0);p.g=function(){var a=Xc(this),b=z(a);return Pa(a,function (a){return G(a,b)})};p.k=function(){return this.g().childNodes.length};
function Uc(a){if(!a.m&&(a.m=[],a.a))for(var b=0;b<a.a.length;b++)a.m.push(a.a.item(b));return a.m}function Xc(a){a.Q||(a.Q=Uc(a).concat(),a.Q.sort(function (a,c){return a.sourceIndex-c.sourceIndex}));return a.Q}p.isCollapsed=function(){return!this.a||!this.a.length};p.r=function(){return new Yc(this)};p.select=function(){this.a&&this.a.select()};p.ga=function(){return new Zc(this)};p.collapse=function(){this.a=k;this.s()};function Zc(a){this.m=Uc(a)}x(Zc,Bc);
function Yc(a){a&&(this.m=Xc(a),this.f=this.m.shift(),this.d=z(this.m)||this.f);U.call(this,this.f,l)}x(Yc,U);p=Yc.prototype;p.f=k;p.d=k;p.m=k;p.b=n("f");p.g=n("d");p.M=function(){return!this.depth&&!this.m.length};p.next=function(){this.M()&&g(I);if(!this.depth){var a=this.m.shift();L(this,a,1,1);return a}return Yc.$.next.call(this)};function $c(){this.t=[];this.P=[];this.U=this.I=k}x($c,V);p=$c.prototype;p.Ea=Ac("goog.dom.MultiRange");p.s=function(){this.P=[];this.U=this.I=k};p.fa=o("mutli");p.W=function(){1<this.t.length&&this.Ea.log(vc,"getBrowserRangeObject called on MultiRange with more than 1 range",h);return this.t[0]};p.D=function(){return this.t.length};p.A=function (a){this.P[a]||(this.P[a]=Ec(new W(this.t[a]),h));return this.P[a]};
p.C=function(){if(!this.U){for(var a=[],b=0,c=this.D();b<c;b++)a.push(this.A(b).C());this.U=fb.apply(k,a)}return this.U};function ad(a){a.I||(a.I=Dc(a),a.I.sort(function (a,c){var d=a.b(),e=a.j(),f=c.b(),j=c.j();return d==f&&e==j?0:Lc(d,e,f,j)?1:-1}));return a.I}p.b=function(){return ad(this)[0].b()};p.j=function(){return ad(this)[0].j()};p.g=function(){return z(ad(this)).g()};p.k=function(){return z(ad(this)).k()};p.isCollapsed=function(){return 0==this.t.length||1==this.t.length&&this.A(0).isCollapsed()};
p.r=function(){return new bd(this)};p.select=function(){var a=Cc(this.qa());a.removeAllRanges();for(var b=0,c=this.D();b<c;b++)a.addRange(this.A(b).W())};p.ga=function(){return new cd(this)};p.collapse=function (a){if(!this.isCollapsed()){var b=a?this.A(0):this.A(this.D()-1);this.s();b.collapse(a);this.P=[b];this.I=[b];this.t=[b.W()]}};function cd(a){Ma(Dc(a),function (a){return a.ga()})}x(cd,Bc);function bd(a){a&&(this.H=Ma(ad(a),function (a){return sb(a)}));U.call(this,a?this.b():k,l)}x(bd,U);p=bd.prototype;
p.H=k;p.V=0;p.b=function(){return this.H[0].b()};p.g=function(){return z(this.H).g()};p.M=function(){return this.H[this.V].M()};p.next=function(){try{var a=this.H[this.V],b=a.next();L(this,a.p,a.q,a.depth);return b}catch(c){return(c!==I||this.H.length-1==this.V)&&g(c),this.V++,this.next()}};function Jc(a){var b,c=l;if(a.createRange)try{b=a.createRange()}catch(d){return k}else if(a.rangeCount){if(1<a.rangeCount){b=new $c;for(var c=0,e=a.rangeCount;c<e;c++)b.t.push(a.getRangeAt(c));return b}b=a.getRangeAt(0);c=Lc(a.anchorNode,a.anchorOffset,a.focusNode,a.focusOffset)}else return k;b&&b.addElement?(a=new Wc,a.a=b):a=Ec(new W(b),c);return a}
function Lc(a,b,c,d){if(a==c)return d<b;var e;if(1==a.nodeType&&b)if(e=a.childNodes[b])a=e,b=0;else if(G(a,c))return i;if(1==c.nodeType&&d)if(e=c.childNodes[d])c=e,d=0;else if(G(c,a))return l;return 0<(cb(a,c)||b-d)};function dd(){N.call(this);this.ja=k;this.B=new B(0,0);this.sa=l}x(dd,N);var Z={};Z[Zb]=[0,1,2,k];Z[$b]=[k,k,2,k];Z[fc]=[0,1,2,k];Z[dc]=[0,0,0,0];Z[cc]=[0,0,0,0];Z[ac]=Z[Zb];Z[bc]=Z[fc];Z[ec]=Z[dc];dd.prototype.move=function (a,b){var c=xb(a);this.B.x=b.x+c.x;this.B.y=b.y+c.y;a!=this.z()&&(c=this.z()===Ca.document.documentElement||this.z()===Ca.document.body,c=!this.sa&&c?k:this.z(),ed(this,dc,a),Ob(this,a),ed(this,ec,c));ed(this,cc)};
function ed(a,b,c){a.sa=i;var d=a.B,e;b in Z?(e=Z[b][a.ja===k?3:a.ja],e===k&&g(new y(13,"Event does not permit the specified mouse button."))):e=0;Mb(a.w,i)&&Eb(a.w)&&(c&&!(ec==b||dc==b)&&g(new y(12,"Event type does not allow related target: "+b)),c={clientX:d.x,clientY:d.y,button:e,altKey:l,ctrlKey:l,shiftKey:l,metaKey:l,wheelDelta:0,relatedTarget:c||k},(a=a.w)&&Sb(a,b,c))};function fd(){N.call(this);this.B=new B(0,0);this.ca=new B(0,0)}x(fd,N);fd.prototype.xa=0;fd.prototype.wa=0;fd.prototype.move=function (a,b,c){this.X()||Ob(this,a);a=xb(a);this.B.x=b.x+a.x;this.B.y=b.y+a.y;r(c)&&(this.ca.x=c.x+a.x,this.ca.y=c.y+a.y);if(this.X()){b=Rb;this.X()||g(new y(13,"Should never fire event when touchscreen is not pressed."));var d,e;this.wa&&(d=this.wa,e=this.ca);Pb(this,b,this.xa,this.B,d,e)}};fd.prototype.X=function(){return!!this.xa};function gd(a,b){this.x=a;this.y=b}x(gd,B);gd.prototype.scale=function (a){this.x*=a;this.y*=a;return this};gd.prototype.add=function (a){this.x+=a.x;this.y+=a.y;return this};function hd(){N.call(this)}x(hd,N);(function (a){a.Ba=function(){return a.Da||(a.Da=new a)}})(hd);function id(a){(!Mb(a,i)||!Eb(a))&&g(new y(12,"Element is not currently interactable and may not be manipulated"));(!Gb(a)||Bb(a,"readOnly"))&&g(new y(12,"Element must be user-editable in order to clear it."));var b=hd.Ba();Ob(b,a);var b=b.ua||b.w,c=D(b).activeElement;if(b!=c){if(c&&w(c.blur))try{c.blur()}catch(d){g(d)}w(b.focus)&&b.focus()}a.value&&(a.value="",Sb(a,Yb));Hb(a)&&(a.innerHTML=" ")}var jd=["_"],$=q;!(jd[0]in $)&&$.execScript&&$.execScript("var "+jd[0]);
for(var ld;jd.length&&(ld=jd.shift());)!jd.length&&r(id)?$[ld]=id:$=$[ld]?$[ld]:$[ld]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
atom.getElementAttribute = function (element, name, window){return function(){var f=null,g=!1,h=this;
function i(a){var c=typeof a;if("object"==c)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return c;var b=Object.prototype.toString.call(a);if("[object Window]"==b)return"object";if("[object Array]"==b||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==b||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==
c&&"undefined"==typeof a.call)return"object";return c}function j(a,c){function b(){}b.prototype=c.prototype;a.f=c.prototype;a.prototype=new b};function k(a,c){for(var b=1;b<arguments.length;b++)var d=(""+arguments[b]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function l(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};var m,n="",o=/rv\:([^\);]+)(\)|;)/.exec(h.navigator?h.navigator.userAgent:f);m=n=o?o[1]:"";var p={};function q(a,c){this.code=a;this.message=c||"";this.name=r[a]||r[13];var b=Error(this.message);b.name=this.name;this.stack=b.stack||""}j(q,Error);
var r={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
q.prototype.toString=function(){return"["+this.name+"] "+this.message};function s(a){this.stack=Error().stack||"";a&&(this.message=""+a)}j(s,Error);s.prototype.name="CustomError";function t(a,c){c.unshift(a);s.call(this,k.apply(f,c));c.shift()}j(t,s);t.prototype.name="AssertionError";function u(a,c){if("string"==typeof a)return"string"!=typeof c||1!=c.length?-1:a.indexOf(c,0);for(var b=0;b<a.length;b++)if(b in a&&a[b]===c)return b;return-1};if(!p["1.9.1"]){for(var v=0,w=l(""+m).split("."),x=l("1.9.1").split("."),y=Math.max(w.length,x.length),z=0;0==v&&z<y;z++){var A=w[z]||"",B=x[z]||"",C=RegExp("(\\d*)(\\D*)","g"),D=RegExp("(\\d*)(\\D*)","g");do{var E=C.exec(A)||["","",""],F=D.exec(B)||["","",""];if(0==E[0].length&&0==F[0].length)break;v=((0==E[1].length?0:parseInt(E[1],10))<(0==F[1].length?0:parseInt(F[1],10))?-1:(0==E[1].length?0:parseInt(E[1],10))>(0==F[1].length?0:parseInt(F[1],10))?1:0)||((0==E[2].length)<(0==F[2].length)?-1:(0==
E[2].length)>(0==F[2].length)?1:0)||(E[2]<F[2]?-1:E[2]>F[2]?1:0)}while(0==v)}p["1.9.1"]=0<=v};var G={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},H={IMG:" ",BR:"\n"};function I(a,c,b){if(!(a.nodeName in G))if(3==a.nodeType)b?c.push((""+a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):c.push(a.nodeValue);else if(a.nodeName in H)c.push(H[a.nodeName]);else for(a=a.firstChild;a;)I(a,c,b),a=a.nextSibling};(function(){var a=h.Components;if(!a)return g;try{if(!a.classes)return g}catch(c){return g}var b=a.classes,a=a.interfaces;b["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);b["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();var J="StopIteration"in h?h.StopIteration:Error("StopIteration");function K(){}K.prototype.next=function(){throw J;};function L(a,c,b,d,e){this.a=!!c;a&&M(this,a,d);this.depth=void 0!=e?e:this.c||0;this.a&&(this.depth*=-1);this.e=!b}j(L,K);L.prototype.b=f;L.prototype.c=0;L.prototype.d=g;function M(a,c,b){if(a.b=c)a.c="number"==typeof b?b:1!=a.b.nodeType?0:a.a?-1:1}
L.prototype.next=function(){var a;if(this.d){if(!this.b||this.e&&0==this.depth)throw J;a=this.b;var c=this.a?-1:1;if(this.c==c){var b=this.a?a.lastChild:a.firstChild;b?M(this,b):M(this,a,-1*c)}else(b=this.a?a.previousSibling:a.nextSibling)?M(this,b):M(this,a.parentNode,-1*c);this.depth+=this.c*(this.a?-1:1)}else this.d=!0;a=this.b;if(!this.b)throw J;return a};
L.prototype.splice=function (a){var c=this.b,b=this.a?1:-1;this.c==b&&(this.c=-1*b,this.depth+=this.c*(this.a?-1:1));this.a=!this.a;L.prototype.next.call(this);this.a=!this.a;for(var b=arguments[0],d=i(b),b="array"==d||"object"==d&&"number"==typeof b.length?arguments[0]:arguments,d=b.length-1;0<=d;d--)c.parentNode&&c.parentNode.insertBefore(b[d],c.nextSibling);c&&c.parentNode&&c.parentNode.removeChild(c)};function N(a,c,b,d){L.call(this,a,c,b,f,d)}j(N,L);N.prototype.next=function(){do N.f.next.call(this);while(-1==this.c);return this.b};function O(a,c){return!!a&&1==a.nodeType&&(!c||a.tagName.toUpperCase()==c)}function P(a){return O(a,"OPTION")?!0:O(a,"INPUT")?(a=a.type.toLowerCase(),"checkbox"==a||"radio"==a):g}var Q={"class":"className",readonly:"readOnly"},R=["checked","disabled","draggable","hidden"];
function S(a,c){var b=Q[c]||c,d=a[b];if(void 0===d&&0<=u(R,b))return g;if(b="value"==c)if(b=O(a,"OPTION")){var e;b=c.toLowerCase();if(a.hasAttribute)e=a.hasAttribute(b);else try{e=a.attributes[b].specified}catch(Y){e=g}b=!e}b&&(d=[],I(a,d,g),d=d.join(""));return d}var T="async,autofocus,autoplay,checked,compact,complete,controls,declare,defaultchecked,defaultselected,defer,disabled,draggable,ended,formnovalidate,hidden,indeterminate,iscontenteditable,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,paused,pubdate,readonly,required,reversed,scoped,seamless,seeking,selected,spellcheck,truespeed,willvalidate".split(",");
function U(a,c){if(8==a.nodeType)return f;c=c.toLowerCase();if("style"==c){var b=l(a.style.cssText).toLowerCase();return b=";"==b.charAt(b.length-1)?b:b+";"}b=a.getAttributeNode(c);return!b?f:0<=u(T,c)?"true":b.specified?b.value:f};function V(a,c){var b=f,d=c.toLowerCase();if("style"==c.toLowerCase()){if((b=a.style)&&"string"!=typeof b)b=b.cssText;return b}if("selected"==d||"checked"==d&&P(a)){if(!P(a))throw new q(15,"Element is not selectable");var e="selected",d=a.type&&a.type.toLowerCase();if("checkbox"==d||"radio"==d)e="checked";return S(a,e)?"true":f}b=O(a,"A");if(O(a,"IMG")&&"src"==d||b&&"href"==d)return(b=U(a,d))&&(b=S(a,d)),b;try{e=S(a,c)}catch(Y){}if(!(d=e==f))d=i(e),d="object"==d||"array"==d||"function"==d;b=d?U(a,
c):e;return b!=f?b.toString():f}var W=["_"],X=h;!(W[0]in X)&&X.execScript&&X.execScript("var "+W[0]);for(var Z;W.length&&(Z=W.shift());)!W.length&&void 0!==V?X[Z]=V:X=X[Z]?X[Z]:X[Z]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
atom.getElementText = function (element, window){return function(){var g=void 0,h=!0,i=null,j=!1,k=this;
function l(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function m(a){return"string"==typeof a}function n(a,b){function c(){}c.prototype=b.prototype;a.h=b.prototype;a.prototype=new c};function o(a){var b=a.length-1;return 0<=b&&a.indexOf(" ",b)==b}function aa(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function p(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}var q={};function ba(a){return q[a]||(q[a]=(""+a).replace(/\-([a-z])/g,function (a,c){return c.toUpperCase()}))};var s,ca="",u=/rv\:([^\);]+)(\)|;)/.exec(k.navigator?k.navigator.userAgent:i);s=ca=u?u[1]:"";var v={};var da=window;function w(a,b){this.code=a;this.message=b||"";this.name=x[a]||x[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}n(w,Error);
var x={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
w.prototype.toString=function(){return"["+this.name+"] "+this.message};function y(a){this.stack=Error().stack||"";a&&(this.message=""+a)}n(y,Error);y.prototype.name="CustomError";function z(a,b){b.unshift(a);y.call(this,aa.apply(i,b));b.shift()}n(z,y);z.prototype.name="AssertionError";function ea(a,b){for(var c=a.length,d=m(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(g,d[e],e,a)}function fa(a,b){for(var c=a.length,d=m(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(g,d[e],e,a))return h;return j}function A(a,b){var c;a:if(m(a))c=!m(b)||1!=b.length?-1:a.indexOf(b,0);else{for(c=0;c<a.length;c++)if(c in a&&a[c]===b)break a;c=-1}return 0<=c};var B;
if(!v["1.9.1"]){for(var C=0,D=p(""+s).split("."),E=p("1.9.1").split("."),ga=Math.max(D.length,E.length),F=0;0==C&&F<ga;F++){var ha=D[F]||"",ia=E[F]||"",ja=RegExp("(\\d*)(\\D*)","g"),ka=RegExp("(\\d*)(\\D*)","g");do{var G=ja.exec(ha)||["","",""],H=ka.exec(ia)||["","",""];if(0==G[0].length&&0==H[0].length)break;C=((0==G[1].length?0:parseInt(G[1],10))<(0==H[1].length?0:parseInt(H[1],10))?-1:(0==G[1].length?0:parseInt(G[1],10))>(0==H[1].length?0:parseInt(H[1],10))?1:0)||((0==G[2].length)<(0==H[2].length)?-1:
(0==G[2].length)>(0==H[2].length)?1:0)||(G[2]<H[2]?-1:G[2]>H[2]?1:0)}while(0==C)}v["1.9.1"]=0<=C};function I(a,b){this.x=a!==g?a:0;this.y=b!==g?b:0}I.prototype.toString=function(){return"("+this.x+", "+this.y+")"};function J(a,b){this.width=a;this.height=b}J.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};var K=3;function L(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function la(a,b){var c=[];return M(a,b,c,h)?c[0]:g}function M(a,b,c,d){if(a!=i)for(a=a.firstChild;a;){if(b(a)&&(c.push(a),d)||M(a,b,c,d))return h;a=a.nextSibling}return j}function ma(a,b){for(var a=a.parentNode,c=0;a;){if(b(a))return a;a=a.parentNode;c++}return i}function N(a){this.g=a||k.document||document}
function na(a){var b=a.g,a="CSS1Compat"==b.compatMode?b.documentElement:b.body,b=b.parentWindow||b.defaultView;return new I(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)};var oa=function(){var a={i:"http://www.w3.org/2000/svg"};return function (b){return a[b]||i}}();
function pa(a,b){var c=function(){var c;a:{var e=L(b);if(e.implementation.hasFeature("XPath","3.0")){try{var f=e.createNSResolver?e.createNSResolver(e.documentElement):oa;c=e.evaluate(a,b,f,9,i);break a}catch(r){if("NS_ERROR_ILLEGAL_VALUE"!=r.name)throw new w(32,"Unable to locate an element with the xpath expression "+a+" because of the following error:\n"+r);}c=g}else c=i}return c?c.singleNodeValue||i:b.selectSingleNode?(c=L(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectSingleNode(a)):
i}();if(c!==i&&(!c||1!=c.nodeType))throw new w(32,'The result of the xpath expression "'+a+'" is: '+c+". It should be an element.");return c};(function(){var a=k.Components;if(!a)return j;try{if(!a.classes)return j}catch(b){return j}var c=a.classes,a=a.interfaces;c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return h})();var P="StopIteration"in k?k.StopIteration:Error("StopIteration");function qa(){}qa.prototype.next=function(){throw P;};function Q(a,b,c,d,e){this.a=!!b;a&&R(this,a,d);this.depth=e!=g?e:this.c||0;this.a&&(this.depth*=-1);this.f=!c}n(Q,qa);Q.prototype.b=i;Q.prototype.c=0;Q.prototype.e=j;function R(a,b,c){if(a.b=b)a.c="number"==typeof c?c:1!=a.b.nodeType?0:a.a?-1:1}
Q.prototype.next=function(){var a;if(this.e){if(!this.b||this.f&&0==this.depth)throw P;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?R(this,c):R(this,a,-1*b)}else(c=this.a?a.previousSibling:a.nextSibling)?R(this,c):R(this,a.parentNode,-1*b);this.depth+=this.c*(this.a?-1:1)}else this.e=h;a=this.b;if(!this.b)throw P;return a};
Q.prototype.splice=function (a){var b=this.b,c=this.a?1:-1;this.c==c&&(this.c=-1*c,this.depth+=this.c*(this.a?-1:1));this.a=!this.a;Q.prototype.next.call(this);this.a=!this.a;for(var c=arguments[0],d=l(c),c="array"==d||"object"==d&&"number"==typeof c.length?arguments[0]:arguments,d=c.length-1;0<=d;d--)b.parentNode&&b.parentNode.insertBefore(c[d],b.nextSibling);b&&b.parentNode&&b.parentNode.removeChild(b)};function S(a,b,c,d){Q.call(this,a,b,c,i,d)}n(S,Q);S.prototype.next=function(){do S.h.next.call(this);while(-1==this.c);return this.b};function ra(a,b){var c=L(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,i))?c[b]||c.getPropertyValue(b):""}function T(a,b){return ra(a,b)||(a.currentStyle?a.currentStyle[b]:i)||a.style&&a.style[b]}
function sa(a){for(var b=L(a),c=T(a,"position"),d="fixed"==c||"absolute"==c,a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=T(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return i}
function ta(a){var b=new I;if(1==a.nodeType)if(a.getBoundingClientRect)a=a.getBoundingClientRect(),b.x=a.left,b.y=a.top;else{var c=na(a?new N(L(a)):B||(B=new N));var d,e=L(a),f=T(a,"position"),r=e.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==f&&(d=e.getBoxObjectFor(a))&&(0>d.screenX||0>d.screenY),f=new I(0,0),t=(e?9==e.nodeType?e:L(e):document).documentElement;if(a!=t)if(a.getBoundingClientRect)d=a.getBoundingClientRect(),a=na(e?new N(L(e)):B||(B=new N)),f.x=d.left+a.x,f.y=d.top+a.y;else if(e.getBoxObjectFor&&
!r)d=e.getBoxObjectFor(a),a=e.getBoxObjectFor(t),f.x=d.screenX-a.screenX,f.y=d.screenY-a.screenY;else{d=a;do f.x+=d.offsetLeft,f.y+=d.offsetTop,d!=a&&(f.x+=d.clientLeft||0,f.y+=d.clientTop||0),d=d.offsetParent;while(d&&d!=a);for(d=a;(d=sa(d))&&d!=e.body&&d!=t;)f.x-=d.scrollLeft,f.y-=d.scrollTop}b.x=f.x-c.x;b.y=f.y-c.y}else c="function"==l(a.d),d=a,a.targetTouches?d=a.targetTouches[0]:c&&a.d().targetTouches&&(d=a.d().targetTouches[0]),b.x=d.clientX,b.y=d.clientY;return b}
function ua(a){var b=a.offsetWidth,c=a.offsetHeight;return b===g&&a.getBoundingClientRect?(a=a.getBoundingClientRect(),new J(a.right-a.left,a.bottom-a.top)):new J(b,c)};function U(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}var va="async,autofocus,autoplay,checked,compact,complete,controls,declare,defaultchecked,defaultselected,defer,disabled,draggable,ended,formnovalidate,hidden,indeterminate,iscontenteditable,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,paused,pubdate,readonly,required,reversed,scoped,seamless,seeking,selected,spellcheck,truespeed,willvalidate".split(",");
function V(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return U(a)?a:i}function W(a,b){b=ba(b);return ra(a,b)||wa(a,b)}function wa(a,b){var c=a.currentStyle||a.style,d=c[b];d===g&&"function"==l(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?d!==g?d:i:(c=V(a))?wa(c,b):i}
function xa(a){if("function"==l(a.getBBox))try{var b=a.getBBox();if(b)return b}catch(c){}if("none"!=T(a,"display"))a=ua(a);else{var b=a.style,d=b.display,e=b.visibility,f=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";a=ua(a);b.display=d;b.position=f;b.visibility=e}return a}
function X(a,b){function c(a){if("none"==W(a,"display"))return j;a=V(a);return!a||c(a)}function d(a){var b=xa(a);return 0<b.height&&0<b.width?h:fa(a.childNodes,function (a){return a.nodeType==K||U(a)&&d(a)})}function e(a){var b=V(a);if(b&&"hidden"==W(b,"overflow")){var c=xa(b),d=ta(b),a=ta(a);return d.x+c.width<a.x||d.y+c.height<a.y?j:e(b)}return h}if(!U(a))throw Error("Argument to isShown must be of type Element");if(U(a,"OPTION")||U(a,"OPTGROUP")){var f=ma(a,function (a){return U(a,"SELECT")});return!!f&&
X(f,h)}if(U(a,"MAP")){if(!a.name)return j;f=L(a);f=f.evaluate?pa('/descendant::*[@usemap = "#'+a.name+'"]',f):la(f,function (b){var c;if(c=U(b))8==b.nodeType?b=i:(c="usemap","style"==c?(b=p(b.style.cssText).toLowerCase(),b=";"==b.charAt(b.length-1)?b:b+";"):(b=b.getAttributeNode(c),b=!b?i:A(va,c)?"true":b.specified?b.value:i)),c=b=="#"+a.name;return c});return!!f&&X(f,b)}return U(a,"AREA")?(f=ma(a,function (a){return U(a,"MAP")}),!!f&&X(f,b)):U(a,"INPUT")&&"hidden"==a.type.toLowerCase()||U(a,"NOSCRIPT")||
"hidden"==W(a,"visibility")||!c(a)||!b&&0==ya(a)||!d(a)||!e(a)?j:h}function za(a){return a.replace(/^[^\S\xa0]+|[^\S\xa0]+$/g,"")}
function Aa(a,b){if(U(a,"BR"))b.push("");else{var c=U(a,"TD"),d=W(a,"display"),e=!c&&!A(Ba,d);e&&!/^[\s\xa0]*$/.test(b[b.length-1]||"")&&b.push("");var f=X(a),r=i,t=i;f&&(r=W(a,"white-space"),t=W(a,"text-transform"));ea(a.childNodes,function (a){a.nodeType==K&&f?Ca(a,b,r,t):U(a)&&Aa(a,b)});var O=b[b.length-1]||"";if((c||"table-cell"==d)&&O&&!o(O))b[b.length-1]+=" ";e&&!/^[\s\xa0]*$/.test(O)&&b.push("")}}var Ba="inline,inline-block,inline-table,none,table-cell,table-column,table-column-group".split(",");
function Ca(a,b,c,d){a=a.nodeValue.replace(/\u200b/g,"");a=a.replace(/(\r\n|\r|\n)/g,"\n");if("normal"==c||"nowrap"==c)a=a.replace(/\n/g," ");a="pre"==c||"pre-wrap"==c?a.replace(/[ \f\t\v\u2028\u2029]/g,"\u00a0"):a.replace(/[\ \f\t\v\u2028\u2029]+/g," ");"capitalize"==d?a=a.replace(/(^|\s)(\S)/g,function (a,b,c){return b+c.toUpperCase()}):"uppercase"==d?a=a.toUpperCase():"lowercase"==d&&(a=a.toLowerCase());c=b.pop()||"";o(c)&&0==a.lastIndexOf(" ",0)&&(a=a.substr(1));b.push(c+a)}
function ya(a){var b=1,c=W(a,"opacity");c&&(b=Number(c));(a=V(a))&&(b*=ya(a));return b};function Da(a){var b;a:{for(b=a;b;){if(b.tagName&&"head"==b.tagName.toLowerCase()){b=h;break a}try{b=b.parentNode}catch(c){break}}b=j}if(b)return b=L(a),"TITLE"==a.tagName.toUpperCase()&&(b?b.parentWindow||b.defaultView:window)==da.top?p(b.title):"";b=[];Aa(a,b);var d=b,a=d.length;b=Array(a);for(var d=m(d)?d.split(""):d,e=0;e<a;e++)e in d&&(b[e]=za.call(g,d[e]));return za(b.join("\n")).replace(/\xa0/g," ")}var Y=["_"],Z=k;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);
for(var $;Y.length&&($=Y.shift());)!Y.length&&Da!==g?Z[$]=Da:Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
atom.isElementEnabled = function (element, window){return function(){var e=this;function f(a,c){function b(){}b.prototype=c.prototype;a.d=c.prototype;a.prototype=new b};function g(a,c){for(var b=1;b<arguments.length;b++)var d=(""+arguments[b]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a};var h,i="",j=/rv\:([^\);]+)(\)|;)/.exec(e.navigator?e.navigator.userAgent:null);h=i=j?j[1]:"";var k={};function l(a,c){this.code=a;this.message=c||"";this.name=m[a]||m[13];var b=Error(this.message);b.name=this.name;this.stack=b.stack||""}f(l,Error);
var m={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
l.prototype.toString=function(){return"["+this.name+"] "+this.message};function n(a){this.stack=Error().stack||"";a&&(this.message=""+a)}f(n,Error);n.prototype.name="CustomError";function o(a,c){c.unshift(a);n.call(this,g.apply(null,c));c.shift()}f(o,n);o.prototype.name="AssertionError";function p(a,c){var b;a:if("string"==typeof a)b="string"!=typeof c||1!=c.length?-1:a.indexOf(c,0);else{for(b=0;b<a.length;b++)if(b in a&&a[b]===c)break a;b=-1}return 0<=b};if(!k["1.9.1"]){for(var q=0,r=(""+h).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),s="1.9.1".replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),t=Math.max(r.length,s.length),u=0;0==q&&u<t;u++){var v=r[u]||"",w=s[u]||"",x=RegExp("(\\d*)(\\D*)","g"),z=RegExp("(\\d*)(\\D*)","g");do{var A=x.exec(v)||["","",""],B=z.exec(w)||["","",""];if(0==A[0].length&&0==B[0].length)break;q=((0==A[1].length?0:parseInt(A[1],10))<(0==B[1].length?0:parseInt(B[1],10))?-1:(0==A[1].length?0:parseInt(A[1],10))>(0==B[1].length?
0:parseInt(B[1],10))?1:0)||((0==A[2].length)<(0==B[2].length)?-1:(0==A[2].length)>(0==B[2].length)?1:0)||(A[2]<B[2]?-1:A[2]>B[2]?1:0)}while(0==q)}k["1.9.1"]=0<=q};(function(){var a=e.Components;if(!a)return!1;try{if(!a.classes)return!1}catch(c){return!1}var b=a.classes,a=a.interfaces;b["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);b["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();function C(a,c,b,d,y){this.b=!!c;if(a&&(this.a=a))this.c="number"==typeof d?d:1!=this.a.nodeType?0:this.b?-1:1;this.depth=void 0!=y?y:this.c||0;this.b&&(this.depth*=-1)}f(C,function(){});C.prototype.a=null;C.prototype.c=0;f(function (a,c,b,d){C.call(this,a,c,0,null,d)},C);var D={"class":"className",readonly:"readOnly"},E=["checked","disabled","draggable","hidden"],F="BUTTON,INPUT,OPTGROUP,OPTION,SELECT,TEXTAREA".split(",");function G(a){var c=a.tagName.toUpperCase();if(p(F,c)){var b;b=D.disabled||"disabled";var d=a[b];b=void 0===d&&p(E,b)?!1:d;a=b?!1:a.parentNode&&1==a.parentNode.nodeType&&"OPTGROUP"==c||"OPTION"==c?G(a.parentNode):!0}else a=!0;return a};var H=G,I=["_"],J=e;!(I[0]in J)&&J.execScript&&J.execScript("var "+I[0]);for(var K;I.length&&(K=I.shift());)!I.length&&void 0!==H?J[K]=H:J=J[K]?J[K]:J[K]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
atom.isElementSelected = function (element, window){return function(){var f=!1,g=this;function h(a,b){function c(){}c.prototype=b.prototype;a.d=b.prototype;a.prototype=new c};function i(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a};var k,l="",m=/rv\:([^\);]+)(\)|;)/.exec(g.navigator?g.navigator.userAgent:null);k=l=m?m[1]:"";var n={};function o(a,b){this.code=a;this.message=b||"";this.name=p[a]||p[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}h(o,Error);
var p={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
o.prototype.toString=function(){return"["+this.name+"] "+this.message};function q(a){this.stack=Error().stack||"";a&&(this.message=""+a)}h(q,Error);q.prototype.name="CustomError";function r(a,b){b.unshift(a);q.call(this,i.apply(null,b));b.shift()}h(r,q);r.prototype.name="AssertionError";if(!n["1.9.1"]){for(var s=0,t=(""+k).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),u="1.9.1".replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),v=Math.max(t.length,u.length),w=0;0==s&&w<v;w++){var x=t[w]||"",y=u[w]||"",z=RegExp("(\\d*)(\\D*)","g"),A=RegExp("(\\d*)(\\D*)","g");do{var B=z.exec(x)||["","",""],C=A.exec(y)||["","",""];if(0==B[0].length&&0==C[0].length)break;s=((0==B[1].length?0:parseInt(B[1],10))<(0==C[1].length?0:parseInt(C[1],10))?-1:(0==B[1].length?0:parseInt(B[1],10))>(0==C[1].length?
0:parseInt(C[1],10))?1:0)||((0==B[2].length)<(0==C[2].length)?-1:(0==B[2].length)>(0==C[2].length)?1:0)||(B[2]<C[2]?-1:B[2]>C[2]?1:0)}while(0==s)}n["1.9.1"]=0<=s};var D={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},E={IMG:" ",BR:"\n"};function F(a,b,c){if(!(a.nodeName in D))if(3==a.nodeType)c?b.push((""+a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in E)b.push(E[a.nodeName]);else for(a=a.firstChild;a;)F(a,b,c),a=a.nextSibling};(function(){var a=g.Components;if(!a)return f;try{if(!a.classes)return f}catch(b){return f}var c=a.classes,a=a.interfaces;c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();function G(a,b,c,d,e){this.b=!!b;if(a&&(this.a=a))this.c="number"==typeof d?d:1!=this.a.nodeType?0:this.b?-1:1;this.depth=void 0!=e?e:this.c||0;this.b&&(this.depth*=-1)}h(G,function(){});G.prototype.a=null;G.prototype.c=0;h(function (a,b,c,d){G.call(this,a,b,0,null,d)},G);function H(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}function I(a){return H(a,"OPTION")?!0:H(a,"INPUT")?(a=a.type.toLowerCase(),"checkbox"==a||"radio"==a):f}var J={"class":"className",readonly:"readOnly"},K=["checked","disabled","draggable","hidden"];function L(a){if(I(a)){if(!I(a))throw new o(15,"Element is not selectable");var b="selected",c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";var c=b,d=J[c]||c,b=a[d],e;if(e=void 0===b){b:if("string"==typeof K)d="string"!=typeof d||1!=d.length?-1:K.indexOf(d,0);else{for(e=0;e<K.length;e++)if(e in K&&K[e]===d){d=e;break b}d=-1}e=0<=d}if(e)a=f;else{if(d="value"==c)if(d=H(a,"OPTION")){var j;c=c.toLowerCase();if(a.hasAttribute)j=a.hasAttribute(c);else try{j=a.attributes[c].specified}catch(P){j=
f}d=!j}d&&(j=[],F(a,j,f),b=j.join(""));a=b}a=!!a}else a=f;return a}var M=["_"],N=g;!(M[0]in N)&&N.execScript&&N.execScript("var "+M[0]);for(var O;M.length&&(O=M.shift());)!M.length&&void 0!==L?N[O]=L:N=N[O]?N[O]:N[O]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
atom.isElementDisplayed = function (element, window){return function(){function h(a){return function(){return a}}var k=this;
function m(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function n(a){return"string"==typeof a};function q(a){var b=0,c=String(r).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split(".");a=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split(".");for(var d=Math.max(c.length,a.length),e=0;0==b&&e<d;e++){var f=c[e]||"",g=a[e]||"",w=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var l=w.exec(f)||["","",""],v=p.exec(g)||["","",""];if(0==l[0].length&&0==v[0].length)break;b=((0==l[1].length?0:parseInt(l[1],10))<(0==v[1].length?0:parseInt(v[1],10))?-1:(0==l[1].length?0:parseInt(l[1],10))>(0==v[1].length?
0:parseInt(v[1],10))?1:0)||((0==l[2].length)<(0==v[2].length)?-1:(0==l[2].length)>(0==v[2].length)?1:0)||(l[2]<v[2]?-1:l[2]>v[2]?1:0)}while(0==b)}return b}function aa(a){return String(a).replace(/\-([a-z])/g,function (a,c){return c.toUpperCase()})};var s=Array.prototype;function t(a,b){for(var c=a.length,d=n(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)}function ba(a,b){if(a.reduce)return a.reduce(b,"");var c="";t(a,function (d,e){c=b.call(void 0,c,d,e,a)});return c}function ca(a,b){for(var c=a.length,d=n(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1}
function da(a,b){var c;a:if(n(a))c=n(b)&&1==b.length?a.indexOf(b,0):-1;else{for(c=0;c<a.length;c++)if(c in a&&a[c]===b)break a;c=-1}return 0<=c}function ea(a,b,c){return 2>=arguments.length?s.slice.call(a,b):s.slice.call(a,b,c)};var u={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",
darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",
ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",
lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var fa="background-color border-top-color border-right-color border-bottom-color border-left-color color outline-color".split(" "),ga=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/;function ha(a){if(!x.test(a))throw Error("'"+a+"' is not a valid hex color");4==a.length&&(a=a.replace(ga,"#$1$1$2$2$3$3"));return a.toLowerCase()}var x=/^#(?:[0-9a-f]{3}){1,2}$/i,ia=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i;
function ja(a){var b=a.match(ia);if(b){a=Number(b[1]);var c=Number(b[2]),d=Number(b[3]),b=Number(b[4]);if(0<=a&&255>=a&&0<=c&&255>=c&&0<=d&&255>=d&&0<=b&&1>=b)return[a,c,d,b]}return[]}var ka=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function la(a){var b=a.match(ka);if(b){a=Number(b[1]);var c=Number(b[2]),b=Number(b[3]);if(0<=a&&255>=a&&0<=c&&255>=c&&0<=b&&255>=b)return[a,c,b]}return[]};function y(a,b){this.code=a;this.state=z[a]||ma;this.message=b||"";var c=this.state.replace(/((?:^|\s+)[a-z])/g,function (a){return a.toUpperCase().replace(/^[\s\xa0]+/g,"")}),d=c.length-5;if(0>d||c.indexOf("Error",d)!=d)c+="Error";this.name=c;c=Error(this.message);c.name=this.name;this.stack=c.stack||""}(function(){var a=Error;function b(){}b.prototype=a.prototype;y.I=a.prototype;y.prototype=new b})();
var ma="unknown error",z={15:"element not selectable",11:"element not visible",31:"ime engine activation failed",30:"ime not available",24:"invalid cookie domain",29:"invalid element coordinates",12:"invalid element state",32:"invalid selector",51:"invalid selector",52:"invalid selector",17:"javascript error",405:"unsupported operation",34:"move target out of bounds",27:"no such alert",7:"no such element",8:"no such frame",23:"no such window",28:"script timeout",33:"session not created",10:"stale element reference",
0:"success",21:"timeout",25:"unable to set cookie",26:"unexpected alert open"};z[13]=ma;z[9]="unknown command";y.prototype.toString=function(){return this.name+": "+this.message};var r,na="",oa=/rv\:([^\);]+)(\)|;)/.exec(k.navigator?k.navigator.userAgent:null);r=na=oa?oa[1]:"";var A={};var B;A["1.9.1"]||(A["1.9.1"]=0<=q("1.9.1"));function C(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}C.prototype.toString=function(){return"("+this.x+", "+this.y+")"};C.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};C.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};C.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function D(a,b){this.width=a;this.height=b}D.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};D.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};D.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};D.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var pa=3;function E(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}
function qa(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?ra(a,b):!c&&E(e,b)?-1*sa(a,b):!d&&E(f,a)?sa(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=F(a);c=d.createRange();c.selectNode(a);c.collapse(!0);d=d.createRange();d.selectNode(b);d.collapse(!0);
return c.compareBoundaryPoints(k.Range.START_TO_END,d)}function sa(a,b){var c=a.parentNode;if(c==b)return-1;for(var d=b;d.parentNode!=c;)d=d.parentNode;return ra(d,a)}function ra(a,b){for(var c=b;c=c.previousSibling;)if(c==a)return-1;return 1}function F(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function ta(a,b){a=a.parentNode;for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function G(a){this.p=a||k.document||document}
function ua(a){var b=a.p;a="CSS1Compat"==b.compatMode?b.documentElement:b.body;b=b.parentWindow||b.defaultView;return new C(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)}G.prototype.contains=E;function H(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?"":b);if("string"!=typeof b)if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;for(var c=0,d=[],b="";a;){do 1!=a.nodeType&&(b+=a.nodeValue),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return""+b}
function I(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function J(a,b,c,d,e){return va.call(null,a,b,n(c)?c:null,n(d)?d:null,e||new K)}
function va(a,b,c,d,e){b.getElementsByName&&d&&"name"==c?(b=b.getElementsByName(d),t(b,function (b){a.matches(b)&&e.add(b)})):b.getElementsByClassName&&d&&"class"==c?(b=b.getElementsByClassName(d),t(b,function (b){b.className==d&&a.matches(b)&&e.add(b)})):b.getElementsByTagName&&(b=b.getElementsByTagName(a.getName()),t(b,function (a){I(a,c,d)&&e.add(a)}));return e}function wa(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)I(b,c,d)&&a.matches(b)&&e.add(b);return e};function K(){this.d=this.c=null;this.g=0}function xa(a){this.m=a;this.next=this.i=null}K.prototype.unshift=function (a){a=new xa(a);a.next=this.c;this.d?this.c.i=a:this.c=this.d=a;this.c=a;this.g++};K.prototype.add=function (a){a=new xa(a);a.i=this.d;this.c?this.d.next=a:this.c=this.d=a;this.d=a;this.g++};function ya(a){return(a=a.c)?a.m:null}function L(a){return new za(a,!1)}function za(a,b){this.F=a;this.j=(this.n=b)?a.d:a.c;this.r=null}
za.prototype.next=function(){var a=this.j;if(null==a)return null;var b=this.r=a;this.j=this.n?a.i:a.next;return b.m};function M(a,b,c,d,e){b=b.evaluate(d);c=c.evaluate(d);var f;if(b instanceof K&&c instanceof K){e=L(b);for(d=e.next();d;d=e.next())for(b=L(c),f=b.next();f;f=b.next())if(a(H(d),H(f)))return!0;return!1}if(b instanceof K||c instanceof K){b instanceof K?e=b:(e=c,c=b);e=L(e);b=typeof c;for(d=e.next();d;d=e.next()){switch(b){case "number":d=+H(d);break;case "boolean":d=!!H(d);break;case "string":d=H(d);break;default:throw Error("Illegal primitive type for comparison.");}if(a(d,c))return!0}return!1}return e?
"boolean"==typeof b||"boolean"==typeof c?a(!!b,!!c):"number"==typeof b||"number"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}function Aa(a,b,c,d){this.s=a;this.H=b;this.o=c;this.q=d}Aa.prototype.toString=function(){return this.s};var Ba={};function N(a,b,c,d){if(a in Ba)throw Error("Binary operator already created: "+a);a=new Aa(a,b,c,d);Ba[a.toString()]=a}N("div",6,1,function (a,b,c){return a.b(c)/b.b(c)});N("mod",6,1,function (a,b,c){return a.b(c)%b.b(c)});N("*",6,1,function (a,b,c){return a.b(c)*b.b(c)});
N("+",5,1,function (a,b,c){return a.b(c)+b.b(c)});N("-",5,1,function (a,b,c){return a.b(c)-b.b(c)});N("<",4,2,function (a,b,c){return M(function (a,b){return a<b},a,b,c)});N(">",4,2,function (a,b,c){return M(function (a,b){return a>b},a,b,c)});N("<=",4,2,function (a,b,c){return M(function (a,b){return a<=b},a,b,c)});N(">=",4,2,function (a,b,c){return M(function (a,b){return a>=b},a,b,c)});N("=",3,2,function (a,b,c){return M(function (a,b){return a==b},a,b,c,!0)});
N("!=",3,2,function (a,b,c){return M(function (a,b){return a!=b},a,b,c,!0)});N("and",2,2,function (a,b,c){return a.f(c)&&b.f(c)});N("or",1,2,function (a,b,c){return a.f(c)||b.f(c)});function Ca(a,b,c,d,e,f,g,w,p){this.h=a;this.o=b;this.D=c;this.C=d;this.B=e;this.q=f;this.A=g;this.w=void 0!==w?w:g;this.G=!!p}Ca.prototype.toString=function(){return this.h};var Da={};function O(a,b,c,d,e,f,g,w){if(a in Da)throw Error("Function already created: "+a+".");Da[a]=new Ca(a,b,c,d,!1,e,f,g,w)}O("boolean",2,!1,!1,function (a,b){return b.f(a)},1);O("ceiling",1,!1,!1,function (a,b){return Math.ceil(b.b(a))},1);
O("concat",3,!1,!1,function (a,b){var c=ea(arguments,1);return ba(c,function (b,c){return b+c.a(a)})},2,null);O("contains",2,!1,!1,function (a,b,c){b=b.a(a);a=c.a(a);return-1!=b.indexOf(a)},2);O("count",1,!1,!1,function (a,b){return b.evaluate(a).g},1,1,!0);O("false",2,!1,!1,h(!1),0);O("floor",1,!1,!1,function (a,b){return Math.floor(b.b(a))},1);
O("id",4,!1,!1,function (a,b){var c=a.e(),d=9==c.nodeType?c:c.ownerDocument,c=b.a(a).split(/\s+/),e=[];t(c,function (a){(a=d.getElementById(a))&&!da(e,a)&&e.push(a)});e.sort(qa);var f=new K;t(e,function (a){f.add(a)});return f},1);O("lang",2,!1,!1,h(!1),1);O("last",1,!0,!1,function (a){if(1!=arguments.length)throw Error("Function last expects ()");return a.u()},0);O("local-name",3,!1,!0,function (a,b){var c=b?ya(b.evaluate(a)):a.e();return c?c.nodeName.toLowerCase():""},0,1,!0);
O("name",3,!1,!0,function (a,b){var c=b?ya(b.evaluate(a)):a.e();return c?c.nodeName.toLowerCase():""},0,1,!0);O("namespace-uri",3,!0,!1,h(""),0,1,!0);O("normalize-space",3,!1,!0,function (a,b){return(b?b.a(a):H(a.e())).replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")},0,1);O("not",2,!1,!1,function (a,b){return!b.f(a)},1);O("number",1,!1,!0,function (a,b){return b?b.b(a):+H(a.e())},0,1);O("position",1,!0,!1,function (a){return a.v()},0);O("round",1,!1,!1,function (a,b){return Math.round(b.b(a))},1);
O("starts-with",2,!1,!1,function (a,b,c){b=b.a(a);a=c.a(a);return 0==b.lastIndexOf(a,0)},2);O("string",3,!1,!0,function (a,b){return b?b.a(a):H(a.e())},0,1);O("string-length",1,!1,!0,function (a,b){return(b?b.a(a):H(a.e())).length},0,1);
O("substring",3,!1,!1,function (a,b,c,d){c=c.b(a);if(isNaN(c)||Infinity==c||-Infinity==c)return"";d=d?d.b(a):Infinity;if(isNaN(d)||-Infinity===d)return"";c=Math.round(c)-1;var e=Math.max(c,0);a=b.a(a);if(Infinity==d)return a.substring(e);b=Math.round(d);return a.substring(e,c+b)},2,3);O("substring-after",3,!1,!1,function (a,b,c){b=b.a(a);a=c.a(a);c=b.indexOf(a);return-1==c?"":b.substring(c+a.length)},2);
O("substring-before",3,!1,!1,function (a,b,c){b=b.a(a);a=c.a(a);a=b.indexOf(a);return-1==a?"":b.substring(0,a)},2);O("sum",1,!1,!1,function (a,b){for(var c=L(b.evaluate(a)),d=0,e=c.next();e;e=c.next())d+=+H(e);return d},1,1,!0);O("translate",3,!1,!1,function (a,b,c,d){b=b.a(a);c=c.a(a);var e=d.a(a);a=[];for(d=0;d<c.length;d++){var f=c.charAt(d);f in a||(a[f]=e.charAt(d))}c="";for(d=0;d<b.length;d++)f=b.charAt(d),c+=f in a?a[f]:f;return c},3);O("true",2,!1,!1,h(!0),0);function Ea(a,b,c,d){this.h=a;this.t=b;this.n=c;this.J=d}Ea.prototype.toString=function(){return this.h};var Fa={};function P(a,b,c,d){if(a in Fa)throw Error("Axis already created: "+a);Fa[a]=new Ea(a,b,c,!!d)}P("ancestor",function (a,b){for(var c=new K,d=b;d=d.parentNode;)a.matches(d)&&c.unshift(d);return c},!0);P("ancestor-or-self",function (a,b){var c=new K,d=b;do a.matches(d)&&c.unshift(d);while(d=d.parentNode);return c},!0);
P("attribute",function (a,b){var c=new K,d=a.getName(),e=b.attributes;if(e)if("*"==d)for(var d=0,f;f=e[d];d++)c.add(f);else(f=e.getNamedItem(d))&&c.add(f);return c},!1);P("child",function (a,b,c,d,e){return wa.call(null,a,b,n(c)?c:null,n(d)?d:null,e||new K)},!1,!0);P("descendant",J,!1,!0);P("descendant-or-self",function (a,b,c,d){var e=new K;I(b,c,d)&&a.matches(b)&&e.add(b);return J(a,b,c,d,e)},!1,!0);
P("following",function (a,b,c,d){var e=new K;do for(var f=b;f=f.nextSibling;)I(f,c,d)&&a.matches(f)&&e.add(f),e=J(a,f,c,d,e);while(b=b.parentNode);return e},!1,!0);P("following-sibling",function (a,b){for(var c=new K,d=b;d=d.nextSibling;)a.matches(d)&&c.add(d);return c},!1);P("namespace",function(){return new K},!1);P("parent",function (a,b){var c=new K;if(9==b.nodeType)return c;if(2==b.nodeType)return c.add(b.ownerElement),c;var d=b.parentNode;a.matches(d)&&c.add(d);return c},!1);
P("preceding",function (a,b,c,d){var e=new K,f=[];do f.unshift(b);while(b=b.parentNode);for(var g=1,w=f.length;g<w;g++){var p=[];for(b=f[g];b=b.previousSibling;)p.unshift(b);for(var l=0,v=p.length;l<v;l++)b=p[l],I(b,c,d)&&a.matches(b)&&e.add(b),e=J(a,b,c,d,e)}return e},!0,!0);P("preceding-sibling",function (a,b){for(var c=new K,d=b;d=d.previousSibling;)a.matches(d)&&c.unshift(d);return c},!0);P("self",function (a,b){var c=new K;a.matches(b)&&c.add(b);return c},!1);var Ga=function(){var a={K:"http://www.w3.org/2000/svg"};return function (b){return a[b]||null}}();
function Ha(a,b){var c=function(){var c;a:{var e=F(b);try{var f=e.createNSResolver?e.createNSResolver(e.documentElement):Ga;c=e.evaluate(a,b,f,9,null);break a}catch(g){if("NS_ERROR_ILLEGAL_VALUE"!=g.name)throw new y(32,"Unable to locate an element with the xpath expression "+a+" because of the following error:\n"+g);}c=void 0}return c?c.singleNodeValue||null:b.selectSingleNode?(c=F(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectSingleNode(a)):null}();if(null!==c&&(!c||1!=c.nodeType))throw new y(32,
'The result of the xpath expression "'+a+'" is: '+c+". It should be an element.");return c};(function(){var a=k.Components;if(!a)return!1;try{if(!a.classes)return!1}catch(b){return!1}var c=a.classes,a=a.interfaces;c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();function Q(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}Q.prototype.toString=function(){return"("+this.left+", "+this.top+" - "+this.width+"w x "+this.height+"h)"};Q.prototype.contains=function (a){return a instanceof Q?this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height:a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height};
Q.prototype.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};Q.prototype.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
Q.prototype.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Ia(a,b){var c=F(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,null))?c[b]||c.getPropertyValue(b)||"":""}function R(a,b){return Ia(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style&&a.style[b]}function Ja(a){var b;try{b=a.getBoundingClientRect()}catch(c){return{left:0,top:0,right:0,bottom:0}}return b}
function Ka(a){var b=F(a),c=R(a,"position"),d="fixed"==c||"absolute"==c;for(a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=R(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return null}
function La(a){if(1==a.nodeType){var b;if(a.getBoundingClientRect)b=Ja(a),b=new C(b.left,b.top);else{b=ua(a?new G(F(a)):B||(B=new G));var c,d=F(a),e=R(a,"position"),f=d.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==e&&(c=d.getBoxObjectFor(a))&&(0>c.screenX||0>c.screenY),e=new C(0,0),g=(d?F(d):document).documentElement;if(a!=g)if(a.getBoundingClientRect)c=Ja(a),d=ua(d?new G(F(d)):B||(B=new G)),e.x=c.left+d.x,e.y=c.top+d.y;else if(d.getBoxObjectFor&&!f)c=d.getBoxObjectFor(a),d=d.getBoxObjectFor(g),
e.x=c.screenX-d.screenX,e.y=c.screenY-d.screenY;else{c=a;do e.x+=c.offsetLeft,e.y+=c.offsetTop,c!=a&&(e.x+=c.clientLeft||0,e.y+=c.clientTop||0),c=c.offsetParent;while(c&&c!=a);for(c=a;(c=Ka(c))&&c!=d.body&&c!=g;)e.x-=c.scrollLeft,e.y-=c.scrollTop}b=new C(e.x-b.x,e.y-b.y)}A[12]||(A[12]=0<=q(12))?a=b:((c=R(a,"-moz-transform"))||(c=R(a,"transform")),a=c?(a=c.match(Ma))?new C(parseFloat(a[1]),parseFloat(a[2])):new C(0,0):new C(0,0),a=new C(b.x+a.x,b.y+a.y));return a}b="function"==m(a.k);c=a;a.targetTouches?
c=a.targetTouches[0]:b&&a.k().targetTouches&&(c=a.k().targetTouches[0]);return new C(c.clientX,c.clientY)}var Ma=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;function S(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}function T(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return S(a)?a:null}
function U(a,b){var c=aa(b);if("float"==c||"cssFloat"==c||"styleFloat"==c)c="cssFloat";c=Ia(a,c)||Na(a,c);if(null===c)c=null;else if(da(fa,b)&&(x.test("#"==c.charAt(0)?c:"#"+c)||la(c).length||u&&u[c.toLowerCase()]||ja(c).length)){var d=ja(c);if(!d.length){a:if(d=la(c),!d.length){d=(d=u[c.toLowerCase()])?d:"#"==c.charAt(0)?c:"#"+c;if(x.test(d)&&(d=ha(d),d=ha(d),d=[parseInt(d.substr(1,2),16),parseInt(d.substr(3,2),16),parseInt(d.substr(5,2),16)],d.length))break a;d=[]}3==d.length&&d.push(1)}c=4!=d.length?
c:"rgba("+d.join(", ")+")"}return c}function Na(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&"function"==m(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?void 0!==d?d:null:(c=T(a))?Na(c,b):null}
function V(a,b){function c(a){if("none"==U(a,"display"))return!1;a=T(a);return!a||c(a)}function d(a){if(a.hasAttribute){if(a.hasAttribute("hidden"))return!1}else return!0;a=T(a);return!a||d(a)}function e(a){var b=W(a);return 0<b.height&&0<b.width?!0:S(a,"PATH")&&(0<b.height||0<b.width)?(a=U(a,"stroke-width"),!!a&&0<parseInt(a,10)):"hidden"!=U(a,"overflow")&&ca(a.childNodes,function (a){return a.nodeType==pa||S(a)&&e(a)})}function f(a){var b=U(a,"-o-transform")||U(a,"-webkit-transform")||U(a,"-ms-transform")||
U(a,"-moz-transform")||U(a,"transform");if(b&&"none"!==b)return b=La(a),a=W(a),0<=b.x+a.width&&0<=b.y+a.height?!0:!1;a=T(a);return!a||f(a)}if(!S(a))throw Error("Argument to isShown must be of type Element");if(S(a,"OPTION")||S(a,"OPTGROUP")){var g=ta(a,function (a){return S(a,"SELECT")});return!!g&&V(g,!0)}return(g=Oa(a))?!!g.l&&0<g.rect.width&&0<g.rect.height&&V(g.l,b):S(a,"INPUT")&&"hidden"==a.type.toLowerCase()||S(a,"NOSCRIPT")||"hidden"==U(a,"visibility")||!c(a)||!b&&0==Pa(a)||!d(a)||!e(a)||Qa(a)==
X?!1:f(a)}var X="hidden";
function Qa(a){function b(a){var b=a;if("visible"==w)if(a==f)b=g;else if(a==g)return{x:"visible",y:"visible"};b={x:U(b,"overflow-x"),y:U(b,"overflow-y")};a==f&&(b.x="hidden"==b.x?"hidden":"auto",b.y="hidden"==b.y?"hidden":"auto");return b}function c(a){var b=U(a,"position");if("fixed"==b)return f;for(a=T(a);a&&a!=f&&(0==U(a,"display").lastIndexOf("inline",0)||"absolute"==b&&"static"==U(a,"position"));)a=T(a);return a}var d=W(a),e=F(a),f=e.documentElement,g=e.body||f,w=U(f,"overflow");for(a=c(a);a;a=
c(a)){var p=W(a),e=b(a),l=d.left>=p.left+p.width,p=d.top>=p.top+p.height;if(l&&"hidden"==e.x||p&&"hidden"==e.y)return X;if(l&&"visible"!=e.x||p&&"visible"!=e.y)return Qa(a)==X?X:"scroll"}return"none"}
function W(a){var b=Oa(a);if(b)return b.rect;if("function"==m(a.getBBox))try{var c=a.getBBox();return new Q(c.x,c.y,c.width,c.height)}catch(d){if("NS_ERROR_FAILURE"===d.name||-1!=d.message.indexOf("Component returned failure code: 0x80004005"))return new Q(0,0,0,0);throw d;}else{if(S(a,"HTML"))return a=((F(a)?F(a).parentWindow||F(a).defaultView:window)||window).document,a="CSS1Compat"==a.compatMode?a.documentElement:a.body,a=new D(a.clientWidth,a.clientHeight),new Q(0,0,a.width,a.height);b=La(a);
return new Q(b.x,b.y,a.offsetWidth,a.offsetHeight)}}
function Oa(a){var b=S(a,"MAP");if(!b&&!S(a,"AREA"))return null;var c=b?a:S(a.parentNode,"MAP")?a.parentNode:null,d=null,e=null;if(c&&c.name&&(d=Ha('/descendant::*[@usemap = "#'+c.name+'"]',F(c)))&&(e=W(d),!b&&"default"!=a.shape.toLowerCase())){var f=Ra(a);a=Math.min(Math.max(f.left,0),e.width);b=Math.min(Math.max(f.top,0),e.height);c=Math.min(f.width,e.width-a);f=Math.min(f.height,e.height-b);e=new Q(a+e.left,b+e.top,c,f)}return{l:d,rect:e||new Q(0,0,0,0)}}
function Ra(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if("rect"==b&&4==a.length){var b=a[0],c=a[1];return new Q(b,c,a[2]-b,a[3]-c)}if("circle"==b&&3==a.length)return b=a[2],new Q(a[0]-b,a[1]-b,2*b,2*b);if("poly"==b&&2<a.length){for(var b=a[0],c=a[1],d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new Q(b,c,d-b,e-c)}return new Q(0,0,0,0)}
function Pa(a){var b=1,c=U(a,"opacity");c&&(b=Number(c));(a=T(a))&&(b*=Pa(a));return b};var Sa=V,Y=["_"],Z=k;Y[0]in Z||!Z.execScript||Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)Y.length||void 0===Sa?Z=Z[$]?Z[$]:Z[$]={}:Z[$]=Sa;; return this._.apply(null,arguments);}.apply({navigator:typeof window!=undefined?window.navigator:null,document:typeof window!=undefined?window.document:null}, arguments);}

View file

@ -0,0 +1,436 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {utils: Cu} = Components;
Cu.import("chrome://marionette/content/element.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/frame.js");
this.EXPORTED_SYMBOLS = ["browser"];
this.browser = {};
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
/**
* Get the <xul:browser> for the specified tab.
*
* @param {<xul:tab>} tab
* The tab whose browser needs to be returned.
*
* @return {<xul:browser>}
* The linked browser for the tab or null if no browser can be found.
*/
browser.getBrowserForTab = function (tab) {
if ("browser" in tab) {
// Fennec
return tab.browser;
} else if ("linkedBrowser" in tab) {
// Firefox
return tab.linkedBrowser;
} else {
return null;
}
};
/**
* Return the tab browser for the specified chrome window.
*
* @param {nsIDOMWindow} win
* The window whose tabbrowser needs to be accessed.
*
* @return {<xul:tabbrowser>}
* Tab browser or null if it's not a browser window.
*/
browser.getTabBrowser = function (win) {
if ("BrowserApp" in win) {
// Fennec
return win.BrowserApp;
} else if ("gBrowser" in win) {
// Firefox
return win.gBrowser;
} else {
return null;
}
};
/**
* Creates a browsing context wrapper.
*
* Browsing contexts handle interactions with the browser, according to
* the current environment (desktop, B2G, Fennec, &c).
*
* @param {nsIDOMWindow} win
* The window whose browser needs to be accessed.
* @param {GeckoDriver} driver
* Reference to the driver the browser is attached to.
*/
browser.Context = class {
/**
* @param {<xul:browser>} win
* Frame that is expected to contain the view of the web document.
* @param {GeckoDriver} driver
* Reference to driver instance.
*/
constructor(win, driver) {
this.window = win;
this.driver = driver;
// In Firefox this is <xul:tabbrowser> (not <xul:browser>!)
// and BrowserApp in Fennec
this.tabBrowser = browser.getTabBrowser(win);
this.knownFrames = [];
// Used in B2G to identify the homescreen content page
this.mainContentId = null;
// Used to set curFrameId upon new session
this.newSession = true;
this.seenEls = new element.Store();
// A reference to the tab corresponding to the current window handle, if any.
// Specifically, this.tab refers to the last tab that Marionette switched
// to in this browser window. Note that this may not equal the currently
// selected tab. For example, if Marionette switches to tab A, and then
// clicks on a button that opens a new tab B in the same browser window,
// this.tab will still point to tab A, despite tab B being the currently
// selected tab.
this.tab = null;
this.pendingCommands = [];
// We should have one frame.Manager per browser.Context so that we
// can handle modals in each <xul:browser>.
this.frameManager = new frame.Manager(driver);
this.frameRegsPending = 0;
// register all message listeners
this.frameManager.addMessageManagerListeners(driver.mm);
this.getIdForBrowser = driver.getIdForBrowser.bind(driver);
this.updateIdForBrowser = driver.updateIdForBrowser.bind(driver);
this._curFrameId = null;
this._browserWasRemote = null;
this._hasRemotenessChange = false;
}
/**
* The current frame ID is managed per browser element on desktop in
* case the ID needs to be refreshed. The currently selected window is
* identified by a tab.
*/
get curFrameId() {
let rv = null;
if (this.driver.appName == "B2G") {
rv = this._curFrameId;
} else if (this.tab) {
rv = this.getIdForBrowser(browser.getBrowserForTab(this.tab));
}
return rv;
}
set curFrameId(id) {
if (this.driver.appName != "Firefox") {
this._curFrameId = id;
}
}
/**
* Retrieves the current tabmodal UI object. According to the browser
* associated with the currently selected tab.
*/
getTabModalUI() {
let br = browser.getBrowserForTab(this.tab);
if (!br.hasAttribute("tabmodalPromptShowing")) {
return null;
}
// The modal is a direct sibling of the browser element.
// See tabbrowser.xml's getTabModalPromptBox.
let modals = br.parentNode.getElementsByTagNameNS(
XUL_NS, "tabmodalprompt");
return modals[0].ui;
}
/**
* Close the current window.
*
* @return {Promise}
* A promise which is resolved when the current window has been closed.
*/
closeWindow() {
return new Promise(resolve => {
this.window.addEventListener("unload", ev => {
resolve();
}, {once: true});
this.window.close();
});
}
/** Called when we start a session with this browser. */
startSession(newSession, win, callback) {
callback(win, newSession);
}
/**
* Close the current tab.
*
* @return {Promise}
* A promise which is resolved when the current tab has been closed.
*
* @throws UnsupportedOperationError
* If tab handling for the current application isn't supported.
*/
closeTab() {
// If the current window is not a browser then close it directly. Do the
// same if only one remaining tab is open, or no tab selected at all.
if (!this.tabBrowser || this.tabBrowser.tabs.length === 1 || !this.tab) {
return this.closeWindow();
}
return new Promise((resolve, reject) => {
if (this.tabBrowser.closeTab) {
// Fennec
this.tabBrowser.deck.addEventListener("TabClose", ev => {
resolve();
}, {once: true});
this.tabBrowser.closeTab(this.tab);
} else if (this.tabBrowser.removeTab) {
// Firefox
this.tab.addEventListener("TabClose", ev => {
resolve();
}, {once: true});
this.tabBrowser.removeTab(this.tab);
} else {
reject(new UnsupportedOperationError(
`closeTab() not supported in ${this.driver.appName}`));
}
});
}
/**
* Opens a tab with given URI.
*
* @param {string} uri
* URI to open.
*/
addTab(uri) {
return this.tabBrowser.addTab(uri, true);
}
/**
* Set the current tab and update remoteness tracking if a tabbrowser is available.
*
* @param {number=} index
* Tab index to switch to. If the parameter is undefined,
* the currently selected tab will be used.
* @param {nsIDOMWindow=} win
* Switch to this window before selecting the tab.
* @param {boolean=} focus
* A boolean value which determins whether to focus
* the window. Defaults to true.
*
* @throws UnsupportedOperationError
* If tab handling for the current application isn't supported.
*/
switchToTab(index, win, focus = true) {
if (win) {
this.window = win;
this.tabBrowser = browser.getTabBrowser(win);
}
if (!this.tabBrowser) {
return;
}
if (typeof index == "undefined") {
this.tab = this.tabBrowser.selectedTab;
} else {
this.tab = this.tabBrowser.tabs[index];
if (focus) {
if (this.tabBrowser.selectTab) {
// Fennec
this.tabBrowser.selectTab(this.tab);
} else if ("selectedTab" in this.tabBrowser) {
// Firefox
this.tabBrowser.selectedTab = this.tab;
} else {
throw new UnsupportedOperationError("switchToTab() not supported");
}
}
}
if (this.driver.appName == "Firefox") {
this._browserWasRemote = browser.getBrowserForTab(this.tab).isRemoteBrowser;
this._hasRemotenessChange = false;
}
}
/**
* Registers a new frame, and sets its current frame id to this frame
* if it is not already assigned, and if a) we already have a session
* or b) we're starting a new session and it is the right start frame.
*
* @param {string} uid
* Frame uid for use by Marionette.
* @param the XUL <browser> that was the target of the originating message.
*/
register(uid, target) {
let remotenessChange = this.hasRemotenessChange();
if (this.curFrameId === null || remotenessChange) {
if (this.tabBrowser) {
// If we're setting up a new session on Firefox, we only process the
// registration for this frame if it belongs to the current tab.
if (!this.tab) {
this.switchToTab();
}
if (target == browser.getBrowserForTab(this.tab)) {
this.updateIdForBrowser(browser.getBrowserForTab(this.tab), uid);
this.mainContentId = uid;
}
} else {
this._curFrameId = uid;
this.mainContentId = uid;
}
}
// used to delete sessions
this.knownFrames.push(uid);
return remotenessChange;
}
/**
* When navigating between pages results in changing a browser's
* process, we need to take measures not to lose contact with a listener
* script. This function does the necessary bookkeeping.
*/
hasRemotenessChange() {
// None of these checks are relevant on b2g or if we don't have a tab yet,
// and may not apply on Fennec.
if (this.driver.appName != "Firefox" ||
this.tab === null ||
browser.getBrowserForTab(this.tab) === null) {
return false;
}
if (this._hasRemotenessChange) {
return true;
}
let currentIsRemote = browser.getBrowserForTab(this.tab).isRemoteBrowser;
this._hasRemotenessChange = this._browserWasRemote !== currentIsRemote;
this._browserWasRemote = currentIsRemote;
return this._hasRemotenessChange;
}
/**
* Flushes any pending commands queued when a remoteness change is being
* processed and mark this remotenessUpdate as complete.
*/
flushPendingCommands() {
if (!this._hasRemotenessChange) {
return;
}
this._hasRemotenessChange = false;
this.pendingCommands.forEach(cb => cb());
this.pendingCommands = [];
}
/**
* This function intercepts commands interacting with content and queues
* or executes them as needed.
*
* No commands interacting with content are safe to process until
* the new listener script is loaded and registers itself.
* This occurs when a command whose effect is asynchronous (such
* as goBack) results in a remoteness change and new commands
* are subsequently posted to the server.
*/
executeWhenReady(cb) {
if (this.hasRemotenessChange()) {
this.pendingCommands.push(cb);
} else {
cb();
}
}
/**
* Returns the position of the OS window.
*/
get position() {
return {
x: this.window.screenX,
y: this.window.screenY,
};
}
};
/**
* The window storage is used to save outer window IDs mapped to weak
* references of Window objects.
*
* Usage:
*
* let wins = new browser.Windows();
* wins.set(browser.outerWindowID, window);
*
* ...
*
* let win = wins.get(browser.outerWindowID);
*
*/
browser.Windows = class extends Map {
/**
* Save a weak reference to the Window object.
*
* @param {string} id
* Outer window ID.
* @param {Window} win
* Window object to save.
*
* @return {browser.Windows}
* Instance of self.
*/
set(id, win) {
let wref = Cu.getWeakReference(win);
super.set(id, wref);
return this;
}
/**
* Get the window object stored by provided |id|.
*
* @param {string} id
* Outer window ID.
*
* @return {Window}
* Saved window object, or |undefined| if no window is stored by
* provided |id|.
*/
get(id) {
let wref = super.get(id);
if (wref) {
return wref.get();
}
}
};

View file

@ -0,0 +1,193 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {utils: Cu} = Components;
Cu.importGlobalProperties(["crypto"]);
this.EXPORTED_SYMBOLS = ["capture"];
const CONTEXT_2D = "2d";
const BG_COLOUR = "rgb(255,255,255)";
const PNG_MIME = "image/png";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
/** Provides primitives to capture screenshots. */
this.capture = {};
capture.Format = {
Base64: 0,
Hash: 1,
};
/**
* Take a screenshot of a single element.
*
* @param {Node} node
* The node to take a screenshot of.
* @param {Array.<Node>=} highlights
* Optional array of nodes, around which a border will be marked to
* highlight them in the screenshot.
*
* @return {HTMLCanvasElement}
* The canvas element where the element has been painted on.
*/
capture.element = function (node, highlights = []) {
let win = node.ownerDocument.defaultView;
let rect = node.getBoundingClientRect();
return capture.canvas(
win,
rect.left,
rect.top,
rect.width,
rect.height,
highlights);
};
/**
* Take a screenshot of the window's viewport by taking into account
* the current offsets.
*
* @param {DOMWindow} win
* The DOM window providing the document element to capture,
* and the offsets for the viewport.
* @param {Array.<Node>=} highlights
* Optional array of nodes, around which a border will be marked to
* highlight them in the screenshot.
*
* @return {HTMLCanvasElement}
* The canvas element where the viewport has been painted on.
*/
capture.viewport = function (win, highlights = []) {
let rootNode = win.document.documentElement;
return capture.canvas(
win,
win.pageXOffset,
win.pageYOffset,
rootNode.clientWidth,
rootNode.clientHeight,
highlights);
};
/**
* Low-level interface to draw a rectangle off the framebuffer.
*
* @param {DOMWindow} win
* The DOM window used for the framebuffer, and providing the interfaces
* for creating an HTMLCanvasElement.
* @param {number} left
* The left, X axis offset of the rectangle.
* @param {number} top
* The top, Y axis offset of the rectangle.
* @param {number} width
* The width dimension of the rectangle to paint.
* @param {number} height
* The height dimension of the rectangle to paint.
* @param {Array.<Node>=} highlights
* Optional array of nodes, around which a border will be marked to
* highlight them in the screenshot.
*
* @return {HTMLCanvasElement}
* The canvas on which the selection from the window's framebuffer
* has been painted on.
*/
capture.canvas = function (win, left, top, width, height, highlights = []) {
let scale = win.devicePixelRatio;
let canvas = win.document.createElementNS(XHTML_NS, "canvas");
canvas.width = width * scale;
canvas.height = height * scale;
let ctx = canvas.getContext(CONTEXT_2D);
let flags = ctx.DRAWWINDOW_DRAW_CARET;
// Disabled in bug 1243415 for webplatform-test failures due to out of view elements.
// Needs https://github.com/w3c/web-platform-tests/issues/4383 fixed.
// ctx.DRAWWINDOW_DRAW_VIEW;
// Bug 1009762 - Crash in [@ mozilla::gl::ReadPixelsIntoDataSurface]
// ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
ctx.scale(scale, scale);
ctx.drawWindow(win, left, top, width, height, BG_COLOUR, flags);
ctx = capture.highlight_(ctx, highlights, top, left);
return canvas;
};
capture.highlight_ = function (context, highlights, top = 0, left = 0) {
if (!highlights) {
return;
}
context.lineWidth = "2";
context.strokeStyle = "red";
context.save();
for (let el of highlights) {
let rect = el.getBoundingClientRect();
let oy = -top;
let ox = -left;
context.strokeRect(
rect.left + ox,
rect.top + oy,
rect.width,
rect.height);
}
return context;
};
/**
* Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
*
* @param {HTMLCanvasElement} canvas
* The canvas to encode.
*
* @return {string}
* A Base64 encoded string.
*/
capture.toBase64 = function (canvas) {
let u = canvas.toDataURL(PNG_MIME);
return u.substring(u.indexOf(",") + 1);
};
/**
* Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
*
* @param {HTMLCanvasElement} canvas
* The canvas to encode.
*
* @return {string}
* A hex digest of the SHA-256 hash of the base64 encoded string.
*/
capture.toHash = function (canvas) {
let u = capture.toBase64(canvas);
let buffer = new TextEncoder("utf-8").encode(u);
return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
};
/**
* Convert buffer into to hex.
*
* @param {ArrayBuffer} buffer
* The buffer containing the data to convert to hex.
*
* @return {string}
* A hex digest of the input buffer.
*/
function hex(buffer) {
let hexCodes = [];
let view = new DataView(buffer);
for (let i = 0; i < view.byteLength; i += 4) {
let value = view.getUint32(i);
let stringValue = value.toString(16);
let padding = '00000000';
let paddedValue = (padding + stringValue).slice(-padding.length);
hexCodes.push(paddedValue);
}
return hexCodes.join("");
};

140
testing/marionette/cert.js Normal file
View file

@ -0,0 +1,140 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = ["cert"];
const registrar =
Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
const sss = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
const CONTRACT_ID = "@mozilla.org/security/certoverride;1";
const CERT_PINNING_ENFORCEMENT_PREF =
"security.cert_pinning.enforcement_level";
const HSTS_PRELOAD_LIST_PREF =
"network.stricttransportsecurity.preloadlist";
/** TLS certificate service override management for Marionette. */
this.cert = {
Error: {
Untrusted: 1,
Mismatch: 2,
Time: 4,
},
currentOverride: null,
};
/**
* Installs a TLS certificate service override.
*
* The provided |service| must implement the |register| and |unregister|
* functions that causes a new |nsICertOverrideService| interface
* implementation to be registered with the |nsIComponentRegistrar|.
*
* After |service| is registered and made the |cert.currentOverride|,
* |nsICertOverrideService| is reinitialised to cause all Gecko components
* to pick up the new service.
*
* If an override is already installed, i.e. when |cert.currentOverride|
* is not null, this functions acts as a NOOP.
*
* @param {cert.Override} service
* Service generator that registers and unregisters the XPCOM service.
*
* @throws {Components.Exception}
* If unable to register or initialise |service|.
*/
cert.installOverride = function (service) {
if (this.currentOverride) {
return;
}
service.register();
cert.currentOverride = service;
};
/**
* Uninstall a TLS certificate service override.
*
* After the service has been unregistered, |cert.currentOverride|
* is reset to null.
*
* If there no current override installed, i.e. if |cert.currentOverride|
* is null, this function acts as a NOOP.
*/
cert.uninstallOverride = function() {
if (!cert.currentOverride) {
return;
}
cert.currentOverride.unregister();
this.currentOverride = null;
};
/**
* Certificate override service that acts in an all-inclusive manner
* on TLS certificates.
*
* When an invalid certificate is encountered, it is overriden
* with the |matching| bit level, which is typically a combination of
* |cert.Error.Untrusted|, |cert.Error.Mismatch|, and |cert.Error.Time|.
*
* @type cert.Override
*
* @throws {Components.Exception}
* If there are any problems registering the service.
*/
cert.InsecureSweepingOverride = function() {
const CID = Components.ID("{4b67cce0-a51c-11e6-9598-0800200c9a66}");
const DESC = "All-encompassing cert service that matches on a bitflag";
// This needs to be an old-style class with a function constructor
// and prototype assignment because... XPCOM. Any attempt at
// modernisation will be met with cryptic error messages which will
// make your life miserable.
let service = function() {};
service.prototype = {
hasMatchingOverride: function (
aHostName, aPort, aCert, aOverrideBits, aIsTemporary) {
aIsTemporary.value = false;
aOverrideBits.value =
cert.Error.Untrusted | cert.Error.Mismatch | cert.Error.Time;
return true;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsICertOverrideService]),
};
let factory = XPCOMUtils.generateSingletonFactory(service);
return {
register: function() {
// make it possible to register certificate overrides for domains
// that use HSTS or HPKP
Preferences.set(HSTS_PRELOAD_LIST_PREF, false);
Preferences.set(CERT_PINNING_ENFORCEMENT_PREF, 0);
registrar.registerFactory(CID, DESC, CONTRACT_ID, factory);
},
unregister: function() {
registrar.unregisterFactory(CID, factory);
Preferences.reset(HSTS_PRELOAD_LIST_PREF);
Preferences.reset(CERT_PINNING_ENFORCEMENT_PREF);
// clear collected HSTS and HPKP state
// through the site security service
sss.clearAll();
sss.clearPreloads();
},
};
};

View file

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE window [
]>
<window id="winTest" title="Title Test" windowtype="Test Type"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<dialog id="dia"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox id="things">
<checkbox id="testBox" label="box" />
<textbox id="textInput" size="6" value="test" label="input" />
<textbox id="textInput2" size="6" value="test" label="input" />
<textbox id="textInput3" class="asdf" size="6" value="test" label="input" />
</vbox>
<iframe id="iframe" name="iframename" src="chrome://marionette/content/test2.xul"/>
<iframe id="iframe" name="iframename" src="chrome://marionette/content/test_nested_iframe.xul"/>
<hbox id="testXulBox"/>
<browser id='aBrowser' src="chrome://marionette/content/test2.xul"/>
</dialog>
</window>

View file

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE window [
]>
<dialog id="dia"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox id="things">
<checkbox id="testBox" label="box" />
<textbox id="textInput" size="6" value="test" label="input" />
<textbox id="textInput2" size="6" value="test" label="input" />
<textbox id="textInput3" class="asdf" size="6" value="test" label="input" />
</vbox>
</dialog>

View file

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE dialog [
]>
<dialog id="testDialogAnonymousNode"
buttons="accept, cancel"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<bindings id="testBindings" xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="framebox">
<content orient="vertical" mousethrough="never">
<xul:browser anonid="content" id="browser" flex="1"
context="contentAreaContextMenu"
src="test.xul"
type="content"/>
</content>
</binding>
<binding id="iframebox">
<content>
<xul:box>
<xul:iframe anonid="iframe" src="chrome://marionette/content/test.xul"></xul:iframe>
</xul:box>
</content>
</binding>
</bindings>
<hbox id="testAnonymousContentBox"/>
<hbox id="container" style="-moz-binding: url('#testBindings');"/>
<hbox id="container2" style="-moz-binding: url('#iframebox');"/>
</dialog>

View file

@ -0,0 +1,7 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY testDialog.title "Test Dialog">
<!ENTITY settings.label "Settings">

View file

@ -0,0 +1,7 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
testDialog.title=Test Dialog
settings.label=Settings

View file

@ -0,0 +1,36 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE testdialog [
<!ENTITY % dialogDTD SYSTEM "chrome://marionette/content/test_dialog.dtd" >
%dialogDTD;
]>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<dialog id="testDialog"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&testDialog.title;"
buttons="accept,cancel">
<vbox flex="1" style="min-width: 300px; min-height: 500px;">
<label>&settings.label;</label>
<separator class="thin"/>
<richlistbox id="test-list" flex="1">
<richlistitem id="item-choose" orient="horizontal" selected="true">
<label id="choose-label" value="First Entry" flex="1"/>
<button id="choose-button" oncommand="" label="Choose..."/>
</richlistitem>
</richlistbox>
<separator class="thin"/>
<checkbox id="check-box" label="Test Mode 2" />
<hbox align="center">
<label id="text-box-label" control="text-box">Name:</label>
<textbox id="text-box" flex="1" />
</hbox>
</vbox>
</dialog>

View file

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE window [
]>
<iframe id="iframe" name="iframename" src="test2.xul"/>

View file

@ -0,0 +1,3 @@
[flake8]
max-line-length = 99
exclude = __init__.py,disti/*,build/*,

View file

@ -0,0 +1,2 @@
exclude MANIFEST.in
include requirements.txt

View file

@ -0,0 +1,153 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/MarionettePythonClient.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/MarionettePythonClient.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/MarionettePythonClient"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/MarionettePythonClient"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View file

@ -0,0 +1,46 @@
Actions
=======
.. py:currentmodule:: marionette
Action Sequences
----------------
:class:`Actions` are designed as a way to simulate user input as closely as possible
on a touch device like a smart phone. A common operation is to tap the screen
and drag your finger to another part of the screen and lift it off.
This can be simulated using an Action::
from marionette import Actions
start_element = marionette.find_element('id', 'start')
end_element = marionette.find_element('id', 'end')
action = Actions(marionette)
action.press(start_element).wait(1).move(end_element).release()
action.perform()
This will simulate pressing an element, waiting for one second, moving the
finger over to another element and then lifting the finger off the screen. The
wait is optional in this case, but can be useful for simulating delays typical
to a users behaviour.
Multi-Action Sequences
----------------------
Sometimes it may be necessary to simulate multiple actions at the same time.
For example a user may be dragging one finger while tapping another. This is
where :class:`MultiActions` come in. MultiActions are simply a way of combining
two or more actions together and performing them all at the same time::
action1 = Actions(marionette)
action1.press(start_element).move(end_element).release()
action2 = Actions(marionette)
action2.press(another_element).wait(1).release()
multi = MultiActions(marionette)
multi.add(action1)
multi.add(action2)
multi.perform()

View file

@ -0,0 +1,54 @@
Debugging
=========
.. py:currentmodule:: marionette
Sometimes when working with Marionette you'll run into unexpected behaviour and
need to do some debugging. This page outlines some of the Marionette methods
that can be useful to you.
Please note that the best tools for debugging are the `ones that ship with
Gecko`_. This page doesn't describe how to use those with Marionette. Also see
a related topic about `using the debugger with Marionette`_ on MDN.
.. _ones that ship with Gecko: https://developer.mozilla.org/en-US/docs/Tools
.. _using the debugger with Marionette: https://developer.mozilla.org/en-US/docs/Marionette/Debugging
Storing Logs on the Server
~~~~~~~~~~~~~~~~~~~~~~~~~~
By calling `~Marionette.log` it is possible to store a message on the server.
Logs can later be retrieved using `~Marionette.get_logs`. For example::
try:
marionette.log("Sending a click event") # logged at INFO level
elem.click()
except:
marionette.log("Something went wrong!", "ERROR")
print(marionette.get_logs())
Disclaimer: Example for illustrative purposes only, don't actually hide
tracebacks like that!
Seeing What's on the Page
~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes it's difficult to tell what is actually on the page that is being
manipulated. Either because it happens too fast, the window isn't big enough or
you are manipulating a remote server! There are two methods that can help you
out. The first is `~Marionette.screenshot`::
marionette.screenshot() # takes screenshot of entire frame
elem = marionette.find_element(By.ID, 'some-div')
marionette.screenshot(elem) # takes a screenshot of only the given element
Sometimes you just want to see the DOM layout. You can do this with the
`~Marionette.page_source` property. Note that the page source depends on the
context you are in::
print(marionette.page_source)
marionette.set_context('chrome')
print(marionette.page_source)

View file

@ -0,0 +1,126 @@
Finding Elements
================
.. py:currentmodule:: marionette
One of the most common and yet often most difficult tasks in Marionette is
finding a DOM element on a webpage or in the chrome UI. Marionette provides
several different search strategies to use when finding elements. All search
strategies work with both :func:`~Marionette.find_element` and
:func:`~Marionette.find_elements`, though some strategies are not implemented
in chrome scope.
In the event that more than one element is matched by the query,
:func:`~Marionette.find_element` will only return the first element found. In
the event that no elements are matched by the query,
:func:`~Marionette.find_element` will raise `NoSuchElementException` while
:func:`~Marionette.find_elements` will return an empty list.
Search Strategies
-----------------
Search strategies are defined in the :class:`By` class::
from marionette import By
print(By.ID)
The strategies are:
* `id` - The easiest way to find an element is to refer to its id directly::
container = client.find_element(By.ID, 'container')
* `class name` - To find elements belonging to a certain class, use `class name`::
buttons = client.find_elements(By.CLASS_NAME, 'button')
* `css selector` - It's also possible to find elements using a `css selector`_::
container_buttons = client.find_elements(By.CSS_SELECTOR, '#container .buttons')
* `name` - Find elements by their name attribute (not implemented in chrome
scope)::
form = client.find_element(By.NAME, 'signup')
* `tag name` - To find all the elements with a given tag, use `tag name`::
paragraphs = client.find_elements(By.TAG_NAME, 'p')
* `link text` - A convenience strategy for finding link elements by their
innerHTML (not implemented in chrome scope)::
link = client.find_element(By.LINK_TEXT, 'Click me!')
* `partial link text` - Same as `link text` except substrings of the innerHTML
are matched (not implemented in chrome scope)::
link = client.find_element(By.PARTIAL_LINK_TEXT, 'Clic')
* `xpath` - Find elements using an xpath_ query::
elem = client.find_element(By.XPATH, './/*[@id="foobar"')
.. _css selector: https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors
.. _xpath: https://developer.mozilla.org/en-US/docs/Web/XPath
Chaining Searches
-----------------
In addition to the methods on the Marionette object, HTMLElement objects also
provide :func:`~HTMLElement.find_element` and :func:`~HTMLElement.find_elements`
methods. The difference is that only child nodes of the element will be searched.
Consider the following html snippet::
<div id="content">
<span id="main"></span>
</div>
<div id="footer"></div>
Doing the following will work::
client.find_element(By.ID, 'container').find_element(By.ID, 'main')
But this will raise a `NoSuchElementException`::
client.find_element(By.ID, 'container').find_element(By.ID, 'footer')
Finding Anonymous Nodes
-----------------------
When working in chrome scope, for example manipulating the Firefox user
interface, you may run into something called an anonymous node.
Firefox uses a markup language called XUL_ for its interface. XUL is similar
to HTML in that it has a DOM and tags that render controls on the display. One
ability of XUL is to create re-useable widgets that are made up out of several
smaller XUL elements. These widgets can be bound to the DOM using something
called the `XML binding language (XBL)`_.
The end result is that the DOM sees the widget as a single entity. It doesn't
know anything about how that widget is made up. All of the smaller XUL elements
that make up the widget are called `anonymous content`_. It is not possible to
query such elements using traditional DOM methods like `getElementById`.
Marionette provides two special strategies used for finding anonymous content.
Unlike normal elements, anonymous nodes can only be seen by their parent. So
it's necessary to first find the parent element and then search for the
anonymous children from there.
* `anon` - Finds all anonymous children of the element, there is no search term
so `None` must be passed in::
anon_children = client.find_element('id', 'parent').find_elements('anon', None)
* `anon attribute` - Find an anonymous child based on an attribute. An
unofficial convention is for anonymous nodes to have an
`anonid` attribute::
anon_child = client.find_element('id', 'parent').find_element('anon attribute', {'anonid': 'container'})
.. _XUL: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL
.. _XML binding language (XBL): https://developer.mozilla.org/en-US/docs/XBL
.. _anonymous content: https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/Anonymous_Content

View file

@ -0,0 +1,13 @@
Advanced Topics
===============
Here are a collection of articles explaining some of the more complicated
aspects of Marionette.
.. toctree::
:maxdepth: 1
findelement
stale
actions
debug

View file

@ -0,0 +1,71 @@
Dealing with Stale Elements
===========================
.. py:currentmodule:: marionette
Marionette does not keep a live representation of the DOM saved. All it can do
is send commands to the Marionette server which queries the DOM on the client's
behalf. References to elements are also not passed from server to client. A
unique id is generated for each element that gets referenced and a mapping of
id to element object is stored on the server. When commands such as
:func:`~HTMLElement.click()` are run, the client sends the element's id along
with the command. The server looks up the proper DOM element in its reference
table and executes the command on it.
In practice this means that the DOM can change state and Marionette will never
know until it sends another query. For example, look at the following HTML::
<head>
<script type=text/javascript>
function addDiv() {
var div = document.createElement("div");
document.getElementById("container").appendChild(div);
}
</script>
</head>
<body>
<div id="container">
</div>
<input id="button" type=button onclick="addDiv();">
</body>
Care needs to be taken as the DOM is being modified after the page has loaded.
The following code has a race condition::
button = client.find_element('id', 'button')
button.click()
assert len(client.find_elements('css selector', '#container div')) > 0
Explicit Waiting and Expected Conditions
----------------------------------------
To avoid the above scenario, manual synchronisation is needed. Waits are used
to pause program execution until a given condition is true. This is a useful
technique to employ when documents load new content or change after
``Document.readyState``'s value changes to "complete".
The :class:`Wait` helper class provided by Marionette avoids some of the
caveats of ``time.sleep(n)``. It will return immediately once the provided
condition evaluates to true.
To avoid the race condition in the above example, one could do::
button = client.find_element('id', 'button')
button.click()
def find_divs():
return client.find_elements('css selector', '#container div')
divs = Wait(client).until(find_divs)
assert len(divs) > 0
This avoids the race condition. Because finding elements is a common condition
to wait for, it is built in to Marionette. Instead of the above, you could
write::
button = client.find_element('id', 'button')
button.click()
assert len(Wait(client).until(expected.elements_present('css selector', '#container div'))) > 0
For a full list of built-in conditions, see :mod:`~marionette.expected`.

View file

@ -0,0 +1,185 @@
.. py:currentmodule:: marionette_driver.marionette
Marionette Python Client
========================
The Marionette Python client library allows you to remotely control a
Gecko-based browser or device which is running a Marionette_
server. This includes Firefox Desktop and Firefox for Android.
The Marionette server is built directly into Gecko and can be started by
passing in a command line option to Gecko, or by using a Marionette-enabled
build. The server listens for connections from various clients. Clients can
then control Gecko by sending commands to the server.
This is the official Python client for Marionette. There also exists a
`NodeJS client`_ maintained by the Firefox OS automation team.
.. _Marionette: https://developer.mozilla.org/en-US/docs/Marionette
.. _NodeJS client: https://github.com/mozilla-b2g/gaia/tree/master/tests/jsmarionette
Getting the Client
------------------
The Python client is officially supported. To install it, first make sure you
have `pip installed`_ then run:
.. parsed-literal::
pip install marionette_driver
It's highly recommended to use virtualenv_ when installing Marionette to avoid
package conflicts and other general nastiness.
You should now be ready to start using Marionette. The best way to learn is to
play around with it. Start a `Marionette-enabled instance of Firefox`_, fire up
a python shell and follow along with the
:doc:`interactive tutorial <interactive>`!
.. _pip installed: https://pip.pypa.io/en/latest/installing.html
.. _virtualenv: http://virtualenv.readthedocs.org/en/latest/
.. _Marionette-enabled instance of Firefox: https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/Builds
Using the Client for Testing
----------------------------
Please visit the `Marionette Tests`_ section on MDN for information regarding
testing with Marionette.
.. _Marionette Tests: https://developer.mozilla.org/en/Marionette/Tests
Session Management
------------------
A session is a single instance of a Marionette client connected to a Marionette
server. Before you can start executing commands, you need to start a session
with :func:`start_session() <Marionette.start_session>`:
.. parsed-literal::
from marionette_driver.marionette import Marionette
client = Marionette('localhost', port=2828)
client.start_session()
This returns a session id and an object listing the capabilities of the
Marionette server. For example, a server running on Firefox Desktop will
have some features which a server running from Firefox Android won't.
It's also possible to access the capabilities using the
:attr:`~Marionette.session_capabilities` attribute. After finishing with a
session, you can delete it with :func:`~Marionette.delete_session()`. Note that
this will also happen automatically when the Marionette object is garbage
collected.
Context Management
------------------
Commands can only be executed in a single window, frame and scope at a time. In
order to run commands elsewhere, it's necessary to explicitly switch to the
appropriate context.
Use :func:`~Marionette.switch_to_window` to execute commands in the context of a
new window:
.. parsed-literal::
original_window = client.current_window_handle
for handle in client.window_handles:
if handle != original_window:
client.switch_to_window(handle)
print("Switched to window with '{}' loaded.".format(client.get_url()))
client.switch_to_window(original_window)
Similarly, use :func:`~Marionette.switch_to_frame` to execute commands in the
context of a new frame (e.g an <iframe> element):
.. parsed-literal::
iframe = client.find_element(By.TAG_NAME, 'iframe')
client.switch_to_frame(iframe)
assert iframe == client.get_active_frame()
Finally Marionette can switch between `chrome` and `content` scope. Chrome is a
privileged scope where you can access things like the Firefox UI itself.
Content scope is where things like webpages live. You can switch between
`chrome` and `content` using the :func:`~Marionette.set_context` and :func:`~Marionette.using_context` functions:
.. parsed-literal::
client.set_context(client.CONTEXT_CONTENT)
# content scope
with client.using_context(client.CONTEXT_CHROME):
#chrome scope
... do stuff ...
# content scope restored
Navigation
----------
Use :func:`~Marionette.navigate` to open a new website. It's also possible to
move through the back/forward cache using :func:`~Marionette.go_forward` and
:func:`~Marionette.go_back` respectively. To retrieve the currently
open website, use :func:`~Marionette.get_url`:
.. parsed-literal::
url = 'http://mozilla.org'
client.navigate(url)
client.go_back()
client.go_forward()
assert client.get_url() == url
DOM Elements
------------
In order to inspect or manipulate actual DOM elements, they must first be found
using the :func:`~Marionette.find_element` or :func:`~Marionette.find_elements`
methods:
.. parsed-literal::
from marionette import HTMLElement
element = client.find_element(By.ID, 'my-id')
assert type(element) == HTMLElement
elements = client.find_elements(By.TAG_NAME, 'a')
assert type(elements) == list
For a full list of valid search strategies, see :doc:`advanced/findelement`.
Now that an element has been found, it's possible to manipulate it:
.. parsed-literal::
element.click()
element.send_keys('hello!')
print(element.get_attribute('style'))
For the full list of possible commands, see the :class:`HTMLElement`
reference.
Be warned that a reference to an element object can become stale if it was
modified or removed from the document. See :doc:`advanced/stale` for tips
on working around this limitation.
Script Execution
----------------
Sometimes Marionette's provided APIs just aren't enough and it is necessary to
run arbitrary javascript. This is accomplished with the
:func:`~Marionette.execute_script` and :func:`~Marionette.execute_async_script`
functions. They accomplish what their names suggest, the former executes some
synchronous JavaScript, while the latter provides a callback mechanism for
running asynchronous JavaScript:
.. parsed-literal::
result = client.execute_script("return arguments[0] + arguments[1];",
script_args=[2, 3])
assert result == 5
The async method works the same way, except it won't return until a special
`marionetteScriptFinished()` function is called:
.. parsed-literal::
result = client.execute_async_script("""
setTimeout(function() {
marionetteScriptFinished("all done");
}, arguments[0]);
""", script_args=[1000])
assert result == "all done"
Beware that running asynchronous scripts can potentially hang the program
indefinitely if they are not written properly. It is generally a good idea to
set a script timeout using :func:`~Marionette.timeout.script` and handling
`ScriptTimeoutException`.

View file

@ -0,0 +1,259 @@
# -*- coding: utf-8 -*-
#
# Marionette Python Client documentation build configuration file, created by
# sphinx-quickstart on Tue Aug 6 13:54:46 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import sys
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
here = os.path.dirname(os.path.abspath(__file__))
parent = os.path.dirname(here)
sys.path.insert(0, parent)
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Marionette Python Client'
copyright = u'2013, Mozilla Automation and Tools and individual contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# version = '0'
# The full version, including alpha/beta/rc tags.
# release = '0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd:
try:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError:
pass
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'MarionettePythonClientdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'MarionettePythonClient.tex', u'Marionette Python Client Documentation',
u'Mozilla Automation and Tools team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'marionettepythonclient', u'Marionette Python Client Documentation',
[u'Mozilla Automation and Tools team'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'MarionettePythonClient', 'Marionette Python Client Documentation',
'Mozilla Automation and Tools team', 'MarionettePythonClient',
'One line description of project.', 'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'

View file

@ -0,0 +1,16 @@
.. include:: basics.rst
Indices and tables
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. toctree::
:hidden:
Getting Started <basics>
Interactive Tutorial <interactive>
advanced/landing
reference

View file

@ -0,0 +1,55 @@
Using the Client Interactively
==============================
Once you installed the client and have Marionette running, you can fire
up your favourite interactive python environment and start playing with
Marionette. Let's use a typical python shell:
.. parsed-literal::
python
First, import Marionette:
.. parsed-literal::
from marionette import Marionette
Now create the client for this session. Assuming you're using the default
port on a Marionette instance running locally:
.. parsed-literal::
client = Marionette(host='localhost', port=2828)
client.start_session()
This will return some id representing your session id. Now that you've
established a connection, let's start doing interesting things:
.. parsed-literal::
client.execute_script("alert('o hai there!');")
You should now see this alert pop up! How exciting! Okay, let's do
something practical. Close the dialog and try this:
.. parsed-literal::
client.navigate("http://www.mozilla.org")
Now you're at mozilla.org! You can even verify it using the following:
.. parsed-literal::
client.get_url()
You can even find an element and click on it. Let's say you want to get
the first link:
.. parsed-literal::
from marionette import By
first_link = client.find_element(By.TAG_NAME, "a")
first_link now holds a reference to the first link on the page. You can click it:
.. parsed-literal::
first_link.click()

View file

@ -0,0 +1,190 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\MarionettePythonClient.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\MarionettePythonClient.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

View file

@ -0,0 +1,51 @@
=============
API Reference
=============
Marionette
----------
.. autoclass:: marionette_driver.marionette.Marionette
:members:
HTMLElement
-----------
.. autoclass:: marionette_driver.marionette.HTMLElement
:members:
DateTimeValue
-------------
.. autoclass:: marionette_driver.DateTimeValue
:members:
Actions
-------
.. autoclass:: marionette_driver.marionette.Actions
:members:
MultiActions
------------
.. autoclass:: marionette_driver.marionette.MultiActions
:members:
Wait
----
.. autoclass:: marionette_driver.Wait
:members:
:special-members:
.. autoattribute marionette_driver.wait.DEFAULT_TIMEOUT
.. autoattribute marionette_driver.wait.DEFAULT_INTERVAL
Built-in Conditions
^^^^^^^^^^^^^^^^^^^
.. automodule:: marionette_driver.expected
:members:
Addons
------
.. autoclass:: marionette_driver.addons.Addons
:members:
Localization
------------
.. autoclass:: marionette_driver.localization.L10n
:members:

View file

@ -0,0 +1,26 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
__version__ = '2.2.0'
from marionette_driver import (
addons,
by,
date_time_value,
decorators,
errors,
expected,
geckoinstance,
gestures,
keys,
localization,
marionette,
selection,
wait,
)
from marionette_driver.by import By
from marionette_driver.date_time_value import DateTimeValue
from marionette_driver.gestures import smooth_scroll, pinch
from marionette_driver.marionette import Actions
from marionette_driver.wait import Wait

View file

@ -0,0 +1,70 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from . import errors
__all__ = ["Addons", "AddonInstallException"]
class AddonInstallException(errors.MarionetteException):
pass
class Addons(object):
"""An API for installing and inspecting addons during Gecko
runtime. This is a partially implemented wrapper around Gecko's
`AddonManager API`_.
For example::
from marionette_driver.addons import Addons
addons = Addons(marionette)
addons.install("/path/to/extension.xpi")
.. _AddonManager API: https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager
"""
def __init__(self, marionette):
self._mn = marionette
def install(self, path, temp=False):
"""Install a Firefox addon.
If the addon is restartless, it can be used right away. Otherwise
a restart using :func:`~marionette_driver.marionette.Marionette.restart`
will be needed.
:param path: A file path to the extension to be installed.
:param temp: Install a temporary addon. Temporary addons will
automatically be uninstalled on shutdown and do not need
to be signed, though they must be restartless.
:returns: The addon ID string of the newly installed addon.
:raises: :exc:`AddonInstallException`
"""
body = {"path": path, "temporary": temp}
try:
return self._mn._send_message("addon:install", body, key="value")
except errors.UnknownException as e:
raise AddonInstallException(e)
def uninstall(self, addon_id):
"""Uninstall a Firefox addon.
If the addon is restartless, it will be uninstalled right away.
Otherwise a restart using :func:`~marionette_driver.marionette.Marionette.restart`
will be needed.
If the call to uninstall is resulting in a `ScriptTimeoutException`,
an invalid ID is likely being passed in. Unfortunately due to
AddonManager's implementation, it's hard to retrieve this error from
Python.
:param addon_id: The addon ID string to uninstall.
"""
body = {"id": addon_id}
self._mn._send_message("addon:uninstall", body)

View file

@ -0,0 +1,27 @@
# Copyright 2008-2009 WebDriver committers
# Copyright 2008-2009 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class By(object):
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
ANON_ATTRIBUTE = "anon attribute"
ANON = "anon"

View file

@ -0,0 +1,49 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
class DateTimeValue(object):
"""
Interface for setting the value of HTML5 "date" and "time" input elements.
Simple usage example:
::
element = marionette.find_element(By.ID, "date-test")
dt_value = DateTimeValue(element)
dt_value.date = datetime(1998, 6, 2)
"""
def __init__(self, element):
self.element = element
@property
def date(self):
"""
Retrieve the element's string value
"""
return self.element.get_attribute('value')
# As per the W3C "date" element specification
# (http://dev.w3.org/html5/markup/input.date.html), this value is formatted
# according to RFC 3339: http://tools.ietf.org/html/rfc3339#section-5.6
@date.setter
def date(self, date_value):
self.element.send_keys(date_value.strftime('%Y-%m-%d'))
@property
def time(self):
"""
Retrieve the element's string value
"""
return self.element.get_attribute('value')
# As per the W3C "time" element specification
# (http://dev.w3.org/html5/markup/input.time.html), this value is formatted
# according to RFC 3339: http://tools.ietf.org/html/rfc3339#section-5.6
@time.setter
def time(self, time_value):
self.element.send_keys(time_value.strftime('%H:%M:%S'))

View file

@ -0,0 +1,69 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from functools import wraps
import socket
def _find_marionette_in_args(*args, **kwargs):
try:
m = [a for a in args + tuple(kwargs.values()) if hasattr(a, 'session')][0]
except IndexError:
print("Can only apply decorator to function using a marionette object")
raise
return m
def do_process_check(func):
"""Decorator which checks the process status after the function has run."""
@wraps(func)
def _(*args, **kwargs):
try:
return func(*args, **kwargs)
except (socket.error, socket.timeout):
# In case of socket failures which will also include crashes of the
# application, make sure to handle those correctly.
m = _find_marionette_in_args(*args, **kwargs)
m._handle_socket_failure()
return _
def uses_marionette(func):
"""Decorator which creates a marionette session and deletes it
afterwards if one doesn't already exist.
"""
@wraps(func)
def _(*args, **kwargs):
m = _find_marionette_in_args(*args, **kwargs)
delete_session = False
if not m.session:
delete_session = True
m.start_session()
m.set_context(m.CONTEXT_CHROME)
ret = func(*args, **kwargs)
if delete_session:
m.delete_session()
return ret
return _
def using_context(context):
"""Decorator which allows a function to execute in certain scope
using marionette.using_context functionality and returns to old
scope once the function exits.
:param context: Either 'chrome' or 'content'.
"""
def wrap(func):
@wraps(func)
def inner(*args, **kwargs):
m = _find_marionette_in_args(*args, **kwargs)
with m.using_context(context):
return func(*args, **kwargs)
return inner
return wrap

View file

@ -0,0 +1,179 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import traceback
class MarionetteException(Exception):
"""Raised when a generic non-recoverable exception has occured."""
status = "webdriver error"
def __init__(self, message=None, cause=None, stacktrace=None):
"""Construct new MarionetteException instance.
:param message: An optional exception message.
:param cause: An optional tuple of three values giving
information about the root exception cause. Expected
tuple values are (type, value, traceback).
:param stacktrace: Optional string containing a stacktrace
(typically from a failed JavaScript execution) that will
be displayed in the exception's string representation.
"""
self.cause = cause
self.stacktrace = stacktrace
super(MarionetteException, self).__init__(message)
def __str__(self):
msg = str(self.message)
tb = None
if self.cause:
if type(self.cause) is tuple:
msg += ", caused by {0!r}".format(self.cause[0])
tb = self.cause[2]
else:
msg += ", caused by {}".format(self.cause)
if self.stacktrace:
st = "".join(["\t{}\n".format(x)
for x in self.stacktrace.splitlines()])
msg += "\nstacktrace:\n{}".format(st)
if tb:
msg += ': ' + "".join(traceback.format_tb(tb))
return msg
class ElementNotSelectableException(MarionetteException):
status = "element not selectable"
class ElementClickInterceptedException(MarionetteException):
status = "element click intercepted"
class InsecureCertificateException(MarionetteException):
status = "insecure certificate"
class InvalidArgumentException(MarionetteException):
status = "invalid argument"
class InvalidSessionIdException(MarionetteException):
status = "invalid session id"
class TimeoutException(MarionetteException):
status = "timeout"
class JavascriptException(MarionetteException):
status = "javascript error"
class NoSuchElementException(MarionetteException):
status = "no such element"
class NoSuchWindowException(MarionetteException):
status = "no such window"
class StaleElementException(MarionetteException):
status = "stale element reference"
class ScriptTimeoutException(MarionetteException):
status = "script timeout"
class ElementNotVisibleException(MarionetteException):
"""Deprecated. Will be removed with the release of Firefox 54."""
status = "element not visible"
def __init__(self,
message="Element is not currently visible and may not be manipulated",
stacktrace=None, cause=None):
super(ElementNotVisibleException, self).__init__(
message, cause=cause, stacktrace=stacktrace)
class ElementNotAccessibleException(MarionetteException):
status = "element not accessible"
class ElementNotInteractableException(MarionetteException):
status = "element not interactable"
class NoSuchFrameException(MarionetteException):
status = "no such frame"
class InvalidElementStateException(MarionetteException):
status = "invalid element state"
class NoAlertPresentException(MarionetteException):
status = "no such alert"
class InvalidCookieDomainException(MarionetteException):
status = "invalid cookie domain"
class UnableToSetCookieException(MarionetteException):
status = "unable to set cookie"
class InvalidElementCoordinates(MarionetteException):
status = "invalid element coordinates"
class InvalidSelectorException(MarionetteException):
status = "invalid selector"
class MoveTargetOutOfBoundsException(MarionetteException):
status = "move target out of bounds"
class SessionNotCreatedException(MarionetteException):
status = "session not created"
class UnexpectedAlertOpen(MarionetteException):
status = "unexpected alert open"
class UnknownCommandException(MarionetteException):
status = "unknown command"
class UnknownException(MarionetteException):
status = "unknown error"
class UnsupportedOperationException(MarionetteException):
status = "unsupported operation"
es_ = [e for e in locals().values() if type(e) == type and issubclass(e, MarionetteException)]
by_string = {e.status: e for e in es_}
def lookup(identifier):
"""Finds error exception class by associated Selenium JSON wire
protocol number code, or W3C WebDriver protocol string.
"""
return by_string.get(identifier, MarionetteException)

View file

@ -0,0 +1,311 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import errors
import types
from marionette import HTMLElement
"""This file provides a set of expected conditions for common use
cases when writing Marionette tests.
The conditions rely on explicit waits that retries conditions a number
of times until they are either successfully met, or they time out.
"""
class element_present(object):
"""Checks that a web element is present in the DOM of the current
context. This does not necessarily mean that the element is
visible.
You can select which element to be checked for presence by
supplying a locator::
el = Wait(marionette).until(expected.element_present(By.ID, "foo"))
Or by using a function/lambda returning an element::
el = Wait(marionette).\
until(expected.element_present(lambda m: m.find_element(By.ID, "foo")))
:param args: locator or function returning web element
:returns: the web element once it is located, or False
"""
def __init__(self, *args):
if len(args) == 1 and isinstance(args[0], types.FunctionType):
self.locator = args[0]
else:
self.locator = lambda m: m.find_element(*args)
def __call__(self, marionette):
return _find(marionette, self.locator)
class element_not_present(element_present):
"""Checks that a web element is not present in the DOM of the current
context.
You can select which element to be checked for lack of presence by
supplying a locator::
r = Wait(marionette).until(expected.element_not_present(By.ID, "foo"))
Or by using a function/lambda returning an element::
r = Wait(marionette).\
until(expected.element_present(lambda m: m.find_element(By.ID, "foo")))
:param args: locator or function returning web element
:returns: True if element is not present, or False if it is present
"""
def __init__(self, *args):
super(element_not_present, self).__init__(*args)
def __call__(self, marionette):
return not super(element_not_present, self).__call__(marionette)
class element_stale(object):
"""Check that the given element is no longer attached to DOM of the
current context.
This can be useful for waiting until an element is no longer
present.
Sample usage::
el = marionette.find_element(By.ID, "foo")
# ...
Wait(marionette).until(expected.element_stale(el))
:param element: the element to wait for
:returns: False if the element is still attached to the DOM, True
otherwise
"""
def __init__(self, element):
self.el = element
def __call__(self, marionette):
try:
# Calling any method forces a staleness check
self.el.is_enabled()
return False
except errors.StaleElementException:
return True
class elements_present(object):
"""Checks that web elements are present in the DOM of the current
context. This does not necessarily mean that the elements are
visible.
You can select which elements to be checked for presence by
supplying a locator::
els = Wait(marionette).until(expected.elements_present(By.TAG_NAME, "a"))
Or by using a function/lambda returning a list of elements::
els = Wait(marionette).\
until(expected.elements_present(lambda m: m.find_elements(By.TAG_NAME, "a")))
:param args: locator or function returning a list of web elements
:returns: list of web elements once they are located, or False
"""
def __init__(self, *args):
if len(args) == 1 and isinstance(args[0], types.FunctionType):
self.locator = args[0]
else:
self.locator = lambda m: m.find_elements(*args)
def __call__(self, marionette):
return _find(marionette, self.locator)
class elements_not_present(elements_present):
"""Checks that web elements are not present in the DOM of the
current context.
You can select which elements to be checked for not being present
by supplying a locator::
r = Wait(marionette).until(expected.elements_not_present(By.TAG_NAME, "a"))
Or by using a function/lambda returning a list of elements::
r = Wait(marionette).\
until(expected.elements_not_present(lambda m: m.find_elements(By.TAG_NAME, "a")))
:param args: locator or function returning a list of web elements
:returns: True if elements are missing, False if one or more are
present
"""
def __init__(self, *args):
super(elements_not_present, self).__init__(*args)
def __call__(self, marionette):
return not super(elements_not_present, self).__call__(marionette)
class element_displayed(object):
"""An expectation for checking that an element is visible.
Visibility means that the element is not only displayed, but also
has a height and width that is greater than 0 pixels.
Stale elements, meaning elements that have been detached from the
DOM of the current context are treated as not being displayed,
meaning this expectation is not analogous to the behaviour of
calling `is_displayed()` on an `HTMLElement`.
You can select which element to be checked for visibility by
supplying a locator::
displayed = Wait(marionette).until(expected.element_displayed(By.ID, "foo"))
Or by supplying an element::
el = marionette.find_element(By.ID, "foo")
displayed = Wait(marionette).until(expected.element_displayed(el))
:param args: locator or web element
:returns: True if element is displayed, False if hidden
"""
def __init__(self, *args):
self.el = None
if len(args) == 1 and isinstance(args[0], HTMLElement):
self.el = args[0]
else:
self.locator = lambda m: m.find_element(*args)
def __call__(self, marionette):
if self.el is None:
self.el = _find(marionette, self.locator)
if not self.el:
return False
try:
return self.el.is_displayed()
except errors.StaleElementException:
return False
class element_not_displayed(element_displayed):
"""An expectation for checking that an element is not visible.
Visibility means that the element is not only displayed, but also
has a height and width that is greater than 0 pixels.
Stale elements, meaning elements that have been detached fom the
DOM of the current context are treated as not being displayed,
meaning this expectation is not analogous to the behaviour of
calling `is_displayed()` on an `HTMLElement`.
You can select which element to be checked for visibility by
supplying a locator::
hidden = Wait(marionette).until(expected.element_not_displayed(By.ID, "foo"))
Or by supplying an element::
el = marionette.find_element(By.ID, "foo")
hidden = Wait(marionette).until(expected.element_not_displayed(el))
:param args: locator or web element
:returns: True if element is hidden, False if displayed
"""
def __init__(self, *args):
super(element_not_displayed, self).__init__(*args)
def __call__(self, marionette):
return not super(element_not_displayed, self).__call__(marionette)
class element_selected(object):
"""An expectation for checking that the given element is selected.
:param element: the element to be selected
:returns: True if element is selected, False otherwise
"""
def __init__(self, element):
self.el = element
def __call__(self, marionette):
return self.el.is_selected()
class element_not_selected(element_selected):
"""An expectation for checking that the given element is not
selected.
:param element: the element to not be selected
:returns: True if element is not selected, False if selected
"""
def __init__(self, element):
super(element_not_selected, self).__init__(element)
def __call__(self, marionette):
return not super(element_not_selected, self).__call__(marionette)
class element_enabled(object):
"""An expectation for checking that the given element is enabled.
:param element: the element to check if enabled
:returns: True if element is enabled, False otherwise
"""
def __init__(self, element):
self.el = element
def __call__(self, marionette):
return self.el.is_enabled()
class element_not_enabled(element_enabled):
"""An expectation for checking that the given element is disabled.
:param element: the element to check if disabled
:returns: True if element is disabled, False if enabled
"""
def __init__(self, element):
super(element_not_enabled, self).__init__(element)
def __call__(self, marionette):
return not super(element_not_enabled, self).__call__(marionette)
def _find(marionette, func):
el = None
try:
el = func(marionette)
except errors.NoSuchElementException:
pass
if el is None:
return False
return el

View file

@ -0,0 +1,467 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
import os
import sys
import tempfile
import time
from copy import deepcopy
import mozversion
from mozprofile import Profile
from mozrunner import Runner, FennecEmulatorRunner
class GeckoInstance(object):
required_prefs = {
# Increase the APZ content response timeout in tests to 1 minute.
# This is to accommodate the fact that test environments tends to be slower
# than production environments (with the b2g emulator being the slowest of them
# all), resulting in the production timeout value sometimes being exceeded
# and causing false-positive test failures. See bug 1176798, bug 1177018,
# bug 1210465.
"apz.content_response_timeout": 60000,
# Do not send Firefox health reports to the production server
"datareporting.healthreport.documentServerURI": "http://%(server)s/dummy/healthreport/",
"datareporting.healthreport.about.reportUrl": "http://%(server)s/dummy/abouthealthreport/",
# Do not show datareporting policy notifications which can interfer with tests
"datareporting.policy.dataSubmissionPolicyBypassNotification": True,
# Until Bug 1238095 is fixed, we have to enable CPOWs in order
# for Marionette tests to work properly.
"dom.ipc.cpows.forbid-unsafe-from-browser": False,
"dom.ipc.reportProcessHangs": False,
# No slow script dialogs
"dom.max_chrome_script_run_time": 0,
"dom.max_script_run_time": 0,
# Only load extensions from the application and user profile
# AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
"extensions.autoDisableScopes": 0,
"extensions.enabledScopes": 5,
# don't block add-ons for e10s
"extensions.e10sBlocksEnabling": False,
# Disable metadata caching for installed add-ons by default
"extensions.getAddons.cache.enabled": False,
# Disable intalling any distribution add-ons
"extensions.installDistroAddons": False,
"extensions.showMismatchUI": False,
# Turn off extension updates so they don't bother tests
"extensions.update.enabled": False,
"extensions.update.notifyUser": False,
# Make sure opening about:addons won"t hit the network
"extensions.webservice.discoverURL": "http://%(server)s/dummy/discoveryURL",
# Allow the application to have focus even it runs in the background
"focusmanager.testmode": True,
# Disable useragent updates
"general.useragent.updates.enabled": False,
# Always use network provider for geolocation tests
# so we bypass the OSX dialog raised by the corelocation provider
"geo.provider.testing": True,
# Do not scan Wifi
"geo.wifi.scan": False,
# No hang monitor
"hangmonitor.timeout": 0,
"javascript.options.showInConsole": True,
"marionette.defaultPrefs.enabled": True,
"media.volume_scale": "0.01",
# Make sure the disk cache doesn't get auto disabled
"network.http.bypass-cachelock-threshold": 200000,
# Do not prompt for temporary redirects
"network.http.prompt-temp-redirect": False,
# Disable speculative connections so they aren"t reported as leaking when they"re
# hanging around
"network.http.speculative-parallel-limit": 0,
# Do not automatically switch between offline and online
"network.manage-offline-status": False,
# Make sure SNTP requests don't hit the network
"network.sntp.pools": "%(server)s",
# Tests don't wait for the notification button security delay
"security.notification_enable_delay": 0,
# Ensure blocklist updates don't hit the network
"services.settings.server": "http://%(server)s/dummy/blocklist/",
# Disable password capture, so that tests that include forms aren"t
# influenced by the presence of the persistent doorhanger notification
"signon.rememberSignons": False,
# Prevent starting into safe mode after application crashes
"toolkit.startup.max_resumed_crashes": -1,
# We want to collect telemetry, but we don't want to send in the results
"toolkit.telemetry.server": "https://%(server)s/dummy/telemetry/",
}
def __init__(self, host=None, port=None, bin=None, profile=None, addons=None,
app_args=None, symbols_path=None, gecko_log=None, prefs=None,
workspace=None, verbose=0):
self.runner_class = Runner
self.app_args = app_args or []
self.runner = None
self.symbols_path = symbols_path
self.binary = bin
self.marionette_host = host
self.marionette_port = port
# Alternative to default temporary directory
self.workspace = workspace
self.addons = addons
# Check if it is a Profile object or a path to profile
self.profile = None
if isinstance(profile, Profile):
self.profile = profile
else:
self.profile_path = profile
self.prefs = prefs
self.required_prefs = deepcopy(self.required_prefs)
if prefs:
self.required_prefs.update(prefs)
self._gecko_log_option = gecko_log
self._gecko_log = None
self.verbose = verbose
@property
def gecko_log(self):
if self._gecko_log:
return self._gecko_log
path = self._gecko_log_option
if path != "-":
if path is None:
path = "gecko.log"
elif os.path.isdir(path):
fname = "gecko-{}.log".format(time.time())
path = os.path.join(path, fname)
path = os.path.realpath(path)
if os.access(path, os.F_OK):
os.remove(path)
self._gecko_log = path
return self._gecko_log
def _update_profile(self):
profile_args = {"preferences": deepcopy(self.required_prefs)}
profile_args["preferences"]["marionette.defaultPrefs.port"] = self.marionette_port
if self.prefs:
profile_args["preferences"].update(self.prefs)
if self.verbose:
level = "TRACE" if self.verbose >= 2 else "DEBUG"
profile_args["preferences"]["marionette.logging"] = level
if "-jsdebugger" in self.app_args:
profile_args["preferences"].update({
"devtools.browsertoolbox.panel": "jsdebugger",
"devtools.debugger.remote-enabled": True,
"devtools.chrome.enabled": True,
"devtools.debugger.prompt-connection": False,
"marionette.debugging.clicktostart": True,
})
if self.addons:
profile_args["addons"] = self.addons
if hasattr(self, "profile_path") and self.profile is None:
if not self.profile_path:
if self.workspace:
profile_args["profile"] = tempfile.mkdtemp(
suffix=".mozrunner-{:.0f}".format(time.time()),
dir=self.workspace)
self.profile = Profile(**profile_args)
else:
profile_args["path_from"] = self.profile_path
profile_name = "{}-{:.0f}".format(
os.path.basename(self.profile_path),
time.time()
)
if self.workspace:
profile_args["path_to"] = os.path.join(self.workspace,
profile_name)
self.profile = Profile.clone(**profile_args)
@classmethod
def create(cls, app=None, *args, **kwargs):
try:
if not app and kwargs["bin"] is not None:
app_id = mozversion.get_version(binary=kwargs["bin"])["application_id"]
app = app_ids[app_id]
instance_class = apps[app]
except (IOError, KeyError):
exc, val, tb = sys.exc_info()
msg = 'Application "{0}" unknown (should be one of {1})'
raise NotImplementedError, msg.format(app, apps.keys()), tb
return instance_class(*args, **kwargs)
def start(self):
self._update_profile()
self.runner = self.runner_class(**self._get_runner_args())
self.runner.start()
def _get_runner_args(self):
process_args = {
"processOutputLine": [NullOutput()],
}
if self.gecko_log == "-":
process_args["stream"] = sys.stdout
else:
process_args["logfile"] = self.gecko_log
env = os.environ.copy()
# environment variables needed for crashreporting
# https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
env.update({"MOZ_CRASHREPORTER": "1",
"MOZ_CRASHREPORTER_NO_REPORT": "1",
"MOZ_CRASHREPORTER_SHUTDOWN": "1",
})
return {
"binary": self.binary,
"profile": self.profile,
"cmdargs": ["-no-remote", "-marionette"] + self.app_args,
"env": env,
"symbols_path": self.symbols_path,
"process_args": process_args
}
def close(self, restart=False):
if not restart:
self.profile = None
if self.runner:
self.runner.stop()
self.runner.cleanup()
def restart(self, prefs=None, clean=True):
self.close(restart=True)
if clean and self.profile:
self.profile.cleanup()
self.profile = None
if prefs:
self.prefs = prefs
else:
self.prefs = None
self.start()
class FennecInstance(GeckoInstance):
fennec_prefs = {
# Enable output of dump()
"browser.dom.window.dump.enabled": True,
# Disable Android snippets
"browser.snippets.enabled": False,
"browser.snippets.syncPromo.enabled": False,
"browser.snippets.firstrunHomepage.enabled": False,
# Disable safebrowsing components
"browser.safebrowsing.downloads.enabled": False,
# Do not restore the last open set of tabs if the browser has crashed
"browser.sessionstore.resume_from_crash": False,
# Disable e10s by default
"browser.tabs.remote.autostart.1": False,
"browser.tabs.remote.autostart.2": False,
"browser.tabs.remote.autostart": False,
# Do not allow background tabs to be zombified, otherwise for tests that
# open additional tabs, the test harness tab itself might get unloaded
"browser.tabs.disableBackgroundZombification": True,
}
def __init__(self, emulator_binary=None, avd_home=None, avd=None,
adb_path=None, serial=None, connect_to_running_emulator=False,
package_name=None, *args, **kwargs):
super(FennecInstance, self).__init__(*args, **kwargs)
self.required_prefs.update(FennecInstance.fennec_prefs)
self.runner_class = FennecEmulatorRunner
# runner args
self._package_name = package_name
self.emulator_binary = emulator_binary
self.avd_home = avd_home
self.adb_path = adb_path
self.avd = avd
self.serial = serial
self.connect_to_running_emulator = connect_to_running_emulator
@property
def package_name(self):
"""
Name of app to run on emulator.
Note that FennecInstance does not use self.binary
"""
if self._package_name is None:
self._package_name = "org.mozilla.fennec"
user = os.getenv("USER")
if user:
self._package_name += "_" + user
return self._package_name
def start(self):
self._update_profile()
self.runner = self.runner_class(**self._get_runner_args())
try:
if self.connect_to_running_emulator:
self.runner.device.connect()
self.runner.start()
except Exception as e:
exc, val, tb = sys.exc_info()
message = "Error possibly due to runner or device args: {}"
raise exc, message.format(e.message), tb
# gecko_log comes from logcat when running with device/emulator
logcat_args = {
"filterspec": "Gecko",
"serial": self.runner.device.dm._deviceSerial
}
if self.gecko_log == "-":
logcat_args["stream"] = sys.stdout
else:
logcat_args["logfile"] = self.gecko_log
self.runner.device.start_logcat(**logcat_args)
# forward marionette port (localhost:2828)
self.runner.device.dm.forward(
local="tcp:{}".format(self.marionette_port),
remote="tcp:{}".format(self.marionette_port))
def _get_runner_args(self):
process_args = {
"processOutputLine": [NullOutput()],
}
runner_args = {
"app": self.package_name,
"avd_home": self.avd_home,
"adb_path": self.adb_path,
"binary": self.emulator_binary,
"profile": self.profile,
"cmdargs": ["-marionette"] + self.app_args,
"symbols_path": self.symbols_path,
"process_args": process_args,
"logdir": self.workspace or os.getcwd(),
"serial": self.serial,
}
if self.avd:
runner_args["avd"] = self.avd
return runner_args
def close(self, restart=False):
super(FennecInstance, self).close(restart)
if self.runner and self.runner.device.connected:
self.runner.device.dm.remove_forward(
"tcp:{}".format(self.marionette_port))
class DesktopInstance(GeckoInstance):
desktop_prefs = {
# Disable application updates
"app.update.enabled": False,
# Enable output of dump()
"browser.dom.window.dump.enabled": True,
# Indicate that the download panel has been shown once so that whichever
# download test runs first doesn"t show the popup inconsistently
"browser.download.panel.shown": True,
# Do not show the EULA notification which can interfer with tests
"browser.EULA.override": True,
# Turn off about:newtab and make use of about:blank instead for new opened tabs
"browser.newtabpage.enabled": False,
# Assume the about:newtab page"s intro panels have been shown to not depend on
# which test runs first and happens to open about:newtab
"browser.newtabpage.introShown": True,
# Background thumbnails in particular cause grief, and disabling thumbnails
# in general can"t hurt - we re-enable them when tests need them
"browser.pagethumbnails.capturing_disabled": True,
# Avoid performing Reader Mode intros during tests
"browser.reader.detectedFirstArticle": True,
# Disable safebrowsing components
"browser.safebrowsing.blockedURIs.enabled": False,
"browser.safebrowsing.downloads.enabled": False,
"browser.safebrowsing.forbiddenURIs.enabled": False,
"browser.safebrowsing.malware.enabled": False,
"browser.safebrowsing.phishing.enabled": False,
# Disable updates to search engines
"browser.search.update": False,
# Do not restore the last open set of tabs if the browser has crashed
"browser.sessionstore.resume_from_crash": False,
# Don't check for the default web browser during startup
"browser.shell.checkDefaultBrowser": False,
# Disable e10s by default
"browser.tabs.remote.autostart.1": False,
"browser.tabs.remote.autostart.2": False,
"browser.tabs.remote.autostart": False,
# Needed for branded builds to prevent opening a second tab on startup
"browser.startup.homepage_override.mstone": "ignore",
# Start with a blank page by default
"browser.startup.page": 0,
# Disable tab animation
"browser.tabs.animate": False,
# Do not warn on exit when multiple tabs are open
"browser.tabs.warnOnClose": False,
# Do not warn when closing all other open tabs
"browser.tabs.warnOnCloseOtherTabs": False,
# Do not warn when multiple tabs will be opened
"browser.tabs.warnOnOpen": False,
# Disable the UI tour
"browser.uitour.enabled": False,
# Disable first-run welcome page
"startup.homepage_welcome_url": "about:blank",
"startup.homepage_welcome_url.additional": "",
}
def __init__(self, *args, **kwargs):
super(DesktopInstance, self).__init__(*args, **kwargs)
self.required_prefs.update(DesktopInstance.desktop_prefs)
class NullOutput(object):
def __call__(self, line):
pass
apps = {
'fennec': FennecInstance,
'fxdesktop': DesktopInstance,
}
app_ids = {
'{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'fennec',
'{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'fxdesktop',
}

View file

@ -0,0 +1,93 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette import MultiActions, Actions
def smooth_scroll(marionette_session, start_element, axis, direction,
length, increments=None, wait_period=None, scroll_back=None):
"""
:param axis: y or x
:param direction: 0 for positive, and -1 for negative
:param length: total length of scroll scroll
:param increments: Amount to be moved per scrolling
:param wait_period: Seconds to wait between scrolling
:param scroll_back: Scroll back to original view?
"""
if axis not in ["x", "y"]:
raise Exception("Axis must be either 'x' or 'y'")
if direction not in [-1, 0]:
raise Exception("Direction must either be -1 negative or 0 positive")
increments = increments or 100
wait_period = wait_period or 0.05
scroll_back = scroll_back or False
current = 0
if axis is "x":
if direction is -1:
offset = [-increments, 0]
else:
offset = [increments, 0]
else:
if direction is -1:
offset = [0, -increments]
else:
offset = [0, increments]
action = Actions(marionette_session)
action.press(start_element)
while (current < length):
current += increments
action.move_by_offset(*offset).wait(wait_period)
if scroll_back:
offset = [-value for value in offset]
while (current > 0):
current -= increments
action.move_by_offset(*offset).wait(wait_period)
action.release()
action.perform()
def pinch(marionette_session, element, x1, y1, x2, y2, x3, y3, x4, y4, duration=200):
"""
:param element: target
:param x1, y1: 1st finger starting position relative to the target
:param x3, y3: 1st finger ending position relative to the target
:param x2, y2: 2nd finger starting position relative to the target
:param x4, y4: 2nd finger ending position relative to the target
:param duration: Amount of time in milliseconds to complete the pinch.
"""
time = 0
time_increment = 10
if time_increment >= duration:
time_increment = duration
move_x1 = time_increment*1.0/duration * (x3 - x1)
move_y1 = time_increment*1.0/duration * (y3 - y1)
move_x2 = time_increment*1.0/duration * (x4 - x2)
move_y2 = time_increment*1.0/duration * (y4 - y2)
multiAction = MultiActions(marionette_session)
action1 = Actions(marionette_session)
action2 = Actions(marionette_session)
action1.press(element, x1, y1)
action2.press(element, x2, y2)
while (time < duration):
time += time_increment
action1.move_by_offset(move_x1, move_y1).wait(time_increment/1000)
action2.move_by_offset(move_x2, move_y2).wait(time_increment/1000)
action1.release()
action2.release()
multiAction.add(action1).add(action2).perform()
def long_press_without_contextmenu(marionette_session, element, time_in_seconds, x=None, y=None):
"""
:param element: The element to press.
:param time_in_seconds: Time in seconds to wait before releasing the press.
#x: Optional, x-coordinate to tap, relative to the top-left corner of the element.
#y: Optional, y-coordinate to tap, relative to the top-leftcorner of the element.
"""
action = Actions(marionette_session)
action.press(element, x, y)
action.move_by_offset(0, 0)
action.wait(time_in_seconds)
action.release()
action.perform()

View file

@ -0,0 +1,84 @@
# copyright 2008-2009 WebDriver committers
# Copyright 2008-2009 Google Inc.
#
# Licensed under the Apache License Version 2.0 = uthe "License")
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http //www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing software
# distributed under the License is distributed on an "AS IS" BASIS
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class Keys(object):
NULL = u'\ue000'
CANCEL = u'\ue001' # ^break
HELP = u'\ue002'
BACK_SPACE = u'\ue003'
TAB = u'\ue004'
CLEAR = u'\ue005'
RETURN = u'\ue006'
ENTER = u'\ue007'
SHIFT = u'\ue008'
LEFT_SHIFT = u'\ue008' # alias
CONTROL = u'\ue009'
LEFT_CONTROL = u'\ue009' # alias
ALT = u'\ue00a'
LEFT_ALT = u'\ue00a' # alias
PAUSE = u'\ue00b'
ESCAPE = u'\ue00c'
SPACE = u'\ue00d'
PAGE_UP = u'\ue00e'
PAGE_DOWN = u'\ue00f'
END = u'\ue010'
HOME = u'\ue011'
LEFT = u'\ue012'
ARROW_LEFT = u'\ue012' # alias
UP = u'\ue013'
ARROW_UP = u'\ue013' # alias
RIGHT = u'\ue014'
ARROW_RIGHT = u'\ue014' # alias
DOWN = u'\ue015'
ARROW_DOWN = u'\ue015' # alias
INSERT = u'\ue016'
DELETE = u'\ue017'
SEMICOLON = u'\ue018'
EQUALS = u'\ue019'
NUMPAD0 = u'\ue01a' # numbe pad keys
NUMPAD1 = u'\ue01b'
NUMPAD2 = u'\ue01c'
NUMPAD3 = u'\ue01d'
NUMPAD4 = u'\ue01e'
NUMPAD5 = u'\ue01f'
NUMPAD6 = u'\ue020'
NUMPAD7 = u'\ue021'
NUMPAD8 = u'\ue022'
NUMPAD9 = u'\ue023'
MULTIPLY = u'\ue024'
ADD = u'\ue025'
SEPARATOR = u'\ue026'
SUBTRACT = u'\ue027'
DECIMAL = u'\ue028'
DIVIDE = u'\ue029'
F1 = u'\ue031' # function keys
F2 = u'\ue032'
F3 = u'\ue033'
F4 = u'\ue034'
F5 = u'\ue035'
F6 = u'\ue036'
F7 = u'\ue037'
F8 = u'\ue038'
F9 = u'\ue039'
F10 = u'\ue03a'
F11 = u'\ue03b'
F12 = u'\ue03c'
META = u'\ue03d'
COMMAND = u'\ue03d'

View file

@ -0,0 +1,54 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
class L10n(object):
"""An API which allows Marionette to handle localized content.
The `localization`_ of UI elements in Gecko based applications is done via
entities and properties. For static values entities are used, which are located
in .dtd files. Whereby for dynamically updated content the values come from
.property files. Both types of elements can be identifed via a unique id,
and the translated content retrieved.
For example::
from marionette_driver.localization import L10n
l10n = L10n(marionette)
l10n.localize_entity(["chrome://global/locale/about.dtd"], "about.version")
l10n.localize_property(["chrome://global/locale/findbar.properties"], "FastFind"))
.. _localization: https://mzl.la/2eUMjyF
"""
def __init__(self, marionette):
self._marionette = marionette
def localize_entity(self, dtd_urls, entity_id):
"""Retrieve the localized string for the specified entity id.
:param dtd_urls: List of .dtd URLs which will be used to search for the entity.
:param entity_id: ID of the entity to retrieve the localized string for.
:returns: The localized string for the requested entity.
:raises: :exc:`NoSuchElementException`
"""
body = {"urls": dtd_urls, "id": entity_id}
return self._marionette._send_message("localization:l10n:localizeEntity",
body, key="value")
def localize_property(self, properties_urls, property_id):
"""Retrieve the localized string for the specified property id.
:param properties_urls: List of .properties URLs which will be used to
search for the property.
:param property_id: ID of the property to retrieve the localized string for.
:returns: The localized string for the requested property.
:raises: :exc:`NoSuchElementException`
"""
body = {"urls": properties_urls, "id": property_id}
return self._marionette._send_message("localization:l10n:localizeProperty",
body, key="value")

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,227 @@
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
class SelectionManager(object):
'''Interface for manipulating the selection and carets of the element.
We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as
caret for short.
Simple usage example:
::
element = marionette.find_element(By.ID, 'input')
sel = SelectionManager(element)
sel.move_caret_to_front()
'''
def __init__(self, element):
self.element = element
def _input_or_textarea(self):
'''Return True if element is either <input> or <textarea>.'''
return self.element.tag_name in ('input', 'textarea')
def js_selection_cmd(self):
'''Return a command snippet to get selection object.
If the element is <input> or <textarea>, return the selection object
associated with it. Otherwise, return the current selection object.
Note: "element" must be provided as the first argument to
execute_script().
'''
if self._input_or_textarea():
# We must unwrap sel so that DOMRect could be returned to Python
# side.
return '''var sel = arguments[0].editor.selection;'''
else:
return '''var sel = window.getSelection();'''
def move_cursor_by_offset(self, offset, backward=False):
'''Move cursor in the element by character offset.
:param offset: Move the cursor to the direction by offset characters.
:param backward: Optional, True to move backward; Default to False to
move forward.
'''
cmd = self.js_selection_cmd() + '''
for (let i = 0; i < {0}; ++i) {{
sel.modify("move", "{1}", "character");
}}
'''.format(offset, 'backward' if backward else 'forward')
self.element.marionette.execute_script(
cmd, script_args=[self.element], sandbox='system')
def move_cursor_to_front(self):
'''Move cursor in the element to the front of the content.'''
if self._input_or_textarea():
cmd = '''arguments[0].setSelectionRange(0, 0);'''
else:
cmd = '''var sel = window.getSelection();
sel.collapse(arguments[0].firstChild, 0);'''
self.element.marionette.execute_script(cmd, script_args=[self.element])
def move_cursor_to_end(self):
'''Move cursor in the element to the end of the content.'''
if self._input_or_textarea():
cmd = '''var len = arguments[0].value.length;
arguments[0].setSelectionRange(len, len);'''
else:
cmd = '''var sel = window.getSelection();
sel.collapse(arguments[0].lastChild, arguments[0].lastChild.length);'''
self.element.marionette.execute_script(cmd, script_args=[self.element])
def selection_rect_list(self, idx):
'''Return the selection's DOMRectList object for the range at given idx.
If the element is either <input> or <textarea>, return the DOMRectList of
the range at given idx of the selection within the element. Otherwise,
return the DOMRectList of the of the range at given idx of current selection.
'''
cmd = self.js_selection_cmd() +\
'''return sel.getRangeAt({}).getClientRects();'''.format(idx)
return self.element.marionette.execute_script(cmd,
script_args=[self.element],
sandbox='system')
def range_count(self):
'''Get selection's range count'''
cmd = self.js_selection_cmd() +\
'''return sel.rangeCount;'''
return self.element.marionette.execute_script(cmd,
script_args=[self.element],
sandbox='system')
def _selection_location_helper(self, location_type):
'''Return the start and end location of the selection in the element.
Return a tuple containing two pairs of (x, y) coordinates of the start
and end locations in the element. The coordinates are relative to the
top left-hand corner of the element. Both ltr and rtl directions are
considered.
'''
range_count = self.range_count()
first_rect_list = self.selection_rect_list(0)
last_rect_list = self.selection_rect_list(range_count - 1)
last_list_length = last_rect_list['length']
first_rect, last_rect = first_rect_list['0'], last_rect_list[str(last_list_length - 1)]
origin_x, origin_y = self.element.rect['x'], self.element.rect['y']
if self.element.get_property('dir') == 'rtl': # such as Arabic
start_pos, end_pos = 'right', 'left'
else:
start_pos, end_pos = 'left', 'right'
# Calculate y offset according to different needs.
if location_type == 'center':
start_y_offset = first_rect['height'] / 2.0
end_y_offset = last_rect['height'] / 2.0
elif location_type == 'caret':
# Selection carets' tip are below the bottom of the two ends of the
# selection. Add 5px to y should be sufficient to locate them.
caret_tip_y_offset = 5
start_y_offset = first_rect['height'] + caret_tip_y_offset
end_y_offset = last_rect['height'] + caret_tip_y_offset
else:
start_y_offset = end_y_offset = 0
caret1_x = first_rect[start_pos] - origin_x
caret1_y = first_rect['top'] + start_y_offset - origin_y
caret2_x = last_rect[end_pos] - origin_x
caret2_y = last_rect['top'] + end_y_offset - origin_y
return ((caret1_x, caret1_y), (caret2_x, caret2_y))
def selection_location(self):
'''Return the start and end location of the selection in the element.
Return a tuple containing two pairs of (x, y) coordinates of the start
and end of the selection. The coordinates are relative to the top
left-hand corner of the element. Both ltr and rtl direction are
considered.
'''
return self._selection_location_helper('center')
def carets_location(self):
'''Return a pair of the two carets' location.
Return a tuple containing two pairs of (x, y) coordinates of the two
carets' tip. The coordinates are relative to the top left-hand corner of
the element. Both ltr and rtl direction are considered.
'''
return self._selection_location_helper('caret')
def cursor_location(self):
'''Return the blanking cursor's center location within the element.
Return (x, y) coordinates of the cursor's center relative to the top
left-hand corner of the element.
'''
return self._selection_location_helper('center')[0]
def first_caret_location(self):
'''Return the first caret's location.
Return (x, y) coordinates of the first caret's tip relative to the top
left-hand corner of the element.
'''
return self.carets_location()[0]
def second_caret_location(self):
'''Return the second caret's location.
Return (x, y) coordinates of the second caret's tip relative to the top
left-hand corner of the element.
'''
return self.carets_location()[1]
def select_all(self):
'''Select all the content in the element.'''
if self._input_or_textarea():
cmd = '''var len = arguments[0].value.length;
arguments[0].focus();
arguments[0].setSelectionRange(0, len);'''
else:
cmd = '''var range = document.createRange();
range.setStart(arguments[0].firstChild, 0);
range.setEnd(arguments[0].lastChild, arguments[0].lastChild.length);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);'''
self.element.marionette.execute_script(cmd, script_args=[self.element])
@property
def content(self):
'''Return all the content of the element.'''
if self._input_or_textarea():
return self.element.get_property('value')
else:
return self.element.text
@property
def selected_content(self):
'''Return the selected portion of the content in the element.'''
cmd = self.js_selection_cmd() +\
'''return sel.toString();'''
return self.element.marionette.execute_script(cmd,
script_args=[self.element],
sandbox='system')

View file

@ -0,0 +1,98 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import errors
DEFAULT_SCRIPT_TIMEOUT = 30
DEFAULT_PAGE_LOAD_TIMEOUT = 300
DEFAULT_IMPLICIT_WAIT_TIMEOUT = 0
class Timeouts(object):
"""Manage timeout settings in the Marionette session.
Usage::
marionette = Marionette(...)
marionette.start_session()
marionette.timeout.page_load = 10
marionette.timeout.page_load
# => 10
"""
def __init__(self, marionette):
self._marionette = marionette
def _set(self, name, sec):
ms = sec * 1000
try:
self._marionette._send_message("setTimeouts", {name: ms})
except errors.UnknownCommandException:
# remove when 55 is stable
self._marionette._send_message("timeouts", {"type": name, "ms": ms})
def _get(self, name):
ms = self._marionette._send_message("getTimeouts", key=name)
return ms / 1000
@property
def script(self):
"""Get the session's script timeout. This specifies the time
to wait for injected scripts to finished before interrupting
them. It is by default 30 seconds.
"""
return self._get("script")
@script.setter
def script(self, sec):
"""Set the session's script timeout. This specifies the time
to wait for injected scripts to finish before interrupting them.
"""
self._set("script", sec)
@property
def page_load(self):
"""Get the session's page load timeout. This specifies the time
to wait for the page loading to complete. It is by default 5
minutes (or 300 seconds).
"""
return self._get("page load")
@page_load.setter
def page_load(self, sec):
"""Set the session's page load timeout. This specifies the time
to wait for the page loading to complete.
"""
self._set("page load", sec)
@property
def implicit(self):
"""Get the session's implicit wait timeout. This specifies the
time to wait for the implicit element location strategy when
retrieving elements. It is by default disabled (0 seconds).
"""
return self._get("implicit")
@implicit.setter
def implicit(self, sec):
"""Set the session's implicit wait timeout. This specifies the
time to wait for the implicit element location strategy when
retrieving elements.
"""
self._set("implicit", sec)
def reset(self):
"""Resets timeouts to their default values."""
self.script = DEFAULT_SCRIPT_TIMEOUT
self.page_load = DEFAULT_PAGE_LOAD_TIMEOUT
self.implicit = DEFAULT_IMPLICIT_WAIT_TIMEOUT

View file

@ -0,0 +1,300 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import json
import socket
import time
class SocketTimeout(object):
def __init__(self, socket, timeout):
self.sock = socket
self.timeout = timeout
self.old_timeout = None
def __enter__(self):
self.old_timeout = self.sock.gettimeout()
self.sock.settimeout(self.timeout)
def __exit__(self, *args, **kwargs):
self.sock.settimeout(self.old_timeout)
class Message(object):
def __init__(self, msgid):
self.id = msgid
def __eq__(self, other):
return self.id == other.id
def __ne__(self, other):
return not self.__eq__(other)
class Command(Message):
TYPE = 0
def __init__(self, msgid, name, params):
Message.__init__(self, msgid)
self.name = name
self.params = params
def __str__(self):
return "<Command id={0}, name={1}, params={2}>".format(self.id, self.name, self.params)
def to_msg(self):
msg = [Command.TYPE, self.id, self.name, self.params]
return json.dumps(msg)
@staticmethod
def from_msg(payload):
data = json.loads(payload)
assert data[0] == Command.TYPE
cmd = Command(data[1], data[2], data[3])
return cmd
class Response(Message):
TYPE = 1
def __init__(self, msgid, error, result):
Message.__init__(self, msgid)
self.error = error
self.result = result
def __str__(self):
return "<Response id={0}, error={1}, result={2}>".format(self.id, self.error, self.result)
def to_msg(self):
msg = [Response.TYPE, self.id, self.error, self.result]
return json.dumps(msg)
@staticmethod
def from_msg(payload):
data = json.loads(payload)
assert data[0] == Response.TYPE
return Response(data[1], data[2], data[3])
class Proto2Command(Command):
"""Compatibility shim that marshals messages from a protocol level
2 and below remote into ``Command`` objects.
"""
def __init__(self, name, params):
Command.__init__(self, None, name, params)
class Proto2Response(Response):
"""Compatibility shim that marshals messages from a protocol level
2 and below remote into ``Response`` objects.
"""
def __init__(self, error, result):
Response.__init__(self, None, error, result)
@staticmethod
def from_data(data):
err, res = None, None
if "error" in data:
err = data
else:
res = data
return Proto2Response(err, res)
class TcpTransport(object):
"""Socket client that communciates with Marionette via TCP.
It speaks the protocol of the remote debugger in Gecko, in which
messages are always preceded by the message length and a colon, e.g.:
7:MESSAGE
On top of this protocol it uses a Marionette message format, that
depending on the protocol level offered by the remote server, varies.
Supported protocol levels are 1 and above.
"""
max_packet_length = 4096
def __init__(self, addr, port, socket_timeout=60.0):
"""If `socket_timeout` is `0` or `0.0`, non-blocking socket mode
will be used. Setting it to `1` or `None` disables timeouts on
socket operations altogether.
"""
self.addr = addr
self.port = port
self._socket_timeout = socket_timeout
self.protocol = 1
self.application_type = None
self.last_id = 0
self.expected_response = None
self.sock = None
@property
def socket_timeout(self):
return self._socket_timeout
@socket_timeout.setter
def socket_timeout(self, value):
if self.sock:
self.sock.settimeout(value)
self._socket_timeout = value
def _unmarshal(self, packet):
msg = None
# protocol 3 and above
if self.protocol >= 3:
typ = int(packet[1])
if typ == Command.TYPE:
msg = Command.from_msg(packet)
elif typ == Response.TYPE:
msg = Response.from_msg(packet)
# protocol 2 and below
else:
data = json.loads(packet)
msg = Proto2Response.from_data(data)
return msg
def receive(self, unmarshal=True):
"""Wait for the next complete response from the remote.
:param unmarshal: Default is to deserialise the packet and
return a ``Message`` type. Setting this to false will return
the raw packet.
"""
now = time.time()
data = ""
bytes_to_recv = 10
while self.socket_timeout is None or (time.time() - now < self.socket_timeout):
try:
chunk = self.sock.recv(bytes_to_recv)
data += chunk
except socket.timeout:
pass
else:
if not chunk:
raise socket.error("No data received over socket")
sep = data.find(":")
if sep > -1:
length = data[0:sep]
remaining = data[sep + 1:]
if len(remaining) == int(length):
if unmarshal:
msg = self._unmarshal(remaining)
self.last_id = msg.id
if self.protocol >= 3:
self.last_id = msg.id
# keep reading incoming responses until
# we receive the user's expected response
if isinstance(msg, Response) and msg != self.expected_response:
return self.receive(unmarshal)
return msg
else:
return remaining
bytes_to_recv = int(length) - len(remaining)
raise socket.timeout("Connection timed out after {}s".format(self.socket_timeout))
def connect(self):
"""Connect to the server and process the hello message we expect
to receive in response.
Returns a tuple of the protocol level and the application type.
"""
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.socket_timeout)
self.sock.connect((self.addr, self.port))
except:
# Unset self.sock so that the next attempt to send will cause
# another connection attempt.
self.sock = None
raise
with SocketTimeout(self.sock, 2.0):
# first packet is always a JSON Object
# which we can use to tell which protocol level we are at
raw = self.receive(unmarshal=False)
hello = json.loads(raw)
self.protocol = hello.get("marionetteProtocol", 1)
self.application_type = hello.get("applicationType")
return (self.protocol, self.application_type)
def send(self, obj):
"""Send message to the remote server. Allowed input is a
``Message`` instance or a JSON serialisable object.
"""
if not self.sock:
self.connect()
if isinstance(obj, Message):
data = obj.to_msg()
if isinstance(obj, Command):
self.expected_response = obj
else:
data = json.dumps(obj)
payload = "{0}:{1}".format(len(data), data)
totalsent = 0
while totalsent < len(payload):
sent = self.sock.send(payload[totalsent:])
if sent == 0:
raise IOError("Socket error after sending {0} of {1} bytes"
.format(totalsent, len(payload)))
else:
totalsent += sent
def respond(self, obj):
"""Send a response to a command. This can be an arbitrary JSON
serialisable object or an ``Exception``.
"""
res, err = None, None
if isinstance(obj, Exception):
err = obj
else:
res = obj
msg = Response(self.last_id, err, res)
self.send(msg)
return self.receive()
def request(self, name, params):
"""Sends a message to the remote server and waits for a response
to come back.
"""
self.last_id = self.last_id + 1
cmd = Command(self.last_id, name, params)
self.send(cmd)
return self.receive()
def close(self):
"""Close the socket."""
if self.sock:
try:
self.sock.shutdown(socket.SHUT_RDWR)
except IOError as exc:
# Errno 57 is "socket not connected", which we don't care about here.
if exc.errno != 57:
raise
self.sock.close()
self.sock = None
def __del__(self):
self.close()

View file

@ -0,0 +1,167 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import collections
import errors
import sys
import time
DEFAULT_TIMEOUT = 5
DEFAULT_INTERVAL = 0.1
class Wait(object):
"""An explicit conditional utility class for waiting until a condition
evaluates to true or not null.
This will repeatedly evaluate a condition in anticipation for a
truthy return value, or its timeout to expire, or its waiting
predicate to become true.
A `Wait` instance defines the maximum amount of time to wait for a
condition, as well as the frequency with which to check the
condition. Furthermore, the user may configure the wait to ignore
specific types of exceptions whilst waiting, such as
`errors.NoSuchElementException` when searching for an element on
the page.
"""
def __init__(self, marionette, timeout=DEFAULT_TIMEOUT,
interval=DEFAULT_INTERVAL, ignored_exceptions=None,
clock=None):
"""Configure the Wait instance to have a custom timeout, interval, and
list of ignored exceptions. Optionally a different time
implementation than the one provided by the standard library
(time) can also be provided.
Sample usage::
# Wait 30 seconds for window to open, checking for its presence once
# every 5 seconds.
wait = Wait(marionette, timeout=30, interval=5,
ignored_exceptions=errors.NoSuchWindowException)
window = wait.until(lambda m: m.switch_to_window(42))
:param marionette: The input value to be provided to
conditions, usually a Marionette instance.
:param timeout: How long to wait for the evaluated condition
to become true. The default timeout is
`wait.DEFAULT_TIMEOUT`.
:param interval: How often the condition should be evaluated.
In reality the interval may be greater as the cost of
evaluating the condition function. If that is not the case the
interval for the next condition function call is shortend to keep
the original interval sequence as best as possible.
The default polling interval is `wait.DEFAULT_INTERVAL`.
:param ignored_exceptions: Ignore specific types of exceptions
whilst waiting for the condition. Any exceptions not
whitelisted will be allowed to propagate, terminating the
wait.
:param clock: Allows overriding the use of the runtime's
default time library. See `wait.SystemClock` for
implementation details.
"""
self.marionette = marionette
self.timeout = timeout
self.clock = clock or SystemClock()
self.end = self.clock.now + self.timeout
self.interval = interval
exceptions = []
if ignored_exceptions is not None:
if isinstance(ignored_exceptions, collections.Iterable):
exceptions.extend(iter(ignored_exceptions))
else:
exceptions.append(ignored_exceptions)
self.exceptions = tuple(set(exceptions))
def until(self, condition, is_true=None, message=""):
"""Repeatedly runs condition until its return value evaluates to true,
or its timeout expires or the predicate evaluates to true.
This will poll at the given interval until the given timeout
is reached, or the predicate or conditions returns true. A
condition that returns null or does not evaluate to true will
fully elapse its timeout before raising an
`errors.TimeoutException`.
If an exception is raised in the condition function and it's
not ignored, this function will raise immediately. If the
exception is ignored, it will continue polling for the
condition until it returns successfully or a
`TimeoutException` is raised.
:param condition: A callable function whose return value will
be returned by this function if it evaluates to true.
:param is_true: An optional predicate that will terminate and
return when it evaluates to False. It should be a
function that will be passed clock and an end time. The
default predicate will terminate a wait when the clock
elapses the timeout.
:param message: An optional message to include in the
exception's message if this function times out.
"""
rv = None
last_exc = None
until = is_true or until_pred
start = self.clock.now
while not until(self.clock, self.end):
try:
next = self.clock.now + self.interval
rv = condition(self.marionette)
except (KeyboardInterrupt, SystemExit):
raise
except self.exceptions:
last_exc = sys.exc_info()
# Re-adjust the interval depending on how long the callback
# took to evaluate the condition
interval_new = max(next - self.clock.now, 0)
if not rv:
self.clock.sleep(interval_new)
continue
if rv is not None:
return rv
self.clock.sleep(interval_new)
if message:
message = " with message: {}".format(message)
raise errors.TimeoutException(
"Timed out after {0} seconds{1}".format(round((self.clock.now - start), 1),
message if message else ""),
cause=last_exc)
def until_pred(clock, end):
return clock.now >= end
class SystemClock(object):
def __init__(self):
self._time = time
def sleep(self, duration):
self._time.sleep(duration)
@property
def now(self):
return self._time.time()

View file

@ -0,0 +1,2 @@
mozrunner >= 6.13
mozversion >= 1.1

View file

@ -0,0 +1,49 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import re
from setuptools import setup, find_packages
THIS_DIR = os.path.dirname(os.path.realpath(__name__))
def read(*parts):
with open(os.path.join(THIS_DIR, *parts)) as f:
return f.read()
def get_version():
return re.findall("__version__ = '([\d\.]+)'",
read('marionette_driver', '__init__.py'), re.M)[0]
setup(name='marionette_driver',
version=get_version(),
description="Marionette Driver",
long_description='See http://marionette-client.readthedocs.org/en/latest/',
# Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Topic :: Software Development :: Quality Assurance',
'Topic :: Software Development :: Testing',
'Topic :: Utilities',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
],
keywords='mozilla',
author='Auto-tools',
author_email='tools-marionette@lists.mozilla.org',
url='https://wiki.mozilla.org/Auto-tools/Projects/Marionette',
license='MPL',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=read('requirements.txt').splitlines(),
)

View file

@ -0,0 +1,237 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Constructor: CC, interfaces: Ci, utils: Cu, classes: Cc} = Components;
const MARIONETTE_CONTRACTID = "@mozilla.org/marionette;1";
const MARIONETTE_CID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
const DEFAULT_PORT = 2828;
const ENABLED_PREF = "marionette.defaultPrefs.enabled";
const PORT_PREF = "marionette.defaultPrefs.port";
const FORCELOCAL_PREF = "marionette.force-local";
const LOG_PREF = "marionette.logging";
/**
* Besides starting based on existing prefs in a profile and a commandline flag,
* we also support inheriting prefs out of an env var, and to start marionette
* that way.
* This allows marionette prefs to persist when we do a restart into a
* different profile in order to test things like Firefox refresh.
* The env var itself, if present, is interpreted as a JSON structure, with the
* keys mapping to preference names in the "marionette." branch, and the values
* to the values of those prefs. So something like {"defaultPrefs.enabled": true}
* in the env var would result in the marionette.defaultPrefs.enabled pref being
* set to true, thus triggering marionette being enabled for that startup.
*/
const ENV_PREF_VAR = "MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS";
const ServerSocket = CC("@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"initSpecialConnection");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
function MarionetteComponent() {
this.loaded_ = false;
this.observerService = Services.obs;
this.logger = this.setupLogger_(this.determineLoggingLevel_());
}
MarionetteComponent.prototype = {
classDescription: "Marionette component",
classID: MARIONETTE_CID,
contractID: MARIONETTE_CONTRACTID,
QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler, Ci.nsIObserver]),
_xpcom_categories: [
{category: "command-line-handler", entry: "b-marionette"},
{category: "profile-after-change", service: true}
],
enabled: false,
finalUiStartup: false,
server: null,
};
MarionetteComponent.prototype.setupLogger_ = function (level) {
let log = Log.repository.getLogger("Marionette");
log.level = level;
log.addAppender(new Log.DumpAppender());
return log;
};
MarionetteComponent.prototype.determineLoggingLevel_ = function() {
let level = Log.Level.Info;
// marionette.logging pref can override default
// with an entry from the Log.Level enum
if (Preferences.has(LOG_PREF)) {
let p = Preferences.get(LOG_PREF);
switch (typeof p) {
// Gecko >= 46
case "string":
let s = p.toLowerCase();
s = s.charAt(0).toUpperCase() + s.slice(1);
level = Log.Level[s];
break;
// Gecko <= 45
case "boolean":
if (p) {
level = Log.Level.Trace;
}
break;
}
}
return level;
};
MarionetteComponent.prototype.onSocketAccepted = function(
socket, transport) {
this.logger.info("onSocketAccepted for Marionette dummy socket");
};
MarionetteComponent.prototype.onStopListening = function (socket, status) {
this.logger.info(`onStopListening for Marionette dummy socket, code ${status}`);
socket.close();
};
/** Check cmdLine argument for {@code --marionette}. */
MarionetteComponent.prototype.handle = function (cmdLine) {
// if the CLI is there then lets do work otherwise nothing to see
if (cmdLine.handleFlag("marionette", false)) {
this.enabled = true;
this.logger.debug("Marionette enabled via command-line flag");
this.init();
}
};
MarionetteComponent.prototype.observe = function (subj, topic, data) {
switch (topic) {
case "profile-after-change":
this.maybeReadPrefsFromEnvironment();
// Using final-ui-startup as the xpcom category doesn't seem to work,
// so we wait for that by adding an observer here.
this.observerService.addObserver(this, "final-ui-startup", false);
#ifdef ENABLE_MARIONETTE
this.enabled = Preferences.get(ENABLED_PREF, false);
if (this.enabled) {
this.logger.debug("Marionette enabled via build flag and pref");
// We want to suppress the modal dialog that's shown
// when starting up in safe-mode to enable testing.
if (Services.appinfo.inSafeMode) {
this.observerService.addObserver(this, "domwindowopened", false);
}
}
#endif
break;
case "final-ui-startup":
this.finalUiStartup = true;
this.observerService.removeObserver(this, topic);
this.observerService.addObserver(this, "xpcom-shutdown", false);
this.init();
break;
case "domwindowopened":
this.observerService.removeObserver(this, topic);
this.suppressSafeModeDialog_(subj);
break;
case "xpcom-shutdown":
this.observerService.removeObserver(this, "xpcom-shutdown");
this.uninit();
break;
}
};
MarionetteComponent.prototype.maybeReadPrefsFromEnvironment = function() {
let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
if (env.exists(ENV_PREF_VAR)) {
let prefStr = env.get(ENV_PREF_VAR);
let prefs;
try {
prefs = JSON.parse(prefStr);
} catch (ex) {
Cu.reportError("Invalid marionette prefs in environment; prefs won't have been applied.");
Cu.reportError(ex);
}
if (prefs) {
for (let prefName of Object.keys(prefs)) {
Preferences.set("marionette." + prefName, prefs[prefName]);
}
}
}
}
MarionetteComponent.prototype.suppressSafeModeDialog_ = function (win) {
// Wait for the modal dialog to finish loading.
win.addEventListener("load", function onload() {
win.removeEventListener("load", onload);
if (win.document.getElementById("safeModeDialog")) {
// Accept the dialog to start in safe-mode
win.setTimeout(() => {
win.document.documentElement.getButton("accept").click();
});
}
});
};
MarionetteComponent.prototype.init = function() {
if (this.loaded_ || !this.enabled || !this.finalUiStartup) {
return;
}
this.loaded_ = true;
let forceLocal = Preferences.get(FORCELOCAL_PREF,
Services.appinfo.name == "B2G" ? false : true);
Preferences.set(FORCELOCAL_PREF, forceLocal);
if (!forceLocal) {
// See bug 800138. Because the first socket that opens with
// force-local=false fails, we open a dummy socket that will fail.
// keepWhenOffline=true so that it still work when offline (local).
// This allows the following attempt by Marionette to open a socket
// to succeed.
let insaneSacrificialGoat =
new ServerSocket(666, Ci.nsIServerSocket.KeepWhenOffline, 4);
insaneSacrificialGoat.asyncListen(this);
}
let port = Preferences.get(PORT_PREF, DEFAULT_PORT);
let s;
try {
Cu.import("chrome://marionette/content/server.js");
s = new MarionetteServer(port, forceLocal);
s.start();
this.logger.info(`Listening on port ${s.port}`);
} catch (e) {
this.logger.error(`Error on starting server: ${e}`);
dump(e.toString() + "\n" + e.stack + "\n");
} finally {
if (s) {
this.server = s;
}
}
};
MarionetteComponent.prototype.uninit = function() {
if (!this.loaded_) {
return;
}
this.server.stop();
this.loaded_ = false;
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MarionetteComponent]);

View file

@ -0,0 +1,4 @@
component {786a1369-dca5-4adc-8486-33d23c88010a} marionette.js
contract @mozilla.org/marionette;1 {786a1369-dca5-4adc-8486-33d23c88010a}
category command-line-handler b-marionette @mozilla.org/marionette;1
category profile-after-change Marionette @mozilla.org/marionette;1

View file

@ -0,0 +1,9 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_COMPONENTS += ["marionette.manifest"]
EXTRA_PP_COMPONENTS += ["marionette.js"]
if CONFIG["ENABLE_MARIONETTE"]:
DEFINES["ENABLE_MARIONETTE"] = 1

View file

@ -0,0 +1,131 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("chrome://marionette/content/error.js");
const logger = Log.repository.getLogger("Marionette");
this.EXPORTED_SYMBOLS = ["Cookies"];
const IPV4_PORT_EXPR = /:\d+$/;
/**
* Interface for manipulating cookies from content space.
*/
this.Cookies = class {
/**
* @param {function(): Document} documentFn
* Closure that returns the current content document.
* @param {Proxy(SyncChromeSender)} chromeProxy
* A synchronous proxy interface to chrome space.
*/
constructor(documentFn, chromeProxy) {
this.documentFn_ = documentFn;
this.chrome = chromeProxy;
}
get document() {
return this.documentFn_();
}
[Symbol.iterator]() {
let path = this.document.location.pathname || "/";
let cs = this.chrome.getVisibleCookies(path, this.document.location.hostname)[0];
return cs[Symbol.iterator]();
}
/**
* Add a new cookie to a content document.
*
* @param {string} name
* Cookie key.
* @param {string} value
* Cookie value.
* @param {Object.<string, ?>} opts
* An object with the optional fields {@code domain}, {@code path},
* {@code secure}, {@code httpOnly}, and {@code expiry}.
*
* @return {Object.<string, ?>}
* A serialisation of the cookie that was added.
*
* @throws UnableToSetCookieError
* If the document's content type isn't HTML, the current document's
* domain is a mismatch to the cookie's provided domain, or there
* otherwise was issues with the input data.
*/
add(name, value, opts={}) {
if (typeof this.document == "undefined" || !this.document.contentType.match(/html/i)) {
throw new UnableToSetCookieError(
"You may only set cookies on HTML documents: " + this.document.contentType);
}
if (!opts.expiry) {
// date twenty years into future, in seconds
let date = new Date();
let now = new Date(Date.now());
date.setYear(now.getFullYear() + 20);
opts.expiry = date.getTime() / 1000;
}
if (!opts.domain) {
opts.domain = this.document.location.host;
} else if (this.document.location.host.indexOf(opts.domain) < 0) {
throw new InvalidCookieDomainError(
"You may only set cookies for the current domain");
}
// remove port from domain, if present.
// unfortunately this catches IPv6 addresses by mistake
// TODO: Bug 814416
opts.domain = opts.domain.replace(IPV4_PORT_EXPR, "");
let cookie = {
domain: opts.domain,
path: opts.path,
name: name,
value: value,
secure: opts.secure,
httpOnly: opts.httpOnly,
session: false,
expiry: opts.expiry,
};
if (!this.chrome.addCookie(cookie)) {
throw new UnableToSetCookieError();
}
return cookie;
}
/**
* Delete cookie by reference or by name.
*
* @param {(string|Object.<string, ?>)} cookie
* Name of cookie or cookie object.
*
* @throws {UnknownError}
* If unable to delete the cookie.
*/
delete(cookie) {
let name;
if (cookie.hasOwnProperty("name")) {
name = cookie.name;
} else {
name = cookie;
}
for (let candidate of this) {
if (candidate.name == name) {
if (!this.chrome.deleteCookie(candidate)) {
throw new UnknownError("Unable to delete cookie by name: " + name);
}
}
}
}
};

View file

@ -0,0 +1,228 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("chrome://marionette/content/assert.js");
Cu.import("chrome://marionette/content/driver.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/message.js");
this.EXPORTED_SYMBOLS = ["Dispatcher"];
const PROTOCOL_VERSION = 3;
const logger = Log.repository.getLogger("Marionette");
/**
* Manages a Marionette connection, and dispatches packets received to
* their correct destinations.
*
* @param {number} connId
* Unique identifier of the connection this dispatcher should handle.
* @param {DebuggerTransport} transport
* Debugger transport connection to the client.
* @param {function(): GeckoDriver} driverFactory
* A factory function that produces a GeckoDriver.
*/
this.Dispatcher = function (connId, transport, driverFactory) {
this.connId = connId;
this.conn = transport;
// transport hooks are Dispatcher#onPacket
// and Dispatcher#onClosed
this.conn.hooks = this;
// callback for when connection is closed
this.onclose = null;
// last received/sent message ID
this.lastId = 0;
this.driver = driverFactory();
// lookup of commands sent by server to client by message ID
this.commands_ = new Map();
};
/**
* Debugger transport callback that cleans up
* after a connection is closed.
*/
Dispatcher.prototype.onClosed = function (reason) {
this.driver.deleteSession();
if (this.onclose) {
this.onclose(this);
}
};
/**
* Callback that receives data packets from the client.
*
* If the message is a Response, we look up the command previously issued
* to the client and run its callback, if any. In case of a Command,
* the corresponding is executed.
*
* @param {Array.<number, number, ?, ?>} data
* A four element array where the elements, in sequence, signifies
* message type, message ID, method name or error, and parameters
* or result.
*/
Dispatcher.prototype.onPacket = function (data) {
let msg = Message.fromMsg(data);
msg.origin = MessageOrigin.Client;
this.log_(msg);
if (msg instanceof Response) {
let cmd = this.commands_.get(msg.id);
this.commands_.delete(msg.id);
cmd.onresponse(msg);
} else if (msg instanceof Command) {
this.lastId = msg.id;
this.execute(msg);
}
};
/**
* Executes a WebDriver command and sends back a response when it has
* finished executing.
*
* Commands implemented in GeckoDriver and registered in its
* {@code GeckoDriver.commands} attribute. The return values from
* commands are expected to be Promises. If the resolved value of said
* promise is not an object, the response body will be wrapped in an object
* under a "value" field.
*
* If the command implementation sends the response itself by calling
* {@code resp.send()}, the response is guaranteed to not be sent twice.
*
* Errors thrown in commands are marshaled and sent back, and if they
* are not WebDriverError instances, they are additionally propagated and
* reported to {@code Components.utils.reportError}.
*
* @param {Command} cmd
* The requested command to execute.
*/
Dispatcher.prototype.execute = function (cmd) {
let resp = new Response(cmd.id, this.send.bind(this));
let sendResponse = () => resp.sendConditionally(resp => !resp.sent);
let sendError = resp.sendError.bind(resp);
let req = Task.spawn(function*() {
let fn = this.driver.commands[cmd.name];
if (typeof fn == "undefined") {
throw new UnknownCommandError(cmd.name);
}
if (cmd.name !== "newSession") {
assert.session(this.driver);
}
let rv = yield fn.bind(this.driver)(cmd, resp);
if (typeof rv != "undefined") {
if (typeof rv != "object") {
resp.body = {value: rv};
} else {
resp.body = rv;
}
}
}.bind(this));
req.then(sendResponse, sendError).catch(error.report);
};
Dispatcher.prototype.sendError = function (err, cmdId) {
let resp = new Response(cmdId, this.send.bind(this));
resp.sendError(err);
};
// Convenience methods:
/**
* When a client connects we send across a JSON Object defining the
* protocol level.
*
* This is the only message sent by Marionette that does not follow
* the regular message format.
*/
Dispatcher.prototype.sayHello = function() {
let whatHo = {
applicationType: "gecko",
marionetteProtocol: PROTOCOL_VERSION,
};
this.sendRaw(whatHo);
};
/**
* Delegates message to client based on the provided {@code cmdId}.
* The message is sent over the debugger transport socket.
*
* The command ID is a unique identifier assigned to the client's request
* that is used to distinguish the asynchronous responses.
*
* Whilst responses to commands are synchronous and must be sent in the
* correct order.
*
* @param {Command,Response} msg
* The command or response to send.
*/
Dispatcher.prototype.send = function (msg) {
msg.origin = MessageOrigin.Server;
if (msg instanceof Command) {
this.commands_.set(msg.id, msg);
this.sendToEmulator(msg);
} else if (msg instanceof Response) {
this.sendToClient(msg);
}
};
// Low-level methods:
/**
* Send given response to the client over the debugger transport socket.
*
* @param {Response} resp
* The response to send back to the client.
*/
Dispatcher.prototype.sendToClient = function (resp) {
this.driver.responseCompleted();
this.sendMessage(resp);
};
/**
* Marshal message to the Marionette message format and send it.
*
* @param {Command,Response} msg
* The message to send.
*/
Dispatcher.prototype.sendMessage = function (msg) {
this.log_(msg);
let payload = msg.toMsg();
this.sendRaw(payload);
};
/**
* Send the given payload over the debugger transport socket to the
* connected client.
*
* @param {Object} payload
* The payload to ship.
*/
Dispatcher.prototype.sendRaw = function (payload) {
this.conn.send(payload);
};
Dispatcher.prototype.log_ = function (msg) {
let a = (msg.origin == MessageOrigin.Client ? " -> " : " <- ");
let s = JSON.stringify(msg.toMsg());
logger.trace(this.connId + a + s);
};

2907
testing/marionette/driver.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

487
testing/marionette/error.js Normal file
View file

@ -0,0 +1,487 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {interfaces: Ci, utils: Cu} = Components;
const ERRORS = new Set([
"ElementClickInterceptedError",
"ElementNotAccessibleError",
"ElementNotInteractableError",
"InsecureCertificateError",
"InvalidArgumentError",
"InvalidElementStateError",
"InvalidSelectorError",
"InvalidSessionIDError",
"JavaScriptError",
"MoveTargetOutOfBoundsError",
"NoAlertOpenError",
"NoSuchElementError",
"NoSuchFrameError",
"NoSuchWindowError",
"ScriptTimeoutError",
"SessionNotCreatedError",
"StaleElementReferenceError",
"TimeoutError",
"UnableToSetCookieError",
"UnknownCommandError",
"UnknownError",
"UnsupportedOperationError",
"WebDriverError",
]);
const BUILTIN_ERRORS = new Set([
"Error",
"EvalError",
"InternalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError",
]);
this.EXPORTED_SYMBOLS = ["error"].concat(Array.from(ERRORS));
this.error = {};
/**
* Checks if obj is an instance of the Error prototype in a safe manner.
* Prefer using this over using instanceof since the Error prototype
* isn't unique across browsers, and XPCOM nsIException's are special
* snowflakes.
*
* @param {*} val
* Any value that should be undergo the test for errorness.
* @return {boolean}
* True if error, false otherwise.
*/
error.isError = function (val) {
if (val === null || typeof val != "object") {
return false;
} else if (val instanceof Ci.nsIException) {
return true;
} else {
// DOMRectList errors on string comparison
try {
let proto = Object.getPrototypeOf(val);
return BUILTIN_ERRORS.has(proto.toString());
} catch (e) {
return false;
}
}
};
/**
* Checks if obj is an object in the WebDriverError prototypal chain.
*/
error.isWebDriverError = function (obj) {
return error.isError(obj) &&
("name" in obj && ERRORS.has(obj.name));
};
/**
* Wraps any error as a WebDriverError. If the given error is already in
* the WebDriverError prototype chain, this function returns it
* unmodified.
*/
error.wrap = function (err) {
if (error.isWebDriverError(err)) {
return err;
}
return new WebDriverError(err);
};
/**
* Unhandled error reporter. Dumps the error and its stacktrace to console,
* and reports error to the Browser Console.
*/
error.report = function (err) {
let msg = "Marionette threw an error: " + error.stringify(err);
dump(msg + "\n");
if (Cu.reportError) {
Cu.reportError(msg);
}
};
/**
* Prettifies an instance of Error and its stacktrace to a string.
*/
error.stringify = function (err) {
try {
let s = err.toString();
if ("stack" in err) {
s += "\n" + err.stack;
}
return s;
} catch (e) {
return "<unprintable error>";
}
};
/**
* Pretty-print values passed to template strings.
*
* Usage:
*
* let bool = {value: true};
* error.pprint`Expected boolean, got ${bool}`;
* => 'Expected boolean, got [object Object] {"value": true}'
*
* let htmlElement = document.querySelector("input#foo");
* error.pprint`Expected element ${htmlElement}`;
* => 'Expected element <input id="foo" class="bar baz">'
*/
error.pprint = function (ss, ...values) {
function prettyObject (obj) {
let proto = Object.prototype.toString.call(obj);
let s = "";
try {
s = JSON.stringify(obj);
} catch (e if e instanceof TypeError) {
s = `<${e.message}>`;
}
return proto + " " + s;
}
function prettyElement (el) {
let ident = [];
if (el.id) {
ident.push(`id="${el.id}"`);
}
if (el.classList.length > 0) {
ident.push(`class="${el.className}"`);
}
let idents = "";
if (ident.length > 0) {
idents = " " + ident.join(" ");
}
return `<${el.localName}${idents}>`;
}
let res = [];
for (let i = 0; i < ss.length; i++) {
res.push(ss[i]);
if (i < values.length) {
let val = values[i];
let typ = Object.prototype.toString.call(val);
let s;
try {
if (val && val.nodeType === 1) {
s = prettyElement(val);
} else {
s = prettyObject(val);
}
} catch (e) {
s = typeof val;
}
res.push(s);
}
}
return res.join("");
};
/**
* WebDriverError is the prototypal parent of all WebDriver errors.
* It should not be used directly, as it does not correspond to a real
* error in the specification.
*/
class WebDriverError extends Error {
/**
* @param {(string|Error)=} x
* Optional string describing error situation or Error instance
* to propagate.
*/
constructor (x) {
super(x);
this.name = this.constructor.name;
this.status = "webdriver error";
// Error's ctor does not preserve x' stack
if (error.isError(x)) {
this.stack = x.stack;
}
}
toJSON () {
return {
error: this.status,
message: this.message || "",
stacktrace: this.stack || "",
}
}
static fromJSON (json) {
if (typeof json.error == "undefined") {
let s = JSON.stringify(json);
throw new TypeError("Undeserialisable error type: " + s);
}
if (!STATUSES.has(json.error)) {
throw new TypeError("Not of WebDriverError descent: " + json.error);
}
let cls = STATUSES.get(json.error);
let err = new cls();
if ("message" in json) {
err.message = json.message;
}
if ("stacktrace" in json) {
err.stack = json.stacktrace;
}
return err;
}
}
class ElementNotAccessibleError extends WebDriverError {
constructor (message) {
super(message);
this.status = "element not accessible";
}
}
/**
* An element click could not be completed because the element receiving
* the events is obscuring the element that was requested clicked.
*
* @param {Element=} obscuredEl
* Element obscuring the element receiving the click. Providing this
* is not required, but will produce a nicer error message.
* @param {Map.<string, number>} coords
* Original click location. Providing this is not required, but
* will produce a nicer error message.
*/
class ElementClickInterceptedError extends WebDriverError {
constructor (obscuredEl = undefined, coords = undefined) {
let msg = "";
if (obscuredEl && coords) {
const doc = obscuredEl.ownerDocument;
const overlayingEl = doc.elementFromPoint(coords.x, coords.y);
msg = error.pprint`Element ${obscuredEl} is not clickable ` +
`at point (${coords.x},${coords.y}) ` +
error.pprint`because another element ${overlayingEl} ` +
`obscures it`;
}
super(msg);
this.status = "element click intercepted";
}
}
class ElementNotInteractableError extends WebDriverError {
constructor (message) {
super(message);
this.status = "element not interactable";
}
}
class InsecureCertificateError extends WebDriverError {
constructor (message) {
super(message);
this.status = "insecure certificate";
}
}
class InvalidArgumentError extends WebDriverError {
constructor (message) {
super(message);
this.status = "invalid argument";
}
}
class InvalidElementStateError extends WebDriverError {
constructor (message) {
super(message);
this.status = "invalid element state";
}
}
class InvalidSelectorError extends WebDriverError {
constructor (message) {
super(message);
this.status = "invalid selector";
}
}
class InvalidSessionIDError extends WebDriverError {
constructor (message) {
super(message);
this.status = "invalid session id";
}
}
/**
* Creates a richly annotated error for an error situation that occurred
* whilst evaluating injected scripts.
*/
class JavaScriptError extends WebDriverError {
/**
* @param {(string|Error)} x
* An Error object instance or a string describing the error
* situation.
* @param {string=} fnName
* Name of the function to use in the stack trace message.
* @param {string=} file
* Filename of the test file on the client.
* @param {number=} line
* Line number of |file|.
* @param {string=} script
* Script being executed, in text form.
*/
constructor (
x,
fnName = undefined,
file = undefined,
line = undefined,
script = undefined) {
let msg = String(x);
let trace = "";
if (fnName) {
trace += fnName;
if (file) {
trace += ` @${file}`;
if (line) {
trace += `, line ${line}`;
}
}
}
if (error.isError(x)) {
let jsStack = x.stack.split("\n");
let match = jsStack[0].match(/:(\d+):\d+$/);
let jsLine = match ? parseInt(match[1]) : 0;
if (script) {
let src = script.split("\n")[jsLine];
trace += "\n" +
`inline javascript, line ${jsLine}\n` +
`src: "${src}"`;
}
trace += "\nStack:\n" + x.stack;
}
super(msg);
this.status = "javascript error";
this.stack = trace;
}
}
class MoveTargetOutOfBoundsError extends WebDriverError {
constructor (message) {
super(message);
this.status = "move target out of bounds";
}
}
class NoAlertOpenError extends WebDriverError {
constructor (message) {
super(message);
this.status = "no such alert";
}
}
class NoSuchElementError extends WebDriverError {
constructor (message) {
super(message);
this.status = "no such element";
}
}
class NoSuchFrameError extends WebDriverError {
constructor (message) {
super(message);
this.status = "no such frame";
}
}
class NoSuchWindowError extends WebDriverError {
constructor (message) {
super(message);
this.status = "no such window";
}
}
class ScriptTimeoutError extends WebDriverError {
constructor (message) {
super(message);
this.status = "script timeout";
}
}
class SessionNotCreatedError extends WebDriverError {
constructor (message) {
super(message);
this.status = "session not created";
}
}
class StaleElementReferenceError extends WebDriverError {
constructor (message) {
super(message);
this.status = "stale element reference";
}
}
class TimeoutError extends WebDriverError {
constructor (message) {
super(message);
this.status = "timeout";
}
}
class UnableToSetCookieError extends WebDriverError {
constructor (message) {
super(message);
this.status = "unable to set cookie";
}
}
class UnknownCommandError extends WebDriverError {
constructor (message) {
super(message);
this.status = "unknown command";
}
}
class UnknownError extends WebDriverError {
constructor (message) {
super(message);
this.status = "unknown error";
}
}
class UnsupportedOperationError extends WebDriverError {
constructor (message) {
super(message);
this.status = "unsupported operation";
}
}
const STATUSES = new Map([
["element not accessible", ElementNotAccessibleError],
["element not interactable", ElementNotInteractableError],
["element click intercepted", ElementClickInterceptedError],
["insecure certificate", InsecureCertificateError],
["invalid argument", InvalidArgumentError],
["invalid element state", InvalidElementStateError],
["invalid selector", InvalidSelectorError],
["invalid session id", InvalidSessionIDError],
["javascript error", JavaScriptError],
["move target out of bounds", MoveTargetOutOfBoundsError],
["no alert open", NoAlertOpenError],
["no such element", NoSuchElementError],
["no such frame", NoSuchFrameError],
["no such window", NoSuchWindowError],
["script timeout", ScriptTimeoutError],
["session not created", SessionNotCreatedError],
["stale element reference", StaleElementReferenceError],
["timeout", TimeoutError],
["unable to set cookie", UnableToSetCookieError],
["unknown command", UnknownCommandError],
["unknown error", UnknownError],
["unsupported operation", UnsupportedOperationError],
["webdriver error", WebDriverError],
]);

View file

@ -0,0 +1,494 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("chrome://marionette/content/error.js");
const logger = Log.repository.getLogger("Marionette");
this.EXPORTED_SYMBOLS = ["evaluate", "sandbox", "Sandboxes"];
const ARGUMENTS = "__webDriverArguments";
const CALLBACK = "__webDriverCallback";
const COMPLETE = "__webDriverComplete";
const DEFAULT_TIMEOUT = 10000; // ms
const FINISH = "finish";
const MARIONETTE_SCRIPT_FINISHED = "marionetteScriptFinished";
const ELEMENT_KEY = "element";
const W3C_ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf";
this.evaluate = {};
/**
* Evaluate a script in given sandbox.
*
* If the option {@code directInject} is not specified, the script will
* be executed as a function with the {@code args} argument applied.
*
* The arguments provided by the {@code args} argument are exposed through
* the {@code arguments} object available in the script context, and if
* the script is executed asynchronously with the {@code async}
* option, an additional last argument that is synonymous to the
* {@code marionetteScriptFinished} global is appended, and can be
* accessed through {@code arguments[arguments.length - 1]}.
*
* The {@code timeout} option specifies the duration for how long the
* script should be allowed to run before it is interrupted and aborted.
* An interrupted script will cause a ScriptTimeoutError to occur.
*
* The {@code async} option indicates that the script will not return
* until the {@code marionetteScriptFinished} global callback is invoked,
* which is analogous to the last argument of the {@code arguments}
* object.
*
* The option {@code directInject} causes the script to be evaluated
* without being wrapped in a function and the provided arguments will
* be disregarded. This will cause such things as root scope return
* statements to throw errors because they are not used inside a function.
*
* The {@code filename} option is used in error messages to provide
* information on the origin script file in the local end.
*
* The {@code line} option is used in error messages, along with
* {@code filename}, to provide the line number in the origin script
* file on the local end.
*
* @param {nsISandbox) sb
* The sandbox the script will be evaluted in.
* @param {string} script
* The script to evaluate.
* @param {Array.<?>=} args
* A sequence of arguments to call the script with.
* @param {Object.<string, ?>=} opts
* Dictionary of options:
*
* async (boolean)
* Indicates if the script should return immediately or wait
* for the callback be invoked before returning.
* debug (boolean)
* Attaches an {@code onerror} event listener.
* directInject (boolean)
* Evaluates the script without wrapping it in a function.
* filename (string)
* File location of the program in the client.
* line (number)
* Line number of the program in the client.
* sandboxName (string)
* Name of the sandbox. Elevated system privileges, equivalent
* to chrome space, will be given if it is "system".
* timeout (boolean)
* Duration in milliseconds before interrupting the script.
*
* @return {Promise}
* A promise that when resolved will give you the return value from
* the script. Note that the return value requires serialisation before
* it can be sent to the client.
*
* @throws JavaScriptError
* If an Error was thrown whilst evaluating the script.
* @throws ScriptTimeoutError
* If the script was interrupted due to script timeout.
*/
evaluate.sandbox = function (sb, script, args = [], opts = {}) {
let scriptTimeoutID, timeoutHandler, unloadHandler;
let promise = new Promise((resolve, reject) => {
let src = "";
sb[COMPLETE] = resolve;
timeoutHandler = () => reject(new ScriptTimeoutError("Timed out"));
unloadHandler = () => reject(
new JavaScriptError("Document was unloaded during execution"));
// wrap in function
if (!opts.directInject) {
if (opts.async) {
sb[CALLBACK] = sb[COMPLETE];
}
sb[ARGUMENTS] = sandbox.cloneInto(args, sb);
// callback function made private
// so that introspection is possible
// on the arguments object
if (opts.async) {
sb[CALLBACK] = sb[COMPLETE];
src += `${ARGUMENTS}.push(rv => ${CALLBACK}(rv));`;
}
src += `(function() { ${script} }).apply(null, ${ARGUMENTS})`;
// marionetteScriptFinished is not WebDriver conformant,
// hence it is only exposed to immutable sandboxes
if (opts.sandboxName) {
sb[MARIONETTE_SCRIPT_FINISHED] = sb[CALLBACK];
}
}
// onerror is not hooked on by default because of the inability to
// differentiate content errors from chrome errors.
//
// see bug 1128760 for more details
if (opts.debug) {
sb.window.onerror = (msg, url, line) => {
let err = new JavaScriptError(`${msg} at ${url}:${line}`);
reject(err);
};
}
// timeout and unload handlers
scriptTimeoutID = setTimeout(timeoutHandler, opts.timeout || DEFAULT_TIMEOUT);
sb.window.onunload = sandbox.cloneInto(unloadHandler, sb);
let res;
try {
res = Cu.evalInSandbox(src, sb, "1.8", opts.filename || "dummy file", 0);
} catch (e) {
let err = new JavaScriptError(
e,
"execute_script",
opts.filename,
opts.line,
script);
reject(err);
}
if (!opts.async) {
resolve(res);
}
});
return promise.then(res => {
clearTimeout(scriptTimeoutID);
sb.window.removeEventListener("unload", unloadHandler);
return res;
});
};
this.sandbox = {};
/**
* Provides a safe way to take an object defined in a privileged scope and
* create a structured clone of it in a less-privileged scope. It returns
* a reference to the clone.
*
* Unlike for |Components.utils.cloneInto|, |obj| may contain functions
* and DOM elemnets.
*/
sandbox.cloneInto = function (obj, sb) {
return Cu.cloneInto(obj, sb, {cloneFunctions: true, wrapReflectors: true});
};
/**
* Augment given sandbox by an adapter that has an {@code exports}
* map property, or a normal map, of function names and function
* references.
*
* @param {Sandbox} sb
* The sandbox to augment.
* @param {Object} adapter
* Object that holds an {@code exports} property, or a map, of
* function names and function references.
*
* @return {Sandbox}
* The augmented sandbox.
*/
sandbox.augment = function (sb, adapter) {
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
let funcs = adapter.exports || entries(adapter);
for (let [name, func] of funcs) {
sb[name] = func;
}
return sb;
};
/**
* Creates a sandbox.
*
* @param {Window} window
* The DOM Window object.
* @param {nsIPrincipal=} principal
* An optional, custom principal to prefer over the Window. Useful if
* you need elevated security permissions.
*
* @return {Sandbox}
* The created sandbox.
*/
sandbox.create = function (window, principal = null, opts = {}) {
let p = principal || window;
opts = Object.assign({
sameZoneAs: window,
sandboxPrototype: window,
wantComponents: true,
wantXrays: true,
}, opts);
return new Cu.Sandbox(p, opts);
};
/**
* Creates a mutable sandbox, where changes to the global scope
* will have lasting side-effects.
*
* @param {Window} window
* The DOM Window object.
*
* @return {Sandbox}
* The created sandbox.
*/
sandbox.createMutable = function (window) {
let opts = {
wantComponents: false,
wantXrays: false,
};
return sandbox.create(window, null, opts);
};
sandbox.createSystemPrincipal = function (window) {
let principal = Cc["@mozilla.org/systemprincipal;1"]
.createInstance(Ci.nsIPrincipal);
return sandbox.create(window, principal);
};
sandbox.createSimpleTest = function (window, harness) {
let sb = sandbox.create(window);
sb = sandbox.augment(sb, harness);
sb[FINISH] = () => sb[COMPLETE](harness.generate_results());
return sb;
};
/**
* Sandbox storage. When the user requests a sandbox by a specific name,
* if one exists in the storage this will be used as long as its window
* reference is still valid.
*/
this.Sandboxes = class {
/**
* @param {function(): Window} windowFn
* A function that returns the references to the current Window
* object.
*/
constructor(windowFn) {
this.windowFn_ = windowFn;
this.boxes_ = new Map();
}
get window_() {
return this.windowFn_();
}
/**
* Factory function for getting a sandbox by name, or failing that,
* creating a new one.
*
* If the sandbox' window does not match the provided window, a new one
* will be created.
*
* @param {string} name
* The name of the sandbox to get or create.
* @param {boolean} fresh
* Remove old sandbox by name first, if it exists.
*
* @return {Sandbox}
* A used or fresh sandbox.
*/
get(name = "default", fresh = false) {
let sb = this.boxes_.get(name);
if (sb) {
if (fresh || sb.window != this.window_) {
this.boxes_.delete(name);
return this.get(name, false);
}
} else {
if (name == "system") {
sb = sandbox.createSystemPrincipal(this.window_);
} else {
sb = sandbox.create(this.window_);
}
this.boxes_.set(name, sb);
}
return sb;
}
/**
* Clears cache of sandboxes.
*/
clear() {
this.boxes_.clear();
}
};
/**
* Stores scripts imported from the local end through the
* {@code GeckoDriver#importScript} command.
*
* Imported scripts are prepended to the script that is evaluated
* on each call to {@code GeckoDriver#executeScript},
* {@code GeckoDriver#executeAsyncScript}, and
* {@code GeckoDriver#executeJSScript}.
*
* Usage:
*
* let importedScripts = new evaluate.ScriptStorage();
* importedScripts.add(firstScript);
* importedScripts.add(secondScript);
*
* let scriptToEval = importedScripts.concat(script);
* // firstScript and secondScript are prepended to script
*
*/
evaluate.ScriptStorage = class extends Set {
/**
* Produce a string of all stored scripts.
*
* The stored scripts are concatenated into a string, with optional
* additional scripts then appended.
*
* @param {...string} addional
* Optional scripts to include.
*
* @return {string}
* Concatenated string consisting of stored scripts and additional
* scripts, in that order.
*/
concat(...additional) {
let rv = "";
for (let s of this) {
rv = s + rv;
}
for (let s of additional) {
rv = rv + s;
}
return rv;
}
toJson() {
return Array.from(this);
}
};
/**
* Service that enables the script storage service to be queried from
* content space.
*
* The storage can back multiple |ScriptStorage|, each typically belonging
* to a |Context|. Since imported scripts' scope are global and not
* scoped to the current browsing context, all imported scripts are stored
* in chrome space and fetched by content space as needed.
*
* Usage in chrome space:
*
* let service = new evaluate.ScriptStorageService(
* [Context.CHROME, Context.CONTENT]);
* let storage = service.for(Context.CHROME);
* let scriptToEval = storage.concat(script);
*
*/
evaluate.ScriptStorageService = class extends Map {
/**
* Create the service.
*
* An optional array of names for script storages to initially create
* can be provided.
*
* @param {Array.<string>=} initialStorages
* List of names of the script storages to create initially.
*/
constructor(initialStorages = []) {
super(initialStorages.map(name => [name, new evaluate.ScriptStorage()]));
}
/**
* Retrieve the scripts associated with the given context.
*
* @param {Context} context
* Context to retrieve the scripts from.
*
* @return {ScriptStorage}
* Scrips associated with given |context|.
*/
for(context) {
return this.get(context);
}
processMessage(msg) {
switch (msg.name) {
case "Marionette:getImportedScripts":
let storage = this.for.apply(this, msg.json);
return storage.toJson();
default:
throw new TypeError("Unknown message: " + msg.name);
}
}
// TODO(ato): The idea of services in chrome space
// can be generalised at some later time (see cookies.js:38).
receiveMessage(msg) {
try {
return this.processMessage(msg);
} catch (e) {
logger.error(e);
}
}
};
evaluate.ScriptStorageService.prototype.QueryInterface =
XPCOMUtils.generateQI([
Ci.nsIMessageListener,
Ci.nsISupportsWeakReference,
]);
/**
* Bridges the script storage in chrome space, to make it possible to
* retrieve a {@code ScriptStorage} associated with a given
* {@code Context} from content space.
*
* Usage in content space:
*
* let client = new evaluate.ScriptStorageServiceClient(chromeProxy);
* let storage = client.for(Context.CONTENT);
* let scriptToEval = storage.concat(script);
*
*/
evaluate.ScriptStorageServiceClient = class {
/**
* @param {proxy.SyncChromeSender} chromeProxy
* Proxy for communicating with chrome space.
*/
constructor(chromeProxy) {
this.chrome = chromeProxy;
}
/**
* Retrieve scripts associated with the given context.
*
* @param {Context} context
* Context to retrieve scripts from.
*
* @return {ScriptStorage}
* Scripts associated with given |context|.
*/
for(context) {
let scripts = this.chrome.getImportedScripts(context)[0];
return new evaluate.ScriptStorage(scripts);
}
};

1365
testing/marionette/event.js Normal file

File diff suppressed because it is too large Load diff

260
testing/marionette/frame.js Normal file
View file

@ -0,0 +1,260 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = ["frame"];
this.frame = {};
const FRAME_SCRIPT = "chrome://marionette/content/listener.js";
// list of OOP frames that has the frame script loaded
var remoteFrames = [];
/**
* An object representing a frame that Marionette has loaded a
* frame script in.
*/
frame.RemoteFrame = function (windowId, frameId) {
// outerWindowId relative to main process
this.windowId = windowId;
// actual frame relative to the windowId's frames list
this.frameId = frameId;
// assigned frame ID, used for messaging
this.targetFrameId = this.frameId;
// list of OOP frames that has the frame script loaded
this.remoteFrames = [];
};
/**
* The FrameManager will maintain the list of Out Of Process (OOP)
* frames and will handle frame switching between them.
*
* It handles explicit frame switching (switchToFrame), and implicit
* frame switching, which occurs when a modal dialog is triggered in B2G.
*
* @param {GeckoDriver} driver
* Reference to the driver instance.
*/
frame.Manager = class {
constructor(driver) {
// messageManager maintains the messageManager
// for the current process' chrome frame or the global message manager
// holds a member of the remoteFrames (for an OOP frame)
// or null (for the main process)
this.currentRemoteFrame = null;
// frame we'll need to restore once interrupt is gone
this.previousRemoteFrame = null;
// set to true when we have been interrupted by a modal
this.handledModal = false;
this.driver = driver;
}
/**
* Receives all messages from content messageManager.
*/
receiveMessage(message) {
switch (message.name) {
case "MarionetteFrame:getInterruptedState":
// this will return true if the calling frame was interrupted by a modal dialog
if (this.previousRemoteFrame) {
// get the frame window of the interrupted frame
let interruptedFrame = Services.wm.getOuterWindowWithId(
this.previousRemoteFrame.windowId);
if (this.previousRemoteFrame.frameId !== null) {
// find OOP frame
let iframes = interruptedFrame.document.getElementsByTagName("iframe");
interruptedFrame = iframes[this.previousRemoteFrame.frameId];
}
// check if the interrupted frame is the same as the calling frame
if (interruptedFrame.src == message.target.src) {
return {value: this.handledModal};
}
// we get here if previousRemoteFrame and currentRemoteFrame are null,
// i.e. if we're in a non-OOP process, or we haven't switched into an OOP frame,
// in which case, handledModal can't be set to true
} else if (this.currentRemoteFrame === null) {
return {value: this.handledModal};
}
return {value: false};
// handleModal is called when we need to switch frames to the main
// process due to a modal dialog interrupt
case "MarionetteFrame:handleModal":
// If previousRemoteFrame was set, that means we switched into a
// remote frame. If this is the case, then we want to switch back
// into the system frame. If it isn't the case, then we're in a
// non-OOP environment, so we don't need to handle remote frames.
let isLocal = true;
if (this.currentRemoteFrame !== null) {
isLocal = false;
this.removeMessageManagerListeners(
this.currentRemoteFrame.messageManager.get());
// store the previous frame so we can switch back to it when
// the modal is dismissed
this.previousRemoteFrame = this.currentRemoteFrame;
// by setting currentRemoteFrame to null,
// it signifies we're in the main process
this.currentRemoteFrame = null;
this.driver.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
}
this.handledModal = true;
this.driver.sendOk(this.driver.command_id);
return {value: isLocal};
case "MarionetteFrame:getCurrentFrameId":
if (this.currentRemoteFrame !== null) {
return this.currentRemoteFrame.frameId;
}
}
}
getOopFrame(winId, frameId) {
// get original frame window
let outerWin = Services.wm.getOuterWindowWithId(winId);
// find the OOP frame
let f = outerWin.document.getElementsByTagName("iframe")[frameId];
return f;
}
getFrameMM(winId, frameId) {
let oopFrame = this.getOopFrame(winId, frameId);
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
return mm;
}
/**
* Switch to OOP frame. We're handling this here so we can maintain
* a list of remote frames.
*/
switchToFrame(winId, frameId) {
let oopFrame = this.getOopFrame(winId, frameId);
let mm = this.getFrameMM(winId, frameId);
// see if this frame already has our frame script loaded in it;
// if so, just wake it up
for (let i = 0; i < remoteFrames.length; i++) {
let f = remoteFrames[i];
let fmm = f.messageManager.get();
try {
fmm.sendAsyncMessage("aliveCheck", {});
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_INITIALIZED) {
remoteFrames.splice(i--, 1);
continue;
}
}
if (fmm == mm) {
this.currentRemoteFrame = f;
this.addMessageManagerListeners(mm);
mm.sendAsyncMessage("Marionette:restart");
return oopFrame.id;
}
}
// if we get here, then we need to load the frame script in this frame,
// and set the frame's ChromeMessageSender as the active message manager
// the driver will listen to.
this.addMessageManagerListeners(mm);
let f = new frame.RemoteFrame(winId, frameId);
f.messageManager = Cu.getWeakReference(mm);
remoteFrames.push(f);
this.currentRemoteFrame = f;
mm.loadFrameScript(FRAME_SCRIPT, true, true);
return oopFrame.id;
}
/*
* This function handles switching back to the frame that was
* interrupted by the modal dialog. It gets called by the interrupted
* frame once the dialog is dismissed and the frame resumes its process.
*/
switchToModalOrigin() {
// only handle this if we indeed switched out of the modal's
// originating frame
if (this.previousRemoteFrame !== null) {
this.currentRemoteFrame = this.previousRemoteFrame;
let mm = this.currentRemoteFrame.messageManager.get();
this.addMessageManagerListeners(mm);
}
this.handledModal = false;
}
/**
* Adds message listeners to the driver, listening for
* messages from content frame scripts. It also adds a
* MarionetteFrame:getInterruptedState message listener to the
* FrameManager, so the frame manager's state can be checked by the frame.
*
* @param {nsIMessageListenerManager} mm
* The message manager object, typically
* ChromeMessageBroadcaster or ChromeMessageSender.
*/
addMessageManagerListeners(mm) {
mm.addWeakMessageListener("Marionette:ok", this.driver);
mm.addWeakMessageListener("Marionette:done", this.driver);
mm.addWeakMessageListener("Marionette:error", this.driver);
mm.addWeakMessageListener("Marionette:emitTouchEvent", this.driver);
mm.addWeakMessageListener("Marionette:log", this.driver);
mm.addWeakMessageListener("Marionette:shareData", this.driver);
mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.driver);
mm.addWeakMessageListener("Marionette:switchedToFrame", this.driver);
mm.addWeakMessageListener("Marionette:getVisibleCookies", this.driver);
mm.addWeakMessageListener("Marionette:getImportedScripts", this.driver.importedScripts);
mm.addWeakMessageListener("Marionette:register", this.driver);
mm.addWeakMessageListener("Marionette:listenersAttached", this.driver);
mm.addWeakMessageListener("MarionetteFrame:handleModal", this);
mm.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
mm.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
}
/**
* Removes listeners for messages from content frame scripts.
* We do not remove the MarionetteFrame:getInterruptedState or
* the Marionette:switchToModalOrigin message listener, because we
* want to allow all known frames to contact the frame manager so
* that it can check if it was interrupted, and if so, it will call
* switchToModalOrigin when its process gets resumed.
*
* @param {nsIMessageListenerManager} mm
* The message manager object, typically
* ChromeMessageBroadcaster or ChromeMessageSender.
*/
removeMessageManagerListeners(mm) {
mm.removeWeakMessageListener("Marionette:ok", this.driver);
mm.removeWeakMessageListener("Marionette:done", this.driver);
mm.removeWeakMessageListener("Marionette:error", this.driver);
mm.removeWeakMessageListener("Marionette:log", this.driver);
mm.removeWeakMessageListener("Marionette:shareData", this.driver);
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.driver);
mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.driver);
mm.removeWeakMessageListener("Marionette:getImportedScripts", this.driver.importedScripts);
mm.removeWeakMessageListener("Marionette:listenersAttached", this.driver);
mm.removeWeakMessageListener("Marionette:register", this.driver);
mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
}
};
frame.Manager.prototype.QueryInterface = XPCOMUtils.generateQI(
[Ci.nsIMessageListener, Ci.nsISupportsWeakReference]);

View file

@ -0,0 +1,3 @@
[flake8]
max-line-length = 99
exclude = __init__.py,disti/*,build/*,marionette_harness/runner/mixins/*, marionette_harness/tests/*

View file

@ -0,0 +1,3 @@
exclude MANIFEST.in
include requirements.txt
recursive-include marionette_harness/www *

View file

@ -0,0 +1,30 @@
marionette-harness
==================
Marionette is an automation driver for Mozilla's Gecko engine. It can remotely
control either the UI or the internal JavaScript of a Gecko platform, such as
Firefox. It can control both the chrome (i.e. menus and functions) or the
content (the webpage loaded inside the browsing context), giving a high level
of control and ability to replicate user actions. In addition to performing
actions on the browser, Marionette can also read the properties and attributes
of the DOM.
The marionette_harness package contains the test runner for Marionette, and
allows you to run automated tests written in Python for Gecko based
applications. Therefore it offers the necessary testcase classes, which are
based on the unittest framework.
For more information and the repository please checkout:
- home and docs: https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette
Example
-------
The following command will run the tests as specified via a manifest file, or
test path, or test folder in Firefox:
marionette --binary %path_to_firefox% [manifest_file | test_file | test_folder]
To get an overview about all possible option run `marionette --help`.

View file

@ -0,0 +1,35 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
__version__ = '4.0.0'
from .marionette_test import (
CommonTestCase,
expectedFailure,
MarionetteTestCase,
parameterized,
run_if_e10s,
run_if_manage_instance,
skip,
skip_if_chrome,
skip_if_desktop,
skip_if_e10s,
skip_if_mobile,
SkipTest,
skip_unless_protocol,
)
from .runner import (
BaseMarionetteArguments,
BaseMarionetteTestRunner,
BrowserMobProxyArguments,
BrowserMobProxyTestCaseMixin,
Marionette,
MarionetteTest,
MarionetteTestResult,
MarionetteTextTestRunner,
TestManifest,
TestResult,
TestResultCollection,
WindowManagerMixin,
)

View file

@ -0,0 +1,31 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
__version__ = '3.1.0'
from unittest.case import (
expectedFailure,
skip,
SkipTest,
)
from .decorators import (
parameterized,
run_if_e10s,
run_if_manage_instance,
skip_if_chrome,
skip_if_desktop,
skip_if_e10s,
skip_if_mobile,
skip_unless_browser_pref,
skip_unless_protocol,
with_parameters,
)
from .testcases import (
CommonTestCase,
MarionetteTestCase,
MetaParameterized,
)

View file

@ -0,0 +1,239 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import functools
import types
from unittest.case import (
SkipTest,
)
def parameterized(func_suffix, *args, **kwargs):
r"""Decorator which generates methods given a base method and some data.
**func_suffix** is used as a suffix for the new created method and must be
unique given a base method. if **func_suffix** countains characters that
are not allowed in normal python function name, these characters will be
replaced with "_".
This decorator can be used more than once on a single base method. The class
must have a metaclass of :class:`MetaParameterized`.
Example::
# This example will generate two methods:
#
# - MyTestCase.test_it_1
# - MyTestCase.test_it_2
#
class MyTestCase(MarionetteTestCase):
@parameterized("1", 5, named='name')
@parameterized("2", 6, named='name2')
def test_it(self, value, named=None):
print value, named
:param func_suffix: will be used as a suffix for the new method
:param \*args: arguments to pass to the new method
:param \*\*kwargs: named arguments to pass to the new method
"""
def wrapped(func):
if not hasattr(func, 'metaparameters'):
func.metaparameters = []
func.metaparameters.append((func_suffix, args, kwargs))
return func
return wrapped
def run_if_e10s(reason):
"""Decorator which runs a test if e10s mode is active."""
def decorator(test_item):
if not isinstance(test_item, types.FunctionType):
raise Exception('Decorator only supported for functions')
@functools.wraps(test_item)
def skip_wrapper(self, *args, **kwargs):
with self.marionette.using_context('chrome'):
multi_process_browser = not self.marionette.execute_script("""
try {
return Services.appinfo.browserTabsRemoteAutostart;
} catch (e) {
return false;
}
""")
if multi_process_browser:
raise SkipTest(reason)
return test_item(self, *args, **kwargs)
return skip_wrapper
return decorator
def run_if_manage_instance(reason):
"""Decorator which runs a test if Marionette manages the application instance."""
def decorator(test_item):
if not isinstance(test_item, types.FunctionType):
raise Exception('Decorator only supported for functions')
@functools.wraps(test_item)
def skip_wrapper(self, *args, **kwargs):
if self.marionette.instance is None:
raise SkipTest(reason)
return test_item(self, *args, **kwargs)
return skip_wrapper
return decorator
def skip_if_chrome(reason):
"""Decorator which skips a test if chrome context is active."""
def decorator(test_item):
if not isinstance(test_item, types.FunctionType):
raise Exception('Decorator only supported for functions')
@functools.wraps(test_item)
def skip_wrapper(self, *args, **kwargs):
if self.marionette._send_message('getContext', key='value') == 'chrome':
raise SkipTest(reason)
return test_item(self, *args, **kwargs)
return skip_wrapper
return decorator
def skip_if_desktop(reason):
"""Decorator which skips a test if run on desktop."""
def decorator(test_item):
if not isinstance(test_item, types.FunctionType):
raise Exception('Decorator only supported for functions')
@functools.wraps(test_item)
def skip_wrapper(self, *args, **kwargs):
if self.marionette.session_capabilities.get('browserName') == 'firefox':
raise SkipTest(reason)
return test_item(self, *args, **kwargs)
return skip_wrapper
return decorator
def skip_if_e10s(reason):
"""Decorator which skips a test if e10s mode is active."""
def decorator(test_item):
if not isinstance(test_item, types.FunctionType):
raise Exception('Decorator only supported for functions')
@functools.wraps(test_item)
def skip_wrapper(self, *args, **kwargs):
with self.marionette.using_context('chrome'):
multi_process_browser = self.marionette.execute_script("""
try {
return Services.appinfo.browserTabsRemoteAutostart;
} catch (e) {
return false;
}
""")
if multi_process_browser:
raise SkipTest(reason)
return test_item(self, *args, **kwargs)
return skip_wrapper
return decorator
def skip_if_mobile(reason):
"""Decorator which skips a test if run on mobile."""
def decorator(test_item):
if not isinstance(test_item, types.FunctionType):
raise Exception('Decorator only supported for functions')
@functools.wraps(test_item)
def skip_wrapper(self, *args, **kwargs):
if self.marionette.session_capabilities.get('browserName') == 'fennec':
raise SkipTest(reason)
return test_item(self, *args, **kwargs)
return skip_wrapper
return decorator
def skip_unless_browser_pref(reason, pref, predicate=bool):
"""Decorator which skips a test based on the value of a browser preference.
:param reason: Message describing why the test need to be skipped.
:param pref: the preference name
:param predicate: a function that should return false to skip the test.
The function takes one parameter, the preference value.
Defaults to the python built-in bool function.
Note that the preference must exist, else a failure is raised.
Example: ::
class TestSomething(MarionetteTestCase):
@skip_unless_browser_pref("Sessionstore needs to be enabled for crashes",
"browser.sessionstore.resume_from_crash",
lambda value: value is True,
)
def test_foo(self):
pass # test implementation here
"""
def decorator(test_item):
if not isinstance(test_item, types.FunctionType):
raise Exception('Decorator only supported for functions')
if not callable(predicate):
raise ValueError('predicate must be callable')
@functools.wraps(test_item)
def skip_wrapper(self, *args, **kwargs):
value = self.marionette.get_pref(pref)
if value is None:
self.fail("No such browser preference: {0!r}".format(pref))
if not predicate(value):
raise SkipTest(reason)
return test_item(self, *args, **kwargs)
return skip_wrapper
return decorator
def skip_unless_protocol(reason, predicate):
"""Decorator which skips a test if the predicate does not match the current protocol level."""
def decorator(test_item):
if not isinstance(test_item, types.FunctionType):
raise Exception('Decorator only supported for functions')
if not callable(predicate):
raise ValueError('predicate must be callable')
@functools.wraps(test_item)
def skip_wrapper(self, *args, **kwargs):
level = self.marionette.client.protocol
if not predicate(level):
raise SkipTest(reason)
return test_item(self, *args, **kwargs)
return skip_wrapper
return decorator
def with_parameters(parameters):
"""Decorator which generates methods given a base method and some data.
Acts like :func:`parameterized`, but define all methods in one call.
Example::
# This example will generate two methods:
#
# - MyTestCase.test_it_1
# - MyTestCase.test_it_2
#
DATA = [("1", [5], {'named':'name'}), ("2", [6], {'named':'name2'})]
class MyTestCase(MarionetteTestCase):
@with_parameters(DATA)
def test_it(self, value, named=None):
print value, named
:param parameters: list of tuples (**func_suffix**, **args**, **kwargs**)
defining parameters like in :func:`todo`.
"""
def wrapped(func):
func.metaparameters = parameters
return func
return wrapped

View file

@ -0,0 +1,504 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import imp
import os
import re
import sys
import time
import types
import unittest
import warnings
import weakref
from unittest.case import (
_ExpectedFailure,
_UnexpectedSuccess,
SkipTest,
)
from marionette_driver.errors import (
MarionetteException,
ScriptTimeoutException,
TimeoutException,
)
from mozlog import get_default_logger
def _wraps_parameterized(func, func_suffix, args, kwargs):
"""Internal: Decorator used in class MetaParameterized."""
def wrapper(self):
return func(self, *args, **kwargs)
wrapper.__name__ = func.__name__ + '_' + str(func_suffix)
wrapper.__doc__ = '[{0}] {1}'.format(func_suffix, func.__doc__)
return wrapper
class MetaParameterized(type):
"""
A metaclass that allow a class to use decorators.
It can be used like :func:`parameterized`
or :func:`with_parameters` to generate new methods.
"""
RE_ESCAPE_BAD_CHARS = re.compile(r'[\.\(\) -/]')
def __new__(cls, name, bases, attrs):
for k, v in attrs.items():
if callable(v) and hasattr(v, 'metaparameters'):
for func_suffix, args, kwargs in v.metaparameters:
func_suffix = cls.RE_ESCAPE_BAD_CHARS.sub('_', func_suffix)
wrapper = _wraps_parameterized(v, func_suffix, args, kwargs)
if wrapper.__name__ in attrs:
raise KeyError("{0} is already a defined method on {1}"
.format(wrapper.__name__, name))
attrs[wrapper.__name__] = wrapper
del attrs[k]
return type.__new__(cls, name, bases, attrs)
class JSTest:
head_js_re = re.compile(r"MARIONETTE_HEAD_JS(\s*)=(\s*)['|\"](.*?)['|\"];")
context_re = re.compile(r"MARIONETTE_CONTEXT(\s*)=(\s*)['|\"](.*?)['|\"];")
timeout_re = re.compile(r"MARIONETTE_TIMEOUT(\s*)=(\s*)(\d+);")
inactivity_timeout_re = re.compile(r"MARIONETTE_INACTIVITY_TIMEOUT(\s*)=(\s*)(\d+);")
class CommonTestCase(unittest.TestCase):
__metaclass__ = MetaParameterized
match_re = None
failureException = AssertionError
pydebugger = None
def __init__(self, methodName, marionette_weakref, fixtures, **kwargs):
super(CommonTestCase, self).__init__(methodName)
self.methodName = methodName
self._marionette_weakref = marionette_weakref
self.fixtures = fixtures
self.loglines = []
self.duration = 0
self.start_time = 0
self.expected = kwargs.pop('expected', 'pass')
self.logger = get_default_logger()
def _enter_pm(self):
if self.pydebugger:
self.pydebugger.post_mortem(sys.exc_info()[2])
def _addSkip(self, result, reason):
addSkip = getattr(result, 'addSkip', None)
if addSkip is not None:
addSkip(self, reason)
else:
warnings.warn("TestResult has no addSkip method, skips not reported",
RuntimeWarning, 2)
result.addSuccess(self)
def run(self, result=None):
# Bug 967566 suggests refactoring run, which would hopefully
# mean getting rid of this inner function, which only sits
# here to reduce code duplication:
def expected_failure(result, exc_info):
addExpectedFailure = getattr(result, "addExpectedFailure", None)
if addExpectedFailure is not None:
addExpectedFailure(self, exc_info)
else:
warnings.warn("TestResult has no addExpectedFailure method, "
"reporting as passes", RuntimeWarning)
result.addSuccess(self)
self.start_time = time.time()
orig_result = result
if result is None:
result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
result.startTest(self)
testMethod = getattr(self, self._testMethodName)
if (getattr(self.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)):
# If the class or method was skipped.
try:
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') or
getattr(testMethod, '__unittest_skip_why__', ''))
self._addSkip(result, skip_why)
finally:
result.stopTest(self)
self.stop_time = time.time()
return
try:
success = False
try:
if self.expected == "fail":
try:
self.setUp()
except Exception:
raise _ExpectedFailure(sys.exc_info())
else:
self.setUp()
except SkipTest as e:
self._addSkip(result, str(e))
except KeyboardInterrupt:
raise
except _ExpectedFailure as e:
expected_failure(result, e.exc_info)
except:
self._enter_pm()
result.addError(self, sys.exc_info())
else:
try:
if self.expected == 'fail':
try:
testMethod()
except:
raise _ExpectedFailure(sys.exc_info())
raise _UnexpectedSuccess
else:
testMethod()
except self.failureException:
self._enter_pm()
result.addFailure(self, sys.exc_info())
except KeyboardInterrupt:
raise
except _ExpectedFailure as e:
expected_failure(result, e.exc_info)
except _UnexpectedSuccess:
addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
if addUnexpectedSuccess is not None:
addUnexpectedSuccess(self)
else:
warnings.warn("TestResult has no addUnexpectedSuccess method, "
"reporting as failures",
RuntimeWarning)
result.addFailure(self, sys.exc_info())
except SkipTest as e:
self._addSkip(result, str(e))
except:
self._enter_pm()
result.addError(self, sys.exc_info())
else:
success = True
try:
if self.expected == "fail":
try:
self.tearDown()
except:
raise _ExpectedFailure(sys.exc_info())
else:
self.tearDown()
except KeyboardInterrupt:
raise
except _ExpectedFailure as e:
expected_failure(result, e.exc_info)
except:
self._enter_pm()
result.addError(self, sys.exc_info())
success = False
# Here we could handle doCleanups() instead of calling cleanTest directly
self.cleanTest()
if success:
result.addSuccess(self)
finally:
result.stopTest(self)
if orig_result is None:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
@classmethod
def match(cls, filename):
"""Determine if the specified filename should be handled by this test class.
This is done by looking for a match for the filename using cls.match_re.
"""
if not cls.match_re:
return False
m = cls.match_re.match(filename)
return m is not None
@classmethod
def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette,
fixtures, testvars, **kwargs):
"""Add all the tests in the specified file to the specified suite."""
raise NotImplementedError
@property
def test_name(self):
if hasattr(self, 'jsFile'):
return os.path.basename(self.jsFile)
else:
return '{0}.py {1}.{2}'.format(self.__class__.__module__,
self.__class__.__name__,
self._testMethodName)
def id(self):
# TBPL starring requires that the "test name" field of a failure message
# not differ over time. The test name to be used is passed to
# mozlog via the test id, so this is overriden to maintain
# consistency.
return self.test_name
def setUp(self):
# Convert the marionette weakref to an object, just for the
# duration of the test; this is deleted in tearDown() to prevent
# a persistent circular reference which in turn would prevent
# proper garbage collection.
self.start_time = time.time()
self.marionette = self._marionette_weakref()
if self.marionette.session is None:
self.marionette.start_session()
self.marionette.timeout.reset()
super(CommonTestCase, self).setUp()
def cleanTest(self):
self._deleteSession()
def _deleteSession(self):
if hasattr(self, 'start_time'):
self.duration = time.time() - self.start_time
if hasattr(self.marionette, 'session'):
if self.marionette.session is not None:
try:
self.loglines.extend(self.marionette.get_logs())
except Exception, inst:
self.loglines = [['Error getting log: {}'.format(inst)]]
try:
self.marionette.delete_session()
except IOError:
# Gecko has crashed?
pass
self.marionette = None
def setup_SpecialPowers_observer(self):
self.marionette.set_context("chrome")
self.marionette.execute_script("""
let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
Components.utils.import("resource://gre/modules/Preferences.jsm");
Preferences.set(SECURITY_PREF, true);
if (!testUtils.hasOwnProperty("specialPowersObserver")) {
let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm",
testUtils);
testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver();
testUtils.specialPowersObserver.init();
}
""")
def run_js_test(self, filename, marionette=None):
"""Run a JavaScript test file.
It collects its set of assertions into the current test's results.
:param filename: The path to the JavaScript test file to execute.
May be relative to the current script.
:param marionette: The Marionette object in which to execute the test.
Defaults to self.marionette.
"""
marionette = marionette or self.marionette
if not os.path.isabs(filename):
# Find the caller's filename and make the path relative to that.
caller_file = sys._getframe(1).f_globals.get('__file__', '')
caller_file = os.path.abspath(caller_file)
filename = os.path.join(os.path.dirname(caller_file), filename)
self.assert_(os.path.exists(filename),
'Script "{}" must exist' .format(filename))
original_test_name = self.marionette.test_name
self.marionette.test_name = os.path.basename(filename)
f = open(filename, 'r')
js = f.read()
args = []
head_js = JSTest.head_js_re.search(js)
if head_js:
head_js = head_js.group(3)
head = open(os.path.join(os.path.dirname(filename), head_js), 'r')
js = head.read() + js
context = JSTest.context_re.search(js)
if context:
context = context.group(3)
else:
context = 'content'
if 'SpecialPowers' in js:
self.setup_SpecialPowers_observer()
if context == 'content':
js = "var SpecialPowers = window.wrappedJSObject.SpecialPowers;\n" + js
else:
marionette.execute_script("""
if (typeof(SpecialPowers) == 'undefined') {
let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js");
loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js");
loader.loadSubScript("chrome://specialpowers/content/ChromePowers.js");
}
""")
marionette.set_context(context)
if context != 'chrome':
marionette.navigate('data:text/html,<html>test page</html>')
timeout = JSTest.timeout_re.search(js)
if timeout:
ms = timeout.group(3)
marionette.timeout.script = int(ms) / 1000.0
inactivity_timeout = JSTest.inactivity_timeout_re.search(js)
if inactivity_timeout:
inactivity_timeout = int(inactivity_timeout.group(3))
try:
results = marionette.execute_js_script(
js,
args,
inactivity_timeout=inactivity_timeout,
filename=os.path.basename(filename)
)
self.assertTrue('timeout' not in filename,
'expected timeout not triggered')
if 'fail' in filename:
self.assertTrue(len(results['failures']) > 0,
"expected test failures didn't occur")
else:
for failure in results['failures']:
diag = "" if failure.get('diag') is None else failure['diag']
name = ("got false, expected true" if failure.get('name') is None else
failure['name'])
self.logger.test_status(self.test_name, name, 'FAIL',
message=diag)
for failure in results['expectedFailures']:
diag = "" if failure.get('diag') is None else failure['diag']
name = ("got false, expected false" if failure.get('name') is None else
failure['name'])
self.logger.test_status(self.test_name, name, 'FAIL',
expected='FAIL', message=diag)
for failure in results['unexpectedSuccesses']:
diag = "" if failure.get('diag') is None else failure['diag']
name = ("got true, expected false" if failure.get('name') is None else
failure['name'])
self.logger.test_status(self.test_name, name, 'PASS',
expected='FAIL', message=diag)
self.assertEqual(0, len(results['failures']),
'{} tests failed' .format(len(results['failures'])))
if len(results['unexpectedSuccesses']) > 0:
raise _UnexpectedSuccess('')
if len(results['expectedFailures']) > 0:
raise _ExpectedFailure((AssertionError, AssertionError(''), None))
self.assertTrue(results['passed'] +
len(results['failures']) +
len(results['expectedFailures']) +
len(results['unexpectedSuccesses']) > 0,
'no tests run')
except ScriptTimeoutException:
if 'timeout' in filename:
# expected exception
pass
else:
self.loglines = marionette.get_logs()
raise
self.marionette.test_name = original_test_name
class MarionetteTestCase(CommonTestCase):
match_re = re.compile(r"test_(.*)\.py$")
def __init__(self, marionette_weakref, fixtures, methodName='runTest',
filepath='', **kwargs):
self.filepath = filepath
self.testvars = kwargs.pop('testvars', None)
super(MarionetteTestCase, self).__init__(
methodName, marionette_weakref=marionette_weakref, fixtures=fixtures, **kwargs)
@classmethod
def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette,
fixtures, testvars, **kwargs):
# since we use imp.load_source to load test modules, if a module
# is loaded with the same name as another one the module would just be
# reloaded.
#
# We may end up by finding too many test in a module then since
# reload() only update the module dict (so old keys are still there!)
# see https://docs.python.org/2/library/functions.html#reload
#
# we get rid of that by removing the module from sys.modules,
# so we ensure that it will be fully loaded by the
# imp.load_source call.
if mod_name in sys.modules:
del sys.modules[mod_name]
test_mod = imp.load_source(mod_name, filepath)
for name in dir(test_mod):
obj = getattr(test_mod, name)
if (isinstance(obj, (type, types.ClassType)) and
issubclass(obj, unittest.TestCase)):
testnames = testloader.getTestCaseNames(obj)
for testname in testnames:
suite.addTest(obj(weakref.ref(marionette),
fixtures,
methodName=testname,
filepath=filepath,
testvars=testvars,
**kwargs))
def setUp(self):
super(MarionetteTestCase, self).setUp()
self.marionette.test_name = self.test_name
self.marionette.execute_script("log('TEST-START: {0}:{1}')"
.format(self.filepath.replace('\\', '\\\\'),
self.methodName),
sandbox="simpletest")
def tearDown(self):
# In the case no session is active (eg. the application was quit), start
# a new session for clean-up steps.
if not self.marionette.session:
self.marionette.start_session()
if not self.marionette.crashed:
try:
self.marionette.clear_imported_scripts()
self.marionette.execute_script("log('TEST-END: {0}:{1}')"
.format(self.filepath.replace('\\', '\\\\'),
self.methodName),
sandbox="simpletest")
self.marionette.test_name = None
except (MarionetteException, IOError):
# We have tried to log the test end when there is no listener
# object that we can access
pass
super(MarionetteTestCase, self).tearDown()
def wait_for_condition(self, method, timeout=30):
timeout = float(timeout) + time.time()
while time.time() < timeout:
value = method(self.marionette)
if value:
return value
time.sleep(0.5)
else:
raise TimeoutException("wait_for_condition timed out")

View file

@ -0,0 +1,22 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from .base import (
BaseMarionetteArguments,
BaseMarionetteTestRunner,
Marionette,
MarionetteTest,
MarionetteTestResult,
MarionetteTextTestRunner,
TestManifest,
TestResult,
TestResultCollection,
)
from .mixins import (
BrowserMobProxyTestCaseMixin,
BrowserMobProxyArguments,
BrowserMobTestCase,
WindowManagerMixin,
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""Specialisation of wptserver.server.WebTestHttpd for testing
Marionette.
"""
import argparse
import os
import select
import sys
import time
import urlparse
from wptserve import server, handlers, routes as default_routes
here = os.path.abspath(os.path.dirname(__file__))
default_doc_root = os.path.join(os.path.dirname(here), "www")
default_ssl_cert = os.path.join(here, "test.cert")
default_ssl_key = os.path.join(here, "test.key")
@handlers.handler
def upload_handler(request, response):
return 200, [], [request.headers.get("Content-Type")] or []
@handlers.handler
def slow_loading_document(request, response):
time.sleep(5)
return """<!doctype html>
<title>ok</title>
<p>ok"""
class NotAliveError(Exception):
"""Occurs when attempting to run a function that requires the HTTPD
to have been started, and it has not.
"""
pass
class FixtureServer(object):
def __init__(self, doc_root, url="http://127.0.0.1:0", use_ssl=False,
ssl_cert=None, ssl_key=None):
if not os.path.isdir(doc_root):
raise ValueError("Server root is not a directory: %s" % doc_root)
url = urlparse.urlparse(url)
if url.scheme is None:
raise ValueError("Server scheme not provided")
scheme, host, port = url.scheme, url.hostname, url.port
if host is None:
host = "127.0.0.1"
if port is None:
port = 0
routes = [("POST", "/file_upload", upload_handler),
("GET", "/slow", slow_loading_document)]
routes.extend(default_routes.routes)
self._httpd = server.WebTestHttpd(host=host,
port=port,
bind_hostname=True,
doc_root=doc_root,
routes=routes,
use_ssl=True if scheme == "https" else False,
certificate=ssl_cert,
key_file=ssl_key)
def start(self, block=False):
if self.is_alive:
return
self._httpd.start(block=block)
def wait(self):
if not self.is_alive:
return
try:
select.select([], [], [])
except KeyboardInterrupt:
self.stop()
def stop(self):
if not self.is_alive:
return
self._httpd.stop()
def get_url(self, path):
if not self.is_alive:
raise NotAliveError()
return self._httpd.get_url(path)
@property
def doc_root(self):
return self._httpd.router.doc_root
@property
def router(self):
return self._httpd.router
@property
def routes(self):
return self._httpd.router.routes
@property
def is_alive(self):
return self._httpd.started
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Specialised HTTP server for testing Marionette.")
parser.add_argument("url", help="""
service address including scheme, hostname, port, and prefix for document root,
e.g. \"https://0.0.0.0:0/base/\"""")
parser.add_argument(
"-r", dest="doc_root", default=default_doc_root,
help="path to document root (default %(default)s)")
parser.add_argument(
"-c", dest="ssl_cert", default=default_ssl_cert,
help="path to SSL certificate (default %(default)s)")
parser.add_argument(
"-k", dest="ssl_key", default=default_ssl_key,
help="path to SSL certificate key (default %(default)s)")
args = parser.parse_args()
httpd = FixtureServer(args.doc_root, args.url,
ssl_cert=args.ssl_cert,
ssl_key=args.ssl_key)
httpd.start()
print >>sys.stderr, "%s: started fixture server on %s" % \
(sys.argv[0], httpd.get_url("/"))
httpd.wait()

View file

@ -0,0 +1,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from .browsermob import (
BrowserMobProxyTestCaseMixin,
BrowserMobProxyArguments,
BrowserMobTestCase,
)
from .window_manager import (
WindowManagerMixin,
)

View file

@ -0,0 +1,57 @@
0.6.0 / 2014-01-21
==================
* Added support for parameters in har creation
* Bug fixes for tests that are out of date
* Setup server constructor to look on path for location of browsermob-proxy executable. As well as looking for a file. Also added example code for using browsermob-proxy with chrome
* Fix project name
* adding docs
0.5.0 / 2013-05-23
==================
* Allow proxying of ssl requests with selenium.
* Updating case for proxy type
0.4.0 / 2013-03-06
==================
* Allow setting basic authentication
* Adding the ability to remap hosts which is available from BrowserMob Proxy Beta 7
* Merge pull request #6 from lukeis/patch-2
* Update readme.md
* initial commit of event listener to auto do record
* server.create_proxy is a function, should be called :)
* forgot to add the port
0.2.0 / 2012-06-18
==================
* pep8 --ignore=E501
* DELETE /proxy/:port/
* /proxy/:port/limits
* /proxy/:port/blacklist
* /proxy/:port/whitelist
* fixing /proxy/:port/har/pageRef
* fixing /proxy/:port/har/pageRef
* fixing passing in a page ref as the name for the page in /proxy/:port/har
* tests around /proxy/:port/har and some cleanup of the implementation
* make /proxy/:port/headers work
* wrapping selenium_proxy with webdriver_proxy since the project is more than just webdriver
* extending the client to play nice with remote webdriver instances
* create_proxy sounds and feels like a method to me, let's make it so
* ensure the self.process exist, to reduce possibilities of AttributeError
* check the path before attempting to start the server
* wait longer than 3 seconds for the server to come up
0.1.0 / 2012-03-22
==================
* Removed httplib2 in preference for requests
* Added support for setting headers
0.0.1 / 2012-01-16
==================
* Initial version

View file

@ -0,0 +1,6 @@
__version__ = '0.5.0'
from .server import Server
from .client import Client
__all__ = ['Server', 'Client', 'browsermobproxy']

View file

@ -0,0 +1,326 @@
import requests
try:
from urllib.parse import urlencode, unquote
except ImportError:
from urllib import urlencode, unquote
import json
class Client(object):
def __init__(self, url, params={}):
"""
Initialises a new Client object
:param url: This is where the BrowserMob Proxy lives
:param params: URL query (for example httpProxy and httpsProxy vars)
"""
self.host = "http://" + url
if params:
urlparams = "?" + unquote(urlencode(params))
else:
urlparams = ""
resp = requests.post('{}/proxy'.format(self.host + urlparams))
jcontent = json.loads(resp.content.decode('utf-8'))
self.port = jcontent['port']
url_parts = self.host.split(":")
self.proxy = url_parts[1][2:] + ":" + str(self.port)
def close(self):
"""
shuts down the proxy and closes the port
"""
r = requests.delete('{0}/proxy/{1}'.format(self.host, self.port))
return r.status_code
# webdriver integration
# ...as a proxy object
def selenium_proxy(self):
"""
Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy
"""
from selenium import webdriver
return webdriver.Proxy({
"httpProxy": self.proxy,
"sslProxy": self.proxy,
})
def webdriver_proxy(self):
"""
Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy
"""
return self.selenium_proxy()
# ...as a capability
def add_to_capabilities(self, capabilities):
"""
Adds an 'proxy' entry to a desired capabilities dictionary with the
BrowserMob proxy information
:param capabilities: The Desired capabilities object from Selenium WebDriver
"""
capabilities['proxy'] = {
'proxyType': "MANUAL",
'httpProxy': self.proxy,
'sslProxy': self.proxy
}
def add_to_webdriver_capabilities(self, capabilities):
self.add_to_capabilities(capabilities)
# browsermob proxy api
@property
def har(self):
"""
Gets the HAR that has been recorded
"""
r = requests.get('{0}/proxy/{1}/har'.format(self.host, self.port))
return r.json()
def new_har(self, ref=None, options={}):
"""
This sets a new HAR to be recorded
:param ref: A reference for the HAR. Defaults to None
:param options: A dictionary that will be passed to BrowserMob Proxy \
with specific keywords. Keywords are: \
captureHeaders - Boolean, capture headers \
captureContent - Boolean, capture content bodies \
captureBinaryContent - Boolean, capture binary content
"""
if ref:
payload = {"initialPageRef": ref}
else:
payload = {}
if options:
payload.update(options)
r = requests.put('{0}/proxy/{1}/har'.format(self.host, self.port), payload)
if r.status_code == 200:
return (r.status_code, r.json())
else:
return (r.status_code, None)
def new_page(self, ref=None):
"""
This sets a new page to be recorded
:param ref: A reference for the new page. Defaults to None
"""
if ref:
payload = {"pageRef": ref}
else:
payload = {}
r = requests.put('{0}/proxy/{1}/har/pageRef'.format(self.host, self.port),
payload)
return r.status_code
def blacklist(self, regexp, status_code):
"""
Sets a list of URL patterns to blacklist
:param regex: a comma separated list of regular expressions
:param status_code: the HTTP status code to return for URLs that do not \
match the blacklist
"""
r = requests.put('{0}/proxy/{1}/blacklist'.format(self.host, self.port),
{'regex': regexp, 'status': status_code})
return r.status_code
def whitelist(self, regexp, status_code):
"""
Sets a list of URL patterns to whitelist
:param regex: a comma separated list of regular expressions
:param status_code: the HTTP status code to return for URLs that do not \
match the whitelist
"""
r = requests.put('{0}/proxy/{1}/whitelist'.format(self.host, self.port),
{'regex': regexp, 'status': status_code})
return r.status_code
def basic_authentication(self, domain, username, password):
"""
This add automatic basic authentication
:param domain: domain to set authentication credentials for
:param username: valid username to use when authenticating
:param password: valid password to use when authenticating
"""
r = requests.post(url='{0}/proxy/{1}/auth/basic/{2}'.format(self.host, self.port, domain),
data=json.dumps({'username': username, 'password': password}),
headers={'content-type': 'application/json'})
return r.status_code
def headers(self, headers):
"""
This sets the headers that will set by the proxy on all requests
:param headers: this is a dictionary of the headers to be set
"""
if not isinstance(headers, dict):
raise TypeError("headers needs to be dictionary")
r = requests.post(url='{0}/proxy/{1}/headers'.format(self.host, self.port),
data=json.dumps(headers),
headers={'content-type': 'application/json'})
return r.status_code
def response_interceptor(self, js):
"""
Executes the javascript against each response
:param js: the javascript to execute
"""
r = requests.post(url='{0}/proxy/{1}/interceptor/response'.format(self.host, self.port),
data=js,
headers={'content-type': 'x-www-form-urlencoded'})
return r.status_code
def request_interceptor(self, js):
"""
Executes the javascript against each request
:param js: the javascript to execute
"""
r = requests.post(url='{0}/proxy/{1}/interceptor/request'.format(self.host, self.port),
data=js,
headers={'content-type': 'x-www-form-urlencoded'})
return r.status_code
LIMITS = {
'upstream_kbps': 'upstreamKbps',
'downstream_kbps': 'downstreamKbps',
'latency': 'latency'
}
def limits(self, options):
"""
Limit the bandwidth through the proxy.
:param options: A dictionary with all the details you want to set. \
downstreamKbps - Sets the downstream kbps \
upstreamKbps - Sets the upstream kbps \
latency - Add the given latency to each HTTP request
"""
params = {}
for (k, v) in list(options.items()):
if k not in self.LIMITS:
raise KeyError('invalid key: {}'.format(k))
params[self.LIMITS[k]] = int(v)
if len(list(params.items())) == 0:
raise KeyError("You need to specify one of the valid Keys")
r = requests.put('{0}/proxy/{1}/limit'.format(self.host, self.port),
params)
return r.status_code
TIMEOUTS = {
'request': 'requestTimeout',
'read': 'readTimeout',
'connection': 'connectionTimeout',
'dns': 'dnsCacheTimeout'
}
def timeouts(self, options):
"""
Configure various timeouts in the proxy
:param options: A dictionary with all the details you want to set. \
request - request timeout (in seconds) \
read - read timeout (in seconds) \
connection - connection timeout (in seconds) \
dns - dns lookup timeout (in seconds)
"""
params = {}
for (k, v) in list(options.items()):
if k not in self.TIMEOUTS:
raise KeyError('invalid key: {}'.format(k))
params[self.TIMEOUTS[k]] = int(v)
if len(list(params.items())) == 0:
raise KeyError("You need to specify one of the valid Keys")
r = requests.put('{0}/proxy/{1}/timeout'.format(self.host, self.port),
params)
return r.status_code
def remap_hosts(self, address, ip_address):
"""
Remap the hosts for a specific URL
:param address: url that you wish to remap
:param ip_address: IP Address that will handle all traffic for the address passed in
"""
assert address is not None and ip_address is not None
r = requests.post('{0}/proxy/{1}/hosts'.format(self.host, self.port),
json.dumps({address: ip_address}),
headers={'content-type': 'application/json'})
return r.status_code
def wait_for_traffic_to_stop(self, quiet_period, timeout):
"""
Waits for the network to be quiet
:param quiet_period: number of miliseconds the network needs to be quiet for
:param timeout: max number of miliseconds to wait
"""
r = requests.put('{0}/proxy/{1}/wait'.format(self.host, self.port),
{'quietPeriodInMs': quiet_period, 'timeoutInMs': timeout})
return r.status_code
def clear_dns_cache(self):
"""
Clears the DNS cache associated with the proxy instance
"""
r = requests.delete('{0}/proxy/{1}/dns/cache'.format(self.host, self.port))
return r.status_code
def rewrite_url(self, match, replace):
"""
Rewrites the requested url.
:param match: a regex to match requests with
:param replace: unicode \
a string to replace the matches with
"""
params = {
"matchRegex": match,
"replace": replace
}
r = requests.put('{0}/proxy/{1}/rewrite'.format(self.host, self.port),
params)
return r.status_code
def retry(self, retry_count):
"""
Retries. No idea what its used for, but its in the API...
:param retry_count: the number of retries
"""
r = requests.put('{0}/proxy/{1}/retry'.format(self.host, self.port),
{'retrycount': retry_count})
return r.status_code

View file

@ -0,0 +1,106 @@
import os
import platform
import socket
import subprocess
import time
from .client import Client
class Server(object):
def __init__(self, path='browsermob-proxy', options={}):
"""
Initialises a Server object
:param path: Path to the browsermob proxy batch file
:param options: Dictionary that can hold the port. \
More items will be added in the future. \
This defaults to an empty dictionary
"""
path_var_sep = ':'
if platform.system() == 'Windows':
path_var_sep = ';'
if not path.endswith('.bat'):
path += '.bat'
exec_not_on_path = True
for directory in os.environ['PATH'].split(path_var_sep):
if(os.path.isfile(os.path.join(directory, path))):
exec_not_on_path = False
break
if not os.path.isfile(path) and exec_not_on_path:
raise IOError("Browsermob-Proxy binary couldn't be found in path"
" provided: {}".format(path))
self.path = path
self.port = options.get('port', 8080)
self.process = None
if platform.system() == 'Darwin':
self.command = ['sh']
else:
self.command = []
self.command += [path, '--port={}'.format(self.port)]
def start(self):
"""
This will start the browsermob proxy and then wait until it can
interact with it
"""
self.log_file = open(os.path.abspath('server.log'), 'w')
self.process = subprocess.Popen(self.command,
stdout=self.log_file,
stderr=subprocess.STDOUT)
count = 0
while not self._is_listening():
time.sleep(0.5)
count += 1
if count == 60:
self.stop()
raise Exception("Can't connect to Browsermob-Proxy")
def stop(self):
"""
This will stop the process running the proxy
"""
if self.process.poll() is not None:
return
try:
self.process.kill()
self.process.wait()
except AttributeError:
# kill may not be available under windows environment
pass
self.log_file.close()
@property
def url(self):
"""
Gets the url that the proxy is running on. This is not the URL clients
should connect to.
"""
return "http://localhost:{}".format(self.port)
def create_proxy(self, params={}):
"""
Gets a client class that allow to set all the proxy details that you
may need to.
:param params: Dictionary where you can specify params \
like httpProxy and httpsProxy
"""
client = Client(self.url[7:], params)
return client
def _is_listening(self):
try:
socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_.settimeout(1)
socket_.connect(("localhost", self.port))
socket_.close()
return True
except socket.error:
return False

View file

@ -0,0 +1,34 @@
from selenium.webdriver.support.abstract_event_listener import AbstractEventListener
class WebDriverEventListener(AbstractEventListener):
def __init__(self, client, refs={}):
self.client = client
self.hars = []
self.refs = refs
def before_navigate_to(self, url, driver):
if len(self.hars) != 0:
self.hars.append(self.client.har)
self.client.new_har("navigate-to-{}".format(url), self.refs)
def before_navigate_back(self, driver=None):
if driver:
name = "-from-{}".format(driver.current_url)
else:
name = "navigate-back"
self.client.new_page(name)
def before_navigate_forward(self, driver=None):
if driver:
name = "-from-{}".format(driver.current_url)
else:
name = "navigate-forward"
self.client.new_page(name)
def before_click(self, element, driver):
name = "click-element-{}".format(element.id)
self.client.new_page(name)
def before_quit(self, driver):
self.hars.append(self.client.har)

View file

@ -0,0 +1,153 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BrowserMobProxy.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BrowserMobProxy.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/BrowserMobProxy"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BrowserMobProxy"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View file

@ -0,0 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 22346719ac3713bfe792fb5638c0a301
tags: 645f666f9bcd5a90fca523b33c5a78b7

View file

@ -0,0 +1,98 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>browsermobproxy &mdash; BrowserMob Proxy 0.6.0 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '../',
VERSION: '0.6.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="../index.html" />
<link rel="up" title="Module code" href="index.html" />
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="../index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
<li><a href="index.html" accesskey="U">Module code</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<h1>Source code for browsermobproxy</h1><div class="highlight"><pre>
<span class="n">__version__</span> <span class="o">=</span> <span class="s">&#39;0.5.0&#39;</span>
<span class="kn">from</span> <span class="nn">server</span> <span class="kn">import</span> <span class="n">Server</span>
<span class="kn">from</span> <span class="nn">client</span> <span class="kn">import</span> <span class="n">Client</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;Server&#39;</span><span class="p">,</span> <span class="s">&#39;Client&#39;</span><span class="p">,</span> <span class="s">&#39;browsermobproxy&#39;</span><span class="p">]</span>
</pre></div>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="../index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
<li><a href="index.html" >Module code</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2014, David Burns.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
</div>
</body>
</html>

View file

@ -0,0 +1,90 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Overview: module code &mdash; BrowserMob Proxy 0.6.0 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '../',
VERSION: '0.6.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="../index.html" />
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="../index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<h1>All modules for which code is available</h1>
<ul><li><a href="browsermobproxy.html">browsermobproxy</a></li>
</ul>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="../index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2014, David Burns.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
</div>
</body>
</html>

View file

@ -0,0 +1,8 @@
.. toctree::
:maxdepth: 2
:mod:`client` Package
---------------------
.. automodule:: browsermobproxy
.. autoclass:: Client
:members:

View file

@ -0,0 +1,72 @@
.. BrowserMob Proxy documentation master file, created by
sphinx-quickstart on Fri May 24 12:37:12 2013.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
.. highlightlang:: python
Welcome to BrowserMob Proxy's documentation!
============================================
Python client for the BrowserMob Proxy 2.0 REST API.
How to install
--------------
BrowserMob Proxy is available on PyPI_, so you can install it with ``pip``::
$ pip install browsermob-proxy
Or with `easy_install`::
$ easy_install browsermob-proxy
Or by cloning the repo from GitHub_::
$ git clone git://github.com/AutomatedTester/browsermob-proxy-py.git
Then install it by running::
$ python setup.py install
How to use with selenium-webdriver
----------------------------------
Manually::
from browsermobproxy import Server
server = Server("path/to/browsermob-proxy")
server.start()
proxy = server.create_proxy()
from selenium import webdriver
profile = webdriver.FirefoxProfile()
profile.set_proxy(proxy.selenium_proxy())
driver = webdriver.Firefox(firefox_profile=profile)
proxy.new_har("google")
driver.get("http://www.google.co.uk")
proxy.har # returns a HAR JSON blob
server.stop()
driver.quit()
Contents:
.. toctree::
:maxdepth: 2
client.rst
server.rst
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. _GitHub: https://github.com/AutomatedTester/browsermob-proxy-py
.. _PyPI: http://pypi.python.org/pypi/browsermob-proxy

View file

@ -0,0 +1,8 @@
.. toctree::
:maxdepth: 2
:mod:`server` Package
---------------------
.. automodule:: browsermobproxy
.. autoclass:: Server
:members:

View file

@ -0,0 +1,537 @@
/*
* basic.css
* ~~~~~~~~~
*
* Sphinx stylesheet -- basic theme.
*
* :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* -- main layout ----------------------------------------------------------- */
div.clearer {
clear: both;
}
/* -- relbar ---------------------------------------------------------------- */
div.related {
width: 100%;
font-size: 90%;
}
div.related h3 {
display: none;
}
div.related ul {
margin: 0;
padding: 0 0 0 10px;
list-style: none;
}
div.related li {
display: inline;
}
div.related li.right {
float: right;
margin-right: 5px;
}
/* -- sidebar --------------------------------------------------------------- */
div.sphinxsidebarwrapper {
padding: 10px 5px 0 10px;
}
div.sphinxsidebar {
float: left;
width: 230px;
margin-left: -100%;
font-size: 90%;
}
div.sphinxsidebar ul {
list-style: none;
}
div.sphinxsidebar ul ul,
div.sphinxsidebar ul.want-points {
margin-left: 20px;
list-style: square;
}
div.sphinxsidebar ul ul {
margin-top: 0;
margin-bottom: 0;
}
div.sphinxsidebar form {
margin-top: 10px;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar #searchbox input[type="text"] {
width: 170px;
}
div.sphinxsidebar #searchbox input[type="submit"] {
width: 30px;
}
img {
border: 0;
max-width: 100%;
}
/* -- search page ----------------------------------------------------------- */
ul.search {
margin: 10px 0 0 20px;
padding: 0;
}
ul.search li {
padding: 5px 0 5px 20px;
background-image: url(file.png);
background-repeat: no-repeat;
background-position: 0 7px;
}
ul.search li a {
font-weight: bold;
}
ul.search li div.context {
color: #888;
margin: 2px 0 0 30px;
text-align: left;
}
ul.keywordmatches li.goodmatch a {
font-weight: bold;
}
/* -- index page ------------------------------------------------------------ */
table.contentstable {
width: 90%;
}
table.contentstable p.biglink {
line-height: 150%;
}
a.biglink {
font-size: 1.3em;
}
span.linkdescr {
font-style: italic;
padding-top: 5px;
font-size: 90%;
}
/* -- general index --------------------------------------------------------- */
table.indextable {
width: 100%;
}
table.indextable td {
text-align: left;
vertical-align: top;
}
table.indextable dl, table.indextable dd {
margin-top: 0;
margin-bottom: 0;
}
table.indextable tr.pcap {
height: 10px;
}
table.indextable tr.cap {
margin-top: 10px;
background-color: #f2f2f2;
}
img.toggler {
margin-right: 3px;
margin-top: 3px;
cursor: pointer;
}
div.modindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
div.genindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
/* -- general body styles --------------------------------------------------- */
a.headerlink {
visibility: hidden;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
visibility: visible;
}
div.body p.caption {
text-align: inherit;
}
div.body td {
text-align: left;
}
.field-list ul {
padding-left: 1em;
}
.first {
margin-top: 0 !important;
}
p.rubric {
margin-top: 30px;
font-weight: bold;
}
img.align-left, .figure.align-left, object.align-left {
clear: left;
float: left;
margin-right: 1em;
}
img.align-right, .figure.align-right, object.align-right {
clear: right;
float: right;
margin-left: 1em;
}
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
/* -- sidebars -------------------------------------------------------------- */
div.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddb;
padding: 7px 7px 0 7px;
background-color: #ffe;
width: 40%;
float: right;
}
p.sidebar-title {
font-weight: bold;
}
/* -- topics ---------------------------------------------------------------- */
div.topic {
border: 1px solid #ccc;
padding: 7px 7px 0 7px;
margin: 10px 0 10px 0;
}
p.topic-title {
font-size: 1.1em;
font-weight: bold;
margin-top: 10px;
}
/* -- admonitions ----------------------------------------------------------- */
div.admonition {
margin-top: 10px;
margin-bottom: 10px;
padding: 7px;
}
div.admonition dt {
font-weight: bold;
}
div.admonition dl {
margin-bottom: 0;
}
p.admonition-title {
margin: 0px 10px 5px 0px;
font-weight: bold;
}
div.body p.centered {
text-align: center;
margin-top: 25px;
}
/* -- tables ---------------------------------------------------------------- */
table.docutils {
border: 0;
border-collapse: collapse;
}
table.docutils td, table.docutils th {
padding: 1px 8px 1px 5px;
border-top: 0;
border-left: 0;
border-right: 0;
border-bottom: 1px solid #aaa;
}
table.field-list td, table.field-list th {
border: 0 !important;
}
table.footnote td, table.footnote th {
border: 0 !important;
}
th {
text-align: left;
padding-right: 5px;
}
table.citation {
border-left: solid 1px gray;
margin-left: 1px;
}
table.citation td {
border-bottom: none;
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {
list-style: decimal;
}
ol.loweralpha {
list-style: lower-alpha;
}
ol.upperalpha {
list-style: upper-alpha;
}
ol.lowerroman {
list-style: lower-roman;
}
ol.upperroman {
list-style: upper-roman;
}
dl {
margin-bottom: 15px;
}
dd p {
margin-top: 0px;
}
dd ul, dd table {
margin-bottom: 10px;
}
dd {
margin-top: 3px;
margin-bottom: 10px;
margin-left: 30px;
}
dt:target, .highlighted {
background-color: #fbe54e;
}
dl.glossary dt {
font-weight: bold;
font-size: 1.1em;
}
.field-list ul {
margin: 0;
padding-left: 1em;
}
.field-list p {
margin: 0;
}
.optional {
font-size: 1.3em;
}
.versionmodified {
font-style: italic;
}
.system-message {
background-color: #fda;
padding: 5px;
border: 3px solid red;
}
.footnote:target {
background-color: #ffa;
}
.line-block {
display: block;
margin-top: 1em;
margin-bottom: 1em;
}
.line-block .line-block {
margin-top: 0;
margin-bottom: 0;
margin-left: 1.5em;
}
.guilabel, .menuselection {
font-family: sans-serif;
}
.accelerator {
text-decoration: underline;
}
.classifier {
font-style: oblique;
}
abbr, acronym {
border-bottom: dotted 1px;
cursor: help;
}
/* -- code displays --------------------------------------------------------- */
pre {
overflow: auto;
overflow-y: hidden; /* fixes display issues on Chrome browsers */
}
td.linenos pre {
padding: 5px 0px;
border: 0;
background-color: transparent;
color: #aaa;
}
table.highlighttable {
margin-left: 0.5em;
}
table.highlighttable td {
padding: 0 0.5em 0 0.5em;
}
tt.descname {
background-color: transparent;
font-weight: bold;
font-size: 1.2em;
}
tt.descclassname {
background-color: transparent;
}
tt.xref, a tt {
background-color: transparent;
font-weight: bold;
}
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
background-color: transparent;
}
.viewcode-link {
float: right;
}
.viewcode-back {
float: right;
font-family: sans-serif;
}
div.viewcode-block:target {
margin: -1px -10px;
padding: 0 10px;
}
/* -- math display ---------------------------------------------------------- */
img.math {
vertical-align: middle;
}
div.body div.math p {
text-align: center;
}
span.eqno {
float: right;
}
/* -- printout stylesheet --------------------------------------------------- */
@media print {
div.document,
div.documentwrapper,
div.bodywrapper {
margin: 0 !important;
width: 100%;
}
div.sphinxsidebar,
div.related,
div.footer,
#top-link {
display: none;
}
}

View file

@ -0,0 +1,256 @@
/*
* default.css_t
* ~~~~~~~~~~~~~
*
* Sphinx stylesheet -- default theme.
*
* :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: sans-serif;
font-size: 100%;
background-color: #11303d;
color: #000;
margin: 0;
padding: 0;
}
div.document {
background-color: #1c4e63;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
div.body {
background-color: #ffffff;
color: #000000;
padding: 0 20px 30px 20px;
}
div.footer {
color: #ffffff;
width: 100%;
padding: 9px 0 9px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #ffffff;
text-decoration: underline;
}
div.related {
background-color: #133f52;
line-height: 30px;
color: #ffffff;
}
div.related a {
color: #ffffff;
}
div.sphinxsidebar {
}
div.sphinxsidebar h3 {
font-family: 'Trebuchet MS', sans-serif;
color: #ffffff;
font-size: 1.4em;
font-weight: normal;
margin: 0;
padding: 0;
}
div.sphinxsidebar h3 a {
color: #ffffff;
}
div.sphinxsidebar h4 {
font-family: 'Trebuchet MS', sans-serif;
color: #ffffff;
font-size: 1.3em;
font-weight: normal;
margin: 5px 0 0 0;
padding: 0;
}
div.sphinxsidebar p {
color: #ffffff;
}
div.sphinxsidebar p.topless {
margin: 5px 10px 10px 10px;
}
div.sphinxsidebar ul {
margin: 10px;
padding: 0;
color: #ffffff;
}
div.sphinxsidebar a {
color: #98dbcc;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
/* -- hyperlink styles ------------------------------------------------------ */
a {
color: #355f7c;
text-decoration: none;
}
a:visited {
color: #355f7c;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* -- body styles ----------------------------------------------------------- */
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Trebuchet MS', sans-serif;
background-color: #f2f2f2;
font-weight: normal;
color: #20435c;
border-bottom: 1px solid #ccc;
margin: 20px -20px 10px -20px;
padding: 3px 0 3px 10px;
}
div.body h1 { margin-top: 0; font-size: 200%; }
div.body h2 { font-size: 160%; }
div.body h3 { font-size: 140%; }
div.body h4 { font-size: 120%; }
div.body h5 { font-size: 110%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #c60f0f;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #c60f0f;
color: white;
}
div.body p, div.body dd, div.body li {
text-align: justify;
line-height: 130%;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.admonition p {
margin-bottom: 5px;
}
div.admonition pre {
margin-bottom: 5px;
}
div.admonition ul, div.admonition ol {
margin-bottom: 5px;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 5px;
background-color: #eeffcc;
color: #333333;
line-height: 120%;
border: 1px solid #ac9;
border-left: none;
border-right: none;
}
tt {
background-color: #ecf0f3;
padding: 0 1px 0 1px;
font-size: 0.95em;
}
th {
background-color: #ede;
}
.warning tt {
background: #efc2c2;
}
.note tt {
background: #d6d6d6;
}
.viewcode-back {
font-family: sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}

View file

@ -0,0 +1,238 @@
/*
* doctools.js
* ~~~~~~~~~~~
*
* Sphinx JavaScript utilities for all documentation.
*
* :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/**
* select a different prefix for underscore
*/
$u = _.noConflict();
/**
* make the code below compatible with browsers without
* an installed firebug like debugger
if (!window.console || !console.firebug) {
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
"profile", "profileEnd"];
window.console = {};
for (var i = 0; i < names.length; ++i)
window.console[names[i]] = function() {};
}
*/
/**
* small helper function to urldecode strings
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
};
/**
* small helper function to urlencode strings
*/
jQuery.urlencode = encodeURIComponent;
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
* it will always return arrays of strings for the value parts.
*/
jQuery.getQueryParameters = function(s) {
if (typeof s == 'undefined')
s = document.location.search;
var parts = s.substr(s.indexOf('?') + 1).split('&');
var result = {};
for (var i = 0; i < parts.length; i++) {
var tmp = parts[i].split('=', 2);
var key = jQuery.urldecode(tmp[0]);
var value = jQuery.urldecode(tmp[1]);
if (key in result)
result[key].push(value);
else
result[key] = [value];
}
return result;
};
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
jQuery.fn.highlightText = function(text, className) {
function highlight(node) {
if (node.nodeType == 3) {
var val = node.nodeValue;
var pos = val.toLowerCase().indexOf(text);
if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) {
var span = document.createElement("span");
span.className = className;
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
document.createTextNode(val.substr(pos + text.length)),
node.nextSibling));
node.nodeValue = val.substr(0, pos);
}
}
else if (!jQuery(node).is("button, select, textarea")) {
jQuery.each(node.childNodes, function() {
highlight(this);
});
}
}
return this.each(function() {
highlight(this);
});
};
/**
* Small JavaScript module for the documentation.
*/
var Documentation = {
init : function() {
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initIndexTable();
},
/**
* i18n support
*/
TRANSLATIONS : {},
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
LOCALE : 'unknown',
// gettext and ngettext don't access this so that the functions
// can safely bound to a different name (_ = Documentation.gettext)
gettext : function(string) {
var translated = Documentation.TRANSLATIONS[string];
if (typeof translated == 'undefined')
return string;
return (typeof translated == 'string') ? translated : translated[0];
},
ngettext : function(singular, plural, n) {
var translated = Documentation.TRANSLATIONS[singular];
if (typeof translated == 'undefined')
return (n == 1) ? singular : plural;
return translated[Documentation.PLURALEXPR(n)];
},
addTranslations : function(catalog) {
for (var key in catalog.messages)
this.TRANSLATIONS[key] = catalog.messages[key];
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
this.LOCALE = catalog.locale;
},
/**
* add context elements like header anchor links
*/
addContextElements : function() {
$('div[id] > :header:first').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this headline')).
appendTo(this);
});
$('dt[id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this definition')).
appendTo(this);
});
},
/**
* workaround a firefox stupidity
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash && $.browser.mozilla)
window.setTimeout(function() {
document.location.href += '';
}, 10);
},
/**
* highlight the search words provided in the url in the text
*/
highlightSearchWords : function() {
var params = $.getQueryParameters();
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
if (terms.length) {
var body = $('div.body');
if (!body.length) {
body = $('body');
}
window.setTimeout(function() {
$.each(terms, function() {
body.highlightText(this.toLowerCase(), 'highlighted');
});
}, 10);
$('<p class="highlight-link"><a href="javascript:Documentation.' +
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
.appendTo($('#searchbox'));
}
},
/**
* init the domain index toggle buttons
*/
initIndexTable : function() {
var togglers = $('img.toggler').click(function() {
var src = $(this).attr('src');
var idnum = $(this).attr('id').substr(7);
$('tr.cg-' + idnum).toggle();
if (src.substr(-9) == 'minus.png')
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
else
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
}).css('display', '');
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
togglers.click();
}
},
/**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
$('#searchbox .highlight-link').fadeOut(300);
$('span.highlighted').removeClass('highlighted');
},
/**
* make the url absolute
*/
makeURL : function(relativeURL) {
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
},
/**
* get the current relative url
*/
getCurrentURL : function() {
var path = document.location.pathname;
var parts = path.split(/\//);
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
if (this == '..')
parts.pop();
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
}
};
// quick alias for translations
_ = Documentation.gettext;
$(document).ready(function() {
Documentation.init();
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,62 @@
.highlight .hll { background-color: #ffffcc }
.highlight { background: #eeffcc; }
.highlight .c { color: #408090; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #007020 } /* Comment.Preproc */
.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #333333 } /* Generic.Output */
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #902000 } /* Keyword.Type */
.highlight .m { color: #208050 } /* Literal.Number */
.highlight .s { color: #4070a0 } /* Literal.String */
.highlight .na { color: #4070a0 } /* Name.Attribute */
.highlight .nb { color: #007020 } /* Name.Builtin */
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
.highlight .no { color: #60add5 } /* Name.Constant */
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #007020 } /* Name.Exception */
.highlight .nf { color: #06287e } /* Name.Function */
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #bb60d5 } /* Name.Variable */
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mf { color: #208050 } /* Literal.Number.Float */
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
.highlight .mi { color: #208050 } /* Literal.Number.Integer */
.highlight .mo { color: #208050 } /* Literal.Number.Oct */
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
.highlight .sr { color: #235388 } /* Literal.String.Regex */
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */

View file

@ -0,0 +1,622 @@
/*
* searchtools.js_t
* ~~~~~~~~~~~~~~~~
*
* Sphinx JavaScript utilties for the full-text search.
*
* :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/**
* Porter Stemmer
*/
var Stemmer = function() {
var step2list = {
ational: 'ate',
tional: 'tion',
enci: 'ence',
anci: 'ance',
izer: 'ize',
bli: 'ble',
alli: 'al',
entli: 'ent',
eli: 'e',
ousli: 'ous',
ization: 'ize',
ation: 'ate',
ator: 'ate',
alism: 'al',
iveness: 'ive',
fulness: 'ful',
ousness: 'ous',
aliti: 'al',
iviti: 'ive',
biliti: 'ble',
logi: 'log'
};
var step3list = {
icate: 'ic',
ative: '',
alize: 'al',
iciti: 'ic',
ical: 'ic',
ful: '',
ness: ''
};
var c = "[^aeiou]"; // consonant
var v = "[aeiouy]"; // vowel
var C = c + "[^aeiouy]*"; // consonant sequence
var V = v + "[aeiou]*"; // vowel sequence
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
var s_v = "^(" + C + ")?" + v; // vowel in stem
this.stemWord = function (w) {
var stem;
var suffix;
var firstch;
var origword = w;
if (w.length < 3)
return w;
var re;
var re2;
var re3;
var re4;
firstch = w.substr(0,1);
if (firstch == "y")
w = firstch.toUpperCase() + w.substr(1);
// Step 1a
re = /^(.+?)(ss|i)es$/;
re2 = /^(.+?)([^s])s$/;
if (re.test(w))
w = w.replace(re,"$1$2");
else if (re2.test(w))
w = w.replace(re2,"$1$2");
// Step 1b
re = /^(.+?)eed$/;
re2 = /^(.+?)(ed|ing)$/;
if (re.test(w)) {
var fp = re.exec(w);
re = new RegExp(mgr0);
if (re.test(fp[1])) {
re = /.$/;
w = w.replace(re,"");
}
}
else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1];
re2 = new RegExp(s_v);
if (re2.test(stem)) {
w = stem;
re2 = /(at|bl|iz)$/;
re3 = new RegExp("([^aeiouylsz])\\1$");
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
if (re2.test(w))
w = w + "e";
else if (re3.test(w)) {
re = /.$/;
w = w.replace(re,"");
}
else if (re4.test(w))
w = w + "e";
}
}
// Step 1c
re = /^(.+?)y$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(s_v);
if (re.test(stem))
w = stem + "i";
}
// Step 2
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = new RegExp(mgr0);
if (re.test(stem))
w = stem + step2list[suffix];
}
// Step 3
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = new RegExp(mgr0);
if (re.test(stem))
w = stem + step3list[suffix];
}
// Step 4
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
re2 = /^(.+?)(s|t)(ion)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(mgr1);
if (re.test(stem))
w = stem;
}
else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1] + fp[2];
re2 = new RegExp(mgr1);
if (re2.test(stem))
w = stem;
}
// Step 5
re = /^(.+?)e$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(mgr1);
re2 = new RegExp(meq1);
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
w = stem;
}
re = /ll$/;
re2 = new RegExp(mgr1);
if (re.test(w) && re2.test(w)) {
re = /.$/;
w = w.replace(re,"");
}
// and turn initial Y back to y
if (firstch == "y")
w = firstch.toLowerCase() + w.substr(1);
return w;
}
}
/**
* Simple result scoring code.
*/
var Scorer = {
// Implement the following function to further tweak the score for each result
// The function takes a result array [filename, title, anchor, descr, score]
// and returns the new score.
/*
score: function(result) {
return result[4];
},
*/
// query matches the full name of an object
objNameMatch: 11,
// or matches in the last dotted part of the object name
objPartialMatch: 6,
// Additive scores depending on the priority of the object
objPrio: {0: 15, // used to be importantResults
1: 5, // used to be objectResults
2: -5}, // used to be unimportantResults
// Used when the priority is not in the mapping.
objPrioDefault: 0,
// query found in title
title: 15,
// query found in terms
term: 5
};
/**
* Search Module
*/
var Search = {
_index : null,
_queued_query : null,
_pulse_status : -1,
init : function() {
var params = $.getQueryParameters();
if (params.q) {
var query = params.q[0];
$('input[name="q"]')[0].value = query;
this.performSearch(query);
}
},
loadIndex : function(url) {
$.ajax({type: "GET", url: url, data: null,
dataType: "script", cache: true,
complete: function(jqxhr, textstatus) {
if (textstatus != "success") {
document.getElementById("searchindexloader").src = url;
}
}});
},
setIndex : function(index) {
var q;
this._index = index;
if ((q = this._queued_query) !== null) {
this._queued_query = null;
Search.query(q);
}
},
hasIndex : function() {
return this._index !== null;
},
deferQuery : function(query) {
this._queued_query = query;
},
stopPulse : function() {
this._pulse_status = 0;
},
startPulse : function() {
if (this._pulse_status >= 0)
return;
function pulse() {
var i;
Search._pulse_status = (Search._pulse_status + 1) % 4;
var dotString = '';
for (i = 0; i < Search._pulse_status; i++)
dotString += '.';
Search.dots.text(dotString);
if (Search._pulse_status > -1)
window.setTimeout(pulse, 500);
}
pulse();
},
/**
* perform a search for something (or wait until index is loaded)
*/
performSearch : function(query) {
// create the required interface elements
this.out = $('#search-results');
this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
this.dots = $('<span></span>').appendTo(this.title);
this.status = $('<p style="display: none"></p>').appendTo(this.out);
this.output = $('<ul class="search"/>').appendTo(this.out);
$('#search-progress').text(_('Preparing search...'));
this.startPulse();
// index already loaded, the browser was quick!
if (this.hasIndex())
this.query(query);
else
this.deferQuery(query);
},
/**
* execute search (requires search index to be loaded)
*/
query : function(query) {
var i;
var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
// stem the searchterms and add them to the correct list
var stemmer = new Stemmer();
var searchterms = [];
var excluded = [];
var hlterms = [];
var tmp = query.split(/\s+/);
var objectterms = [];
for (i = 0; i < tmp.length; i++) {
if (tmp[i] !== "") {
objectterms.push(tmp[i].toLowerCase());
}
if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
tmp[i] === "") {
// skip this "word"
continue;
}
// stem the word
var word = stemmer.stemWord(tmp[i].toLowerCase());
var toAppend;
// select the correct list
if (word[0] == '-') {
toAppend = excluded;
word = word.substr(1);
}
else {
toAppend = searchterms;
hlterms.push(tmp[i].toLowerCase());
}
// only add if not already in the list
if (!$u.contains(toAppend, word))
toAppend.push(word);
}
var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
// console.debug('SEARCH: searching for:');
// console.info('required: ', searchterms);
// console.info('excluded: ', excluded);
// prepare search
var terms = this._index.terms;
var titleterms = this._index.titleterms;
// array of [filename, title, anchor, descr, score]
var results = [];
$('#search-progress').empty();
// lookup as object
for (i = 0; i < objectterms.length; i++) {
var others = [].concat(objectterms.slice(0, i),
objectterms.slice(i+1, objectterms.length));
results = results.concat(this.performObjectSearch(objectterms[i], others));
}
// lookup as search terms in fulltext
results = results.concat(this.performTermsSearch(searchterms, excluded, terms, Scorer.term))
.concat(this.performTermsSearch(searchterms, excluded, titleterms, Scorer.title));
// let the scorer override scores with a custom scoring function
if (Scorer.score) {
for (i = 0; i < results.length; i++)
results[i][4] = Scorer.score(results[i]);
}
// now sort the results by score (in opposite order of appearance, since the
// display function below uses pop() to retrieve items) and then
// alphabetically
results.sort(function(a, b) {
var left = a[4];
var right = b[4];
if (left > right) {
return 1;
} else if (left < right) {
return -1;
} else {
// same score: sort alphabetically
left = a[1].toLowerCase();
right = b[1].toLowerCase();
return (left > right) ? -1 : ((left < right) ? 1 : 0);
}
});
// for debugging
//Search.lastresults = results.slice(); // a copy
//console.info('search results:', Search.lastresults);
// print the results
var resultCount = results.length;
function displayNextItem() {
// results left, load the summary and display it
if (results.length) {
var item = results.pop();
var listItem = $('<li style="display:none"></li>');
if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
// dirhtml builder
var dirname = item[0] + '/';
if (dirname.match(/\/index\/$/)) {
dirname = dirname.substring(0, dirname.length-6);
} else if (dirname == 'index/') {
dirname = '';
}
listItem.append($('<a/>').attr('href',
DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
highlightstring + item[2]).html(item[1]));
} else {
// normal html builders
listItem.append($('<a/>').attr('href',
item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
highlightstring + item[2]).html(item[1]));
}
if (item[3]) {
listItem.append($('<span> (' + item[3] + ')</span>'));
Search.output.append(listItem);
listItem.slideDown(5, function() {
displayNextItem();
});
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
$.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[0] + '.txt',
dataType: "text",
complete: function(jqxhr, textstatus) {
var data = jqxhr.responseText;
if (data !== '') {
listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
}
Search.output.append(listItem);
listItem.slideDown(5, function() {
displayNextItem();
});
}});
} else {
// no source available, just display title
Search.output.append(listItem);
listItem.slideDown(5, function() {
displayNextItem();
});
}
}
// search finished, update title and status message
else {
Search.stopPulse();
Search.title.text(_('Search Results'));
if (!resultCount)
Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
else
Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
Search.status.fadeIn(500);
}
}
displayNextItem();
},
/**
* search for object names
*/
performObjectSearch : function(object, otherterms) {
var filenames = this._index.filenames;
var objects = this._index.objects;
var objnames = this._index.objnames;
var titles = this._index.titles;
var i;
var results = [];
for (var prefix in objects) {
for (var name in objects[prefix]) {
var fullname = (prefix ? prefix + '.' : '') + name;
if (fullname.toLowerCase().indexOf(object) > -1) {
var score = 0;
var parts = fullname.split('.');
// check for different match types: exact matches of full name or
// "last name" (i.e. last dotted part)
if (fullname == object || parts[parts.length - 1] == object) {
score += Scorer.objNameMatch;
// matches in last name
} else if (parts[parts.length - 1].indexOf(object) > -1) {
score += Scorer.objPartialMatch;
}
var match = objects[prefix][name];
var objname = objnames[match[1]][2];
var title = titles[match[0]];
// If more than one term searched for, we require other words to be
// found in the name/title/description
if (otherterms.length > 0) {
var haystack = (prefix + ' ' + name + ' ' +
objname + ' ' + title).toLowerCase();
var allfound = true;
for (i = 0; i < otherterms.length; i++) {
if (haystack.indexOf(otherterms[i]) == -1) {
allfound = false;
break;
}
}
if (!allfound) {
continue;
}
}
var descr = objname + _(', in ') + title;
var anchor = match[3];
if (anchor === '')
anchor = fullname;
else if (anchor == '-')
anchor = objnames[match[1]][1] + '-' + fullname;
// add custom score for some objects according to scorer
if (Scorer.objPrio.hasOwnProperty(match[2])) {
score += Scorer.objPrio[match[2]];
} else {
score += Scorer.objPrioDefault;
}
results.push([filenames[match[0]], fullname, '#'+anchor, descr, score]);
}
}
}
return results;
},
/**
* search for full-text terms in the index
*/
performTermsSearch : function(searchterms, excluded, terms, score) {
var filenames = this._index.filenames;
var titles = this._index.titles;
var i, j, file, files;
var fileMap = {};
var results = [];
// perform the search on the required terms
for (i = 0; i < searchterms.length; i++) {
var word = searchterms[i];
// no match but word was a required one
if ((files = terms[word]) === undefined)
break;
if (files.length === undefined) {
files = [files];
}
// create the mapping
for (j = 0; j < files.length; j++) {
file = files[j];
if (file in fileMap)
fileMap[file].push(word);
else
fileMap[file] = [word];
}
}
// now check if the files don't contain excluded terms
for (file in fileMap) {
var valid = true;
// check if all requirements are matched
if (fileMap[file].length != searchterms.length)
continue;
// ensure that none of the excluded terms is in the search result
for (i = 0; i < excluded.length; i++) {
if (terms[excluded[i]] == file ||
$u.contains(terms[excluded[i]] || [], file)) {
valid = false;
break;
}
}
// if we have still a valid result we can add it to the result list
if (valid) {
results.push([filenames[file], titles[file], '', null, score]);
}
}
return results;
},
/**
* helper function to return a node containing the
* search summary for a given text. keywords is a list
* of stemmed words, hlwords is the list of normal, unstemmed
* words. the first one is used to find the occurance, the
* latter for highlighting it.
*/
makeSearchSummary : function(text, keywords, hlwords) {
var textLower = text.toLowerCase();
var start = 0;
$.each(keywords, function() {
var i = textLower.indexOf(this.toLowerCase());
if (i > -1)
start = i;
});
start = Math.max(start - 120, 0);
var excerpt = ((start > 0) ? '...' : '') +
$.trim(text.substr(start, 240)) +
((start + 240 - text.length) ? '...' : '');
var rv = $('<div class="context"></div>').text(excerpt);
$.each(hlwords, function() {
rv = rv.highlightText(this, 'highlighted');
});
return rv;
}
};
$(document).ready(function() {
Search.init();
});

View file

@ -0,0 +1,159 @@
/*
* sidebar.js
* ~~~~~~~~~~
*
* This script makes the Sphinx sidebar collapsible.
*
* .sphinxsidebar contains .sphinxsidebarwrapper. This script adds
* in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton
* used to collapse and expand the sidebar.
*
* When the sidebar is collapsed the .sphinxsidebarwrapper is hidden
* and the width of the sidebar and the margin-left of the document
* are decreased. When the sidebar is expanded the opposite happens.
* This script saves a per-browser/per-session cookie used to
* remember the position of the sidebar among the pages.
* Once the browser is closed the cookie is deleted and the position
* reset to the default (expanded).
*
* :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
$(function() {
// global elements used by the functions.
// the 'sidebarbutton' element is defined as global after its
// creation, in the add_sidebar_button function
var bodywrapper = $('.bodywrapper');
var sidebar = $('.sphinxsidebar');
var sidebarwrapper = $('.sphinxsidebarwrapper');
// for some reason, the document has no sidebar; do not run into errors
if (!sidebar.length) return;
// original margin-left of the bodywrapper and width of the sidebar
// with the sidebar expanded
var bw_margin_expanded = bodywrapper.css('margin-left');
var ssb_width_expanded = sidebar.width();
// margin-left of the bodywrapper and width of the sidebar
// with the sidebar collapsed
var bw_margin_collapsed = '.8em';
var ssb_width_collapsed = '.8em';
// colors used by the current theme
var dark_color = $('.related').css('background-color');
var light_color = $('.document').css('background-color');
function sidebar_is_collapsed() {
return sidebarwrapper.is(':not(:visible)');
}
function toggle_sidebar() {
if (sidebar_is_collapsed())
expand_sidebar();
else
collapse_sidebar();
}
function collapse_sidebar() {
sidebarwrapper.hide();
sidebar.css('width', ssb_width_collapsed);
bodywrapper.css('margin-left', bw_margin_collapsed);
sidebarbutton.css({
'margin-left': '0',
'height': bodywrapper.height()
});
sidebarbutton.find('span').text('»');
sidebarbutton.attr('title', _('Expand sidebar'));
document.cookie = 'sidebar=collapsed';
}
function expand_sidebar() {
bodywrapper.css('margin-left', bw_margin_expanded);
sidebar.css('width', ssb_width_expanded);
sidebarwrapper.show();
sidebarbutton.css({
'margin-left': ssb_width_expanded-12,
'height': bodywrapper.height()
});
sidebarbutton.find('span').text('«');
sidebarbutton.attr('title', _('Collapse sidebar'));
document.cookie = 'sidebar=expanded';
}
function add_sidebar_button() {
sidebarwrapper.css({
'float': 'left',
'margin-right': '0',
'width': ssb_width_expanded - 28
});
// create the button
sidebar.append(
'<div id="sidebarbutton"><span>&laquo;</span></div>'
);
var sidebarbutton = $('#sidebarbutton');
light_color = sidebarbutton.css('background-color');
// find the height of the viewport to center the '<<' in the page
var viewport_height;
if (window.innerHeight)
viewport_height = window.innerHeight;
else
viewport_height = $(window).height();
sidebarbutton.find('span').css({
'display': 'block',
'margin-top': (viewport_height - sidebar.position().top - 20) / 2
});
sidebarbutton.click(toggle_sidebar);
sidebarbutton.attr('title', _('Collapse sidebar'));
sidebarbutton.css({
'color': '#FFFFFF',
'border-left': '1px solid ' + dark_color,
'font-size': '1.2em',
'cursor': 'pointer',
'height': bodywrapper.height(),
'padding-top': '1px',
'margin-left': ssb_width_expanded - 12
});
sidebarbutton.hover(
function () {
$(this).css('background-color', dark_color);
},
function () {
$(this).css('background-color', light_color);
}
);
}
function set_position_from_cookie() {
if (!document.cookie)
return;
var items = document.cookie.split(';');
for(var k=0; k<items.length; k++) {
var key_val = items[k].split('=');
var key = key_val[0].replace(/ /, ""); // strip leading spaces
if (key == 'sidebar') {
var value = key_val[1];
if ((value == 'collapsed') && (!sidebar_is_collapsed()))
collapse_sidebar();
else if ((value == 'expanded') && (sidebar_is_collapsed()))
expand_sidebar();
}
}
}
add_sidebar_button();
var sidebarbutton = $('#sidebarbutton');
set_position_from_cookie();
});

View file

@ -0,0 +1,31 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);

View file

@ -0,0 +1,808 @@
/*
* websupport.js
* ~~~~~~~~~~~~~
*
* sphinx.websupport utilties for all documentation.
*
* :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
(function($) {
$.fn.autogrow = function() {
return this.each(function() {
var textarea = this;
$.fn.autogrow.resize(textarea);
$(textarea)
.focus(function() {
textarea.interval = setInterval(function() {
$.fn.autogrow.resize(textarea);
}, 500);
})
.blur(function() {
clearInterval(textarea.interval);
});
});
};
$.fn.autogrow.resize = function(textarea) {
var lineHeight = parseInt($(textarea).css('line-height'), 10);
var lines = textarea.value.split('\n');
var columns = textarea.cols;
var lineCount = 0;
$.each(lines, function() {
lineCount += Math.ceil(this.length / columns) || 1;
});
var height = lineHeight * (lineCount + 1);
$(textarea).css('height', height);
};
})(jQuery);
(function($) {
var comp, by;
function init() {
initEvents();
initComparator();
}
function initEvents() {
$('a.comment-close').live("click", function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
});
$('a.vote').live("click", function(event) {
event.preventDefault();
handleVote($(this));
});
$('a.reply').live("click", function(event) {
event.preventDefault();
openReply($(this).attr('id').substring(2));
});
$('a.close-reply').live("click", function(event) {
event.preventDefault();
closeReply($(this).attr('id').substring(2));
});
$('a.sort-option').live("click", function(event) {
event.preventDefault();
handleReSort($(this));
});
$('a.show-proposal').live("click", function(event) {
event.preventDefault();
showProposal($(this).attr('id').substring(2));
});
$('a.hide-proposal').live("click", function(event) {
event.preventDefault();
hideProposal($(this).attr('id').substring(2));
});
$('a.show-propose-change').live("click", function(event) {
event.preventDefault();
showProposeChange($(this).attr('id').substring(2));
});
$('a.hide-propose-change').live("click", function(event) {
event.preventDefault();
hideProposeChange($(this).attr('id').substring(2));
});
$('a.accept-comment').live("click", function(event) {
event.preventDefault();
acceptComment($(this).attr('id').substring(2));
});
$('a.delete-comment').live("click", function(event) {
event.preventDefault();
deleteComment($(this).attr('id').substring(2));
});
$('a.comment-markup').live("click", function(event) {
event.preventDefault();
toggleCommentMarkupBox($(this).attr('id').substring(2));
});
}
/**
* Set comp, which is a comparator function used for sorting and
* inserting comments into the list.
*/
function setComparator() {
// If the first three letters are "asc", sort in ascending order
// and remove the prefix.
if (by.substring(0,3) == 'asc') {
var i = by.substring(3);
comp = function(a, b) { return a[i] - b[i]; };
} else {
// Otherwise sort in descending order.
comp = function(a, b) { return b[by] - a[by]; };
}
// Reset link styles and format the selected sort option.
$('a.sel').attr('href', '#').removeClass('sel');
$('a.by' + by).removeAttr('href').addClass('sel');
}
/**
* Create a comp function. If the user has preferences stored in
* the sortBy cookie, use those, otherwise use the default.
*/
function initComparator() {
by = 'rating'; // Default to sort by rating.
// If the sortBy cookie is set, use that instead.
if (document.cookie.length > 0) {
var start = document.cookie.indexOf('sortBy=');
if (start != -1) {
start = start + 7;
var end = document.cookie.indexOf(";", start);
if (end == -1) {
end = document.cookie.length;
by = unescape(document.cookie.substring(start, end));
}
}
}
setComparator();
}
/**
* Show a comment div.
*/
function show(id) {
$('#ao' + id).hide();
$('#ah' + id).show();
var context = $.extend({id: id}, opts);
var popup = $(renderTemplate(popupTemplate, context)).hide();
popup.find('textarea[name="proposal"]').hide();
popup.find('a.by' + by).addClass('sel');
var form = popup.find('#cf' + id);
form.submit(function(event) {
event.preventDefault();
addComment(form);
});
$('#s' + id).after(popup);
popup.slideDown('fast', function() {
getComments(id);
});
}
/**
* Hide a comment div.
*/
function hide(id) {
$('#ah' + id).hide();
$('#ao' + id).show();
var div = $('#sc' + id);
div.slideUp('fast', function() {
div.remove();
});
}
/**
* Perform an ajax request to get comments for a node
* and insert the comments into the comments tree.
*/
function getComments(id) {
$.ajax({
type: 'GET',
url: opts.getCommentsURL,
data: {node: id},
success: function(data, textStatus, request) {
var ul = $('#cl' + id);
var speed = 100;
$('#cf' + id)
.find('textarea[name="proposal"]')
.data('source', data.source);
if (data.comments.length === 0) {
ul.html('<li>No comments yet.</li>');
ul.data('empty', true);
} else {
// If there are comments, sort them and put them in the list.
var comments = sortComments(data.comments);
speed = data.comments.length * 100;
appendComments(comments, ul);
ul.data('empty', false);
}
$('#cn' + id).slideUp(speed + 200);
ul.slideDown(speed);
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem retrieving the comments.');
},
dataType: 'json'
});
}
/**
* Add a comment via ajax and insert the comment into the comment tree.
*/
function addComment(form) {
var node_id = form.find('input[name="node"]').val();
var parent_id = form.find('input[name="parent"]').val();
var text = form.find('textarea[name="comment"]').val();
var proposal = form.find('textarea[name="proposal"]').val();
if (text == '') {
showError('Please enter a comment.');
return;
}
// Disable the form that is being submitted.
form.find('textarea,input').attr('disabled', 'disabled');
// Send the comment to the server.
$.ajax({
type: "POST",
url: opts.addCommentURL,
dataType: 'json',
data: {
node: node_id,
parent: parent_id,
text: text,
proposal: proposal
},
success: function(data, textStatus, error) {
// Reset the form.
if (node_id) {
hideProposeChange(node_id);
}
form.find('textarea')
.val('')
.add(form.find('input'))
.removeAttr('disabled');
var ul = $('#cl' + (node_id || parent_id));
if (ul.data('empty')) {
$(ul).empty();
ul.data('empty', false);
}
insertComment(data.comment);
var ao = $('#ao' + node_id);
ao.find('img').attr({'src': opts.commentBrightImage});
if (node_id) {
// if this was a "root" comment, remove the commenting box
// (the user can get it back by reopening the comment popup)
$('#ca' + node_id).slideUp();
}
},
error: function(request, textStatus, error) {
form.find('textarea,input').removeAttr('disabled');
showError('Oops, there was a problem adding the comment.');
}
});
}
/**
* Recursively append comments to the main comment list and children
* lists, creating the comment tree.
*/
function appendComments(comments, ul) {
$.each(comments, function() {
var div = createCommentDiv(this);
ul.append($(document.createElement('li')).html(div));
appendComments(this.children, div.find('ul.comment-children'));
// To avoid stagnating data, don't store the comments children in data.
this.children = null;
div.data('comment', this);
});
}
/**
* After adding a new comment, it must be inserted in the correct
* location in the comment tree.
*/
function insertComment(comment) {
var div = createCommentDiv(comment);
// To avoid stagnating data, don't store the comments children in data.
comment.children = null;
div.data('comment', comment);
var ul = $('#cl' + (comment.node || comment.parent));
var siblings = getChildren(ul);
var li = $(document.createElement('li'));
li.hide();
// Determine where in the parents children list to insert this comment.
for(i=0; i < siblings.length; i++) {
if (comp(comment, siblings[i]) <= 0) {
$('#cd' + siblings[i].id)
.parent()
.before(li.html(div));
li.slideDown('fast');
return;
}
}
// If we get here, this comment rates lower than all the others,
// or it is the only comment in the list.
ul.append(li.html(div));
li.slideDown('fast');
}
function acceptComment(id) {
$.ajax({
type: 'POST',
url: opts.acceptCommentURL,
data: {id: id},
success: function(data, textStatus, request) {
$('#cm' + id).fadeOut('fast');
$('#cd' + id).removeClass('moderate');
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem accepting the comment.');
}
});
}
function deleteComment(id) {
$.ajax({
type: 'POST',
url: opts.deleteCommentURL,
data: {id: id},
success: function(data, textStatus, request) {
var div = $('#cd' + id);
if (data == 'delete') {
// Moderator mode: remove the comment and all children immediately
div.slideUp('fast', function() {
div.remove();
});
return;
}
// User mode: only mark the comment as deleted
div
.find('span.user-id:first')
.text('[deleted]').end()
.find('div.comment-text:first')
.text('[deleted]').end()
.find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
.remove();
var comment = div.data('comment');
comment.username = '[deleted]';
comment.text = '[deleted]';
div.data('comment', comment);
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem deleting the comment.');
}
});
}
function showProposal(id) {
$('#sp' + id).hide();
$('#hp' + id).show();
$('#pr' + id).slideDown('fast');
}
function hideProposal(id) {
$('#hp' + id).hide();
$('#sp' + id).show();
$('#pr' + id).slideUp('fast');
}
function showProposeChange(id) {
$('#pc' + id).hide();
$('#hc' + id).show();
var textarea = $('#pt' + id);
textarea.val(textarea.data('source'));
$.fn.autogrow.resize(textarea[0]);
textarea.slideDown('fast');
}
function hideProposeChange(id) {
$('#hc' + id).hide();
$('#pc' + id).show();
var textarea = $('#pt' + id);
textarea.val('').removeAttr('disabled');
textarea.slideUp('fast');
}
function toggleCommentMarkupBox(id) {
$('#mb' + id).toggle();
}
/** Handle when the user clicks on a sort by link. */
function handleReSort(link) {
var classes = link.attr('class').split(/\s+/);
for (var i=0; i<classes.length; i++) {
if (classes[i] != 'sort-option') {
by = classes[i].substring(2);
}
}
setComparator();
// Save/update the sortBy cookie.
var expiration = new Date();
expiration.setDate(expiration.getDate() + 365);
document.cookie= 'sortBy=' + escape(by) +
';expires=' + expiration.toUTCString();
$('ul.comment-ul').each(function(index, ul) {
var comments = getChildren($(ul), true);
comments = sortComments(comments);
appendComments(comments, $(ul).empty());
});
}
/**
* Function to process a vote when a user clicks an arrow.
*/
function handleVote(link) {
if (!opts.voting) {
showError("You'll need to login to vote.");
return;
}
var id = link.attr('id');
if (!id) {
// Didn't click on one of the voting arrows.
return;
}
// If it is an unvote, the new vote value is 0,
// Otherwise it's 1 for an upvote, or -1 for a downvote.
var value = 0;
if (id.charAt(1) != 'u') {
value = id.charAt(0) == 'u' ? 1 : -1;
}
// The data to be sent to the server.
var d = {
comment_id: id.substring(2),
value: value
};
// Swap the vote and unvote links.
link.hide();
$('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
.show();
// The div the comment is displayed in.
var div = $('div#cd' + d.comment_id);
var data = div.data('comment');
// If this is not an unvote, and the other vote arrow has
// already been pressed, unpress it.
if ((d.value !== 0) && (data.vote === d.value * -1)) {
$('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
$('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
}
// Update the comments rating in the local data.
data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
data.vote = d.value;
div.data('comment', data);
// Change the rating text.
div.find('.rating:first')
.text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
// Send the vote information to the server.
$.ajax({
type: "POST",
url: opts.processVoteURL,
data: d,
error: function(request, textStatus, error) {
showError('Oops, there was a problem casting that vote.');
}
});
}
/**
* Open a reply form used to reply to an existing comment.
*/
function openReply(id) {
// Swap out the reply link for the hide link
$('#rl' + id).hide();
$('#cr' + id).show();
// Add the reply li to the children ul.
var div = $(renderTemplate(replyTemplate, {id: id})).hide();
$('#cl' + id)
.prepend(div)
// Setup the submit handler for the reply form.
.find('#rf' + id)
.submit(function(event) {
event.preventDefault();
addComment($('#rf' + id));
closeReply(id);
})
.find('input[type=button]')
.click(function() {
closeReply(id);
});
div.slideDown('fast', function() {
$('#rf' + id).find('textarea').focus();
});
}
/**
* Close the reply form opened with openReply.
*/
function closeReply(id) {
// Remove the reply div from the DOM.
$('#rd' + id).slideUp('fast', function() {
$(this).remove();
});
// Swap out the hide link for the reply link
$('#cr' + id).hide();
$('#rl' + id).show();
}
/**
* Recursively sort a tree of comments using the comp comparator.
*/
function sortComments(comments) {
comments.sort(comp);
$.each(comments, function() {
this.children = sortComments(this.children);
});
return comments;
}
/**
* Get the children comments from a ul. If recursive is true,
* recursively include childrens' children.
*/
function getChildren(ul, recursive) {
var children = [];
ul.children().children("[id^='cd']")
.each(function() {
var comment = $(this).data('comment');
if (recursive)
comment.children = getChildren($(this).find('#cl' + comment.id), true);
children.push(comment);
});
return children;
}
/** Create a div to display a comment in. */
function createCommentDiv(comment) {
if (!comment.displayed && !opts.moderator) {
return $('<div class="moderate">Thank you! Your comment will show up '
+ 'once it is has been approved by a moderator.</div>');
}
// Prettify the comment rating.
comment.pretty_rating = comment.rating + ' point' +
(comment.rating == 1 ? '' : 's');
// Make a class (for displaying not yet moderated comments differently)
comment.css_class = comment.displayed ? '' : ' moderate';
// Create a div for this comment.
var context = $.extend({}, opts, comment);
var div = $(renderTemplate(commentTemplate, context));
// If the user has voted on this comment, highlight the correct arrow.
if (comment.vote) {
var direction = (comment.vote == 1) ? 'u' : 'd';
div.find('#' + direction + 'v' + comment.id).hide();
div.find('#' + direction + 'u' + comment.id).show();
}
if (opts.moderator || comment.text != '[deleted]') {
div.find('a.reply').show();
if (comment.proposal_diff)
div.find('#sp' + comment.id).show();
if (opts.moderator && !comment.displayed)
div.find('#cm' + comment.id).show();
if (opts.moderator || (opts.username == comment.username))
div.find('#dc' + comment.id).show();
}
return div;
}
/**
* A simple template renderer. Placeholders such as <%id%> are replaced
* by context['id'] with items being escaped. Placeholders such as <#id#>
* are not escaped.
*/
function renderTemplate(template, context) {
var esc = $(document.createElement('div'));
function handle(ph, escape) {
var cur = context;
$.each(ph.split('.'), function() {
cur = cur[this];
});
return escape ? esc.text(cur || "").html() : cur;
}
return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
return handle(arguments[2], arguments[1] == '%' ? true : false);
});
}
/** Flash an error message briefly. */
function showError(message) {
$(document.createElement('div')).attr({'class': 'popup-error'})
.append($(document.createElement('div'))
.attr({'class': 'error-message'}).text(message))
.appendTo('body')
.fadeIn("slow")
.delay(2000)
.fadeOut("slow");
}
/** Add a link the user uses to open the comments popup. */
$.fn.comment = function() {
return this.each(function() {
var id = $(this).attr('id').substring(1);
var count = COMMENT_METADATA[id];
var title = count + ' comment' + (count == 1 ? '' : 's');
var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
var addcls = count == 0 ? ' nocomment' : '';
$(this)
.append(
$(document.createElement('a')).attr({
href: '#',
'class': 'sphinx-comment-open' + addcls,
id: 'ao' + id
})
.append($(document.createElement('img')).attr({
src: image,
alt: 'comment',
title: title
}))
.click(function(event) {
event.preventDefault();
show($(this).attr('id').substring(2));
})
)
.append(
$(document.createElement('a')).attr({
href: '#',
'class': 'sphinx-comment-close hidden',
id: 'ah' + id
})
.append($(document.createElement('img')).attr({
src: opts.closeCommentImage,
alt: 'close',
title: 'close'
}))
.click(function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
})
);
});
};
var opts = {
processVoteURL: '/_process_vote',
addCommentURL: '/_add_comment',
getCommentsURL: '/_get_comments',
acceptCommentURL: '/_accept_comment',
deleteCommentURL: '/_delete_comment',
commentImage: '/static/_static/comment.png',
closeCommentImage: '/static/_static/comment-close.png',
loadingImage: '/static/_static/ajax-loader.gif',
commentBrightImage: '/static/_static/comment-bright.png',
upArrow: '/static/_static/up.png',
downArrow: '/static/_static/down.png',
upArrowPressed: '/static/_static/up-pressed.png',
downArrowPressed: '/static/_static/down-pressed.png',
voting: false,
moderator: false
};
if (typeof COMMENT_OPTIONS != "undefined") {
opts = jQuery.extend(opts, COMMENT_OPTIONS);
}
var popupTemplate = '\
<div class="sphinx-comments" id="sc<%id%>">\
<p class="sort-options">\
Sort by:\
<a href="#" class="sort-option byrating">best rated</a>\
<a href="#" class="sort-option byascage">newest</a>\
<a href="#" class="sort-option byage">oldest</a>\
</p>\
<div class="comment-header">Comments</div>\
<div class="comment-loading" id="cn<%id%>">\
loading comments... <img src="<%loadingImage%>" alt="" /></div>\
<ul id="cl<%id%>" class="comment-ul"></ul>\
<div id="ca<%id%>">\
<p class="add-a-comment">Add a comment\
(<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
<div class="comment-markup-box" id="mb<%id%>">\
reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
<tt>``code``</tt>, \
code blocks: <tt>::</tt> and an indented block after blank line</div>\
<form method="post" id="cf<%id%>" class="comment-form" action="">\
<textarea name="comment" cols="80"></textarea>\
<p class="propose-button">\
<a href="#" id="pc<%id%>" class="show-propose-change">\
Propose a change &#9657;\
</a>\
<a href="#" id="hc<%id%>" class="hide-propose-change">\
Propose a change &#9663;\
</a>\
</p>\
<textarea name="proposal" id="pt<%id%>" cols="80"\
spellcheck="false"></textarea>\
<input type="submit" value="Add comment" />\
<input type="hidden" name="node" value="<%id%>" />\
<input type="hidden" name="parent" value="" />\
</form>\
</div>\
</div>';
var commentTemplate = '\
<div id="cd<%id%>" class="sphinx-comment<%css_class%>">\
<div class="vote">\
<div class="arrow">\
<a href="#" id="uv<%id%>" class="vote" title="vote up">\
<img src="<%upArrow%>" />\
</a>\
<a href="#" id="uu<%id%>" class="un vote" title="vote up">\
<img src="<%upArrowPressed%>" />\
</a>\
</div>\
<div class="arrow">\
<a href="#" id="dv<%id%>" class="vote" title="vote down">\
<img src="<%downArrow%>" id="da<%id%>" />\
</a>\
<a href="#" id="du<%id%>" class="un vote" title="vote down">\
<img src="<%downArrowPressed%>" />\
</a>\
</div>\
</div>\
<div class="comment-content">\
<p class="tagline comment">\
<span class="user-id"><%username%></span>\
<span class="rating"><%pretty_rating%></span>\
<span class="delta"><%time.delta%></span>\
</p>\
<div class="comment-text comment"><#text#></div>\
<p class="comment-opts comment">\
<a href="#" class="reply hidden" id="rl<%id%>">reply &#9657;</a>\
<a href="#" class="close-reply" id="cr<%id%>">reply &#9663;</a>\
<a href="#" id="sp<%id%>" class="show-proposal">proposal &#9657;</a>\
<a href="#" id="hp<%id%>" class="hide-proposal">proposal &#9663;</a>\
<a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\
<span id="cm<%id%>" class="moderation hidden">\
<a href="#" id="ac<%id%>" class="accept-comment">accept</a>\
</span>\
</p>\
<pre class="proposal" id="pr<%id%>">\
<#proposal_diff#>\
</pre>\
<ul class="comment-children" id="cl<%id%>"></ul>\
</div>\
<div class="clearleft"></div>\
</div>\
</div>';
var replyTemplate = '\
<li>\
<div class="reply-div" id="rd<%id%>">\
<form id="rf<%id%>">\
<textarea name="comment" cols="80"></textarea>\
<input type="submit" value="Add reply" />\
<input type="button" value="Cancel" />\
<input type="hidden" name="parent" value="<%id%>" />\
<input type="hidden" name="node" value="" />\
</form>\
</div>\
</li>';
$(document).ready(function() {
init();
});
})(jQuery);
$(document).ready(function() {
// add comment anchors for all paragraphs that are commentable
$('.sphinx-has-comment').comment();
// highlight search words in search results
$("div.context").each(function() {
var params = $.getQueryParameters();
var terms = (params.q) ? params.q[0].split(/\s+/) : [];
var result = $(this);
$.each(terms, function() {
result.highlightText(this.toLowerCase(), 'highlighted');
});
});
// directly open comment window if requested
var anchor = document.location.hash;
if (anchor.substring(0, 9) == '#comment-') {
$('#ao' + anchor.substring(9)).click();
document.location.hash = '#s' + anchor.substring(9);
}
});

View file

@ -0,0 +1,404 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>client Package &mdash; BrowserMob Proxy 0.6.0 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '0.6.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
<link rel="next" title="server Package" href="server.html" />
<link rel="prev" title="Welcome to BrowserMob Proxy’s documentation!" href="index.html" />
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="server.html" title="server Package"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="index.html" title="Welcome to BrowserMob Proxy’s documentation!"
accesskey="P">previous</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="toctree-wrapper compound">
<ul class="simple">
</ul>
</div>
<div class="section" id="module-browsermobproxy">
<span id="client-package"></span><h1><tt class="xref py py-mod docutils literal"><span class="pre">client</span></tt> Package<a class="headerlink" href="#module-browsermobproxy" title="Permalink to this headline"></a></h1>
<dl class="class">
<dt id="browsermobproxy.Client">
<em class="property">class </em><tt class="descclassname">browsermobproxy.</tt><tt class="descname">Client</tt><big>(</big><em>url</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client" title="Permalink to this definition"></a></dt>
<dd><p>Initialises a new Client object</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>url</strong> &#8211; This is where the BrowserMob Proxy lives</td>
</tr>
</tbody>
</table>
<dl class="method">
<dt id="browsermobproxy.Client.add_to_capabilities">
<tt class="descname">add_to_capabilities</tt><big>(</big><em>capabilities</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.add_to_capabilities" title="Permalink to this definition"></a></dt>
<dd><p>Adds an &#8216;proxy&#8217; entry to a desired capabilities dictionary with the
BrowserMob proxy information</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>capabilities</strong> &#8211; The Desired capabilities object from Selenium WebDriver</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.basic_authentication">
<tt class="descname">basic_authentication</tt><big>(</big><em>domain</em>, <em>username</em>, <em>password</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.basic_authentication" title="Permalink to this definition"></a></dt>
<dd><p>This add automatic basic authentication</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>domain</strong> &#8211; domain to set authentication credentials for</li>
<li><strong>username</strong> &#8211; valid username to use when authenticating</li>
<li><strong>password</strong> &#8211; valid password to use when authenticating</li>
</ul>
</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.blacklist">
<tt class="descname">blacklist</tt><big>(</big><em>regexp</em>, <em>status_code</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.blacklist" title="Permalink to this definition"></a></dt>
<dd><p>Sets a list of URL patterns to blacklist</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>regex</strong> &#8211; a comma separated list of regular expressions</li>
<li><strong>status_code</strong> &#8211; the HTTP status code to return for URLs that do not match the blacklist</li>
</ul>
</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.clear_dns_cache">
<tt class="descname">clear_dns_cache</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Client.clear_dns_cache" title="Permalink to this definition"></a></dt>
<dd><p>Clears the DNS cache associated with the proxy instance</p>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.close">
<tt class="descname">close</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Client.close" title="Permalink to this definition"></a></dt>
<dd><p>shuts down the proxy and closes the port</p>
</dd></dl>
<dl class="attribute">
<dt id="browsermobproxy.Client.har">
<tt class="descname">har</tt><a class="headerlink" href="#browsermobproxy.Client.har" title="Permalink to this definition"></a></dt>
<dd><p>Gets the HAR that has been recorded</p>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.headers">
<tt class="descname">headers</tt><big>(</big><em>headers</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.headers" title="Permalink to this definition"></a></dt>
<dd><p>This sets the headers that will set by the proxy on all requests</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>headers</strong> &#8211; this is a dictionary of the headers to be set</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.limits">
<tt class="descname">limits</tt><big>(</big><em>options</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.limits" title="Permalink to this definition"></a></dt>
<dd><p>Limit the bandwidth through the proxy.</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>options</strong> &#8211; A dictionary with all the details you want to set. downstreamKbps - Sets the downstream kbps upstreamKbps - Sets the upstream kbps latency - Add the given latency to each HTTP request</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.new_har">
<tt class="descname">new_har</tt><big>(</big><em>ref=None</em>, <em>options={}</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.new_har" title="Permalink to this definition"></a></dt>
<dd><p>This sets a new HAR to be recorded</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>ref</strong> &#8211; A reference for the HAR. Defaults to None</li>
<li><strong>options</strong> &#8211; A dictionary that will be passed to BrowserMob Proxy with specific keywords. Keywords are: captureHeaders - Boolean, capture headers captureContent - Boolean, capture content bodies captureBinaryContent - Boolean, capture binary content</li>
</ul>
</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.new_page">
<tt class="descname">new_page</tt><big>(</big><em>ref=None</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.new_page" title="Permalink to this definition"></a></dt>
<dd><p>This sets a new page to be recorded</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>ref</strong> &#8211; A reference for the new page. Defaults to None</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.remap_hosts">
<tt class="descname">remap_hosts</tt><big>(</big><em>address</em>, <em>ip_address</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.remap_hosts" title="Permalink to this definition"></a></dt>
<dd><p>Remap the hosts for a specific URL</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>address</strong> &#8211; url that you wish to remap</li>
<li><strong>ip_address</strong> &#8211; IP Address that will handle all traffic for the address passed in</li>
</ul>
</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.request_interceptor">
<tt class="descname">request_interceptor</tt><big>(</big><em>js</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.request_interceptor" title="Permalink to this definition"></a></dt>
<dd><p>Executes the javascript against each request</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>js</strong> &#8211; the javascript to execute</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.response_interceptor">
<tt class="descname">response_interceptor</tt><big>(</big><em>js</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.response_interceptor" title="Permalink to this definition"></a></dt>
<dd><p>Executes the javascript against each response</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>js</strong> &#8211; the javascript to execute</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.retry">
<tt class="descname">retry</tt><big>(</big><em>retry_count</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.retry" title="Permalink to this definition"></a></dt>
<dd><p>Retries. No idea what its used for, but its in the API...</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>retry_count</strong> &#8211; the number of retries</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.rewrite_url">
<tt class="descname">rewrite_url</tt><big>(</big><em>match</em>, <em>replace</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.rewrite_url" title="Permalink to this definition"></a></dt>
<dd><p>Rewrites the requested url.</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>match</strong> &#8211; a regex to match requests with</li>
<li><strong>replace</strong> &#8211; unicode a string to replace the matches with</li>
</ul>
</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.selenium_proxy">
<tt class="descname">selenium_proxy</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Client.selenium_proxy" title="Permalink to this definition"></a></dt>
<dd><p>Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy</p>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.timeouts">
<tt class="descname">timeouts</tt><big>(</big><em>options</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.timeouts" title="Permalink to this definition"></a></dt>
<dd><p>Configure various timeouts in the proxy</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>options</strong> &#8211; A dictionary with all the details you want to set. request - request timeout (in seconds) read - read timeout (in seconds) connection - connection timeout (in seconds) dns - dns lookup timeout (in seconds)</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.wait_for_traffic_to_stop">
<tt class="descname">wait_for_traffic_to_stop</tt><big>(</big><em>quiet_period</em>, <em>timeout</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.wait_for_traffic_to_stop" title="Permalink to this definition"></a></dt>
<dd><p>Waits for the network to be quiet</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>quiet_period</strong> &#8211; number of seconds the network needs to be quiet for</li>
<li><strong>timeout</strong> &#8211; max number of seconds to wait</li>
</ul>
</td>
</tr>
</tbody>
</table>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.webdriver_proxy">
<tt class="descname">webdriver_proxy</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Client.webdriver_proxy" title="Permalink to this definition"></a></dt>
<dd><p>Returns a Selenium WebDriver Proxy class with details of the HTTP Proxy</p>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Client.whitelist">
<tt class="descname">whitelist</tt><big>(</big><em>regexp</em>, <em>status_code</em><big>)</big><a class="headerlink" href="#browsermobproxy.Client.whitelist" title="Permalink to this definition"></a></dt>
<dd><p>Sets a list of URL patterns to whitelist</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>regex</strong> &#8211; a comma separated list of regular expressions</li>
<li><strong>status_code</strong> &#8211; the HTTP status code to return for URLs that do not match the whitelist</li>
</ul>
</td>
</tr>
</tbody>
</table>
</dd></dl>
</dd></dl>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<h4>Previous topic</h4>
<p class="topless"><a href="index.html"
title="previous chapter">Welcome to BrowserMob Proxy&#8217;s documentation!</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="server.html"
title="next chapter"><tt class="docutils literal"><span class="pre">server</span></tt> Package</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/client.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="server.html" title="server Package"
>next</a> |</li>
<li class="right" >
<a href="index.html" title="Welcome to BrowserMob Proxy’s documentation!"
>previous</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2014, David Burns.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
</div>
</body>
</html>

View file

@ -0,0 +1,297 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Index &mdash; BrowserMob Proxy 0.6.0 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '0.6.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<h1 id="index">Index</h1>
<div class="genindex-jumpbox">
<a href="#A"><strong>A</strong></a>
| <a href="#B"><strong>B</strong></a>
| <a href="#C"><strong>C</strong></a>
| <a href="#H"><strong>H</strong></a>
| <a href="#L"><strong>L</strong></a>
| <a href="#N"><strong>N</strong></a>
| <a href="#R"><strong>R</strong></a>
| <a href="#S"><strong>S</strong></a>
| <a href="#T"><strong>T</strong></a>
| <a href="#U"><strong>U</strong></a>
| <a href="#W"><strong>W</strong></a>
</div>
<h2 id="A">A</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.add_to_capabilities">add_to_capabilities() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="B">B</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.basic_authentication">basic_authentication() (browsermobproxy.Client method)</a>
</dt>
<dt><a href="client.html#browsermobproxy.Client.blacklist">blacklist() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#module-browsermobproxy">browsermobproxy (module)</a>, <a href="server.html#module-browsermobproxy">[1]</a>
</dt>
</dl></td>
</tr></table>
<h2 id="C">C</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.clear_dns_cache">clear_dns_cache() (browsermobproxy.Client method)</a>
</dt>
<dt><a href="client.html#browsermobproxy.Client">Client (class in browsermobproxy)</a>
</dt>
</dl></td>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.close">close() (browsermobproxy.Client method)</a>
</dt>
<dt><a href="server.html#browsermobproxy.Server.create_proxy">create_proxy() (browsermobproxy.Server method)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="H">H</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.har">har (browsermobproxy.Client attribute)</a>
</dt>
</dl></td>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.headers">headers() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="L">L</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.limits">limits() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="N">N</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.new_har">new_har() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.new_page">new_page() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="R">R</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.remap_hosts">remap_hosts() (browsermobproxy.Client method)</a>
</dt>
<dt><a href="client.html#browsermobproxy.Client.request_interceptor">request_interceptor() (browsermobproxy.Client method)</a>
</dt>
<dt><a href="client.html#browsermobproxy.Client.response_interceptor">response_interceptor() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.retry">retry() (browsermobproxy.Client method)</a>
</dt>
<dt><a href="client.html#browsermobproxy.Client.rewrite_url">rewrite_url() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="S">S</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.selenium_proxy">selenium_proxy() (browsermobproxy.Client method)</a>
</dt>
<dt><a href="server.html#browsermobproxy.Server">Server (class in browsermobproxy)</a>
</dt>
</dl></td>
<td style="width: 33%" valign="top"><dl>
<dt><a href="server.html#browsermobproxy.Server.start">start() (browsermobproxy.Server method)</a>
</dt>
<dt><a href="server.html#browsermobproxy.Server.stop">stop() (browsermobproxy.Server method)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="T">T</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.timeouts">timeouts() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="U">U</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="server.html#browsermobproxy.Server.url">url (browsermobproxy.Server attribute)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="W">W</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.wait_for_traffic_to_stop">wait_for_traffic_to_stop() (browsermobproxy.Client method)</a>
</dt>
<dt><a href="client.html#browsermobproxy.Client.webdriver_proxy">webdriver_proxy() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
<td style="width: 33%" valign="top"><dl>
<dt><a href="client.html#browsermobproxy.Client.whitelist">whitelist() (browsermobproxy.Client method)</a>
</dt>
</dl></td>
</tr></table>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
>index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2014, David Burns.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
</div>
</body>
</html>

View file

@ -0,0 +1,174 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Welcome to BrowserMob Proxy’s documentation! &mdash; BrowserMob Proxy 0.6.0 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '0.6.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="#" />
<link rel="next" title="client Package" href="client.html" />
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="client.html" title="client Package"
accesskey="N">next</a> |</li>
<li><a href="#">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="section" id="welcome-to-browsermob-proxy-s-documentation">
<h1>Welcome to BrowserMob Proxy&#8217;s documentation!<a class="headerlink" href="#welcome-to-browsermob-proxy-s-documentation" title="Permalink to this headline"></a></h1>
<p>Python client for the BrowserMob Proxy 2.0 REST API.</p>
<div class="section" id="how-to-install">
<h2>How to install<a class="headerlink" href="#how-to-install" title="Permalink to this headline"></a></h2>
<p>BrowserMob Proxy is available on <a class="reference external" href="http://pypi.python.org/pypi/browsermob-proxy">PyPI</a>, so you can install it with <tt class="docutils literal"><span class="pre">pip</span></tt>:</p>
<div class="highlight-python"><div class="highlight"><pre>$ pip install browsermob-proxy
</pre></div>
</div>
<p>Or with <cite>easy_install</cite>:</p>
<div class="highlight-python"><div class="highlight"><pre>$ easy_install browsermob-proxy
</pre></div>
</div>
<p>Or by cloning the repo from <a class="reference external" href="https://github.com/AutomatedTester/browsermob-proxy-py">GitHub</a>:</p>
<div class="highlight-python"><div class="highlight"><pre>$ git clone git://github.com/AutomatedTester/browsermob-proxy-py.git
</pre></div>
</div>
<p>Then install it by running:</p>
<div class="highlight-python"><div class="highlight"><pre>$ python setup.py install
</pre></div>
</div>
</div>
<div class="section" id="how-to-use-with-selenium-webdriver">
<h2>How to use with selenium-webdriver<a class="headerlink" href="#how-to-use-with-selenium-webdriver" title="Permalink to this headline"></a></h2>
<p>Manually:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="kn">from</span> <span class="nn">browsermobproxy</span> <span class="kn">import</span> <span class="n">Server</span>
<span class="n">server</span> <span class="o">=</span> <span class="n">Server</span><span class="p">(</span><span class="s">&quot;path/to/browsermob-proxy&quot;</span><span class="p">)</span>
<span class="n">server</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
<span class="n">proxy</span> <span class="o">=</span> <span class="n">server</span><span class="o">.</span><span class="n">create_proxy</span><span class="p">()</span>
<span class="kn">from</span> <span class="nn">selenium</span> <span class="kn">import</span> <span class="n">webdriver</span>
<span class="n">profile</span> <span class="o">=</span> <span class="n">webdriver</span><span class="o">.</span><span class="n">FirefoxProfile</span><span class="p">()</span>
<span class="n">profile</span><span class="o">.</span><span class="n">set_proxy</span><span class="p">(</span><span class="n">proxy</span><span class="o">.</span><span class="n">selenium_proxy</span><span class="p">())</span>
<span class="n">driver</span> <span class="o">=</span> <span class="n">webdriver</span><span class="o">.</span><span class="n">Firefox</span><span class="p">(</span><span class="n">firefox_profile</span><span class="o">=</span><span class="n">profile</span><span class="p">)</span>
<span class="n">proxy</span><span class="o">.</span><span class="n">new_har</span><span class="p">(</span><span class="s">&quot;google&quot;</span><span class="p">)</span>
<span class="n">driver</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&quot;http://www.google.co.uk&quot;</span><span class="p">)</span>
<span class="n">proxy</span><span class="o">.</span><span class="n">har</span> <span class="c"># returns a HAR JSON blob</span>
<span class="n">server</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
<span class="n">driver</span><span class="o">.</span><span class="n">quit</span><span class="p">()</span>
</pre></div>
</div>
<p>Contents:</p>
<div class="toctree-wrapper compound">
<ul>
<li class="toctree-l1"><a class="reference internal" href="client.html"><tt class="docutils literal"><span class="pre">client</span></tt> Package</a></li>
<li class="toctree-l1"><a class="reference internal" href="server.html"><tt class="docutils literal"><span class="pre">server</span></tt> Package</a></li>
</ul>
</div>
</div>
</div>
<div class="section" id="indices-and-tables">
<h1>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Permalink to this headline"></a></h1>
<ul class="simple">
<li><a class="reference internal" href="genindex.html"><em>Index</em></a></li>
<li><a class="reference internal" href="py-modindex.html"><em>Module Index</em></a></li>
<li><a class="reference internal" href="search.html"><em>Search Page</em></a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<h3><a href="#">Table Of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">Welcome to BrowserMob Proxy&#8217;s documentation!</a><ul>
<li><a class="reference internal" href="#how-to-install">How to install</a></li>
<li><a class="reference internal" href="#how-to-use-with-selenium-webdriver">How to use with selenium-webdriver</a></li>
</ul>
</li>
<li><a class="reference internal" href="#indices-and-tables">Indices and tables</a></li>
</ul>
<h4>Next topic</h4>
<p class="topless"><a href="client.html"
title="next chapter"><tt class="docutils literal"><span class="pre">client</span></tt> Package</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/index.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="client.html" title="client Package"
>next</a> |</li>
<li><a href="#">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2014, David Burns.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
</div>
</body>
</html>

View file

@ -0,0 +1,6 @@
# Sphinx inventory version 2
# Project: BrowserMob Proxy
# Version: 0.6.0
# The remainder of this file is compressed using zlib.
xÚ¥TÁnÂ0 ½ó•¶kѸrÝi‡I“ø€ÈM µ–4<E28093>í®ð÷ ¤Ó€M¨dǺ~ÏñË{ ÑQçp_‰ºµ‡}U¯ªþP‡éDzÕàª×è<06>Õ˱´Øa÷è»:!r« °m/smjÛä†7Øá¢á8
rˆMÏqHÇX‡<÷)<29>ø9ƒr±~¬êkÈòÙvºdüPÔP§È{<7B>|bCm£«V•Ím'¶4 Yƒ¶é‹,(Å®„§…ÓxPejÅÈMÞWX/Fž qc¤ O÷1ó#å<>‡çŒFc¡‡†<)¡”ÐXK€cKŠžDKÀަOž*Œ<>cJ"—kǨ|(S+åÁ¸N’î¶Å²Ù#'åÌÀ¾  7m•2¸ô±üoôF 5ÛÈF¶Û”ÂdEÑØßÅ•Ct„X"3Ò3ÅnÒî<°3sw4Þ÷»_ ‡\¤zãÁ¾ß<C2BE>•iYËÉ/¿Í>se¥€q(³‹§@EÈóK<C3B3>ý–^Ùçz¿/z’Ä

View file

@ -0,0 +1,112 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Python Module Index &mdash; BrowserMob Proxy 0.6.0 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '0.6.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
<script type="text/javascript">
DOCUMENTATION_OPTIONS.COLLAPSE_INDEX = true;
</script>
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="#" title="Python Module Index"
>modules</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<h1>Python Module Index</h1>
<div class="modindex-jumpbox">
<a href="#cap-b"><strong>b</strong></a>
</div>
<table class="indextable modindextable" cellspacing="0" cellpadding="2">
<tr class="pcap"><td></td><td>&nbsp;</td><td></td></tr>
<tr class="cap" id="cap-b"><td></td><td>
<strong>b</strong></td><td></td></tr>
<tr>
<td></td>
<td>
<a href="server.html#module-browsermobproxy"><tt class="xref">browsermobproxy</tt></a></td><td>
<em></em></td></tr>
</table>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="#" title="Python Module Index"
>modules</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2014, David Burns.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
</div>
</body>
</html>

View file

@ -0,0 +1,105 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Search &mdash; BrowserMob Proxy 0.6.0 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '0.6.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/searchtools.js"></script>
<link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
<script type="text/javascript">
jQuery(function() { Search.loadIndex("searchindex.js"); });
</script>
<script type="text/javascript" id="searchindexloader"></script>
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<h1 id="search-documentation">Search</h1>
<div id="fallback" class="admonition warning">
<script type="text/javascript">$('#fallback').hide();</script>
<p>
Please activate JavaScript to enable the search
functionality.
</p>
</div>
<p>
From here you can search these documents. Enter your search
words into the box below and click "search". Note that the search
function will automatically search for all of the words. Pages
containing fewer words won't appear in the result list.
</p>
<form action="" method="get">
<input type="text" name="q" value="" />
<input type="submit" value="search" />
<span id="search-progress" style="padding-left: 10px"></span>
</form>
<div id="search-results">
</div>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2014, David Burns.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
</div>
</body>
</html>

View file

@ -0,0 +1 @@
Search.setIndex({envversion:42,terms:{all:[1,2],basic_authent:1,execut:1,whitelist:1,rest:0,bandwidth:1,code:1,comma:1,kbp:1,paramet:[1,2],profil:0,configur:1,param:[],should:2,quiet_period:1,add:1,latenc:1,dict:[],blob:0,har:[0,1],pass:1,match:1,"return":[0,1],string:1,variou:1,get:[0,1,2],read:1,browsermobproxi:[0,1,2],express:1,stop:[0,2],number:1,repo:0,capturecont:1,traffic:1,upstream:1,password:1,ip_address:1,specif:1,list:1,authent:1,server:[],separ:1,retry_count:1,api:[0,1],timeout:1,each:1,through:1,unicod:1,where:1,page:[0,1],www:0,set:[1,2],captur:1,manual:0,idea:1,second:1,remap:1,connect:[1,2],domain:1,arg:2,close:1,process:2,port:[1,2],index:0,statu:1,network:1,item:2,pattern:1,content:[0,1],rewrit:1,retri:1,max:1,"import":0,ref:1,refer:1,shut:1,run:[0,2],webdriver_proxi:1,javascript:1,bodi:1,host:1,dictionari:[1,2],address:1,path:[0,2],wait:[1,2],search:0,quiet:1,against:1,initialis:[1,2],instanc:1,request_interceptor:1,rewrite_url:1,com:0,firefox:0,status_cod:1,modul:0,easy_instal:0,automat:1,down:1,header:1,"boolean":1,empti:2,wait_for_traffic_to_stop:1,regexp:1,regex:1,quit:0,given:1,git:0,from:[0,1],interact:2,json:0,been:1,avail:0,start:[0,2],live:1,basic:1,upstreamkbp:1,until:2,more:2,automatedtest:0,desir:1,option:[1,2],python:0,blacklist:1,hold:2,capturebinarycont:1,cach:1,clear_dns_cach:1,none:1,keyword:1,"default":[1,2],wish:1,setup:0,batch:2,record:1,limit:1,can:[0,2],str:[],remap_host:1,firefoxprofil:0,new_har:[0,1],"int":[],request:1,selenium_proxi:[0,1],capturehead:1,downstreamkbp:1,need:[1,2],replac:1,file:2,pip:0,create_proxi:[0,2],mai:2,downstream:1,want:1,when:1,detail:[1,2],binari:1,valid:1,lookup:1,futur:2,"new":1,you:[0,1,2],respons:1,add_to_cap:1,http:[0,1],allow:2,usernam:1,pypi:0,clone:0,object:[1,2],driver:0,what:1,firefox_profil:0,capabl:1,regular:1,set_proxi:0,associ:1,"class":[1,2],googl:0,handl:1,github:0,response_interceptor:1,url:[1,2],entri:1,clear:1,credenti:1,inform:1,client:[],thi:[1,2],new_pag:1},objtypes:{"0":"py:module","1":"py:method","2":"py:attribute","3":"py:class"},objnames:{"0":["py","module","Python module"],"1":["py","method","Python method"],"2":["py","attribute","Python attribute"],"3":["py","class","Python class"]},filenames:["index","client","server"],titles:["Welcome to BrowserMob Proxy&#8217;s documentation!","<tt class=\"docutils literal\"><span class=\"pre\">client</span></tt> Package","<tt class=\"docutils literal\"><span class=\"pre\">server</span></tt> Package"],objects:{"":{browsermobproxy:[2,0,0,"-"]},browsermobproxy:{Client:[1,3,1,""],Server:[2,3,1,""]},"browsermobproxy.Server":{url:[2,2,1,""],start:[2,1,1,""],stop:[2,1,1,""],create_proxy:[2,1,1,""]},"browsermobproxy.Client":{remap_hosts:[1,1,1,""],blacklist:[1,1,1,""],selenium_proxy:[1,1,1,""],response_interceptor:[1,1,1,""],webdriver_proxy:[1,1,1,""],clear_dns_cache:[1,1,1,""],timeouts:[1,1,1,""],whitelist:[1,1,1,""],new_page:[1,1,1,""],basic_authentication:[1,1,1,""],retry:[1,1,1,""],request_interceptor:[1,1,1,""],limits:[1,1,1,""],wait_for_traffic_to_stop:[1,1,1,""],rewrite_url:[1,1,1,""],close:[1,1,1,""],har:[1,2,1,""],add_to_capabilities:[1,1,1,""],headers:[1,1,1,""],new_har:[1,1,1,""]}},titleterms:{welcom:0,packag:[1,2],browsermob:0,selenium:0,webdriv:0,server:2,how:0,client:1,indic:0,tabl:0,instal:0,document:0,proxi:0}})

View file

@ -0,0 +1,157 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>server Package &mdash; BrowserMob Proxy 0.6.0 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '0.6.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="top" title="BrowserMob Proxy 0.6.0 documentation" href="index.html" />
<link rel="prev" title="client Package" href="client.html" />
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="client.html" title="client Package"
accesskey="P">previous</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="toctree-wrapper compound">
<ul class="simple">
</ul>
</div>
<div class="section" id="module-browsermobproxy">
<span id="server-package"></span><h1><tt class="xref py py-mod docutils literal"><span class="pre">server</span></tt> Package<a class="headerlink" href="#module-browsermobproxy" title="Permalink to this headline"></a></h1>
<dl class="class">
<dt id="browsermobproxy.Server">
<em class="property">class </em><tt class="descclassname">browsermobproxy.</tt><tt class="descname">Server</tt><big>(</big><em>path='browsermob-proxy'</em>, <em>options={}</em><big>)</big><a class="headerlink" href="#browsermobproxy.Server" title="Permalink to this definition"></a></dt>
<dd><p>Initialises a Server object</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Args:</th><td class="field-body"></td>
</tr>
<tr class="field-even field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>path</strong> &#8211; Path to the browsermob proxy batch file</li>
<li><strong>options</strong> &#8211; Dictionary that can hold the port. More items will be added in the future. This defaults to an empty dictionary</li>
</ul>
</td>
</tr>
</tbody>
</table>
<dl class="method">
<dt id="browsermobproxy.Server.create_proxy">
<tt class="descname">create_proxy</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Server.create_proxy" title="Permalink to this definition"></a></dt>
<dd><p>Gets a client class that allow to set all the proxy details that you
may need to.</p>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Server.start">
<tt class="descname">start</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Server.start" title="Permalink to this definition"></a></dt>
<dd><p>This will start the browsermob proxy and then wait until it can
interact with it</p>
</dd></dl>
<dl class="method">
<dt id="browsermobproxy.Server.stop">
<tt class="descname">stop</tt><big>(</big><big>)</big><a class="headerlink" href="#browsermobproxy.Server.stop" title="Permalink to this definition"></a></dt>
<dd><p>This will stop the process running the proxy</p>
</dd></dl>
<dl class="attribute">
<dt id="browsermobproxy.Server.url">
<tt class="descname">url</tt><a class="headerlink" href="#browsermobproxy.Server.url" title="Permalink to this definition"></a></dt>
<dd><p>Gets the url that the proxy is running on. This is not the URL clients
should connect to.</p>
</dd></dl>
</dd></dl>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<h4>Previous topic</h4>
<p class="topless"><a href="client.html"
title="previous chapter"><tt class="docutils literal"><span class="pre">client</span></tt> Package</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/server.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="client.html" title="client Package"
>previous</a> |</li>
<li><a href="index.html">BrowserMob Proxy 0.6.0 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2014, David Burns.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
</div>
</body>
</html>

View file

@ -0,0 +1,8 @@
.. toctree::
:maxdepth: 2
:mod:`client` Package
---------------------
.. automodule:: browsermobproxy
.. autoclass:: Client
:members:

View file

@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-
#
# BrowserMob Proxy documentation build configuration file, created by
# sphinx-quickstart on Fri May 24 12:37:12 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.autodoc']
autoclass_content = 'both'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'BrowserMob Proxy'
copyright = u'2014, David Burns'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.6.0'
# The full version, including alpha/beta/rc tags.
release = '0.6.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'BrowserMobProxydoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'BrowserMobProxy.tex', u'BrowserMob Proxy Documentation',
u'David Burns', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'browsermobproxy', u'BrowserMob Proxy Documentation',
[u'David Burns'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'BrowserMobProxy', u'BrowserMob Proxy Documentation',
u'David Burns', 'BrowserMobProxy', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

Some files were not shown because too many files have changed in this diff Show more