mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-08 00:07:30 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
441
testing/marionette/accessibility.js
Normal file
441
testing/marionette/accessibility.js
Normal 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
1348
testing/marionette/action.js
Normal file
File diff suppressed because it is too large
Load diff
104
testing/marionette/addon.js
Normal file
104
testing/marionette/addon.js
Normal 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();
|
||||
});
|
||||
});
|
||||
};
|
||||
322
testing/marionette/assert.js
Normal file
322
testing/marionette/assert.js
Normal 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
192
testing/marionette/atom.js
Normal 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,"&"));-1!=a.indexOf("<")&&(a=a.replace(la,"<"));-1!=a.indexOf(">")&&(a=a.replace(ma,">"));-1!=a.indexOf('"')&&(a=a.replace(na,"""));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);}
|
||||
436
testing/marionette/browser.js
Normal file
436
testing/marionette/browser.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
193
testing/marionette/capture.js
Normal file
193
testing/marionette/capture.js
Normal 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
140
testing/marionette/cert.js
Normal 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();
|
||||
},
|
||||
};
|
||||
};
|
||||
25
testing/marionette/chrome/test.xul
Normal file
25
testing/marionette/chrome/test.xul
Normal 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>
|
||||
19
testing/marionette/chrome/test2.xul
Normal file
19
testing/marionette/chrome/test2.xul
Normal 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>
|
||||
37
testing/marionette/chrome/test_anonymous_content.xul
Normal file
37
testing/marionette/chrome/test_anonymous_content.xul
Normal 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>
|
||||
7
testing/marionette/chrome/test_dialog.dtd
Normal file
7
testing/marionette/chrome/test_dialog.dtd
Normal 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">
|
||||
7
testing/marionette/chrome/test_dialog.properties
Normal file
7
testing/marionette/chrome/test_dialog.properties
Normal 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
|
||||
36
testing/marionette/chrome/test_dialog.xul
Normal file
36
testing/marionette/chrome/test_dialog.xul
Normal 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>
|
||||
9
testing/marionette/chrome/test_nested_iframe.xul
Normal file
9
testing/marionette/chrome/test_nested_iframe.xul
Normal 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"/>
|
||||
3
testing/marionette/client/.flake8
Normal file
3
testing/marionette/client/.flake8
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[flake8]
|
||||
max-line-length = 99
|
||||
exclude = __init__.py,disti/*,build/*,
|
||||
2
testing/marionette/client/MANIFEST.in
Normal file
2
testing/marionette/client/MANIFEST.in
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
exclude MANIFEST.in
|
||||
include requirements.txt
|
||||
153
testing/marionette/client/docs/Makefile
Normal file
153
testing/marionette/client/docs/Makefile
Normal 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."
|
||||
46
testing/marionette/client/docs/advanced/actions.rst
Normal file
46
testing/marionette/client/docs/advanced/actions.rst
Normal 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()
|
||||
54
testing/marionette/client/docs/advanced/debug.rst
Normal file
54
testing/marionette/client/docs/advanced/debug.rst
Normal 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)
|
||||
126
testing/marionette/client/docs/advanced/findelement.rst
Normal file
126
testing/marionette/client/docs/advanced/findelement.rst
Normal 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
|
||||
13
testing/marionette/client/docs/advanced/landing.rst
Normal file
13
testing/marionette/client/docs/advanced/landing.rst
Normal 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
|
||||
71
testing/marionette/client/docs/advanced/stale.rst
Normal file
71
testing/marionette/client/docs/advanced/stale.rst
Normal 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`.
|
||||
185
testing/marionette/client/docs/basics.rst
Normal file
185
testing/marionette/client/docs/basics.rst
Normal 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`.
|
||||
259
testing/marionette/client/docs/conf.py
Normal file
259
testing/marionette/client/docs/conf.py
Normal 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'
|
||||
16
testing/marionette/client/docs/index.rst
Normal file
16
testing/marionette/client/docs/index.rst
Normal 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
|
||||
55
testing/marionette/client/docs/interactive.rst
Normal file
55
testing/marionette/client/docs/interactive.rst
Normal 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()
|
||||
|
||||
190
testing/marionette/client/docs/make.bat
Normal file
190
testing/marionette/client/docs/make.bat
Normal 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
|
||||
51
testing/marionette/client/docs/reference.rst
Normal file
51
testing/marionette/client/docs/reference.rst
Normal 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:
|
||||
26
testing/marionette/client/marionette_driver/__init__.py
Normal file
26
testing/marionette/client/marionette_driver/__init__.py
Normal 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
|
||||
70
testing/marionette/client/marionette_driver/addons.py
Normal file
70
testing/marionette/client/marionette_driver/addons.py
Normal 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)
|
||||
27
testing/marionette/client/marionette_driver/by.py
Normal file
27
testing/marionette/client/marionette_driver/by.py
Normal 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"
|
||||
|
|
@ -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'))
|
||||
69
testing/marionette/client/marionette_driver/decorators.py
Normal file
69
testing/marionette/client/marionette_driver/decorators.py
Normal 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
|
||||
179
testing/marionette/client/marionette_driver/errors.py
Normal file
179
testing/marionette/client/marionette_driver/errors.py
Normal 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)
|
||||
311
testing/marionette/client/marionette_driver/expected.py
Normal file
311
testing/marionette/client/marionette_driver/expected.py
Normal 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
|
||||
467
testing/marionette/client/marionette_driver/geckoinstance.py
Normal file
467
testing/marionette/client/marionette_driver/geckoinstance.py
Normal 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',
|
||||
}
|
||||
93
testing/marionette/client/marionette_driver/gestures.py
Normal file
93
testing/marionette/client/marionette_driver/gestures.py
Normal 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()
|
||||
84
testing/marionette/client/marionette_driver/keys.py
Normal file
84
testing/marionette/client/marionette_driver/keys.py
Normal 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'
|
||||
54
testing/marionette/client/marionette_driver/localization.py
Normal file
54
testing/marionette/client/marionette_driver/localization.py
Normal 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")
|
||||
2153
testing/marionette/client/marionette_driver/marionette.py
Normal file
2153
testing/marionette/client/marionette_driver/marionette.py
Normal file
File diff suppressed because it is too large
Load diff
227
testing/marionette/client/marionette_driver/selection.py
Normal file
227
testing/marionette/client/marionette_driver/selection.py
Normal 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')
|
||||
98
testing/marionette/client/marionette_driver/timeout.py
Normal file
98
testing/marionette/client/marionette_driver/timeout.py
Normal 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
|
||||
300
testing/marionette/client/marionette_driver/transport.py
Normal file
300
testing/marionette/client/marionette_driver/transport.py
Normal 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()
|
||||
167
testing/marionette/client/marionette_driver/wait.py
Normal file
167
testing/marionette/client/marionette_driver/wait.py
Normal 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()
|
||||
2
testing/marionette/client/requirements.txt
Normal file
2
testing/marionette/client/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mozrunner >= 6.13
|
||||
mozversion >= 1.1
|
||||
49
testing/marionette/client/setup.py
Normal file
49
testing/marionette/client/setup.py
Normal 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(),
|
||||
)
|
||||
237
testing/marionette/components/marionette.js
Normal file
237
testing/marionette/components/marionette.js
Normal 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]);
|
||||
4
testing/marionette/components/marionette.manifest
Normal file
4
testing/marionette/components/marionette.manifest
Normal 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
|
||||
9
testing/marionette/components/moz.build
Normal file
9
testing/marionette/components/moz.build
Normal 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
|
||||
131
testing/marionette/cookies.js
Normal file
131
testing/marionette/cookies.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
228
testing/marionette/dispatcher.js
Normal file
228
testing/marionette/dispatcher.js
Normal 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
2907
testing/marionette/driver.js
Normal file
File diff suppressed because it is too large
Load diff
1159
testing/marionette/element.js
Normal file
1159
testing/marionette/element.js
Normal file
File diff suppressed because it is too large
Load diff
487
testing/marionette/error.js
Normal file
487
testing/marionette/error.js
Normal 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],
|
||||
]);
|
||||
494
testing/marionette/evaluate.js
Normal file
494
testing/marionette/evaluate.js
Normal 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
1365
testing/marionette/event.js
Normal file
File diff suppressed because it is too large
Load diff
260
testing/marionette/frame.js
Normal file
260
testing/marionette/frame.js
Normal 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]);
|
||||
3
testing/marionette/harness/.flake8
Normal file
3
testing/marionette/harness/.flake8
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[flake8]
|
||||
max-line-length = 99
|
||||
exclude = __init__.py,disti/*,build/*,marionette_harness/runner/mixins/*, marionette_harness/tests/*
|
||||
3
testing/marionette/harness/MANIFEST.in
Normal file
3
testing/marionette/harness/MANIFEST.in
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
exclude MANIFEST.in
|
||||
include requirements.txt
|
||||
recursive-include marionette_harness/www *
|
||||
30
testing/marionette/harness/README.rst
Normal file
30
testing/marionette/harness/README.rst
Normal 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`.
|
||||
35
testing/marionette/harness/marionette_harness/__init__.py
Normal file
35
testing/marionette/harness/marionette_harness/__init__.py
Normal 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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
|
|
@ -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,
|
||||
)
|
||||
1076
testing/marionette/harness/marionette_harness/runner/base.py
Normal file
1076
testing/marionette/harness/marionette_harness/runner/base.py
Normal file
File diff suppressed because it is too large
Load diff
142
testing/marionette/harness/marionette_harness/runner/httpd.py
Normal file
142
testing/marionette/harness/marionette_harness/runner/httpd.py
Normal 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()
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
__version__ = '0.5.0'
|
||||
|
||||
from .server import Server
|
||||
from .client import Client
|
||||
|
||||
__all__ = ['Server', 'Client', 'browsermobproxy']
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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."
|
||||
|
|
@ -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
|
||||
|
|
@ -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 — 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> »</li>
|
||||
<li><a href="index.html" accesskey="U">Module code</a> »</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">'0.5.0'</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">'Server'</span><span class="p">,</span> <span class="s">'Client'</span><span class="p">,</span> <span class="s">'browsermobproxy'</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> »</li>
|
||||
<li><a href="index.html" >Module code</a> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2014, David Burns.
|
||||
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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 — 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> »</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> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2014, David Burns.
|
||||
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
:mod:`client` Package
|
||||
---------------------
|
||||
.. automodule:: browsermobproxy
|
||||
.. autoclass:: Client
|
||||
:members:
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
:mod:`server` Package
|
||||
---------------------
|
||||
.. automodule:: browsermobproxy
|
||||
.. autoclass:: Server
|
||||
:members:
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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>«</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();
|
||||
});
|
||||
|
|
@ -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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};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);
|
||||
|
|
@ -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 ▹\
|
||||
</a>\
|
||||
<a href="#" id="hc<%id%>" class="hide-propose-change">\
|
||||
Propose a change ▿\
|
||||
</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 ▹</a>\
|
||||
<a href="#" class="close-reply" id="cr<%id%>">reply ▿</a>\
|
||||
<a href="#" id="sp<%id%>" class="show-proposal">proposal ▹</a>\
|
||||
<a href="#" id="hp<%id%>" class="hide-proposal">proposal ▿</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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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 — 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> »</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> – 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 ‘proxy’ 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> – 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> – domain to set authentication credentials for</li>
|
||||
<li><strong>username</strong> – valid username to use when authenticating</li>
|
||||
<li><strong>password</strong> – 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> – a comma separated list of regular expressions</li>
|
||||
<li><strong>status_code</strong> – 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> – 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> – 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> – A reference for the HAR. Defaults to None</li>
|
||||
<li><strong>options</strong> – 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> – 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> – url that you wish to remap</li>
|
||||
<li><strong>ip_address</strong> – 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> – 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> – 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> – 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> – a regex to match requests with</li>
|
||||
<li><strong>replace</strong> – 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> – 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> – number of seconds the network needs to be quiet for</li>
|
||||
<li><strong>timeout</strong> – 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> – a comma separated list of regular expressions</li>
|
||||
<li><strong>status_code</strong> – 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’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> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2014, David Burns.
|
||||
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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 — 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> »</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> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2014, David Burns.
|
||||
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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! — 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> »</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’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">"path/to/browsermob-proxy"</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">"google"</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">"http://www.google.co.uk"</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’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> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2014, David Burns.
|
||||
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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’Ä
|
||||
|
|
@ -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 — 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> »</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> </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> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2014, David Burns.
|
||||
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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 — 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> »</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> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2014, David Burns.
|
||||
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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’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}})
|
||||
|
|
@ -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 — 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> »</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> – Path to the browsermob proxy batch file</li>
|
||||
<li><strong>options</strong> – 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> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2014, David Burns.
|
||||
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.2.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
:mod:`client` Package
|
||||
---------------------
|
||||
.. automodule:: browsermobproxy
|
||||
.. autoclass:: Client
|
||||
:members:
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue