]> git.gir.st - VimFx.git/blob - extension/lib/utils.coffee
Fix lint errors
[VimFx.git] / extension / lib / utils.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2012, 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014, 2015.
4 # Copyright Wang Zhuochun 2013.
5 #
6 # This file is part of VimFx.
7 #
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.
12 #
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.
17 #
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/>.
20 ###
21
22 # This file contains lots of different helper functions.
23
24 HTMLAnchorElement = Ci.nsIDOMHTMLAnchorElement
25 HTMLButtonElement = Ci.nsIDOMHTMLButtonElement
26 HTMLInputElement = Ci.nsIDOMHTMLInputElement
27 HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement
28 HTMLSelectElement = Ci.nsIDOMHTMLSelectElement
29 HTMLFrameElement = Ci.nsIDOMHTMLFrameElement
30 HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement
31 XULDocument = Ci.nsIDOMXULDocument
32 XULButtonElement = Ci.nsIDOMXULButtonElement
33 XULControlElement = Ci.nsIDOMXULControlElement
34 XULMenuListElement = Ci.nsIDOMXULMenuListElement
35 XULTextBoxElement = Ci.nsIDOMXULTextBoxElement
36
37 USE_CAPTURE = true
38
39
40
41 # Element classification helpers
42
43 isActivatable = (element) ->
44 return element instanceof HTMLAnchorElement or
45 element instanceof HTMLButtonElement or
46 (element instanceof HTMLInputElement and element.type in [
47 'button', 'submit', 'reset', 'image'
48 ]) or
49 element instanceof XULButtonElement
50
51 isAdjustable = (element) ->
52 return element instanceof HTMLInputElement and element.type in [
53 'checkbox', 'radio', 'file', 'color'
54 'date', 'time', 'datetime', 'datetime-local', 'month', 'week'
55 ] or
56 element instanceof XULControlElement or
57 # Youtube special case.
58 element.classList?.contains('html5-video-player') or
59 element.classList?.contains('ytp-button')
60
61 isContentEditable = (element) ->
62 return element.isContentEditable or
63 # `g_editable` is a non-standard attribute commonly used by Google.
64 element.getAttribute?('g_editable') == 'true' or
65 element.ownerDocument?.body?.getAttribute('g_editable') == 'true'
66
67 isProperLink = (element) ->
68 # `.getAttribute` is used below instead of `.hasAttribute` to exclude `<a
69 # href="">`s used as buttons on some sites.
70 return element.getAttribute('href') and
71 (element instanceof HTMLAnchorElement or
72 element.ownerDocument instanceof XULDocument) and
73 not element.href.endsWith('#') and
74 not element.href.endsWith('#?') and
75 not element.href.startsWith('javascript:')
76
77 isTextInputElement = (element) ->
78 return (element instanceof HTMLInputElement and element.type in [
79 'text', 'search', 'tel', 'url', 'email', 'password', 'number'
80 ]) or
81 element instanceof HTMLTextAreaElement or
82 element instanceof XULTextBoxElement
83
84 isTypingElement = (element) ->
85 return isTextInputElement(element) or
86 # `<select>` elements can also receive text input: You may type the
87 # text of an item to select it.
88 element instanceof HTMLSelectElement or
89 element instanceof XULMenuListElement
90
91
92
93 # Active/focused element helpers
94
95 getActiveElement = (window) ->
96 { activeElement } = window.document
97 if activeElement instanceof HTMLFrameElement or
98 activeElement instanceof HTMLIFrameElement
99 return getActiveElement(activeElement.contentWindow)
100 else
101 return activeElement
102
103 blurActiveElement = (window, extraAllowedElements = null) ->
104 # Only blur focusable elements, in order to interfere with the browser as
105 # little as possible.
106 activeElement = getActiveElement(window)
107 if activeElement and
108 (activeElement.tabIndex > -1 or extraAllowedElements?.has(activeElement))
109 activeElement.blur()
110
111 blurActiveBrowserElement = (vim) ->
112 # - Some browser UI elements, such as the web console, are not marked as
113 # focusable, so we can’t check if the element is focusable as in
114 # `blurActiveElement`.
115 # - Blurring in the next tick allows to pass `<escape>` to the location bar to
116 # reset it, for example.
117 # - Focusing the current browser afterwards allows to pass `<escape>` as well
118 # as unbound keys to the page. However, focusing the browser also triggers
119 # focus events on `document` and `window` in the current page. Many pages
120 # re-focus some text input on those events, making it impossible to blur
121 # those! Therefore we tell the frame script to suppress those events.
122 { window } = vim
123 activeElement = getActiveElement(window)
124 vim._send('browserRefocus')
125 nextTick(window, ->
126 activeElement.blur()
127 window.gBrowser.selectedBrowser.focus()
128 )
129
130 # Focus an element and tell Firefox that the focus happened because of a user
131 # keypress (not just because some random programmatic focus).
132 focusElement = (element, options = {}) ->
133 focusManager = Cc['@mozilla.org/focus-manager;1']
134 .getService(Ci.nsIFocusManager)
135 focusManager.setFocus(element, focusManager.FLAG_BYKEY)
136 element.select?() if options.select
137
138 moveFocus = (direction) ->
139 focusManager = Cc['@mozilla.org/focus-manager;1']
140 .getService(Ci.nsIFocusManager)
141 directionFlag =
142 if direction == -1
143 focusManager.MOVEFOCUS_BACKWARD
144 else
145 focusManager.MOVEFOCUS_FORWARD
146 focusManager.moveFocus(
147 null, # Use current window.
148 null, # Move relative to the currently focused element.
149 directionFlag,
150 focusManager.FLAG_BYKEY
151 )
152
153 getFocusType = (event) ->
154 target = event.originalTarget
155 return switch
156 when isTextInputElement(target) or isContentEditable(target)
157 'editable'
158 when isActivatable(target)
159 'activatable'
160 when isAdjustable(target)
161 'adjustable'
162 else
163 null
164
165
166
167 # Event helpers
168
169 listen = (element, eventName, listener, useCapture = true) ->
170 element.addEventListener(eventName, listener, useCapture)
171 module.onShutdown(->
172 element.removeEventListener(eventName, listener, useCapture)
173 )
174
175 listenOnce = (element, eventName, listener, useCapture = true) ->
176 fn = (event) ->
177 listener(event)
178 element.removeEventListener(eventName, fn, useCapture)
179 listen(element, eventName, fn, useCapture)
180
181 suppressEvent = (event) ->
182 event.preventDefault()
183 event.stopPropagation()
184
185 # Simulate mouse click with a full chain of events. ('command' is for XUL
186 # elements.)
187 eventSequence = ['mouseover', 'mousedown', 'mouseup', 'click', 'command']
188 simulateClick = (element) ->
189 window = element.ownerDocument.defaultView
190 for type in eventSequence
191 mouseEvent = new window.MouseEvent(type, {
192 # Let the event bubble in order to trigger delegated event listeners.
193 bubbles: true
194 # Make the event cancelable so that `<a href="#">` can be used as a
195 # JavaScript-powered button without scrolling to the top of the page.
196 cancelable: true
197 })
198 element.dispatchEvent(mouseEvent)
199 return
200
201
202
203 # DOM helpers
204
205 area = (element) ->
206 return element.clientWidth * element.clientHeight
207
208 createBox = (document, className, parent = null, text = null) ->
209 box = document.createElement('box')
210 box.className = className
211 box.textContent = text if text?
212 parent.appendChild(box) if parent?
213 return box
214
215 insertText = (input, value) ->
216 { selectionStart, selectionEnd } = input
217 input.value =
218 input.value[0...selectionStart] + value + input.value[selectionEnd..]
219 input.selectionStart = input.selectionEnd = selectionStart + value.length
220
221 setAttributes = (element, attributes) ->
222 for attribute, value of attributes
223 element.setAttribute(attribute, value)
224 return
225
226
227
228 # Language helpers
229
230 class Counter
231 constructor: ({ start: @value = 0, @step = 1 }) ->
232 tick: -> @value += @step
233
234 class EventEmitter
235 constructor: ->
236 @listeners = {}
237
238 on: (event, listener) ->
239 (@listeners[event] ?= []).push(listener)
240
241 emit: (event, data) ->
242 for listener in @listeners[event] ? []
243 listener(data)
244 return
245
246 has = (obj, prop) -> Object::hasOwnProperty.call(obj, prop)
247
248 nextTick = (window, fn) -> window.setTimeout(fn, 0)
249
250 regexEscape = (s) -> s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
251
252 removeDuplicates = (array) ->
253 # coffeelint: disable=no_backticks
254 return `[...new Set(array)]`
255 # coffeelint: enable=no_backticks
256
257 # Remove duplicate characters from string (case insensitive).
258 removeDuplicateCharacters = (str) ->
259 return removeDuplicates( str.toLowerCase().split('') ).join('')
260
261
262
263 # Misc helpers
264
265 formatError = (error) ->
266 stack = String(error.stack?.formattedStack ? error.stack ? '')
267 .split('\n')
268 .filter((line) -> line.includes('.xpi!'))
269 .map((line) -> ' ' + line.replace(/(?:\/<)*@.+\.xpi!/g, '@'))
270 .join('\n')
271 return "#{ error }\n#{ stack }"
272
273 getCurrentLocation = ->
274 window = getCurrentWindow()
275 return new window.URL(window.gBrowser.selectedBrowser.currentURI.spec)
276
277 getCurrentWindow = ->
278 windowMediator = Cc['@mozilla.org/appshell/window-mediator;1']
279 .getService(Components.interfaces.nsIWindowMediator)
280 return windowMediator.getMostRecentWindow('navigator:browser')
281
282 loadCss = (name) ->
283 sss = Cc['@mozilla.org/content/style-sheet-service;1']
284 .getService(Ci.nsIStyleSheetService)
285 uri = Services.io.newURI("chrome://vimfx/skin/#{ name }.css", null, null)
286 method = sss.AUTHOR_SHEET
287 unless sss.sheetRegistered(uri, method)
288 sss.loadAndRegisterSheet(uri, method)
289 module.onShutdown(->
290 sss.unregisterSheet(uri, method)
291 )
292
293 observe = (topic, observer) ->
294 observer = {observe: observer} if typeof observer == 'function'
295 Services.obs.addObserver(observer, topic, false)
296 module.onShutdown(->
297 Services.obs.removeObserver(observer, topic, false)
298 )
299
300 openTab = (window, url, options) ->
301 { gBrowser } = window
302 window.TreeStyleTabService?.readyToOpenChildTab(gBrowser.selectedTab)
303 gBrowser.loadOneTab(url, options)
304
305 # Executes `fn` and measures how much time it took.
306 timeIt = (fn, name) ->
307 console.time(name)
308 result = fn()
309 console.timeEnd(name)
310 return result
311
312 writeToClipboard = (text) ->
313 clipboardHelper = Cc['@mozilla.org/widget/clipboardhelper;1']
314 .getService(Ci.nsIClipboardHelper)
315 clipboardHelper.copyString(text)
316
317
318
319 module.exports = {
320 isActivatable
321 isAdjustable
322 isContentEditable
323 isProperLink
324 isTextInputElement
325 isTypingElement
326
327 getActiveElement
328 blurActiveElement
329 blurActiveBrowserElement
330 focusElement
331 moveFocus
332 getFocusType
333
334 listen
335 listenOnce
336 suppressEvent
337 simulateClick
338
339 area
340 createBox
341 insertText
342 setAttributes
343
344 Counter
345 EventEmitter
346 has
347 nextTick
348 regexEscape
349 removeDuplicates
350 removeDuplicateCharacters
351
352 formatError
353 getCurrentLocation
354 getCurrentWindow
355 loadCss
356 observe
357 openTab
358 timeIt
359 writeToClipboard
360 }
Imprint / Impressum