1 utils = require 'utils'
2 keyUtils = require 'key-utils'
3 { Vim } = require 'vim'
4 { getPref } = require 'prefs'
5 { updateToolbarButton } = require 'button'
6 { unload } = require 'unload'
8 { interfaces: Ci } = Components
10 vimBucket = new utils.Bucket(utils.getWindowId, (w) -> new Vim(w))
12 keyStrFromEvent = (event) ->
13 { ctrlKey: ctrl, metaKey: meta, altKey: alt, shiftKey: shift } = event
16 return unless keyChar = keyUtils.keyCharFromCode(event.keyCode, shift)
17 keyStr = keyUtils.applyModifiers(keyChar, ctrl, alt, meta)
22 # When a menu or panel is shown VimFx should temporarily stop processing keyboard input, allowing
23 # accesskeys to be used.
24 popupPassthrough = false
25 checkPassthrough = (event) ->
26 if event.target.nodeName in ['menupopup', 'panel']
27 popupPassthrough = switch event.type
28 when 'popupshown' then true
29 when 'popuphidden' then false
32 suppressEvent = (event) ->
34 event.preventDefault()
35 event.stopPropagation()
37 removeVimFromTab = (tab, gBrowser) ->
38 return unless browser = gBrowser.getBrowserForTab(tab)
39 vimBucket.forget(browser.contentWindow)
41 updateButton = (vim) ->
42 return unless rootWindow = utils.getRootWindow(vim.window)
43 updateToolbarButton(rootWindow, {blacklisted: vim.blacklisted, insertMode: vim.mode == 'insert'})
45 # The following listeners are installed on every top level Chrome window
49 # No matter what, always reset the `suppress` flag, so we don't suppress more than intended.
52 return if getPref('disabled')
55 # The `popupPassthrough` flag is set a bit unreliably. Sometimes it can be stuck as `true`
56 # even though no popup is shown, effectively disabling the extension. Therefore we check
57 # if there actually _are_ any open popups before stopping processing keyboard input. This is
58 # only done when popups (might) be open (not on every keystroke) of performance reasons.
59 return unless rootWindow = utils.getEventRootWindow(event)
60 popups = rootWindow.document.querySelectorAll('menupopup, panel')
62 return if popup.state == 'open'
63 popupPassthrough = false # No popup was actually open: Reset the flag.
65 return unless window = utils.getEventCurrentTabWindow(event)
66 return unless vim = vimBucket.get(window)
68 return if vim.blacklisted
70 return unless keyStr = keyStrFromEvent(event)
71 suppress = vim.onInput(keyStr, event)
76 console.error("#{ error }\n#{ error.stack.replace(/@.+-> /g, '@') }")
78 # Note that the below event listeners can suppress the event even in blacklisted sites. That's
79 # intentional. For example, if you press 'x' to close the current tab, it will close before keyup
80 # fires. So keyup (and perhaps keypress) will fire in another tab. Even if that particular tab is
81 # blacklisted, we must suppress the event, so that 'x' isn't sent to the page. The rule is simple:
82 # If the `suppress` flag is `true`, the event should be suppressed, no matter what. It has the
84 keypress: suppressEvent
87 popupshown: checkPassthrough
88 popuphidden: checkPassthrough
90 # When the top level window closes we should release all Vims that were
91 # associated with tabs in this window
92 DOMWindowClose: (event) ->
93 return unless { gBrowser } = event.originalTarget
94 for tab in gBrowser.tabs
95 removeVimFromTab(tab, gBrowser)
98 return unless { gBrowser } = utils.getEventRootWindow(event) ? {}
99 tab = event.originalTarget
100 removeVimFromTab(tab, gBrowser)
102 # Update the toolbar button icon to reflect the blacklisted state
103 TabSelect: (event) ->
104 return unless window = event.originalTarget?.linkedBrowser?.contentDocument?.defaultView
105 return unless vim = vimBucket.get(window)
108 # This listener works on individual tabs within Chrome Window
110 # Listenfor location changes and disable the extension on blacklisted urls
111 onLocationChange: (browser, webProgress, request, location) ->
112 return unless vim = vimBucket.get(browser.contentWindow)
114 # If the location changes when in hints mode (for example because the reload button has been
115 # clicked), we're going to end up in hints mode without any markers. So switch back to normal
117 if vim.mode == 'hints'
118 vim.enterMode('normal')
120 vim.blacklisted = utils.isBlacklisted(location.spec)
123 addEventListeners = (window) ->
124 for name, listener of windowsListeners
125 window.addEventListener(name, listener, true)
127 window.gBrowser.addTabsProgressListener(tabsListener)
130 for name, listener of windowsListeners
131 window.removeEventListener(name, listener, true)
133 window.gBrowser.removeTabsProgressListener(tabsListener)
135 exports.addEventListeners = addEventListeners
136 exports.vimBucket = vimBucket