]> git.gir.st - VimFx.git/blob - extension/lib/utils.coffee
Move Text Input mode into Normal mode
[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 { classes: Cc, interfaces: Ci, utils: Cu } = Components
23
24 Window = Ci.nsIDOMWindow
25 ChromeWindow = Ci.nsIDOMChromeWindow
26 Element = Ci.nsIDOMElement
27 HTMLDocument = Ci.nsIDOMHTMLDocument
28 HTMLAnchorElement = Ci.nsIDOMHTMLAnchorElement
29 HTMLButtonElement = Ci.nsIDOMHTMLButtonElement
30 HTMLInputElement = Ci.nsIDOMHTMLInputElement
31 HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement
32 HTMLSelectElement = Ci.nsIDOMHTMLSelectElement
33 XULDocument = Ci.nsIDOMXULDocument
34 XULButtonElement = Ci.nsIDOMXULButtonElement
35 XULControlElement = Ci.nsIDOMXULControlElement
36 XULMenuListElement = Ci.nsIDOMXULMenuListElement
37 XULTextBoxElement = Ci.nsIDOMXULTextBoxElement
38
39 class Bucket
40 constructor: (@newFunc, @observer = null) ->
41 @bucket = new WeakMap()
42
43 get: (obj) ->
44 if @bucket.has(obj)
45 value = @bucket.get(obj)
46 else
47 value = @newFunc(obj)
48 @bucket.set(obj, value)
49 @observer.emit('bucket.get', value) if @observer
50 return value
51
52 forget: (obj) ->
53 @bucket.delete(obj)
54
55 class EventEmitter
56 constructor: ->
57 @listeners = {}
58
59 on: (event, listener) ->
60 (@listeners[event] ?= []).push(listener)
61
62 emit: (event, data) ->
63 for listener in @listeners[event] ? []
64 listener(data)
65 return
66
67 getEventWindow = (event) ->
68 if event.originalTarget instanceof Window
69 return event.originalTarget
70 else
71 doc = event.originalTarget.ownerDocument or event.originalTarget
72 if doc instanceof HTMLDocument or doc instanceof XULDocument
73 return doc.defaultView
74
75 getEventRootWindow = (event) ->
76 return unless window = getEventWindow(event)
77 return getRootWindow(window)
78
79 getEventCurrentTabWindow = (event) ->
80 return unless rootWindow = getEventRootWindow(event)
81 return getCurrentTabWindow(rootWindow)
82
83 getRootWindow = (window) ->
84 return window
85 .QueryInterface(Ci.nsIInterfaceRequestor)
86 .getInterface(Ci.nsIWebNavigation)
87 .QueryInterface(Ci.nsIDocShellTreeItem)
88 .rootTreeItem
89 .QueryInterface(Ci.nsIInterfaceRequestor)
90 .getInterface(Window)
91
92 getCurrentTabWindow = (window) ->
93 return window.gBrowser.selectedTab.linkedBrowser.contentWindow
94
95 blurActiveElement = (window) ->
96 # Only blur focusable elements, in order to interfere with the browser as
97 # little as possible.
98 { activeElement } = window.document
99 if activeElement and activeElement.tabIndex > -1
100 activeElement.blur()
101
102 # Focus an element and tell Firefox that the focus happened because of a user
103 # keypress (not just because some random programmatic focus).
104 focusElement = (element, options = {}) ->
105 focusManager = Cc['@mozilla.org/focus-manager;1']
106 .getService(Ci.nsIFocusManager)
107 focusManager.setFocus(element, focusManager.FLAG_BYKEY)
108 element.select?() if options.select
109
110 isProperLink = (element) ->
111 # `.getAttribute` is used below instead of `.hasAttribute` to exclude `<a
112 # href="">`s used as buttons on some sites.
113 return element.getAttribute('href') and
114 (element instanceof HTMLAnchorElement or
115 element.ownerDocument instanceof XULDocument) and
116 not element.href.endsWith('#') and
117 not element.href.startsWith('javascript:')
118
119 isTextInputElement = (element) ->
120 return (element instanceof HTMLInputElement and element.type in [
121 'text', 'search', 'tel', 'url', 'email', 'password', 'number'
122 ]) or
123 element instanceof HTMLTextAreaElement or
124 # `<select>` elements can also receive text input: You may type the
125 # text of an item to select it.
126 element instanceof HTMLSelectElement or
127 element instanceof XULMenuListElement or
128 element instanceof XULTextBoxElement
129
130 isContentEditable = (element) ->
131 return element.isContentEditable or
132 isGoogleEditable(element)
133
134 isGoogleEditable = (element) ->
135 # `g_editable` is a non-standard attribute commonly used by Google.
136 return element.getAttribute?('g_editable') == 'true' or
137 element.ownerDocument.body?.getAttribute('g_editable') == 'true'
138
139 isActivatable = (element) ->
140 return element instanceof HTMLAnchorElement or
141 element instanceof HTMLButtonElement or
142 (element instanceof HTMLInputElement and element.type in [
143 'button', 'submit', 'reset', 'image'
144 ]) or
145 element instanceof XULButtonElement
146
147 isAdjustable = (element) ->
148 return element instanceof HTMLInputElement and element.type in [
149 'checkbox', 'radio', 'file', 'color'
150 'date', 'time', 'datetime', 'datetime-local', 'month', 'week'
151 ] or
152 element instanceof XULControlElement or
153 # Youtube special case.
154 element.classList?.contains('html5-video-player') or
155 element.classList?.contains('ytp-button')
156
157 area = (element) ->
158 return element.clientWidth * element.clientHeight
159
160 getSessionStore = ->
161 Cc['@mozilla.org/browser/sessionstore;1'].getService(Ci.nsISessionStore)
162
163 loadCss = (name) ->
164 sss = Cc['@mozilla.org/content/style-sheet-service;1']
165 .getService(Ci.nsIStyleSheetService)
166 uri = Services.io.newURI("chrome://vimfx/skin/#{ name }.css", null, null)
167 method = sss.AUTHOR_SHEET
168 unless sss.sheetRegistered(uri, method)
169 sss.loadAndRegisterSheet(uri, method)
170
171 module.onShutdown(->
172 sss.unregisterSheet(uri, method)
173 )
174
175 # Store events that we’ve simulated. A `WeakMap` is used in order not to leak
176 # memory. This approach is better than for example setting `event.simulated =
177 # true`, since that tells the sites that the click was simulated, and allows
178 # sites to spoof it.
179 simulated_events = new WeakMap()
180
181 # Simulate mouse click with a full chain of events. ('command' is for XUL
182 # elements.)
183 eventSequence = ['mouseover', 'mousedown', 'mouseup', 'click', 'command']
184 simulateClick = (element) ->
185 window = element.ownerDocument.defaultView
186 for type in eventSequence
187 mouseEvent = new window.MouseEvent(type, {
188 # Let the event bubble in order to trigger delegated event listeners.
189 bubbles: true
190 # Make the event cancelable so that `<a href="#">` can be used as a
191 # JavaScript-powered button without scrolling to the top of the page.
192 cancelable: true
193 })
194 element.dispatchEvent(mouseEvent)
195
196 isEventSimulated = (event) ->
197 return simulated_events.has(event)
198
199 # Write a string to the system clipboard.
200 writeToClipboard = (text) ->
201 clipboardHelper = Cc['@mozilla.org/widget/clipboardhelper;1']
202 .getService(Ci.nsIClipboardHelper)
203 clipboardHelper.copyString(text)
204
205 # Executes function `func` and measures how much time it took.
206 timeIt = (func, name) ->
207 console.time(name)
208 result = func()
209 console.timeEnd(name)
210 return result
211
212 createBox = (document, className, parent = null, text = null) ->
213 box = document.createElement('box')
214 box.className = className
215 box.textContent = text if text?
216 parent.appendChild(box) if parent?
217 return box
218
219 setAttributes = (element, attributes) ->
220 for attribute, value of attributes
221 element.setAttribute(attribute, value)
222 return
223
224 insertText = (input, value) ->
225 { selectionStart, selectionEnd } = input
226 input.value =
227 input.value[0...selectionStart] + value + input.value[selectionEnd..]
228 input.selectionStart = input.selectionEnd = selectionStart + value.length
229
230 isURL = (str) ->
231 try
232 url = Cc['@mozilla.org/network/io-service;1']
233 .getService(Ci.nsIIOService)
234 .newURI(str, null, null)
235 .QueryInterface(Ci.nsIURL)
236 return true
237 catch err
238 return false
239
240 # Use Firefox services to search for a given string.
241 browserSearchSubmission = (str) ->
242 ss = Cc['@mozilla.org/browser/search-service;1']
243 .getService(Ci.nsIBrowserSearchService)
244
245 engine = ss.currentEngine or ss.defaultEngine
246 return engine.getSubmission(str, null)
247
248 openTab = (rootWindow, url, options) ->
249 { gBrowser } = rootWindow
250 rootWindow.TreeStyleTabService?.readyToOpenChildTab(gBrowser.selectedTab)
251 gBrowser.loadOneTab(url, options)
252
253 # Remove duplicate characters from string (case insensitive).
254 removeDuplicateCharacters = (str) ->
255 return removeDuplicates( str.toLowerCase().split('') ).join('')
256
257 # Escape a string to render it usable in regular expressions.
258 regexpEscape = (s) -> s and s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
259
260 removeDuplicates = (array) ->
261 # coffeelint: disable=no_backticks
262 return `[...new Set(array)]`
263 # coffeelint: enable=no_backticks
264
265 formatError = (error) ->
266 stack = String(error.stack?.formattedStack ? error.stack ? '')
267 .split('\n')
268 .filter((line) -> line.contains('.xpi!'))
269 .map((line) -> ' ' + line.replace(/(?:\/<)*@.+\.xpi!/g, '@'))
270 .join('\n')
271 return "#{ error }\n#{ stack }"
272
273 observe = (topic, observer) ->
274 observer = {observe: observer} if typeof observer == 'function'
275 Services.obs.addObserver(observer, topic, false)
276 module.onShutdown(->
277 Services.obs.removeObserver(observer, topic, false)
278 )
279
280 has = Function::call.bind(Object::hasOwnProperty)
281
282 class Counter
283 constructor: ({start, step}) ->
284 @value = start ? 0
285 @step = step ? 1
286 tick: -> @value += @step
287
288 module.exports = {
289 Bucket
290 EventEmitter
291 getEventWindow
292 getEventRootWindow
293 getEventCurrentTabWindow
294 getRootWindow
295 getCurrentTabWindow
296
297 blurActiveElement
298 focusElement
299 isProperLink
300 isTextInputElement
301 isContentEditable
302 isActivatable
303 isAdjustable
304 area
305 getSessionStore
306
307 loadCss
308
309 simulateClick
310 isEventSimulated
311 writeToClipboard
312 timeIt
313
314 createBox
315 setAttributes
316 insertText
317 isURL
318 browserSearchSubmission
319 openTab
320 regexpEscape
321 removeDuplicates
322 removeDuplicateCharacters
323 formatError
324 observe
325 has
326 Counter
327 }
Imprint / Impressum