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