]> git.gir.st - VimFx.git/blob - extension/lib/vim-frame.coffee
Change license to MIT
[VimFx.git] / extension / lib / vim-frame.coffee
1 # This file is the equivalent to vim.coffee. `vim.window` is called
2 # `vim.content` to be consistent with Firefox’s frame script terminology and to
3 # avoid confusion about what it represents. There is one `VimFrame` instance for
4 # each tab. It mostly tries to mimic the `Vim` class in vim.coffee, but also
5 # keeps track of web page state. `VimFrame` is not part of the config file API.
6
7 messageManager = require('./message-manager')
8 ScrollableElements = require('./scrollable-elements')
9 utils = require('./utils')
10
11 class VimFrame
12 constructor: (@content) ->
13 @mode = 'normal'
14 @hintMatcher = null
15
16 @resetState()
17
18 messageManager.listen('modeChange', ({mode}) =>
19 @mode = mode
20 )
21
22 messageManager.listen(
23 'markPageInteraction', @markPageInteraction.bind(this)
24 )
25
26 messageManager.listen('clearHover', @clearHover.bind(this))
27
28 # If the target is the topmost document, reset everything. Otherwise filter
29 # out elements belonging to the target frame. On some sites, such as Gmail,
30 # some elements might be dead at this point.
31 resetState: (target = @content.document) ->
32 if target == @content.document
33 @state = {
34 hasInteraction: false
35 shouldRefocus: false
36 marks: {}
37 jumpList: []
38 jumpListIndex: -1
39 explicitBodyFocus: false
40 hasFocusedTextInput: false
41 lastFocusedTextInput: null
42 lastHover: {
43 element: null
44 browserOffset: {x: 0, y: 0}
45 }
46 scrollableElements: new ScrollableElements(@content)
47 markerElements: []
48 inputs: null
49 }
50
51 else
52 isDead = (element) ->
53 return Cu.isDeadWrapper(element) or element.ownerDocument == target
54 check = (obj, prop) ->
55 obj[prop] = null if obj[prop] and isDead(obj[prop])
56
57 check(@state, 'lastFocusedTextInput')
58 check(@state.lastHover, 'element')
59 @state.scrollableElements.reject(isDead)
60 # `markerElements` and `inputs` could theoretically need to be filtered
61 # too at this point. YAGNI until an issue arises from it.
62
63 options: (prefs) -> messageManager.get('options', {prefs})
64
65 _enterMode: (@mode, args...) ->
66 messageManager.send('vimMethod', {
67 method: '_enterMode'
68 args: [@mode, args...]
69 })
70
71 notify: (args...) ->
72 messageManager.send('vimMethod', {method: 'notify', args})
73
74 hideNotification: ->
75 messageManager.send('vimMethod', {method: 'hideNotification'})
76
77 markPageInteraction: (value = true) -> @state.hasInteraction = value
78
79 setHover: (element, browserOffset) ->
80 utils.setHover(element, true)
81 utils.simulateMouseEvents(element, 'hover-start', browserOffset)
82 @state.lastHover.element = element
83 @state.lastHover.browserOffset = browserOffset
84
85 clearHover: ->
86 if @state.lastHover.element
87 {element, browserOffset} = @state.lastHover
88 utils.setHover(element, false)
89 utils.simulateMouseEvents(element, 'hover-end', browserOffset)
90 @state.lastHover.element = null
91
92 addToJumpList: ->
93 [newX, newY] = position = @state.scrollableElements.getPageScrollPosition()
94 jumpList = @state.jumpList[..@state.jumpListIndex]
95 .filter(([x, y]) -> not (x == newX and y == newY))
96 .concat([position])
97 @state.jumpList = jumpList
98 @state.jumpListIndex = jumpList.length - 1
99
100 module.exports = VimFrame
Imprint / Impressum