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

Back out most of the changes accidentally merged from key-processing.

--HG--
extra : rebase_source : a00510584f7e13917f8496e15b7dd36852d98ea7
This commit is contained in:
Kris Maglione
2011-01-27 15:12:33 -05:00
parent 2c3fd51812
commit d16c0b0d06
47 changed files with 9350 additions and 1141 deletions

View File

@@ -115,8 +115,9 @@ var Bookmarks = Module("bookmarks", {
if (charset != null && charset !== "UTF-8")
options["-charset"] = charset;
CommandExMode().open(
commands.commandToString({ command: "bmark", options: options, arguments: [url] }) + " -keyword ");
commandline.open(":",
commands.commandToString({ command: "bmark", options: options, arguments: [url] }) + " -keyword ",
modes.EX);
},
/**
@@ -581,8 +582,9 @@ var Bookmarks = Module("bookmarks", {
options["-charset"] = content.document.characterSet;
}
CommandExMode().open(
commands.commandToString({ command: "bmark", options: options, arguments: [buffer.uri.spec] }));
commandline.open(":",
commands.commandToString({ command: "bmark", options: options, arguments: [buffer.uri.spec] }),
modes.EX);
});
mappings.add(myModes, ["A"],

View File

@@ -65,33 +65,33 @@ var Browser = Module("browser", {
mappings: function () {
mappings.add([modes.NORMAL],
["y", "<yank-location>"], "Yank current location to the clipboard",
["y"], "Yank current location to the clipboard",
function () { dactyl.clipboardWrite(buffer.uri.spec, true); });
// opening websites
mappings.add([modes.NORMAL],
["o"], "Open one or more URLs",
function () { CommandExMode().open("open "); });
function () { commandline.open(":", "open ", modes.EX); });
mappings.add([modes.NORMAL], ["O"],
"Open one or more URLs, based on current location",
function () { CommandExMode().open("open " + buffer.uri.spec); });
function () { commandline.open(":", "open " + buffer.uri.spec, modes.EX); });
mappings.add([modes.NORMAL], ["t"],
"Open one or more URLs in a new tab",
function () { CommandExMode().open("tabopen "); });
function () { commandline.open(":", "tabopen ", modes.EX); });
mappings.add([modes.NORMAL], ["T"],
"Open one or more URLs in a new tab, based on current location",
function () { CommandExMode().open("tabopen " + buffer.uri.spec); });
function () { commandline.open(":", "tabopen " + buffer.uri.spec, modes.EX); });
mappings.add([modes.NORMAL], ["w"],
"Open one or more URLs in a new window",
function () { CommandExMode().open("winopen "); });
function () { commandline.open(":", "winopen ", modes.EX); });
mappings.add([modes.NORMAL], ["W"],
"Open one or more URLs in a new window, based on current location",
function () { CommandExMode().open("winopen " + buffer.uri.spec); });
function () { commandline.open(":", "winopen " + buffer.uri.spec, modes.EX); });
mappings.add([modes.NORMAL],
["<C-a>"], "Increment last number in URL",

View File

@@ -766,8 +766,7 @@ var Buffer = Module("buffer", {
try {
window.urlSecurityCheck(uri.spec, doc.nodePrincipal);
io.CommandFileMode("Save link: ", {
onSubmit: function (path) {
commandline.input("Save link: ", function (path) {
let file = io.File(path);
if (file.exists() && file.isDirectory())
file.append(buffer.getDefaultNames(elem)[0][0]);
@@ -781,10 +780,11 @@ var Buffer = Module("buffer", {
}
buffer.saveURI(uri, file);
},
completer: function (context) completion.savePage(context, elem)
}).open();
}, {
autocomplete: true,
completer: function (context) completion.savePage(context, elem),
history: "file"
});
}
catch (e) {
dactyl.echoerr(e);
@@ -1360,15 +1360,17 @@ var Buffer = Module("buffer", {
},
openUploadPrompt: function openUploadPrompt(elem) {
io.CommandFileMode("Upload file: ", {
onSubmit: function (path) {
commandline.input("Upload file: ", function (path) {
let file = io.File(path);
dactyl.assert(file.exists());
elem.value = file.path;
events.dispatch(elem, events.create(elem.ownerDocument, "change", {}));
}
}).open(elem.value);
}, {
completer: function (context) completion.file(context),
default: elem.value,
history: "file"
});
}
}, {
commands: function () {
@@ -1528,8 +1530,7 @@ var Buffer = Module("buffer", {
if (/^>>/.test(context.filter))
context.advance(/^>>\s*/.exec(context.filter)[0].length);
completion.savePage(context, content.document);
context.fork("file", 0, completion, "file");
return completion.savePage(context, content.document);
},
literal: 0
});
@@ -1641,6 +1642,7 @@ var Buffer = Module("buffer", {
this, function (context) {
context.completions = buffer.getDefaultNames(node);
});
return context.fork("files", 0, completion, "file");
};
},
events: function () {
@@ -1651,7 +1653,7 @@ var Buffer = Module("buffer", {
mappings: function () {
var myModes = config.browserModes;
mappings.add(myModes, [".", "<repeat-key>"],
mappings.add(myModes, ["."],
"Repeat the last key event",
function (args) {
if (mappings.repeat) {
@@ -1670,31 +1672,31 @@ var Buffer = Module("buffer", {
function () { ex.stop(); });
// scrolling
mappings.add(myModes, ["j", "<Down>", "<C-e>", "<scroll-down-line>"],
mappings.add(myModes, ["j", "<Down>", "<C-e>"],
"Scroll document down",
function (args) { buffer.scrollVertical("lines", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["k", "<Up>", "<C-y>", "<scroll-up-line>"],
mappings.add(myModes, ["k", "<Up>", "<C-y>"],
"Scroll document up",
function (args) { buffer.scrollVertical("lines", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, dactyl.has("mail") ? ["h", "<scroll-left-column>"] : ["h", "<Left>", "<scroll-left-column>"],
mappings.add(myModes, dactyl.has("mail") ? ["h"] : ["h", "<Left>"],
"Scroll document to the left",
function (args) { buffer.scrollHorizontal("columns", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, dactyl.has("mail") ? ["l", "<scroll-right-column>"] : ["l", "<Right>", "<scroll-right-column>"],
mappings.add(myModes, dactyl.has("mail") ? ["l"] : ["l", "<Right>"],
"Scroll document to the right",
function (args) { buffer.scrollHorizontal("columns", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["0", "^", "<scroll-begin>"],
mappings.add(myModes, ["0", "^"],
"Scroll to the absolute left of the document",
function () { buffer.scrollToPercent(0, null); });
mappings.add(myModes, ["$", "<scroll-end>"],
mappings.add(myModes, ["$"],
"Scroll to the absolute right of the document",
function () { buffer.scrollToPercent(100, null); });
@@ -1708,7 +1710,7 @@ var Buffer = Module("buffer", {
function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 100); },
{ count: true });
mappings.add(myModes, ["%", "<scroll-percent>"],
mappings.add(myModes, ["%"],
"Scroll to {count} percent of the document",
function (args) {
dactyl.assert(args.count > 0 && args.count <= 100);
@@ -1716,59 +1718,59 @@ var Buffer = Module("buffer", {
},
{ count: true });
mappings.add(myModes, ["<C-d>", "<scroll-down>"],
mappings.add(myModes, ["<C-d>"],
"Scroll window downwards in the buffer",
function (args) { buffer._scrollByScrollSize(args.count, true); },
{ count: true });
mappings.add(myModes, ["<C-u>", "<scroll-up>"],
mappings.add(myModes, ["<C-u>"],
"Scroll window upwards in the buffer",
function (args) { buffer._scrollByScrollSize(args.count, false); },
{ count: true });
mappings.add(myModes, ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-page-up>"],
mappings.add(myModes, ["<C-b>", "<PageUp>", "<S-Space>"],
"Scroll up a full page",
function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["<C-f>", "<PageDown>", "<Space>", "<scroll-page-down>"],
mappings.add(myModes, ["<C-f>", "<PageDown>", "<Space>"],
"Scroll down a full page",
function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["]f", "<previous-frame>"],
mappings.add(myModes, ["]f"],
"Focus next frame",
function (args) { buffer.shiftFrameFocus(Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["[f", "<next-frame>"],
mappings.add(myModes, ["[f"],
"Focus previous frame",
function (args) { buffer.shiftFrameFocus(-Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["]]", "<next-page>"],
mappings.add(myModes, ["]]"],
"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, ["[[", "<previous-page>"],
mappings.add(myModes, ["[["],
"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", "<view-source>"],
mappings.add(myModes, ["gf"],
"Toggle between rendered and source view",
function () { buffer.viewSource(null, false); });
mappings.add(myModes, ["gF", "<view-source-externally>"],
mappings.add(myModes, ["gF"],
"View source with an external editor",
function () { buffer.viewSource(null, true); });
mappings.add(myModes, ["gi", "<focus-input>"],
mappings.add(myModes, ["gi"],
"Focus last used input field",
function (args) {
let elem = buffer.lastInputField;
@@ -1808,7 +1810,7 @@ var Buffer = Module("buffer", {
dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB, background: true });
});
mappings.add(myModes, ["p", "<MiddleMouse>", "<open-clipboard-url>"],
mappings.add(myModes, ["p", "<MiddleMouse>"],
"Open (put) a URL based on the current clipboard contents in the current buffer",
function () {
let url = dactyl.clipboardRead();
@@ -1816,7 +1818,7 @@ var Buffer = Module("buffer", {
dactyl.open(url);
});
mappings.add(myModes, ["P", "<tab-open-clipboard-url>"],
mappings.add(myModes, ["P"],
"Open (put) a URL based on the current clipboard contents in a new buffer",
function () {
let url = dactyl.clipboardRead();
@@ -1825,16 +1827,16 @@ var Buffer = Module("buffer", {
});
// reloading
mappings.add(myModes, ["r", "<reload>"],
mappings.add(myModes, ["r"],
"Reload the current web page",
function () { tabs.reload(tabs.getTab(), false); });
mappings.add(myModes, ["R", "<full-reload>"],
mappings.add(myModes, ["R"],
"Reload while skipping the cache",
function () { tabs.reload(tabs.getTab(), true); });
// yanking
mappings.add(myModes, ["Y", "<yank-word>"],
mappings.add(myModes, ["Y"],
"Copy selected text or current word",
function () {
let sel = buffer.getCurrentWord();
@@ -1843,62 +1845,62 @@ var Buffer = Module("buffer", {
});
// zooming
mappings.add(myModes, ["zi", "+", "<text-zoom-in>"],
mappings.add(myModes, ["zi", "+"],
"Enlarge text zoom of current web page",
function (args) { buffer.zoomIn(Math.max(args.count, 1), false); },
{ count: true });
mappings.add(myModes, ["zm", "<text-zoom-more>"],
mappings.add(myModes, ["zm"],
"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", "-", "<text-zoom-out>"],
mappings.add(myModes, ["zo", "-"],
"Reduce text zoom of current web page",
function (args) { buffer.zoomOut(Math.max(args.count, 1), false); },
{ count: true });
mappings.add(myModes, ["zr", "<text-zoom-reduce>"],
mappings.add(myModes, ["zr"],
"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", "<text-zoom>"],
mappings.add(myModes, ["zz"],
"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", "<full-zoom-in>"],
mappings.add(myModes, ["ZI", "zI"],
"Enlarge full zoom of current web page",
function (args) { buffer.zoomIn(Math.max(args.count, 1), true); },
{ count: true });
mappings.add(myModes, ["ZM", "zM", "<full-zoom-more>"],
mappings.add(myModes, ["ZM", "zM"],
"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", "<full-zoom-out>"],
mappings.add(myModes, ["ZO", "zO"],
"Reduce full zoom of current web page",
function (args) { buffer.zoomOut(Math.max(args.count, 1), true); },
{ count: true });
mappings.add(myModes, ["ZR", "zR", "<full-zoom-reduce>"],
mappings.add(myModes, ["ZR", "zR"],
"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", "<full-zoom>"],
mappings.add(myModes, ["zZ"],
"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>", "<page-info>"],
mappings.add(myModes, ["<C-g>"],
"Print the current file name",
function () { buffer.showPageInfo(false); });
mappings.add(myModes, ["g<C-g>", "<more-page-info>"],
mappings.add(myModes, ["g<C-g>"],
"Print file information",
function () { buffer.showPageInfo(true); });
},

File diff suppressed because it is too large Load Diff

View File

@@ -40,15 +40,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
this.commands["dactyl.restart"] = function (event) {
dactyl.restart();
};
styles.registerSheet("resource://dactyl-skin/dactyl.css");
},
cleanup: function () {
delete window.dactyl;
delete window.liberator;
styles.unregisterSheet("resource://dactyl-skin/dactyl.css");
},
destroy: function () {
@@ -1131,7 +1127,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
});
params = params || {};
if (isString(params))
if (isArray(params))
params = { where: params };
let flags = 0;
@@ -1319,8 +1315,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
*/
trapErrors: function trapErrors(func, self) {
try {
if (isString(func))
func = self[func];
return func.apply(self || this, Array.slice(arguments, 2));
}
catch (e) {

View File

@@ -11,17 +11,15 @@
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;
let params = input.main == mode.main ? mode.params : input.main.params;
if (params.preExecute)
input.preExecute = params.preExecute;
if (params.postExecute)
@@ -33,70 +31,9 @@ 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);
@@ -133,7 +70,9 @@ var ProcessorStack = Class("ProcessorStack", {
dbg("ACTIONS: " + actions.length + " " + this.actions.length);
dbg("PROCESSORS:", processors);
this._actions = actions;
if (!processors.some(function (p) p.main.ownsBuffer))
statusline.updateInputBuffer(processors.length ? this.buffer : "");
this.actions = actions.concat(this.actions);
if (result === Events.KILL)
@@ -148,7 +87,41 @@ var ProcessorStack = Class("ProcessorStack", {
this.processors = processors;
return this.execute(result, options["timeout"] && options["timeoutlen"] === 0)
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 && 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;
}
});
@@ -184,13 +157,12 @@ var KeyProcessor = Class("KeyProcessor", {
return this.onKeyPress(event);
},
execute: function execute(map, args)
let (self = this)
execute: function execute(map)
let (self = this, args = arguments)
function execute() {
if (self.preExecute)
self.preExecute.apply(self, args);
let res = map.execute.call(map, update({ self: self.main.params.mappingSelf || self.main.mappingSelf || map },
args))
let res = map.execute.apply(map, Array.slice(args, 1));
if (self.postExecute)
self.postExecute.apply(self, args);
return res;
@@ -515,7 +487,6 @@ 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 });
@@ -616,14 +587,9 @@ 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("");
},
@@ -678,17 +644,11 @@ 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) {
if (arguments.length === 1)
unknownOk = true;
let out = [];
let re = RegExp("<.*?>?>|[^<]|<(?!.*>)", "g");
let match;
while ((match = re.exec(input))) {
@@ -895,7 +855,10 @@ var Events = Module("events", {
isContentNode: function isContentNode(node) {
let win = (node.ownerDocument || node).defaultView || node;
return XPCNativeWrapper(win).top == content;
for (; win; win = win.parent != win && win.parent)
if (win == content)
return true;
return false;
},
/**
@@ -904,24 +867,39 @@ var Events = Module("events", {
*
* @returns {boolean}
*/
waitForPageLoad: function (time) {
waitForPageLoad: function () {
util.threadYield(true); // clear queue
if (buffer.loaded)
if (buffer.loaded == 1)
return true;
dactyl.echo("Waiting for page to load...", commandline.DISALLOW_MULTILINE);
const maxWaitTime = (time || 25);
const maxWaitTime = 25;
let start = Date.now();
let end = start + (maxWaitTime * 1000);
let end = start + (maxWaitTime * 1000); // maximum time to wait - TODO: add option
let now;
while (now = Date.now(), now < end) {
util.threadYield();
util.waitFor(function () !events.feedingKeys || buffer.loaded || Date.now() > end);
if (!events.feedingKeys)
return false;
if (buffer.loaded > 0) {
util.sleep(250);
break;
}
else
dactyl.echo("Waiting for page to load...", commandline.DISALLOW_MULTILINE);
}
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;
},
@@ -1014,7 +992,6 @@ var Events = Module("events", {
*/
input: function onInput(event) {
if ("dactylKeyPress" in event.originalTarget)
delete event.originalTarget.dactylKeyPress;
},
@@ -1039,17 +1016,18 @@ var Events = Module("events", {
let duringFeed = this.duringFeed || [];
this.duringFeed = [];
try {
if (this.feedingEvent)
if (this.feedingEvent && [!(k in event) || event[k] === v for ([k, v] in Iterator(this.feedingEvent))].every(util.identity)) {
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 && !event.isReplay)
if (modes.recording && (!this._input || !mappings.user.hasMap(modes.main, this._input.buffer + key)))
events._macroKeys.push(key);
// feedingKeys needs to be separate from interrupted so
@@ -1057,6 +1035,8 @@ 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) {
@@ -1106,8 +1086,7 @@ var Events = Module("events", {
let hives = mappings.hives.slice(event.noremap ? -1 : 0);
let main = { __proto__: mode.main, params: mode.params };
let keyModes = array([mode.params.keyModes, main, mode.main.allBases]).flatten().compact();
let keyModes = array([mode.params.keyModes, mode.main, mode.main.allBases]).flatten().compact();
this.processor = ProcessorStack(mode, hives, keyModes);
}
@@ -1189,6 +1168,10 @@ 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 == modes.COMMAND_LINE)
return;
function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument
let win = window.document.commandDispatcher.focusedWindow;
@@ -1210,7 +1193,7 @@ var Events = Module("events", {
}
if (Events.isInputElement(elem)) {
if (!modes.main.input)
if (!(modes.main & (modes.INSERT | modes.TEXT_EDIT | modes.VISUAL)))
modes.push(modes.INSERT);
if (hasHTMLDocument(win))
@@ -1224,7 +1207,7 @@ var Events = Module("events", {
if (modes.main == modes.VISUAL && elem.selectionEnd == elem.selectionStart)
modes.pop();
if (!modes.main.input)
if (!(modes.main & (modes.INSERT | modes.TEXT_EDIT | modes.VISUAL)))
if (options["insertmode"])
modes.push(modes.INSERT);
else {
@@ -1330,59 +1313,39 @@ var Events = Module("events", {
};
},
mappings: function () {
mappings.add(modes.MAIN,
["<C-z>", "<pass-all-keys>"], "Temporarily ignore all " + config.appName + " key bindings",
mappings.add(modes.all,
["<C-z>"], "Temporarily ignore all " + config.appName + " key bindings",
function () { modes.push(modes.PASS_THROUGH); });
mappings.add(modes.MAIN,
["<C-v>", "<pass-next-key>"], "Pass through next key",
mappings.add(modes.all,
["<C-v>"], "Pass through next key",
function () {
if (modes.main == modes.QUOTE)
return Events.PASS;
modes.push(modes.QUOTE);
});
mappings.add(modes.BASE,
mappings.add(modes.all,
["<Nop>"], "Do nothing",
function () {});
// macros
mappings.add([modes.COMMAND],
["q", "<record-macro>"], "Record a key sequence into a macro",
mappings.add([modes.NORMAL, modes.TEXT_AREA, modes.PLAYER].filter(util.identity),
["q"], "Record a key sequence into a macro",
function ({ arg }) {
events._macroKeys.pop();
events[modes.recording ? "finishRecording" : "startRecording"](arg);
},
{ get arg() !modes.recording });
mappings.add([modes.COMMAND],
["@", "<play-macro>"], "Play a macro",
mappings.add([modes.NORMAL, modes.TEXT_AREA, modes.PLAYER].filter(util.identity),
["@"], "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"],
@@ -1403,18 +1366,9 @@ 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

@@ -9,18 +9,16 @@
/** @scope modules */
/** @instance hints */
var HintSession = Class("HintSession", CommandMode, {
var HintSession = Class("HintSession", {
init: function init(mode, opts) {
init.supercall(this);
opts = opts || {};
// Hack.
if (!opts.window && modes.main == modes.OUTPUT_MULTILINE)
opts.window = commandline.widgets.multilineOutput.contentWindow;
this.hintMode = hints.modes[mode];
dactyl.assert(this.hintMode);
this.mode = hints.modes[mode];
dactyl.assert(this.mode);
this.activeTimeout = null; // needed for hinttimeout > 0
this.continue = Boolean(opts.continue);
@@ -33,7 +31,8 @@ var HintSession = Class("HintSession", CommandMode, {
this.usedTabKey = false;
this.validHints = []; // store the indices of the "hints" array with valid elements
this.open();
commandline.input(UTF8(this.mode.prompt) + ": ", null, this.closure);
modes.extended = modes.HINTS;
this.top = opts.window || content;
this.top.addEventListener("resize", hints.resizeTimer.closure.tell, true);
@@ -52,22 +51,7 @@ var HintSession = Class("HintSession", CommandMode, {
this.checkUnique();
},
get mode() modes.HINTS,
get prompt() ["Question", UTF8(this.hintMode.prompt) + ": "],
leave: function leave(stack) {
leave.superapply(this, arguments);
if (!stack.push) {
if (hints.hintSession == this)
hints.hintSession = null;
if (this.top)
this.top.removeEventListener("resize", hints.resizeTimer.closure.tell, true);
this.removeHints(0);
}
},
get extended() modes.HINTS,
checkUnique: function _checkUnique() {
if (this.hintNumber == 0)
@@ -252,7 +236,7 @@ var HintSession = Class("HintSession", CommandMode, {
let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none"/>, doc);
let mode = this.hintMode;
let mode = this.mode;
let res = util.evaluateXPath(mode.xpath, doc, true);
let start = this.pageHints.length;
@@ -305,6 +289,18 @@ var HintSession = Class("HintSession", CommandMode, {
return true;
},
leave: function leave(stack) {
if (!stack.push) {
if (hints.hintSession == this)
hints.hintSession = null;
this.continue = false;
if (this.top)
this.top.removeEventListener("resize", hints.resizeTimer.closure.tell, true);
this.removeHints(0);
}
},
/**
* Handle user input.
*
@@ -433,7 +429,7 @@ var HintSession = Class("HintSession", CommandMode, {
if ((modes.extended & modes.HINTS) && !this.continue)
modes.pop();
commandline.lastEcho = null; // Hack.
dactyl.trapErrors("action", this.hintMode,
dactyl.trapErrors(this.mode.action, this.mode,
elem, elem.href || elem.src || "",
this.extendedhintCount, top);
if (this.continue && this.top)
@@ -666,7 +662,6 @@ var Hints = Module("hints", {
if (modes.extended & modes.HINTS)
modes.getStack(0).params.onResize();
});
let appContent = document.getElementById("appcontent");
if (appContent)
events.addSessionListener(appContent, "scroll", this.resizeTimer.closure.tell, false);
@@ -686,9 +681,9 @@ var Hints = Module("hints", {
this.addMode("t", "Follow hint in a new tab", function (elem) buffer.followLink(elem, dactyl.NEW_TAB));
this.addMode("b", "Follow hint in a background tab", function (elem) buffer.followLink(elem, dactyl.NEW_BACKGROUND_TAB));
this.addMode("w", "Follow hint in a new window", function (elem) buffer.followLink(elem, dactyl.NEW_WINDOW));
this.addMode("O", "Generate an :open URL prompt", function (elem, loc) CommandExMode.open("open " + loc));
this.addMode("T", "Generate a :tabopen URL prompt", function (elem, loc) CommandExMode.open("tabopen " + loc));
this.addMode("W", "Generate a :winopen URL prompt", function (elem, loc) CommandExMode.open("winopen " + loc));
this.addMode("O", "Generate an :open URL prompt", function (elem, loc) commandline.open(":", "open " + loc, modes.EX));
this.addMode("T", "Generate a :tabopen URL prompt", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX));
this.addMode("W", "Generate a :winopen URL prompt", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX));
this.addMode("a", "Add a bookmark", function (elem) bookmarks.addSearchKeyword(elem));
this.addMode("S", "Add a search keyword", function (elem) bookmarks.addSearchKeyword(elem));
this.addMode("v", "View hint source", function (elem, loc) buffer.viewSource(loc, false));
@@ -957,19 +952,20 @@ var Hints = Module("hints", {
open: function open(mode, opts) {
this._extendedhintCount = opts.count;
commandline.input(["Normal", mode], "", {
commandline.input(mode, null, {
completer: function (context) {
context.compare = function () 0;
context.completions = [[k, v.prompt] for ([k, v] in Iterator(hints.modes))];
},
onSubmit: function (arg) {
onAccept: function (arg) {
if (arg)
hints.show(arg, opts);
},
onChange: function () {
this.accepted = true;
modes.pop();
util.timeout(function () {
dactyl.trapErrors(hints.show, hints, arg, opts);
});
},
get onCancel() this.onAccept,
onChange: function () { modes.pop(); },
promptHighlight: "Normal"
});
},
@@ -1079,15 +1075,6 @@ var Hints = Module("hints", {
Mode: Struct("name", "prompt", "action", "tags", "filter")
}, {
modes: function () {
modes.addMode("HINTS", {
extended: true,
description: "Active when selecting elements in QuickHint or ExtendedHint mode",
bases: [modes.COMMAND_LINE],
input: true,
ownsBuffer: true
});
},
mappings: function () {
var myModes = config.browserModes.concat(modes.OUTPUT_MULTILINE);
mappings.add(myModes, ["f"],
@@ -1110,23 +1097,25 @@ var Hints = Module("hints", {
mappings.add(modes.HINTS, ["<Return>"],
"Follow the selected hint",
function ({ self }) { self.update(true); });
function () { hints.hintSession.update(true); });
mappings.add(modes.HINTS, ["<Tab>"],
"Focus the next matching hint",
function ({ self }) { self.tab(false); });
function () { hints.hintSession.tab(false); });
mappings.add(modes.HINTS, ["<S-Tab>"],
"Focus the previous matching hint",
function ({ self }) { self.tab(true); });
function () { hints.hintSession.tab(true); });
mappings.add(modes.HINTS, ["<BS>", "<C-h>"],
"Delete the previous character",
function ({ self }) self.backspace());
function () hints.hintSession.backspace());
mappings.add(modes.HINTS, ["<Leader>"],
"Toggle hint filtering",
function ({ self }) { self.escapeNumbers = !self.escapeNumbers; });
function () {
hints.hintSession.escapeNumbers = !hints.hintSession.escapeNumbers;
});
},
options: function () {
const DEFAULT_HINTTAGS =

View File

@@ -113,17 +113,9 @@ 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;
var res = repeat();
}
catch (e) {
events.feedingKeys = false;
}
finally {
let res = dactyl.trapErrors(repeat);
this.executing = false;
}
return res;
}
@@ -303,8 +295,6 @@ var Mappings = Module("mappings", {
this.allHives = [this.user, this.builtin];
},
repeat: Modes.boundProperty(),
hives: Class.memoize(function () array(this.allHives.filter(function (h) h.filter(buffer.uri)))),
get userHives() this.allHives.filter(function (h) h !== this.builtin, this),

View File

@@ -119,6 +119,12 @@ var Modes = Module("modes", {
input: true,
ownsFocus: true
});
this.addMode("COMMAND_LINE", {
char: "c",
description: "Active when the command line is focused",
input: true
});
this.addMode("EMBED", {
input: true,
@@ -154,9 +160,31 @@ var Modes = Module("modes", {
input: true
});
// this._extended modes, can include multiple modes, and even main modes
this.addMode("EX", {
extended: true,
description: "Ex command mode, active when the command line is open for Ex commands",
input: true
}, { history: "command" });
this.addMode("HINTS", {
extended: true,
description: "Active when selecting elements in QuickHint or ExtendedHint mode",
count: false,
ownsBuffer: true
});
this.addMode("INPUT_MULTILINE", {
extended: true,
hidden: true,
input: true
});
this.addMode("LINE", {
extended: true, hidden: true
});
this.addMode("PROMPT", {
extended: true,
description: "Active when a prompt is open in the command line",
input: true
});
this.push(this.NORMAL, 0, {
enter: function (stack, prev) {
@@ -186,9 +214,9 @@ var Modes = Module("modes", {
_getModeMessage: function () {
// when recording a macro
let macromode = "";
if (this.recording)
if (modes.recording)
macromode = "recording";
else if (this.replaying)
else if (modes.replaying)
macromode = "replaying";
let val = this._modeMap[this._main].display();
@@ -264,11 +292,11 @@ var Modes = Module("modes", {
delayed: [],
delay: function (callback, self) { this.delayed.push([callback, self]); },
save: function save(id, obj, prop, test) {
save: function save(id, obj, prop) {
if (!(id in this.boundProperties))
for (let elem in array.iterValues(this._modeStack))
elem.saved[id] = { obj: obj, prop: prop, value: obj[prop], test: test };
this.boundProperties[id] = { obj: Cu.getWeakReference(obj), prop: prop, test: test };
elem.saved[id] = { obj: obj, prop: prop, value: obj[prop] };
this.boundProperties[id] = { obj: Cu.getWeakReference(obj), prop: prop };
},
// helper function to set both modes in one go
@@ -289,26 +317,22 @@ var Modes = Module("modes", {
}
if (stack && stack.pop && stack.pop.params.leave)
dactyl.trapErrors("leave", stack.pop.params,
stack, this.topOfStack);
stack.pop.params.leave(stack, this.topOfStack);
let push = mainMode != null && !(stack && stack.pop) &&
Modes.StackElement(this._main, this._extended, params, {});
if (push && this.topOfStack) {
if (this.topOfStack.params.leave)
dactyl.trapErrors("leave", this.topOfStack.params,
{ push: push }, push);
for (let [id, { obj, prop, test }] in Iterator(this.boundProperties)) {
this.topOfStack.params.leave({ push: push }, push);
for (let [id, { obj, prop }] in Iterator(this.boundProperties)) {
if (!obj.get())
delete this.boundProperties[id];
else
this.topOfStack.saved[id] = { obj: obj.get(), prop: prop, value: obj.get()[prop], test: test };
this.topOfStack.saved[id] = { obj: obj.get(), prop: prop, value: obj.get()[prop] };
}
}
let delayed = this.delayed;
this.delayed.forEach(function ([fn, self]) fn.call(self));
this.delayed = [];
let prev = stack && stack.pop || this.topOfStack;
@@ -316,17 +340,11 @@ var Modes = Module("modes", {
this._modeStack.push(push);
if (stack && stack.pop)
for (let { obj, prop, value, test } in values(this.topOfStack.saved))
if (!test || !test(stack, prev))
for (let { obj, prop, value } in values(this.topOfStack.saved))
obj[prop] = value;
this.show();
delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self));
if (this.topOfStack.params.enter && prev)
dactyl.trapErrors("enter", this.topOfStack.params,
push ? { push: push } : stack || {},
this.topOfStack.params.enter(push ? { push: push } : stack || {},
prev);
dactyl.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack);
@@ -421,7 +439,7 @@ var Modes = Module("modes", {
input: false,
get passUnknown() this.input,
passUnknown: false,
get mask() this,
@@ -458,7 +476,7 @@ var Modes = Module("modes", {
return val === undefined ? value : val;
},
set: function (val) {
modes.save(id, this, prop, desc.test);
modes.save(id, this, prop);
if (desc.set)
value = desc.set.call(this, val);
value = !desc.set || value === undefined ? val : value;

View File

@@ -1,358 +0,0 @@
// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
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: {
eventTarget: this
},
append: <e4x xmlns={XUL} xmlns:dactyl={NS}>
<window id={document.documentElement.id}>
<popupset>
<menupopup id="dactyl-contextmenu" highlight="Events" events="contextEvents">
<menuitem id="dactyl-context-copylink"
label="Copy Link Location" dactyl:group="link"
oncommand="goDoCommand('cmd_copyLink');"/>
<menuitem id="dactyl-context-copypath"
label="Copy File Path" dactyl:group="link path"
oncommand="dactyl.clipboardWrite(document.popupNode.getAttribute('path'));"/>
<menuitem id="dactyl-context-copy"
label="Copy" dactyl:group="selection"
command="cmd_copy"/>
<menuitem id="dactyl-context-selectall"
label="Select All"
command="cmd_selectAll"/>
</menupopup>
</popupset>
</window>
<vbox id={config.commandContainer}>
<vbox class="dactyl-container" id="dactyl-multiline-output-container" hidden="false" collapsed="true">
<iframe id="dactyl-multiline-output" src="dactyl://content/buffer.xhtml"
flex="1" hidden="false" collapsed="false" contextmenu="dactyl-contextmenu"
highlight="Events" />
</vbox>
</vbox>
</e4x>
});
},
__noSuchMethod__: function (meth, args) Buffer[meth].apply(Buffer, [this.body].concat(args)),
get widget() this.widgets.multilineOutput,
widgets: Class.memoize(function () commandline.widgets),
body: Class.memoize(function () this.widget.contentDocument.documentElement),
document: Class.memoize(function () this.widget.contentDocument),
window: Class.memoize(function () this.widget.contentWindow),
/**
* Display a multi-line message.
*
* @param {string} data
* @param {string} highlightGroup
*/
echo: function echo(data, highlightGroup, silent) {
let body = this.document.body;
this.widgets.message = null;
if (!commandline.commandVisible)
commandline.hide();
this._startHints = false;
if (modes.main != modes.OUTPUT_MULTILINE) {
modes.push(modes.OUTPUT_MULTILINE, null, {
onKeyPress: this.closure.onKeyPress,
leave: this.closure(function leave(stack) {
if (stack.pop)
for (let message in values(this.messages))
if (message.leave)
message.leave(stack);
})
});
this.messages = [];
}
// If it's already XML, assume it knows what it's doing.
// Otherwise, white space is significant.
// The problem elsewhere is that E4X tends to insert new lines
// after interpolated data.
XML.ignoreWhitespace = XML.prettyPrinting = false;
if (isObject(data)) {
this.lastOutput = null;
var output = util.xmlToDom(<div class="ex-command-output" style="white-space: nowrap" highlight={highlightGroup}/>,
this.document);
data.document = this.document;
output.appendChild(data.message);
this.messages.push(data);
}
else {
let style = isString(data) ? "pre" : "nowrap";
this.lastOutput = <div class="ex-command-output" style={"white-space: " + style} highlight={highlightGroup}>{data}</div>;
var output = util.xmlToDom(this.lastOutput, this.document);
}
// FIXME: need to make sure an open MOW is closed when commands
// that don't generate output are executed
if (this.widgets.mowContainer.collapsed) {
this.body.scrollTop = 0;
while (body.firstChild)
body.removeChild(body.firstChild);
}
body.appendChild(output);
let str = typeof data !== "xml" && data.message || data;
if (!silent)
dactyl.triggerObserver("echoMultiline", data, highlightGroup, output);
this.resize(true);
if (options["more"] && this.isScrollable(1)) {
// start the last executed command's output at the top of the screen
let elements = this.document.getElementsByClassName("ex-command-output");
elements[elements.length - 1].scrollIntoView(true);
}
else
this.body.scrollTop = this.body.scrollHeight;
dactyl.focus(this.window);
this.updateMorePrompt();
},
events: {
click: function onClick(event) {
if (event.getPreventDefault())
return;
const openLink = function openLink(where) {
event.preventDefault();
dactyl.open(event.target.href, where);
}
if (event.target instanceof HTMLAnchorElement)
switch (events.toString(event)) {
case "<LeftMouse>":
openLink(dactyl.CURRENT_TAB);
break;
case "<MiddleMouse>":
case "<C-LeftMouse>":
case "<C-M-LeftMouse>":
openLink({ where: dactyl.NEW_TAB, background: true });
break;
case "<S-MiddleMouse>":
case "<C-S-LeftMouse>":
case "<C-M-S-LeftMouse>":
openLink({ where: dactyl.NEW_TAB, background: false });
break;
case "<S-LeftMouse>":
openLink(dactyl.NEW_WINDOW);
break;
}
},
unload: function onUnload(event) {
event.preventDefault();
}
},
contextEvents: {
popupshowing: function (event) {
let enabled = {
link: window.document.popupNode instanceof HTMLAnchorElement,
path: window.document.popupNode.hasAttribute("path"),
selection: !window.document.commandDispatcher.focusedWindow.getSelection().isCollapsed
};
for (let node in array.iterValues(event.target.children)) {
let group = node.getAttributeNS(NS, "group");
node.hidden = group && !group.split(/\s+/).every(function (g) enabled[g]);
}
}
},
onContext: function onContext(event) {
return true;
},
onKeyPress: function onKeyPress(event) {
const KILL = false, PASS = true;
if (options["more"] && mow.isScrollable(1))
commandline.updateMorePrompt(false, true);
else {
modes.pop();
events.feedkeys(events.toString(event));
return KILL;
}
return PASS;
},
/**
* Changes the height of the message window to fit in the available space.
*
* @param {boolean} open If true, the widget will be opened if it's not
* already so.
*/
resize: function updateOutputHeight(open, extra) {
if (!open && this.widgets.mowContainer.collapsed)
return;
let doc = this.widget.contentDocument;
let availableHeight = config.outputHeight;
if (!this.widgets.mowContainer.collapsed)
availableHeight += parseFloat(this.widgets.mowContainer.height);
availableHeight -= extra || 0;
doc.body.style.minWidth = this.widgets.commandbar.commandline.scrollWidth + "px";
this.widgets.mowContainer.height = Math.min(doc.body.clientHeight, availableHeight) + "px";
this.timeout(function ()
this.widgets.mowContainer.height = Math.min(doc.body.clientHeight, availableHeight) + "px",
0);
doc.body.style.minWidth = "";
this.visible = true;
},
get spaceNeeded() {
let rect = this.widgets.commandbar.commandline.getBoundingClientRect();
let offset = rect.bottom - window.innerHeight;
return Math.max(0, offset);
},
/**
* Update or remove the multi-line output widget's "MORE" prompt.
*
* @param {boolean} force If true, "-- More --" is shown even if we're
* at the end of the output.
* @param {boolean} showHelp When true, show the valid key sequences
* and what they do.
*/
updateMorePrompt: function updateMorePrompt(force, showHelp) {
if (this.widgets.mowContainer.collapsed)
return this.widgets.message = null;
let elem = this.widget.contentDocument.documentElement;
if (showHelp)
this.widgets.message = ["MoreMsg", "-- More -- SPACE/<C-f>/j: screen/page/line down, <C-b>/<C-u>/k: up, q: quit"];
else if (force || (options["more"] && Buffer.isScrollable(elem, 1)))
this.widgets.message = ["MoreMsg", "-- More --"];
else
this.widgets.message = ["Question", "Press ENTER or type command to continue"];
},
visible: Modes.boundProperty({
set: function set_mowVisible(value) {
this.widgets.mowContainer.collapsed = !value;
let elem = this.widget;
if (!value && elem && elem.contentWindow == document.commandDispatcher.focusedWindow)
document.commandDispatcher.focusedWindow = content;
}
}),
}, {
}, {
mappings: function () {
const PASS = true;
const DROP = false;
const BEEP = {};
mappings.add([modes.COMMAND],
["g<lt>"], "Redisplay the last command output",
function () {
dactyl.assert(commandline.lastOutput, "No previous command output");
mow.echo(mow.lastOutput, "Normal");
});
bind = function bind(keys, description, action, test, default_) {
mappings.add([modes.OUTPUT_MULTILINE],
keys, description,
function (command) {
if (!options["more"])
var res = PASS;
else if (test && !test(command))
res = default_;
else
res = action.call(command);
if (res === PASS || res === DROP)
modes.pop();
else
mow.updateMorePrompt();
if (res === BEEP)
dactyl.beep();
else if (res === PASS)
events.feedkeys(command);
});
}
bind(["j", "<C-e>", "<Down>"], "Scroll down one line",
function () { mow.scrollVertical("lines", 1); },
function () mow.isScrollable(1), BEEP);
bind(["k", "<C-y>", "<Up>"], "Scroll up one line",
function () { mow.scrollVertical("lines", -1); },
function () mow.isScrollable(-1), BEEP);
bind(["<C-j>", "<C-m>", "<Return>"], "Scroll down one line, exit on last line",
function () { mow.scrollVertical("lines", 1); },
function () mow.isScrollable(1), DROP);
// half page down
bind(["<C-d>"], "Scroll down half a page",
function () { mow.scrollVertical("pages", .5); },
function () mow.isScrollable(1), BEEP);
bind(["<C-f>", "<PageDown>"], "Scroll down one page",
function () { mow.scrollVertical("pages", 1); },
function () mow.isScrollable(1), BEEP);
bind(["<Space>"], "Scroll down one page",
function () { mow.scrollVertical("pages", 1); },
function () mow.isScrollable(1), DROP);
bind(["<C-u>"], "Scroll up half a page",
function () { mow.scrollVertical("pages", -.5); },
function () mow.isScrollable(-1), BEEP);
bind(["<C-b>", "<PageUp>"], "Scroll up half a page",
function () { mow.scrollVertical("pages", -1); },
function () mow.isScrollable(-1), BEEP);
bind(["gg"], "Scroll to the beginning of output",
function () { mow.scrollToPercent(null, 0); })
bind(["G"], "Scroll to the end of output",
function () { mow.body.scrollTop = mow.body.scrollHeight; })
// copy text to clipboard
bind(["<C-y>"], "Yank selection to clipboard",
function () { dactyl.clipboardWrite(buffer.getCurrentWord(mow.window)); });
// close the window
bind(["q"], "Close the output window",
function () {},
function () false, DROP);
},
options: function () {
options.add(["more"],
"Pause the message list window when the full output will not fit on one page",
"boolean", true);
}
});

View File

@@ -910,7 +910,7 @@ var Tabs = Module("tabs", {
if (count != null)
tabs.switchTo(String(count));
else
CommandExMode.open("buffer! ");
commandline.open(":", "buffer! ", modes.EX);
},
{ count: true });

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 <pass-all-keys> <C-z> CTRL-Z]]></tags>
<tags><![CDATA[pass-through <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 <pass-next-key> <C-v> CTRL-V]]></tags>
<tags><![CDATA[send-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[<open-clipboard-url> <MiddleMouse> p]]></tags>
<tags><![CDATA[<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>&lt;tab-open-clipboard-url> P</tags>
<tags>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>&lt;reload> r</tags>
<tags>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>&lt;full-reload> R</tags>
<tags>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[<page-info> <C-g>]]></tags>
<tags><![CDATA[<C-g>]]></tags>
<strut/>
<spec>&lt;C-g></spec>
<description>
@@ -35,7 +35,7 @@
</item>
<item>
<tags><![CDATA[<more-page-info> g<C-g>]]></tags>
<tags><![CDATA[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>&lt;scroll-begin> ^ 0</tags>
<tags>^ 0</tags>
<strut/>
<spec>0</spec>
<description>
@@ -107,7 +107,7 @@
</item>
<item>
<tags>&lt;scroll-end> $</tags>
<tags>$</tags>
<spec>$</spec>
<description short="true">
<p>Scroll to the absolute right of the document</p>
@@ -140,7 +140,7 @@
</item>
<item>
<tags>&lt;scroll-percent> N%</tags>
<tags>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[<scroll-column-left> <Left> h]]></tags>
<tags><![CDATA[<Left> h]]></tags>
<strut/>
<spec><oa>count</oa>h</spec>
<description>
@@ -160,7 +160,7 @@
</item>
<item>
<tags><![CDATA[<scroll-line-down> <C-e> <Down> j]]></tags>
<tags><![CDATA[<C-e> <Down> j]]></tags>
<strut/>
<spec><oa>count</oa>j</spec>
<description>
@@ -172,7 +172,7 @@
</item>
<item>
<tags><![CDATA[<scroll-line-up> <C-y> <Up> k]]></tags>
<tags><![CDATA[<C-y> <Up> k]]></tags>
<strut/>
<spec><oa>count</oa>k</spec>
<description>
@@ -184,7 +184,7 @@
</item>
<item>
<tags><![CDATA[<scroll-column-right> <Right> l]]></tags>
<tags><![CDATA[<Right> l]]></tags>
<strut/>
<spec><oa>count</oa>l</spec>
<description>
@@ -196,7 +196,7 @@
</item>
<item>
<tags><![CDATA[<scroll-down> <C-d>]]></tags>
<tags><![CDATA[<C-d>]]></tags>
<strut/>
<spec><oa>count</oa>&lt;C-d></spec>
<description>
@@ -209,7 +209,7 @@
</item>
<item>
<tags><![CDATA[<scroll-up> <C-u>]]></tags>
<tags><![CDATA[<C-u>]]></tags>
<strut/>
<spec><oa>count</oa>&lt;C-u></spec>
<description>
@@ -222,7 +222,7 @@
</item>
<item>
<tags><![CDATA[<scroll-page-up> <S-Space> <PageUp> <C-b>]]></tags>
<tags><![CDATA[<S-Space> <PageUp> <C-b>]]></tags>
<strut/>
<spec><oa>count</oa>&lt;C-b></spec>
<description>
@@ -234,7 +234,7 @@
</item>
<item>
<tags><![CDATA[<scroll-page-down> <Space> <PageDown> <C-f>]]></tags>
<tags><![CDATA[<Space> <PageDown> <C-f>]]></tags>
<strut/>
<spec><oa>count</oa>&lt;C-f></spec>
<description>
@@ -264,7 +264,7 @@
</item>
<item>
<tags>&lt;focus-input> gi</tags>
<tags>gi</tags>
<strut/>
<spec><oa>count</oa>gi</spec>
<description>
@@ -277,7 +277,7 @@
</item>
<item>
<tags>&lt;next-frame> ]f</tags>
<tags>]f</tags>
<strut/>
<spec><oa>count</oa>]f</spec>
<description>
@@ -290,7 +290,7 @@
</item>
<item>
<tags>&lt;previous-frame> [f</tags>
<tags>[f</tags>
<strut/>
<spec><oa>count</oa>[f</spec>
<description>
@@ -303,7 +303,7 @@
</item>
<item>
<tags>&lt;next-page> ]]</tags>
<tags>]]</tags>
<strut/>
<spec><oa>count</oa>]]</spec>
<description>
@@ -316,7 +316,7 @@
</item>
<item>
<tags>&lt;previous-page> [[</tags>
<tags>[[</tags>
<strut/>
<spec><oa>count</oa>[[</spec>
<description>
@@ -361,7 +361,7 @@
</note>
<item>
<tags><![CDATA[<text-zoom-in> + zi]]></tags>
<tags>+ 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><![CDATA[<text-zoom-more> zm]]></tags>
<tags>zm</tags>
<strut/>
<spec><oa>count</oa>zm</spec>
<description>
@@ -378,7 +378,7 @@
</item>
<item>
<tags><![CDATA[<text-zoom-out> - zo]]></tags>
<tags>- 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><![CDATA[<text-zoom-reduce> zr]]></tags>
<tags>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><![CDATA[<text-zoom> zz]]></tags>
<tags>zz</tags>
<strut/>
<spec><oa>count</oa>zz</spec>
<description>
@@ -407,7 +407,7 @@
</item>
<item>
<tags><![CDATA[<full-zoom-in> ZI zI]]></tags>
<tags>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><![CDATA[<full-zoom-more> ZM zM]]></tags>
<tags>ZM zM</tags>
<strut/>
<spec><oa>count</oa>ZM</spec>
<description>
@@ -424,7 +424,7 @@
</item>
<item>
<tags><![CDATA[<full-zoom-out> ZO zO]]></tags>
<tags>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><![CDATA[<full-zoom-reduce> ZR zR]]></tags>
<tags>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><![CDATA[<full-zoom> zZ]]></tags>
<tags>zZ</tags>
<strut/>
<spec><oa>count</oa>zZ</spec>
<description>
@@ -489,7 +489,7 @@
</p>
<item>
<tags>&lt;yank-location> y</tags>
<tags>y</tags>
<spec>y</spec>
<description short="true">
<p>Yank current location to the clipboard.</p>
@@ -497,7 +497,7 @@
</item>
<item>
<tags>&lt;yank-word> Y</tags>
<tags>Y</tags>
<spec>Y</spec>
<description short="true">
<p>Copy currently selected text to the system clipboard.</p>

View File

@@ -218,14 +218,6 @@
</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

@@ -21,7 +21,7 @@
<h2 tag="single-repeat">Single repeats</h2>
<item>
<tags>&lt;repeat-key> .</tags>
<tags>.</tags>
<strut/>
<spec><oa>count</oa>.</spec>
<description>
@@ -45,7 +45,7 @@
<h2 tag="macros complex-repeat">Macros</h2>
<item>
<tags>&lt;record-macro> q</tags>
<tags>q</tags>
<strut/>
<spec>q<a>0-9a-zA-Z</a></spec>
<description>
@@ -82,7 +82,7 @@
</item>
<item>
<tags>&lt;play-macro> @</tags>
<tags>@</tags>
<spec><oa>count</oa>@<a>a-z0-9</a></spec>
<description>
<p>
@@ -100,39 +100,6 @@
</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><![CDATA[<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

@@ -614,9 +614,6 @@ 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

@@ -15,25 +15,24 @@ var RangeFinder = Module("rangefinder", {
Local: function (dactyl, modules, window) ({
init: function () {
this.dactyl = dactyl;
this.modules = modules;
this.commandline = modules.commandline;
this.modes = modules.modes;
this.window = window;
this.options = modules.options;
this.lastFindPattern = "";
},
get commandline() this.modules.commandline,
get modes() this.modules.modes,
get options() this.modules.options,
get rangeFind() modules.buffer.localStore.rangeFind,
set rangeFind(val) modules.buffer.localStore.rangeFind = val
}),
openPrompt: function (mode) {
this.CommandMode(mode).open();
let backwards = mode == this.modes.FIND_BACKWARD;
this.commandline.open(backwards ? "?" : "/", "", mode);
if (this.rangeFind && this.rangeFind.window.get() === this.window)
this.rangeFind.reset();
this.find("", mode === this.modes.FIND_BACKWARD);
this.find("", backwards);
},
bootstrap: function (str, backward) {
@@ -120,12 +119,8 @@ var RangeFinder = Module("rangefinder", {
this.rangeFind.focus();
},
onCancel: function () {
if (this.rangeFind)
this.rangeFind.cancel();
},
onChange: function (command) {
// Called when the user types a key in the find dialog. Triggers a find attempt if 'incfind' is set
onKeyPress: function (command) {
if (this.options["incfind"]) {
command = this.bootstrap(command);
this.rangeFind.find(command);
@@ -143,6 +138,13 @@ var RangeFinder = Module("rangefinder", {
this.rangeFind.focus();
},
// Called when the find is canceled - for example if someone presses
// escape while typing a find
onCancel: function () {
if (this.rangeFind)
this.rangeFind.cancel();
},
/**
* Highlights all occurrences of the last sought for string in the
* current buffer.
@@ -161,12 +163,12 @@ var RangeFinder = Module("rangefinder", {
}
}, {
}, {
/* Must come before commandline. */
modes: function (dactyl, modules, window) {
const { commandline, modes } = modules;
const { modes } = modules;
modes.addMode("FIND", {
extended: true,
description: "Find mode, active when typing search input",
bases: [modes.COMMAND_LINE],
input: true
});
modes.addMode("FIND_FORWARD", {
@@ -174,13 +176,22 @@ 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" });
},
commandline: function (dactyl, modules, window) {
const { commandline, modes, rangefinder } = modules;
commandline.registerCallback("change", modes.FIND_FORWARD, rangefinder.closure.onKeyPress);
commandline.registerCallback("submit", modes.FIND_FORWARD, rangefinder.closure.onSubmit);
commandline.registerCallback("cancel", modes.FIND_FORWARD, rangefinder.closure.onCancel);
commandline.registerCallback("change", modes.FIND_BACKWARD, rangefinder.closure.onKeyPress);
commandline.registerCallback("submit", modes.FIND_BACKWARD, rangefinder.closure.onSubmit);
commandline.registerCallback("cancel", modes.FIND_BACKWARD, rangefinder.closure.onCancel);
},
commands: function (dactyl, modules, window) {
const { commands, rangefinder } = modules;
@@ -189,22 +200,6 @@ var RangeFinder = Module("rangefinder", {
function () { rangefinder.clear(); },
{ argCount: "0" });
},
commandline: function (dactyl, modules, window) {
this.CommandMode = Class("CommandFindMode", modules.CommandMode, {
init: function init(mode) {
this.mode = mode;
init.supercall(this);
},
historyKey: "find",
get prompt() this.mode === modules.modes.FIND_BACKWARD ? "?" : "/",
onCancel: this.closure.onCancel,
onChange: this.closure.onChange,
onSubmit: this.closure.onSubmit
});
},
mappings: function (dactyl, modules, window) {
const { buffer, config, mappings, modes, rangefinder } = modules;
var myModes = config.browserModes.concat([modes.CARET]);

View File

@@ -61,27 +61,6 @@ var IO = Module("io", {
services.downloadManager.addListener(this.downloadListener);
},
CommandFileMode: Class("CommandFileMode", modules.CommandMode, {
init: function init(prompt, params) {
init.supercall(this);
this.prompt = isArray(prompt) ? prompt : ["Question", prompt];
update(this, params);
},
historyKey: "file",
get mode() modules.modes.FILE_INPUT,
complete: function (context) {
if (this.completer)
this.completer(context);
context = context.fork("files", 0);
modules.completion.file(context);
context.filters = context.filters.concat(this.filters || []);
}
}),
destroy: function destroy() {
services.downloadManager.removeListener(this.downloadListener);
for (let [, plugin] in Iterator(plugins.contexts))
@@ -1025,16 +1004,6 @@ unlet s:cpo_save
}]);
},
modes: function (dactyl, modules, window) {
const { commandline, modes } = modules;
modes.addMode("FILE_INPUT", {
extended: true,
description: "Active when selecting a file",
bases: [modes.COMMAND_LINE],
input: true
});
},
options: function (dactyl, modules, window) {
const { options } = modules;

View File

@@ -176,7 +176,6 @@ var Overlay = Module("Overlay", {
"io",
"mappings",
"marks",
"mow",
"options",
"statusline",
"styles",
@@ -204,14 +203,12 @@ var Overlay = Module("Overlay", {
modules.loaded = loaded;
function init(module) {
let name = module.constructor.className;
function init(func, mod)
function () defineModule.time(module.className || module.constructor.className, mod,
func, modules[name],
func, module,
modules.dactyl, modules, window);
set.add(loaded, name);
set.add(loaded, module.constructor.className);
for (let [mod, func] in Iterator(module.INIT)) {
if (mod in loaded)
init(func, mod)();
@@ -243,7 +240,7 @@ var Overlay = Module("Overlay", {
defineModule.loadLog.push("Load" + (isString(prereq) ? " " + prereq + " dependency: " : ": ") + module.className);
if (frame && frame.filename)
defineModule.loadLog.push(" from: " + frame.filename.replace(/.* -> /, "") + ":" + frame.lineNumber);
defineModule.loadLog.push(" from: " + frame.filename + ":" + frame.lineNumber);
delete modules[module.className];
modules[module.className] = defineModule.time(module.className, "init", module);

View File

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

View File

@@ -0,0 +1,140 @@
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

@@ -0,0 +1,13 @@
<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

@@ -0,0 +1,685 @@
/* ***** 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

@@ -0,0 +1,411 @@
/* ***** 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

@@ -0,0 +1,307 @@
/* ***** 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

@@ -0,0 +1,236 @@
/* ***** 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

@@ -0,0 +1,208 @@
/* ***** 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

@@ -0,0 +1,192 @@
/* ***** 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

@@ -0,0 +1,384 @@
/* ***** 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

@@ -0,0 +1,237 @@
/* ***** 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

@@ -0,0 +1,10 @@
# 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

@@ -0,0 +1,131 @@
/* ***** 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

@@ -0,0 +1,836 @@
/* ***** 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

@@ -0,0 +1,318 @@
/* ***** 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

@@ -0,0 +1,530 @@
/* * ***** 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

@@ -0,0 +1,503 @@
/* ***** 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

@@ -0,0 +1,629 @@
/* ***** 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

@@ -0,0 +1,509 @@
/* ***** 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

@@ -0,0 +1,445 @@
/* ***** 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

@@ -0,0 +1,82 @@
/* ***** 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

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,69 @@
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

@@ -0,0 +1,42 @@
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

@@ -0,0 +1,62 @@
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

@@ -0,0 +1,33 @@
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

@@ -0,0 +1,34 @@
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: