2 # Copyright Anton Khodakivskiy 2012, 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014, 2015, 2016.
4 # Copyright Wang Zhuochun 2013, 2014.
6 # This file is part of VimFx.
8 # VimFx is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # VimFx is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
22 # This file defines all Normal mode commands. Commands that need to interact
23 # with web page content do so by running `vim._run(name)`, which invokes `name`
24 # in commands-frame.coffee.
26 # NOTE: Most tab related commands need to do their actual tab manipulations in
27 # the next tick (`utils.nextTick`) to work around bug 1200334.
29 config = require('./config')
30 help = require('./help')
31 hints = require('./hints')
32 prefs = require('./prefs')
33 translate = require('./l10n')
34 utils = require('./utils')
35 SelectionManager = require('./selection')
36 viewportUtils = require('./viewport')
38 {ContentClick} = Cu.import('resource:///modules/ContentClick.jsm', {})
39 {FORWARD, BACKWARD} = SelectionManager
41 SPRING_CONSTANT_PREF = 'layout.css.scroll-behavior.spring-constant'
47 commands.focus_location_bar = ({vim}) ->
48 vim.window.focusAndSelectUrlBar()
50 commands.focus_search_bar = ({vim, count}) ->
51 # The `.webSearch()` method opens a search engine in a tab if the search bar
52 # has been removed. Therefore we first check if it exists.
53 if vim.window.BrowserSearch.searchBar
54 vim.window.BrowserSearch.webSearch()
56 vim.notify(translate('notification.focus_search_bar.none'))
58 helper_paste_and_go = (props, {vim}) ->
59 {gURLBar} = vim.window
60 gURLBar.value = vim.window.readFromClipboard()
61 gURLBar.handleCommand(new vim.window.KeyboardEvent('keydown', props))
63 commands.paste_and_go = helper_paste_and_go.bind(null, null)
65 commands.paste_and_go_in_tab = helper_paste_and_go.bind(null, {altKey: true})
67 commands.copy_current_url = ({vim}) ->
68 utils.writeToClipboard(vim.window.gBrowser.currentURI.spec)
69 vim.notify(translate('notification.copy_current_url.success'))
71 commands.go_up_path = ({vim, count}) ->
72 vim._run('go_up_path', {count})
74 commands.go_to_root = ({vim}) ->
75 vim._run('go_to_root')
77 commands.go_home = ({vim}) ->
78 vim.window.BrowserHome()
80 helper_go_history = (direction, {vim, count = 1}) ->
82 {SessionStore, gBrowser} = window
84 if (direction == 'back' and not gBrowser.canGoBack) or
85 (direction == 'forward' and not gBrowser.canGoForward)
86 vim.notify(translate("notification.history_#{direction}.limit"))
89 # `SessionStore.getSessionHistory()` (used below to support counts) starts
90 # lots of asynchronous tasks internally, which is a bit unreliable, it has
91 # turned out. The primary use of the `history_back` and `history_forward`
92 # commands is to go _one_ step back or forward, though, so those cases are
93 # optimized to use more reliable ways of going back and forward. Also, some
94 # extensions override the following functions, so calling them also gives
95 # better interoperability.
97 if direction == 'back'
100 window.BrowserForward()
103 SessionStore.getSessionHistory(gBrowser.selectedTab, (sessionHistory) ->
104 {index} = sessionHistory
105 newIndex = index + count * (if direction == 'back' then -1 else 1)
106 newIndex = Math.max(newIndex, 0)
107 newIndex = Math.min(newIndex, sessionHistory.entries.length - 1)
108 gBrowser.gotoIndex(newIndex)
111 commands.history_back = helper_go_history.bind(null, 'back')
113 commands.history_forward = helper_go_history.bind(null, 'forward')
115 commands.history_list = ({vim}) ->
116 menu = vim.window.document.getElementById('backForwardMenu')
117 utils.openPopup(menu)
118 if menu.childElementCount == 0
119 vim.notify(translate('notification.history_list.none'))
121 commands.reload = ({vim}) ->
122 vim.window.BrowserReload()
124 commands.reload_force = ({vim}) ->
125 vim.window.BrowserReloadSkipCache()
127 commands.reload_all = ({vim}) ->
128 vim.window.gBrowser.reloadAllTabs()
130 commands.reload_all_force = ({vim}) ->
131 for tab in vim.window.gBrowser.visibleTabs
132 gBrowser = tab.linkedBrowser
133 consts = gBrowser.webNavigation
134 flags = consts.LOAD_FLAGS_BYPASS_PROXY | consts.LOAD_FLAGS_BYPASS_CACHE
135 gBrowser.reload(flags)
138 commands.stop = ({vim}) ->
139 vim.window.BrowserStop()
141 commands.stop_all = ({vim}) ->
142 for tab in vim.window.gBrowser.visibleTabs
143 tab.linkedBrowser.stop()
153 helper_scroll = (vim, uiEvent, args...) ->
155 method, type, directions, amounts
156 properties = null, adjustment = 0, name = 'scroll'
159 method, type, directions, amounts, properties, adjustment
161 prefs.root.get('general.smoothScroll') and
162 prefs.root.get("general.smoothScroll.#{type}")
166 # Temporarily set Firefox’s “spring constant” pref to get the desired smooth
167 # scrolling speed. Reset it `reset_timeout` milliseconds after the last
168 # scrolling command was invoked.
169 springConstant.nonce = nonce = {}
170 springConstant.value ?= prefs.root.get(SPRING_CONSTANT_PREF)
172 SPRING_CONSTANT_PREF,
173 vim.options["smoothScroll.#{type}.spring-constant"]
176 vim.window.setTimeout((->
177 return unless springConstant.nonce == nonce
178 prefs.root.set(SPRING_CONSTANT_PREF, springConstant.value)
179 springConstant.nonce = null
180 springConstant.value = null
181 ), vim.options['scroll.reset_timeout'])
183 helpScroll = help.getHelp(vim.window)?.querySelector('.wrapper')
184 if uiEvent or helpScroll
185 activeElement = helpScroll or utils.getActiveElement(vim.window)
186 if vim._state.scrollableElements.has(activeElement) or helpScroll
187 viewportUtils.scroll(activeElement, options)
191 vim._run(name, options, reset)
194 helper_scrollByLinesX = (amount, {vim, uiEvent, count = 1}) ->
195 distance = prefs.root.get('toolkit.scrollbox.horizontalScrollDistance')
197 vim, uiEvent, 'scrollBy', 'lines', ['left'], [amount * distance * count * 5]
200 helper_scrollByLinesY = (amount, {vim, uiEvent, count = 1}) ->
201 distance = prefs.root.get('toolkit.scrollbox.verticalScrollDistance')
203 vim, uiEvent, 'scrollBy', 'lines', ['top'], [amount * distance * count * 20]
206 helper_scrollByPagesY = (amount, type, {vim, uiEvent, count = 1}) ->
207 adjustment = vim.options["scroll.#{type}_page_adjustment"]
209 vim, uiEvent, 'scrollBy', 'pages', ['top'], [amount * count],
210 ['clientHeight'], adjustment
213 helper_scrollToX = (amount, {vim, uiEvent}) ->
214 helper_mark_last_scroll_position(vim)
216 vim, uiEvent, 'scrollTo', 'other', ['left'], [amount], ['scrollLeftMax']
219 helper_scrollToY = (amount, {vim, uiEvent}) ->
220 helper_mark_last_scroll_position(vim)
222 vim, uiEvent, 'scrollTo', 'other', ['top'], [amount], ['scrollTopMax']
225 commands.scroll_left = helper_scrollByLinesX.bind(null, -1)
226 commands.scroll_right = helper_scrollByLinesX.bind(null, +1)
227 commands.scroll_down = helper_scrollByLinesY.bind(null, +1)
228 commands.scroll_up = helper_scrollByLinesY.bind(null, -1)
229 commands.scroll_page_down = helper_scrollByPagesY.bind(null, +1, 'full')
230 commands.scroll_page_up = helper_scrollByPagesY.bind(null, -1, 'full')
231 commands.scroll_half_page_down = helper_scrollByPagesY.bind(null, +0.5, 'half')
232 commands.scroll_half_page_up = helper_scrollByPagesY.bind(null, -0.5, 'half')
233 commands.scroll_to_top = helper_scrollToY.bind(null, 0)
234 commands.scroll_to_bottom = helper_scrollToY.bind(null, Infinity)
235 commands.scroll_to_left = helper_scrollToX.bind(null, 0)
236 commands.scroll_to_right = helper_scrollToX.bind(null, Infinity)
238 helper_mark_last_scroll_position = (vim) ->
239 keyStr = vim.options['scroll.last_position_mark']
240 vim._run('mark_scroll_position', {keyStr, notify: false})
242 commands.mark_scroll_position = ({vim}) ->
243 vim.enterMode('marks', (keyStr) -> vim._run('mark_scroll_position', {keyStr}))
244 vim.notify(translate('notification.mark_scroll_position.enter'))
246 commands.scroll_to_mark = ({vim}) ->
247 vim.enterMode('marks', (keyStr) ->
248 unless keyStr == vim.options['scroll.last_position_mark']
249 helper_mark_last_scroll_position(vim)
251 vim, null, 'scrollTo', 'other', ['top', 'left'], keyStr,
252 ['scrollTopMax', 'scrollLeftMax'], 0, 'scroll_to_mark'
255 vim.notify(translate('notification.scroll_to_mark.enter'))
259 commands.tab_new = ({vim}) ->
260 utils.nextTick(vim.window, ->
261 vim.window.BrowserOpenTab()
264 commands.tab_new_after_current = ({vim}) ->
266 newTabPosition = window.gBrowser.selectedTab._tPos + 1
267 utils.nextTick(window, ->
268 utils.listenOnce(window, 'TabOpen', (event) ->
269 newTab = event.originalTarget
270 window.gBrowser.moveTabTo(newTab, newTabPosition)
272 window.BrowserOpenTab()
275 commands.tab_duplicate = ({vim}) ->
276 {gBrowser} = vim.window
277 utils.nextTick(vim.window, ->
278 gBrowser.duplicateTab(gBrowser.selectedTab)
281 absoluteTabIndex = (relativeIndex, gBrowser, {pinnedSeparate}) ->
282 tabs = gBrowser.visibleTabs
283 {selectedTab} = gBrowser
285 currentIndex = tabs.indexOf(selectedTab)
286 absoluteIndex = currentIndex + relativeIndex
287 numTabsTotal = tabs.length
288 numPinnedTabs = gBrowser._numPinnedTabs
290 [numTabs, min] = switch
291 when not pinnedSeparate
293 when selectedTab.pinned
296 [numTabsTotal - numPinnedTabs, numPinnedTabs]
298 # Wrap _once_ if at one of the ends of the tab bar and cannot move in the
300 if (relativeIndex < 0 and currentIndex == min) or
301 (relativeIndex > 0 and currentIndex == min + numTabs - 1)
302 if absoluteIndex < min
303 absoluteIndex += numTabs
304 else if absoluteIndex >= min + numTabs
305 absoluteIndex -= numTabs
307 absoluteIndex = Math.max(min, absoluteIndex)
308 absoluteIndex = Math.min(absoluteIndex, min + numTabs - 1)
312 helper_switch_tab = (direction, {vim, count = 1}) ->
313 {gBrowser} = vim.window
314 index = absoluteTabIndex(direction * count, gBrowser, {pinnedSeparate: false})
315 utils.nextTick(vim.window, ->
316 gBrowser.selectTabAtIndex(index)
319 commands.tab_select_previous = helper_switch_tab.bind(null, -1)
321 commands.tab_select_next = helper_switch_tab.bind(null, +1)
323 helper_is_visited = (tab) ->
324 return tab.getAttribute('VimFx-visited') or not tab.getAttribute('unread')
326 commands.tab_select_most_recent = ({vim, count = 1}) ->
327 {gBrowser} = vim.window
331 (tab) -> not tab.closing and helper_is_visited(tab)
332 ).sort((a, b) -> b.lastAccessed - a.lastAccessed)[1..] # Remove current tab.
333 tab = tabsSorted[Math.min(count - 1, tabsSorted.length - 1)]
335 gBrowser.selectedTab = tab
337 vim.notify(translate('notification.tab_select_most_recent.none'))
339 commands.tab_select_oldest_unvisited = ({vim, count = 1}) ->
340 {gBrowser} = vim.window
344 (tab) -> not tab.closing and not helper_is_visited(tab)
345 ).sort((a, b) -> a.lastAccessed - b.lastAccessed)
346 tab = tabsSorted[Math.min(count - 1, tabsSorted.length - 1)]
348 gBrowser.selectedTab = tab
350 vim.notify(translate('notification.tab_select_oldest_unvisited.none'))
352 helper_move_tab = (direction, {vim, count = 1}) ->
353 {gBrowser} = vim.window
354 index = absoluteTabIndex(direction * count, gBrowser, {pinnedSeparate: true})
355 utils.nextTick(vim.window, ->
356 gBrowser.moveTabTo(gBrowser.selectedTab, index)
359 commands.tab_move_backward = helper_move_tab.bind(null, -1)
361 commands.tab_move_forward = helper_move_tab.bind(null, +1)
363 commands.tab_move_to_window = ({vim}) ->
364 {gBrowser} = vim.window
365 gBrowser.replaceTabWithWindow(gBrowser.selectedTab)
367 commands.tab_select_first = ({vim, count = 1}) ->
368 utils.nextTick(vim.window, ->
369 vim.window.gBrowser.selectTabAtIndex(count - 1)
372 commands.tab_select_first_non_pinned = ({vim, count = 1}) ->
373 firstNonPinned = vim.window.gBrowser._numPinnedTabs
374 utils.nextTick(vim.window, ->
375 vim.window.gBrowser.selectTabAtIndex(firstNonPinned + count - 1)
378 commands.tab_select_last = ({vim, count = 1}) ->
379 utils.nextTick(vim.window, ->
380 vim.window.gBrowser.selectTabAtIndex(-count)
383 commands.tab_toggle_pinned = ({vim}) ->
384 currentTab = vim.window.gBrowser.selectedTab
386 vim.window.gBrowser.unpinTab(currentTab)
388 vim.window.gBrowser.pinTab(currentTab)
390 commands.tab_close = ({vim, count = 1}) ->
391 {gBrowser} = vim.window
392 return if gBrowser.selectedTab.pinned
393 currentIndex = gBrowser.visibleTabs.indexOf(gBrowser.selectedTab)
394 utils.nextTick(vim.window, ->
395 for tab in gBrowser.visibleTabs[currentIndex...(currentIndex + count)]
396 gBrowser.removeTab(tab)
400 commands.tab_restore = ({vim, count = 1}) ->
401 utils.nextTick(vim.window, ->
402 for index in [0...count] by 1
403 restoredTab = vim.window.undoCloseTab()
404 if not restoredTab and index == 0
405 vim.notify(translate('notification.tab_restore.none'))
410 commands.tab_restore_list = ({vim}) ->
412 fragment = window.RecentlyClosedTabsAndWindowsMenuUtils.getTabsFragment(
415 if fragment.childElementCount == 0
416 vim.notify(translate('notification.tab_restore.none'))
418 utils.openPopup(utils.injectTemporaryPopup(window.document, fragment))
420 commands.tab_close_to_end = ({vim}) ->
421 {gBrowser} = vim.window
422 gBrowser.removeTabsToTheEndFrom(gBrowser.selectedTab)
424 commands.tab_close_other = ({vim}) ->
425 {gBrowser} = vim.window
426 gBrowser.removeAllTabsBut(gBrowser.selectedTab)
430 helper_follow = (name, vim, callback, count = null) ->
431 vim.markPageInteraction()
432 help.removeHelp(vim.window)
434 # Enter hints mode immediately, with an empty set of markers. The user might
435 # press keys before the `vim._run` callback is invoked. Those key presses
436 # should be handled in hints mode, not normal mode.
438 storage = vim.enterMode(
439 'hints', initialMarkers, callback, count, vim.options.hints_sleep
442 setMarkers = ({wrappers, viewport}) ->
443 if wrappers.length > 0
444 {markers, markerMap} = hints.injectHints(
445 vim.window, wrappers, viewport, vim.options
447 storage.markers = markers
448 storage.markerMap = markerMap
450 vim.notify(translate('notification.follow.none'))
451 vim.enterMode('normal')
453 vim._run(name, {}, (result) ->
454 # The user might have exited hints mode (and perhaps even entered it again)
455 # before this callback is invoked. If so, `storage.markers` has been
456 # cleared, or set to a new value. Only proceed if it is unchanged.
457 return unless storage.markers == initialMarkers
459 storage.markEverything = ->
460 lastMarkers = storage.markers
461 vim._run(name, {markEverything: true}, (newResult) ->
462 return unless storage.markers == lastMarkers
463 setMarkers(newResult)
467 helper_follow_clickable = (options, {vim, count = 1}) ->
468 callback = (marker, timesLeft, keyStr) ->
469 {inTab, inBackground} = options
470 {type, elementIndex} = marker.wrapper
471 isLast = (timesLeft == 1)
472 isLink = (type == 'link')
475 when keyStr.startsWith(vim.options.hints_toggle_in_tab)
477 when keyStr.startsWith(vim.options.hints_toggle_in_background)
479 inBackground = not inBackground
485 inTab = false unless isLink
487 if type == 'text' or (isLink and not (inTab and inBackground))
490 vim._focusMarkerElement(elementIndex)
493 utils.nextTick(vim.window, ->
494 # `ContentClick.contentAreaClick` is what Firefox invokes when you click
495 # links using the mouse. Using that instead of simply
496 # `gBrowser.loadOneTab(url, options)` gives better interoperability with
497 # other add-ons, such as Tree Style Tab and BackTrack Tab History.
498 reset = prefs.root.tmp('browser.tabs.loadInBackground', true)
499 ContentClick.contentAreaClick({
500 href: marker.wrapper.href
501 shiftKey: not inBackground
508 vim._run('click_marker_element', {
510 preventTargetBlank: vim.options.prevent_target_blank
515 name = if options.inTab then 'follow_in_tab' else 'follow'
516 helper_follow(name, vim, callback, count)
519 helper_follow_clickable.bind(null, {inTab: false, inBackground: true})
521 commands.follow_in_tab =
522 helper_follow_clickable.bind(null, {inTab: true, inBackground: true})
524 commands.follow_in_focused_tab =
525 helper_follow_clickable.bind(null, {inTab: true, inBackground: false})
527 commands.follow_in_window = ({vim}) ->
528 callback = (marker) ->
529 vim._focusMarkerElement(marker.wrapper.elementIndex)
530 {href} = marker.wrapper
531 vim.window.openLinkIn(href, 'window', {}) if href
532 helper_follow('follow_in_tab', vim, callback)
534 commands.follow_multiple = (args) ->
535 args.count = Infinity
536 commands.follow(args)
538 commands.follow_copy = ({vim}) ->
539 callback = (marker) ->
540 property = switch marker.wrapper.type
545 when 'contenteditable', 'other'
547 helper_copy_marker_element(vim, marker.wrapper.elementIndex, property)
548 helper_follow('follow_copy', vim, callback)
550 commands.follow_focus = ({vim}) ->
551 callback = (marker) ->
552 vim._focusMarkerElement(marker.wrapper.elementIndex, {select: true})
553 helper_follow('follow_focus', vim, callback)
555 commands.click_browser_element = ({vim}) ->
558 filter = ({markEverything}, element, getElementShape) ->
559 document = element.ownerDocument
562 when vim._state.scrollableElements.has(element)
564 when utils.isFocusable(element) or
565 (element.onclick and element.localName != 'statuspanel')
568 if markEverything and not type
573 return unless shape = getElementShape(element)
574 length = markerElements.push(element)
575 return {type, semantic, shape, elementIndex: length - 1}
577 callback = (marker) ->
578 element = markerElements[marker.wrapper.elementIndex]
579 switch marker.wrapper.type
581 utils.focusElement(element, {flag: 'FLAG_BYKEY'})
582 when 'clickable', 'other'
584 if element.localName == 'tab'
588 utils.focusElement(element)
589 utils.simulateMouseEvents(element, sequence)
591 createMarkers = (wrappers) ->
592 viewport = viewportUtils.getWindowViewport(vim.window)
593 {markers} = hints.injectHints(vim.window, wrappers, viewport, {
594 hint_chars: vim.options.hint_chars
599 wrappers = hints.getMarkableElements(
600 vim.window, filter.bind(null, {markEverything: false})
603 if wrappers.length > 0
604 storage = vim.enterMode('hints', createMarkers(wrappers), callback)
605 storage.markEverything = ->
606 newWrappers = hints.getMarkableElements(
607 vim.window, filter.bind(null, {markEverything: true})
609 storage.markers = createMarkers(newWrappers)
611 vim.notify(translate('notification.follow.none'))
613 helper_follow_pattern = (type, {vim}) ->
615 pattern_selector: vim.options.pattern_selector
616 pattern_attrs: vim.options.pattern_attrs
617 patterns: vim.options["#{type}_patterns"]
619 vim._run('follow_pattern', {type, options})
621 commands.follow_previous = helper_follow_pattern.bind(null, 'prev')
623 commands.follow_next = helper_follow_pattern.bind(null, 'next')
625 commands.focus_text_input = ({vim, count}) ->
626 vim.markPageInteraction()
627 vim._run('focus_text_input', {count})
629 helper_follow_selectable = ({select}, {vim}) ->
630 callback = (marker) ->
631 vim._run('element_text_select', {
632 elementIndex: marker.wrapper.elementIndex
636 vim.enterMode('caret', select)
637 helper_follow('follow_selectable', vim, callback)
639 commands.element_text_caret =
640 helper_follow_selectable.bind(null, {select: false})
642 commands.element_text_select =
643 helper_follow_selectable.bind(null, {select: true})
645 commands.element_text_copy = ({vim}) ->
646 callback = (marker) ->
647 helper_copy_marker_element(vim, marker.wrapper.elementIndex, '_selection')
648 helper_follow('follow_selectable', vim, callback)
650 helper_copy_marker_element = (vim, elementIndex, property) ->
651 if property == '_selection'
652 # Selecting the text and then copying that selection is better than copying
653 # `.textContent`. Slack uses markdown-style backtick syntax for code spans
654 # and then includes those backticks in the compiled output (!), in hidden
655 # `<span>`s, so `.textContent` would copy those too. In `contenteditable`
656 # elements, text selection gives better whitespace than `.textContent`.
657 vim._run('element_text_select', {elementIndex, full: true}, ->
658 vim.window.goDoCommand('cmd_copy') # See `caret.copy_selection_and_exit`.
659 vim._run('clear_selection')
662 vim._run('copy_marker_element', {elementIndex, property})
666 findStorage = {lastSearchString: ''}
668 helper_find_from_top_of_viewport = (vim, direction, callback) ->
669 if vim.options.find_from_top_of_viewport
670 vim._run('find_from_top_of_viewport', {direction}, callback)
674 helper_find = ({highlight, linksOnly = false}, {vim}) ->
675 helpSearchInput = help.getSearchInput(vim.window)
677 helpSearchInput.select()
680 helper_mark_last_scroll_position(vim)
681 helper_find_from_top_of_viewport(vim, FORWARD, ->
682 findBar = vim.window.gBrowser.getFindBar()
684 mode = if linksOnly then findBar.FIND_LINKS else findBar.FIND_NORMAL
685 findBar.startFind(mode)
686 utils.focusElement(findBar._findField, {select: true})
689 return unless highlightButton = findBar.getElement('highlight')
690 if highlightButton.checked != highlight
691 highlightButton.click()
694 commands.find = helper_find.bind(null, {highlight: false})
696 commands.find_highlight_all = helper_find.bind(null, {highlight: true})
698 commands.find_links_only = helper_find.bind(null, {linksOnly: true})
700 helper_find_again = (direction, {vim}) ->
701 findBar = vim.window.gBrowser.getFindBar()
702 if findStorage.lastSearchString.length == 0
703 vim.notify(translate('notification.find_again.none'))
706 helper_mark_last_scroll_position(vim)
707 helper_find_from_top_of_viewport(vim, direction, ->
708 findBar._findField.value = findStorage.lastSearchString
709 findBar.onFindAgainCommand(not direction)
710 message = findBar._findStatusDesc.textContent
711 vim.notify(message) if message
714 commands.find_next = helper_find_again.bind(null, FORWARD)
716 commands.find_previous = helper_find_again.bind(null, BACKWARD)
720 commands.window_new = ({vim}) ->
721 vim.window.OpenBrowserWindow()
723 commands.window_new_private = ({vim}) ->
724 vim.window.OpenBrowserWindow({private: true})
726 commands.enter_mode_ignore = ({vim}) ->
727 vim.enterMode('ignore', {type: 'explicit'})
729 # Quote next keypress (pass it through to the page).
730 commands.quote = ({vim, count = 1}) ->
731 vim.enterMode('ignore', {type: 'explicit', count})
733 commands.enter_reader_view = ({vim}) ->
734 button = vim.window.document.getElementById('reader-mode-button')
735 if not button?.hidden
738 vim.notify(translate('notification.enter_reader_view.none'))
740 commands.reload_config_file = ({vim}) ->
741 vim._parent.emit('shutdown')
742 config.load(vim._parent, (status) -> switch status
744 vim.notify(translate('notification.reload_config_file.none'))
746 vim.notify(translate('notification.reload_config_file.success'))
748 vim.notify(translate('notification.reload_config_file.failure'))
751 commands.help = ({vim}) ->
752 help.injectHelp(vim.window, vim._parent)
754 commands.dev = ({vim}) ->
755 vim.window.DeveloperToolbar.show(true) # `true` to focus.
757 commands.esc = ({vim}) ->
759 utils.blurActiveBrowserElement(vim)
760 vim.window.gBrowser.getFindBar().close()
761 hints.removeHints(vim.window) # Better safe than sorry.
763 # Calling `.hide()` when the toolbar is not open can destroy it for the rest
764 # of the Firefox session. The code here is taken from the `.toggle()` method.
765 {DeveloperToolbar} = vim.window
766 if DeveloperToolbar.visible
767 DeveloperToolbar.hide().catch(console.error)
769 unless help.getSearchInput(vim.window)?.getAttribute('focused')
770 help.removeHelp(vim.window)