]> git.gir.st - VimFx.git/blob - extension/lib/utils.coffee
Merge pull request #512 from akhodakivskiy/multi-process
[VimFx.git] / extension / lib / utils.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2012, 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014.
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 class EventEmitter
40 constructor: ->
41 @listeners = {}
42
43 on: (event, listener) ->
44 (@listeners[event] ?= []).push(listener)
45
46 emit: (event, data) ->
47 for listener in @listeners[event] ? []
48 listener(data)
49 return
50
51 blurActiveElement = (window, { force = false } = {}) ->
52 # Only blur focusable elements, in order to interfere with the browser as
53 # little as possible.
54 activeElement = getActiveElement(window)
55 if activeElement and (activeElement.tabIndex > -1 or force)
56 activeElement.blur()
57
58 getActiveElement = (window) ->
59 { activeElement } = window.document
60 if activeElement instanceof HTMLFrameElement or
61 activeElement instanceof HTMLIFrameElement
62 return getActiveElement(activeElement.contentWindow)
63 else
64 return activeElement
65
66 # Focus an element and tell Firefox that the focus happened because of a user
67 # keypress (not just because some random programmatic focus).
68 focusElement = (element, options = {}) ->
69 focusManager = Cc['@mozilla.org/focus-manager;1']
70 .getService(Ci.nsIFocusManager)
71 focusManager.setFocus(element, focusManager.FLAG_BYKEY)
72 element.select?() if options.select
73
74 isProperLink = (element) ->
75 # `.getAttribute` is used below instead of `.hasAttribute` to exclude `<a
76 # href="">`s used as buttons on some sites.
77 return element.getAttribute('href') and
78 (element instanceof HTMLAnchorElement or
79 element.ownerDocument instanceof XULDocument) and
80 not element.href.endsWith('#') and
81 not element.href.startsWith('javascript:')
82
83 isTextInputElement = (element) ->
84 return (element instanceof HTMLInputElement and element.type in [
85 'text', 'search', 'tel', 'url', 'email', 'password', 'number'
86 ]) or
87 element instanceof HTMLTextAreaElement or
88 # `<select>` elements can also receive text input: You may type the
89 # text of an item to select it.
90 element instanceof HTMLSelectElement or
91 element instanceof XULMenuListElement or
92 element instanceof XULTextBoxElement
93
94 isContentEditable = (element) ->
95 return element.isContentEditable or
96 isGoogleEditable(element)
97
98 isGoogleEditable = (element) ->
99 # `g_editable` is a non-standard attribute commonly used by Google.
100 return element.getAttribute?('g_editable') == 'true' or
101 element.ownerDocument.body?.getAttribute('g_editable') == 'true'
102
103 isActivatable = (element) ->
104 return element instanceof HTMLAnchorElement or
105 element instanceof HTMLButtonElement or
106 (element instanceof HTMLInputElement and element.type in [
107 'button', 'submit', 'reset', 'image'
108 ]) or
109 element instanceof XULButtonElement
110
111 isAdjustable = (element) ->
112 return element instanceof HTMLInputElement and element.type in [
113 'checkbox', 'radio', 'file', 'color'
114 'date', 'time', 'datetime', 'datetime-local', 'month', 'week'
115 ] or
116 element instanceof XULControlElement or
117 # Youtube special case.
118 element.classList?.contains('html5-video-player') or
119 element.classList?.contains('ytp-button')
120
121 getFocusType = (event) ->
122 target = event.originalTarget
123 return switch
124 when isTextInputElement(target) or isContentEditable(target)
125 'editable'
126 when isActivatable(target)
127 'activatable'
128 when isAdjustable(target)
129 'adjustable'
130 else
131 null
132
133 area = (element) ->
134 return element.clientWidth * element.clientHeight
135
136 loadCss = (name) ->
137 sss = Cc['@mozilla.org/content/style-sheet-service;1']
138 .getService(Ci.nsIStyleSheetService)
139 uri = Services.io.newURI("chrome://vimfx/skin/#{ name }.css", null, null)
140 method = sss.AUTHOR_SHEET
141 unless sss.sheetRegistered(uri, method)
142 sss.loadAndRegisterSheet(uri, method)
143
144 module.onShutdown(->
145 sss.unregisterSheet(uri, method)
146 )
147
148 # Simulate mouse click with a full chain of events. ('command' is for XUL
149 # elements.)
150 eventSequence = ['mouseover', 'mousedown', 'mouseup', 'click', 'command']
151 simulateClick = (element) ->
152 window = element.ownerDocument.defaultView
153 simulatedEvents = {}
154 for type in eventSequence
155 mouseEvent = new window.MouseEvent(type, {
156 # Let the event bubble in order to trigger delegated event listeners.
157 bubbles: true
158 # Make the event cancelable so that `<a href="#">` can be used as a
159 # JavaScript-powered button without scrolling to the top of the page.
160 cancelable: true
161 })
162 element.dispatchEvent(mouseEvent)
163 simulatedEvents[type] = mouseEvent
164 return simulatedEvents
165
166 listen = (element, eventName, listener) ->
167 element.addEventListener(eventName, listener, USE_CAPTURE)
168 module.onShutdown(->
169 element.removeEventListener(eventName, listener, USE_CAPTURE)
170 )
171
172 suppressEvent = (event) ->
173 event.preventDefault()
174 event.stopPropagation()
175
176 # Write a string to the system clipboard.
177 writeToClipboard = (text) ->
178 clipboardHelper = Cc['@mozilla.org/widget/clipboardhelper;1']
179 .getService(Ci.nsIClipboardHelper)
180 clipboardHelper.copyString(text)
181
182 # Executes function `func` and measures how much time it took.
183 timeIt = (func, name) ->
184 console.time(name)
185 result = func()
186 console.timeEnd(name)
187 return result
188
189 createBox = (document, className, parent = null, text = null) ->
190 box = document.createElement('box')
191 box.className = className
192 box.textContent = text if text?
193 parent.appendChild(box) if parent?
194 return box
195
196 setAttributes = (element, attributes) ->
197 for attribute, value of attributes
198 element.setAttribute(attribute, value)
199 return
200
201 insertText = (input, value) ->
202 { selectionStart, selectionEnd } = input
203 input.value =
204 input.value[0...selectionStart] + value + input.value[selectionEnd..]
205 input.selectionStart = input.selectionEnd = selectionStart + value.length
206
207 openTab = (window, url, options) ->
208 { gBrowser } = window
209 window.TreeStyleTabService?.readyToOpenChildTab(gBrowser.selectedTab)
210 gBrowser.loadOneTab(url, options)
211
212 # Remove duplicate characters from string (case insensitive).
213 removeDuplicateCharacters = (str) ->
214 return removeDuplicates( str.toLowerCase().split('') ).join('')
215
216 # Escape a string to render it usable in regular expressions.
217 regexpEscape = (s) -> s and s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
218
219 removeDuplicates = (array) ->
220 # coffeelint: disable=no_backticks
221 return `[...new Set(array)]`
222 # coffeelint: enable=no_backticks
223
224 formatError = (error) ->
225 stack = String(error.stack?.formattedStack ? error.stack ? '')
226 .split('\n')
227 .filter((line) -> line.includes('.xpi!'))
228 .map((line) -> ' ' + line.replace(/(?:\/<)*@.+\.xpi!/g, '@'))
229 .join('\n')
230 return "#{ error }\n#{ stack }"
231
232 observe = (topic, observer) ->
233 observer = {observe: observer} if typeof observer == 'function'
234 Services.obs.addObserver(observer, topic, false)
235 module.onShutdown(->
236 Services.obs.removeObserver(observer, topic, false)
237 )
238
239 has = Function::call.bind(Object::hasOwnProperty)
240
241 class Counter
242 constructor: ({start, step}) ->
243 @value = start ? 0
244 @step = step ? 1
245 tick: -> @value += @step
246
247 getCurrentWindow = ->
248 windowMediator = Cc['@mozilla.org/appshell/window-mediator;1']
249 .getService(Components.interfaces.nsIWindowMediator)
250 return windowMediator.getMostRecentWindow('navigator:browser')
251
252 getCurrentLocation = (browser = null) ->
253 browser = getCurrentWindow().gBrowser.selectedBrowser unless browser?
254 return new browser.ownerGlobal.URL(browser.currentURI.spec)
255
256 module.exports = {
257 EventEmitter
258
259 blurActiveElement
260 getActiveElement
261 focusElement
262 isProperLink
263 isTextInputElement
264 isContentEditable
265 isActivatable
266 isAdjustable
267 getFocusType
268 area
269
270 loadCss
271
272 simulateClick
273 listen
274 suppressEvent
275 writeToClipboard
276 timeIt
277
278 createBox
279 setAttributes
280 insertText
281 openTab
282 regexpEscape
283 removeDuplicates
284 removeDuplicateCharacters
285 formatError
286 observe
287 has
288 Counter
289 getCurrentWindow
290 getCurrentLocation
291 }
Imprint / Impressum