1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2014 Kris Maglione <maglione.k at Gmail>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
12 * A hive used mainly for tracking event listeners and cleaning them up when a
15 var EventHive = Class("EventHive", Contexts.Hive, {
16 init: function init(group) {
17 init.supercall(this, group);
18 this.sessionListeners = [];
21 cleanup: function cleanup() {
25 _events: function _events(event, callback) {
27 var [self, events] = [null, array.toObject([[event, callback]])];
29 [self, events] = [event, event[callback || "events"]];
31 if (hasOwnProperty(events, "input") && !hasOwnProperty(events, "dactyl-input"))
32 events["dactyl-input"] = events.input;
34 return [self, events];
38 * Adds an event listener for this session and removes it on
41 * @param {Element} target The element on which to listen.
42 * @param {string} event The event to listen for.
43 * @param {function} callback The function to call when the event is received.
44 * @param {boolean} capture When true, listen during the capture
45 * phase, otherwise during the bubbling phase.
46 * @param {boolean} allowUntrusted When true, allow capturing of
49 listen: function (target, event, callback, capture, allowUntrusted) {
50 var [self, events] = this._events(event, callback);
52 for (let [event, callback] in Iterator(events)) {
53 let args = [util.weakReference(target),
54 util.weakReference(self),
56 this.wrapListener(callback, self),
60 target.addEventListener.apply(target, args.slice(2));
61 this.sessionListeners.push(args);
66 * Remove an event listener.
68 * @param {Element} target The element on which to listen.
69 * @param {string} event The event to listen for.
70 * @param {function} callback The function to call when the event is received.
71 * @param {boolean} capture When true, listen during the capture
72 * phase, otherwise during the bubbling phase.
74 unlisten: function (target, event, callback, capture) {
76 var [self, events] = this._events(event, callback);
78 this.sessionListeners = this.sessionListeners.filter(function (args) {
79 let elem = args[0].get();
80 if (target == null || elem == target
81 && self == args[1].get()
82 && hasOwnProperty(events, args[2])
83 && args[3].wrapped == events[args[2]]
84 && args[4] == capture) {
86 elem.removeEventListener.apply(elem, args.slice(2));
93 get wrapListener() events.bound.wrapListener
99 var Events = Module("events", {
105 overlay.overlayWindow(window, {
107 ["window", { id: document.documentElement.id, xmlns: "xul" },
108 // http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands
109 ["commandset", { id: "dactyl-onfocus", commandupdater: "true", events: "focus",
110 commandupdate: this.bound.onFocusChange }],
111 ["commandset", { id: "dactyl-onselect", commandupdater: "true", events: "select",
112 commandupdate: this.bound.onSelectionChange }]]]
115 this._fullscreen = window.fullScreen;
116 this._lastFocus = { get: function () null };
117 this._macroKeys = [];
118 this._lastMacro = "";
120 this._macros = storage.newMap("registers", { privateData: true, store: true });
121 if (storage.exists("macros")) {
122 for (let [k, m] in storage.newMap("macros", { store: true }))
123 this._macros.set(k, { text: m.keys, timestamp: m.timeRecorded * 1000 });
124 storage.remove("macros");
132 update: function update(elem) {
134 if (elem instanceof Ci.nsIAutoCompletePopup
135 || elem.localName == "tooltip"
136 || !elem.popupBoxObject)
139 if (!~this.active.indexOf(elem))
140 this.active.push(elem);
143 this.active = this.active.filter(e => e.popupBoxObject && e.popupBoxObject.popupState != "closed");
145 if (!this.active.length && !this.activeMenubar)
146 modes.remove(modes.MENU, true);
147 else if (modes.main != modes.MENU)
148 modes.push(modes.MENU);
152 DOMMenuBarActive: function onDOMMenuBarActive(event) {
153 this.activeMenubar = event.target;
154 if (modes.main != modes.MENU)
155 modes.push(modes.MENU);
158 DOMMenuBarInactive: function onDOMMenuBarInactive(event) {
159 this.activeMenubar = null;
160 modes.remove(modes.MENU, true);
163 popupshowing: function onPopupShowing(event) {
164 this.update(event.originalTarget);
167 popupshown: function onPopupShown(event) {
168 let elem = event.originalTarget;
171 if (elem instanceof Ci.nsIAutoCompletePopup) {
172 if (modes.main != modes.AUTOCOMPLETE)
173 modes.push(modes.AUTOCOMPLETE);
175 else if (elem.hidePopup && elem.localName !== "tooltip"
176 && Events.isHidden(elem)
177 && Events.isHidden(elem.parentNode)) {
182 popuphidden: function onPopupHidden(event) {
184 modes.remove(modes.AUTOCOMPLETE);
189 this.listen(window, this, "events", true);
190 this.listen(window, this.popups, "events", true);
193 this.grabFocusTimer = Timer(100, 10000, () => {
198 cleanup: function cleanup() {
199 let elem = dactyl.focusedElement;
200 if (DOM(elem).isEditable)
201 util.trapErrors("removeEditActionListener",
202 DOM(elem).editor, editor);
206 "browser.locationChange": function (webProgress, request, uri) {
207 options.get("passkeys").flush();
209 "modes.change": function (oldMode, newMode) {
210 delete this.processor;
214 get listen() this.builtin.bound.listen,
215 addSessionListener: deprecated("events.listen", { get: function addSessionListener() this.listen }),
218 * Wraps an event listener to ensure that errors are reported.
220 wrapListener: function wrapListener(method, self=this) {
221 method.wrapper = wrappedListener;
222 wrappedListener.wrapped = method;
223 function wrappedListener(event) {
225 method.apply(self, arguments);
228 dactyl.reportError(e);
229 if (e.message == "Interrupted")
230 dactyl.echoerr(_("error.interrupted"), commandline.FORCE_SINGLELINE);
232 dactyl.echoerr(_("event.error", event.type, e.echoerr || e),
233 commandline.FORCE_SINGLELINE);
236 return wrappedListener;
240 * @property {boolean} Whether synthetic key events are currently being
246 * Initiates the recording of a key event macro.
248 * @param {string} macro The name for the macro.
251 get recording() this._recording,
253 set recording(macro) {
254 dactyl.assert(macro == null || /[a-zA-Z0-9]/.test(macro),
255 _("macro.invalid", macro));
257 modes.recording = macro;
259 if (/[A-Z]/.test(macro)) { // Append.
260 macro = macro.toLowerCase();
261 this._macroKeys = DOM.Event.iterKeys(editor.getRegister(macro))
264 else if (macro) { // Record afresh.
265 this._macroKeys = [];
267 else if (this.recording) { // Save.
268 editor.setRegister(this.recording, this._macroKeys.join(""));
270 dactyl.log(_("macro.recorded", this.recording, this._macroKeys.join("")), 9);
271 dactyl.echomsg(_("macro.recorded", this.recording));
273 this._recording = macro || null;
279 * @param {string} The name of the macro to replay.
282 playMacro: function (macro) {
283 dactyl.assert(/^[a-zA-Z0-9@]$/.test(macro),
284 _("macro.invalid", macro));
287 dactyl.assert(this._lastMacro, _("macro.noPrevious"));
289 this._lastMacro = macro.toLowerCase(); // XXX: sets last played macro, even if it does not yet exist
291 let keys = editor.getRegister(this._lastMacro);
293 return modes.withSavedValues(["replaying"], function () {
294 this.replaying = true;
295 return events.feedkeys(keys, { noremap: true });
298 // TODO: ignore this like Vim?
299 dactyl.echoerr(_("macro.noSuch", this._lastMacro));
304 * Returns all macros matching *filter*.
306 * @param {string} filter A regular expression filter string. A null
307 * filter selects all macros.
309 getMacros: function (filter) {
310 let re = RegExp(filter || "");
311 return ([k, m.text] for ([k, m] in editor.registers) if (re.test(k)));
315 * Deletes all macros matching *filter*.
317 * @param {string} filter A regular expression filter string. A null
318 * filter deletes all macros.
320 deleteMacros: function (filter) {
321 let re = RegExp(filter || "");
322 for (let [item, ] in editor.registers) {
323 if (!filter || re.test(item))
324 editor.registers.remove(item);
329 * Feeds a list of events to *target* or the originalTarget member
330 * of each event if *target* is null.
332 * @param {EventTarget} target The destination node for the events.
334 * @param {[Event]} list The events to dispatch.
335 * @param {object} extra Extra properties for processing by dactyl.
338 feedevents: function feedevents(target, list, extra) {
339 list.forEach(function _feedevent(event, i) {
340 let elem = target || event.originalTarget;
342 let doc = elem.ownerDocument || elem.document || elem;
343 let evt = DOM.Event(doc, event.type, event);
344 DOM.Event.dispatch(elem, evt, extra);
346 else if (i > 0 && event.type === "keypress")
347 events.events.keypress.call(events, event);
352 * Pushes keys onto the event queue from dactyl. It is similar to
353 * Vim's feedkeys() method, but cannot cope with 2 partially-fed
354 * strings, you have to feed one parseable string.
356 * @param {string} keys A string like "2<C-f>" to push onto the event
357 * queue. If you want "<" to be taken literally, prepend it with a
359 * @param {boolean} noremap Whether recursive mappings should be
361 * @param {boolean} silent Whether the command should be echoed to the
365 feedkeys: function (keys, noremap, quiet, mode) {
367 var savedEvents = this._processor && this._processor.keyEvents;
369 var wasFeeding = this.feedingKeys;
370 this.feedingKeys = true;
372 var wasQuiet = commandline.quiet;
374 commandline.quiet = quiet;
376 for (let [, evt_obj] in Iterator(DOM.Event.parse(keys))) {
377 let now = Date.now();
378 let key = DOM.Event.stringify(evt_obj);
379 for (let type in values(["keydown", "keypress", "keyup"])) {
380 let evt = update({}, evt_obj, { type: type });
381 if (type !== "keypress" && !evt.keyCode)
382 evt.keyCode = evt._keyCode || 0;
384 if (isObject(noremap))
385 update(evt, noremap);
387 evt.noremap = !!noremap;
389 evt.dactylMode = mode;
390 evt.dactylSavedEvents = savedEvents;
391 DOM.Event.feedingEvent = evt;
393 let doc = document.commandDispatcher.focusedWindow.document;
395 let target = dactyl.focusedElement
396 || ["complete", "interactive"].indexOf(doc.readyState) >= 0 && doc.documentElement
399 if (target instanceof Element && !Events.isInputElement(target) &&
400 ["<Return>", "<Space>"].indexOf(key) == -1)
401 target = target.ownerDocument.documentElement;
403 let event = DOM.Event(doc, type, evt);
404 if (!evt_obj.dactylString && !mode)
405 DOM.Event.dispatch(target, event, evt);
406 else if (type === "keypress")
407 events.events.keypress.call(events, event);
410 if (!this.feedingKeys)
418 DOM.Event.feedingEvent = null;
419 this.feedingKeys = wasFeeding;
421 commandline.quiet = wasQuiet;
422 dactyl.triggerObserver("events.doneFeeding");
427 canonicalKeys: deprecated("DOM.Event.canonicalKeys", { get: function canonicalKeys() DOM.Event.bound.canonicalKeys }),
428 create: deprecated("DOM.Event", function create() DOM.Event.apply(null, arguments)),
429 dispatch: deprecated("DOM.Event.dispatch", function dispatch() DOM.Event.dispatch.apply(DOM.Event, arguments)),
430 fromString: deprecated("DOM.Event.parse", { get: function fromString() DOM.Event.bound.parse }),
431 iterKeys: deprecated("DOM.Event.iterKeys", { get: function iterKeys() DOM.Event.bound.iterKeys }),
433 toString: function toString() {
434 if (!arguments.length)
435 return toString.supercall(this);
437 deprecated.warn(toString, "toString", "DOM.Event.stringify");
438 return DOM.Event.stringify.apply(DOM.Event, arguments);
441 get defaultTarget() dactyl.focusedElement || content.document.body || document.documentElement,
444 * Returns true if there's a known native key handler for the given
445 * event in the given mode.
447 * @param {Event} event A keypress event.
448 * @param {Modes.Mode} mode The main mode.
449 * @param {boolean} passUnknown Whether unknown keys should be passed.
451 hasNativeKey: function hasNativeKey(event, mode, passUnknown) {
452 if (mode.input && event.charCode && !(event.ctrlKey || event.metaKey))
458 var elements = document.getElementsByTagNameNS(XUL, "key");
462 filters.push(["keycode", this._code_nativeKey[event.keyCode]]);
463 if (event.charCode) {
464 let key = String.fromCharCode(event.charCode);
465 filters.push(["key", key.toUpperCase()],
466 ["key", key.toLowerCase()]);
469 let accel = config.OS.isMacOSX ? "metaKey" : "ctrlKey";
471 let access = iter({ 1: "shiftKey", 2: "ctrlKey", 4: "altKey", 8: "metaKey" })
472 .filter(function ([k, v]) this & k,
473 prefs.get("ui.key.chromeAccess"))
474 .map(([k, v]) => [v, true])
478 for (let [, key] in iter(elements))
479 if (filters.some(([k, v]) => key.getAttribute(k) == v)) {
480 let keys = { ctrlKey: false, altKey: false, shiftKey: false, metaKey: false };
481 let needed = { ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, metaKey: event.metaKey };
483 let modifiers = (key.getAttribute("modifiers") || "").trim().split(/[\s,]+/);
484 for (let modifier in values(modifiers))
486 case "access": update(keys, access); break;
487 case "accel": keys[accel] = true; break;
488 default: keys[modifier + "Key"] = true; break;
490 if (!iter.some(keys, ([k, v]) => v && needed[k]))
492 for (let [k, v] in iter(keys)) {
500 if (iter(needed).every(([k, v]) => (v == keys[k])))
508 * Returns true if *key* is a key code defined to accept/execute input on
511 * @param {string} key The key code to test.
514 isAcceptKey: function (key) key == "<Return>" || key == "<C-j>" || key == "<C-m>",
517 * Returns true if *key* is a key code defined to reject/cancel input on
520 * @param {string} key The key code to test.
523 isCancelKey: function (key) key == "<Esc>" || key == "<C-[>" || key == "<C-c>",
526 * Returns true if *node* belongs to the current content document or any
529 * @param {Node|Document|Window} node The node to test.
532 isContentNode: function isContentNode(node) {
533 let win = (node.ownerDocument || node).defaultView || node;
534 return XPCNativeWrapper(win).top == content;
538 * Waits for the current buffer to successfully finish loading. Returns
539 * true for a successful page load otherwise false.
543 waitForPageLoad: function (time) {
547 dactyl.echo(_("macro.loadWaiting"), commandline.FORCE_SINGLELINE);
549 const maxWaitTime = (time || 25);
550 util.waitFor(() => buffer.loaded, this,
551 maxWaitTime * 1000, true);
553 dactyl.echo("", commandline.FORCE_SINGLELINE);
555 dactyl.echoerr(_("macro.loadFailed", maxWaitTime));
557 return buffer.loaded;
561 * Ensures that the currently focused element is visible and blurs
564 checkFocus: function () {
565 if (dactyl.focusedElement) {
566 let rect = dactyl.focusedElement.getBoundingClientRect();
567 if (!rect.width || !rect.height) {
568 services.focus.clearFocus(window);
569 document.commandDispatcher.focusedWindow = content;
570 // onFocusChange needs to die.
571 this.onFocusChange();
577 blur: function onBlur(event) {
578 let elem = event.originalTarget;
579 if (DOM(elem).editor)
580 util.trapErrors("removeEditActionListener",
581 DOM(elem).editor, editor);
583 if (elem instanceof Window && services.focus.activeWindow == null
584 && document.commandDispatcher.focusedWindow !== window) {
585 // Deals with circumstances where, after the main window
586 // blurs while a collapsed frame has focus, re-activating
587 // the main window does not restore focus and we lose key
589 services.focus.clearFocus(window);
590 document.commandDispatcher.focusedWindow = Editor.getEditor(content) ? window : content;
593 let hold = modes.topOfStack.params.holdFocus;
596 this.timeout(function () { dactyl.focus(hold); });
600 // TODO: Merge with onFocusChange
601 focus: function onFocus(event) {
602 let elem = event.originalTarget;
603 if (DOM(elem).editor)
604 util.trapErrors("addEditActionListener",
605 DOM(elem).editor, editor);
608 overlay.activeWindow = window;
610 overlay.setData(elem, "had-focus", true);
611 if (event.target instanceof Ci.nsIDOMXULTextBoxElement)
612 if (Events.isHidden(elem, true))
615 let win = (elem.ownerDocument || elem).defaultView || elem;
617 if (!(services.focus.getLastFocusMethod(win) & 0x3000)
618 && events.isContentNode(elem)
619 && !buffer.focusAllowed(elem)
620 && isinstance(elem, [Ci.nsIDOMHTMLInputElement,
621 Ci.nsIDOMHTMLSelectElement,
622 Ci.nsIDOMHTMLTextAreaElement,
624 if (this.grabFocus++ > 5)
625 ; // Something is fighting us. Give up.
627 this.grabFocusTimer.tell();
628 if (elem.frameElement)
629 dactyl.focusContent(true);
630 else if (!(elem instanceof Window) || Editor.getEditor(elem))
631 dactyl.focus(window);
635 if (elem instanceof Element)
636 delete overlay.getData(elem)["focus-allowed"];
640 onFocus: function onFocus(event) {
641 let elem = event.originalTarget;
642 if (!(elem instanceof Element))
644 let win = elem.ownerDocument.defaultView;
647 util.dump(elem, services.focus.getLastFocusMethod(win) & (0x7000));
648 if (buffer.focusAllowed(win))
649 win.dactylLastFocus = elem;
650 else if (isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement])) {
651 if (win.dactylLastFocus)
652 dactyl.focus(win.dactylLastFocus);
658 util.dump(win, String(elem.ownerDocument), String(elem.ownerDocument && elem.ownerDocument.defaultView));
664 input: function onInput(event) {
665 event.originalTarget.dactylKeyPress = undefined;
668 // this keypress handler gets always called first, even if e.g.
669 // the command-line has focus
670 // TODO: ...help me...please...
671 keypress: function onKeyPress(event) {
672 event.dactylDefaultPrevented = event.defaultPrevented;
674 let duringFeed = this.duringFeed || [];
675 this.duringFeed = [];
677 let ourEvent = DOM.Event.feedingEvent;
678 DOM.Event.feedingEvent = null;
680 for (let [k, v] in Iterator(ourEvent))
684 let key = DOM.Event.stringify(ourEvent || event);
685 event.dactylString = key;
687 // Hack to deal with <BS> and so forth not dispatching input
689 if (key && event.originalTarget instanceof Ci.nsIDOMHTMLInputElement && !modes.main.passthrough) {
690 let elem = event.originalTarget;
691 elem.dactylKeyPress = elem.value;
692 util.timeout(function () {
693 if (elem.dactylKeyPress !== undefined && elem.value !== elem.dactylKeyPress)
694 DOM(elem).dactylInput();
695 elem.dactylKeyPress = undefined;
702 if (modes.recording && !event.isReplay)
703 events._macroKeys.push(key);
705 // feedingKeys needs to be separate from interrupted so
706 // we can differentiate between a recorded <C-c>
707 // interrupting whatever it's started and a real <C-c>
708 // interrupting our playback.
709 if (events.feedingKeys && !event.isMacro) {
710 if (key == "<C-c>") {
711 events.feedingKeys = false;
712 if (modes.replaying) {
713 modes.replaying = false;
714 this.timeout(function () { dactyl.echomsg(_("macro.canceled", this._lastMacro)); }, 100);
718 duringFeed.push(event);
720 return Events.kill(event);
724 events.dbg("ON KEYPRESS " + key + " processor: " + this.processor,
725 event.originalTarget instanceof Element ? event.originalTarget : String(event.originalTarget));
727 let mode = modes.getStack(0);
728 if (event.dactylMode)
729 mode = Modes.StackElement(event.dactylMode);
733 if (mode.main == modes.PASS_THROUGH)
734 ignore = !Events.isEscape(key) && key != "<C-v>";
735 else if (mode.main == modes.QUOTE) {
736 if (modes.getStack(1).main == modes.PASS_THROUGH) {
737 mode = Modes.StackElement(modes.getStack(2).main);
738 ignore = Events.isEscape(key);
740 else if (events.shouldPass(event))
741 mode = Modes.StackElement(modes.getStack(1).main);
747 else if (!event.isMacro && !event.noremap && events.shouldPass(event))
751 events.dbg("ON KEYPRESS " + key + " ignore: " + ignore,
752 event.originalTarget instanceof Element ? event.originalTarget : String(event.originalTarget));
757 // FIXME: Why is this hard coded? --Kris
759 util.interrupted = true;
761 this.processor = ProcessorStack(mode, mappings.hives.array, event.noremap);
762 this.processor.keyEvents = this.keyEvents;
765 let { keyEvents, processor } = this;
766 this._processor = processor;
767 this.processor = null;
770 if (!processor.process(event)) {
771 this.keyEvents = keyEvents;
772 this.processor = processor;
777 dactyl.reportError(e);
780 [duringFeed, this.duringFeed] = [this.duringFeed, duringFeed];
781 if (this.feedingKeys)
782 this.duringFeed = this.duringFeed.concat(duringFeed);
784 for (let event in values(duringFeed))
786 DOM.Event.dispatch(event.originalTarget, event, event);
794 keyup: function onKeyUp(event) {
795 if (event.type == "keydown")
796 this.keyEvents.push(event);
797 else if (!this.processor)
800 let pass = this.passing && !event.isMacro ||
801 DOM.Event.feedingEvent && DOM.Event.feedingEvent.isReplay ||
803 modes.main == modes.PASS_THROUGH ||
804 modes.main == modes.QUOTE
805 && modes.getStack(1).main !== modes.PASS_THROUGH
806 && !this.shouldPass(event) ||
807 !modes.passThrough && this.shouldPass(event) ||
808 !this.processor && event.type === "keydown"
809 && options.get("passunknown").getKey(modes.main.allBases)
810 && let (key = DOM.Event.stringify(event))
811 !(modes.main.count && /^\d$/.test(key) ||
812 modes.main.allBases.some(
813 mode => mappings.hives
814 .some(hive => hive.get(mode, key)
815 || hive.getCandidates(mode, key))));
817 events.dbg("ON " + event.type.toUpperCase() + " " + DOM.Event.stringify(event) +
818 " passing: " + this.passing + " " +
820 " replay: " + event.isReplay +
821 " macro: " + event.isMacro);
823 if (event.type === "keydown")
826 // Prevents certain sites from transferring focus to an input box
827 // before we get a chance to process our key bindings on the
830 event.stopPropagation();
832 keydown: function onKeyDown(event) {
834 this.passing = false;
835 this.events.keyup.call(this, event);
838 mousedown: function onMouseDown(event) {
839 let elem = event.target;
840 let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
842 for (; win; win = win != win.parent && win.parent) {
843 for (; elem instanceof Element; elem = elem.parentNode)
844 overlay.setData(elem, "focus-allowed", true);
845 overlay.setData(win.document, "focus-allowed", true);
849 resize: function onResize(event) {
850 if (window.fullScreen != this._fullscreen) {
851 statusline.statusBar.removeAttribute("moz-collapsed");
852 this._fullscreen = window.fullScreen;
853 dactyl.triggerObserver("fullscreen", this._fullscreen);
854 autocommands.trigger("Fullscreen", { url: this._fullscreen ? "on" : "off", state: this._fullscreen });
856 statusline.updateZoomLevel();
860 // argument "event" is deliberately not used, as i don't seem to have
861 // access to the real focus target
863 onFocusChange: util.wrapCallback(function onFocusChange(event) {
864 function hasHTMLDocument(win) win && win.document && win.document instanceof Ci.nsIDOMHTMLDocument
865 if (dactyl.ignoreFocus)
868 let win = window.document.commandDispatcher.focusedWindow;
869 let elem = window.document.commandDispatcher.focusedElement;
871 if (elem == null && Editor.getEditor(win))
874 if (win && win.top == content && dactyl.has("tabs"))
875 buffer.focusedFrame = win;
878 if (elem && elem.readOnly)
881 if (isinstance(elem, [Ci.nsIDOMHTMLEmbedElement, Ci.nsIDOMHTMLEmbedElement])) {
882 if (!modes.main.passthrough && modes.main != modes.EMBED)
883 modes.push(modes.EMBED);
887 let haveInput = modes.stack.some(m => m.main.input);
889 if (DOM(elem || win).isEditable) {
891 if (!(e instanceof Ci.nsIDOMWindow &&
892 DOM(e.document.activeElement).style.MozUserModify != "read-write")) {
894 if (!isinstance(modes.main, [modes.INPUT, modes.TEXT_EDIT, modes.VISUAL]))
895 if (options["insertmode"])
896 modes.push(modes.INSERT);
898 modes.push(modes.TEXT_EDIT);
899 if (elem.selectionEnd - elem.selectionStart > 0)
900 modes.push(modes.VISUAL);
903 if (hasHTMLDocument(win))
904 buffer.lastInputField = elem || win;
909 if (elem && Events.isInputElement(elem)) {
911 modes.push(modes.INSERT);
913 if (hasHTMLDocument(win))
914 buffer.lastInputField = elem;
918 if (config.focusChange) {
919 config.focusChange(win);
923 let urlbar = document.getElementById("urlbar");
924 if (elem == null && urlbar && urlbar.inputField == this._lastFocus.get())
925 util.threadYield(true); // Why? --Kris
927 while (modes.main.ownsFocus
928 && let ({ ownsFocus } = modes.topOfStack.params)
930 ownsFocus.get() != elem &&
931 ownsFocus.get() != win)
932 && !modes.topOfStack.params.holdFocus)
933 modes.pop(null, { fromFocus: true });
936 this._lastFocus = util.weakReference(elem);
938 if (modes.main.ownsFocus)
939 modes.topOfStack.params.ownsFocus = util.weakReference(elem);
943 onSelectionChange: function onSelectionChange(event) {
944 // Ignore selection events caused by editor commands.
945 if (editor.inEditMap || modes.main == modes.OPERATOR)
948 let controller = document.commandDispatcher.getControllerForCommand("cmd_copy");
949 let couldCopy = controller && controller.isCommandEnabled("cmd_copy");
952 if (modes.main == modes.TEXT_EDIT)
953 modes.push(modes.VISUAL);
954 else if (modes.main == modes.CARET)
955 modes.push(modes.VISUAL);
959 shouldPass: function shouldPass(event)
960 !event.noremap && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)) &&
961 options.get("passkeys").has(DOM.Event.stringify(event))
969 isEscape: function isEscape(event)
970 let (key = isString(event) ? event : DOM.Event.stringify(event))
971 key === "<Esc>" || key === "<C-[>",
973 isHidden: function isHidden(elem, aggressive) {
974 if (DOM(elem).style.visibility !== "visible")
978 for (let e = elem; e instanceof Element; e = e.parentNode) {
979 if (!/set$/.test(e.localName) && e.boxObject && e.boxObject.height === 0)
981 else if (e.namespaceURI == XUL && e.localName === "panel")
987 isInputElement: function isInputElement(elem) {
988 return elem instanceof Ci.nsIDOMElement && DOM(elem).isEditable ||
989 isinstance(elem, [Ci.nsIDOMHTMLEmbedElement,
990 Ci.nsIDOMHTMLObjectElement,
991 Ci.nsIDOMHTMLSelectElement]);
994 kill: function kill(event) {
995 event.stopPropagation();
996 event.preventDefault();
999 contexts: function initContexts(dactyl, modules, window) {
1000 update(Events.prototype, {
1001 hives: contexts.Hives("events", EventHive),
1002 user: contexts.hives.events.user,
1003 builtin: contexts.hives.events.builtin
1007 commands: function initCommands() {
1008 commands.add(["delmac[ros]"],
1011 dactyl.assert(!args.bang || !args[0], _("error.invalidArgument"));
1014 events.deleteMacros();
1016 events.deleteMacros(args[0]);
1018 dactyl.echoerr(_("error.argumentRequired"));
1022 completer: function (context) completion.macro(context),
1026 commands.add(["mac[ros]"],
1028 function (args) { completion.listCompleter("macro", args[0]); }, {
1030 completer: function (context) completion.macro(context)
1033 completion: function initCompletion() {
1034 completion.macro = function macro(context) {
1035 context.title = ["Macro", "Keys"];
1036 context.completions = [item for (item in events.getMacros())];
1039 mappings: function initMappings() {
1041 mappings.add([modes.MAIN],
1042 ["<A-b>", "<pass-next-key-builtin>"], "Process the next key as a builtin mapping",
1044 events.processor = ProcessorStack(modes.getStack(0), mappings.hives.array, true);
1045 events.processor.keyEvents = events.keyEvents;
1048 mappings.add([modes.MAIN],
1049 ["<C-z>", "<pass-all-keys>"], "Temporarily ignore all " + config.appName + " key bindings",
1051 if (modes.main != modes.PASS_THROUGH)
1052 modes.push(modes.PASS_THROUGH);
1055 mappings.add([modes.MAIN, modes.PASS_THROUGH, modes.QUOTE],
1056 ["<C-v>", "<pass-next-key>"], "Pass through next key",
1058 if (modes.main == modes.QUOTE)
1060 modes.push(modes.QUOTE);
1063 mappings.add([modes.BASE],
1064 ["<CapsLock>"], "Do Nothing",
1067 mappings.add([modes.BASE],
1068 ["<Nop>"], "Do nothing",
1071 mappings.add([modes.BASE],
1072 ["<Pass>"], "Pass the events consumed by the last executed mapping",
1073 function ({ keypressEvents: [event] }) {
1074 dactyl.assert(event.dactylSavedEvents,
1075 _("event.nothingToPass"));
1076 return function () {
1077 events.feedevents(null, event.dactylSavedEvents,
1078 { skipmap: true, isMacro: true, isReplay: true });
1083 mappings.add([modes.COMMAND],
1084 ["q", "<record-macro>"], "Record a key sequence into a macro",
1085 function ({ arg }) {
1086 util.assert(arg == null || /^[a-z]$/i.test(arg));
1087 events._macroKeys.pop();
1088 events.recording = arg;
1090 { get arg() !modes.recording });
1092 mappings.add([modes.COMMAND],
1093 ["@", "<play-macro>"], "Play a macro",
1094 function ({ arg, count }) {
1095 count = Math.max(count, 1);
1097 events.playMacro(arg);
1099 { arg: true, count: true });
1101 mappings.add([modes.COMMAND],
1102 ["<A-m>s", "<sleep>"], "Sleep for {count} milliseconds before continuing macro playback",
1103 function ({ command, count }) {
1104 let now = Date.now();
1105 dactyl.assert(count, _("error.countRequired", command));
1106 if (events.feedingKeys)
1111 mappings.add([modes.COMMAND],
1112 ["<A-m>l", "<wait-for-page-load>"], "Wait for the current page to finish loading before continuing macro playback",
1113 function ({ count }) {
1114 if (events.feedingKeys && !events.waitForPageLoad(count)) {
1115 util.interrupted = true;
1116 throw Error("Interrupted");
1121 options: function initOptions() {
1122 const Hive = Class("Hive", {
1123 init: function init(values, map) {
1124 this.name = "passkeys:" + map;
1125 this.stack = MapHive.Stack(values.map(v => Map(v[map + "Keys"])));
1126 function Map(keys) ({
1127 execute: function () Events.PASS_THROUGH,
1132 get active() this.stack.length,
1134 get: function get(mode, key) this.stack.mappings[key],
1136 getCandidates: function getCandidates(mode, key) this.stack.candidates[key]
1138 options.add(["passkeys", "pk"],
1139 "Pass certain keys through directly for the given URLs",
1141 flush: function flush() {
1142 memoize(this, "filters", function () this.value.filter(function (f) f(buffer.documentURI)));
1143 memoize(this, "pass", function () RealSet(array.flatten(this.filters.map(function (f) f.keys))));
1144 memoize(this, "commandHive", function hive() Hive(this.filters, "command"));
1145 memoize(this, "inputHive", function hive() Hive(this.filters, "input"));
1148 has: function (key) this.pass.has(key) || hasOwnProperty(this.commandHive.stack.mappings, key),
1150 get pass() (this.flush(), this.pass),
1152 parse: function parse() {
1153 let value = parse.superapply(this, arguments);
1154 value.forEach(function (filter) {
1155 let vals = Option.splitList(filter.result);
1156 filter.keys = DOM.Event.parse(vals[0]).map(DOM.Event.bound.stringify);
1158 filter.commandKeys = vals.slice(1).map(DOM.Event.bound.canonicalKeys);
1159 filter.inputKeys = filter.commandKeys.filter(bind("test", /^<[ACM]-/));
1166 setter: function (value) {
1172 options.add(["strictfocus", "sf"],
1173 "Prevent scripts from focusing input elements without user intervention",
1174 "sitemap", "'chrome:*':laissez-faire,*:moderate",
1177 despotic: "Only allow focus changes when explicitly requested by the user",
1178 moderate: "Allow focus changes after user-initiated focus change",
1179 "laissez-faire": "Always allow focus changes"
1183 options.add(["timeout", "tmo"],
1184 "Whether to execute a shorter key command after a timeout when a longer command exists",
1187 options.add(["timeoutlen", "tmol"],
1188 "Maximum time (milliseconds) to wait for a longer key command when a shorter one exists",
1193 // vim: set fdm=marker sw=4 sts=4 ts=8 et: