]> git.gir.st - VimFx.git/blob - extension/lib/events.coffee
Support multi-process Firefox
[VimFx.git] / extension / lib / events.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2012, 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014.
4 #
5 # This file is part of VimFx.
6 #
7 # VimFx is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # VimFx is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
19 ###
20
21 # This file sets up all event listeners needed to power VimFx: To know when to
22 # launch commands and to provide state to them. Events in web page content are
23 # listened for in events-frame.coffee.
24
25 button = require('./button')
26 utils = require('./utils')
27
28 HELD_MODIFIERS_ATTRIBUTE = 'vimfx-held-modifiers'
29
30 class UIEventManager
31 constructor: (@vimfx, @window) ->
32 @listen = utils.listen.bind(null, @window)
33
34 # This flag controls whether to suppress the various key events or not.
35 @suppress = false
36
37 # When a menu or panel is shown VimFx should temporarily stop processing
38 # keyboard input, allowing accesskeys to be used.
39 @popupPassthrough = false
40
41 addListeners: ->
42 checkPassthrough = (value, event) =>
43 if event.target.nodeName in ['menupopup', 'panel']
44 @popupPassthrough = value
45
46 @listen('popupshown', checkPassthrough.bind(null, true))
47 @listen('popuphidden', checkPassthrough.bind(null, false))
48
49 @listen('keydown', (event) =>
50 try
51 # No matter what, always reset the `suppress` flag, so we don't suppress
52 # more than intended.
53 @suppress = false
54
55 if @popupPassthrough
56 # The `@popupPassthrough` flag is set a bit unreliably. Sometimes it
57 # can be stuck as `true` even though no popup is shown, effectively
58 # disabling the extension. Therefore we check if there actually _are_
59 # any open popups before stopping processing keyboard input. This is
60 # only done when popups (might) be open (not on every keystroke) of
61 # performance reasons.
62 #
63 # The autocomplete popup in text inputs (for example) is technically a
64 # panel, but it does not respond to key presses. Therefore
65 # `[ignorekeys="true"]` is excluded.
66 #
67 # coffeelint: disable=max_line_length
68 # <https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/PopupKeys#Ignoring_Keys>
69 # coffeelint: enable=max_line_length
70 popups = @window.document.querySelectorAll(
71 ':-moz-any(menupopup, panel):not([ignorekeys="true"])'
72 )
73 for popup in popups
74 return if popup.state == 'open'
75 @popupPassthrough = false # No popup was actually open.
76
77 vim = @vimfx.getCurrentVim(@window)
78
79 if vim.isFrameEvent(event)
80 vim._listenOnce('onInput-frame', ({ @suppress }) =>
81 @setHeldModifiers(event)
82 )
83 else
84 @suppress = vim._onInput(event, utils.getFocusType(event))
85 @setHeldModifiers(event)
86 utils.suppressEvent(event) if @suppress
87
88 # The command might have closed the current tab. If so, it is an error
89 # trying to communicate with the its frame scripts.
90 if @window.gBrowser.getTabForBrowser(vim.browser)
91 vim._send('keydown', {@suppress})
92
93 catch error
94 console.error(utils.formatError(error))
95 )
96
97 @listen('keypress', (event) =>
98 utils.suppressEvent(event) if @suppress
99 )
100
101 @listen('keyup', (event) =>
102 utils.suppressEvent(event) if @suppress
103 @setHeldModifiers(event, {filterCurrentOnly: true})
104 )
105
106 checkFindbar = (mode, event) =>
107 target = event.originalTarget
108 findBar = @window.gBrowser.getFindBar()
109 if target == findBar._findField.mInputField
110 vim = @vimfx.getCurrentVim(@window)
111 vim.enterMode(mode)
112
113 @listen('focus', checkFindbar.bind(null, 'find'))
114 @listen('blur', checkFindbar.bind(null, 'normal'))
115
116 @listen('click', (event) =>
117 target = event.originalTarget
118 vim = @vimfx.getCurrentVim(@window)
119
120 # If the user clicks the reload button or a link when in hints mode, we’re
121 # going to end up in hints mode without any markers. Or if the user clicks
122 # a text input, then that input will be focused, but you can’t type in it
123 # (instead markers will be matched). So if the user clicks anything in
124 # hints mode it’s better to leave it.
125 if vim.mode == 'hints' and not vim.isFrameEvent(event) and
126 # Exclude the VimFx button, though, since clicking it returns to normal
127 # mode. Otherwise we’d first returned to normal mode and then the
128 # button would have opened the help dialog.
129 target != @window.document.getElementById(button.BUTTON_ID)
130 vim.enterMode('normal')
131 )
132
133 setHeldModifiers: (event, { filterCurrentOnly = false } = {}) ->
134 mainWindow = @window.document.documentElement
135 modifiers =
136 if filterCurrentOnly
137 mainWindow.getAttribute(HELD_MODIFIERS_ATTRIBUTE)
138 else
139 if @suppress == null then 'alt ctrl meta shift' else ''
140 isHeld = (modifier) -> event["#{ modifier }Key"]
141 mainWindow.setAttribute(HELD_MODIFIERS_ATTRIBUTE,
142 modifiers.split(' ').filter(isHeld).join(' '))
143
144 module.exports = UIEventManager
Imprint / Impressum