]> git.gir.st - VimFx.git/blob - extension/lib/utils.coffee
Change license to MIT
[VimFx.git] / extension / lib / utils.coffee
1 # This file contains lots of different helper functions.
2
3 {OS} = Components.utils.import('resource://gre/modules/osfile.jsm', {})
4
5 nsIClipboardHelper = Cc['@mozilla.org/widget/clipboardhelper;1']
6 .getService(Ci.nsIClipboardHelper)
7 nsIDomUtils = Cc['@mozilla.org/inspector/dom-utils;1']
8 .getService(Ci.inIDOMUtils)
9 nsIEventListenerService = Cc['@mozilla.org/eventlistenerservice;1']
10 .getService(Ci.nsIEventListenerService)
11 nsIFocusManager = Cc['@mozilla.org/focus-manager;1']
12 .getService(Ci.nsIFocusManager)
13 nsIStyleSheetService = Cc['@mozilla.org/content/style-sheet-service;1']
14 .getService(Ci.nsIStyleSheetService)
15 nsIWindowMediator = Cc['@mozilla.org/appshell/window-mediator;1']
16 .getService(Ci.nsIWindowMediator)
17
18 # For XUL, `instanceof` checks are often better than `.localName` checks,
19 # because some of the below interfaces are extended by many elements.
20 XULDocument = Ci.nsIDOMXULDocument
21 XULButtonElement = Ci.nsIDOMXULButtonElement
22 XULControlElement = Ci.nsIDOMXULControlElement
23 XULMenuListElement = Ci.nsIDOMXULMenuListElement
24 XULTextBoxElement = Ci.nsIDOMXULTextBoxElement
25
26 # Full chains of events for different mouse actions. Note: 'click' is fired
27 # by Firefox automatically after 'mousedown' and 'mouseup'. Similarly,
28 # 'command' is fired automatically after 'click' on xul pages.
29 EVENTS_CLICK = ['mousedown', 'mouseup']
30 EVENTS_CLICK_XUL = ['click']
31 EVENTS_CONTEXT = ['contextmenu']
32 EVENTS_HOVER_START = ['mouseover', 'mouseenter', 'mousemove']
33 EVENTS_HOVER_END = ['mouseout', 'mouseleave']
34
35
36
37 # Element classification helpers
38
39 hasMarkableTextNode = (element) ->
40 return Array.some(element.childNodes, (node) ->
41 # Ignore whitespace-only text nodes, and single-letter ones (which are
42 # common in many syntax highlighters).
43 return node.nodeType == 3 and node.data.trim().length > 1
44 )
45
46 isActivatable = (element) ->
47 return element.localName in ['a', 'button'] or
48 (element.localName == 'input' and element.type in [
49 'button', 'submit', 'reset', 'image'
50 ]) or
51 element instanceof XULButtonElement
52
53 isAdjustable = (element) ->
54 return element.localName == 'input' and element.type in [
55 'checkbox', 'radio', 'file', 'color'
56 'date', 'time', 'datetime', 'datetime-local', 'month', 'week'
57 ] or
58 element.localName in ['video', 'audio', 'embed', 'object'] or
59 element instanceof XULControlElement or
60 # Custom video players.
61 includes(element.className, 'video') or
62 includes(element.className, 'player') or
63 # Youtube special case.
64 element.classList?.contains('ytp-button') or
65 # Allow navigating object inspection trees in th devtools with the
66 # arrow keys, even if the arrow keys are used as VimFx shortcuts.
67 isDevtoolsElement(element)
68
69 isContentEditable = (element) ->
70 return element.isContentEditable or
71 isIframeEditor(element) or
72 # Google.
73 element.getAttribute?('g_editable') == 'true' or
74 element.ownerDocument?.body?.getAttribute?('g_editable') == 'true' or
75 # Codeacademy terminals.
76 element.classList?.contains('real-terminal')
77
78 isDevtoolsElement = (element) ->
79 return false unless element.ownerGlobal
80 return Array.some(element.ownerGlobal.top.frames, isDevtoolsWindow)
81
82 isDevtoolsWindow = (window) ->
83 return window.location?.href in [
84 'about:devtools-toolbox'
85 'chrome://devtools/content/framework/toolbox.xul'
86 ]
87
88 isFocusable = (element) ->
89 # Focusable elements have `.tabIndex > 1` (but not necessarily a
90 # `tabindex="…"` attribute) …
91 return (element.tabIndex > -1 or
92 # … or an explicit `tabindex="-1"` attribute (which means that it is
93 # focusable, but not reachable with `<tab>`).
94 element.getAttribute?('tabindex') == '-1') and
95 not (element.localName?.endsWith?('box') and
96 element.localName != 'checkbox') and
97 not (element.localName == 'toolbarbutton' and
98 element.parentNode?.localName == 'toolbarbutton') and
99 element.localName not in ['tabs', 'menuitem', 'menuseparator']
100
101 isIframeEditor = (element) ->
102 return false unless element.localName == 'body'
103 return \
104 # Etherpad.
105 element.id == 'innerdocbody' or
106 # XpressEditor.
107 (element.classList?.contains('xe_content') and
108 element.classList?.contains('editable')) or
109 # vBulletin.
110 element.classList?.contains('wysiwyg') or
111 # TYPO3 CMS.
112 element.classList?.contains('htmlarea-content-body') or
113 # The wasavi extension.
114 element.hasAttribute?('data-wasavi-state')
115
116 isIgnoreModeFocusType = (element) ->
117 return \
118 # The wasavi extension.
119 element.hasAttribute?('data-wasavi-state') or
120 element.closest?('#wasavi_container') or
121 # CodeMirror in Vim mode.
122 (element.localName == 'textarea' and
123 element.closest?('.CodeMirror') and _hasVimEventListener(element))
124
125 # CodeMirror’s Vim mode is really sucky to detect. The only way seems to be to
126 # check if the there are any event listener functions with Vim-y words in them.
127 _hasVimEventListener = (element) ->
128 for listener in nsIEventListenerService.getListenerInfoFor(element)
129 if listener.listenerObject and
130 /\bvim\b|insertmode/i.test(String(listener.listenerObject))
131 return true
132 return false
133
134 isProperLink = (element) ->
135 # `.getAttribute` is used below instead of `.hasAttribute` to exclude `<a
136 # href="">`s used as buttons on some sites.
137 return element.getAttribute?('href') and
138 (element.localName == 'a' or
139 element.ownerDocument instanceof XULDocument) and
140 not element.href?.endsWith?('#') and
141 not element.href?.endsWith?('#?') and
142 not element.href?.startsWith?('javascript:')
143
144 isTextInputElement = (element) ->
145 return (element.localName == 'input' and element.type in [
146 'text', 'search', 'tel', 'url', 'email', 'password', 'number'
147 ]) or
148 element.localName == 'textarea' or
149 element instanceof XULTextBoxElement or
150 isContentEditable(element)
151
152 isTypingElement = (element) ->
153 return isTextInputElement(element) or
154 # `<select>` elements can also receive text input: You may type the
155 # text of an item to select it.
156 element.localName == 'select' or
157 element instanceof XULMenuListElement
158
159
160
161 # Active/focused element helpers
162
163 blurActiveBrowserElement = (vim) ->
164 # - Blurring in the next tick allows to pass `<escape>` to the location bar to
165 # reset it, for example.
166 # - Focusing the current browser afterwards allows to pass `<escape>` as well
167 # as unbound keys to the page. However, focusing the browser also triggers
168 # focus events on `document` and `window` in the current page. Many pages
169 # re-focus some text input on those events, making it impossible to blur
170 # those! Therefore we tell the frame script to suppress those events.
171 {window} = vim
172 activeElement = getActiveElement(window)
173 activeElement.closest('tabmodalprompt')?.abortPrompt()
174 vim._send('browserRefocus')
175 nextTick(window, ->
176 activeElement.blur()
177 window.gBrowser.selectedBrowser.focus()
178 )
179
180 blurActiveElement = (window) ->
181 # Blurring a frame element also blurs any active elements inside it. Recursing
182 # into the frames and blurring the “real” active element directly would give
183 # focus to the `<body>` of its containing frame, while blurring the top-most
184 # frame gives focus to the top-most `<body>`. This allows to blur fancy text
185 # editors which use an `<iframe>` as their text area.
186 window.document.activeElement?.blur()
187
188 # Focus an element and tell Firefox that the focus happened because of a user
189 # action (not just because some random programmatic focus). `.FLAG_BYKEY` might
190 # look more appropriate, but it unconditionally selects all text, which
191 # `.FLAG_BYMOUSE` does not.
192 focusElement = (element, options = {}) ->
193 nsIFocusManager.setFocus(element, options.flag ? 'FLAG_BYMOUSE')
194 element.select?() if options.select
195
196 # NOTE: In frame scripts, `document.activeElement` may be `null` when the page
197 # is loading. Therefore always check if anything was returned, such as:
198 #
199 # return unless activeElement = utils.getActiveElement(window)
200 getActiveElement = (window) ->
201 {activeElement} = window.document
202 return null unless activeElement
203 # If the active element is a frame, recurse into it. The easiest way to detect
204 # a frame that works both in browser UI and in web page content is to check
205 # for the presence of `.contentWindow`. However, in non-multi-process,
206 # `<browser>` (sometimes `<xul:browser>`) elements have a `.contentWindow`
207 # pointing to the web page content `window`, which we don’t want to recurse
208 # into. The problem is that there are _some_ `<browser>`s which we _want_ to
209 # recurse into, such as the sidebar (for instance the history sidebar), and
210 # dialogs in `about:preferences`. Checking the `contextmenu` attribute seems
211 # to be a reliable test, catching both the main tab `<browser>`s and bookmarks
212 # opened in the sidebar.
213 if (activeElement.localName == 'browser' and
214 activeElement.getAttribute?('contextmenu') == 'contentAreaContextMenu') or
215 not activeElement.contentWindow
216 return activeElement
217 else
218 return getActiveElement(activeElement.contentWindow)
219
220 getFocusType = (element) -> switch
221 when isIgnoreModeFocusType(element)
222 'ignore'
223 when isTypingElement(element)
224 if element.closest?('findbar') then 'findbar' else 'editable'
225 when isActivatable(element)
226 'activatable'
227 when isAdjustable(element)
228 'adjustable'
229 else
230 'none'
231
232
233
234 # Event helpers
235
236 listen = (element, eventName, listener, useCapture = true) ->
237 element.addEventListener(eventName, listener, useCapture)
238 module.onShutdown(->
239 element.removeEventListener(eventName, listener, useCapture)
240 )
241
242 listenOnce = (element, eventName, listener, useCapture = true) ->
243 fn = (event) ->
244 listener(event)
245 element.removeEventListener(eventName, fn, useCapture)
246 listen(element, eventName, fn, useCapture)
247
248 onRemoved = (element, fn) ->
249 window = element.ownerGlobal
250
251 disconnected = false
252 disconnect = ->
253 return if disconnected
254 disconnected = true
255 mutationObserver.disconnect() unless Cu.isDeadWrapper(mutationObserver)
256
257 mutationObserver = new window.MutationObserver((changes) ->
258 for change in changes then for removedElement in change.removedNodes
259 if removedElement.contains?(element)
260 disconnect()
261 fn()
262 return
263 )
264 mutationObserver.observe(window.document.documentElement, {
265 childList: true
266 subtree: true
267 })
268 module.onShutdown(disconnect)
269
270 return disconnect
271
272 simulateMouseEvents = (element, sequence, browserOffset) ->
273 window = element.ownerGlobal
274 rect = element.getBoundingClientRect()
275 topOffset = getTopOffset(element)
276
277 eventSequence = switch sequence
278 when 'click'
279 EVENTS_CLICK
280 when 'click-xul'
281 EVENTS_CLICK_XUL
282 when 'context'
283 EVENTS_CONTEXT
284 when 'hover-start'
285 EVENTS_HOVER_START
286 when 'hover-end'
287 EVENTS_HOVER_END
288 else
289 sequence
290
291 for type in eventSequence
292 buttonNum = switch
293 when type in EVENTS_CONTEXT
294 2
295 when type in EVENTS_CLICK
296 1
297 else
298 0
299
300 mouseEvent = new window.MouseEvent(type, {
301 # Let the event bubble in order to trigger delegated event listeners.
302 bubbles: type not in ['mouseenter', 'mouseleave']
303 # Make the event cancelable so that `<a href="#">` can be used as a
304 # JavaScript-powered button without scrolling to the top of the page.
305 cancelable: type not in ['mouseenter', 'mouseleave']
306 # These properties are just here for mimicing a real click as much as
307 # possible.
308 buttons: buttonNum
309 detail: buttonNum
310 view: window
311 # `page{X,Y}` are set automatically to the correct values when setting
312 # `client{X,Y}`. `{offset,layer,movement}{X,Y}` are not worth the trouble
313 # to set.
314 clientX: rect.left
315 clientY: rect.top + rect.height / 2
316 screenX: browserOffset.x + topOffset.x
317 screenY: browserOffset.y + topOffset.y + rect.height / 2
318 })
319
320 if type == 'mousemove'
321 # If the below technique is used for this event, the “URL popup” (shown
322 # when hovering or focusing links) does not appear.
323 element.dispatchEvent(mouseEvent)
324 else
325 # The last `true` below marks the event as trusted, which some APIs
326 # require, such as `requestFullscreen()`.
327 # (`element.dispatchEvent(mouseEvent)` is not able to do this.)
328 window
329 .QueryInterface(Ci.nsIInterfaceRequestor)
330 .getInterface(Ci.nsIDOMWindowUtils)
331 .dispatchDOMEventViaPresShell(element, mouseEvent, true)
332
333 return
334
335 suppressEvent = (event) ->
336 event.preventDefault()
337 event.stopPropagation()
338
339
340
341 # DOM helpers
342
343 area = (element) ->
344 return element.clientWidth * element.clientHeight
345
346 checkElementOrAncestor = (element, fn) ->
347 window = element.ownerGlobal
348 while element.parentElement
349 return true if fn(element)
350 element = element.parentElement
351 return false
352
353 clearSelectionDeep = (window, {blur = true} = {}) ->
354 # The selection might be `null` in hidden frames.
355 selection = window.getSelection()
356 selection?.removeAllRanges()
357 for frame in window.frames
358 clearSelectionDeep(frame, {blur})
359 # Allow parents to re-gain control of text selection.
360 frame.frameElement.blur() if blur
361 return
362
363 containsDeep = (parent, element) ->
364 parentWindow = parent.ownerGlobal
365 elementWindow = element.ownerGlobal
366
367 # Owner windows might be missing when opening the devtools.
368 while elementWindow and parentWindow and
369 elementWindow != parentWindow and elementWindow.top != elementWindow
370 element = elementWindow.frameElement
371 elementWindow = element.ownerGlobal
372
373 return parent.contains(element)
374
375 createBox = (document, className = '', parent = null, text = null) ->
376 box = document.createElement('box')
377 box.className = "#{className} vimfx-box"
378 box.textContent = text if text?
379 parent.appendChild(box) if parent?
380 return box
381
382 # In quirks mode (when the page lacks a doctype), such as on Hackernews,
383 # `<body>` is considered the root element rather than `<html>`.
384 getRootElement = (document) ->
385 if document.compatMode == 'BackCompat' and document.body?
386 return document.body
387 else
388 return document.documentElement
389
390 getText = (element) ->
391 text = element.textContent or element.value or element.placeholder or ''
392 return text.trim().replace(/\s+/, ' ')
393
394 getTopOffset = (element) ->
395 window = element.ownerGlobal
396
397 {left: x, top: y} = element.getBoundingClientRect()
398 while window.frameElement
399 frame = window.frameElement
400 frameRect = frame.getBoundingClientRect()
401 x += frameRect.left
402 y += frameRect.top
403
404 computedStyle = frame.ownerGlobal.getComputedStyle(frame)
405 if computedStyle
406 x +=
407 parseFloat(computedStyle.getPropertyValue('border-left-width')) +
408 parseFloat(computedStyle.getPropertyValue('padding-left'))
409 y +=
410 parseFloat(computedStyle.getPropertyValue('border-top-width')) +
411 parseFloat(computedStyle.getPropertyValue('padding-top'))
412
413 window = window.parent
414 return {x, y}
415
416 injectTemporaryPopup = (document, contents) ->
417 popup = document.createElement('menupopup')
418 popup.appendChild(contents)
419 document.getElementById('mainPopupSet').appendChild(popup)
420 listenOnce(popup, 'popuphidden', popup.remove.bind(popup))
421 return popup
422
423 insertText = (input, value) ->
424 {selectionStart, selectionEnd} = input
425 input.value =
426 input.value[0...selectionStart] + value + input.value[selectionEnd..]
427 input.selectionStart = input.selectionEnd = selectionStart + value.length
428
429 isDetached = (element) ->
430 return not element.ownerDocument?.documentElement?.contains?(element)
431
432 isNonEmptyTextNode = (node) ->
433 return node.nodeType == 3 and node.data.trim() != ''
434
435 querySelectorAllDeep = (window, selector) ->
436 elements = Array.from(window.document.querySelectorAll(selector))
437 for frame in window.frames
438 elements.push(querySelectorAllDeep(frame, selector)...)
439 return elements
440
441 selectAllSubstringMatches = (element, substring, {caseSensitive = true} = {}) ->
442 window = element.ownerGlobal
443 selection = window.getSelection()
444 {textContent} = element
445
446 format = (string) -> if caseSensitive then string else string.toLowerCase()
447 offsets =
448 getAllNonOverlappingRangeOffsets(format(textContent), format(substring))
449 offsetsLength = offsets.length
450 return if offsetsLength == 0
451
452 textIndex = 0
453 offsetsIndex = 0
454 [currentOffset] = offsets
455 searchIndex = currentOffset.start
456 start = null
457
458 walkTextNodes(element, (textNode) ->
459 {length} = textNode.data
460 return false if length == 0
461
462 while textIndex + length > searchIndex
463 if start
464 range = window.document.createRange()
465 range.setStart(start.textNode, start.offset)
466 range.setEnd(textNode, currentOffset.end - textIndex)
467 selection.addRange(range)
468
469 offsetsIndex += 1
470 return true if offsetsIndex >= offsetsLength
471 currentOffset = offsets[offsetsIndex]
472
473 start = null
474 searchIndex = currentOffset.start
475
476 else
477 start = {textNode, offset: currentOffset.start - textIndex}
478 searchIndex = currentOffset.end - 1
479
480 textIndex += length
481 return false
482 )
483
484 selectElement = (element) ->
485 window = element.ownerGlobal
486 selection = window.getSelection()
487 range = window.document.createRange()
488 range.selectNodeContents(element)
489 selection.addRange(range)
490
491 setAttributes = (element, attributes) ->
492 for attribute, value of attributes
493 element.setAttribute(attribute, value)
494 return
495
496 setHover = (element, hover) ->
497 method = if hover then 'addPseudoClassLock' else 'removePseudoClassLock'
498 while element.parentElement
499 nsIDomUtils[method](element, ':hover')
500 element = element.parentElement
501 return
502
503 walkTextNodes = (element, fn) ->
504 for node in element.childNodes then switch node.nodeType
505 when 3 # TextNode.
506 stop = fn(node)
507 return true if stop
508 when 1 # Element.
509 stop = walkTextNodes(node, fn)
510 return true if stop
511 return false
512
513
514
515 # Language helpers
516
517 class Counter
518 constructor: ({start: @value = 0, @step = 1}) ->
519 tick: -> @value += @step
520
521 class EventEmitter
522 constructor: ->
523 @listeners = {}
524
525 on: (event, listener) ->
526 (@listeners[event] ?= new Set()).add(listener)
527
528 off: (event, listener) ->
529 @listeners[event]?.delete(listener)
530
531 emit: (event, data) ->
532 @listeners[event]?.forEach((listener) ->
533 listener(data)
534 )
535
536 # Returns `[nonMatch, adjacentMatchAfter]`, where `adjacentMatchAfter - nonMatch
537 # == 1`. `fn(n)` is supposed to return `false` for `n <= nonMatch` and `true`
538 # for `n >= adjacentMatchAfter`. Both `nonMatch` and `adjacentMatchAfter` may be
539 # `null` if they cannot be found. Otherwise they’re in the range `min <= n <=
540 # max`. `[null, null]` is returned in non-sensical cases. This function is
541 # intended to be used as a faster alternative to something like this:
542 #
543 # adjacentMatchAfter = null
544 # for n in [min..max]
545 # if fn(n)
546 # adjacentMatchAfter = n
547 # break
548 bisect = (min, max, fn) ->
549 return [null, null] unless max - min >= 0 and min % 1 == 0 and max % 1 == 0
550
551 while max - min > 1
552 mid = min + (max - min) // 2
553 match = fn(mid)
554 if match
555 max = mid
556 else
557 min = mid
558
559 matchMin = fn(min)
560 matchMax = fn(max)
561
562 return switch
563 when matchMin and matchMax
564 [null, min]
565 when not matchMin and not matchMax
566 [max, null]
567 when not matchMin and matchMax
568 [min, max]
569 else
570 [null, null]
571
572 getAllNonOverlappingRangeOffsets = (string, substring) ->
573 {length} = substring
574 return [] if length == 0
575
576 offsets = []
577 lastOffset = {start: -Infinity, end: -Infinity}
578 index = -1
579
580 loop
581 index = string.indexOf(substring, index + 1)
582 break if index == -1
583 if index > lastOffset.end
584 lastOffset = {start: index, end: index + length}
585 offsets.push(lastOffset)
586 else
587 lastOffset.end = index + length
588
589 return offsets
590
591 has = (obj, prop) -> Object::hasOwnProperty.call(obj, prop)
592
593 # Check if `search` exists in `string` (case insensitively). Returns `false` if
594 # `string` doesn’t exist or isn’t a string, such as `<SVG element>.className`.
595 includes = (string, search) ->
596 return false unless typeof string == 'string'
597 return string.toLowerCase().includes(search)
598
599 # Calls `fn` repeatedly, with at least `interval` ms between each call.
600 interval = (window, interval, fn) ->
601 stopped = false
602 currentIntervalId = null
603 next = ->
604 return if stopped
605 currentIntervalId = window.setTimeout((-> fn(next)), interval)
606 clearInterval = ->
607 stopped = true
608 window.clearTimeout(currentIntervalId)
609 next()
610 return clearInterval
611
612 nextTick = (window, fn) -> window.setTimeout((-> fn()) , 0)
613
614 overlaps = (rectA, rectB) ->
615 return \
616 Math.round(rectA.right) >= Math.round(rectB.left) and
617 Math.round(rectA.left) <= Math.round(rectB.right) and
618 Math.round(rectA.bottom) >= Math.round(rectB.top) and
619 Math.round(rectA.top) <= Math.round(rectB.bottom)
620
621 partition = (array, fn) ->
622 matching = []
623 nonMatching = []
624 for item, index in array
625 if fn(item, index, array)
626 matching.push(item)
627 else
628 nonMatching.push(item)
629 return [matching, nonMatching]
630
631 regexEscape = (s) -> s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
632
633 removeDuplicateChars = (string) -> removeDuplicates(string.split('')).join('')
634
635 removeDuplicates = (array) -> Array.from(new Set(array))
636
637 sum = (numbers) -> numbers.reduce(((sum, number) -> sum + number), 0)
638
639
640
641 # Misc helpers
642
643 expandPath = (path) ->
644 if path.startsWith('~/') or path.startsWith('~\\')
645 return OS.Constants.Path.homeDir + path[1..]
646 else
647 return path
648
649 getCurrentLocation = ->
650 return unless window = getCurrentWindow()
651 return new window.URL(window.gBrowser.selectedBrowser.currentURI.spec)
652
653 # This function might return `null` on startup.
654 getCurrentWindow = -> nsIWindowMediator.getMostRecentWindow('navigator:browser')
655
656 hasEventListeners = (element, type) ->
657 for listener in nsIEventListenerService.getListenerInfoFor(element)
658 if listener.listenerObject and listener.type == type
659 return true
660 return false
661
662 loadCss = (uriString) ->
663 uri = Services.io.newURI(uriString, null, null)
664 method = nsIStyleSheetService.AUTHOR_SHEET
665 unless nsIStyleSheetService.sheetRegistered(uri, method)
666 nsIStyleSheetService.loadAndRegisterSheet(uri, method)
667 module.onShutdown(->
668 nsIStyleSheetService.unregisterSheet(uri, method)
669 )
670
671 observe = (topic, observer) ->
672 observer = {observe: observer} if typeof observer == 'function'
673 Services.obs.addObserver(observer, topic, false)
674 module.onShutdown(->
675 Services.obs.removeObserver(observer, topic, false)
676 )
677
678 # Try to open a button’s dropdown menu, if any.
679 openDropdown = (element) ->
680 if element.ownerDocument instanceof XULDocument and
681 element.getAttribute?('type') == 'menu' and
682 element.open == false # Only change `.open` if it is already a boolean.
683 element.open = true
684
685 openPopup = (popup) ->
686 window = popup.ownerGlobal
687 # Show the popup so it gets a height and width.
688 popup.openPopupAtScreen(0, 0)
689 # Center the popup inside the window.
690 popup.moveTo(
691 window.screenX + window.outerWidth / 2 - popup.clientWidth / 2,
692 window.screenY + window.outerHeight / 2 - popup.clientHeight / 2
693 )
694
695 writeToClipboard = (text) -> nsIClipboardHelper.copyString(text)
696
697
698
699 module.exports = {
700 hasMarkableTextNode
701 isActivatable
702 isAdjustable
703 isContentEditable
704 isDevtoolsElement
705 isDevtoolsWindow
706 isFocusable
707 isIframeEditor
708 isIgnoreModeFocusType
709 isProperLink
710 isTextInputElement
711 isTypingElement
712
713 blurActiveBrowserElement
714 blurActiveElement
715 focusElement
716 getActiveElement
717 getFocusType
718
719 listen
720 listenOnce
721 onRemoved
722 simulateMouseEvents
723 suppressEvent
724
725 area
726 checkElementOrAncestor
727 clearSelectionDeep
728 containsDeep
729 createBox
730 getRootElement
731 getText
732 getTopOffset
733 injectTemporaryPopup
734 insertText
735 isDetached
736 isNonEmptyTextNode
737 querySelectorAllDeep
738 selectAllSubstringMatches
739 selectElement
740 setAttributes
741 setHover
742 walkTextNodes
743
744 Counter
745 EventEmitter
746 bisect
747 getAllNonOverlappingRangeOffsets
748 has
749 includes
750 interval
751 nextTick
752 overlaps
753 partition
754 regexEscape
755 removeDuplicateChars
756 removeDuplicates
757 sum
758
759 expandPath
760 getCurrentLocation
761 getCurrentWindow
762 hasEventListeners
763 loadCss
764 observe
765 openDropdown
766 openPopup
767 writeToClipboard
768 }
Imprint / Impressum