2 # Copyright Anton Khodakivskiy 2012, 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014.
4 # Copyright Wang Zhuochun 2013.
6 # This file is part of VimFx.
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.
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.
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/>.
22 { classes: Cc, interfaces: Ci, utils: Cu } = Components
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
40 constructor: (@newFunc, @observer = null) ->
41 @bucket = new WeakMap()
45 value = @bucket.get(obj)
48 @bucket.set(obj, value)
49 @observer.emit('bucket.get', value) if @observer
59 on: (event, listener) ->
60 (@listeners[event] ?= []).push(listener)
62 emit: (event, data) ->
63 for listener in @listeners[event] ? []
67 getEventWindow = (event) ->
68 if event.originalTarget instanceof Window
69 return event.originalTarget
71 doc = event.originalTarget.ownerDocument or event.originalTarget
72 if doc instanceof HTMLDocument or doc instanceof XULDocument
73 return doc.defaultView
75 getEventRootWindow = (event) ->
76 return unless window = getEventWindow(event)
77 return getRootWindow(window)
79 getEventCurrentTabWindow = (event) ->
80 return unless rootWindow = getEventRootWindow(event)
81 return getCurrentTabWindow(rootWindow)
83 getRootWindow = (window) ->
85 .QueryInterface(Ci.nsIInterfaceRequestor)
86 .getInterface(Ci.nsIWebNavigation)
87 .QueryInterface(Ci.nsIDocShellTreeItem)
89 .QueryInterface(Ci.nsIInterfaceRequestor)
92 getCurrentTabWindow = (window) ->
93 return window.gBrowser.selectedTab.linkedBrowser.contentWindow
95 blurActiveElement = (window) ->
96 # Only blur focusable elements, in order to interfere with the browser as
98 { activeElement } = window.document
99 if activeElement and activeElement.tabIndex > -1
102 isProperLink = (element) ->
103 # `.getAttribute` is used below instead of `.hasAttribute` to exclude `<a
104 # href="">`s used as buttons on some sites.
105 return element.getAttribute('href') and
106 (element instanceof HTMLAnchorElement or
107 element.ownerDocument instanceof XULDocument) and
108 not element.href.endsWith('#') and
109 not element.href.startsWith('javascript:')
111 isTextInputElement = (element) ->
112 return (element instanceof HTMLInputElement and element.type in [
113 'text', 'search', 'tel', 'url', 'email', 'password', 'number'
115 element instanceof HTMLTextAreaElement or
116 # `<select>` elements can also receive text input: You may type the
117 # text of an item to select it.
118 element instanceof HTMLSelectElement or
119 element instanceof XULMenuListElement or
120 element instanceof XULTextBoxElement
122 isContentEditable = (element) ->
123 return element.isContentEditable or
124 isGoogleEditable(element)
126 isGoogleEditable = (element) ->
127 # `g_editable` is a non-standard attribute commonly used by Google.
128 return element.getAttribute?('g_editable') == 'true' or
129 element.ownerDocument.body?.getAttribute('g_editable') == 'true'
131 isActivatable = (element) ->
132 return element instanceof HTMLAnchorElement or
133 element instanceof HTMLButtonElement or
134 (element instanceof HTMLInputElement and element.type in [
135 'button', 'submit', 'reset', 'image'
137 element instanceof XULButtonElement
139 isAdjustable = (element) ->
140 return element instanceof HTMLInputElement and element.type in [
141 'checkbox', 'radio', 'file', 'color'
142 'date', 'time', 'datetime', 'datetime-local', 'month', 'week'
144 element instanceof XULControlElement or
145 # Youtube special case.
146 element.classList?.contains('html5-video-player') or
147 element.classList?.contains('ytp-button')
150 return element.clientWidth * element.clientHeight
153 Cc['@mozilla.org/browser/sessionstore;1'].getService(Ci.nsISessionStore)
156 sss = Cc['@mozilla.org/content/style-sheet-service;1']
157 .getService(Ci.nsIStyleSheetService)
158 uri = Services.io.newURI("chrome://vimfx/skin/#{ name }.css", null, null)
159 method = sss.AUTHOR_SHEET
160 unless sss.sheetRegistered(uri, method)
161 sss.loadAndRegisterSheet(uri, method)
164 sss.unregisterSheet(uri, method)
167 # Store events that we’ve simulated. A `WeakMap` is used in order not to leak
168 # memory. This approach is better than for example setting `event.simulated =
169 # true`, since that tells the sites that the click was simulated, and allows
171 simulated_events = new WeakMap()
173 # Simulate mouse click with a full chain of events. ('command' is for XUL
175 eventSequence = ['mouseover', 'mousedown', 'mouseup', 'click', 'command']
176 simulateClick = (element) ->
177 window = element.ownerDocument.defaultView
178 for type in eventSequence
179 mouseEvent = new window.MouseEvent(type, {
180 # Let the event bubble in order to trigger delegated event listeners.
182 # Make the event cancelable so that `<a href="#">` can be used as a
183 # JavaScript-powered button without scrolling to the top of the page.
186 element.dispatchEvent(mouseEvent)
188 isEventSimulated = (event) ->
189 return simulated_events.has(event)
191 # Write a string to the system clipboard.
192 writeToClipboard = (text) ->
193 clipboardHelper = Cc['@mozilla.org/widget/clipboardhelper;1']
194 .getService(Ci.nsIClipboardHelper)
195 clipboardHelper.copyString(text)
197 # Executes function `func` and measures how much time it took.
198 timeIt = (func, name) ->
201 console.timeEnd(name)
204 createBox = (document, className, parent = null, text = null) ->
205 box = document.createElement('box')
206 box.className = className
207 box.textContent = text if text?
208 parent.appendChild(box) if parent?
211 setAttributes = (element, attributes) ->
212 for attribute, value of attributes
213 element.setAttribute(attribute, value)
216 insertText = (input, value) ->
217 { selectionStart, selectionEnd } = input
219 input.value[0...selectionStart] + value + input.value[selectionEnd..]
220 input.selectionStart = input.selectionEnd = selectionStart + value.length
224 url = Cc['@mozilla.org/network/io-service;1']
225 .getService(Ci.nsIIOService)
226 .newURI(str, null, null)
227 .QueryInterface(Ci.nsIURL)
232 # Use Firefox services to search for a given string.
233 browserSearchSubmission = (str) ->
234 ss = Cc['@mozilla.org/browser/search-service;1']
235 .getService(Ci.nsIBrowserSearchService)
237 engine = ss.currentEngine or ss.defaultEngine
238 return engine.getSubmission(str, null)
240 openTab = (rootWindow, url, options) ->
241 { gBrowser } = rootWindow
242 rootWindow.TreeStyleTabService?.readyToOpenChildTab(gBrowser.selectedTab)
243 gBrowser.loadOneTab(url, options)
245 # Remove duplicate characters from string (case insensitive).
246 removeDuplicateCharacters = (str) ->
247 return removeDuplicates( str.toLowerCase().split('') ).join('')
249 # Escape a string to render it usable in regular expressions.
250 regexpEscape = (s) -> s and s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
252 removeDuplicates = (array) ->
253 # coffeelint: disable=no_backticks
254 return `[...new Set(array)]`
255 # coffeelint: enable=no_backticks
257 formatError = (error) ->
258 stack = String(error.stack?.formattedStack ? error.stack ? '')
260 .filter((line) -> line.contains('.xpi!'))
261 .map((line) -> ' ' + line.replace(/(?:\/<)*@.+\.xpi!/g, '@'))
263 return "#{ error }\n#{ stack }"
265 observe = (topic, observer) ->
266 observer = {observe: observer} if typeof observer == 'function'
267 Services.obs.addObserver(observer, topic, false)
269 Services.obs.removeObserver(observer, topic, false)
272 has = Function::call.bind(Object::hasOwnProperty)
275 constructor: ({start, step}) ->
278 tick: -> @value += @step
285 getEventCurrentTabWindow
309 browserSearchSubmission
313 removeDuplicateCharacters