X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fcontent%2Fevents.js;h=117d64b5891fcd9daf7969bd8d7f6bf238b2c92a;hb=70740024f9c028c1fd63e1a1850ab062ff956054;hp=0b279fd275757fc435ee6643507ea4ba4080d672;hpb=eeed0be1a8abf7e3c97f43b63c1d595e940fef21;p=dactyl.git diff --git a/common/content/events.js b/common/content/events.js index 0b279fd..117d64b 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -16,22 +16,27 @@ var ProcessorStack = Class("ProcessorStack", { this.buffer = ""; this.events = []; + events.dbg("STACK " + mode); + let main = { __proto__: mode.main, params: mode.params }; - let keyModes = array([mode.params.keyModes, main, mode.main.allBases]).flatten().compact(); + this.modes = array([mode.params.keyModes, main, mode.main.allBases.slice(1)]).flatten().compact(); if (builtin) hives = hives.filter(function (h) h.name === "builtin"); - this.processors = keyModes.map(function (m) hives.map(function (h) KeyProcessor(m, h))) - .flatten().array; + this.processors = this.modes.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; + if (params.preExecute) input.preExecute = params.preExecute; + if (params.postExecute) input.postExecute = params.postExecute; + if (params.onKeyPress && input.hive === mappings.builtin) input.fallthrough = function fallthrough(events) { return params.onKeyPress(events) === false ? Events.KILL : Events.PASS; @@ -39,15 +44,17 @@ var ProcessorStack = Class("ProcessorStack", { } let hive = options.get("passkeys")[this.main.input ? "inputHive" : "commandHive"]; - if (!builtin && hive.active - && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement))) + if (!builtin && hive.active && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement))) this.processors.unshift(KeyProcessor(modes.BASE, hive)); }, + passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.modes)), + notify: function () { + events.dbg("NOTIFY()"); events.keyEvents = []; events.processor = null; - if (!this.execute(Events.KILL, true)) { + if (!this.execute(undefined, true)) { events.processor = this; events.keyEvents = this.keyEvents; } @@ -60,64 +67,91 @@ var ProcessorStack = Class("ProcessorStack", { callable(result) ? result.toSource().substr(0, 50) : result), execute: function execute(result, force) { + events.dbg("EXECUTE(" + this._result(result) + ", " + force + ") events:" + this.events.length + + " processors:" + this.processors.length + " actions:" + this.actions.length); + + let processors = this.processors; + let length = 1; - if (force && this.actions.length) - this.processors.length = 0; + if (force) + this.processors = []; if (this.ownsBuffer) statusline.inputBuffer = this.processors.length ? this.buffer : ""; if (!this.processors.some(function (p) !p.extended) && this.actions.length) { - if (this._actions.length == 0) { - dactyl.beep(); - events.feedingKeys = false; - } + // We have matching actions and no processors other than + // those waiting on further arguments. Execute actions as + // long as they continue to return PASS. for (var action in values(this.actions)) { while (callable(action)) { + length = action.eventLength; action = dactyl.trapErrors(action); - events.dbg("ACTION RES: " + this._result(action)); + events.dbg("ACTION RES: " + length + " " + this._result(action)); } if (action !== Events.PASS) break; } + // Result is the result of the last action. Unless it's + // PASS, kill any remaining argument processors. result = action !== undefined ? action : Events.KILL; if (action !== Events.PASS) this.processors.length = 0; } else if (this.processors.length) { + // We're still waiting on the longest matching processor. + // Kill the event, set a timeout to give up waiting if applicable. + result = Events.KILL; - if (this.actions.length && options["timeout"]) + if (options["timeout"] && (this.actions.length || events.hasNativeKey(this.events[0], this.main, this.passUnknown))) this.timer = services.Timer(this, options["timeoutlen"], services.Timer.TYPE_ONE_SHOT); } else if (result !== Events.KILL && !this.actions.length && - (this.events.length > 1 || - this.processors.some(function (p) !p.main.passUnknown))) { - result = Events.KILL; + !(this.events[0].isReplay || this.passUnknown + || this.modes.some(function (m) m.passEvent(this), this.events[0]))) { + // No patching processors, this isn't a fake, pass-through + // event, we're not in pass-through mode, and we're not + // choosing to pass unknown keys. Kill the event and beep. + + result = Events.ABORT; if (!Events.isEscape(this.events.slice(-1)[0])) dactyl.beep(); events.feedingKeys = false; } else if (result === undefined) + // No matching processors, we're willing to pass this event, + // and we don't have a default action from a processor. Just + // pass the event. result = Events.PASS; - events.dbg("RESULT: " + this._result(result)); - - if (result === Events.PASS || result === Events.PASS_THROUGH) - if (this.events[0].originalTarget) - this.events[0].originalTarget.dactylKeyPress = undefined; + events.dbg("RESULT: " + length + " " + this._result(result) + "\n\n"); if (result !== Events.PASS || this.events.length > 1) - Events.kill(this.events[this.events.length - 1]); + if (result !== Events.ABORT || !this.events[0].isReplay) + Events.kill(this.events[this.events.length - 1]); + + if (result === Events.PASS_THROUGH || result === Events.PASS && this.passUnknown) + events.passing = true; + + if (result === Events.PASS_THROUGH && this.keyEvents.length) + events.dbg("PASS_THROUGH:\n\t" + this.keyEvents.map(function (e) [e.type, events.toString(e)]).join("\n\t")); if (result === Events.PASS_THROUGH) events.feedevents(null, this.keyEvents, { skipmap: true, isMacro: true, isReplay: true }); - else if (result === Events.PASS || result === Events.ABORT) { + else { let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented); - if (list.length) - events.dbg("REFEED: " + list.map(events.closure.toString).join("")); - events.feedevents(null, list, { skipmap: true, isMacro: true, isReplay: true }); + + if (result === Events.PASS) + events.dbg("PASS THROUGH: " + list.slice(0, length).filter(function (e) e.type === "keypress").map(events.closure.toString)); + if (list.length > length) + events.dbg("REFEED: " + list.slice(length).filter(function (e) e.type === "keypress").map(events.closure.toString)); + + if (result === Events.PASS) + events.feedevents(null, list.slice(0, length), { skipmap: true, isMacro: true, isReplay: true }); + if (list.length > length && this.processors.length === 0) + events.feedevents(null, list.slice(length)); } return this.processors.length === 0; @@ -137,7 +171,7 @@ var ProcessorStack = Class("ProcessorStack", { let actions = []; let processors = []; - events.dbg("KEY: " + key + " skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay); + events.dbg("PROCESS(" + key + ") skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay); for (let [i, input] in Iterator(this.processors)) { let res = input.process(event); @@ -149,8 +183,6 @@ var ProcessorStack = Class("ProcessorStack", { if (res === Events.KILL) break; - buffer = buffer || input.inputBuffer; - if (callable(res)) actions.push(res); @@ -162,11 +194,15 @@ var ProcessorStack = Class("ProcessorStack", { events.dbg("RESULT: " + event.getPreventDefault() + " " + this._result(result)); events.dbg("ACTIONS: " + actions.length + " " + this.actions.length); - events.dbg("PROCESSORS:", processors); + events.dbg("PROCESSORS:", processors, "\n"); this._actions = actions; this.actions = actions.concat(this.actions); + for (let action in values(actions)) + if (!("eventLength" in action)) + action.eventLength = this.events.length; + if (result === Events.KILL) this.actions = []; else if (!this.actions.length && !processors.length) @@ -220,8 +256,10 @@ var KeyProcessor = Class("KeyProcessor", { 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)); + + args.self = self.main.params.mappingSelf || self.main.mappingSelf || map; + let res = map.execute.call(map, args); + if (self.postExecute) self.postExecute.apply(self, args); return res; @@ -285,6 +323,10 @@ var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, { } }); +/** + * A hive used mainly for tracking event listeners and cleaning them up when a + * group is destroyed. + */ var EventHive = Class("EventHive", Contexts.Hive, { init: function init(group) { init.supercall(this, group); @@ -312,9 +354,12 @@ var EventHive = Class("EventHive", Contexts.Hive, { var [self, events] = [null, array.toObject([[event, callback]])]; else { [self, events] = [event, event[callback || "events"]]; - [,, capture, allowUntrusted] = arguments; + [, , capture, allowUntrusted] = arguments; } + if (Set.has(events, "input") && !Set.has(events, "dactyl-input")) + events["dactyl-input"] = events.input; + for (let [event, callback] in Iterator(events)) { let args = [Cu.getWeakReference(target), event, @@ -354,7 +399,6 @@ var Events = Module("events", { dbg: function () {}, init: function () { - const self = this; this.keyEvents = []; update(this, { @@ -369,9 +413,7 @@ var Events = Module("events", { util.overlayWindow(window, { append: - - + = 0 && doc.documentElement + || doc.defaultView; + + if (target instanceof Element && !Events.isInputElement(target) && + ["", ""].indexOf(key) == -1) + target = target.ownerDocument.documentElement; + + if (!evt_obj.dactylString && !mode) + events.dispatch(target, event, evt); else if (type === "keypress") events.events.keypress.call(events, event); } @@ -689,8 +737,7 @@ var Events = Module("events", { * @param {Object} opts The pseudo-event. @optional */ create: function (doc, type, opts) { - opts = opts || {}; - var DEFAULTS = { + const DEFAULTS = { HTML: { type: type, bubbles: true, cancelable: false }, @@ -713,22 +760,31 @@ var Events = Module("events", { relatedTarget: null } }; - const TYPES = { - change: "", input: "", submit: "", - click: "Mouse", mousedown: "Mouse", mouseup: "Mouse", - mouseover: "Mouse", mouseout: "Mouse", - keypress: "Key", keyup: "Key", keydown: "Key" - }; - var t = TYPES[type]; + + opts = opts || {}; + + var t = this._create_types[type]; var evt = doc.createEvent((t || "HTML") + "Events"); let defaults = DEFAULTS[t || "HTML"]; - evt["init" + t + "Event"].apply(evt, Object.keys(defaults) - .map(function (k) k in opts ? opts[k] - : defaults[k])); + + let args = Object.keys(defaults) + .map(function (k) k in opts ? opts[k] : defaults[k]); + + evt["init" + t + "Event"].apply(evt, args); return evt; }, + _create_types: Class.memoize(function () iter( + { + Mouse: "click mousedown mouseout mouseover mouseup", + Key: "keydown keypress keyup", + "": "change dactyl-input input submit" + } + ).map(function ([k, v]) v.split(" ").map(function (v) [v, k])) + .flatten() + .toObject()), + /** * Converts a user-input string of keys into a canonical * representation. @@ -755,11 +811,11 @@ var Events = Module("events", { return events.fromString(keys, unknownOk).map(events.closure.toString).join(""); }, - iterKeys: function (keys) { + iterKeys: function (keys) iter(function () { let match, re = /<.*?>?>|[^<]/g; while (match = re.exec(keys)) yield match[0]; - }, + }()), /** * Dispatches an event to an element as if it were a native event. @@ -829,34 +885,40 @@ var Events = Module("events", { let out = []; for (let match in util.regexp.iterate(/<.*?>?>|[^<]|<(?!.*>)/g, input)) { let evt_str = match[0]; + let evt_obj = { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false, keyCode: 0, charCode: 0, type: "keypress" }; - if (evt_str.length > 1) { // <.*?> - let [match, modifier, keyname] = evt_str.match(/^<((?:[CSMA]-)*)(.+?)>$/i) || [false, '', '']; - modifier = modifier.toUpperCase(); + if (evt_str.length == 1) { + evt_obj.charCode = evt_str.charCodeAt(0); + evt_obj._keyCode = this._key_code[evt_str[0].toLowerCase()]; + evt_obj.shiftKey = evt_str !== evt_str.toLowerCase(); + } + else { + let [match, modifier, keyname] = evt_str.match(/^<((?:[*12CASM⌘]-)*)(.+?)>$/i) || [false, '', '']; + modifier = Set(modifier.toUpperCase()); keyname = keyname.toLowerCase(); evt_obj.dactylKeyname = keyname; if (/^u[0-9a-f]+$/.test(keyname)) keyname = String.fromCharCode(parseInt(keyname.substr(1), 16)); if (keyname && (unknownOk || keyname.length == 1 || /mouse$/.test(keyname) || - this._key_code[keyname] || set.has(this._pseudoKeys, keyname))) { - evt_obj.ctrlKey = /C-/.test(modifier); - evt_obj.altKey = /A-/.test(modifier); - evt_obj.shiftKey = /S-/.test(modifier); - evt_obj.metaKey = /M-/.test(modifier); + this._key_code[keyname] || Set.has(this._pseudoKeys, keyname))) { + evt_obj.globKey ="*" in modifier; + evt_obj.ctrlKey ="C" in modifier; + evt_obj.altKey ="A" in modifier; + evt_obj.shiftKey ="S" in modifier; + evt_obj.metaKey ="M" in modifier || "⌘" in modifier; + evt_obj.dactylShift = evt_obj.shiftKey; if (keyname.length == 1) { // normal characters - if (evt_obj.shiftKey) { + if (evt_obj.shiftKey) keyname = keyname.toUpperCase(); - if (keyname == keyname.toLowerCase()) - evt_obj.dactylShift = true; - } evt_obj.charCode = keyname.charCodeAt(0); + evt_obj._keyCode = this._key_code[keyname.toLowerCase()]; } - else if (set.has(this._pseudoKeys, keyname)) { + else if (Set.has(this._pseudoKeys, keyname)) { evt_obj.dactylString = "<" + this._key_key[keyname] + ">"; } else if (/mouse$/.test(keyname)) { // mouse events @@ -875,8 +937,6 @@ var Events = Module("events", { continue; } } - else // a simple key (no <...>) - evt_obj.charCode = evt_str.charCodeAt(0); // TODO: make a list of characters that need keyCode and charCode somewhere if (evt_obj.keyCode == 32 || evt_obj.charCode == 32) @@ -884,10 +944,10 @@ var Events = Module("events", { if (evt_obj.keyCode == 60 || evt_obj.charCode == 60) evt_obj.charCode = evt_obj.keyCode = 60; // - evt_obj.modifiers = (evt_obj.ctrlKey && Ci.nsIDOMNSEvent.CONTROL_MASK) - | (evt_obj.altKey && Ci.nsIDOMNSEvent.ALT_MASK) + evt_obj.modifiers = (evt_obj.ctrlKey && Ci.nsIDOMNSEvent.CONTROL_MASK) + | (evt_obj.altKey && Ci.nsIDOMNSEvent.ALT_MASK) | (evt_obj.shiftKey && Ci.nsIDOMNSEvent.SHIFT_MASK) - | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK); + | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK); out.push(evt_obj); } @@ -911,6 +971,8 @@ var Events = Module("events", { let key = null; let modifier = ""; + if (event.globKey) + modifier += "*-"; if (event.ctrlKey) modifier += "C-"; if (event.altKey) @@ -931,6 +993,7 @@ var Events = Module("events", { key = key.toUpperCase(); else key = key.toLowerCase(); + if (!modifier && /^[a-z0-9]$/i.test(key)) return key; } @@ -975,7 +1038,7 @@ var Events = Module("events", { else { // a shift modifier is only allowed if the key is alphabetical and used in a C-A-M- mapping in the uppercase, // or if the shift has been forced for a non-alphabetical character by the user while :map-ping - if (key != key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift) + if (key !== key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift) modifier += "S-"; if (/^\s$/.test(key)) key = let (s = charCode.toString(16)) "U" + "0000".substr(4 - s.length) + s; @@ -983,8 +1046,11 @@ var Events = Module("events", { return key; } } - if (key == null) + if (key == null) { + if (event.shiftKey) + modifier += "S-"; key = this._key_key[event.dactylKeyname] || event.dactylKeyname; + } if (key == null) return null; } @@ -1015,8 +1081,71 @@ var Events = Module("events", { }, /** - * Whether *key* is a key code defined to accept/execute input on the - * command line. + * Returns true if there's a known native key handler for the given + * event in the given mode. + * + * @param {Event} event A keypress event. + * @param {Modes.Mode} mode The main mode. + * @param {boolean} passUnknown Whether unknown keys should be passed. + */ + hasNativeKey: function hasNativeKey(event, mode, passUnknown) { + if (mode.input && event.charCode && !(event.ctrlKey || event.metaKey)) + return true; + + if (!passUnknown) + return false; + + var elements = document.getElementsByTagNameNS(XUL, "key"); + var filters = []; + + if (event.keyCode) + filters.push(["keycode", this._code_nativeKey[event.keyCode]]); + if (event.charCode) { + let key = String.fromCharCode(event.charCode); + filters.push(["key", key.toUpperCase()], + ["key", key.toLowerCase()]); + } + + let accel = util.OS.isMacOSX ? "metaKey" : "ctrlKey"; + + let access = iter({ 1: "shiftKey", 2: "ctrlKey", 4: "altKey", 8: "metaKey" }) + .filter(function ([k, v]) this & k, prefs.get("ui.key.chromeAccess")) + .map(function ([k, v]) [v, true]) + .toObject(); + + outer: + for (let [, key] in iter(elements)) + if (filters.some(function ([k, v]) key.getAttribute(k) == v)) { + let keys = { ctrlKey: false, altKey: false, shiftKey: false, metaKey: false }; + let needed = { ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, metaKey: event.metaKey }; + + let modifiers = (key.getAttribute("modifiers") || "").trim().split(/[\s,]+/); + for (let modifier in values(modifiers)) + switch (modifier) { + case "access": update(keys, access); break; + case "accel": keys[accel] = true; break; + default: keys[modifier + "Key"] = true; break; + case "any": + if (!iter.some(keys, function ([k, v]) v && needed[k])) + continue outer; + for (let [k, v] in iter(keys)) { + if (v) + needed[k] = false; + keys[k] = false; + } + break; + } + + if (iter(needed).every(function ([k, v]) v == keys[k])) + return key; + } + + return false; + }, + + /** + * Returns true if *key* is a key code defined to accept/execute input on + * the command line. * * @param {string} key The key code to test. * @returns {boolean} @@ -1024,14 +1153,21 @@ var Events = Module("events", { isAcceptKey: function (key) key == "" || key == "" || key == "", /** - * Whether *key* is a key code defined to reject/cancel input on the - * command line. + * Returns true if *key* is a key code defined to reject/cancel input on + * the command line. * * @param {string} key The key code to test. * @returns {boolean} */ isCancelKey: function (key) key == "" || key == "" || key == "", + /** + * Returns true if *node* belongs to the current content document or any + * sub-frame thereof. + * + * @param {Node|Document|Window} node The node to test. + * @returns {boolean} + */ isContentNode: function isContentNode(node) { let win = (node.ownerDocument || node).defaultView || node; return XPCNativeWrapper(win).top == content; @@ -1047,11 +1183,12 @@ var Events = Module("events", { if (buffer.loaded) return true; - dactyl.echo(_("macro.loadWaiting"), commandline.DISALLOW_MULTILINE); + dactyl.echo(_("macro.loadWaiting"), commandline.FORCE_SINGLELINE); const maxWaitTime = (time || 25); - util.waitFor(function () !events.feedingKeys || buffer.loaded, this, maxWaitTime * 1000, true); + util.waitFor(function () buffer.loaded, this, maxWaitTime * 1000, true); + dactyl.echo("", commandline.FORCE_SINGLELINE); if (!buffer.loaded) dactyl.echoerr(_("macro.loadFailed", maxWaitTime)); @@ -1115,14 +1252,19 @@ var Events = Module("events", { let win = (elem.ownerDocument || elem).defaultView || elem; - if (events.isContentNode(elem) && !buffer.focusAllowed(elem) - && !(services.focus.getLastFocusMethod(win) & 0x7000) + if (!(services.focus.getLastFocusMethod(win) & 0x7000) + && events.isContentNode(elem) + && !buffer.focusAllowed(elem) && isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, Window])) { + if (elem.frameElement) dactyl.focusContent(true); else if (!(elem instanceof Window) || Editor.getEditor(elem)) dactyl.focus(window); } + + if (elem instanceof Element) + elem.dactylFocusAllowed = undefined; }, /* @@ -1178,7 +1320,7 @@ var Events = Module("events", { elem.dactylKeyPress = elem.value; util.timeout(function () { if (elem.dactylKeyPress !== undefined && elem.value !== elem.dactylKeyPress) - events.dispatch(elem, events.create(elem.ownerDocument, "input")); + events.dispatch(elem, events.create(elem.ownerDocument, "dactyl-input")); elem.dactylKeyPress = undefined; }); } @@ -1214,20 +1356,19 @@ var Events = Module("events", { let ignore = false; - if (modes.main == modes.PASS_THROUGH) + if (mode.main == modes.PASS_THROUGH) ignore = !Events.isEscape(key) && key != ""; - else if (modes.main == modes.QUOTE) { + else if (mode.main == modes.QUOTE) { if (modes.getStack(1).main == modes.PASS_THROUGH) { - mode.params.mainMode = modes.getStack(2).main; + mode = Modes.StackElement(modes.getStack(2).main); ignore = Events.isEscape(key); } else if (events.shouldPass(event)) - mode.params.mainMode = modes.getStack(1).main; + mode = Modes.StackElement(modes.getStack(1).main); else ignore = true; - if (ignore && !Events.isEscape(key)) - modes.pop(); + modes.pop(); } else if (!event.isMacro && !event.noremap && events.shouldPass(event)) ignore = true; @@ -1277,25 +1418,40 @@ var Events = Module("events", { }, keyup: function onKeyUp(event) { - this.keyEvents.push(event); + if (event.type == "keydown") + this.keyEvents.push(event); + else if (!this.processor) + this.keyEvents = []; - let pass = this.feedingEvent && this.feedingEvent.isReplay || + let pass = this.passing && !event.isMacro || + this.feedingEvent && this.feedingEvent.isReplay || event.isReplay || modes.main == modes.PASS_THROUGH || modes.main == modes.QUOTE && modes.getStack(1).main !== modes.PASS_THROUGH && !this.shouldPass(event) || - !modes.passThrough && this.shouldPass(event); + !modes.passThrough && this.shouldPass(event) || + !this.processor && event.type === "keydown" + && options.get("passunknown").getKey(modes.main.allBases) + && let (key = events.toString(event)) + !modes.main.allBases.some( + function (mode) mappings.hives.some( + function (hive) hive.get(mode, key) || hive.getCandidates(mode, key))); + + if (event.type === "keydown") + this.passing = pass; - events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass); + events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass + " replay: " + event.isReplay + " macro: " + event.isMacro); // Prevents certain sites from transferring focus to an input box // before we get a chance to process our key bindings on the // "keypress" event. - if (!pass && !Events.isInputElement(dactyl.focusedElement)) + if (!pass) event.stopPropagation(); }, keydown: function onKeyDown(event) { + if (!event.isMacro) + this.passing = false; this.events.keyup.call(this, event); }, @@ -1303,8 +1459,11 @@ var Events = Module("events", { let elem = event.target; let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem; - for (; win; win = win != win.parent && win.parent) + for (; win; win = win != win.parent && win.parent) { + for (; elem instanceof Element; elem = elem.parentNode) + elem.dactylFocusAllowed = true; win.document.dactylFocusAllowed = true; + } }, popupshown: function onPopupShown(event) { @@ -1322,8 +1481,7 @@ var Events = Module("events", { modes.push(modes.MENU); }, - popuphidden: function onPopupHidden() { - // gContextMenu is set to NULL, when a context menu is closed + popuphidden: function onPopupHidden(event) { if (window.gContextMenu == null && !this._activeMenubar) modes.remove(modes.MENU, true); modes.remove(modes.AUTOCOMPLETE); @@ -1406,11 +1564,15 @@ var Events = Module("events", { if (elem == null && urlbar && urlbar.inputField == this._lastFocus) util.threadYield(true); // Why? --Kris - while (modes.main.ownsFocus && !modes.topOfStack.params.holdFocus) + while (modes.main.ownsFocus && modes.topOfStack.params.ownsFocus != elem + && !modes.topOfStack.params.holdFocus) modes.pop(null, { fromFocus: true }); } finally { this._lastFocus = elem; + + if (modes.main.ownsFocus) + modes.topOfStack.params.ownsFocus = elem; } }, @@ -1445,20 +1607,25 @@ var Events = Module("events", { key === "" || key === "", isHidden: function isHidden(elem, aggressive) { - for (let e = elem; e instanceof Element; e = e.parentNode) { - if (util.computedStyle(e).visibility !== "visible" || - aggressive && e.boxObject && e.boxObject.height === 0) - return true; - } + if (util.computedStyle(elem).visibility !== "visible") + return true; + + if (aggressive) + for (let e = elem; e instanceof Element; e = e.parentNode) { + if (!/set$/.test(e.localName) && e.boxObject && e.boxObject.height === 0) + return true; + else if (e.namespaceURI == XUL && e.localName === "panel") + break; + } return false; }, isInputElement: function isInputElement(elem) { - return elem instanceof HTMLInputElement && set.has(util.editableInputs, elem.type) || - isinstance(elem, [HTMLIsIndexElement, HTMLEmbedElement, + return elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type) || + isinstance(elem, [HTMLEmbedElement, HTMLObjectElement, HTMLSelectElement, HTMLTextAreaElement, - Ci.nsIDOMXULTreeElement, Ci.nsIDOMXULTextBoxElement]) || + Ci.nsIDOMXULTextBoxElement]) || elem instanceof Window && Editor.getEditor(elem); }, @@ -1480,12 +1647,13 @@ var Events = Module("events", { else dactyl.echoerr(_("error.argumentRequired")); }, { + argCount: "?", bang: true, completer: function (context) completion.macro(context), literal: 0 }); - commands.add(["macros"], + commands.add(["mac[ros]"], "List all macros", function (args) { completion.listCompleter("macro", args[0]); }, { argCount: "?", @@ -1501,7 +1669,7 @@ var Events = Module("events", { mappings: function () { mappings.add([modes.MAIN], - [""], "Process the next key as a builtin mapping", + ["", ""], "Process the next key as a builtin mapping", function () { events.processor = ProcessorStack(modes.getStack(0), mappings.hives.array, true); events.processor.keyEvents = events.keyEvents; @@ -1511,7 +1679,7 @@ var Events = Module("events", { ["", ""], "Temporarily ignore all " + config.appName + " key bindings", function () { modes.push(modes.PASS_THROUGH); }); - mappings.add([modes.MAIN], + mappings.add([modes.MAIN, modes.PASS_THROUGH, modes.QUOTE], ["", ""], "Pass through next key", function () { if (modes.main == modes.QUOTE) @@ -1519,6 +1687,10 @@ var Events = Module("events", { modes.push(modes.QUOTE); }); + mappings.add([modes.BASE], + [""], "Do Nothing", + function () {}); + mappings.add([modes.BASE], [""], "Do nothing", function () {}); @@ -1594,12 +1766,12 @@ var Events = Module("events", { "sitemap", "", { flush: function flush() { memoize(this, "filters", function () this.value.filter(function (f) f(buffer.documentURI))); - memoize(this, "pass", function () set(array.flatten(this.filters.map(function (f) f.keys)))); + memoize(this, "pass", function () Set(array.flatten(this.filters.map(function (f) f.keys)))); memoize(this, "commandHive", function hive() Hive(this.filters, "command")); memoize(this, "inputHive", function hive() Hive(this.filters, "input")); }, - has: function (key) set.has(this.pass, key) || set.has(this.commandHive.stack.mappings, key), + has: function (key) Set.has(this.pass, key) || Set.has(this.commandHive.stack.mappings, key), get pass() (this.flush(), this.pass), @@ -1611,7 +1783,7 @@ var Events = Module("events", { filter.keys = events.fromString(vals[0]).map(events.closure.toString); filter.commandKeys = vals.slice(1).map(events.closure.canonicalKeys); - filter.inputKeys = filter.commandKeys.filter(/^<[ACM]-/); + filter.inputKeys = filter.commandKeys.filter(bind("test", /^<[ACM]-/)); }); this.flush(); return values; @@ -1620,7 +1792,14 @@ var Events = Module("events", { options.add(["strictfocus", "sf"], "Prevent scripts from focusing input elements without user intervention", - "boolean", true); + "sitemap", "'chrome:*':laissez-faire,*:moderate", + { + values: { + despotic: "Only allow focus changes when explicitly requested by the user", + moderate: "Allow focus changes after user-initiated focus change", + "laissez-faire": "Always allow focus changes" + } + }); options.add(["timeout", "tmo"], "Whether to execute a shorter key command after a timeout when a longer command exists",