]> git.gir.st - VimFx.git/blob - extension/lib/events-frame.coffee
Merge pull request #553 from akhodakivskiy/non-multi-process-event-quirks
[VimFx.git] / extension / lib / events-frame.coffee
1 ###
2 # Copyright Simon Lydell 2015.
3 #
4 # This file is part of VimFx.
5 #
6 # VimFx is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # VimFx is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
18 ###
19
20 # This file is the equivalent to events.coffee, but for frame scripts.
21
22 messageManager = require('./message-manager')
23 utils = require('./utils')
24
25 class FrameEventManager
26 constructor: (@vim) ->
27 @numFocusToSuppress = 0
28
29 listen: utils.listen.bind(null, FRAME_SCRIPT_ENVIRONMENT)
30 listenOnce: utils.listenOnce.bind(null, FRAME_SCRIPT_ENVIRONMENT)
31
32 addListeners: ->
33 messageManager.listen('locationChange', @vim.resetState.bind(@vim))
34
35 # If the page already was loaded when VimFx was initialized, send the
36 # 'DOMWindowCreated' message straight away.
37 if @vim.content.document.readyState in ['interactive', 'complete']
38 messageManager.send('DOMWindowCreated')
39 else
40 @listen('DOMWindowCreated', (event) ->
41 messageManager.send('DOMWindowCreated')
42 )
43
44 @listen('click', (event) =>
45 if @vim.mode == 'hints' and event.isTrusted
46 messageManager.send('enterMode', {mode: 'normal'})
47 )
48
49 @listen('overflow', (event) =>
50 target = event.originalTarget
51 return unless computedStyle = @vim.content.getComputedStyle(target)
52 return if computedStyle.getPropertyValue('overflow') == 'hidden'
53 @vim.state.scrollableElements.add(target)
54 )
55
56 @listen('underflow', (event) =>
57 target = event.originalTarget
58 @vim.state.scrollableElements.delete(target)
59 )
60
61 @listen('keydown', (event) =>
62 suppress = @vim.onInput(event)
63
64 # This also suppresses the 'keypress' and 'keyup' events. (Yes, in frame
65 # scripts, suppressing the 'keydown' events does seem to even suppress
66 # the 'keyup' event!)
67 utils.suppressEvent(event) if suppress
68
69 # From this line on, the rest of the code in `addListeners` is more or
70 # less devoted to autofocus prevention. When enabled, focus events that
71 # occur before the user has interacted with page are prevented.
72 #
73 # If this keydown event wasn’t suppressed (`not suppress`), it’s an
74 # obvious interaction with the page. If it _was_ suppressed, though, it’s
75 # an interaction depending on the command triggered; if it calls
76 # `vim.markPageInteraction()` or not.
77 @vim.markPageInteraction() unless suppress
78 )
79
80 @listen('keydown', ((event) ->
81 suppress = messageManager.get('lateKeydown', {
82 defaultPrevented: event.defaultPrevented
83 })
84 utils.suppressEvent(event) if suppress
85 ), false)
86
87 # Clicks are always counted as page interaction. Listen for 'mousedown'
88 # instead of 'click' to mark the interaction as soon as possible.
89 @listen('mousedown', (event) => @vim.markPageInteraction())
90
91 messageManager.listen('browserRefocus', =>
92 # Suppress the next two focus events (for `document` and `window`; see
93 # `blurActiveBrowserElement`).
94 @numFocusToSuppress = 2
95 )
96
97 @listen('focus', (event) =>
98 target = event.originalTarget
99
100 if @numFocusToSuppress > 0
101 utils.suppressEvent(event)
102 @numFocusToSuppress--
103 return
104
105 # Reset `hasInteraction` when (re-)selecting a tab, or coming back from
106 # another window, in order to prevent the common “automatically re-focus
107 # when switching back to the tab” behaviour many sites have, unless a text
108 # input _should_ be re-focused when coming back to the tab (see the 'blur'
109 # event below).
110 if target == @vim.content.document
111 if @vim.state.shouldRefocus
112 @vim.state.hasInteraction = true
113 @vim.state.shouldRefocus = false
114 else
115 @vim.state.hasInteraction = false
116 return
117
118 options = @vim.options(['prevent_autofocus', 'prevent_autofocus_modes'])
119
120 # Save the last focused text input regardless of whether that input might
121 # be blurred because of autofocus prevention.
122 if utils.isTextInputElement(target)
123 @vim.state.lastFocusedTextInput = target
124
125 focusManager = Cc['@mozilla.org/focus-manager;1']
126 .getService(Ci.nsIFocusManager)
127
128 # Blur the focus target, if autofocus prevention is enabled…
129 if options.prevent_autofocus and
130 @vim.mode in options.prevent_autofocus_modes and
131 # …and the user has interacted with the page…
132 not @vim.state.hasInteraction and
133 # …and the event is programmatic (not caused by clicks or keypresses)…
134 focusManager.getLastFocusMethod(null) == 0 and
135 # …and the target may steal most keystrokes.
136 (utils.isTextInputElement(target) or utils.isContentEditable(target))
137 # Some sites (such as icloud.com) re-focuses inputs if they are blurred,
138 # causing an infinite loop of autofocus prevention and re-focusing.
139 # Therefore, blur events that happen just after an autofocus prevention
140 # are suppressed.
141 @listenOnce('blur', utils.suppressEvent)
142 target.blur()
143 )
144
145 @listen('blur', (event) =>
146 target = event.originalTarget
147
148 # If a text input is blurred in a background tab, it most likely means
149 # that the user switched tab, for example by pressing `<c-tab>`, while the
150 # text input was focused. The 'TabSelect' event fires first, then the
151 # 'blur' event. In this case, when switching back to that tab, the text
152 # input will be re-focused (because it was focused when you left the tab).
153 # This case is kept track of so that the autofocus prevention does not
154 # catch it.
155 if utils.isTextInputElement(target) or utils.isContentEditable(target)
156 @vim.state.shouldRefocus = not @vim.content.document.hasFocus()
157 )
158
159 module.exports = FrameEventManager
Imprint / Impressum