1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 05:58:03 +01:00

Fix tab number updates in FF36. Closes issue 300.

This commit is contained in:
Kris Maglione
2011-01-26 16:22:29 -05:00
parent a6fe97b787
commit 218562e21b
48 changed files with 498 additions and 8666 deletions

View File

@@ -65,7 +65,7 @@ var Browser = Module("browser", {
mappings: function () {
mappings.add([modes.NORMAL],
["y"], "Yank current location to the clipboard",
["y", "<yank-location>"], "Yank current location to the clipboard",
function () { dactyl.clipboardWrite(buffer.uri.spec, true); });
// opening websites

View File

@@ -1651,7 +1651,7 @@ var Buffer = Module("buffer", {
mappings: function () {
var myModes = config.browserModes;
mappings.add(myModes, ["."],
mappings.add(myModes, [".", "<repeat-key>"],
"Repeat the last key event",
function (args) {
if (mappings.repeat) {
@@ -1670,31 +1670,31 @@ var Buffer = Module("buffer", {
function () { ex.stop(); });
// scrolling
mappings.add(myModes, ["j", "<Down>", "<C-e>"],
mappings.add(myModes, ["j", "<Down>", "<C-e>", "<scroll-down-line>"],
"Scroll document down",
function (args) { buffer.scrollVertical("lines", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["k", "<Up>", "<C-y>"],
mappings.add(myModes, ["k", "<Up>", "<C-y>", "<scroll-up-line>"],
"Scroll document up",
function (args) { buffer.scrollVertical("lines", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, dactyl.has("mail") ? ["h"] : ["h", "<Left>"],
mappings.add(myModes, dactyl.has("mail") ? ["h", "<scroll-left-column>"] : ["h", "<Left>", "<scroll-left-column>"],
"Scroll document to the left",
function (args) { buffer.scrollHorizontal("columns", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, dactyl.has("mail") ? ["l"] : ["l", "<Right>"],
mappings.add(myModes, dactyl.has("mail") ? ["l", "<scroll-right-column>"] : ["l", "<Right>", "<scroll-right-column>"],
"Scroll document to the right",
function (args) { buffer.scrollHorizontal("columns", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["0", "^"],
mappings.add(myModes, ["0", "^", "<scroll-begin>"],
"Scroll to the absolute left of the document",
function () { buffer.scrollToPercent(0, null); });
mappings.add(myModes, ["$"],
mappings.add(myModes, ["$", "<scroll-end>"],
"Scroll to the absolute right of the document",
function () { buffer.scrollToPercent(100, null); });
@@ -1708,7 +1708,7 @@ var Buffer = Module("buffer", {
function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 100); },
{ count: true });
mappings.add(myModes, ["%"],
mappings.add(myModes, ["%", "<scroll-percent>"],
"Scroll to {count} percent of the document",
function (args) {
dactyl.assert(args.count > 0 && args.count <= 100);
@@ -1716,59 +1716,59 @@ var Buffer = Module("buffer", {
},
{ count: true });
mappings.add(myModes, ["<C-d>"],
mappings.add(myModes, ["<C-d>", "<scroll-down>"],
"Scroll window downwards in the buffer",
function (args) { buffer._scrollByScrollSize(args.count, true); },
{ count: true });
mappings.add(myModes, ["<C-u>"],
mappings.add(myModes, ["<C-u>", "<scroll-up>"],
"Scroll window upwards in the buffer",
function (args) { buffer._scrollByScrollSize(args.count, false); },
{ count: true });
mappings.add(myModes, ["<C-b>", "<PageUp>", "<S-Space>"],
mappings.add(myModes, ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-page-up>"],
"Scroll up a full page",
function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["<C-f>", "<PageDown>", "<Space>"],
mappings.add(myModes, ["<C-f>", "<PageDown>", "<Space>", "<scroll-page-down>"],
"Scroll down a full page",
function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["]f"],
mappings.add(myModes, ["]f", "<previous-frame>"],
"Focus next frame",
function (args) { buffer.shiftFrameFocus(Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["[f"],
mappings.add(myModes, ["[f", "<next-frame>"],
"Focus previous frame",
function (args) { buffer.shiftFrameFocus(-Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["]]"],
mappings.add(myModes, ["]]", "<next-page>"],
"Follow the link labeled 'next' or '>' if it exists",
function (args) {
buffer.findLink("next", options["nextpattern"], (args.count || 1) - 1, true);
},
{ count: true });
mappings.add(myModes, ["[["],
mappings.add(myModes, ["[[", "<previous-page>"],
"Follow the link labeled 'prev', 'previous' or '<' if it exists",
function (args) {
buffer.findLink("previous", options["previouspattern"], (args.count || 1) - 1, true);
},
{ count: true });
mappings.add(myModes, ["gf"],
mappings.add(myModes, ["gf", "<view-source>"],
"Toggle between rendered and source view",
function () { buffer.viewSource(null, false); });
mappings.add(myModes, ["gF"],
mappings.add(myModes, ["gF", "<view-source-externally>"],
"View source with an external editor",
function () { buffer.viewSource(null, true); });
mappings.add(myModes, ["gi"],
mappings.add(myModes, ["gi", "<focus-input>"],
"Focus last used input field",
function (args) {
let elem = buffer.lastInputField;
@@ -1808,7 +1808,7 @@ var Buffer = Module("buffer", {
dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB, background: true });
});
mappings.add(myModes, ["p", "<MiddleMouse>"],
mappings.add(myModes, ["p", "<MiddleMouse>", "<open-clipboard-url>"],
"Open (put) a URL based on the current clipboard contents in the current buffer",
function () {
let url = dactyl.clipboardRead();
@@ -1816,7 +1816,7 @@ var Buffer = Module("buffer", {
dactyl.open(url);
});
mappings.add(myModes, ["P"],
mappings.add(myModes, ["P", "<tab-open-clipboard-url>"],
"Open (put) a URL based on the current clipboard contents in a new buffer",
function () {
let url = dactyl.clipboardRead();
@@ -1825,16 +1825,16 @@ var Buffer = Module("buffer", {
});
// reloading
mappings.add(myModes, ["r"],
mappings.add(myModes, ["r", "<reload>"],
"Reload the current web page",
function () { tabs.reload(tabs.getTab(), false); });
mappings.add(myModes, ["R"],
mappings.add(myModes, ["R", "<full-reload>"],
"Reload while skipping the cache",
function () { tabs.reload(tabs.getTab(), true); });
// yanking
mappings.add(myModes, ["Y"],
mappings.add(myModes, ["Y", "<yank-word>"],
"Copy selected text or current word",
function () {
let sel = buffer.getCurrentWord();
@@ -1843,62 +1843,62 @@ var Buffer = Module("buffer", {
});
// zooming
mappings.add(myModes, ["zi", "+"],
mappings.add(myModes, ["zi", "+", "<text-zoom-in>"],
"Enlarge text zoom of current web page",
function (args) { buffer.zoomIn(Math.max(args.count, 1), false); },
{ count: true });
mappings.add(myModes, ["zm"],
mappings.add(myModes, ["zm", "<text-zoom-more>"],
"Enlarge text zoom of current web page by a larger amount",
function (args) { buffer.zoomIn(Math.max(args.count, 1) * 3, false); },
{ count: true });
mappings.add(myModes, ["zo", "-"],
mappings.add(myModes, ["zo", "-", "<text-zoom-out>"],
"Reduce text zoom of current web page",
function (args) { buffer.zoomOut(Math.max(args.count, 1), false); },
{ count: true });
mappings.add(myModes, ["zr"],
mappings.add(myModes, ["zr", "<text-zoom-reduce>"],
"Reduce text zoom of current web page by a larger amount",
function (args) { buffer.zoomOut(Math.max(args.count, 1) * 3, false); },
{ count: true });
mappings.add(myModes, ["zz"],
mappings.add(myModes, ["zz", "<text-zoom>"],
"Set text zoom value of current web page",
function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, false); },
{ count: true });
mappings.add(myModes, ["ZI", "zI"],
mappings.add(myModes, ["ZI", "zI", "<full-zoom-in>"],
"Enlarge full zoom of current web page",
function (args) { buffer.zoomIn(Math.max(args.count, 1), true); },
{ count: true });
mappings.add(myModes, ["ZM", "zM"],
mappings.add(myModes, ["ZM", "zM", "<full-zoom-more>"],
"Enlarge full zoom of current web page by a larger amount",
function (args) { buffer.zoomIn(Math.max(args.count, 1) * 3, true); },
{ count: true });
mappings.add(myModes, ["ZO", "zO"],
mappings.add(myModes, ["ZO", "zO", "<full-zoom-out>"],
"Reduce full zoom of current web page",
function (args) { buffer.zoomOut(Math.max(args.count, 1), true); },
{ count: true });
mappings.add(myModes, ["ZR", "zR"],
mappings.add(myModes, ["ZR", "zR", "<full-zoom-reduce>"],
"Reduce full zoom of current web page by a larger amount",
function (args) { buffer.zoomOut(Math.max(args.count, 1) * 3, true); },
{ count: true });
mappings.add(myModes, ["zZ"],
mappings.add(myModes, ["zZ", "<full-zoom>"],
"Set full zoom value of current web page",
function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, true); },
{ count: true });
// page info
mappings.add(myModes, ["<C-g>"],
mappings.add(myModes, ["<C-g>", "<page-info>"],
"Print the current file name",
function () { buffer.showPageInfo(false); });
mappings.add(myModes, ["g<C-g>"],
mappings.add(myModes, ["g<C-g>", "<more-page-info>"],
"Print file information",
function () { buffer.showPageInfo(true); });
},

View File

@@ -148,12 +148,6 @@ var CommandWidgets = Class("CommandWidgets", {
return this.commandbar;
}
});
let fontSize = util.computedStyle(document.documentElement).fontSize;
styles.system.add("font-size", "dactyl://content/buffer.xhtml",
"body { font-size: " + fontSize + "; } \
html|html > xul|scrollbar { visibility: collapse !important; }",
true);
},
addElement: function addElement(obj) {
const self = this;
@@ -301,18 +295,10 @@ var CommandMode = Class("CommandMode", {
this.keepCommand = userContext.hidden_option_command_afterimage;
if (this.historyKey)
this.history = CommandLine.History(commandline.widgets.active.command.inputField, this.historyKey);
this.history = CommandLine.History(commandline.widgets.active.command.inputField, this.historyKey, this);
if (this.complete)
this.completions = CommandLine.Completions(commandline.widgets.active.command.inputField);
this.autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) {
if (!events.feedingKeys && this.completions && options["autocomplete"].length) {
this.completions.complete(true, false);
if (this.completions)
this.completions.itemList.visible = true;
}
}, this);
this.completions = CommandLine.Completions(commandline.widgets.active.command.inputField, this);
},
open: function (command) {
@@ -335,17 +321,14 @@ var CommandMode = Class("CommandMode", {
commandline.commandSession = this;
if (this.command || stack.pop && commandline.command) {
this.onChange(commandline.command);
this.autocompleteTimer.flush(true);
if (this.completions)
this.completions.autocompleteTimer.flush(true);
}
},
leave: function (stack) {
this.autocompleteTimer.reset();
if (this.completions) {
this.completions.previewClear();
this.completions.tabTimer.reset();
}
if (this.completions)
this.completions.cleanup();
if (this.history)
this.history.save();
@@ -368,9 +351,9 @@ var CommandMode = Class("CommandMode", {
if (this.completions) {
this.resetCompletions();
this.autocompleteTimer.tell(false);
this.completions.autocompleteTimer.tell(false);
if (!this.completions.itemList.visible)
this.autocompleteTimer.flush();
this.completions.autocompleteTimer.flush();
}
this.onChange(commandline.command);
},
@@ -614,8 +597,10 @@ var CommandLine = Module("commandline", {
if (this.widgets.message && this.widgets.message[1] === this._lastClearable)
this.widgets.message = null;
if (modes.main != modes.COMMAND_LINE)
if (!this.commandSession) {
this.widgets.command = null;
this.hideCompletions();
}
if (modes.main == modes.OUTPUT_MULTILINE && !mow.isScrollable(1))
modes.pop();
@@ -866,10 +851,11 @@ var CommandLine = Module("commandline", {
* @param {string} mode The mode for which we need history.
*/
History: Class("History", {
init: function init(inputField, mode) {
init: function init(inputField, mode, session) {
this.mode = mode;
this.input = inputField;
this.reset();
this.session = session;
},
get store() commandline._store.get(this.mode, []),
set store(ary) { commandline._store.set(this.mode, ary); },
@@ -915,8 +901,9 @@ var CommandLine = Module("commandline", {
*/
replace: function replace(val) {
delete this.input.dactylKeyPress;
if (this.completions)
this.completions.previewClear();
this.input.value = val;
commandline.commandSession.onChange(val, "history");
},
/**
@@ -928,8 +915,8 @@ var CommandLine = Module("commandline", {
*/
select: function select(backward, matchCurrent) {
// always reset the tab completion if we use up/down keys
if (commandline._completions)
commandline._completions.reset();
if (this.session.completions)
this.session.completions.reset();
let diff = backward ? -1 : 1;
@@ -976,22 +963,36 @@ var CommandLine = Module("commandline", {
* @param {Object} input
*/
Completions: Class("Completions", {
init: function init(input) {
init: function init(input, session) {
this.context = CompletionContext(input.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
this.context.onUpdate = this.closure._reset;
this.editor = input.editor;
this.input = input;
this.session = session;
this.selected = null;
this.wildmode = options.get("wildmode");
this.wildtypes = this.wildmode.value;
this.itemList = commandline.completionList;
this.itemList.setItems(this.context);
this.autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) {
if (!events.feedingKeys && options["autocomplete"].length) {
this.complete(true, false);
this.itemList.visible = true;
}
}, this);
this.tabTimer = Timer(0, 0, function tabTell(event) {
this.tab(event.shiftKey, event.altKey && options["altwildmode"]);
}, this);
},
cleanup: function () {
this.previewClear();
this.tabTimer.reset();
this.autocompleteTimer.reset();
this.itemList.visible = false;
},
UP: {},
DOWN: {},
PAGE_UP: {},
@@ -1038,7 +1039,7 @@ var CommandLine = Module("commandline", {
complete: function complete(show, tabPressed) {
this.context.reset();
this.context.tabPressed = tabPressed;
commandline.commandSession.complete(this.context);
this.session.complete(this.context);
this.context.updateAsync = true;
this.reset(show, tabPressed);
this.wildIndex = 0;
@@ -1204,7 +1205,7 @@ var CommandLine = Module("commandline", {
tabs: [],
tab: function tab(reverse, wildmode) {
commandline.commandSession.autocompleteTimer.flush();
this.autocompleteTimer.flush();
if (this._caret != this.caret)
this.reset();

View File

@@ -110,10 +110,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
get menuItems() Dactyl.getMenuItems(),
// Global constants
CURRENT_TAB: [],
NEW_TAB: [],
NEW_BACKGROUND_TAB: [],
NEW_WINDOW: [],
CURRENT_TAB: "here",
NEW_TAB: "tab",
NEW_BACKGROUND_TAB: "background-tab",
NEW_WINDOW: "window",
forceNewTab: false,
forceNewWindow: false,
@@ -173,7 +173,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
let filters = args.map(function (arg) RegExp("\\b" + util.regexp.escape(arg) + "\\b", "i"));
if (filters.length)
results = results.filter(function (item) filters.every(function (re) re.test(item.name + item.description)));
results = results.filter(function (item) filters.every(function (re) re.test(item.name + " " + item.description)));
commandline.commandOutput(
template.usage(results, params.format));
@@ -380,15 +380,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
},
userEval: function (str, context, fileName, lineNumber) {
let ctxt;
if (jsmodules.__proto__ != window)
str = "with (window) { with (modules) { (this.eval || eval)(" + str.quote() + ") } }";
if (fileName == null)
if (io.sourcing && io.sourcing.file[0] !== "[")
({ file: fileName, line: lineNumber }) = io.sourcing;
({ file: fileName, line: lineNumber, context: ctxt }) = io.sourcing;
else try {
if (!context)
context = userContext;
context = userContext || ctxt;
context[EVAL_ERROR] = null;
context[EVAL_STRING] = str;
@@ -411,7 +412,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
}
if (!context)
context = _userContext;
context = _userContext || ctxt;
return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
},
@@ -560,10 +561,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
* @private
*/
initDocument: function initDocument(doc) {
try {
if (doc.location.protocol === "dactyl:") {
dactyl.initHelp();
config.styleHelp();
}
}
catch (e) {
util.reportError(e);
}
},
/**
@@ -662,9 +668,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
let re = util.regexp(<![CDATA[
^ (?P<space> \s*)
(?P<char> [-*+]) \x20
(?P<char> [-*+]) \ //
(?P<content> .*\n
(?: \1\x20\x20.*\n | \s*\n)* )
(?: \1\ \ .*\n | \s*\n)* )
|
(?P<par>
(?: ^ [^\S\n]*
@@ -676,30 +682,55 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
(?: ^ [^\S\n]* \n) +
]]>, "gmy");
let betas = util.regexp(/\[(b\d)\]/, "g");
let beta = array(betas.iterate(NEWS))
.map(function (m) m[1]).uniq().slice(-1)[0];
default xml namespace = NS;
function rec(text, level) {
function rec(text, level, li) {
let res = <></>;
let list, space;
let list, space, i = 0;
for (let match in re.iterate(text)) {
if (match.char) {
if (!list)
res += list = <ul/>;
list.* += <li>{rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1)}</li>;
let li = <li/>;
li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li)
list.* += li;
}
else if (match.par) {
let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
let t = tags;
tags = array(betas.iterate(tags)).map(function (m) m[1]);
let group = tags.length && !tags.some(function (t) t == beta) ? "HelpNewsOld" : "";
if (i === 0 && li) {
li.@highlight = group;
group = "";
}
list = null;
if (level == 0 && /^.*:\n$/.test(match.par))
res += <h2>{template.linkifyHelp(match.par.slice(0, -1), true)}</h2>;
if (level == 0 && /^.*:\n$/.test())
var elem = <h2>{template.linkifyHelp(par.slice(0, -1), true)}</h2>;
else {
let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(match.par);
res += <p>{
let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
res += <p highlight={group + " HelpNews"}>{
!tags.length ? "" :
<hl key="HelpNewsTag">{tags.join(" ")}</hl>
}{
a ? <hl key="HelpWarning">{a}</hl> : ""
}{
template.linkifyHelp(b, true)
}</p>;
}
}
i++;
}
for each (let attr in res..@highlight) {
attr.parent().@NS::highlight = attr;
delete attr.parent().@highlight;
}
return res;
}
@@ -710,7 +741,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
unescape(encodeURI( // UTF-8 handling hack.
<document xmlns={NS}
<document xmlns={NS} xmlns:dactyl={NS}
name="versions" title={config.appName + " Versions"}>
<h1 tag="versions news">{config.appName} Versions</h1>
<toc start="2"/>

View File

@@ -11,12 +11,14 @@
var ProcessorStack = Class("ProcessorStack", {
init: function (mode, hives, keyModes) {
this.main = mode.main;
this._actions = [];
this.actions = [];
this.buffer = "";
this.events = [];
this.processors = keyModes.map(function (m) hives.map(function (h) KeyProcessor(m, h)))
.flatten().array;
this.ownsBuffer = !this.processors.some(function (p) p.main.ownsBuffer);
for (let [i, input] in Iterator(this.processors)) {
let params = input.main.params;
@@ -31,9 +33,70 @@ var ProcessorStack = Class("ProcessorStack", {
}
},
notify: function () {
this.execute(Events.KILL, true);
},
execute: function execute(result, force) {
if (force && this.actions.length)
this.processors.length = 0;
if (this.ownsBuffer)
statusline.updateInputBuffer(this.processors.length ? this.buffer : "");
if (this.processors.length) {
result = Events.KILL;
if (this.actions.length && options["timeout"])
this.timer = services.Timer(this, options["timeoutlen"], services.Timer.TYPE_ONE_SHOT);
}
else if (this.actions.length) {
if (this._actions.length == 0) {
dactyl.beep();
events.feedingKeys = false;
}
for (var res = this.actions[0]; callable(res);)
res = res();
result = res === Events.PASS ? Events.PASS : Events.KILL;
}
else if (result !== Events.KILL && !this.actions.length &&
this.processors.some(function (p) !p.main.passUnknown)) {
result = Events.KILL;
dactyl.beep();
events.feedingKeys = false;
}
else if (result === undefined)
result = Events.PASS;
if (result !== Events.PASS)
Events.kill(this.events[this.events.length - 1]);
if (result === Events.PASS || result === Events.ABORT)
this.events.filter(function (e) e.getPreventDefault())
.forEach(function (event, i) {
let elem = event.originalTarget;
if (event.originalTarget) {
let doc = elem.ownerDocument || elem.document || elem;
let evt = events.create(doc, event.type, event);
events.dispatch(elem, evt, { skipmap: true, isMacro: true, isReplay: true });
}
else if (i > 0)
events.events.keypress.call(events, event);
});
if (force && this.processors.length === 0)
events.processor = null;
return this.processors.length == 0;
},
process: function process(event) {
function dbg() {}
if (this.timer)
this.timer.cancel();
let key = events.toString(event);
this.events.push(event);
@@ -70,9 +133,7 @@ var ProcessorStack = Class("ProcessorStack", {
dbg("ACTIONS: " + actions.length + " " + this.actions.length);
dbg("PROCESSORS:", processors);
if (!processors.some(function (p) p.main.ownsBuffer))
statusline.updateInputBuffer(processors.length ? this.buffer : "");
this._actions = actions;
this.actions = actions.concat(this.actions);
if (result === Events.KILL)
@@ -87,42 +148,7 @@ var ProcessorStack = Class("ProcessorStack", {
this.processors = processors;
if (processors.length)
result = Events.KILL;
else if (this.actions.length) {
if (actions.length == 0)
dactyl.beep();
if (modes.replaying && !events.waitForPageLoad())
result = Events.KILL;
else {
for (var res = this.actions[0]; callable(res);)
res = res();
result = res === Events.PASS ? Events.PASS : Events.KILL;
}
}
else if (result !== Events.KILL && !this.actions.length &&
processors.some(function (p) !p.main.passUnknown)) {
result = Events.KILL;
dactyl.beep();
}
else if (result === undefined)
result = Events.PASS;
if (result !== Events.PASS)
Events.kill(event);
if (result === Events.PASS || result === Events.ABORT)
this.events.filter(function (e) e.getPreventDefault())
.forEach(function (event, i) {
if (event.originalTarget) {
let evt = events.create(event.originalTarget.ownerDocument, event.type, event);
events.dispatch(event.originalTarget, evt, { skipmap: true, isMacro: true });
}
else if (i > 0)
events.events.keypress.call(events, event);
});
return this.processors.length == 0;
return this.execute(result, options["timeout"] && options["timeoutlen"] === 0)
}
});
@@ -491,6 +517,7 @@ var Events = Module("events", {
util.threadYield(1, true);
for (let [, evt_obj] in Iterator(events.fromString(keys))) {
let now = Date.now();
for (let type in values(["keydown", "keyup", "keypress"])) {
let evt = update({}, evt_obj, { type: type });
@@ -511,10 +538,6 @@ var Events = Module("events", {
if (!this.feedingKeys)
break;
// Stop feeding keys if page loading failed.
if (modes.replaying && !this.waitForPageLoad())
break;
}
}
catch (e) {
@@ -594,9 +617,14 @@ var Events = Module("events", {
* of x.
*
* @param {string} keys Messy form.
* @param {boolean} unknownOk Whether unknown keys are passed
* through rather than being converted to <lt>keyname>.
* @default false
* @returns {string} Canonical form.
*/
canonicalKeys: function (keys, unknownOk) {
if (arguments.length === 1)
unknownOk = true;
return events.fromString(keys, unknownOk).map(events.closure.toString).join("");
},
@@ -651,11 +679,17 @@ var Events = Module("events", {
* <S-@> where @ is a non-case-changeable, non-space character.
*
* @param {string} keys The string to parse.
* @param {boolean} unknownOk Whether unknown keys are passed
* through rather than being converted to <lt>keyname>.
* @default false
* @returns {Array[Object]}
*/
fromString: function (input, unknownOk) {
let out = [];
if (arguments.length === 1)
unknownOk = true;
let out = [];
let re = RegExp("<.*?>?>|[^<]|<(?!.*>)", "g");
let match;
while ((match = re.exec(input))) {
@@ -862,10 +896,7 @@ var Events = Module("events", {
isContentNode: function isContentNode(node) {
let win = (node.ownerDocument || node).defaultView || node;
for (; win; win = win.parent != win && win.parent)
if (win == content)
return true;
return false;
return XPCNativeWrapper(win).top == content;
},
/**
@@ -874,39 +905,24 @@ var Events = Module("events", {
*
* @returns {boolean}
*/
waitForPageLoad: function () {
waitForPageLoad: function (time) {
util.threadYield(true); // clear queue
if (buffer.loaded == 1)
if (buffer.loaded)
return true;
const maxWaitTime = 25;
let start = Date.now();
let end = start + (maxWaitTime * 1000); // maximum time to wait - TODO: add option
let now;
while (now = Date.now(), now < end) {
util.threadYield();
if (!events.feedingKeys)
return false;
if (buffer.loaded > 0) {
util.sleep(250);
break;
}
else
dactyl.echo("Waiting for page to load...", commandline.DISALLOW_MULTILINE);
}
const maxWaitTime = (time || 25);
let start = Date.now();
let end = start + (maxWaitTime * 1000);
util.waitFor(function () !events.feedingKeys || buffer.loaded || Date.now() > end);
commandline.clear();
// TODO: allow macros to be continued when page does not fully load with an option
if (!buffer.loaded)
dactyl.echoerr("Page did not load completely in " + maxWaitTime + " seconds. Macro stopped.");
// sometimes the input widget had focus when replaying a macro
// maybe this call should be moved somewhere else?
// dactyl.focusContent(true);
return buffer.loaded;
},
@@ -1000,6 +1016,7 @@ var Events = Module("events", {
*/
input: function onInput(event) {
if ("dactylKeyPress" in event.originalTarget)
delete event.originalTarget.dactylKeyPress;
},
@@ -1024,18 +1041,17 @@ var Events = Module("events", {
let duringFeed = this.duringFeed || [];
this.duringFeed = [];
try {
if (this.feedingEvent && [!(k in event) || event[k] === v for ([k, v] in Iterator(this.feedingEvent))].every(util.identity)) {
if (this.feedingEvent)
for (let [k, v] in Iterator(this.feedingEvent))
if (!(k in event))
event[k] = v;
this.feedingEvent = null;
}
let key = events.toString(event);
if (!key)
return null;
if (modes.recording && (!this._input || !mappings.user.hasMap(modes.main, this._input.buffer + key)))
if (modes.recording && !event.isReplay)
events._macroKeys.push(key);
// feedingKeys needs to be separate from interrupted so
@@ -1043,8 +1059,6 @@ var Events = Module("events", {
// interrupting whatever it's started and a real <C-c>
// interrupting our playback.
if (events.feedingKeys && !event.isMacro) {
if (!event.originalTarget)
util.dumpStack();
if (key == "<C-c>") {
events.feedingKeys = false;
if (modes.replaying) {
@@ -1177,10 +1191,6 @@ var Events = Module("events", {
// access to the real focus target
// Huh? --djk
onFocusChange: function onFocusChange(event) {
// command line has its own focus change handler
if (modes.main.input)
return;
function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument
let win = window.document.commandDispatcher.focusedWindow;
@@ -1202,7 +1212,7 @@ var Events = Module("events", {
}
if (Events.isInputElement(elem)) {
if (!(modes.main & (modes.INSERT | modes.TEXT_EDIT | modes.VISUAL)))
if (!modes.main.input)
modes.push(modes.INSERT);
if (hasHTMLDocument(win))
@@ -1216,7 +1226,7 @@ var Events = Module("events", {
if (modes.main == modes.VISUAL && elem.selectionEnd == elem.selectionStart)
modes.pop();
if (!(modes.main & (modes.INSERT | modes.TEXT_EDIT | modes.VISUAL)))
if (!modes.main.input)
if (options["insertmode"])
modes.push(modes.INSERT);
else {
@@ -1323,11 +1333,11 @@ var Events = Module("events", {
},
mappings: function () {
mappings.add(modes.all,
["<C-z>"], "Temporarily ignore all " + config.appName + " key bindings",
["<C-z>", "<pass-all-keys>"], "Temporarily ignore all " + config.appName + " key bindings",
function () { modes.push(modes.PASS_THROUGH); });
mappings.add(modes.all,
["<C-v>"], "Pass through next key",
["<C-v>", "<pass-next-key>"], "Pass through next key",
function () {
if (modes.main == modes.QUOTE)
return Events.PASS;
@@ -1340,7 +1350,7 @@ var Events = Module("events", {
// macros
mappings.add([modes.NORMAL, modes.TEXT_AREA, modes.PLAYER].filter(util.identity),
["q"], "Record a key sequence into a macro",
["q", "<record-macro>"], "Record a key sequence into a macro",
function ({ arg }) {
events._macroKeys.pop();
events[modes.recording ? "finishRecording" : "startRecording"](arg);
@@ -1348,13 +1358,33 @@ var Events = Module("events", {
{ get arg() !modes.recording });
mappings.add([modes.NORMAL, modes.TEXT_AREA, modes.PLAYER].filter(util.identity),
["@"], "Play a macro",
["@", "<play-macro>"], "Play a macro",
function ({ arg, count }) {
count = Math.max(count, 1);
while (count-- && events.playMacro(arg))
;
},
{ arg: true, count: true });
mappings.add([modes.COMMAND],
["<A-m>s", "<sleep>"], "Sleep for {count} milliseconds before continuing macro playback",
function ({ command, count }) {
let now = Date.now();
dactyl.assert(count, "Count required for " + command);
if (events.feedingKeys)
util.sleep(count);
},
{ count: true });
mappings.add([modes.COMMAND],
["<A-m>l", "<wait-for-page-load>"], "Wait for the current page to finish loading before continuing macro playback",
function ({ count }) {
if (events.feedingKeys && !events.waitForPageLoad(count)) {
util.interrupted = true;
throw Error("Interrupted");
}
},
{ count: true });
},
options: function () {
options.add(["passkeys", "pk"],
@@ -1375,9 +1405,18 @@ var Events = Module("events", {
return values;
}
});
options.add(["strictfocus", "sf"],
"Prevent scripts from focusing input elements without user intervention",
"boolean", true);
options.add(["timeout", "tmo"],
"Whether to execute a shorter key command after a timeout when a longer command exists",
"boolean", true);
options.add(["timeoutlen", "tmol"],
"Maximum time (milliseconds) to wait for a longer key command when a shorter one exists",
"number", 1000);
},
sanitizer: function () {
sanitizer.addItem("macros", {

View File

@@ -113,9 +113,17 @@ var Map = Class("Map", {
if (this.executing)
util.dumpStack("Attempt to execute mapping recursively: " + args.command);
dactyl.assert(!this.executing, "Attempt to execute mapping recursively: " + args.command);
try {
this.executing = true;
let res = dactyl.trapErrors(repeat);
var res = repeat();
}
catch (e) {
events.feedingKeys = false;
}
finally {
this.executing = false;
}
return res;
}
@@ -507,7 +515,7 @@ var Mappings = Module("mappings", {
description: "Accept a count before the requisite key press"
},
{
names: ["-description", "-d"],
names: ["-description", "-desc", "-d"],
description: "A description of this mapping",
default: "User-defined mapping",
type: CommandOption.STRING

View File

@@ -8,6 +8,12 @@
var MOW = Module("mow", {
init: function () {
let fontSize = util.computedStyle(document.documentElement).fontSize;
styles.system.add("font-size", "dactyl://content/buffer.xhtml",
"body { font-size: " + fontSize + "; } \
html|html > xul|scrollbar { visibility: collapse !important; }",
true);
XML.ignoreWhitespace = true;
util.overlayWindow(window, {
objects: {

View File

@@ -867,9 +867,11 @@ var Tabs = Module("tabs", {
},
events: function () {
let tabContainer = config.tabbrowser.mTabContainer;
["TabMove", "TabOpen", "TabClose"].forEach(function (event) {
events.addSessionListener(tabContainer, event, this.closure.updateTabCount, false);
}, this);
function callback() {
tabs.timeout(function () { this.updateTabCount(); });
}
for (let event in values(["TabMove", "TabOpen", "TabClose"]))
events.addSessionListener(tabContainer, event, callback, false);
events.addSessionListener(tabContainer, "TabSelect", this.closure._onTabSelect, false);
},
mappings: function () {

View File

@@ -20,7 +20,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
&dactyl.host; or to a web page, you have two options:
<item>
<tags><![CDATA[pass-through <C-z> CTRL-Z]]></tags>
<tags><![CDATA[pass-through <pass-all-keys> <C-z> CTRL-Z]]></tags>
<spec>&lt;C-z></spec>
<description>
<p>
@@ -33,7 +33,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
</item>
<item>
<tags><![CDATA[send-key <C-v> CTRL-V]]></tags>
<tags><![CDATA[send-key <pass-next-key> <C-v> CTRL-V]]></tags>
<spec>&lt;C-v></spec>
<description>
<p>
@@ -171,7 +171,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
</item>
<item>
<tags><![CDATA[<MiddleMouse> p]]></tags>
<tags><![CDATA[<open-clipboard-url> <MiddleMouse> p]]></tags>
<strut/>
<spec>p</spec>
<description>
@@ -185,7 +185,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
</item>
<item>
<tags>P</tags>
<tags>&lt;tab-open-clipboard-url> P</tags>
<strut/>
<spec>P</spec>
<description>
@@ -343,7 +343,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
<h2 tag="reloading">Reloading</h2>
<item>
<tags>r</tags>
<tags>&lt;reload> r</tags>
<spec>r</spec>
<description short="true">
<p>Reload the current web page.</p>
@@ -351,7 +351,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
</item>
<item>
<tags>R</tags>
<tags>&lt;full-reload> R</tags>
<spec>R</spec>
<description short="true">
<p>Reload the current web page without using the cache.</p>

View File

@@ -22,7 +22,7 @@
<h2 tag="buffer-information">Buffer information</h2>
<item>
<tags><![CDATA[<C-g>]]></tags>
<tags><![CDATA[<page-info> <C-g>]]></tags>
<strut/>
<spec>&lt;C-g></spec>
<description>
@@ -35,7 +35,7 @@
</item>
<item>
<tags><![CDATA[g<C-g>]]></tags>
<tags><![CDATA[<more-page-info> g<C-g>]]></tags>
<spec>g&lt;C-g></spec>
<description short="true">
<p>Print file information. Same as <ex>:pa<oa>geinfo</oa></ex>.</p>
@@ -95,7 +95,7 @@
<h2 tag="motion scrolling">Motion commands</h2>
<item>
<tags>^ 0</tags>
<tags>&lt;scroll-begin> ^ 0</tags>
<strut/>
<spec>0</spec>
<description>
@@ -107,7 +107,7 @@
</item>
<item>
<tags>$</tags>
<tags>&lt;scroll-end> $</tags>
<spec>$</spec>
<description short="true">
<p>Scroll to the absolute right of the document</p>
@@ -140,7 +140,7 @@
</item>
<item>
<tags>N%</tags>
<tags>&lt;scroll-percent> N%</tags>
<spec><a>count</a>%</spec>
<description short="true">
<p>Scroll to <a>count</a> percent of the document.</p>
@@ -148,7 +148,7 @@
</item>
<item>
<tags><![CDATA[<Left> h]]></tags>
<tags><![CDATA[<scroll-column-left> <Left> h]]></tags>
<strut/>
<spec><oa>count</oa>h</spec>
<description>
@@ -160,7 +160,7 @@
</item>
<item>
<tags><![CDATA[<C-e> <Down> j]]></tags>
<tags><![CDATA[<scroll-line-down> <C-e> <Down> j]]></tags>
<strut/>
<spec><oa>count</oa>j</spec>
<description>
@@ -172,7 +172,7 @@
</item>
<item>
<tags><![CDATA[<C-y> <Up> k]]></tags>
<tags><![CDATA[<scroll-line-up> <C-y> <Up> k]]></tags>
<strut/>
<spec><oa>count</oa>k</spec>
<description>
@@ -184,7 +184,7 @@
</item>
<item>
<tags><![CDATA[<Right> l]]></tags>
<tags><![CDATA[<scroll-column-right> <Right> l]]></tags>
<strut/>
<spec><oa>count</oa>l</spec>
<description>
@@ -196,7 +196,7 @@
</item>
<item>
<tags><![CDATA[<C-d>]]></tags>
<tags><![CDATA[<scroll-down> <C-d>]]></tags>
<strut/>
<spec><oa>count</oa>&lt;C-d></spec>
<description>
@@ -209,7 +209,7 @@
</item>
<item>
<tags><![CDATA[<C-u>]]></tags>
<tags><![CDATA[<scroll-up> <C-u>]]></tags>
<strut/>
<spec><oa>count</oa>&lt;C-u></spec>
<description>
@@ -222,7 +222,7 @@
</item>
<item>
<tags><![CDATA[<S-Space> <PageUp> <C-b>]]></tags>
<tags><![CDATA[<scroll-page-up> <S-Space> <PageUp> <C-b>]]></tags>
<strut/>
<spec><oa>count</oa>&lt;C-b></spec>
<description>
@@ -234,7 +234,7 @@
</item>
<item>
<tags><![CDATA[<Space> <PageDown> <C-f>]]></tags>
<tags><![CDATA[<scroll-page-down> <Space> <PageDown> <C-f>]]></tags>
<strut/>
<spec><oa>count</oa>&lt;C-f></spec>
<description>
@@ -264,7 +264,7 @@
</item>
<item>
<tags>gi</tags>
<tags>&lt;focus-input> gi</tags>
<strut/>
<spec><oa>count</oa>gi</spec>
<description>
@@ -277,7 +277,7 @@
</item>
<item>
<tags>]f</tags>
<tags>&lt;next-frame> ]f</tags>
<strut/>
<spec><oa>count</oa>]f</spec>
<description>
@@ -290,7 +290,7 @@
</item>
<item>
<tags>[f</tags>
<tags>&lt;previous-frame> [f</tags>
<strut/>
<spec><oa>count</oa>[f</spec>
<description>
@@ -303,7 +303,7 @@
</item>
<item>
<tags>]]</tags>
<tags>&lt;next-page> ]]</tags>
<strut/>
<spec><oa>count</oa>]]</spec>
<description>
@@ -316,7 +316,7 @@
</item>
<item>
<tags>[[</tags>
<tags>&lt;previous-page> [[</tags>
<strut/>
<spec><oa>count</oa>[[</spec>
<description>
@@ -361,7 +361,7 @@
</note>
<item>
<tags>+ zi</tags>
<tags><![CDATA[<text-zoom-in> + zi]]></tags>
<spec><oa>count</oa>zi</spec>
<description short="true">
<p>Enlarge text zoom of current web page. Mnemonic: zoom in.</p>
@@ -369,7 +369,7 @@
</item>
<item>
<tags>zm</tags>
<tags><![CDATA[<text-zoom-more> zm]]></tags>
<strut/>
<spec><oa>count</oa>zm</spec>
<description>
@@ -378,7 +378,7 @@
</item>
<item>
<tags>- zo</tags>
<tags><![CDATA[<text-zoom-out> - zo]]></tags>
<spec><oa>count</oa>zo</spec>
<description short="true">
<p>Reduce text zoom of current web page. Mnemonic: zoom out.</p>
@@ -386,7 +386,7 @@
</item>
<item>
<tags>zr</tags>
<tags><![CDATA[<text-zoom-reduce> zr]]></tags>
<spec><oa>count</oa>zr</spec>
<description short="true">
<p>Reduce text zoom of current web page by a larger amount. Mnemonic: zoom reduce.</p>
@@ -394,7 +394,7 @@
</item>
<item>
<tags>zz</tags>
<tags><![CDATA[<text-zoom> zz]]></tags>
<strut/>
<spec><oa>count</oa>zz</spec>
<description>
@@ -407,7 +407,7 @@
</item>
<item>
<tags>ZI zI</tags>
<tags><![CDATA[<full-zoom-in> ZI zI]]></tags>
<spec><oa>count</oa>ZI</spec>
<description short="true">
<p>Enlarge full zoom of current web page. Mnemonic: zoom in.</p>
@@ -415,7 +415,7 @@
</item>
<item>
<tags>ZM zM</tags>
<tags><![CDATA[<full-zoom-more> ZM zM]]></tags>
<strut/>
<spec><oa>count</oa>ZM</spec>
<description>
@@ -424,7 +424,7 @@
</item>
<item>
<tags>ZO zO</tags>
<tags><![CDATA[<full-zoom-out> ZO zO]]></tags>
<spec><oa>count</oa>ZO</spec>
<description short="true">
<p>Reduce full zoom of current web page. Mnemonic: zoom out.</p>
@@ -432,7 +432,7 @@
</item>
<item>
<tags>ZR zR</tags>
<tags><![CDATA[<full-zoom-reduce> ZR zR]]></tags>
<spec><oa>count</oa>ZR</spec>
<description short="true">
<p>Reduce full zoom of current web page by a larger amount. Mnemonic: zoom reduce.</p>
@@ -440,7 +440,7 @@
</item>
<item>
<tags>zZ</tags>
<tags><![CDATA[<full-zoom> zZ]]></tags>
<strut/>
<spec><oa>count</oa>zZ</spec>
<description>
@@ -489,7 +489,7 @@
</p>
<item>
<tags>y</tags>
<tags>&lt;yank-location> y</tags>
<spec>y</spec>
<description short="true">
<p>Yank current location to the clipboard.</p>
@@ -497,7 +497,7 @@
</item>
<item>
<tags>Y</tags>
<tags>&lt;yank-word> Y</tags>
<spec>Y</spec>
<description short="true">
<p>Copy currently selected text to the system clipboard.</p>

View File

@@ -217,6 +217,14 @@
</description>
</item>
<h3 tag=":map-timeout map-timeout">Mapping timeout</h3>
<p>
When &dactyl.appName; receives a key event that has a separate binding and
at the same time is part of a key chain, values of the <o>timeout</o> and
<o>timeoutlen</o> options are used to decide what to do. See the
documentation of those options for more information.
</p>
<h3 tag=":map-arguments">Special arguments</h3>
<tags>:map-&lt;silent></tags>

View File

@@ -1391,6 +1391,34 @@
</description>
</item>
<item>
<tags>'tmo' 'timeout'</tags>
<spec>'timeout' 'tmo'</spec>
<type>boolean</type>
<default>true</default>
<description>
<p>
When this option is set and a key sequence interpretable both as a
complete command and as a start of a longer command is typed,
execute the shorter command after <o>timeoutlen</o> milliseconds.
</p>
</description>
</item>
<item>
<tags>'tmol' 'timeoutlen'</tags>
<spec>'timeoutlen' 'tmol'</spec>
<type>number</type>
<default>1000</default>
<description>
<p>
Maximum number of milliseconds to wait for a longer key command
when a shorter one exists. Only effective when <o>timeout</o> is
set.
</p>
</description>
</item>
<item>
<tags>'titlestring'</tags>
<spec>'titlestring'</spec>

View File

@@ -21,7 +21,7 @@
<h2 tag="single-repeat">Single repeats</h2>
<item>
<tags>.</tags>
<tags>&lt;repeat-key> .</tags>
<strut/>
<spec><oa>count</oa>.</spec>
<description>
@@ -45,7 +45,7 @@
<h2 tag="macros complex-repeat">Macros</h2>
<item>
<tags>q</tags>
<tags>&lt;record-macro> q</tags>
<strut/>
<spec>q<a>0-9a-zA-Z</a></spec>
<description>
@@ -82,7 +82,7 @@
</item>
<item>
<tags>@</tags>
<tags>&lt;play-macro> @</tags>
<spec><oa>count</oa>@<a>a-z0-9</a></spec>
<description>
<p>
@@ -100,6 +100,39 @@
</description>
</item>
<h2 tag="macro-utilities">Macro utilities</h2>
<p>
The following key bindings facilitate the recording of efficient
macros. They have no effect when typed normally, but are
recorded and take effect during macro playback.
</p>
<item>
<tags><sleep> <A-m>s</tags>
<strut/>
<spec><a>count</a>&lt;m>s</spec>
<description>
<p>
Sleep for <a>count</a> milliseconds before resuming playback.
</p>
</description>
</item>
<item>
<tags><![CDATA[<wait-for-page-load> <A-m>l]]></tags>
<strut/>
<spec><oa>count</oa><![CDATA[<A-m>l]]></spec>
<description>
<p>
Wait for the current page to finish loading before resuming
playback. If <oa>count</oa> is given, wait no more than
<oa>count</oa> seconds. Otherwise wait no more than 25 seconds.
</p>
</description>
</item>
<h2 tag="using-scripts">Using scripts</h2>
<item>

View File

@@ -1219,6 +1219,15 @@ update(iter, {
func.call(self, val);
},
indexOf: function indexOf(iter, elem) {
let i = 0;
for (let item in iter) {
if (item == elem)
return i;
i++;
}
},
/**
* Returns the array that results from applying *func* to each property of
* *obj*.

View File

@@ -613,6 +613,9 @@ var ConfigBase = Class("ConfigBase", {
HelpString[delim]::before content: attr(delim);
HelpString[delim]::after content: attr(delim);
HelpNews position: relative;
HelpNewsOld opacity: .7;
HelpNewsTag position: absolute; left: 100%; padding-left: 1em; color: #527BBD; opacity: .6; white-space: pre;
HelpHead;html|h1,html|h2,html|h3,html|h4;dactyl://help/* {
font-weight: bold;

View File

@@ -174,13 +174,13 @@ var RangeFinder = Module("rangefinder", {
description: "Forward Find mode, active when typing search input",
bases: [modes.FIND],
input: true
}, { history: "search" });
});
modes.addMode("FIND_BACKWARD", {
extended: true,
description: "Backward Find mode, active when typing search input",
bases: [modes.FIND],
input: true
}, { history: "search" });
});
},
commands: function (dactyl, modules, window) {
const { commands, rangefinder } = modules;

View File

@@ -185,8 +185,10 @@ var IO = Module("io", {
else if (/\.css$/.test(filename))
styles.registerSheet(uri.spec, false, true);
else {
if (!(file.path in plugins))
plugins[file.path] = modules.newContext(modules.userContext);
modules.commands.execute(file.read(), null, silent || "loud", null,
{ file: file.path, line: 1 });
{ file: file.path, line: 1, context: plugins[file.path] });
}
if (this._scriptNames.indexOf(file.path) == -1)

View File

@@ -478,7 +478,7 @@ var JavaScript = Module("javascript", {
if (obj.length) {
let func = obj[0][0][funcName];
if (callable(func)) {
let [, prefix, args] = /^(function .*?)\((.*)?\)/.exec(Function.prototype.toString.call(func));
let [, prefix, args] = /^(function .*?)\((.*?)\)/.exec(Function.prototype.toString.call(func));
let n = this._get(i).comma.length;
args = template.map(Iterator(args.split(", ")),
function ([i, arg]) <span highlight={i == n ? "Filter" : ""}>{arg}</span>,

View File

@@ -1200,7 +1200,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
}
if (post)
return [url, elems.join('&'), charset, elems];
return [url + "?" + elems.join('&'), null, charset];
return [url + "?" + elems.join('&'), null, charset, elems];
},
/**
@@ -1270,8 +1270,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
if (tokens)
expr = String.replace(expr, /(\(?P)?<(\w+)>/g, function (m, n1, n2) !n1 && set.has(tokens, n2) ? tokens[n2].dactylSource || tokens[n2].source || tokens[n2] : m);
expr = String.replace(expr, /\/\/[^\n]*|\/\*[^]*?\*\//gm, "")
.replace(/\s+/g, "");
expr = String.replace(expr, /(\\.)|\/\/[^\n]*|\/\*[^]*?\*\/|\s+/gm, function (m, m1) m1 || "");
if (/\(\?P</.test(expr)) {
var source = expr;

View File

@@ -1 +0,0 @@
- shared-modules should be synced with http://hg.mozilla.org/qa/mozmill-tests/

View File

@@ -1,140 +0,0 @@
var elementslib = {}; Components.utils.import("resource://mozmill/modules/elementslib.js", elementslib);
var jumlib = {}; Components.utils.import("resource://mozmill/modules/jum.js", jumlib);
/**
* A controller for simulating Dactyl related user actions and for making
* assertions about the expected outcomes of such actions.
*
* @param {MozMillController} controller The browser's MozMill controller.
*/
function Controller(controller) {
this.controller = controller;
this._dactyl = controller.window.dactyl.modules;
}
Controller.prototype = {
/**
* Asserts that the output message line text content matches *text*.
*
* @param {string|RegExp} text The expected text of the expected message line.
* @param {string} message The message to display upon assertion failure.
*/
assertMessageLine: function (text, message) {
let value = this.readMessageLine();
jumlib.assertTrue(typeof text == "string" ? text == value : text.test(value), message);
},
/**
* Asserts that the output message window text content matches *text*.
*
* @param {string|RegExp} text The expected text of the message window.
* @param {string} message The message to display upon assertion failure.
*/
assertMessageWindow: function (text, message) {
let value = this.readMessageWindow();
jumlib.assertTrue(typeof text == "string" ? text == value : text.test(value), message);
},
/**
* Asserts that an error message has been echoed to the message line or
* appended to the message window with the given *text*.
*
* @param {string|RegExp} text The expected text of the error message.
* @param {string} message The message to display upon assertion failure.
*/
// TODO: test against the tail of the MOW too.
assertErrorMessage: function (text, message) {
this.controller.sleep(0); // XXX
let messageBox = new elementslib.ID(this.controller.window.document, "dactyl-message").getNode();
jumlib.assertTrue(messageBox.value == text && /\bErrorMsg\b/.test(messageBox.getAttribute("highlight")), message);
},
/**
* Asserts that the current window selection matches *text*.
*
* @param {string|RegExp} text The expected text of the current selection.
* @param {string} message The message to display upon assertion failure.
*/
assertSelection: function (text, message) {
let selection = String(this.controller.window.content.getSelection());
jumlib.assertTrue(typeof text == "string" ? text == selection : text.test(selection), message);
},
/**
* Runs a Vi command.
*
* @param {string|Array} keys Either a string of simple keys suitable for
* {@link MozMillController#type} or an array of keysym - modifier
* pairs suitable for {@link MozMillController#keypress}.
*/
runViCommand: function (keys) {
if (typeof keys == "string")
keys = [[k] for each (k in keys)];
let self = this;
keys.forEach(function ([key, modifiers]) { self.controller.keypress(null, key, modifiers || {}); });
},
/**
* Runs an Ex command.
*
* @param {string} cmd The Ex command string as entered on the command
* line.
*/
runExCommand: function (cmd) {
this.controller.keypress(null, ":", {});
this.controller.type(null, cmd);
this.controller.keypress(null, "VK_RETURN", {});
},
/**
* Returns the text content of the output message line.
*
* @returns {string} The message line text content.
*/
readMessageLine: function () {
this.controller.sleep(0); // XXX
return new elementslib.ID(this.controller.window.document, "dactyl-message").getNode().value;
},
/**
* Returns the text content of the output message window.
*
* @returns {string} The message window text content.
*/
readMessageWindow: function () {
let messageWindow = new elementslib.ID(this.controller.window.document, "dactyl-multiline-output").getNode();
try {
this.controller.waitForEval("subject.collapsed == false", 1000, 100, messageWindow.parentNode);
return messageWindow.contentDocument.body.textContent;
}
catch (e) {
return "";
}
},
/**
* Opens the output message window by echoing a single newline character.
*/
openMessageWindow: function() {
//this.runExCommand("echo '\\n'");
this.runExCommand("echo " + "\n".quote());
},
/**
* Closes the output message window if open.
*/
closeMessageWindow: function() {
if (!this._dactyl.commandline.widgets.mowContainer.collapsed) // XXX
this.runViCommand([["VK_RETURN"]]);
},
/**
* @property {string} The specific Dactyl application. Eg. Pentadactyl
*/
get applicationName() this._dactyl.config.appName // XXX
};
exports.Controller = Controller;
// vim: sw=4 ts=8 et:

View File

@@ -1,13 +0,0 @@
<title>Test Find Commands</title>
<p>
A (play /ˈeɪ/; named a, plural aes) is the first letter and a vowel in the basic modern Latin alphabet. It is similar to the Ancient Greek letter Alpha, from which it derives.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p>
π (sometimes written pi) is a mathematical constant whose value is the ratio of any circle's circumference to its diameter in the Euclidean plane; this is the same value as the ratio of a circle's area to the square of its radius. It is approximately equal to 3.14159265 in the usual decimal notation. Many formulae from mathematics, science, and engineering involve π, which makes it one of the most important mathematical constants.
</p>

File diff suppressed because it is too large Load Diff

View File

@@ -1,685 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Adrian Kalla <akalla@aviary.pl>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Include required modules
var modalDialog = require("modal-dialog");
var utils = require("utils");
/**
* Unwraps a node which is wrapped into a XPCNativeWrapper or XrayWrapper
*
* @param {DOMnode} Wrapped DOM node
* @returns {DOMNode} Unwrapped DOM node
*/
function unwrapNode(aNode) {
var node = aNode;
if (node) {
// unwrap is not available on older branches (3.5 and 3.6) - Bug 533596
if ("unwrap" in XPCNativeWrapper) {
node = XPCNativeWrapper.unwrap(node);
}
else if ("wrappedJSObject" in node) {
node = node.wrappedJSObject;
}
}
return node;
}
/**
* DOMWalker Constructor
*
* @param {MozMillController} controller
* MozMill controller of the window to operate on.
* @param {Function} callbackFilter
* callback-method to filter nodes
* @param {Function} callbackNodeTest
* callback-method to test accepted nodes
* @param {Function} callbackResults
* callback-method to process the results
* [optional - default: undefined]
*/
function DOMWalker(controller, callbackFilter, callbackNodeTest,
callbackResults) {
this._controller = controller;
this._callbackFilter = callbackFilter;
this._callbackNodeTest = callbackNodeTest;
this._callbackResults = callbackResults;
}
DOMWalker.FILTER_ACCEPT = 1;
DOMWalker.FILTER_REJECT = 2;
DOMWalker.FILTER_SKIP = 3;
DOMWalker.GET_BY_ID = "id";
DOMWalker.GET_BY_SELECTOR = "selector";
DOMWalker.WINDOW_CURRENT = 1;
DOMWalker.WINDOW_MODAL = 2;
DOMWalker.WINDOW_NEW = 4;
DOMWalker.prototype = {
/**
* Returns the filter-callback
*
* @returns Function
*/
get callbackFilter() {
return this._callbackFilter;
},
/**
* Returns the node-testing-callback
*
* @returns Function
*/
get callbackNodeTest() {
return this._callbackNodeTest;
},
/**
* Returns the results-callback
*
* @returns Function
*/
get callbackResults() {
return this._callbackResults;
},
/**
* Returns the MozMill controller
*
* @returns Mozmill controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* The main DOMWalker function.
*
* It start's the _walk-method for a given window or other dialog, runs
* a callback to process the results for that window/dialog.
* After that switches to provided new windows/dialogs.
*
* @param {array of objects} ids
* Contains informations on the elements to open while
* Object-elements: getBy - attribute-name of the attribute
* containing the identification
* information for the opener-element
* subContent - array of ids of the opener-elements
* in the window with the value of
* the above getBy-attribute
* target - information, where the new
* elements will be opened
* [1|2|4]
* title - title of the opened dialog/window
* waitFunction - The function used as an argument
* for MozmillController.waitFor to
* wait before starting the walk.
* [optional - default: no waiting]
* windowHandler - Window instance
* [only needed for some tests]
*
* @param {Node} root
* Node to start testing from
* [optional - default: this._controller.window.document.documentElement]
* @param {Function} waitFunction
* The function used as an argument for MozmillController.waitFor to
* wait before starting the walk.
* [optional - default: no waiting]
*/
walk : function DOMWalker_walk(ids, root, waitFunction) {
if (typeof waitFunction == 'function')
this._controller.waitFor(waitFunction());
if (!root)
root = this._controller.window.document.documentElement;
var resultsArray = this._walk(root);
if (typeof this._callbackResults == 'function')
this._callbackResults(this._controller, resultsArray);
if (ids)
this._prepareTargetWindows(ids);
},
/**
* DOMWalker_filter filters a given node by submitting it to the
* this._callbackFilter method to decide, if it should be submitted to
* a provided this._callbackNodeTest method for testing (that hapens in case
* of FILTER_ACCEPT).
* In case of FILTER_ACCEPT and FILTER_SKIP, the children of such a node
* will be filtered recursively.
* Nodes with the nodeStatus "FILTER_REJECT" and their descendants will be
* completetly ignored.
*
* @param {Node} node
* Node to filter
* @param {array of elements} collectedResults
* An array with gathered all results from testing a given element
* @returns An array with gathered all results from testing a given element
* @type {array of elements}
*/
_filter : function DOMWalker_filter(node, collectedResults) {
var nodeStatus = this._callbackFilter(node);
var nodeTestResults = [];
switch (nodeStatus) {
case DOMWalker.FILTER_ACCEPT:
nodeTestResults = this._callbackNodeTest(node);
collectedResults = collectedResults.concat(nodeTestResults);
// no break here as we have to perform the _walk below too
case DOMWalker.FILTER_SKIP:
nodeTestResults = this._walk(node);
break;
default:
break;
}
collectedResults = collectedResults.concat(nodeTestResults);
return collectedResults;
},
/**
* Retrieves and returns a wanted node based on the provided identification
* set.
*
* @param {array of objects} idSet
* Contains informations on the elements to open while
* Object-elements: getBy - attribute-name of the attribute
* containing the identification
* information for the opener-element
* subContent - array of ids of the opener-elements
* in the window with the value of
* the above getBy-attribute
* target - information, where the new
* elements will be opened
* [1|2|4]
* title - title of the opened dialog/window
* waitFunction - The function used as an argument
* for MozmillController.waitFor to
* wait before starting the walk.
* [optional - default: no waiting]
* windowHandler - Window instance
* [only needed for some tests]
*
* @returns Node
* @type {Node}
*/
_getNode : function DOMWalker_getNode(idSet) {
var doc = this._controller.window.document;
// QuerySelector seems to be unusuale for id's in this case:
// https://developer.mozilla.org/En/Code_snippets/QuerySelector
switch (idSet.getBy) {
case DOMWalker.GET_BY_ID:
return doc.getElementById(idSet[idSet.getBy]);
case DOMWalker.GET_BY_SELECTOR:
return doc.querySelector(idSet[idSet.getBy]);
default:
throw new Error("Not supported getBy-attribute: " + idSet.getBy);
}
},
/**
* Main entry point to open new elements like windows, tabpanels, prefpanes,
* dialogs
*
* @param {array of objects} ids
* Contains informations on the elements to open while
* Object-elements: getBy - attribute-name of the attribute
* containing the identification
* information for the opener-element
* subContent - array of ids of the opener-elements
* in the window with the value of
* the above getBy-attribute
* target - information, where the new
* elements will be opened
* [1|2|4]
* title - title of the opened dialog/window
* waitFunction - The function used as an argument
* for MozmillController.waitFor to
* wait before starting the walk.
* [optional - default: no waiting]
* windowHandler - Window instance
* [only needed for some tests]
*/
_prepareTargetWindows : function DOMWalker_prepareTargetWindows(ids) {
var doc = this._controller.window.document;
// Go through all the provided ids
for (var i = 0; i < ids.length; i++) {
var node = this._getNode(ids[i]);
// Go further only, if the needed element exists
if (node) {
var idSet = ids[i];
// Decide if what we want to open is a new normal/modal window or if it
// will be opened in the current window.
switch (idSet.target) {
case DOMWalker.WINDOW_CURRENT:
this._processNode(node, idSet);
break;
case DOMWalker.WINDOW_MODAL:
// Modal windows have to be able to access that informations
var modalInfos = {ids : idSet.subContent,
callbackFilter : this._callbackFilter,
callbackNodeTest : this._callbackNodeTest,
callbackResults : this._callbackResults,
waitFunction : idSet.waitFunction}
persisted.modalInfos = modalInfos;
var md = new modalDialog.modalDialog(this._controller.window);
md.start(this._modalWindowHelper);
this._processNode(node, idSet);
md.waitForDialog();
break;
case DOMWalker.WINDOW_NEW:
this._processNode(node, idSet);
// Get the new non-modal window controller
var controller = utils.handleWindow('title', idSet.title,
undefined, false);
// Start a new DOMWalker instance
let domWalker = new DOMWalker(controller, this._callbackFilter,
this._callbackNodeTest,
this._callbackResults);
domWalker.walk(idSet.subContent,
controller.window.document.documentElement,
idSet.waitFunction);
// Close the window
controller.window.close();
break;
default:
throw new Error("Node does not exist: " + ids[i][ids[i].getBy]);
}
}
}
},
/**
* Opens new windows/dialog and starts the DOMWalker.walk() in case of dialogs
* in existing windows.
*
* @param {Node} activeNode
* Node that holds the information which way
* to open the new window/dialog
* @param {object} idSet
* ID set for the element to open
*/
_processNode: function DOMWalker_processNode(activeNode, idSet) {
var doc = this._controller.window.document;
var nodeToProcess = this._getNode(idSet);
// Opens a new window/dialog through a menulist and runs DOMWalker.walk()
// for it.
// If the wanted window/dialog is already selected, just run this function
// recursively for it's descendants.
if (activeNode.localName == "menulist") {
if (nodeToProcess.value != idSet.value) {
var dropDown = new elementslib.Elem(nodeToProcess);
this._controller.waitForElement(dropDown);
this._controller.select(dropDown, null, null, idSet.value);
this._controller.waitFor(function() {
return nodeToProcess.value == idSet.value;
}, "The menu item did not load in time: " + idSet.value);
// If the target is a new modal/non-modal window, this.walk() has to be
// started by the method opening that window. If not, we do it here.
if (idSet.target == DOMWalker.WINDOW_CURRENT)
this.walk(idSet.subContent, null, idSet.waitFunction);
} else if (nodeToProcess.selected && idSet.subContent &&
idSet.subContent.length > 0) {
this._prepareTargetWindows(idSet.subContent);
}
}
// Opens a new prefpane using a provided windowHandler object
// and runs DOMWalker.walk() for it.
// If the wanted prefpane is already selected, just run this function
// recursively for it's descendants.
else if (activeNode.localName == "prefpane") {
var windowHandler = idSet.windowHandler;
if (windowHandler.paneId != idSet.id) {
windowHandler.paneId = idSet.id;
// Wait for the pane's content to load and to be fully displayed
this._controller.waitFor(function() {
return (nodeToProcess.loaded &&
(!mozmill.isMac ||
nodeToProcess.style.opacity == 1 ||
nodeToProcess.style.opacity == null));
}, "The pane did not load in time: " + idSet.id);
// If the target is a new modal/non-modal window, this.walk() has to be
// started by the method opening that window. If not, we do it here.
if (idSet.target == DOMWalker.WINDOW_CURRENT)
this.walk(idSet.subContent, null, idSet.waitFunction);
} else if (windowHandler.paneId == idSet.id && idSet.subContent &&
idSet.subContent.length > 0) {
this._prepareTargetWindows(idSet.subContent);
}
}
// Switches to another tab and runs DOMWalker.walk() for it.
// If the wanted tabpanel is already selected, just run this function
// recursively for it's descendants.
else if (activeNode.localName == "tab") {
if (nodeToProcess.selected != true) {
this._controller.click(new elementslib.Elem(nodeToProcess));
// If the target is a new modal/non-modal window, this.walk() has to be
// started by the method opening that window. If not, we do it here.
if (idSet.target == DOMWalker.WINDOW_CURRENT)
this.walk(idSet.subContent, null, idSet.waitFunction);
} else if (nodeToProcess.selected && idSet.subContent
&& idSet.subContent.length > 0) {
this._prepareTargetWindows(idSet.subContent);
}
}
// Opens a new dialog/window by clicking on an object and runs
// DOMWalker.walk() for it.
else {
this._controller.click(new elementslib.Elem(nodeToProcess));
// If the target is a new modal/non-modal window, this.walk() has to be
// started by the method opening that window. If not, we do it here.
if (idSet.target == DOMWalker.WINDOW_CURRENT)
this.walk(idSet.subContent, null, idSet.waitFunction);
}
},
/**
* DOMWalker_walk goes recursively through the DOM, starting with a provided
* root-node and filters the nodes using the this._filter method.
*
* @param {Node} root
* Node to start testing from
* [optional - default: this._controller.window.document.documentElement]
* @returns An array with gathered all results from testing a given element
* @type {array of elements}
*/
_walk : function DOMWalker__walk(root) {
if (!root.childNodes)
throw new Error("root.childNodes does not exist");
var collectedResults = [];
// There seems to be no other way to get to the nodes hidden in the
// "_buttons" object (see Bug 614949)
if (root._buttons) {
for each (button in root._buttons) {
collectedResults = this._filter(button, collectedResults);
}
}
for (var i = 0; i < root.childNodes.length; i++) {
collectedResults = this._filter(root.childNodes[i], collectedResults);
}
return collectedResults;
},
/**
* Callback function to handle new windows
*
* @param {MozMillController} controller
* MozMill controller of the new window to operate on.
*/
_modalWindowHelper: function DOMWalker_modalWindowHelper(controller) {
let domWalker = new DOMWalker(controller,
persisted.modalInfos.callbackFilter,
persisted.modalInfos.callbackNodeTest,
persisted.modalInfos.callbackResults);
domWalker.walk(persisted.modalInfos.ids,
controller.window.document.documentElement,
persisted.modalInfos.waitFunction);
delete persisted.modalInfos;
controller.window.close();
}
}
/**
* Default constructor
*
* @param {object} aRoot
* Root node in the DOM to use as parent
*/
function nodeCollector(aRoot) {
this._root = aRoot.wrappedJSObject ? aRoot.wrappedJSObject : aRoot;
this._document = this._root.ownerDocument ? this._root.ownerDocument : this._root;
this._nodes = [ ];
}
/**
* Node collector class
*/
nodeCollector.prototype = {
/**
* Converts current nodes to elements
*
* @returns List of elements
* @type {array of ElemBase}
*/
get elements() {
var elements = [ ];
Array.forEach(this._nodes, function(element) {
elements.push(new elementslib.Elem(element));
});
return elements;
},
/**
* Get the current list of DOM nodes
*
* @returns List of nodes
* @type {array of object}
*/
get nodes() {
return this._nodes;
},
/**
* Sets current nodes to entries from the node list
*
* @param {array of objects} aNodeList
* List of DOM nodes to set
*/
set nodes(aNodeList) {
if (aNodeList) {
this._nodes = [ ];
Array.forEach(aNodeList, function(node) {
this._nodes.push(node);
}, this);
}
},
/**
* Get the root node used as parent for a node collection
*
* @returns Current root node
* @type {object}
*/
get root() {
return this._root;
},
/**
* Sets root node to the specified DOM node
*
* @param {object} aRoot
* DOM node to use as root for node collection
*/
set root(aRoot) {
if (aRoot) {
this._root = aRoot;
this._nodes = [ ];
}
},
/**
* Filter nodes given by the specified callback function
*
* @param {function} aCallback
* Function to test each element of the array.
* Elements: node, index (optional) , array (optional)
* @param {object} aThisObject
* Object to use as 'this' when executing callback.
* [optional - default: function scope]
*
* @returns The class instance
* @type {object}
*/
filter : function nodeCollector_filter(aCallback, aThisObject) {
if (!aCallback)
throw new Error(arguments.callee.name + ": No callback specified");
this.nodes = Array.filter(this.nodes, aCallback, aThisObject);
return this;
},
/**
* Filter nodes by DOM property and its value
*
* @param {string} aProperty
* Property to filter for
* @param {string} aValue
* Expected value of the DOM property
* [optional - default: n/a]
*
* @returns The class instance
* @type {object}
*/
filterByDOMProperty : function nodeCollector_filterByDOMProperty(aProperty, aValue) {
return this.filter(function(node) {
if (aProperty && aValue)
return node.getAttribute(aProperty) == aValue;
else if (aProperty)
return node.hasAttribute(aProperty);
else
return true;
});
},
/**
* Filter nodes by JS property and its value
*
* @param {string} aProperty
* Property to filter for
* @param {string} aValue
* Expected value of the JS property
* [optional - default: n/a]
*
* @returns The class instance
* @type {object}
*/
filterByJSProperty : function nodeCollector_filterByJSProperty(aProperty, aValue) {
return this.filter(function(node) {
if (aProperty && aValue)
return node.aProperty == aValue;
else if (aProperty)
return node.aProperty !== undefined;
else
return true;
});
},
/**
* Find anonymouse nodes with the specified attribute and value
*
* @param {string} aAttribute
* DOM attribute of the wanted node
* @param {string} aValue
* Value of the DOM attribute
*
* @returns The class instance
* @type {object}
*/
queryAnonymousNodes : function nodeCollector_queryAnonymousNodes(aAttribute, aValue) {
var node = this._document.getAnonymousElementByAttribute(this._root,
aAttribute,
aValue);
this.nodes = node ? [node] : [ ];
return this;
},
/**
* Find nodes with the specified selector
*
* @param {string} aSelector
* jQuery like element selector string
*
* @returns The class instance
* @type {object}
*/
queryNodes : function nodeCollector_queryNodes(aSelector) {
this.nodes = this._root.querySelectorAll(aSelector);
return this;
}
}
// Exports of functions
exports.unwrapNode = unwrapNode;
// Exports of classes
exports.DOMWalker = DOMWalker;
exports.nodeCollector = nodeCollector;

View File

@@ -1,411 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Anthony Hughes <anthony.s.hughes@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The DownloadsAPI adds support for download related functions. It also gives
* access to the Download Manager.
*
* @version 1.0.1
*/
// Include required modules
var utils = require("utils");
const gTimeout = 5000;
/**
* List of available download states
*/
const downloadState = {
notStarted : -1,
downloading : 0,
finished : 1,
failed : 2,
canceled : 3,
paused : 4,
queued : 5,
blockedParental : 6,
scanning : 7,
dirty : 8,
blockedPolicy : 9
}
/**
* Constructor
*/
function downloadManager() {
this._controller = null;
this.downloadState = downloadState;
this._dms = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager);
}
/**
* Download Manager class
*/
downloadManager.prototype = {
/**
* Returns the controller of the current window
*
* @returns Mozmill Controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* Returns the number of currently active downloads
*
* @returns Number of active downloads
* @type {number}
*/
get activeDownloadCount() {
return this._dms.activeDownloadCount;
},
/**
* Cancel all active downloads
*/
cancelActiveDownloads : function downloadManager_cancelActiveDownloads() {
// Get a list of all active downloads (nsISimpleEnumerator)
var downloads = this._dms.activeDownloads;
// Iterate through each active download and cancel it
while (downloads.hasMoreElements()) {
var download = downloads.getNext().QueryInterface(Ci.nsIDownload);
this._dms.cancelDownload(download.id);
}
},
/**
* Remove all downloads from the database
*/
cleanUp : function downloadManager_cleanUp()
{
this._dms.cleanUp();
},
/**
* Cancel any active downloads, remove the files, and clean
* up the Download Manager database
*
* @param {Array of download} downloads
* Downloaded files which should be deleted (optional)
*/
cleanAll : function downloadManager_cleanAll(downloads) {
// Cancel any active downloads
this.cancelActiveDownloads();
// If no downloads have been specified retrieve the list from the database
if (downloads === undefined || downloads.length == 0)
downloads = this.getAllDownloads();
else
downloads = downloads.concat(this.getAllDownloads());
// Delete all files referred to in the Download Manager
this.deleteDownloadedFiles(downloads);
// Clean any entries from the Download Manager database
this.cleanUp();
},
/**
* Close the download manager
*
* @param {boolean} force
* Force the closing of the DM window
*/
close : function downloadManager_close(force) {
var windowCount = mozmill.utils.getWindows().length;
if (this._controller) {
// Check if we should force the closing of the DM window
if (force) {
this._controller.window.close();
} else {
var cmdKey = utils.getEntity(this.getDtds(), "cmd.close.commandKey");
this._controller.keypress(null, cmdKey, {accelKey: true});
}
this._controller.waitForEval("subject.getWindows().length == " + (windowCount - 1),
gTimeout, 100, mozmill.utils);
this._controller = null;
}
},
/**
* Delete all downloads from the local drive
*
* @param {download} downloads
* List of downloaded files
*/
deleteDownloadedFiles : function downloadManager_deleteDownloadedFiles(downloads) {
downloads.forEach(function(download) {
try {
var file = getLocalFileFromNativePathOrUrl(download.target);
file.remove(false);
} catch (ex) {
}
});
},
/**
* Get the list of all downloaded files in the database
*
* @returns List of downloads
* @type {Array of download}
*/
getAllDownloads : function downloadManager_getAllDownloads() {
var dbConn = this._dms.DBConnection;
var stmt = null;
if (dbConn.schemaVersion < 3)
return new Array();
// Run a SQL query and iterate through all results which have been found
var downloads = [];
stmt = dbConn.createStatement("SELECT * FROM moz_downloads");
while (stmt.executeStep()) {
downloads.push({
id: stmt.row.id, name: stmt.row.name, target: stmt.row.target,
tempPath: stmt.row.tempPath, startTime: stmt.row.startTime,
endTime: stmt.row.endTime, state: stmt.row.state,
referrer: stmt.row.referrer, entityID: stmt.row.entityID,
currBytes: stmt.row.currBytes, maxBytes: stmt.row.maxBytes,
mimeType : stmt.row.mimeType, autoResume: stmt.row.autoResume,
preferredApplication: stmt.row.preferredApplication,
preferredAction: stmt.row.preferredAction
});
};
stmt.reset();
return downloads;
},
/**
* Gets the download state of the given download
*
* @param {ElemBase} download
* Download which state should be checked
*/
getDownloadState : function downloadManager_getDownloadState(download) {
return download.getNode().getAttribute('state');
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function downloadManager_getDtds() {
var dtds = ["chrome://browser/locale/browser.dtd",
"chrome://mozapps/locale/downloads/downloads.dtd"];
return dtds;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type {ElemBase}
*/
getElement : function downloadManager_getElement(spec) {
var elem = null;
switch(spec.type) {
/**
* subtype: subtype of property to match
* value: value of property to match
*/
case "download":
// Use a temporary lookup to get the download item
var download = new elementslib.Lookup(this._controller.window.document,
'/id("downloadManager")/id("downloadView")/' +
'{"' + spec.subtype + '":"' + spec.value + '"}');
this._controller.waitForElement(download, gTimeout);
// Use its download id to construct the real lookup expression
elem = new elementslib.Lookup(this._controller.window.document,
'/id("downloadManager")/id("downloadView")/' +
'id("' + download.getNode().getAttribute('id') + '")');
break;
/**
* subtype: Identifier of the specified download button (cancel, pause, resume, retry)
* value: Entry (download) of the download list
*/
case "download_button":
// XXX: Bug 555347 - There are outstanding events to process
this._controller.sleep(0);
elem = new elementslib.Lookup(this._controller.window.document, spec.value.expression +
'/anon({"flex":"1"})/[1]/[1]/{"cmd":"cmd_' + spec.subtype + '"}');
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
},
/**
* Open the Download Manager
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
* @param {boolean} shortcut
* If true the keyboard shortcut is used
*/
open : function downloadManager_open(controller, shortcut) {
if (shortcut) {
if (mozmill.isLinux) {
var cmdKey = utils.getEntity(this.getDtds(), "downloadsUnix.commandkey");
controller.keypress(null, cmdKey, {ctrlKey: true, shiftKey: true});
} else {
var cmdKey = utils.getEntity(this.getDtds(), "downloads.commandkey");
controller.keypress(null, cmdKey, {accelKey: true});
}
} else {
controller.click(new elementslib.Elem(controller.menus["tools-menu"].menu_openDownloads));
}
controller.sleep(500);
this.waitForOpened(controller);
},
/**
* Wait for the given download state
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
* @param {downloadState} state
* Expected state of the download
* @param {number} timeout
* Timeout for waiting for the download state (optional)
*/
waitForDownloadState : function downloadManager_waitForDownloadState(download, state, timeout) {
this._controller.waitForEval("subject.manager.getDownloadState(subject.download) == subject.state", timeout, 100,
{manager: this, download: download, state: state});
},
/**
* Wait until the Download Manager has been opened
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
*/
waitForOpened : function downloadManager_waitForOpened(controller) {
this._controller = utils.handleWindow("type", "Download:Manager",
undefined, false);
}
};
/**
* Download the file of unkown type from the given location by saving it
* automatically to disk
*
* @param {MozMillController} controller
* MozMillController of the browser window
* @param {string} url
* URL of the file which has to be downloaded
*/
var downloadFileOfUnknownType = function(controller, url) {
controller.open(url);
// Wait until the unknown content type dialog has been opened
controller.waitForEval("subject.getMostRecentWindow('').document.documentElement.id == 'unknownContentType'",
gTimeout, 100, mozmill.wm);
utils.handleWindow("type", "", function (controller) {
// Select to save the file directly
var saveFile = new elementslib.ID(controller.window.document, "save");
controller.waitThenClick(saveFile, gTimeout);
controller.waitForEval("subject.selected == true", gTimeout, 100,
saveFile.getNode());
// Wait until the OK button has been enabled and click on it
var button = new elementslib.Lookup(controller.window.document,
'/id("unknownContentType")/anon({"anonid":"buttons"})/{"dlgtype":"accept"}');
controller.waitForElement(button, gTimeout);
controller.waitForEval("subject.okButton.hasAttribute('disabled') == false", gTimeout, 100,
{okButton: button.getNode()});
controller.click(button);
});
}
/**
* Get a local file from a native path or URL
*
* @param {string} aPathOrUrl
* Native path or URL of the file
* @see http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/downloads/content/downloads.js#1309
*/
function getLocalFileFromNativePathOrUrl(aPathOrUrl) {
if (aPathOrUrl.substring(0,7) == "file://") {
// if this is a URL, get the file from that
let ioSvc = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
// XXX it's possible that using a null char-set here is bad
const fileUrl = ioSvc.newURI(aPathOrUrl, null, null)
.QueryInterface(Ci.nsIFileURL);
return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
} else {
// if it's a pathname, create the nsILocalFile directly
var f = new nsLocalFile(aPathOrUrl);
return f;
}
}
// Export of variables
exports.downloadState = downloadState;
// Export of functions
exports.downloadFileOfUnknownType = downloadFileOfUnknownType;
exports.getLocalFileFromNativePathOrUrl = getLocalFileFromNativePathOrUrl;
// Export of classes
exports.downloadManager = downloadManager;

View File

@@ -1,307 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Adrian Kalla <akalla@aviary.pl>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Include required modules
var domUtils = require("dom-utils");
var screenshot = require("screenshot");
var utils = require("utils");
const jumlib = {};
Components.utils.import("resource://mozmill/modules/jum.js", jumlib);
/**
* Callback function for parsing the results of testing for duplicated
* access keys.
*
* This function processes the access keys found in one access keys scope
* looking for access keys that are listed more than one time.
* At the end, it calls the screenshot.create to create a screenshot with the
* elements containing the broken access keys highlighted.
*
* @param {array of array of object} accessKeysSet
* @param {MozmillController} controller
*/
function checkAccessKeysResults(controller, accessKeysSet) {
// Sort the access keys to have them in a A->Z order
var accessKeysList = accessKeysSet.sort();
// List of access keys
var aKeysList = [];
// List of values to identify the access keys
var valueList = [];
// List of rectangles of nodes containing access keys
var rects = [];
// List of rectangles of nodes with broken access keys
var badRects = [];
// Makes lists of all access keys and the values the access keys are in
for (var i = 0; i < accessKeysList.length; i++) {
var accessKey = accessKeysList[i][0];
var node = accessKeysList[i][1];
// Set the id and label to be shown in the console
var id = node.id || "(id is undefined)";
var label = node.label || "(label is undefined)";
var box = node.boxObject;
var innerIds = [];
var innerRects = [];
// if the access key is already in our list, take it out to replace it
// later
if (accessKey == aKeysList[aKeysList.length-1]) {
innerIds = valueList.pop();
innerRects = rects.pop();
} else {
aKeysList.push([accessKey]);
}
innerIds.push("[id: " + id + ", label: " + label + "]");
valueList.push(innerIds);
innerRects.push([box.x, box.y, box.width, box.height]);
rects.push(innerRects);
}
// Go through all access keys and find the duplicated ones
for (var i = 0; i < valueList.length; i++) {
// Only access keys contained in more than one node are the ones we are
// looking for
if (valueList[i].length > 1) {
for (var j = 0; j < rects[i].length; j++) {
badRects.push(rects[i][j]);
}
jumlib.assert(false, 'accessKey: ' + aKeysList[i] +
' found in string\'s: ' + valueList[i].join(", "));
}
}
// If we have found broken access keys, make a screenshot
if (badRects.length > 0) {
screenshot.create(controller, badRects);
}
}
/**
* Callback function for testing for cropped elements.
*
* Checks if the XUL boxObject has screen coordinates outside of
* the screen coordinates of its parent. If there's no parent, return.
*
* @param {node} child
* @returns List of boxes that can be highlighted on a screenshot
* @type {array of array of int}
*/
function checkDimensions(child) {
if (!child.boxObject)
return [];
var childBox = child.boxObject;
var parent = childBox.parentBox;
// toplevel element or hidden elements, like script tags
if (!parent || parent == child.element || !parent.boxObject) {
return [];
}
var parentBox = parent.boxObject;
var badRects = [];
// check width
if (childBox.height && childBox.screenX < parentBox.screenX) {
badRects.push([childBox.x, childBox.y, parentBox.x - childBox.x,
childBox.height]);
jumlib.assert(false, 'Node is cut off at the left: ' +
_reportNode(child) + '. Parent node: ' + _reportNode(parent));
}
if (childBox.height && childBox.screenX + childBox.width >
parentBox.screenX + parentBox.width) {
badRects.push([parentBox.x + parentBox.width, childBox.y,
childBox.x + childBox.width - parentBox.x - parentBox.width,
childBox.height]);
jumlib.assert(false, 'Node is cut off at the right: ' +
_reportNode(child) + '. Parent node: ' + _reportNode(parent));
}
// check height
// We don't want to test menupopup's, as they always report the full height
// of all items in the popup
if (child.nodeName != 'menupopup' && parent.nodeName != 'menupopup') {
if (childBox.width && childBox.screenY < parentBox.screenY) {
badRects.push([childBox.x, childBox.y, parentBox.y - childBox.y,
childBox.width]);
jumlib.assert(false, 'Node is cut off at the top: ' +
_reportNode(child) + '. Parent node: ' + _reportNode(parent));
}
if (childBox.width && childBox.screenY + childBox.height >
parentBox.screenY + parentBox.height) {
badRects.push([childBox.x, parentBox.y + parentBox.height,
childBox.width,
childBox.y + childBox.height - parentBox.y - parentBox.height]);
jumlib.assert(false, 'Node is cut off at the bottom: ' +
_reportNode(child) + '. Parent node: ' + _reportNode(parent));
}
}
return badRects;
}
/**
* Filters out nodes which should not be tested because they are not in the
* current access key scope.
*
* @param {node} node
* @returns Filter status of the given node
* @type {array of array of int}
*/
function filterAccessKeys(node) {
// Menus will need a separate filter set
var notAllowedLocalNames = ["menu", "menubar", "menupopup", "popupset"];
if (!node.disabled && !node.collapsed && !node.hidden &&
notAllowedLocalNames.indexOf(node.localName) == -1) {
// Code specific to the preferences panes to reject out not visible nodes
// in the panes.
if (node.parentNode && (node.parentNode.localName == "prefwindow" &&
node.parentNode.currentPane.id != node.id) ||
((node.parentNode.localName == "tabpanels" ||
node.parentNode.localName == "deck") &&
node.parentNode.selectedPanel.id != node.id)) {
return domUtils.DOMWalker.FILTER_REJECT;
// end of the specific code
} else if (node.accessKey) {
return domUtils.DOMWalker.FILTER_ACCEPT;
} else {
return domUtils.DOMWalker.FILTER_SKIP;
}
} else {
// we don't want to test not visible elements
return domUtils.DOMWalker.FILTER_REJECT;
}
}
/**
* Filters out nodes which should not be tested because they are not visible
*
* @param {node} node
* @returns Filter status of the given node
* @type {array of array of int}
*/
function filterCroppedNodes(node) {
if (!node.boxObject) {
return domUtils.DOMWalker.FILTER_SKIP;
} else {
if (!node.disabled && !node.collapsed && !node.hidden) {
// Code specific to the preferences panes to reject out not visible nodes
// in the panes.
if (node.parentNode && (node.parentNode.localName == "prefwindow" &&
node.parentNode.currentPane.id != node.id) ||
((node.parentNode.localName == "tabpanels" ||
node.parentNode.localName == "deck") &&
node.parentNode.selectedPanel.id != node.id)) {
return domUtils.DOMWalker.FILTER_REJECT;
// end of the specific code
} else {
return domUtils.DOMWalker.FILTER_ACCEPT;
}
} else {
// we don't want to test not visible elements
return domUtils.DOMWalker.FILTER_REJECT;
}
}
}
/**
* Callback function for testing access keys. To be used with the DOMWalker.
*
* It packs a submitted node and its access key into a double array
*
* @param {node} node Node containing the access key
* @returns lower-cased access key and its node in a nested array
* @type {array of array}
*/
function prepareAccessKey(node) {
return [[node.accessKey.toLowerCase(), node]];
}
/**
* Callback function for parsing the results of testing for cropped elements.
*
* This function calls the screenshot.create method if there is at least one
* box.
*
* @param {array of array of int} boxes
* @param {MozmillController} controller
*/
function processDimensionsResults(controller, boxes) {
if (boxes && boxes.length > 0) {
screenshot.create(controller, boxes);
}
}
/**
* Tries to return a useful string identificator of the given node
*
* @param {node} node
* @returns Identificator of the node
* @type {String}
*/
function _reportNode(node) {
if (node.id) {
return "id: " + node.id;
} else if (node.label) {
return "label: " + node.label;
} else if (node.value) {
return "value: " + node.value;
} else if (node.hasAttributes()) {
var attrs = "node attributes: ";
for (var i = node.attributes.length - 1; i >= 0; i--) {
attrs += node.attributes[i].name + "->" + node.attributes[i].value + ";";
}
return attrs;
} else {
return "anonymous node";
}
}
// Export of functions
exports.checkAccessKeysResults = checkAccessKeysResults;
exports.checkDimensions = checkDimensions;
exports.filterAccessKeys = filterAccessKeys;
exports.filterCroppedNodes = filterCroppedNodes;
exports.prepareAccessKey = prepareAccessKey;
exports.processDimensionsResults = processDimensionsResults;

View File

@@ -1,236 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert <ctalbert@mozilla.com>
* Henrik Skupin <hskupin@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Include required modules
var domUtils = require("dom-utils");
const TIMEOUT_MODAL_DIALOG = 5000;
const DELAY_CHECK = 100;
/**
* Observer object to find the modal dialog spawned by a controller
*
* @constructor
* @class Observer used to find a modal dialog
*
* @param {object} aOpener
* Window which is the opener of the modal dialog
* @param {function} aCallback
* The callback handler to use to interact with the modal dialog
*/
function mdObserver(aOpener, aCallback) {
this._opener = aOpener;
this._callback = aCallback;
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
}
mdObserver.prototype = {
/**
* Set our default values for our internal properties
*/
_opener : null,
_callback: null,
_timer: null,
exception: null,
finished: false,
/**
* Check if the modal dialog has been opened
*
* @returns {object} The modal dialog window found, or null.
*/
findWindow : function mdObserver_findWindow() {
// If a window has been opened from content, it has to be unwrapped.
var window = domUtils.unwrapNode(mozmill.wm.getMostRecentWindow(''));
// Get the WebBrowserChrome and check if it's a modal window
var chrome = window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebBrowserChrome);
if (!chrome.isWindowModal()) {
return null;
}
// Opening a modal dialog from a modal dialog would fail, if we wouldn't
// check for the opener of the modal dialog
var found = false;
if (window.opener) {
// XXX Bug 614757 - an already unwrapped node returns a wrapped node
var opener = domUtils.unwrapNode(window.opener);
found = (mozmill.utils.getChromeWindow(opener) == this._opener);
}
else {
// Also note that it could happen that dialogs don't have an opener
// (i.e. clear recent history). In such a case make sure that the most
// recent window is not the passed in reference opener
found = (window != this._opener);
}
return (found ? window : null);
},
/**
* Called by the timer in the given interval to check if the modal dialog has
* been opened. Once it has been found the callback gets executed
*
* @param {object} aSubject Not used.
* @param {string} aTopic Not used.
* @param {string} aData Not used.
*/
observe : function mdObserver_observe(aSubject, aTopic, aData) {
// Once the window has been found and loaded we can execute the callback
var window = this.findWindow();
if (window && ("documentLoaded" in window)) {
try {
this._callback(new mozmill.controller.MozMillController(window));
}
catch (ex) {
// Store the exception, so it can be forwarded if a modal dialog has
// been opened by another modal dialog
this.exception = ex;
}
if (window) {
window.close();
}
this.finished = true;
this.stop();
}
else {
// otherwise try again in a bit
this._timer.init(this, DELAY_CHECK, Ci.nsITimer.TYPE_ONE_SHOT);
}
},
/**
* Stop the timer which checks for new modal dialogs
*/
stop : function mdObserver_stop() {
delete this._timer;
}
};
/**
* Creates a new instance of modalDialog.
*
* @constructor
* @class Handler for modal dialogs
*
* @param {object} aWindow [optional - default: null]
* Window which is the opener of the modal dialog
*/
function modalDialog(aWindow) {
this._window = aWindow || null;
}
modalDialog.prototype = {
/**
* Simply checks if the modal dialog has been processed
*
* @returns {boolean} True, if the dialog has been processed
*/
get finished() {
return (!this._observer || this._observer.finished);
},
/**
* Start timer to wait for the modal dialog.
*
* @param {function} aCallback
* The callback handler to use to interact with the modal dialog
*/
start : function modalDialog_start(aCallback) {
if (!aCallback)
throw new Error(arguments.callee.name + ": Callback not specified.");
this._observer = new mdObserver(this._window, aCallback);
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._timer.init(this._observer, DELAY_CHECK, Ci.nsITimer.TYPE_ONE_SHOT);
},
/**
* Stop the timer which checks for new modal dialogs
*/
stop : function modalDialog_stop() {
delete this._timer;
if (this._observer) {
this._observer.stop();
this._observer = null;
}
},
/**
* Wait until the modal dialog has been processed.
*
* @param {Number} aTimeout (optional - default 5s)
* Duration to wait
*/
waitForDialog : function modalDialog_waitForDialog(aTimeout) {
var timeout = aTimeout || TIMEOUT_MODAL_DIALOG;
if (!this._observer) {
return;
}
try {
mozmill.utils.waitFor(function () {
return this.finished;
}, "Modal dialog has been found and processed", timeout, undefined, this);
// Forward the raised exception so we can detect failures in modal dialogs
if (this._observer.exception) {
throw this._observer.exception;
}
}
finally {
this.stop();
}
}
}
// Export of classes
exports.modalDialog = modalDialog;

View File

@@ -1,208 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Geo Mealer <gmealer@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Paths for mapped memory and allocated memory, respectively. Use as
// keys to access the appropriate memory reporters.
const PATH_MAPPED = "malloc/mapped";
const PATH_ALLOCATED = "malloc/allocated";
// Returning this as a numeric constant to simplify memory calculations
// Neither allocated nor mapped should be 0 in real life.
const MEMORY_UNAVAILABLE = "0";
// INITIALIZE MEMORY REPORTERS
// gMemReporters will be a dictionary, key=path and val=reporter
// See initMemReporters() for how it's used.
var gMemReporters = {};
/**
* Initialize the static memory reporters
*
* Called during module initialization, below.
* See also aboutMemory.js in Firefox code
*/
function initMemReporters() {
var memMgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
// Grab all the memory reporters, load into gMemReporters as a dictionary
var e = memMgr.enumerateReporters();
while (e.hasMoreElements()) {
var memReporter = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
gMemReporters[memReporter.path] = memReporter;
}
}
initMemReporters();
/**
* PERFORMANCE TRACER
*
* Keeps a trace log of both actions and performance statistics
* throughout a test run.
*
* Performance stats currently include mapped and allocated memory.
* More stats will be added as methods to read them are discovered.
*
* Usage:
* Before test, create a new PerfTracer named after the test.
* Ex: var perf = new performance.PerfTracer("MyTestFunc");
*
* During test, after notable actions call PerfTracer.addCheckpoint(label)
* Ex: perf.addCheckpoint("Opened preferences dialog");
*
* After test, call PerfTracer.finish()
* Ex: perf.finish();
*/
/**
* PerfTracer constructor
*
* @param {string} name
* Name of the tracer, currently used in the output title
*/
function PerfTracer(name) {
if (!name) {
throw new Error(arguments.callee.name + ": name not supplied.");
}
this.clearLog();
this._name = name;
}
PerfTracer.prototype = {
// UTILITY METHODS
/**
* Format a single result for printing
*
* @param {object} result
* Result as created by addCheckpoint()
* Elements: timestamp {Date} - date/time
* allocated {number} - allocated memory
* mapped {number} - mapped memory
* label {string} - label for result
*
* @returns Result string formatted for output
* @type {string}
*/
_formatResult : function PerfTracer_formatResult(result) {
var resultString = result.timestamp.toUTCString() + " | " +
result.allocated + " | " +
result.mapped + " | " +
result.label + "\n";
return resultString;
},
// PUBLIC INTERFACE
/**
* Get a memory value from a reporter
*
* @param {string} path
* Path of memory reporter (e.g. PATH_MAPPED)
* @returns Memory value from requested reporter, MEMORY_UNAVAILABLE if
* not found
* @type {number}
*/
getMemory : function PerfTracer_getMemory(path) {
var val = MEMORY_UNAVAILABLE;
if (path in gMemReporters) {
val = gMemReporters[path].memoryUsed;
}
return val;
},
/**
* Clears the tracker log and starts over
*/
clearLog : function PerfTracer_clearLog() {
this._log = new Array();
},
/**
* Adds a checkpoint to the tracker log, with time and performance info
*
* @param {string} aLabel
* Label attached to performance results. Typically should be
* whatever the test just did.
*/
addCheckpoint : function PerfTracer_addCheckpoint(aLabel) {
var result = {
label: aLabel,
timestamp: new Date(),
mapped: this.getMemory(PATH_MAPPED),
allocated: this.getMemory(PATH_ALLOCATED)
};
this._log.push(result);
},
/**
* Prints all results to console.
* XXX: make this work with output files
*/
finish : function PerfTracer_finish() {
// Title
var title = "Performance Trace (" + this._name + ")";
// Separator
var sep = "";
for(var i = 0; i < title.length; i++) {
sep += "=";
}
dump(sep + "\n");
dump(title + "\n");
dump(sep + "\n");
// Log
for(i = 0; i < this._log.length; i++) {
dump(this._formatResult(this._log[i]));
}
}
}
// Exported constants
exports.PATH_MAPPED = PATH_MAPPED;
exports.PATH_ALLOCATED = PATH_ALLOCATED;
exports.MEMORY_UNAVAILABLE = MEMORY_UNAVAILABLE;
// Exported class
exports.PerfTracer = PerfTracer;

View File

@@ -1,192 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Geo Mealer <gmealer@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The ModalDialogAPI adds support for handling modal dialogs. It
* has to be used e.g. for alert boxes and other commonDialog instances.
*
* @version 1.0.2
*/
// Include required modules
var utils = require("utils");
const gTimeout = 5000;
// Default bookmarks.html file lives in omni.jar, get via resource URI
const BOOKMARKS_RESOURCE = "resource:///defaults/profile/bookmarks.html";
// Bookmarks can take up to ten seconds to restore
const BOOKMARKS_TIMEOUT = 10000;
// Observer topics we need to watch to know whether we're finished
const TOPIC_BOOKMARKS_RESTORE_SUCCESS = "bookmarks-restore-success";
/**
* Instance of the bookmark service to gain access to the bookmark API.
*
* @see http://mxr.mozilla.org/mozilla-central (nsINavBookmarksService.idl)
*/
var bookmarksService = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
/**
* Instance of the history service to gain access to the history API.
*
* @see http://mxr.mozilla.org/mozilla-central (nsINavHistoryService.idl)
*/
var historyService = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
/**
* Instance of the livemark service to gain access to the livemark API
*
* @see http://mxr.mozilla.org/mozilla-central (nsILivemarkService.idl)
*/
var livemarkService = Cc["@mozilla.org/browser/livemark-service;2"].
getService(Ci.nsILivemarkService);
/**
* Instance of the browser history interface to gain access to
* browser-specific history API
*
* @see http://mxr.mozilla.org/mozilla-central (nsIBrowserHistory.idl)
*/
var browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsIBrowserHistory);
/**
* Instance of the observer service to gain access to the observer API
*
* @see http://mxr.mozilla.org/mozilla-central (nsIObserverService.idl)
*/
var observerService = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
/**
* Check if an URI is bookmarked within the specified folder
*
* @param (nsIURI) uri
* URI of the bookmark
* @param {String} folderId
* Folder in which the search has to take place
* @return Returns if the URI exists in the given folder
* @type Boolean
*/
function isBookmarkInFolder(uri, folderId)
{
var ids = bookmarksService.getBookmarkIdsForURI(uri, {});
for (let i = 0; i < ids.length; i++) {
if (bookmarksService.getFolderIdForItem(ids[i]) == folderId)
return true;
}
return false;
}
/**
* Restore the default bookmarks for the current profile
*/
function restoreDefaultBookmarks() {
// Set up the observer -- we're only checking for success here, so we'll simply
// time out and throw on failure. It makes the code much clearer than handling
// finished state and success state separately.
var importSuccessful = false;
var importObserver = {
observe: function (aSubject, aTopic, aData) {
if (aTopic == TOPIC_BOOKMARKS_RESTORE_SUCCESS) {
importSuccessful = true;
}
}
}
observerService.addObserver(importObserver, TOPIC_BOOKMARKS_RESTORE_SUCCESS, false);
try {
// Fire off the import
var bookmarksURI = utils.createURI(BOOKMARKS_RESOURCE);
var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].
getService(Ci.nsIPlacesImportExportService);
importer.importHTMLFromURI(bookmarksURI, true);
// Wait for it to be finished--the observer above will flip this flag
mozmill.utils.waitFor(function () {
return importSuccessful;
}, "Default bookmarks have finished importing", BOOKMARKS_TIMEOUT);
}
finally {
// Whatever happens, remove the observer afterwards
observerService.removeObserver(importObserver, TOPIC_BOOKMARKS_RESTORE_SUCCESS);
}
}
/**
* Synchronous wrapper around browserHistory.removeAllPages()
* Removes history and blocks until done
*/
function removeAllHistory() {
const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
// Create flag visible to both the eval and the observer object
var finishedFlag = {
state: false
}
// Set up an observer so we get notified when remove completes
let observer = {
observe: function(aSubject, aTopic, aData) {
observerService.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
finishedFlag.state = true;
}
}
observerService.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);
// Remove the pages, then block until we're done or until timeout is reached
browserHistory.removeAllPages();
mozmill.controller.waitForEval("subject.state == true", gTimeout, 100, finishedFlag);
}
// Export of variables
exports.bookmarksService = bookmarksService;
exports.historyService = historyService;
exports.livemarkService = livemarkService;
exports.browserHistory = browserHistory;
// Export of functions
exports.isBookmarkInFolder = isBookmarkInFolder;
exports.restoreDefaultBookmarks = restoreDefaultBookmarks;
exports.removeAllHistory = removeAllHistory;

View File

@@ -1,384 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Clint Talbert <ctalbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The PrefsAPI adds support for preferences related functions. It gives access
* to the preferences system and allows to handle the preferences dialog
*
* @version 1.0.1
*/
// Include required modules
var modalDialog = require("modal-dialog");
var utils = require("utils");
const gTimeout = 5000;
// Preferences dialog element templates
const PREF_DIALOG_BUTTONS = '/{"type":"prefwindow"}/anon({"anonid":"dlg-buttons"})';
const PREF_DIALOG_DECK = '/{"type":"prefwindow"}/anon({"class":"paneDeckContainer"})/anon({"anonid":"paneDeck"})';
const PREF_DIALOG_SELECTOR = '/{"type":"prefwindow"}/anon({"orient":"vertical"})/anon({"anonid":"selector"})';
/**
* Constructor
*
* @param {MozMillController} controller
* MozMill controller of the browser window to operate on.
*/
function preferencesDialog(controller) {
this._controller = controller;
}
/**
* Preferences dialog object to simplify the access to this dialog
*/
preferencesDialog.prototype = {
/**
* Returns the MozMill controller
*
* @returns Mozmill controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* Retrieve the currently selected panel
*
* @returns The panel element
* @type {ElemBase}
*/
get selectedPane() {
return this.getElement({type: "deck_pane"});
},
/**
* Get the given pane id
*/
get paneId() {
// Check if the selector and the pane are consistent
var selector = this.getElement({type: "selector"});
this._controller.waitForEval("subject.selector.getAttribute('pane') == subject.dlg.selectedPane.getNode().id", gTimeout, 100,
{selector: selector.getNode().selectedItem, dlg: this});
return this.selectedPane.getNode().id;
},
/**
* Set the given pane by id
*
* @param {string} id of the pane
*/
set paneId(id) {
var button = this.getElement({type: "selector_button", value: id});
this._controller.waitThenClick(button, gTimeout);
// Check if the correct selector is selected
var selector = this.getElement({type: "selector"});
this._controller.waitForEval("subject.selector.getAttribute('pane') == subject.newPane", gTimeout, 100,
{selector: selector.getNode().selectedItem, newPane: id});
return this.paneId;
},
/**
* Close the preferences dialog
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
* @param {boolean} saveChanges
* (Optional) If true the OK button is clicked on Windows which saves
* the changes. On OS X and Linux changes are applied immediately
*/
close : function preferencesDialog_close(saveChanges) {
saveChanges = (saveChanges == undefined) ? false : saveChanges;
if (mozmill.isWindows) {
var button = this.getElement({type: "button", subtype: (saveChanges ? "accept" : "cancel")});
this._controller.click(button);
} else {
this._controller.keypress(null, 'w', {accelKey: true});
}
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function preferencesDialog_getDtds() {
return null;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type {ElemBase}
*/
getElement : function aboutSessionRestore_getElement(spec) {
var elem = null;
switch(spec.type) {
case "button":
elem = new elementslib.Lookup(this._controller.window.document, PREF_DIALOG_BUTTONS +
'/{"dlgtype":"' + spec.subtype + '"}');
break;
case "deck":
elem = new elementslib.Lookup(this._controller.window.document, PREF_DIALOG_DECK);
break;
case "deck_pane":
var deck = this.getElement({type: "deck"}).getNode();
// XXX: Bug 390724 - selectedPane is broken. So iterate through all elements
var panel = deck.boxObject.firstChild;
for (var ii = 0; ii < deck.selectedIndex; ii++)
panel = panel.nextSibling;
elem = new elementslib.Elem(panel);
break;
case "selector":
elem = new elementslib.Lookup(this._controller.window.document, PREF_DIALOG_SELECTOR);
break;
case "selector_button":
elem = new elementslib.Lookup(this._controller.window.document, PREF_DIALOG_SELECTOR +
'/{"pane":"' + spec.value + '"}');
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
}
};
/**
* Preferences object to simplify the access to the nsIPrefBranch.
*/
var preferences = {
_prefService : Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService),
/**
* Use branch to access low level functions of nsIPrefBranch
*
* @return Instance of the preferences branch
* @type nsIPrefBranch
*/
get prefBranch() {
return this._prefService.QueryInterface(Ci.nsIPrefBranch);
},
/**
* Use defaultPrefBranch to access low level functions of the default branch
*
* @return Instance of the preferences branch
* @type nsIPrefBranch
*/
get defaultPrefBranch() {
return this._prefService.getDefaultBranch("");
},
/**
* Use prefService to access low level functions of nsIPrefService
*
* @return Instance of the pref service
* @type nsIPrefService
*/
get prefService() {
return this._prefService;
},
/**
* Clear a user set preference
*
* @param {string} prefName
* The user-set preference to clear
* @return False if the preference had the default value
* @type boolean
**/
clearUserPref : function preferences_clearUserPref(prefName) {
try {
this.prefBranch.clearUserPref(prefName);
return true;
} catch (e) {
return false;
}
},
/**
* Retrieve the value of an individual preference.
*
* @param {string} prefName
* The preference to get the value of.
* @param {boolean/number/string} defaultValue
* The default value if preference cannot be found.
* @param {boolean/number/string} defaultBranch
* If true the value will be read from the default branch (optional)
* @param {string} interfaceType
* Interface to use for the complex value (optional)
* (nsILocalFile, nsISupportsString, nsIPrefLocalizedString)
*
* @return The value of the requested preference
* @type boolean/int/string/complex
*/
getPref : function preferences_getPref(prefName, defaultValue, defaultBranch,
interfaceType) {
try {
branch = defaultBranch ? this.defaultPrefBranch : this.prefBranch;
// If interfaceType has been set, handle it differently
if (interfaceType != undefined) {
return branch.getComplexValue(prefName, interfaceType);
}
switch (typeof defaultValue) {
case ('boolean'):
return branch.getBoolPref(prefName);
case ('string'):
return branch.getCharPref(prefName);
case ('number'):
return branch.getIntPref(prefName);
default:
return undefined;
}
} catch(e) {
return defaultValue;
}
},
/**
* Set the value of an individual preference.
*
* @param {string} prefName
* The preference to set the value of.
* @param {boolean/number/string/complex} value
* The value to set the preference to.
* @param {string} interfaceType
* Interface to use for the complex value
* (nsILocalFile, nsISupportsString, nsIPrefLocalizedString)
*
* @return Returns if the value was successfully set.
* @type boolean
*/
setPref : function preferences_setPref(prefName, value, interfaceType) {
try {
switch (typeof value) {
case ('boolean'):
this.prefBranch.setBoolPref(prefName, value);
break;
case ('string'):
this.prefBranch.setCharPref(prefName, value);
break;
case ('number'):
this.prefBranch.setIntPref(prefName, value);
break;
default:
this.prefBranch.setComplexValue(prefName, interfaceType, value);
}
} catch(e) {
return false;
}
return true;
}
};
/**
* Open the preferences dialog and call the given handler
*
* @param {MozMillController} controller
* MozMillController which is the opener of the preferences dialog
* @param {function} callback
* The callback handler to use to interact with the preference dialog
* @param {function} launcher
* (Optional) A callback handler to launch the preference dialog
*/
function openPreferencesDialog(controller, callback, launcher) {
if (!controller)
throw new Error("No controller given for Preferences Dialog");
if (typeof callback != "function")
throw new Error("No callback given for Preferences Dialog");
if (mozmill.isWindows) {
// Preference dialog is modal on windows, set up our callback
var prefModal = new modalDialog.modalDialog(controller.window);
prefModal.start(callback);
}
// Launch the preference dialog
if (launcher) {
launcher();
} else {
mozmill.getPreferencesController();
}
if (mozmill.isWindows) {
prefModal.waitForDialog();
} else {
// Get the window type of the preferences window depending on the application
var prefWindowType = null;
switch (mozmill.Application) {
case "Thunderbird":
prefWindowType = "Mail:Preferences";
break;
default:
prefWindowType = "Browser:Preferences";
}
utils.handleWindow("type", prefWindowType, callback);
}
}
// Export of variables
exports.preferences = preferences;
// Export of functions
exports.openPreferencesDialog = openPreferencesDialog;
// Export of classes
exports.preferencesDialog = preferencesDialog;

View File

@@ -1,237 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The PrivateBrowsingAPI adds support for handling the private browsing mode.
*
* @version 1.0.0
*/
// Include required modules
var modalDialog = require("modal-dialog");
var prefs = require("prefs");
var utils = require("utils");
// Preference for confirmation dialog when entering Private Browsing mode
const PB_NO_PROMPT_PREF = 'browser.privatebrowsing.dont_prompt_on_enter';
const gTimeout = 5000;
/**
* Create a new privateBrowsing instance.
*
* @class This class adds support for the Private Browsing mode
* @param {MozMillController} controller
* MozMillController to use for the modal entry dialog
*/
function privateBrowsing(controller) {
this._controller = controller;
this._handler = null;
/**
* Menu item in the main menu to enter/leave Private Browsing mode
* @private
*/
this._pbMenuItem = new elementslib.Elem(this._controller.menus['tools-menu'].privateBrowsingItem);
this._pbTransitionItem = new elementslib.ID(this._controller.window.document, "Tools:PrivateBrowsing");
this.__defineGetter__('_pbs', function() {
delete this._pbs;
return this._pbs = Cc["@mozilla.org/privatebrowsing;1"].
getService(Ci.nsIPrivateBrowsingService);
});
}
/**
* Prototype definition of the privateBrowsing class
*/
privateBrowsing.prototype = {
/**
* Returns the controller of the current window
*
* @returns Mozmill Controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* Checks the state of the Private Browsing mode
*
* @returns Enabled state
* @type {boolean}
*/
get enabled() {
return this._pbs.privateBrowsingEnabled;
},
/**
* Sets the state of the Private Browsing mode
*
* @param {boolean} value
* New state of the Private Browsing mode
*/
set enabled(value) {
this._pbs.privateBrowsingEnabled = value;
},
/**
* Sets the callback handler for the confirmation dialog
*
* @param {function} callback
* Callback handler for the confirmation dialog
*/
set handler(callback) {
this._handler = callback;
},
/**
* Gets the enabled state of the confirmation dialog
*
* @returns Enabled state
* @type {boolean}
*/
get showPrompt() {
return !prefs.preferences.getPref(PB_NO_PROMPT_PREF, true);
},
/**
* Sets the enabled state of the confirmation dialog
*
* @param {boolean} value
* New enabled state of the confirmation dialog
*/
set showPrompt(value){
prefs.preferences.setPref(PB_NO_PROMPT_PREF, !value);
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function downloadManager_getDtds() {
var dtds = ["chrome://branding/locale/brand.dtd",
"chrome://browser/locale/browser.dtd",
"chrome://browser/locale/aboutPrivateBrowsing.dtd"];
return dtds;
},
/**
* Turn off Private Browsing mode and reset all changes
*/
reset : function privateBrowsing_reset() {
try {
this.stop(true);
} catch (ex) {
// Do a hard reset
this.enabled = false;
}
this.showPrompt = true;
},
/**
* Start the Private Browsing mode
*
* @param {boolean} useShortcut
* Use the keyboard shortcut if true otherwise the menu entry is used
*/
start: function privateBrowsing_start(useShortcut) {
var dialog = null;
if (this.enabled)
return;
if (this.showPrompt) {
dialog = new modalDialog.modalDialog(this._controller.window);
dialog.start(this._handler);
}
if (useShortcut) {
var cmdKey = utils.getEntity(this.getDtds(), "privateBrowsingCmd.commandkey");
this._controller.keypress(null, cmdKey, {accelKey: true, shiftKey: true});
} else {
this._controller.click(this._pbMenuItem);
}
if (dialog) {
dialog.waitForDialog();
}
this.waitForTransistionComplete(true);
},
/**
* Stop the Private Browsing mode
*
* @param {boolean} useShortcut
* Use the keyboard shortcut if true otherwise the menu entry is used
*/
stop: function privateBrowsing_stop(useShortcut)
{
if (!this.enabled)
return;
if (useShortcut) {
var privateBrowsingCmdKey = utils.getEntity(this.getDtds(), "privateBrowsingCmd.commandkey");
this._controller.keypress(null, privateBrowsingCmdKey, {accelKey: true, shiftKey: true});
} else {
this._controller.click(this._pbMenuItem);
}
this.waitForTransistionComplete(false);
},
/**
* Waits until the transistion into or out of the Private Browsing mode happened
*
* @param {boolean} state
* Expected target state of the Private Browsing mode
*/
waitForTransistionComplete : function privateBrowsing_waitForTransitionComplete(state) {
// We have to wait until the transition has been finished
this._controller.waitForEval("subject.hasAttribute('disabled') == false", gTimeout, 100,
this._pbTransitionItem.getNode());
this._controller.waitForEval("subject.privateBrowsing.enabled == subject.state", gTimeout, 100,
{privateBrowsing: this, state: state});
}
}
// Export of classes
exports.privateBrowsing = privateBrowsing;

View File

@@ -1,10 +0,0 @@
# Shared Modules #
Many common elements are referenced across the Mozmill tests. To make it easier
to work with these elements and execute common actions, shared modules have been
implemented. These modules contain helper classes and helper functions with a
focus on user interface. Some of these shared modules are unique to Firefox,
while others can also be used in other applications based on the Gecko platform.
For more information on Shared Modules visit:
https://developer.mozilla.org/en/Mozmill_Tests/Shared_Modules

View File

@@ -1,131 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Adrian Kalla <akalla@aviary.pl>
* Axel Hecht <axel@pike.org>
* Henrik Skupin <hskupin@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Include the required modules
var utils = require("utils");
/**
* This function creates a screenshot of the window provided in the given
* controller and highlights elements from the coordinates provided in the
* given boxes-array.
*
* @param {array of array of int} boxes
* @param {MozmillController} controller
*/
function create(controller, boxes) {
var doc = controller.window.document;
var maxWidth = doc.documentElement.boxObject.width;
var maxHeight = doc.documentElement.boxObject.height;
var rect = [];
for (var i = 0, j = boxes.length; i < j; ++i) {
rect = boxes[i];
if (rect[0] + rect[2] > maxWidth) maxWidth = rect[0] + rect[2];
if (rect[1] + rect[3] > maxHeight) maxHeight = rect[1] + rect[3];
}
var canvas = doc.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
var width = doc.documentElement.boxObject.width;
var height = doc.documentElement.boxObject.height;
canvas.width = maxWidth;
canvas.height = maxHeight;
var ctx = canvas.getContext("2d");
ctx.clearRect(0,0, canvas.width, canvas.height);
ctx.save();
ctx.drawWindow(controller.window, 0, 0, width, height, "rgb(0,0,0)");
ctx.restore();
ctx.save();
ctx.fillStyle = "rgba(255,0,0,0.4)";
for (var i = 0, j = boxes.length; i < j; ++i) {
rect = boxes[i];
ctx.fillRect(rect[0], rect[1], rect[2], rect[3]);
}
ctx.restore();
_saveCanvas(canvas);
}
/**
* Saves a given Canvas object to a file.
* The path to save the file under should be given on the command line. If not,
* it will be saved in the temporary folder of the system.
*
* @param {canvas} canvas
*/
function _saveCanvas(canvas) {
// Use the path given on the command line and saved under
// persisted.screenshotPath, if available. If not, use the path to the
// temporary folder as a fallback.
var file = null;
if ("screenshotPath" in persisted) {
file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(persisted.screenshotPath);
}
else {
file = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties).
get("TmpD", Ci.nsIFile);
}
var fileName = utils.appInfo.name + "-" +
utils.appInfo.locale + "." +
utils.appInfo.version + "." +
utils.appInfo.buildID + "." +
utils.appInfo.os + ".png";
file.append(fileName);
// if a file already exists, don't overwrite it and create a new name
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
// create a data url from the canvas and then create URIs of the source
// and targets
var io = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
var source = io.newURI(canvas.toDataURL("image/png", ""), "UTF8", null);
var target = io.newFileURI(file)
// prepare to save the canvas data
var wbPersist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].
createInstance(Ci.nsIWebBrowserPersist);
wbPersist.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
wbPersist.persistFlags |= Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
// save the canvas data to the file
wbPersist.saveURI(source, null, null, null, null, file);
}
// Export of functions
exports.create = create;

View File

@@ -1,836 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The SearchAPI adds support for search related functions like the search bar.
*/
// Include required modules
var modalDialog = require("modal-dialog");
var utils = require("utils");
var widgets = require("widgets");
const TIMEOUT = 5000;
const TIMEOUT_REQUEST_SUGGESTIONS = 750;
// Helper lookup constants for the engine manager elements
const MANAGER_BUTTONS = '/id("engineManager")/anon({"anonid":"buttons"})';
// Helper lookup constants for the search bar elements
const NAV_BAR = '/id("main-window")/id("tab-view-deck")/{"flex":"1"}' +
'/id("navigator-toolbox")/id("nav-bar")';
const SEARCH_BAR = NAV_BAR + '/id("search-container")/id("searchbar")';
const SEARCH_TEXTBOX = SEARCH_BAR + '/anon({"anonid":"searchbar-textbox"})';
const SEARCH_DROPDOWN = SEARCH_TEXTBOX + '/[0]/anon({"anonid":"searchbar-engine-button"})';
const SEARCH_POPUP = SEARCH_DROPDOWN + '/anon({"anonid":"searchbar-popup"})';
const SEARCH_INPUT = SEARCH_TEXTBOX + '/anon({"class":"autocomplete-textbox-container"})' +
'/anon({"anonid":"textbox-input-box"})' +
'/anon({"anonid":"input"})';
const SEARCH_CONTEXT = SEARCH_TEXTBOX + '/anon({"anonid":"textbox-input-box"})' +
'/anon({"anonid":"input-box-contextmenu"})';
const SEARCH_GO_BUTTON = SEARCH_TEXTBOX + '/anon({"class":"search-go-container"})' +
'/anon({"class":"search-go-button"})';
const SEARCH_AUTOCOMPLETE = '/id("main-window")/id("mainPopupSet")/id("PopupAutoComplete")';
/**
* Constructor
*
* @param {MozMillController} controller
* MozMillController of the engine manager
*/
function engineManager(controller)
{
this._controller = controller;
}
/**
* Search Manager class
*/
engineManager.prototype = {
/**
* Get the controller of the associated engine manager dialog
*
* @returns Controller of the browser window
* @type MozMillController
*/
get controller()
{
return this._controller;
},
/**
* Gets the list of search engines
*
* @returns List of engines
* @type object
*/
get engines() {
var engines = [ ];
var tree = this.getElement({type: "engine_list"}).getNode();
for (var ii = 0; ii < tree.view.rowCount; ii ++) {
engines.push({name: tree.view.getCellText(ii, tree.columns.getColumnAt(0)),
keyword: tree.view.getCellText(ii, tree.columns.getColumnAt(1))});
}
return engines;
},
/**
* Gets the name of the selected search engine
*
* @returns Name of the selected search engine
* @type string
*/
get selectedEngine() {
var treeNode = this.getElement({type: "engine_list"}).getNode();
if(this.selectedIndex != -1) {
return treeNode.view.getCellText(this.selectedIndex,
treeNode.columns.getColumnAt(0));
} else {
return null;
}
},
/**
* Select the engine with the given name
*
* @param {string} name
* Name of the search engine to select
*/
set selectedEngine(name) {
var treeNode = this.getElement({type: "engine_list"}).getNode();
for (var ii = 0; ii < treeNode.view.rowCount; ii ++) {
if (name == treeNode.view.getCellText(ii, treeNode.columns.getColumnAt(0))) {
this.selectedIndex = ii;
break;
}
}
},
/**
* Gets the index of the selected search engine
*
* @returns Index of the selected search engine
* @type number
*/
get selectedIndex() {
var tree = this.getElement({type: "engine_list"});
var treeNode = tree.getNode();
return treeNode.view.selection.currentIndex;
},
/**
* Select the engine with the given index
*
* @param {number} index
* Index of the search engine to select
*/
set selectedIndex(index) {
var tree = this.getElement({type: "engine_list"});
var treeNode = tree.getNode();
if (index < treeNode.view.rowCount) {
widgets.clickTreeCell(this._controller, tree, index, 0, {});
}
this._controller.waitForEval("subject.manager.selectedIndex == subject.newIndex", TIMEOUT, 100,
{manager: this, newIndex: index});
},
/**
* Gets the suggestions enabled state
*/
get suggestionsEnabled() {
var checkbox = this.getElement({type: "suggest"});
return checkbox.getNode().checked;
},
/**
* Sets the suggestions enabled state
*/
set suggestionsEnabled(state) {
var checkbox = this.getElement({type: "suggest"});
this._controller.check(checkbox, state);
},
/**
* Close the engine manager
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
* @param {boolean} saveChanges
* (Optional) If true the OK button is clicked otherwise Cancel
*/
close : function preferencesDialog_close(saveChanges) {
saveChanges = (saveChanges == undefined) ? false : saveChanges;
var button = this.getElement({type: "button", subtype: (saveChanges ? "accept" : "cancel")});
this._controller.click(button);
},
/**
* Edit the keyword associated to a search engine
*
* @param {string} name
* Name of the engine to remove
* @param {function} handler
* Callback function for Engine Manager
*/
editKeyword : function engineManager_editKeyword(name, handler)
{
// Select the search engine
this.selectedEngine = name;
// Setup the modal dialog handler
md = new modalDialog.modalDialog(this._controller.window);
md.start(handler);
var button = this.getElement({type: "engine_button", subtype: "edit"});
this._controller.click(button);
md.waitForDialog();
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function engineManager_getDtds() {
var dtds = ["chrome://browser/locale/engineManager.dtd"];
return dtds;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type ElemBase
*/
getElement : function engineManager_getElement(spec) {
var elem = null;
switch(spec.type) {
/**
* subtype: subtype to match
* value: value to match
*/
case "more_engines":
elem = new elementslib.ID(this._controller.window.document, "addEngines");
break;
case "button":
elem = new elementslib.Lookup(this._controller.window.document, MANAGER_BUTTONS +
'/{"dlgtype":"' + spec.subtype + '"}');
break;
case "engine_button":
switch(spec.subtype) {
case "down":
elem = new elementslib.ID(this._controller.window.document, "dn");
break;
case "edit":
elem = new elementslib.ID(this._controller.window.document, "edit");
break;
case "remove":
elem = new elementslib.ID(this._controller.window.document, "remove");
break;
case "up":
elem = new elementslib.ID(this._controller.window.document, "up");
break;
}
break;
case "engine_list":
elem = new elementslib.ID(this._controller.window.document, "engineList");
break;
case "suggest":
elem = new elementslib.ID(this._controller.window.document, "enableSuggest");
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
},
/**
* Clicks the "Get more search engines..." link
*/
getMoreSearchEngines : function engineManager_getMoreSearchEngines() {
var link = this.getElement({type: "more_engines"});
this._controller.click(link);
},
/**
* Move down the engine with the given name
*
* @param {string} name
* Name of the engine to remove
*/
moveDownEngine : function engineManager_moveDownEngine(name) {
this.selectedEngine = name;
var index = this.selectedIndex;
var button = this.getElement({type: "engine_button", subtype: "down"});
this._controller.click(button);
this._controller.waitForEval("subject.manager.selectedIndex == subject.oldIndex + 1", TIMEOUT, 100,
{manager: this, oldIndex: index});
},
/**
* Move up the engine with the given name
*
* @param {string} name
* Name of the engine to remove
*/
moveUpEngine : function engineManager_moveUpEngine(name) {
this.selectedEngine = name;
var index = this.selectedIndex;
var button = this.getElement({type: "engine_button", subtype: "up"});
this._controller.click(button);
this._controller.waitForEval("subject.manager.selectedIndex == subject.oldIndex - 1", TIMEOUT, 100,
{manager: this, oldIndex: index});
},
/**
* Remove the engine with the given name
*
* @param {string} name
* Name of the engine to remove
*/
removeEngine : function engineManager_removeEngine(name) {
this.selectedEngine = name;
var button = this.getElement({type: "engine_button", subtype: "remove"});
this._controller.click(button);
this._controller.waitForEval("subject.manager.selectedEngine != subject.removedEngine", TIMEOUT, 100,
{manager: this, removedEngine: name});
},
/**
* Restores the defaults for search engines
*/
restoreDefaults : function engineManager_restoreDefaults() {
var button = this.getElement({type: "button", subtype: "extra2"});
this._controller.click(button);
}
};
/**
* Constructor
*
* @param {MozMillController} controller
* MozMillController of the browser window to operate on
*/
function searchBar(controller)
{
this._controller = controller;
this._bss = Cc["@mozilla.org/browser/search-service;1"]
.getService(Ci.nsIBrowserSearchService);
}
/**
* Search Manager class
*/
searchBar.prototype = {
/**
* Get the controller of the associated browser window
*
* @returns Controller of the browser window
* @type MozMillController
*/
get controller()
{
return this._controller;
},
/**
* Get the names of all installed engines
*/
get engines()
{
var engines = [ ];
var popup = this.getElement({type: "searchBar_dropDownPopup"});
for (var ii = 0; ii < popup.getNode().childNodes.length; ii++) {
var entry = popup.getNode().childNodes[ii];
if (entry.className.indexOf("searchbar-engine") != -1) {
engines.push({name: entry.id,
selected: entry.selected,
tooltipText: entry.getAttribute('tooltiptext')
});
}
}
return engines;
},
/**
* Get the search engines drop down open state
*/
get enginesDropDownOpen()
{
var popup = this.getElement({type: "searchBar_dropDownPopup"});
return popup.getNode().state != "closed";
},
/**
* Set the search engines drop down open state
*/
set enginesDropDownOpen(newState)
{
if (this.enginesDropDownOpen != newState) {
var button = this.getElement({type: "searchBar_dropDown"});
this._controller.click(button);
this._controller.waitForEval("subject.searchBar.enginesDropDownOpen == subject.newState", TIMEOUT, 100,
{searchBar: this, newState: newState });
this._controller.sleep(0);
}
},
/**
* Get the names of all installable engines
*/
get installableEngines()
{
var engines = [ ];
var popup = this.getElement({type: "searchBar_dropDownPopup"});
for (var ii = 0; ii < popup.getNode().childNodes.length; ii++) {
var entry = popup.getNode().childNodes[ii];
if (entry.className.indexOf("addengine-item") != -1) {
engines.push({name: entry.getAttribute('title'),
selected: entry.selected,
tooltipText: entry.getAttribute('tooltiptext')
});
}
}
return engines;
},
/**
* Returns the currently selected search engine
*
* @return Name of the currently selected engine
* @type string
*/
get selectedEngine()
{
// Open drop down which updates the list of search engines
var state = this.enginesDropDownOpen;
this.enginesDropDownOpen = true;
var engine = this.getElement({type: "engine", subtype: "selected", value: "true"});
this._controller.waitForElement(engine, TIMEOUT);
this.enginesDropDownOpen = state;
return engine.getNode().id;
},
/**
* Select the search engine with the given name
*
* @param {string} name
* Name of the search engine to select
*/
set selectedEngine(name) {
// Open drop down and click on search engine
this.enginesDropDownOpen = true;
var engine = this.getElement({type: "engine", subtype: "id", value: name});
this._controller.waitThenClick(engine, TIMEOUT);
// Wait until the drop down has been closed
this._controller.waitForEval("subject.searchBar.enginesDropDownOpen == false", TIMEOUT, 100,
{searchBar: this});
this._controller.waitForEval("subject.searchBar.selectedEngine == subject.newEngine", TIMEOUT, 100,
{searchBar: this, newEngine: name});
},
/**
* Returns all the visible search engines (API call)
*/
get visibleEngines()
{
return this._bss.getVisibleEngines({});
},
/**
* Checks if the correct target URL has been opened for the search
*
* @param {string} searchTerm
* Text which should be checked for
*/
checkSearchResultPage : function searchBar_checkSearchResultPage(searchTerm) {
// Retrieve the URL which is used for the currently selected search engine
var targetUrl = this._bss.currentEngine.getSubmission(searchTerm, null).uri;
var currentUrl = this._controller.tabs.activeTabWindow.document.location;
var domainRegex = /[^\.]+\.([^\.]+)\..+$/gi;
var targetDomainName = targetUrl.host.replace(domainRegex, "$1");
var currentDomainName = currentUrl.host.replace(domainRegex, "$1");
this._controller.assert(function () {
return currentDomainName === targetDomainName;
}, "Current domain name matches target domain name - got '" +
currentDomainName + "', expected '" + targetDomainName + "'");
// Check if search term is listed in URL
this._controller.assert(function () {
return currentUrl.href.toLowerCase().indexOf(searchTerm.toLowerCase()) != -1;
}, "Current URL contains the search term - got '" +
currentUrl.href.toLowerCase() + "', expected '" + searchTerm.toLowerCase() + "'");
},
/**
* Clear the search field
*/
clear : function searchBar_clear()
{
var activeElement = this._controller.window.document.activeElement;
var searchInput = this.getElement({type: "searchBar_input"});
var cmdKey = utils.getEntity(this.getDtds(), "selectAllCmd.key");
this._controller.keypress(searchInput, cmdKey, {accelKey: true});
this._controller.keypress(searchInput, 'VK_DELETE', {});
if (activeElement)
activeElement.focus();
},
/**
* Focus the search bar text field
*
* @param {object} event
* Specifies the event which has to be used to focus the search bar
*/
focus : function searchBar_focus(event)
{
var input = this.getElement({type: "searchBar_input"});
switch (event.type) {
case "click":
this._controller.click(input);
break;
case "shortcut":
if (mozmill.isLinux) {
var cmdKey = utils.getEntity(this.getDtds(), "searchFocusUnix.commandkey");
} else {
var cmdKey = utils.getEntity(this.getDtds(), "searchFocus.commandkey");
}
this._controller.keypress(null, cmdKey, {accelKey: true});
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + event.type);
}
// Check if the search bar has the focus
var activeElement = this._controller.window.document.activeElement;
this._controller.assertJS("subject.isFocused == true",
{isFocused: input.getNode() == activeElement});
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function searchBar_getDtds() {
var dtds = ["chrome://browser/locale/browser.dtd"];
return dtds;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type ElemBase
*/
getElement : function searchBar_getElement(spec) {
var elem = null;
switch(spec.type) {
/**
* subtype: subtype to match
* value: value to match
*/
case "engine":
// XXX: bug 555938 - Mozmill can't fetch the element via a lookup here.
// That means we have to grab it temporarily by iterating through all childs.
var popup = this.getElement({type: "searchBar_dropDownPopup"}).getNode();
for (var ii = 0; ii < popup.childNodes.length; ii++) {
var entry = popup.childNodes[ii];
if (entry.getAttribute(spec.subtype) == spec.value) {
elem = new elementslib.Elem(entry);
break;
}
}
//elem = new elementslib.Lookup(this._controller.window.document, SEARCH_POPUP +
// '/anon({"' + spec.subtype + '":"' + spec.value + '"})');
break;
case "engine_manager":
// XXX: bug 555938 - Mozmill can't fetch the element via a lookup here.
// That means we have to grab it temporarily by iterating through all childs.
var popup = this.getElement({type: "searchBar_dropDownPopup"}).getNode();
for (var ii = popup.childNodes.length - 1; ii >= 0; ii--) {
var entry = popup.childNodes[ii];
if (entry.className == "open-engine-manager") {
elem = new elementslib.Elem(entry);
break;
}
}
//elem = new elementslib.Lookup(this._controller.window.document, SEARCH_POPUP +
// '/anon({"anonid":"open-engine-manager"})');
break;
case "searchBar":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_BAR);
break;
case "searchBar_autoCompletePopup":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_AUTOCOMPLETE);
break;
case "searchBar_contextMenu":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_CONTEXT);
break;
case "searchBar_dropDown":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_DROPDOWN);
break;
case "searchBar_dropDownPopup":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_POPUP);
break;
case "searchBar_goButton":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_GO_BUTTON);
break;
case "searchBar_input":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_INPUT);
break;
case "searchBar_suggestions":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_AUTOCOMPLETE +
'/anon({"anonid":"tree"})');
break;
case "searchBar_textBox":
elem = new elementslib.Lookup(this._controller.window.document, SEARCH_TEXTBOX);
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
},
/**
* Returns the search suggestions for the search term
*/
getSuggestions : function(searchTerm) {
var suggestions = [ ];
var popup = this.getElement({type: "searchBar_autoCompletePopup"});
var treeElem = this.getElement({type: "searchBar_suggestions"});
// XXX Bug 542990, Bug 392633
// Typing too fast can cause several issue like the suggestions not to appear.
// Lets type the letters one by one and wait for the popup or the timeout
for (var i = 0; i < searchTerm.length; i++) {
try {
this.type(searchTerm[i]);
this._controller.waitFor(function () {
return popup.getNode().state === 'open';
}, "", TIMEOUT_REQUEST_SUGGESTIONS);
}
catch (e) {
// We are not interested in handling the timeout for now
}
}
// After entering the search term the suggestions have to be visible
this._controller.assert(function () {
return popup.getNode().state === 'open';
}, "Search suggestions are visible");
this._controller.waitForElement(treeElem, TIMEOUT);
// Get all suggestions
var tree = treeElem.getNode();
this._controller.waitForEval("subject.tree.view != null", TIMEOUT, 100,
{tree: tree});
for (var i = 0; i < tree.view.rowCount; i ++) {
suggestions.push(tree.view.getCellText(i, tree.columns.getColumnAt(0)));
}
// Close auto-complete popup
this._controller.keypress(popup, "VK_ESCAPE", {});
this._controller.waitForEval("subject.popup.state == 'closed'", TIMEOUT, 100,
{popup: popup.getNode()});
return suggestions;
},
/**
* Check if a search engine is installed (API call)
*
* @param {string} name
* Name of the search engine to check
*/
isEngineInstalled : function searchBar_isEngineInstalled(name)
{
var engine = this._bss.getEngineByName(name);
return (engine != null);
},
/**
* Open the Engine Manager
*
* @param {function} handler
* Callback function for Engine Manager
*/
openEngineManager : function searchBar_openEngineManager(handler)
{
this.enginesDropDownOpen = true;
var engineManager = this.getElement({type: "engine_manager"});
// Setup the modal dialog handler
md = new modalDialog.modalDialog(this._controller.window);
md.start(handler);
// XXX: Bug 555347 - Process any outstanding events before clicking the entry
this._controller.sleep(0);
this._controller.click(engineManager);
md.waitForDialog();
this._controller.assert(function () {
return this.enginesDropDownOpen == false;
}, "The search engine drop down menu has been closed", this);
},
/**
* Remove the search engine with the given name (API call)
*
* @param {string} name
* Name of the search engine to remove
*/
removeEngine : function searchBar_removeEngine(name)
{
if (this.isEngineInstalled(name)) {
var engine = this._bss.getEngineByName(name);
this._bss.removeEngine(engine);
}
},
/**
* Restore the default set of search engines (API call)
*/
restoreDefaultEngines : function searchBar_restoreDefaults()
{
// XXX: Bug 556477 - Restore default sorting
this.openEngineManager(function(controller) {
var manager = new engineManager(controller);
// We have to do any action so the restore button gets enabled
manager.moveDownEngine(manager.engines[0].name);
manager.restoreDefaults();
manager.close(true);
});
// Update the visibility status for each engine and reset the default engine
this._bss.restoreDefaultEngines();
this._bss.currentEngine = this._bss.defaultEngine;
// Clear any entered search term
this.clear();
},
/**
* Start a search with the given search term and check if the resulting URL
* contains the search term.
*
* @param {object} data
* Object which contains the search term and the action type
*/
search : function searchBar_search(data)
{
var searchBar = this.getElement({type: "searchBar"});
this.type(data.text);
switch (data.action) {
case "returnKey":
this._controller.keypress(searchBar, 'VK_RETURN', {});
break;
case "goButton":
default:
this._controller.click(this.getElement({type: "searchBar_goButton"}));
break;
}
this._controller.waitForPageLoad();
this.checkSearchResultPage(data.text);
},
/**
* Enter a search term into the search bar
*
* @param {string} searchTerm
* Text which should be searched for
*/
type : function searchBar_type(searchTerm) {
var searchBar = this.getElement({type: "searchBar"});
this._controller.type(searchBar, searchTerm);
}
};
// Export of classes
exports.engineManager = engineManager;
exports.searchBar = searchBar;

View File

@@ -1,318 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Aaron Train <aaron.train@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The SessionStoreAPI adds support for accessing session related elements and features
*
* @version 1.0.0
*/
// Include required modules
var prefs = require("prefs");
var utils = require("utils");
var widgets = require("widgets");
// Session Store service
var sessionStoreService = Cc["@mozilla.org/browser/sessionstore;1"]
.getService(Ci.nsISessionStore);
// Preference for indicating the amount of restorable tabs
const SESSIONSTORE_MAXTABS_PREF = 'browser.sessionstore.max_tabs_undo';
const gTimeout = 5000;
/**
* Constructor
*
* @param {MozMillController} controller
* MozMill controller of the browser window to operate on.
*/
function aboutSessionRestore(controller)
{
this._controller = controller;
}
/**
* This class handles the about:sessionrestore page.
*/
aboutSessionRestore.prototype = {
/**
* Returns the MozMill controller
*
* @returns Mozmill controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* Returns the tree which contains the windows and tabs
*
* @returns Tree with windows and tabs to restore
* @type {ElemBase}
*/
get tabList() {
return this.getElement({type: "tabList"});
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function aboutSessionRestore_getDtds() {
var dtds = ["chrome://browser/locale/browser.dtd",
"chrome://browser/locale/aboutSessionRestore.dtd"];
return dtds;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type {ElemBase}
*/
getElement : function aboutSessionRestore_getElement(spec) {
var elem = null;
switch(spec.type) {
case "button_restoreSession":
elem = new elementslib.ID(this._controller.tabs.activeTab, "errorTryAgain");
break;
case "error_longDesc":
elem = new elementslib.ID(this._controller.tabs.activeTab, "errorLongDesc");
break;
case "error_pageContainer":
elem = new elementslib.ID(this._controller.tabs.activeTab, "errorPageContainer");
break;
case "error_shortDesc":
elem = new elementslib.ID(this._controller.tabs.activeTab, "errorShortDescText");
break;
case "error_title":
elem = new elementslib.ID(this._controller.tabs.activeTab, "errorTitleText");
break;
case "tabList":
elem = new elementslib.ID(this._controller.window.document, "tabList");
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
},
/**
* Returns the current restore state of the given element
*
* @param {object} element
* Element which restore state should be retrieved
* @returns True if the element should be restored
* @type {boolean}
*
*/
getRestoreState : function aboutSessionRestore_getRestoreState(element) {
var tree = this.tabList.getNode();
return tree.view.getCellValue(element.listIndex, tree.columns.getColumnAt(0));
},
/**
* Get restorable tabs under the given window
*
* @param {object} window
* Window inside the tree
* @returns List of tabs
* @type {array of object}
*/
getTabs : function aboutSessionRestore_getTabs(window) {
var tabs = [ ];
var tree = this.tabList.getNode();
// Add entries when they are tabs (no container)
var ii = window.listIndex + 1;
while (ii < tree.view.rowCount && !tree.view.isContainer(ii)) {
tabs.push({
index: tabs.length,
listIndex : ii,
restore: tree.view.getCellValue(ii, tree.columns.getColumnAt(0)),
title: tree.view.getCellText(ii, tree.columns.getColumnAt(2))
});
ii++;
}
return tabs;
},
/**
* Get restorable windows
*
* @returns List of windows
* @type {array of object}
*/
getWindows : function aboutSessionRestore_getWindows() {
var windows = [ ];
var tree = this.tabList.getNode();
for (var ii = 0; ii < tree.view.rowCount; ii++) {
if (tree.view.isContainer(ii)) {
windows.push({
index: windows.length,
listIndex : ii,
open: tree.view.isContainerOpen(ii),
restore: tree.view.getCellValue(ii, tree.columns.getColumnAt(0)),
title: tree.view.getCellText(ii, tree.columns.getColumnAt(2))
});
}
}
return windows;
},
/**
* Toggles the restore state for the element
*
* @param {object} element
* Specifies the element which restore state should be toggled
*/
toggleRestoreState : function aboutSessionRestore_toggleRestoreState(element) {
var state = this.getRestoreState(element);
widgets.clickTreeCell(this._controller, this.tabList, element.listIndex, 0, {});
this._controller.sleep(0);
this._controller.assertJS("subject.newState != subject.oldState",
{newState : this.getRestoreState(element), oldState : state});
}
}
/**
* Resets the list of recently closed tabs by setting and clearing the user preference
*/
function resetRecentlyClosedTabs()
{
prefs.preferences.setPref(SESSIONSTORE_MAXTABS_PREF, 0);
prefs.preferences.clearUserPref(SESSIONSTORE_MAXTABS_PREF);
}
/**
* Returns the number of restorable tabs for a given window
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
* @returns The number of restorable tabs in the window
*/
function getClosedTabCount(controller)
{
return sessionStoreService.getClosedTabCount(controller.window);
}
/**
* Restores the tab which has been recently closed
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
* @param {object} event
* Specifies the event to use to execute the command
*/
function undoClosedTab(controller, event)
{
var count = sessionStoreService.getClosedTabCount(controller.window);
switch (event.type) {
case "menu":
throw new Error("Menu gets build dynamically and cannot be accessed.");
break;
case "shortcut":
var cmdKey = utils.getEntity(this.getDtds(), "tabCmd.commandkey");
controller.keypress(null, cmdKey, {accelKey: true, shiftKey: true});
break;
}
if (count > 0)
controller.assertJS("subject.newTabCount < subject.oldTabCount",
{
newTabCount : sessionStoreService.getClosedTabCount(controller.window),
oldTabCount : count
});
}
/**
* Restores the window which has been recently closed
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
* @param {object} event
* Specifies the event to use to execute the command
*/
function undoClosedWindow(controller, event)
{
var count = sessionStoreService.getClosedWindowCount(controller.window);
switch (event.type) {
case "menu":
throw new Error("Menu gets build dynamically and cannot be accessed.");
break;
case "shortcut":
var cmdKey = utils.getEntity(this.getDtds(), "newNavigatorCmd.key");
controller.keypress(null, cmdKey, {accelKey: true, shiftKey: true});
break;
}
if (count > 0)
controller.assertJS("subject.newWindowCount < subject.oldWindowCount",
{
newWindowCount : sessionStoreService.getClosedWindowCount(controller.window),
oldWindowCount : count
});
}
// Export of functions
exports.getClosedTabCount = getClosedTabCount;
exports.resetRecentlyClosedTabs = resetRecentlyClosedTabs;
exports.undoClosedTab = undoClosedTab;
exports.undoClosedWindow = undoClosedWindow;
// Export of classes
exports.aboutSessionRestore = aboutSessionRestore;

View File

@@ -1,530 +0,0 @@
/* * ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* **** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The SoftwareUpdateAPI adds support for an easy access to the update process.
*/
// Include required modules
var prefs = require("prefs");
var utils = require("utils");
const gTimeoutUpdateCheck = 10000;
const gTimeoutUpdateDownload = 360000;
const PREF_DISABLED_ADDONS = "extensions.disabledAddons";
// Helper lookup constants for elements of the software update dialog
const WIZARD = '/id("updates")';
const WIZARD_BUTTONS = WIZARD + '/anon({"anonid":"Buttons"})';
const WIZARD_DECK = WIZARD + '/anon({"anonid":"Deck"})';
const WIZARD_PAGES = {
dummy: 'dummy',
checking: 'checking',
pluginUpdatesFound: 'pluginupdatesfound',
noUpdatesFound: 'noupdatesfound',
manualUpdate: 'manualUpdate',
incompatibleCheck: 'incompatibleCheck',
updatesFoundBasic: 'updatesfoundbasic',
updatesFoundBillboard: 'updatesfoundbillboard',
license: 'license',
incompatibleList: 'incompatibleList',
downloading: 'downloading',
errors: 'errors',
errorPatching: 'errorpatching',
finished: 'finished',
finishedBackground: 'finishedBackground',
installed: 'installed'
}
// On Mac there is another DOM structure used as on Windows and Linux
if (mozmill.isMac) {
var WIZARD_BUTTONS_BOX = WIZARD_BUTTONS +
'/anon({"flex":"1"})/{"class":"wizard-buttons-btm"}/';
var WIZARD_BUTTON = {
back: '{"dlgtype":"back"}',
next: '{"dlgtype":"next"}',
cancel: '{"dlgtype":"cancel"}',
finish: '{"dlgtype":"finish"}',
extra1: '{"dlgtype":"extra1"}',
extra2: '{"dlgtype":"extra2"}'
}
} else {
var WIZARD_BUTTONS_BOX = WIZARD_BUTTONS +
'/anon({"flex":"1"})/{"class":"wizard-buttons-box-2"}/';
var WIZARD_BUTTON = {
back: '{"dlgtype":"back"}',
next: 'anon({"anonid":"WizardButtonDeck"})/[1]/{"dlgtype":"next"}',
cancel: '{"dlgtype":"cancel"}',
finish: 'anon({"anonid":"WizardButtonDeck"})/[0]/{"dlgtype":"finish"}',
extra1: '{"dlgtype":"extra1"}',
extra2: '{"dlgtype":"extra2"}'
}
}
/**
* Constructor for software update class
*/
function softwareUpdate() {
this._controller = null;
this._wizard = null;
this._downloadDuration = -1;
this._aus = Cc["@mozilla.org/updates/update-service;1"].
getService(Ci.nsIApplicationUpdateService);
this._ums = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
this._vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
getService(Ci.nsIVersionComparator);
}
/**
* Class for software updates
*/
softwareUpdate.prototype = {
/**
* Returns the active update
*
* @returns The currently selected update
* @type nsIUpdate
*/
get activeUpdate() {
return this._ums.activeUpdate;
},
/**
* Check if the user has permissions to run the software update
*
* @returns Status if the user has the permissions.
* @type {boolean}
*/
get allowed() {
return this._aus.canCheckForUpdates && this._aus.canApplyUpdates;
},
/**
* Returns information of the current build version
*/
get buildInfo() {
return {
buildid : utils.appInfo.buildID,
disabled_addons : prefs.preferences.getPref(PREF_DISABLED_ADDONS, ''),
locale : utils.appInfo.locale,
user_agent : utils.appInfo.userAgent,
version : utils.appInfo.version
};
},
/**
* Returns the current update channel
*/
get channel() {
return prefs.preferences.getPref('app.update.channel', '');
},
/**
* Get the controller of the associated engine manager dialog
*
* @returns Controller of the browser window
* @type MozMillController
*/
get controller() {
return this._controller;
},
/**
* Returns the current step of the software update dialog wizard
*/
get currentPage() {
return this._wizard.getNode().getAttribute('currentpageid');
},
/**
* Returns true if the offered update is a complete update
*/
get isCompleteUpdate() {
// Throw when isCompleteUpdate is called without an update. This should
// never happen except if the test is incorrectly written.
if (!this.activeUpdate)
throw new Error(arguments.callee.name + ": isCompleteUpdate called " +
"when activeUpdate is null!");
var patchCount = this.activeUpdate.patchCount;
if ((patchCount < 1) || (patchCount > 2)) {
throw new Error("An update must have one or two patches included.");
}
// XXX: After Firefox 4 has been released and we do not have to test any
// beta release anymore uncomment out the following code
// if (this.activeUpdate.patchCount == 2) {
// var patch0URL = this.activeUpdate.getPatchAt(0).URL;
// var patch1URL = this.activeUpdate.getPatchAt(1).URL;
// Test that the update snippet created by releng doesn't have the same
// url for both patches (bug 514040).
// controller.assertJS("subject.patch0URL != subject.patch1URL",
// {patch0URL: patch0URL, patch1URL: patch1URL});
// }
return (this.activeUpdate.selectedPatch.type == "complete");
},
/**
* Returns information of the active update in the queue.
*/
get patchInfo() {
this._controller.assert(function() {
return !!this.activeUpdate;
}, "An active update is in the queue.", this);
return {
buildid : this.activeUpdate.buildID,
channel : this.channel,
is_complete : this.isCompleteUpdate,
size : this.activeUpdate.selectedPatch.size,
type : this.activeUpdate.type,
url : this.activeUpdate.selectedPatch.finalURL || "n/a",
download_duration : this._downloadDuration,
version : this.activeUpdate.version
};
},
/**
* Returns the update type (minor or major)
*
* @returns The update type
*/
get updateType() {
return this.activeUpdate.type;
},
/**
* Check if updates have been found
*/
get updatesFound() {
return this.currentPage.indexOf("updatesfound") == 0;
},
/**
* Checks if an update has been applied correctly
*
* @param {object} updateData
* All the data collected during the update process
*/
assertUpdateApplied : function softwareUpdate_assertUpdateApplied(updateData) {
// Get the information from the last update
var info = updateData.updates[updateData.updateIndex];
// The upgraded version should be identical with the version given by
// the update and we shouldn't have run a downgrade
var check = this._vc.compare(info.build_post.version, info.build_pre.version);
this._controller.assert(function () {
return check >= 0;
}, "The version number of the upgraded build is higher or equal.");
// The build id should be identical with the one from the update
this._controller.assert(function () {
return info.build_post.buildid === info.patch.buildid;
}, "The build id is equal to the build id of the update.");
// If a target build id has been given, check if it matches the updated build
info.target_buildid = updateData.targetBuildID;
if (info.target_buildid) {
this._controller.assert(function () {
return info.build_post.buildid === info.target_buildid;
}, "Target build id matches id of updated build.");
}
// An upgrade should not change the builds locale
this._controller.assert(function () {
return info.build_post.locale === info.build_pre.locale;
}, "The locale of the updated build is identical to the original locale.");
// Check that no application-wide add-ons have been disabled
this._controller.assert(function () {
return info.build_post.disabled_addons === info.build_pre.disabled_addons;
}, "No application-wide add-ons have been disabled by the update.");
},
/**
* Close the software update dialog
*/
closeDialog: function softwareUpdate_closeDialog() {
if (this._controller) {
this._controller.keypress(null, "VK_ESCAPE", {});
this._controller.sleep(500);
this._controller = null;
this._wizard = null;
}
},
/**
* Download the update of the given channel and type
* @param {string} channel
* Update channel to use
* @param {boolean} waitForFinish
* Sets if the function should wait until the download has been finished
* @param {number} timeout
* Timeout the download has to stop
*/
download : function softwareUpdate_download(channel, waitForFinish, timeout) {
waitForFinish = waitForFinish ? waitForFinish : true;
// Check that the correct channel has been set
this._controller.assert(function() {
return channel == this.channel;
}, "The current update channel is identical to the specified one.", this);
// Retrieve the timestamp, so we can measure the duration of the download
var startTime = Date.now();
// Click the next button
var next = this.getElement({type: "button", subtype: "next"});
this._controller.click(next);
// Wait for the download page - if it fails the update was already cached
try {
this.waitForWizardPage(WIZARD_PAGES.downloading);
if (waitForFinish)
this.waitforDownloadFinished(timeout);
} catch (ex) {
this.waitForWizardPage(WIZARD_PAGES.finished);
}
// Calculate the duration in ms
this._downloadDuration = Date.now() - startTime;
},
/**
* Update the update.status file and set the status to 'failed:6'
*/
forceFallback : function softwareUpdate_forceFallback() {
var dirService = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
var updateDir;
var updateStatus;
// Check the global update folder first
try {
updateDir = dirService.get("UpdRootD", Ci.nsIFile);
updateDir.append("updates");
updateDir.append("0");
updateStatus = updateDir.clone();
updateStatus.append("update.status");
} catch (ex) {
}
if (updateStatus == undefined || !updateStatus.exists()) {
updateDir = dirService.get("XCurProcD", Ci.nsIFile);
updateDir.append("updates");
updateDir.append("0");
updateStatus = updateDir.clone();
updateStatus.append("update.status");
}
var foStream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
var status = "failed: 6\n";
foStream.init(updateStatus, 0x02 | 0x08 | 0x20, -1, 0);
foStream.write(status, status.length);
foStream.close();
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function softwareUpdate_getDtds() {
var dtds = ["chrome://mozapps/locale/update/history.dtd",
"chrome://mozapps/locale/update/updates.dtd"]
return dtds;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type {ElemBase}
*/
getElement : function softwareUpdate_getElement(spec) {
var elem = null;
switch(spec.type) {
/**
* subtype: subtype to match
* value: value to match
*/
case "button":
elem = new elementslib.Lookup(this._controller.window.document,
WIZARD_BUTTONS_BOX + WIZARD_BUTTON[spec.subtype]);
break;
case "wizard":
elem = new elementslib.Lookup(this._controller.window.document, WIZARD);
break;
case "wizard_page":
elem = new elementslib.Lookup(this._controller.window.document, WIZARD_DECK +
'/id(' + spec.subtype + ')');
break;
case "download_progress":
elem = new elementslib.ID(this._controller.window.document, "downloadProgress");
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
},
/**
* Open software update dialog
*
* @param {MozMillController} browserController
* Mozmill controller of the browser window
*/
openDialog: function softwareUpdate_openDialog(browserController) {
// XXX: After Firefox 4 has been released and we do not have to test any
// beta release anymore uncomment out the following code
// With version >= 4.0b7pre the update dialog is reachable from within the
// about window now.
var appVersion = utils.appInfo.version;
if (this._vc.compare(appVersion, "4.0b7pre") >= 0) {
// XXX: We can't open the about window, otherwise a parallel download of
// the update will let us fallback to a complete one all the time
// Open the about window and check the update button
//var aboutItem = new elementslib.Elem(browserController.menus.helpMenu.aboutName);
//browserController.click(aboutItem);
//
//utils.handleWindow("type", "Browser:About", function(controller) {
// // XXX: Bug 599290 - Check for updates has been completely relocated
// // into the about window. We can't check the in-about ui yet.
// var updateButton = new elementslib.ID(controller.window.document,
// "checkForUpdatesButton");
// //controller.click(updateButton);
// controller.waitForElement(updateButton, gTimeout);
//});
// For now just call the old ui until we have support for the about window.
var updatePrompt = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
updatePrompt.checkForUpdates();
} else {
// For builds <4.0b7pre
updateItem = new elementslib.Elem(browserController.menus.helpMenu.checkForUpdates);
browserController.click(updateItem);
}
this.waitForDialogOpen(browserController);
},
/**
* Wait that check for updates has been finished
* @param {number} timeout
*/
waitForCheckFinished : function softwareUpdate_waitForCheckFinished(timeout) {
timeout = timeout ? timeout : gTimeoutUpdateCheck;
this._controller.waitFor(function() {
return this.currentPage != WIZARD_PAGES.checking;
}, "Check for updates has been completed.", timeout, null, this);
},
/**
* Wait for the software update dialog
*
* @param {MozMillController} browserController
* Mozmill controller of the browser window
*/
waitForDialogOpen : function softwareUpdate_waitForDialogOpen(browserController) {
this._controller = utils.handleWindow("type", "Update:Wizard",
undefined, false);
this._wizard = this.getElement({type: "wizard"});
this._controller.waitFor(function () {
return this.currentPage !== WIZARD_PAGES.dummy;
}, "Dummy wizard page has been made invisible - got " + this.currentPage,
undefined, undefined, this);
this._controller.window.focus();
},
/**
* Wait until the download has been finished
*
* @param {number} timeout
* Timeout the download has to stop
*/
waitforDownloadFinished: function softwareUpdate_waitForDownloadFinished(timeout) {
timeout = timeout ? timeout : gTimeoutUpdateDownload;
var progress = this.getElement({type: "download_progress"});
this._controller.waitFor(function () {
return progress.getNode().value === '100';
}, "Update has been finished downloading.", timeout);
this.waitForWizardPage(WIZARD_PAGES.finished);
},
/**
* Waits for the given page of the update dialog wizard
*/
waitForWizardPage : function softwareUpdate_waitForWizardPage(step) {
this._controller.waitFor(function () {
return this.currentPage === step;
}, "New wizard page has been selected - got " + this.currentPage +
", expected " + step, undefined, undefined, this);
}
}
// Export of variables
exports.WIZARD_PAGES = WIZARD_PAGES;
// Export of classes
exports.softwareUpdate = softwareUpdate;

View File

@@ -1,503 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Anthony Hughes <ahughes@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The TabbedBrowsingAPI adds support for accessing and interacting with tab elements
*
* @version 1.0.0
*/
// Include required modules
var utils = require("utils");
var prefs = require("prefs");
const TIMEOUT = 5000;
const PREF_TABS_ANIMATE = "browser.tabs.animate";
const TABS_VIEW = '/id("main-window")/id("tab-view-deck")/{"flex":"1"}';
const TABS_BROWSER = TABS_VIEW + '/id("browser")/id("appcontent")/id("content")';
const TABS_TOOLBAR = TABS_VIEW + '/id("navigator-toolbox")/id("TabsToolbar")';
const TABS_TABS = TABS_TOOLBAR + '/id("tabbrowser-tabs")';
const TABS_ARROW_SCROLLBOX = TABS_TABS + '/anon({"anonid":"arrowscrollbox"})';
const TABS_STRIP = TABS_ARROW_SCROLLBOX + '/anon({"anonid":"scrollbox"})/anon({"flex":"1"})';
/**
* Close all tabs and open about:blank
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
*/
function closeAllTabs(controller)
{
var browser = new tabBrowser(controller);
browser.closeAllTabs();
}
/**
* Check and return all open tabs with the specified URL
*
* @param {string} aUrl
* URL to check for
*
* @returns Array of tabs
*/
function getTabsWithURL(aUrl) {
var tabs = [ ];
var uri = utils.createURI(aUrl, null, null);
var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator);
var winEnum = wm.getEnumerator("navigator:browser");
// Iterate through all windows
while (winEnum.hasMoreElements()) {
var window = winEnum.getNext();
// Don't check windows which are about to close or don't have gBrowser set
if (window.closed || !("gBrowser" in window))
continue;
// Iterate through all tabs in the current window
var browsers = window.gBrowser.browsers;
for (var i = 0; i < browsers.length; i++) {
var browser = browsers[i];
if (browser.currentURI.equals(uri)) {
tabs.push({
controller : new mozmill.controller.MozMillController(window),
index : i
});
}
}
}
return tabs;
}
/**
* Constructor
*
* @param {MozMillController} controller
* MozMill controller of the window to operate on
*/
function tabBrowser(controller)
{
this._controller = controller;
this._tabs = this.getElement({type: "tabs"});
}
/**
* Tabbed Browser class
*/
tabBrowser.prototype = {
/**
* Returns the MozMill controller
*
* @returns Mozmill controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* Get the amount of open tabs
*
* @returns Number of tabs
* @type {number}
*/
get length() {
return this._tabs.getNode().itemCount;
},
/**
* Get the currently selected tab index
*
* @returns Index of currently selected tab
* @type {number}
*/
get selectedIndex() {
return this._tabs.getNode().selectedIndex;
},
/**
* Select the tab with the given index
*
* @param {number} index
* Index of the tab which should be selected
*/
set selectedIndex(index) {
this._controller.click(this.getTab(index));
},
/**
* Close all tabs of the window except the last one and open a blank page.
*/
closeAllTabs : function tabBrowser_closeAllTabs()
{
while (this._controller.tabs.length > 1) {
this.closeTab({type: "menu"});
}
this._controller.open("about:blank");
this._controller.waitForPageLoad();
},
/**
* Close an open tab
*
* @param {object} aEvent
* The event specifies how to close a tab
* Elements: type - Type of event (closeButton, menu, middleClick, shortcut)
* [optional - default: menu]
*/
closeTab : function tabBrowser_closeTab(aEvent) {
var event = aEvent || { };
var type = (event.type == undefined) ? "menu" : event.type;
// Disable tab closing animation for default behavior
prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
// Add event listener to wait until the tab has been closed
var self = { closed: false };
function checkTabClosed() { self.closed = true; }
this._controller.window.addEventListener("TabClose", checkTabClosed, false);
switch (type) {
case "closeButton":
var button = this.getElement({type: "tabs_tabCloseButton",
subtype: "tab", value: this.getTab()});
this._controller.click(button);
break;
case "menu":
var menuitem = new elementslib.Elem(this._controller.menus['file-menu'].menu_close);
this._controller.click(menuitem);
break;
case "middleClick":
var tab = this.getTab(event.index);
this._controller.middleClick(tab);
break;
case "shortcut":
var cmdKey = utils.getEntity(this.getDtds(), "closeCmd.key");
this._controller.keypress(null, cmdKey, {accelKey: true});
break;
default:
throw new Error(arguments.callee.name + ": Unknown event type - " + type);
}
try {
this._controller.waitForEval("subject.tab.closed == true", TIMEOUT, 100,
{tab: self});
} finally {
this._controller.window.removeEventListener("TabClose", checkTabClosed, false);
prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
}
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function tabBrowser_getDtds() {
var dtds = ["chrome://browser/locale/browser.dtd",
"chrome://browser/locale/tabbrowser.dtd",
"chrome://global/locale/global.dtd"];
return dtds;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type {ElemBase}
*/
getElement : function tabBrowser_getElement(spec) {
var document = this._controller.window.document;
var elem = null;
switch(spec.type) {
/**
* subtype: subtype to match
* value: value to match
*/
case "tabs":
elem = new elementslib.Lookup(this._controller.window.document,
TABS_TABS);
break;
case "tabs_allTabsButton":
elem = new elementslib.Lookup(this._controller.window.document,
TABS_TOOLBAR + '/id("alltabs-button")');
break;
case "tabs_allTabsPopup":
elem = new elementslib.Lookup(this._controller.window.document, TABS_TOOLBAR +
'/id("alltabs-button")/id("alltabs-popup")');
break;
case "tabs_newTabButton":
elem = new elementslib.Lookup(this._controller.window.document,
TABS_ARROW_SCROLLBOX + '/anon({"class":"tabs-newtab-button"})');
break;
case "tabs_scrollButton":
elem = new elementslib.Lookup(this._controller.window.document,
TABS_ARROW_SCROLLBOX +
'/anon({"anonid":"scrollbutton-' + spec.subtype + '"})');
break;
case "tabs_strip":
elem = new elementslib.Lookup(this._controller.window.document, TABS_STRIP);
break;
case "tabs_tab":
switch (spec.subtype) {
case "index":
elem = new elementslib.Elem(this._tabs.getNode().getItemAtIndex(spec.value));
break;
}
break;
case "tabs_tabCloseButton":
var node = document.getAnonymousElementByAttribute(
spec.value.getNode(),
"anonid",
"close-button"
);
elem = new elementslib.Elem(node);
break;
case "tabs_tabFavicon":
var node = document.getAnonymousElementByAttribute(
spec.value.getNode(),
"class",
"tab-icon-image"
);
elem = new elementslib.Elem(node);
break;
case "tabs_tabPanel":
var panelId = spec.value.getNode().getAttribute("linkedpanel");
elem = new elementslib.Lookup(this._controller.window.document, TABS_BROWSER +
'/anon({"anonid":"tabbox"})/anon({"anonid":"panelcontainer"})' +
'/{"id":"' + panelId + '"}');
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
},
/**
* Get the tab at the specified index
*
* @param {number} index
* Index of the tab
* @returns The requested tab
* @type {ElemBase}
*/
getTab : function tabBrowser_getTab(index) {
if (index === undefined)
index = this.selectedIndex;
return this.getElement({type: "tabs_tab", subtype: "index", value: index});
},
/**
* Creates the child element of the tab's notification bar
*
* @param {number} tabIndex
* (Optional) Index of the tab to check
* @param {string} elemString
* (Optional) Lookup string of the notification bar's child element
* @return The created child element
* @type {ElemBase}
*/
getTabPanelElement : function tabBrowser_getTabPanelElement(tabIndex, elemString)
{
var index = tabIndex ? tabIndex : this.selectedIndex;
var elemStr = elemString ? elemString : "";
// Get the tab panel and check if an element has to be fetched
var panel = this.getElement({type: "tabs_tabPanel", subtype: "tab", value: this.getTab(index)});
var elem = new elementslib.Lookup(this._controller.window.document, panel.expression + elemStr);
return elem;
},
/**
* Open element (link) in a new tab
*
* @param {object} aEvent
* The event specifies how to open the element in a new tab
* Elements: type - Type of event (contextMenu, middleClick)
* [optional - default: middleClick]
* target - Element to click on
* [optional - default: -]
*/
openInNewTab : function tabBrowser_openInNewTab(aEvent) {
var event = aEvent || { };
var type = (event.type == undefined) ? "middleClick" : event.type;
var target = event.target;
// Disable tab closing animation for default behavior
prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
// Add event listener to wait until the tab has been opened
var self = { opened: false };
function checkTabOpened() { self.opened = true; }
this._controller.window.addEventListener("TabOpen", checkTabOpened, false);
switch (type) {
case "contextMenu":
var contextMenuItem = new elementslib.ID(this._controller.window.document,
"context-openlinkintab");
this._controller.rightClick(target);
this._controller.click(contextMenuItem);
utils.closeContentAreaContextMenu(this._controller);
break;
case "middleClick":
this._controller.middleClick(target);
break;
default:
throw new Error(arguments.callee.name + ": Unknown event type - " + type);
}
try {
this._controller.waitForEval("subject.tab.opened == true", TIMEOUT, 100,
{tab: self});
} finally {
this._controller.window.removeEventListener("TabOpen", checkTabOpened, false);
prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
}
},
/**
* Open a new tab
*
* @param {object} aEvent
* The event specifies how to open a new tab (menu, shortcut,
* Elements: type - Type of event (menu, newTabButton, shortcut, tabStrip)
* [optional - default: menu]
*/
openTab : function tabBrowser_openTab(aEvent) {
var event = aEvent || { };
var type = (event.type == undefined) ? "menu" : event.type;
// Disable tab closing animation for default behavior
prefs.preferences.setPref(PREF_TABS_ANIMATE, false);
// Add event listener to wait until the tab has been opened
var self = { opened: false };
function checkTabOpened() { self.opened = true; }
this._controller.window.addEventListener("TabOpen", checkTabOpened, false);
switch (type) {
case "menu":
var menuitem = new elementslib.Elem(this._controller.menus['file-menu'].menu_newNavigatorTab);
this._controller.click(menuitem);
break;
case "shortcut":
var cmdKey = utils.getEntity(this.getDtds(), "tabCmd.commandkey");
this._controller.keypress(null, cmdKey, {accelKey: true});
break;
case "newTabButton":
var newTabButton = this.getElement({type: "tabs_newTabButton"});
this._controller.click(newTabButton);
break;
case "tabStrip":
var tabStrip = this.getElement({type: "tabs_strip"});
// RTL-locales need to be treated separately
if (utils.getEntity(this.getDtds(), "locale.dir") == "rtl") {
// XXX: Workaround until bug 537968 has been fixed
this._controller.click(tabStrip, 100, 3);
// Todo: Calculate the correct x position
this._controller.doubleClick(tabStrip, 100, 3);
} else {
// XXX: Workaround until bug 537968 has been fixed
this._controller.click(tabStrip, tabStrip.getNode().clientWidth - 100, 3);
// Todo: Calculate the correct x position
this._controller.doubleClick(tabStrip, tabStrip.getNode().clientWidth - 100, 3);
}
break;
default:
throw new Error(arguments.callee.name + ": Unknown event type - " + type);
}
try {
this._controller.waitForEval("subject.tab.opened == true", TIMEOUT, 100,
{tab: self});
} finally {
this._controller.window.removeEventListener("TabOpen", checkTabOpened, false);
prefs.preferences.clearUserPref(PREF_TABS_ANIMATE);
}
},
/**
* Waits for a particular tab panel element to display and stop animating
*
* @param {number} tabIndex
* Index of the tab to check
* @param {string} elemString
* Lookup string of the tab panel element
*/
waitForTabPanel: function tabBrowser_waitForTabPanel(tabIndex, elemString) {
// Get the specified tab panel element
var tabPanel = this.getTabPanelElement(tabIndex, elemString);
// Get the style information for the tab panel element
var style = this._controller.window.getComputedStyle(tabPanel.getNode(), null);
// Wait for the top margin to be 0px - ie. has stopped animating
// XXX: A notification bar starts at a negative pixel margin and drops down
// to 0px. This creates a race condition where a test may click
// before the notification bar appears at it's anticipated screen location
this._controller.waitFor(function () {
return style.marginTop == '0px';
}, "Expected notification bar to be visible: '" + elemString + "' ");
}
}
// Export of functions
exports.closeAllTabs = closeAllTabs;
exports.getTabsWithURL = getTabsWithURL;
// Export of classes
exports.tabBrowser = tabBrowser;

View File

@@ -1,629 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Include required modules
var domUtils = require("dom-utils");
var tabs = require("tabs");
var utils = require("utils");
const TIMEOUT = 5000;
/**
* Constructor
*/
function tabView(aController) {
this._controller = aController;
this._tabView = null;
this._tabViewDoc = this._controller.window.document;
this._tabViewObject = this._controller.window.TabView;
}
/**
* Tab View class
*/
tabView.prototype = {
///////////////////////////////
// Global section
///////////////////////////////
/**
* Returns the MozMill controller
*
* @returns Mozmill controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* Check if the Tab View is open
*
* @returns True if the Tab View is open
* @type {boolean}
*/
get isOpen() {
var deck = this.getElement({type: "deck"});
return deck.getNode().getAttribute("selectedIndex") == "1";
},
/**
* Open the Tab View
*/
open : function tabView_open() {
var menuitem = new elementslib.Elem(this._controller.menus['view-menu'].menu_tabview);
this._controller.click(menuitem);
this.waitForOpened();
this._tabView = this.getElement({type: "tabView"});
this._tabViewDoc = this._tabView.getNode().webNavigation.document;
},
/**
* Reset the Tab View settings for the current window
*/
reset : function tabView_reset() {
// Make sure to close TabView before resetting its ui
if (this.isOpen) {
this.close();
}
var self = this;
this._tabViewObject._initFrame(function () {
var contentWindow = self._tabViewObject._window;
contentWindow.UI.reset();
});
// Make sure all tabs will be shown
Array.forEach(this._controller.window.gBrowser.tabs, function (tab) {
this._controller.window.gBrowser.showTab(tab);
}, this);
},
/**
* Wait until the Tab View has been opened
*/
waitForOpened : function tabView_waitForOpened() {
// Add event listener to wait until the tabview has been opened
var self = { opened: false };
function checkOpened() { self.opened = true; }
this._controller.window.addEventListener("tabviewshown", checkOpened, false);
try {
mozmill.utils.waitFor(function () {
return self.opened == true;
}, "TabView is not open.");
this._groupItemsObject = this._tabViewObject._window.GroupItems;
this._tabItemsObject = this._tabViewObject._window.TabItems;
}
finally {
this._controller.window.removeEventListener("tabviewshown", checkOpened, false);
}
},
/**
* Close the Tab View
*/
close : function tabView_close() {
var menuitem = new elementslib.Elem(this._controller.menus['view-menu'].menu_tabview);
this._controller.click(menuitem);
this.waitForClosed();
this._tabView = null;
this._tabViewDoc = this._controller.window.document;
},
/**
* Wait until the Tab View has been closed
*/
waitForClosed : function tabView_waitForClosed() {
// Add event listener to wait until the tabview has been closed
var self = { closed: false };
function checkClosed() { self.closed = true; }
this._controller.window.addEventListener("tabviewhidden", checkClosed, false);
try {
mozmill.utils.waitFor(function () {
return self.closed == true;
}, "TabView is still open.");
} finally {
this._controller.window.removeEventListener("tabviewhidden", checkClosed, false);
}
this._groupItemsObject = null;
this._tabItemsObject = null;
},
///////////////////////////////
// Groups section
///////////////////////////////
/**
* Returns the tab groups which match the filter criteria
*
* @param {object} aSpec
* Information about the filter to apply
* Elements: filter - Type of filter to apply
* (active, title)
* [optional - default: ""]
* value - Value of the element
* [optional - default: ""]
*
* @returns List of groups
* @type {array of ElemBase}
*/
getGroups : function tabView_getGroups(aSpec) {
var spec = aSpec || {};
return this.getElements({
type: "groups",
subtype: spec.filter,
value: spec.value
});
},
/**
* Retrieve the group's title box
*
* @param {object} aSpec
* Information on which group to operate on
* Elements: group - Group element
*
* @returns Group title box
* @type {ElemBase}
*/
getGroupTitleBox : function tabView_getGroupTitleBox(aSpec) {
var spec = aSpec || {};
var group = spec.group;
if (!group) {
throw new Error(arguments.callee.name + ": Group not specified.");
}
return this.getElement({
type: "group_titleBox",
parent: group
});
},
/**
* Close the specified tab group
*
* @param {object} aSpec
* Information on which group to operate on
* Elements: group - Group
*/
closeGroup : function tabView_closeGroup(aSpec) {
var spec = aSpec || {};
var group = spec.group;
if (!group) {
throw new Error(arguments.callee.name + ": Group not specified.");
}
var button = this.getElement({
type: "group_closeButton",
parent: group
});
this._controller.click(button);
this.waitForGroupClosed({group: group});
},
/**
* Wait until the specified tab group has been closed
*
* @param {object} aSpec
* Information on which group to operate on
* Elements: group - Group
*/
waitForGroupClosed : function tabView_waitForGroupClosed(aSpec) {
var spec = aSpec || {};
var group = spec.group;
var groupObj = null;
var self = { closed: false };
function checkClosed() { self.closed = true; }
if (!group) {
throw new Error(arguments.callee.name + ": Group not specified.");
}
this._groupItemsObject.groupItems.forEach(function (node) {
if (node.container == group.getNode()) {
groupObj = node;
}
});
if (!groupObj) {
throw new Error(arguments.callee.name + ": Group not found.");
}
try {
groupObj.addSubscriber(groupObj, "groupHidden", checkClosed);
mozmill.utils.waitFor(function () {
return self.closed;
}, "Tab Group has not been closed.");
}
finally {
groupObj.removeSubscriber(groupObj, "groupHidden");
}
},
/**
* Undo the closing of the specified tab group
*
* @param {object} aSpec
* Information on which group to operate on
* Elements: group - Group
*/
undoCloseGroup : function tabView_undoCloseGroup(aSpec) {
var spec = aSpec || {};
var group = spec.group;
if (!group) {
throw new Error(arguments.callee.name + ": Group not specified.");
}
var undo = this.getElement({
type: "group_undoButton",
parent: group
});
this._controller.waitThenClick(undo);
this.waitForGroupUndo({group: group});
},
/**
* Wait until the specified tab group has been reopened
*
* @param {object} aSpec
* Information on which group to operate on
* Elements: group - Group
*/
waitForGroupUndo : function tabView_waitForGroupUndo(aSpec) {
var spec = aSpec || {};
var group = spec.group;
var groupObj = null;
var self = { reopened: false };
function checkClosed() { self.reopened = true; }
if (!group) {
throw new Error(arguments.callee.name + ": Group not specified.");
}
var groupObj = null;
this._groupItemsObject.groupItems.forEach(function(node) {
if (node.container == group.getNode()) {
groupObj = node;
}
});
if (!groupObj) {
throw new Error(arguments.callee.name + ": Group not found.");
}
try {
groupObj.addSubscriber(groupObj, "groupShown", checkClosed);
mozmill.utils.waitFor(function () {
return self.reopened;
}, "Tab Group has not been reopened.");
}
finally {
groupObj.removeSubscriber(groupObj, "groupShown");
}
},
///////////////////////////////
// Tabs section
///////////////////////////////
/**
* Returns the tabs which match the filter criteria
*
* @param {object} aSpec
* Information about the filter to apply
* Elements: filter - Type of filter to apply
* (active, title)
* [optional - default: ""]
* value - Value of the element
* [optional - default: ""]
*
* @returns List of tabs
* @type {array of ElemBase}
*/
getTabs : function tabView_getTabs(aSpec) {
var spec = aSpec || {};
return this.getElements({
type: "tabs",
subtype: spec.filter,
value: spec.value
});
},
/**
* Close a tab
*
* @param {object} aSpec
* Information about the element to operate on
* Elements: tab - Tab to close
*/
closeTab : function tabView_closeTab(aSpec) {
var spec = aSpec || {};
var tab = spec.tab;
if (!tab) {
throw new Error(arguments.callee.name + ": Tab not specified.");
}
var button = this.getElement({
type: "tab_closeButton",
value: tab}
);
this._controller.click(button);
},
/**
* Retrieve the tab's title box
*
* @param {object} aSpec
* Information on which tab to operate on
* Elements: tab - Tab
*
* @returns Tab title box
* @type {ElemBase}
*/
getTabTitleBox : function tabView_getTabTitleBox(aSpec) {
var spec = aSpec || {};
var tab = spec.tab;
if (!tab) {
throw new Error(arguments.callee.name + ": Tab not specified.");
}
return this.getElement({
type: "tab_titleBox",
parent: spec.tab
});
},
/**
* Open a new tab in the specified group
*
* @param {object} aSpec
* Information about the element to operate on
* Elements: group - Group to create a new tab in
*/
openTab : function tabView_openTab(aSpec) {
var spec = aSpec || {};
var group = spec.group;
if (!group) {
throw new Error(arguments.callee.name + ": Group not specified.");
}
var button = this.getElement({
type: "group_newTabButton",
parent: group
});
this._controller.click(button);
this.waitForClosed();
},
///////////////////////////////
// UI Elements section
///////////////////////////////
/**
* Retrieve an UI element based on the given specification
*
* @param {object} aSpec
* Information of the UI elements which should be retrieved
* Elements: type - Identifier of the element
* subtype - Attribute of the element to filter
* [optional - default: ""]
* value - Value of the attribute to filter
* [optional - default: ""]
* parent - Parent of the to find element
* [optional - default: document]
*
* @returns Element which has been found
* @type {ElemBase}
*/
getElement : function tabView_getElement(aSpec) {
var elements = this.getElements(aSpec);
return (elements.length > 0) ? elements[0] : undefined;
},
/**
* Retrieve list of UI elements based on the given specification
*
* @param {object} aSpec
* Information of the UI elements which should be retrieved
* Elements: type - Identifier of the element
* subtype - Attribute of the element to filter
* [optional - default: ""]
* value - Value of the attribute to filter
* [optional - default: ""]
* parent - Parent of the to find element
* [optional - default: document]
*
* @returns Elements which have been found
* @type {array of ElemBase}
*/
getElements : function tabView_getElement(aSpec) {
var spec = aSpec || { };
var type = spec.type;
var subtype = spec.subtype;
var value = spec.value;
var parent = spec.parent;
var root = parent ? parent.getNode() : this._tabViewDoc;
var nodeCollector = new domUtils.nodeCollector(root);
switch(type) {
// Top level elements
case "tabView":
nodeCollector.root = this._controller.window.document;
nodeCollector.queryNodes("#tab-view");
break;
case "contentArea":
nodeCollector.queryNodes("#content");
break;
case "deck":
nodeCollector.root = this._controller.window.document;
nodeCollector.queryNodes("#tab-view-deck");
break;
case "exitButton":
nodeCollector.queryNodes("#exit-button");
break;
// Group elements
case "group_appTabs":
nodeCollector.queryNodes(".appTabIcon");
break;
case "group_closeButton":
nodeCollector.queryNodes(".close");
break;
case "group_newTabButton":
nodeCollector.queryNodes(".newTabButton");
break;
case "group_resizer":
nodeCollector.queryNodes(".iq-resizable-handle");
break;
case "group_stackExpander":
nodeCollector.queryNodes(".stackExpander");
break;
case "group_titleBox":
nodeCollector.queryNodes(".name");
break;
case "group_undoButton":
// Bug 596504 - No reference to the undo button
nodeCollector.root = this._tabViewDoc;
nodeCollector.queryNodes(".undo").filter(function (node) {
var groups = this._groupItemsObject.groupItems;
for (var i = 0; i < groups.length; i++) {
var group = groups[i];
if (group.container == parent.getNode() &&
group.$undoContainer.length == 1) {
return true;
}
}
return false;
}, this);
break;
case "groups":
nodeCollector.queryNodes(".groupItem").filter(function (node) {
switch(subtype) {
case "active":
return node.className.indexOf("activeGroup") != -1;
case "title":
// If no title is given the default name is used
if (!value) {
value = utils.getProperty("chrome://browser/locale/tabview.properties",
"tabview.groupItem.defaultName");
}
var title = node.querySelector(".name");
return (value == title.value);
default:
return true;
}
}, this);
break;
// Search elements
case "search_box":
nodeCollector.queryNodes("#searchbox");
break;
case "search_button":
nodeCollector.queryNodes("#searchbutton");
break;
// Tab elements
case "tab_closeButton":
nodeCollector.queryNodes(".tab .close");
break;
case "tab_favicon":
nodeCollector.queryNodes(".tab .favicon");
break;
case "tab_titleBox":
nodeCollector.queryNodes(".tab .tab-title");
break;
case "tabs":
nodeCollector.queryNodes(".tab").filter(function (node) {
switch (subtype) {
case "active":
return (node.className.indexOf("focus") != -1);
case "group":
var group = value ? value.getNode() : null;
if (group) {
var tabs = this._tabItemsObject.getItems();
for (var i = 0; i < tabs.length; i++) {
var tab = tabs[i];
if (tab.parent && tab.parent.container == group) {
return true;
}
}
return false;
}
else {
return (node.className.indexOf("tabInGroupItem") == -1);
}
default:
return true;
}
}, this);
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " +
aSpec.type);
}
return nodeCollector.elements;
}
}
// Export of classes
exports.tabView = tabView;

View File

@@ -1,509 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Aaron Train <atrain@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The ToolbarAPI adds support for accessing and interacting with toolbar elements
*
* @version 1.0.0
*/
// Include required modules
var utils = require("utils");
const TIMEOUT = 5000;
const AUTOCOMPLETE_POPUP = '/id("main-window")/id("mainPopupSet")/id("PopupAutoCompleteRichResult")';
const NOTIFICATION_POPUP = '/id("main-window")/id("mainPopupSet")/id("notification-popup")';
const URLBAR_CONTAINER = '/id("main-window")/id("tab-view-deck")/{"flex":"1"}' +
'/id("navigator-toolbox")/id("nav-bar")/id("urlbar-container")';
const URLBAR_INPUTBOX = URLBAR_CONTAINER + '/id("urlbar")/anon({"anonid":"stack"})' +
'/anon({"anonid":"textbox-container"})' +
'/anon({"anonid":"textbox-input-box"})';
const CONTEXT_MENU = URLBAR_INPUTBOX + '/anon({"anonid":"input-box-contextmenu"})';
/**
* Constructor
*
* @param {MozmillController} controller
* MozMillController of the window to operate on
*/
function autoCompleteResults(controller) {
this._controller = controller;
this._popup = this.getElement({type: "popup"});
this._results = this.getElement({type: "results"});
}
/**
* AutoComplete Result class
*/
autoCompleteResults.prototype = {
/**
* Returns all autocomplete results
*
* @returns Autocomplete results
* @type {Array of ElemBase}
*/
get allResults() {
var results = [];
for (ii = 0; ii < this.length; ii++) {
results.push(this.getResult(ii));
}
return results;
},
/**
* Returns the controller of the current window
*
* @returns Mozmill Controller
* @type MozMillController
*/
get controller() {
return this._controller;
},
/**
* Check if the autocomplete popup is open
*
* @returns True if the panel is open
* @type {boolean}
*/
get isOpened() {
return (this._popup.getNode().state == 'open');
},
/**
* Return the amount of autocomplete entries
*
* @returns Number of all entries
* @type {number}
*/
get length() {
return this._results.getNode().itemCount;
},
/**
* Returns the currently selected index
*
* @returns Selected index
* @type {number}
*/
get selectedIndex() {
return this._results.getNode().selectedIndex;
},
/**
* Returns the visible autocomplete results
*
* @returns Results
* @type {Array of ElemBase}
*/
get visibleResults() {
var results = [];
for (ii = 0; ii < this.length; ii++) {
var result = this.getResult(ii);
if (!result.getNode().hasAttribute("collapsed"))
results.push(result);
}
return results;
},
/**
* Returns the underlined text of all results from the text or URL
*
* @param {ElemBase} result
* Autocomplete result which has to be checked
* @param {string} type
* Type of element to check (text or url)
*
* @returns An array of substrings which are underlined
* @type {Array of string}
*/
getUnderlinedText : function autoCompleteResults_getUnderlinedText(result, type) {
this._controller.assertJS("subject.resultNode != null",
{resultNode: result.getNode()});
// Get the description element of the given title or url
var description = null;
switch (type) {
case "title":
description = result.getNode().boxObject.firstChild.childNodes[1].childNodes[0];
break;
case "url":
description = result.getNode().boxObject.lastChild.childNodes[2].childNodes[0];
break;
default:
throw new Error(arguments.callee.name + ": Type unknown - " + type);
}
let values = [ ];
for each (node in description.childNodes) {
if (node.nodeName == 'span') {
// Only add underlined text to the results
values.push(node.innerHTML);
}
}
return values;
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function autoCompleteResults_getDtds() {
return null;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type {ElemBase}
*/
getElement : function autoCompleteResults_getElement(spec) {
var elem = null;
switch (spec.type) {
/**
* subtype: subtype to match
* value: value to match
*/
case "popup":
elem = new elementslib.Lookup(this._controller.window.document, AUTOCOMPLETE_POPUP);
break;
case "results":
elem = new elementslib.Lookup(this._controller.window.document,
AUTOCOMPLETE_POPUP + '/anon({"anonid":"richlistbox"})');
break;
case "result":
elem = new elementslib.Elem(this._results.getNode().getItemAtIndex(spec.value));
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
},
/**
* Returns the autocomplete result element of the given index
*
* @param {number} index
* Index of the result to return
* @returns Autocomplete result element
* @type {ElemBase}
*/
getResult : function autoCompleteResults_getResult(index) {
return this.getElement({type: "result", value: index});
},
/**
* Close the autocomplete popup
*
* @param {boolean} force
* Force the closing of the autocomplete popup
*/
close : function autoCompleteResults_close(force) {
if (this.isOpened) {
if (force) {
this._popup.getNode().hidePopup();
} else {
this._controller.keypress(locationBar.urlbar, "VK_ESCAPE", {});
}
this._controller.waitFor(function () {
return !this.isOpened;
}, "Autocomplete list should not be open.");
}
}
}
/**
* Constructor
*
* @param {MozmillController} controller
* MozMillController of the window to operate on
*/
function locationBar(controller)
{
this._controller = controller;
this._autoCompleteResults = new autoCompleteResults(controller);
}
/**
* Location Bar class
*/
locationBar.prototype = {
/**
* Returns the autocomplete object
*
* @returns Autocomplete object
* @type {object}
*/
get autoCompleteResults() {
return this._autoCompleteResults;
},
/**
* Returns the controller of the current window
*
* @returns Mozmill controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* Returns the urlbar element
*
* @returns URL bar
* @type {ElemBase}
*/
get urlbar() {
return this.getElement({type: "urlbar"});
},
/**
* Returns the currently shown URL
*
* @returns Text inside the location bar
* @type {string}
*/
get value() {
return this.urlbar.getNode().value;
},
/**
* Clear the location bar
*/
clear : function locationBar_clear() {
this.focus({type: "shortcut"});
this._controller.keypress(this.urlbar, "VK_DELETE", {});
this._controller.waitForEval("subject.value == ''",
TIMEOUT, 100, this.urlbar.getNode());
},
/**
* Close the context menu of the urlbar input field
*/
closeContextMenu : function locationBar_closeContextMenu() {
var menu = this.getElement({type: "contextMenu"});
this._controller.keypress(menu, "VK_ESCAPE", {});
},
/**
* Check if the location bar contains the given text
*
* @param {string} text
* Text which should be checked against
*/
contains : function locationBar_contains(text) {
return this.urlbar.getNode().value.indexOf(text) != -1;
},
/**
* Focus the location bar
*
* @param {object} event
* Focus the location bar with the given event (click or shortcut)
*/
focus : function locationBar_focus(event) {
switch (event.type) {
case "click":
this._controller.click(this.urlbar);
break;
case "shortcut":
var cmdKey = utils.getEntity(this.getDtds(), "openCmd.commandkey");
this._controller.keypress(null, cmdKey, {accelKey: true});
break;
default:
throw new Error(arguments.callee.name + ": Unkown event type - " + event.type);
}
// Wait until the location bar has been focused
this._controller.waitForEval("subject.getAttribute('focused') == 'true'",
TIMEOUT, 100, this.urlbar.getNode());
},
/**
* Gets all the needed external DTD urls as an array
*
* @returns Array of external DTD urls
* @type [string]
*/
getDtds : function locationBar_getDtds() {
var dtds = ["chrome://branding/locale/brand.dtd",
"chrome://browser/locale/browser.dtd"];
return dtds;
},
/**
* Retrieve an UI element based on the given spec
*
* @param {object} spec
* Information of the UI element which should be retrieved
* type: General type information
* subtype: Specific element or property
* value: Value of the element or property
* @returns Element which has been created
* @type ElemBase
*/
getElement : function locationBar_getElement(spec) {
var elem = null;
switch(spec.type) {
/**
* subtype: subtype to match
* value: value to match
*/
case "contextMenu":
elem = new elementslib.Lookup(this._controller.window.document, CONTEXT_MENU);
break;
case "contextMenu_entry":
elem = new elementslib.Lookup(this._controller.window.document, CONTEXT_MENU +
'/{"cmd":"cmd_' + spec.subtype + '"}');
break;
case "favicon":
elem = new elementslib.ID(this._controller.window.document, "page-proxy-favicon");
break;
case "feedButton":
elem = new elementslib.ID(this._controller.window.document, "feed-button");
break;
case "goButton":
elem = new elementslib.ID(this._controller.window.document, "urlbar-go-button");
break;
case "historyDropMarker":
elem = new elementslib.Lookup(this._controller.window.document,
URLBAR_CONTAINER + '/id("urlbar")/anon({"anonid":"historydropmarker"})');
break;
case "identityBox":
elem = new elementslib.ID(this._controller.window.document, "identity-box");
break;
case "notification_element":
elem = new elementslib.Lookup(this._controller.window.document, NOTIFICATION_POPUP +
spec.subtype);
break;
case "notification_popup":
elem = new elementslib.Lookup(this._controller.window.document, NOTIFICATION_POPUP);
break;
case "starButton":
elem = new elementslib.ID(this._controller.window.document, "star-button");
break;
case "stopButton":
elem = new elementslib.ID(this._controller.window.document, "urlbar-stop-button");
break;
case "urlbar":
elem = new elementslib.ID(this._controller.window.document, "urlbar");
break;
case "urlbar_input":
elem = new elementslib.Lookup(this._controller.window.document, URLBAR_INPUTBOX +
'/anon({"anonid":"input"})');
break;
default:
throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
}
return elem;
},
/**
* Retrieves the notification popup
*
* @return The notification popup element
* @type {ElemBase}
*/
getNotification : function locationBar_getNotification() {
return this.getElement({type: "notification_popup"});
},
/**
* Retrieves the specified element of the door hanger notification bar
*
* @param {string} aType
* Type of the notification bar to look for
* @param {string} aLookupString
* Lookup string of the notification bar's child element
* [optional - default: ""]
*
* @return The created element
* @type {ElemBase}
*/
getNotificationElement : function locationBar_getNotificationElement(aType, aLookupString)
{
var lookup = '/id("' + aType + '")';
lookup = aLookupString ? lookup + aLookupString : lookup;
// Get the notification and fetch the child element if wanted
return this.getElement({type: "notification_element", subtype: lookup});
},
/**
* Load the given URL
*
* @param {string} url
* URL of web page to load
*/
loadURL : function locationBar_loadURL(url) {
this.focus({type: "shortcut"});
this.type(url);
this._controller.keypress(this.urlbar, "VK_RETURN", {});
},
/**
* Type the given text into the location bar
*
* @param {string} text
* Text to enter into the location bar
*/
type : function locationBar_type(text) {
this._controller.type(this.urlbar, text);
this.contains(text);
}
}
// Export of classes
exports.locationBar = locationBar;
exports.autoCompleteResults = autoCompleteResults;

View File

@@ -1,445 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Anthony Hughes <ahughes@mozilla.com>
* M.-A. Darche <mozdev@cynode.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The UtilsAPI offers various helper functions for any other API which is
* not already covered by another shared module.
*
* @version 1.0.3
*/
// Include required modules
var prefs = require("prefs");
const gTimeout = 5000;
/**
* Get application specific informations
* @see http://mxr.mozilla.org/mozilla-central/source/xpcom/system/nsIXULAppInfo.idl
*/
var appInfo = {
_service: null,
/**
* Get the application info service
* @returns XUL runtime object
* @type nsiXULRuntime
*/
get appInfo() {
if (!this._appInfo) {
this._service = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo)
.QueryInterface(Ci.nsIXULRuntime);
}
return this._service;
},
/**
* Get the build id
* @returns Build id
* @type string
*/
get buildID() this.appInfo.appBuildID,
/**
* Get the application id
* @returns Application id
* @type string
*/
get ID() this.appInfo.ID,
/**
* Get the application name
* @returns Application name
* @type string
*/
get name() this.appInfo.name,
/**
* Get the operation system
* @returns Operation system name
* @type string
*/
get os() this.appInfo.OS,
/**
* Get the product vendor
* @returns Vendor name
* @type string
*/
get vendor() this.appInfo.vendor,
/**
* Get the application version
* @returns Application version
* @type string
*/
get version() this.appInfo.version,
/**
* Get the build id of the Gecko platform
* @returns Platform build id
* @type string
*/
get platformBuildID() this.appInfo.platformBuildID,
/**
* Get the version of the Gecko platform
* @returns Platform version
* @type string
*/
get platformVersion() this.appInfo.platformVersion,
/**
* Get the currently used locale
* @returns Current locale
* @type string
*/
get locale() {
var registry = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry);
return registry.getSelectedLocale("global");
},
/**
* Get the user agent string
* @returns User agent
* @type string
*/
get userAgent() {
var window = mozmill.wm.getMostRecentWindow("navigator:browser");
if (window)
return window.navigator.userAgent;
return "";
}
};
/**
* Checks the visibility of an element.
* XXX: Mozmill doesn't check if an element is visible and also operates on
* elements which are invisible. (Bug 490548)
*
* @param {MozmillController} controller
* MozMillController of the window to operate on
* @param {ElemBase} elem
* Element to check its visibility
* @param {boolean} expectedVisibility
* Expected visibility state of the element
*/
function assertElementVisible(controller, elem, expectedVisibility) {
var element = elem.getNode();
var visible;
switch (element.nodeName) {
case 'panel':
visible = (element.state == 'open');
break;
default:
var style = controller.window.getComputedStyle(element, '');
var state = style.getPropertyValue('visibility');
visible = (state == 'visible');
}
controller.assertJS('subject.visible == subject.expectedVisibility', {
visible: visible,
expectedVisibility: expectedVisibility
});
}
/**
* Assert if the current URL is identical to the target URL.
* With this function also redirects can be tested.
*
* @param {MozmillController} controller
* MozMillController of the window to operate on
* @param {string} targetURL
* URL to check
*/
function assertLoadedUrlEqual(controller, targetUrl) {
var locationBar = new elementslib.ID(controller.window.document, "urlbar");
var currentURL = locationBar.getNode().value;
// Load the target URL
controller.open(targetUrl);
controller.waitForPageLoad();
// Check the same web page has been opened
controller.waitFor(function () {
return locationBar.getNode().value === currentURL;
}, "Current URL should be identical to the target URL - got " +
locationBar.getNode().value + ", expected " + currentURL);
}
/**
* Close the context menu inside the content area of the currently open tab
*
* @param {MozmillController} controller
* MozMillController of the window to operate on
*/
function closeContentAreaContextMenu(controller) {
var contextMenu = new elementslib.ID(controller.window.document, "contentAreaContextMenu");
controller.keypress(contextMenu, "VK_ESCAPE", {});
}
/**
* Run tests against a given search form
*
* @param {MozMillController} controller
* MozMillController of the window to operate on
* @param {ElemBase} searchField
* The HTML input form element to test
* @param {string} searchTerm
* The search term for the test
* @param {ElemBase} submitButton
* (Optional) The forms submit button
* @param {number} timeout
* The timeout value for the single tests
*/
function checkSearchField(controller, searchField,
searchTerm, submitButton,
timeout) {
controller.waitThenClick(searchField, timeout);
controller.type(searchField, searchTerm);
if (submitButton != undefined) {
controller.waitThenClick(submitButton, timeout);
}
}
/**
* Create a new URI
*
* @param {string} spec
* The URI string in UTF-8 encoding.
* @param {string} originCharset
* The charset of the document from which this URI string originated.
* @param {string} baseURI
* If null, spec must specify an absolute URI. Otherwise, spec may be
* resolved relative to baseURI, depending on the protocol.
* @return A URI object
* @type nsIURI
*/
function createURI(spec, originCharset, baseURI)
{
let iosvc = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return iosvc.newURI(spec, originCharset, baseURI);
}
/**
* Empty the clipboard by assigning an empty string
*/
function emptyClipboard() {
var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
clipboard.copyString("");
}
/**
* Format a URL by replacing all placeholders
*
* @param {string} prefName
* The preference name which contains the URL
* @return The formatted URL
* @type string
*/
function formatUrlPref(prefName) {
var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]
.getService(Ci.nsIURLFormatter);
return formatter.formatURLPref(prefName);
}
/**
* Returns the default home page
*
* @return The URL of the default homepage
* @type string
*/
function getDefaultHomepage() {
var preferences = prefs.preferences;
var prefValue = preferences.getPref("browser.startup.homepage", "",
true, Ci.nsIPrefLocalizedString);
return prefValue.data;
}
/**
* Returns the value of an individual entity in a DTD file.
*
* @param [string] urls
* Array of DTD urls.
* @param {string} entityId
* The ID of the entity to get the value of.
*
* @return The value of the requested entity
* @type string
*/
function getEntity(urls, entityId) {
// Add xhtml11.dtd to prevent missing entity errors with XHTML files
urls.push("resource:///res/dtd/xhtml11.dtd");
// Build a string of external entities
var extEntities = "";
for (i = 0; i < urls.length; i++) {
extEntities += '<!ENTITY % dtd' + i + ' SYSTEM "' +
urls[i] + '">%dtd' + i + ';';
}
var parser = Cc["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Ci.nsIDOMParser);
var header = '<?xml version="1.0"?><!DOCTYPE elem [' + extEntities + ']>';
var elem = '<elem id="elementID">&' + entityId + ';</elem>';
var doc = parser.parseFromString(header + elem, 'text/xml');
var elemNode = doc.querySelector('elem[id="elementID"]');
if (elemNode == null)
throw new Error(arguments.callee.name + ": Unknown entity - " + entityId);
return elemNode.textContent;
}
/**
* Returns the value of an individual property.
*
* @param {string} url
* URL of the string bundle.
* @param {string} prefName
* The property to get the value of.
*
* @return The value of the requested property
* @type string
*/
function getProperty(url, prefName) {
var sbs = Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService);
var bundle = sbs.createBundle(url);
try {
return bundle.GetStringFromName(prefName);
} catch (ex) {
throw new Error(arguments.callee.name + ": Unknown property - " + prefName);
}
}
/**
* Function to handle non-modal windows
*
* @param {string} type
* Specifies how to check for the new window (possible values: type or title)
* @param {string} text
* The window type of title string to search for
* @param {function} callback (optional)
* Callback function to call for window specific tests
* @param {boolean} close (optional - default: true)
* Make sure the window is closed after the return from the callback handler
* @returns The MozMillController of the window (if the window hasn't been closed)
*/
function handleWindow(type, text, callback, close) {
// Set the window opener function to use depending on the type
var func_ptr = null;
switch (type) {
case "type":
func_ptr = mozmill.utils.getWindowByType;
break;
case "title":
func_ptr = mozmill.utils.getWindowByTitle;
break;
default:
throw new Error(arguments.callee.name + ": Unknown opener type - " + type);
}
var window = null;
var controller = null;
try {
// Wait until the window has been opened
mozmill.utils.waitFor(function () {
window = func_ptr(text);
return window != null;
}, "Window has been found.");
// XXX: We still have to find a reliable way to wait until the new window
// content has been finished loading. Let's wait for now.
controller = new mozmill.controller.MozMillController(window);
controller.sleep(200);
if (callback) {
callback(controller);
}
// Check if we have to close the window
if (close === undefined)
close = true;
if (close && window) {
controller.window.close();
mozmill.utils.waitFor(function () {
return func_ptr(text) != window;
}, "Window has been closed.");
window = null;
controller = null;
}
return controller;
} catch (ex) {
if (window)
window.close();
throw ex;
}
}
// Export of variables
exports.appInfo = appInfo;
// Export of functions
exports.assertElementVisible = assertElementVisible;
exports.assertLoadedUrlEqual = assertLoadedUrlEqual;
exports.closeContentAreaContextMenu = closeContentAreaContextMenu;
exports.checkSearchField = checkSearchField;
exports.createURI = createURI;
exports.formatUrlPref = formatUrlPref;
exports.emptyClipboard = emptyClipboard;
exports.getDefaultHomepage = getDefaultHomepage;
exports.getEntity = getEntity;
exports.getProperty = getProperty;
exports.handleWindow = handleWindow;

View File

@@ -1,82 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @fileoverview
* The WidgetsAPI adds support for handling objects like trees.
*/
var EventUtils = {};
Components.utils.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
const gTimeout = 5000;
/**
* Click the specified tree cell
*
* @param {MozMillController} controller
* MozMillController of the browser window to operate on
* @param {tree} tree
* Tree to operate on
* @param {number } rowIndex
* Index of the row
* @param {number} columnIndex
* Index of the column
* @param {object} eventDetails
* Details about the mouse event
*/
function clickTreeCell(controller, tree, rowIndex, columnIndex, eventDetails)
{
tree = tree.getNode();
var selection = tree.view.selection;
selection.select(rowIndex);
tree.treeBoxObject.ensureRowIsVisible(rowIndex);
// get cell coordinates
var x = {}, y = {}, width = {}, height = {};
var column = tree.columns[columnIndex];
tree.treeBoxObject.getCoordsForCellItem(rowIndex, column, "text",
x, y, width, height);
controller.sleep(0);
EventUtils.synthesizeMouse(tree.body, x.value + 4, y.value + 4,
eventDetails, tree.ownerDocument.defaultView);
controller.sleep(0);
}
// Export of functions
exports.clickTreeCell = clickTreeCell;

View File

@@ -1,18 +0,0 @@
var setupModule = function (module) {
controller = mozmill.getBrowserController();
};
var testAboutPage_WhenOpened_PageIsLoadedWithExpectedTitle = function () {
const ABOUT_PAGE_URL = "about:pentadactyl";
const EXPECTED_TITLE = "About Pentadactyl";
const BLANK_PAGE_URL = "about:blank";
controller.open(BLANK_PAGE_URL);
controller.waitForPageLoad(controller.tabs.activeTab);
controller.open(ABOUT_PAGE_URL);
controller.waitForPageLoad(controller.tabs.activeTab);
controller.assert(function () controller.tabs.activeTab.title === EXPECTED_TITLE);
};
// vim: sw=4 ts=8 et:

View File

@@ -1,69 +0,0 @@
var dactyllib = require("dactyl");
var setupModule = function (module) {
controller = mozmill.getBrowserController();
dactyl = new dactyllib.Controller(controller);
};
var teardownTest = function (test) {
dactyl.closeMessageWindow();
};
var testEchoCommand_SingleLineMessageAndClosedMOW_MessageDisplayedInMessageLine = function () {
const output = "foobar";
assertEchoGeneratesLineOutput({
ECHO_COMMAND: "echo " + output.quote(),
EXPECTED_OUTPUT: output
});
};
var testEchoCommand_SingleLineMessageAndOpenMOW_MessageAppendedToMOW = function () {
const output = "foobar";
dactyl.openMessageWindow();
assertEchoGeneratesWindowOutput({
ECHO_COMMAND: "echo " + output.quote(),
EXPECTED_OUTPUT: RegExp(output)
});
};
var testEchoCommand_MultilineMessageAndClosedMOW_MessageDisplayedInMOW = function () {
const output = "foo\nbar";
assertEchoGeneratesWindowOutput({
ECHO_COMMAND: "echo " + output.quote(),
EXPECTED_OUTPUT: output
});
};
var testEchoCommand_MultilineMessageAndOpenMOW_MessageAppendedToMOW = function () {
const output = "foo\nbar";
dactyl.openMessageWindow();
assertEchoGeneratesWindowOutput({
ECHO_COMMAND: "echo " + output.quote(),
EXPECTED_OUTPUT: RegExp(output)
});
};
var testEchoCommand_ObjectArgumentAndClosedMOW_MessageDisplayedInMOW = function () {
assertEchoGeneratesWindowOutput({
ECHO_COMMAND: "echo var obj = { x: 1, y: 2 }; obj;",
EXPECTED_OUTPUT: "[object\u00A0Object]::\nx: 1\ny: 2\n"
});
};
function assertEchoGeneratesWindowOutput({ ECHO_COMMAND, EXPECTED_OUTPUT }) {
dactyl.runExCommand(ECHO_COMMAND);
dactyl.assertMessageWindow(EXPECTED_OUTPUT);
}
function assertEchoGeneratesLineOutput({ ECHO_COMMAND, EXPECTED_OUTPUT }) {
dactyl.runExCommand(ECHO_COMMAND);
dactyl.assertMessageLine(EXPECTED_OUTPUT);
}
// vim: sw=4 ts=8 et:

View File

@@ -1,42 +0,0 @@
var dactyllib = require("dactyl");
const FIND_TEST_PAGE = collector.addHttpResource("./data/") + "find.html";
var setupModule = function (module) {
controller = mozmill.getBrowserController();
dactyl = new dactyllib.Controller(controller);
};
var setupTest = function (test) {
controller.open(FIND_TEST_PAGE);
controller.waitForPageLoad(controller.tabs.activeTab);
};
var testFindCommand_PresentAlphabeticText_TextSelected = function () {
assertTextFoundInPage("letter")
};
var testFindCommand_PresentNumericText_TextSelected = function () {
assertTextFoundInPage("3.141")
};
var testFindCommand_MissingText_ErrorMessageDisplayed = function () {
const MISSING_TEXT = "8c307545a017f60add90ef08955e148e";
const PATTERN_NOT_FOUND_ERROR = "E486: Pattern not found: " + MISSING_TEXT;
runTextSearchCommand(MISSING_TEXT);
dactyl.assertErrorMessage(PATTERN_NOT_FOUND_ERROR);
};
function runTextSearchCommand(str) {
dactyl.runViCommand("/" + str);
dactyl.runViCommand([["VK_RETURN"]]);
}
function assertTextFoundInPage(text) {
runTextSearchCommand(text);
dactyl.assertSelection(text);
}
// vim: sw=4 ts=8 et:

View File

@@ -1,62 +0,0 @@
var jumlib = {}; Components.utils.import("resource://mozmill/modules/jum.js", jumlib);
var dactyllib = require("dactyl");
var setupModule = function (module) {
controller = mozmill.getBrowserController();
dactyl = new dactyllib.Controller(controller);
};
var setupTest = function (test) {
dactyl.runViCommand([["VK_ESCAPE"]]);
};
const HELP_FILES = ["all", "tutorial", "intro", "starting", "browsing",
"buffer", "cmdline", "insert", "options", "pattern", "tabs", "hints",
"map", "eval", "marks", "repeat", "autocommands", "print", "gui",
"styling", "message", "developer", "various", "faq", "index", "plugins"];
var testViHelpCommand_OpensIntroHelpPage = function () {
assertHelpOpensPageWithTag({
HELP_COMMAND: function () { dactyl.runViCommand([["VK_F1"]]); },
EXPECTED_HELP_TAG: "intro.xml"
});
};
var testViHelpAllCommand_OpensAllHelpPage = function () {
assertHelpOpensPageWithTag({
HELP_COMMAND: function () { dactyl.runViCommand([["VK_F1", { altKey: true }]]); },
EXPECTED_HELP_TAG: "all.xml"
});
};
var testExHelpCommand_NoArgs_OpensIntroHelpPage = function () {
assertHelpOpensPageWithTag({
HELP_COMMAND: function () { dactyl.runExCommand("help"); },
EXPECTED_HELP_TAG: "intro.xml"
});
};
var testExHelpAllCommand_NoArgs_OpensAllHelpPage = function () {
assertHelpOpensPageWithTag({
HELP_COMMAND: function () { dactyl.runExCommand("helpall"); },
EXPECTED_HELP_TAG: "all.xml"
});
};
var testExHelpCommand_PageTagArg_OpensHelpPageContainingTag = function () {
for (let [, file] in Iterator(HELP_FILES)) {
let tag = file + ".xml";
assertHelpOpensPageWithTag({
HELP_COMMAND: function () { dactyl.runExCommand("help " + tag); },
EXPECTED_HELP_TAG: tag
});
}
};
function assertHelpOpensPageWithTag({ HELP_COMMAND, EXPECTED_HELP_TAG }) {
HELP_COMMAND();
controller.waitForPageLoad(controller.tabs.activeTab);
controller.assertNode(new elementslib.ID(controller.tabs.activeTab, EXPECTED_HELP_TAG));
}
// vim: sw=4 ts=8 et:

View File

@@ -1,33 +0,0 @@
var dactyllib = require("dactyl");
var setupModule = function (module) {
controller = mozmill.getBrowserController();
dactyl = new dactyllib.Controller(controller);
};
var teardownTest = function (test) {
dactyl.closeMessageWindow();
};
var testRunCommand_ExecutingOutputCommand_OutputDisplayed = function () {
const EXPECTED_OUTPUT = "foobar";
const COMMAND = "run echo " + EXPECTED_OUTPUT;
dactyl.runExCommand(COMMAND);
dactyl.assertMessageWindow(RegExp(EXPECTED_OUTPUT));
};
var testRunCommand_RepeatArg_LastCommandRepeated = function () {
const EXPECTED_OUTPUT = /foobar$/; // XXX
const COMMAND = "run echo 'foobar'";
const REPEAT_COMMAND = "run!";
dactyl.runExCommand(COMMAND);
dactyl.closeMessageWindow();
dactyl.runExCommand(REPEAT_COMMAND);
dactyl.assertMessageWindow(EXPECTED_OUTPUT);
};
// vim: sw=4 ts=8 et:

View File

@@ -1,34 +0,0 @@
var dactyllib = require("dactyl");
var setupModule = function (module) {
controller = mozmill.getBrowserController();
dactyl = new dactyllib.Controller(controller);
};
var setupTest = function (test) {
dactyl.closeMessageWindow();
};
var testVersionCommand_NoArg_VersionStringDisplayed = function () {
const EXPECTED_OUTPUT = RegExp(dactyl.applicationName + ".+ (.+) running on:.+"); // XXX
dactyl.runExCommand("version");
dactyl.assertMessageWindow(EXPECTED_OUTPUT);
};
var testVersionCommand_BangArg_HostAppVersionPageDisplayed = function () {
const EXPECTED_URL = "about:";
const EXPECTED_TITLE = "About:";
const BLANK_PAGE_URL = "about:blank";
controller.open(BLANK_PAGE_URL);
controller.waitForPageLoad(controller.tabs.activeTab);
dactyl.runExCommand("version!");
controller.waitForPageLoad(controller.tabs.activeTab);
controller.assert(function () controller.tabs.activeTab.location.href === EXPECTED_URL);
controller.assert(function () controller.tabs.activeTab.title === EXPECTED_TITLE);
};
// vim: sw=4 ts=8 et:

View File

@@ -2,23 +2,24 @@
* Extensive Firefox 4 support, including:
- Fully restartless. Can now be installed, uninstalled,
enabled, disabled, and upgraded without restarting Firefox.
[b4]
- Tabs in :buffer completions and listings are grouped
by panorama groups.
by panorama groups. [b1]
- Only visible tabs are considered in tab numbering,
gt/gn/gN, etc.
* Improved startup time by a factor of 7.
gt/gn/gN, etc. [b1]
* Improved startup time by a factor of 7. [b1]
* Significant completion speed improvements, especially for
JavaScript.
JavaScript. [b1]
* Greatly improved private mode support and :sanitize command.
- Full integration with Firefox data clearing dialogs.
- Support for sanitizing items at shutdown.
- Full integration with Firefox data clearing dialogs. [b3]
- Support for sanitizing items at shutdown. [b3]
- Fine-grained control over what data is sanitized and for
what timespan.
what timespan. [b1]
- Support for sanitizing reference to particular hosts,
everywhere from command-line and message history to option
values and cookies.
values and cookies. [b1]
* New and much more powerful incremental search implementation.
Improvements over the standard Firefox find include:
Improvements over the standard Firefox find include: [b1]
- Starts at the cursor position in the currently selected
frame, unlike Firefox, which always starts at the start of
the first frame.
@@ -28,135 +29,146 @@
backspace.
- Supports reverse incremental search.
- Input boxes are not focused when matches are highlighted.
* It's now possible to map keys in many more modes, including
Hint, Multi-line Output, and Menu.
* Ex command parsing improvements, including:
- Multiple Ex commands may now be separated by |
- Multiple Ex commands may now be separated by | [b1]
- Commands can continue over multiple lines in RC files by
prefixing the continuation lines with a \
prefixing the continuation lines with a \ [b3]
- The \ character is no longer treated specially within single
quotes, i.e., 'fo\o''bar' ⇒ fo\o'bar
quotes, i.e., 'fo\o''bar' ⇒ fo\o'bar [b1]
* Command line is now hidden by default. Added c, C, and M to
'guioptions'.
'guioptions'. [b4]
* Hint mode improvements, including:
- Added g; continued extended hint mode, which allows
selecting multiple hints. Removed ;F.
- Hints are now updated after scrolling and window resizing.
selecting multiple hints. Removed ;F. [b1]
- Hints are now updated after scrolling and window resizing. [b3]
- ;s now prompts for a filename on the command-line rather
than in a dialog.
- Added ;a and ;S modes for creating bookmarks and search keywords.
- Added 'hintkeys' option.
- Added "transliterated" option to 'hintmatching'.
* JavaScript completion improvements, including:
than in a dialog. [b5]
- Added ;a and ;S modes for creating bookmarks and search keywords. [b4][b3]
- Added 'hintkeys' option. [b2]
- Added "transliterated" option to 'hintmatching'. [b1]
* JavaScript completion improvements, including: [b2]
- The prototype of the function whose arguments are currently
being typed is displayed during completion.
- Non-enumerable global properties are now completed for the
global object, including XMLHttpRequest and encodeURI.
* The concept of completion contexts is now exposed to the user
(see :h :contexts), allowing for powerful and fine-grained
completion system customization.
completion system customization. [b1]
* The external editor can now be configured to open to a given
line number and column, used for opening source links and
editing input fields with i_<C-i>. See :h 'editor'.
editing input fields with i_<C-i>. See :h 'editor'. [b4]
* Mapping changes:
- It's now possible to map keys in many more modes, including
Hint, Multi-line Output, and Menu. [b4]
- Added site-specific mapping groups and related command
changes. [b6]
- Added 'timeout' and 'timeoutlen' options. [b6]
- Added <A-m>l and <A-m>s to aid in the construction of
macros. [b6]
- Removed the implicit page load delays during macro playback. [b6]
- Added the base modes Base, Main, and Command which other
modes inherit key bindings from. [b6]
* Command changes:
- :viusage, :optionusage and :exusage were replaced with :listkeys,
:listoptions and :listcommands, providing more powerful and
consistent interactive help facility (improvements include
listing keys for modes other than Normal, filtering the output
and linking to source code locations).
and linking to source code locations). [b4]
- :downloads now opens a download list in the multi-line output
buffer.
- Added :mapgroup command and -group flag to :map, :unmap, and
:mapclear. [b6]
- :extensions has been replaced with a more powerful :addons.
- Added :cookies command.
- :extadd now supports remote URLs as well as local files on Firefox 4.
- Added :if/:elseif/:else/:endif conditionals.
- Added -charset and -post to :bmark.
- Added -keyword, -tags, -title to :delbmarks.
- Added :extrehash, :exttoggle, :extupdate, and :rehash commands.
- Added :feedkeys command.
- Added -sort option to :history.
- Added :cookies command. [b3]
- :extadd now supports remote URLs as well as local files on Firefox 4. [b2]
- Added :if/:elseif/:else/:endif conditionals. [b3]
- Added -charset and -post to :bmark. [b5]
- Added -keyword, -tags, -title to :delbmarks. [b2]
- Added :extrehash, :exttoggle, :extupdate, and :rehash commands. [b5]
- Added :feedkeys command. [b4]
- Added -sort option to :history. [b4]
- Added several new options, including -javascript, to :abbreviate and
:map.
- Added :mksyntax command to auto-generate Vim syntax files.
- :open now only opens files beginning with /, ./, ../, or ~/
:map. [b2]
- Added :mksyntax command to auto-generate Vim syntax files. [b4]
- :open now only opens files beginning with /, ./, ../, or ~/ [b1]
- :saveas now provides completions for default file names, and
automatically chooses a filename when the save target is a
directory.
directory. [b4]
- :sidebar now accepts a ! flag to toggle the sidebar rather
than open it unconditionally.
- Added :write !cmd and :write >>file.
- Added :yank command.
than open it unconditionally. [b6]
- Added :write !cmd and :write >>file. [b3]
- Added :yank command. [b3]
- :delmarks, :marks and :qmarks now also accept ranges, same as
:delqmarks.
- :command now accepts comma-separated alternative command names.
- :command -complete custom now also accepts a completions array, see
:h :command-completion-custom.
* Improvements to :style and :highlight:
- Added -agent flag to :style.
- Added -link flag to :highlight. [b4]
- Added -agent flag to :style. [b2]
- The -append flag now updates existing properties rather than
simply appending its arguments to the previous value.
- Active filters are now highlighted in :style listings.
simply appending its arguments to the previous value. [b4]
- Active filters are now highlighted in :style listings. [b4]
- :style-related commands now divide their completions between
those active and inactive for the current site.
- CSS property name completion is now available.
those active and inactive for the current site. [b4]
- CSS property name completion is now available. [b4]
* IMPORTANT option changes:
- Boolean options no longer accept an argument.
- Option value quoting has changed. List options will
no longer be split at quoted commas and the option name, operators, and
= sign may no longer be quoted. This will break certain
automatically-generated configuration files. See :help stringlist. [b2]
- Boolean options no longer accept an argument. [b4]
- 'cdpath' and 'runtimepath' no longer treat ",,"
specially. Use "." instead.
specially. Use "." instead. [b2]
- 'incsearch', 'hlsearch', 'ignorecase', and 'smartcase' have
been replaced with 'incfind', 'hlfind', and 'findcase'.
been replaced with 'incfind', 'hlfind', and 'findcase'. [b4]
- 'extendedhinttags' is now a regexpmap rather than a
string.
- 'guioptions' default value has changed.
string. [b2]
- 'guioptions' default value has changed. [b4]
- 'laststatus' has been replaced with the "s" flag in
'guioptions'.
'guioptions'. [b4]
- 'linksearch' has been removed. The \l search modifier can
still be used for this purpose.
still be used for this purpose. [b4]
- 'loadplugins' is now a regexplist option rather than
a boolean.
a boolean. [b2]
- 'mapleader' is now an option rather than a :let
variable.
- 'showstatuslinks' and 'showtabline' are now string options.
* IMPORTANT: Command script files now use the *.penta file extension.
variable. [b4]
- 'showstatuslinks' and 'showtabline' are now string options. [b4]
* IMPORTANT: Command script files now use the *.penta file extension. [b2]
* IMPORTANT: Plugins are now loaded from the 'plugins/'
directory in 'runtimepath' rather than 'plugin/'.
* IMPORTANT: Option value quoting has changed. List options will
no longer be split at quoted commas and the option name,
operators, and = sign may no longer be quoted. This will break
certain automatically-generated configuration files.
See :help stringlist.
directory in 'runtimepath' rather than 'plugin/'. [b1]
* Option changes:
- Added "bookmarks", "diverted", and "links" to 'activate'
- Added "bookmarks", "diverted", and "links" to 'activate' [b2]
option.
- Added 'altwildmode' and c_<A-Tab> command-line key binding.
- Added 'altwildmode' and c_<A-Tab> command-line key binding. [b2]
- Added 'autocomplete' option for specifying which completion
groups should be auto-completed.
- Added 'banghist' option.
- Replaced 'focuscontent' with 'strictfocus'.
groups should be auto-completed. [b2]
- Added 'banghist' option. [b1]
- Replaced 'focuscontent' with 'strictfocus'. [b1]
- 'complete' now defaults to "slf" but file completion only
triggers when the URL begins as above.
- Added 'passkeys' option.
- Changed 'urlseparator' default value to "|".
- Added "passwords" and "venkman" dialogs to :dialog.
- Added 'wildanchor' option.
- Added 'cookies', 'cookieaccept', and 'cookielifetime' options.
* Added BookmarkChange, BookmarkRemove autocommands.
triggers when the URL begins as above. [b1]
- Added 'passkeys' option. [b3]
- Changed 'urlseparator' default value to "|". [b3]
- Added "passwords" and "venkman" dialogs to :dialog. [b2]
- Added 'wildanchor' option. [b2]
- Added 'cookies', 'cookieaccept', and 'cookielifetime' options. [b3]
* Added BookmarkChange, BookmarkRemove autocommands. [b2]
* Removed the :source line at the end of files generated by
:mkpentadactylrc.
:mkpentadactylrc. [b2]
* gf now toggles between source and content view.
The | key binding has been removed.
The | key binding has been removed. [b1]
* Page zoom information is now shown in the status bar, and
change in zoom status no longer appears in :messages.
* Added ZO, ZI, ZM, and ZR as aliases for zO, zI, zM, and zR.
change in zoom status no longer appears in :messages. [b1]
* Added ZO, ZI, ZM, and ZR as aliases for zO, zI, zM, and zR. [b1]
* Completion list now behaves better when the multi-line output
window is displayed.
window is displayed. [b1]
* Major help system improvements:
- Plugins may now provide full-fledged :help documentation.
- Add basic plugin authorship documentation.
- Plugins may now provide full-fledged :help documentation. [b1]
- Add basic plugin authorship documentation. [b1]
- The help system is newly modularized and features significant
updates, rewrites, and formatting improvements.
- Added <A-F1> to open the single unchunked help page.
* Removed :beep.
* Removed :edit, :tabedit, and :winedit aliases.
* Removed :play.
updates, rewrites, and formatting improvements. [b1]
- Added <A-F1> to open the single unchunked help page. [b5]
* Removed :beep. [b2]
* Removed :edit, :tabedit, and :winedit aliases. [b2]
* Removed :play. [b2]