]> git.gir.st - VimFx.git/blob - extension/packages/utils.coffee
Merge branch 'develop' into proper-modes
[VimFx.git] / extension / packages / utils.coffee
1 { unload } = require 'unload'
2 { getPref
3 , setPref
4 , getDefaultPref
5 } = require 'prefs'
6
7 { classes: Cc, interfaces: Ci, utils: Cu } = Components
8
9 HTMLInputElement = Ci.nsIDOMHTMLInputElement
10 HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement
11 HTMLSelectElement = Ci.nsIDOMHTMLSelectElement
12 XULDocument = Ci.nsIDOMXULDocument
13 XULElement = Ci.nsIDOMXULElement
14 HTMLDocument = Ci.nsIDOMHTMLDocument
15 HTMLElement = Ci.nsIDOMHTMLElement
16 Window = Ci.nsIDOMWindow
17 ChromeWindow = Ci.nsIDOMChromeWindow
18
19 _clip = Cc['@mozilla.org/widget/clipboard;1'].getService(Ci.nsIClipboard)
20
21 class Bucket
22 constructor: (@idFunc, @newFunc) ->
23 @bucket = {}
24
25 get: (obj) ->
26 id = @idFunc(obj)
27 if container = @bucket[id]
28 return container
29 else
30 return @bucket[id] = @newFunc(obj)
31
32 forget: (obj) ->
33 delete @bucket[id] if id = @idFunc(obj)
34
35 getEventWindow = (event) ->
36 if event.originalTarget instanceof Window
37 return event.originalTarget
38 else
39 doc = event.originalTarget.ownerDocument or event.originalTarget
40 if doc instanceof HTMLDocument or doc instanceof XULDocument
41 return doc.defaultView
42
43 getEventRootWindow = (event) ->
44 return unless window = getEventWindow(event)
45 return getRootWindow(window)
46
47 getEventCurrentTabWindow = (event) ->
48 return unless rootWindow = getEventRootWindow(event)
49 return getCurrentTabWindow(rootWindow)
50
51 getRootWindow = (window) ->
52 return window
53 .QueryInterface(Ci.nsIInterfaceRequestor)
54 .getInterface(Ci.nsIWebNavigation)
55 .QueryInterface(Ci.nsIDocShellTreeItem)
56 .rootTreeItem
57 .QueryInterface(Ci.nsIInterfaceRequestor)
58 .getInterface(Window)
59
60 getCurrentTabWindow = (window) ->
61 return window.gBrowser.selectedTab.linkedBrowser.contentWindow
62
63 blurActiveElement = (window) ->
64 # Only blur editable elements, in order not to interfere with the browser too much. TODO: Is that
65 # really needed? What if a website has made more elements focusable -- shouldn't those also be
66 # blurred?
67 { activeElement } = window.document
68 if isElementEditable(activeElement)
69 activeElement.blur()
70
71 isTextInputElement = (element) ->
72 return element instanceof HTMLInputElement or \
73 element instanceof HTMLTextAreaElement
74
75 isElementEditable = (element) ->
76 return element.isContentEditable or \
77 element instanceof HTMLInputElement or \
78 element instanceof HTMLTextAreaElement or \
79 element instanceof HTMLSelectElement or \
80 element.getAttribute('g_editable') == 'true' or \
81 element.getAttribute('contenteditable')?.toLowerCase() == 'true' or \
82 element.ownerDocument?.designMode?.toLowerCase() == 'on'
83
84 getWindowId = (window) ->
85 return window
86 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
87 .getInterface(Components.interfaces.nsIDOMWindowUtils)
88 .outerWindowID
89
90 getSessionStore = ->
91 Cc['@mozilla.org/browser/sessionstore;1'].getService(Ci.nsISessionStore)
92
93 # Function that returns a URI to the css file that's part of the extension
94 cssUri = do ->
95 (name) ->
96 baseURI = Services.io.newURI(__SCRIPT_URI_SPEC__, null, null)
97 uri = Services.io.newURI("resources/#{ name }.css", null, baseURI)
98 return uri
99
100 # Loads the css identified by the name in the StyleSheetService as User Stylesheet
101 # The stylesheet is then appended to every document, but it can be overwritten by
102 # any user css
103 loadCss = do ->
104 sss = Cc['@mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService)
105 return (name) ->
106 uri = cssUri(name)
107 # `AGENT_SHEET` is used to override userContent.css and Stylish. Custom website themes installed
108 # by users often make the hint markers unreadable, for example. Just using `!important` in the
109 # CSS is not enough.
110 if !sss.sheetRegistered(uri, sss.AGENT_SHEET)
111 sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET)
112
113 unload ->
114 sss.unregisterSheet(uri, sss.AGENT_SHEET)
115
116 # Simulate mouse click with full chain of event
117 # Copied from Vimium codebase
118 simulateClick = (element, modifiers = {}) ->
119 document = element.ownerDocument
120 window = document.defaultView
121
122 eventSequence = ['mouseover', 'mousedown', 'mouseup', 'click']
123 for event in eventSequence
124 mouseEvent = document.createEvent('MouseEvents')
125 mouseEvent.initMouseEvent(event, true, true, window, 1, 0, 0, 0, 0, modifiers.ctrlKey, false, false,
126 modifiers.metaKey, 0, null)
127 # Debugging note: Firefox will not execute the element's default action if we dispatch this click event,
128 # but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately
129 element.dispatchEvent(mouseEvent)
130
131 WHEEL_MODE_PIXEL = Ci.nsIDOMWheelEvent.DOM_DELTA_PIXEL
132 WHEEL_MODE_LINE = Ci.nsIDOMWheelEvent.DOM_DELTA_LINE
133 WHEEL_MODE_PAGE = Ci.nsIDOMWheelEvent.DOM_DELTA_PAGE
134
135 # Simulate mouse scroll event by specific offsets given
136 # that mouse cursor is at specified position
137 simulateWheel = (window, deltaX, deltaY, mode = WHEEL_MODE_PIXEL) ->
138 windowUtils = window
139 .QueryInterface(Ci.nsIInterfaceRequestor)
140 .getInterface(Ci.nsIDOMWindowUtils)
141
142 [pX, pY] = [window.innerWidth / 2, window.innerHeight / 2]
143 windowUtils.sendWheelEvent(
144 pX, pY, # Window offset (x, y) in pixels
145 deltaX, deltaY, 0, # Deltas (x, y, z)
146 mode, # Mode (pixel, line, page)
147 0, # Key Modifiers
148 0, 0, # Line or Page deltas (x, y)
149 0 # Options
150 )
151
152 # Write a string into system clipboard
153 writeToClipboard = (window, text) ->
154 str = Cc['@mozilla.org/supports-string;1'].createInstance(Ci.nsISupportsString)
155 str.data = text
156
157 trans = Cc['@mozilla.org/widget/transferable;1'].createInstance(Ci.nsITransferable)
158
159 if trans.init
160 privacyContext = window
161 .QueryInterface(Ci.nsIInterfaceRequestor)
162 .getInterface(Ci.nsIWebNavigation)
163 .QueryInterface(Ci.nsILoadContext)
164 trans.init(privacyContext)
165
166 trans.addDataFlavor('text/unicode')
167 trans.setTransferData('text/unicode', str, text.length * 2)
168
169 _clip.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard)
170
171 # Write a string into system clipboard
172 readFromClipboard = (window) ->
173 trans = Cc['@mozilla.org/widget/transferable;1'].createInstance(Ci.nsITransferable)
174
175 if trans.init
176 privacyContext = window
177 .QueryInterface(Ci.nsIInterfaceRequestor)
178 .getInterface(Ci.nsIWebNavigation)
179 .QueryInterface(Ci.nsILoadContext)
180 trans.init(privacyContext)
181
182 trans.addDataFlavor('text/unicode')
183
184 _clip.getData(trans, Ci.nsIClipboard.kGlobalClipboard)
185
186 str = {}
187 strLength = {}
188
189 trans.getTransferData('text/unicode', str, strLength)
190
191 if str
192 str = str.value.QueryInterface(Ci.nsISupportsString)
193 return str.data.substring(0, strLength.value / 2)
194
195 return undefined
196
197 # Executes function `func` and mearues how much time it took
198 timeIt = (func, msg) ->
199 start = new Date().getTime()
200 result = func()
201 end = new Date().getTime()
202
203 console.log(msg, end - start)
204 return result
205
206 isBlacklisted = (str) ->
207 matchingRules = getMatchingBlacklistRules(str)
208 return (matchingRules.length != 0)
209
210 # Returns all rules in the blacklist that match the provided string
211 getMatchingBlacklistRules = (str) ->
212 matchingRules = []
213 for rule in getBlacklist()
214 # Wildcards: * and !
215 regexifiedRule = regexpEscape(rule).replace(/\\\*/g, '.*').replace(/!/g, '.')
216 if str.match(///^#{ regexifiedRule }$///)
217 matchingRules.push(rule)
218
219 return matchingRules
220
221 getBlacklist = ->
222 return splitBlacklistString(getPref('black_list'))
223
224 setBlacklist = (blacklist) ->
225 setPref('black_list', blacklist.join(', '))
226
227 splitBlacklistString = (str) ->
228 # Comma/space separated list
229 return str.split(/[\s,]+/)
230
231 updateBlacklist = ({ add, remove} = {}) ->
232 blacklist = getBlacklist()
233
234 if add
235 blacklist.push(splitBlacklistString(add)...)
236
237 blacklist = blacklist.filter((rule) -> rule != '')
238 blacklist = removeDuplicates(blacklist)
239
240 if remove
241 for rule in splitBlacklistString(remove) when rule in blacklist
242 blacklist.splice(blacklist.indexOf(rule), 1)
243
244 setBlacklist(blacklist)
245
246 # Gets VimFx verions. AddonManager only provides async API to access addon data, so it's a bit tricky...
247 getVersion = do ->
248 version = null
249
250 if version == null
251 scope = {}
252 addonId = getPref('addon_id')
253 Cu.import('resource://gre/modules/AddonManager.jsm', scope)
254 scope.AddonManager.getAddonByID(addonId, (addon) -> version = addon.version)
255
256 return ->
257 return version
258
259 parseHTML = (document, html) ->
260 parser = Cc['@mozilla.org/parserutils;1'].getService(Ci.nsIParserUtils)
261 flags = parser.SanitizerAllowStyle
262 return parser.parseFragment(html, flags, false, null, document.documentElement)
263
264 # Uses nsIIOService to parse a string as a URL and find out if it is a URL
265 isURL = (str) ->
266 try
267 url = Cc['@mozilla.org/network/io-service;1']
268 .getService(Ci.nsIIOService)
269 .newURI(str, null, null)
270 .QueryInterface(Ci.nsIURL)
271 return true
272 catch err
273 return false
274
275 # Use Firefox services to search for a given string
276 browserSearchSubmission = (str) ->
277 ss = Cc['@mozilla.org/browser/search-service;1']
278 .getService(Ci.nsIBrowserSearchService)
279
280 engine = ss.currentEngine or ss.defaultEngine
281 return engine.getSubmission(str, null)
282
283 # Get hint characters, convert them to lower case and fall back
284 # to default hint characters if there are less than 3 chars
285 getHintChars = ->
286 hintChars = removeDuplicateCharacters(getPref('hint_chars'))
287 if hintChars.length < 2
288 hintChars = getDefaultPref('hint_chars')
289
290 return hintChars
291
292 # Remove duplicate characters from string (case insensitive)
293 removeDuplicateCharacters = (str) ->
294 return removeDuplicates( str.toLowerCase().split('') ).join('')
295
296 # Return URI to some file in the extension packaged as resource
297 getResourceURI = do ->
298 baseURI = Services.io.newURI(__SCRIPT_URI_SPEC__, null, null)
299 return (path) -> return Services.io.newURI(path, null, baseURI)
300
301 # Escape string to render it usable in regular expressions
302 regexpEscape = (s) -> s and s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
303
304 removeDuplicates = (array) ->
305 seen = {}
306 return array.filter((item) -> if seen[item] then false else (seen[item] = true))
307
308 exports.Bucket = Bucket
309 exports.getEventWindow = getEventWindow
310 exports.getEventRootWindow = getEventRootWindow
311 exports.getEventCurrentTabWindow = getEventCurrentTabWindow
312 exports.getRootWindow = getRootWindow
313 exports.getCurrentTabWindow = getCurrentTabWindow
314
315 exports.getWindowId = getWindowId
316 exports.blurActiveElement = blurActiveElement
317 exports.isTextInputElement = isTextInputElement
318 exports.isElementEditable = isElementEditable
319 exports.getSessionStore = getSessionStore
320
321 exports.loadCss = loadCss
322
323 exports.simulateClick = simulateClick
324 exports.simulateWheel = simulateWheel
325 exports.WHEEL_MODE_PIXEL = WHEEL_MODE_PIXEL
326 exports.WHEEL_MODE_LINE = WHEEL_MODE_LINE
327 exports.WHEEL_MODE_PAGE = WHEEL_MODE_PAGE
328 exports.readFromClipboard = readFromClipboard
329 exports.writeToClipboard = writeToClipboard
330 exports.timeIt = timeIt
331
332 exports.getMatchingBlacklistRules = getMatchingBlacklistRules
333 exports.isBlacklisted = isBlacklisted
334 exports.updateBlacklist = updateBlacklist
335
336 exports.getVersion = getVersion
337 exports.parseHTML = parseHTML
338 exports.isURL = isURL
339 exports.browserSearchSubmission = browserSearchSubmission
340 exports.getHintChars = getHintChars
341 exports.removeDuplicateCharacters = removeDuplicateCharacters
342 exports.getResourceURI = getResourceURI
Imprint / Impressum