1 # This file contains lots of different helper functions.
3 {E10SUtils} = Cu.import('resource://gre/modules/E10SUtils.jsm', {})
4 {OS} = Components.utils.import('resource://gre/modules/osfile.jsm', {})
5 {PlacesUIUtils} = Cu.import('resource:///modules/PlacesUIUtils.jsm', {})
6 {PrivateBrowsingUtils} =
7 Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm', {})
9 nsIClipboardHelper = Cc['@mozilla.org/widget/clipboardhelper;1']
10 .getService(Ci.nsIClipboardHelper)
11 nsIEventListenerService = Cc['@mozilla.org/eventlistenerservice;1']
12 .getService(Ci.nsIEventListenerService)
13 nsIFocusManager = Cc['@mozilla.org/focus-manager;1']
14 .getService(Ci.nsIFocusManager)
15 nsIStyleSheetService = Cc['@mozilla.org/content/style-sheet-service;1']
16 .getService(Ci.nsIStyleSheetService)
17 nsIWindowMediator = Cc['@mozilla.org/appshell/window-mediator;1']
18 .getService(Ci.nsIWindowMediator)
20 # For XUL, `instanceof` checks are often better than `.localName` checks,
21 # because some of the below interfaces are extended by many elements.
22 XULButtonElement = Ci.nsIDOMXULButtonElement
23 XULControlElement = Ci.nsIDOMXULControlElement
24 XULMenuListElement = Ci.nsIDOMXULMenuListElement
26 # Traverse the DOM upwards until we hit its containing document (most likely an
27 # HTMLDocument or (<=fx68) XULDocument) or the ShadowRoot.
28 getDocument = (e) -> if e.parentNode? then arguments.callee(e.parentNode) else e
30 isInShadowRoot = (element) ->
31 ShadowRoot? and getDocument(element) instanceof ShadowRoot
33 isXULElement = (element) ->
34 XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'
35 element.namespaceURI == XUL_NS
37 # Full chains of events for different mouse actions. Note: 'click' is fired
38 # by Firefox automatically after 'mousedown' and 'mouseup'. Similarly,
39 # 'command' is fired automatically after 'click' on xul pages.
40 EVENTS_CLICK = ['mousedown', 'mouseup']
41 EVENTS_CLICK_XUL = ['click']
42 EVENTS_CONTEXT = ['contextmenu']
43 EVENTS_HOVER_START = ['mouseover', 'mouseenter', 'mousemove']
44 EVENTS_HOVER_END = ['mouseout', 'mouseleave']
48 # Element classification helpers
50 hasMarkableTextNode = (element) ->
51 return Array.prototype.some.call(element.childNodes, (node) ->
52 # Ignore whitespace-only text nodes, and single-letter ones (which are
53 # common in many syntax highlighters).
54 return node.nodeType == 3 and node.data.trim().length > 1
57 isActivatable = (element) ->
58 return element.localName in ['a', 'button'] or
59 (element.localName == 'input' and element.type in [
60 'button', 'submit', 'reset', 'image'
62 element instanceof XULButtonElement
64 isAdjustable = (element) ->
65 return element.localName == 'input' and element.type in [
66 'checkbox', 'radio', 'file', 'color'
67 'date', 'time', 'datetime', 'datetime-local', 'month', 'week'
69 element.localName in ['video', 'audio', 'embed', 'object'] or
70 element instanceof XULControlElement or
71 # Custom video players.
72 includes(element.className, 'video') or
73 includes(element.className, 'player') or
74 # Youtube special case.
75 element.classList?.contains('ytp-button') or
76 # Allow navigating object inspection trees in th devtools with the
77 # arrow keys, even if the arrow keys are used as VimFx shortcuts.
78 isDevtoolsElement(element)
80 isContentEditable = (element) ->
81 return element.isContentEditable or
82 isIframeEditor(element) or
84 element.getAttribute?('g_editable') == 'true' or
85 element.ownerDocument?.body?.getAttribute?('g_editable') == 'true' or
86 # Codeacademy terminals.
87 element.classList?.contains('real-terminal')
89 isDevtoolsElement = (element) ->
90 return false unless element.ownerGlobal
91 return Array.prototype.some.call(
92 element.ownerGlobal.top.frames, isDevtoolsWindow
95 isDevtoolsWindow = (window) ->
96 # Note: this function is called for each frame by isDevtoolsElement. When
97 # called on an out-of-process iframe, accessing .href will fail with
98 # SecurityError; the `try` around it makes it `undefined` in such a case.
99 return (try window.location?.href) in [
100 'about:devtools-toolbox'
101 'chrome://devtools/content/framework/toolbox.xul'
102 'chrome://devtools/content/framework/toolbox.xhtml' # fx72+
105 # Note: this is possibly a bit overzealous, but Works For Now™.
106 isDockedDevtoolsElement = (element) ->
107 return element.ownerDocument.URL.startsWith('chrome://devtools/content/')
109 isFocusable = (element) ->
110 # Focusable elements have `.tabIndex > 1` (but not necessarily a
111 # `tabindex="…"` attribute) …
112 return (element.tabIndex > -1 or
113 # … or an explicit `tabindex="-1"` attribute (which means that it is
114 # focusable, but not reachable with `<tab>`).
115 element.getAttribute?('tabindex') == '-1') and
116 not (element.localName?.endsWith?('box') and
117 element.localName != 'checkbox') and
118 not (element.localName == 'toolbarbutton' and
119 element.parentNode?.localName == 'toolbarbutton') and
120 element.localName not in ['tabs', 'menuitem', 'menuseparator']
122 isIframeEditor = (element) ->
123 return false unless element.localName == 'body'
126 element.id == 'innerdocbody' or
128 (element.classList?.contains('xe_content') and
129 element.classList?.contains('editable')) or
131 element.classList?.contains('wysiwyg') or
133 element.classList?.contains('htmlarea-content-body') or
134 # The wasavi extension.
135 element.hasAttribute?('data-wasavi-state')
137 isIgnoreModeFocusType = (element) ->
139 # The wasavi extension.
140 element.hasAttribute?('data-wasavi-state') or
141 element.closest?('#wasavi_container') or
142 # CodeMirror in Vim mode.
143 (element.localName == 'textarea' and
144 element.closest?('.CodeMirror') and _hasVimEventListener(element))
146 # CodeMirror’s Vim mode is really sucky to detect. The only way seems to be to
147 # check if the there are any event listener functions with Vim-y words in them.
148 _hasVimEventListener = (element) ->
149 for listener in nsIEventListenerService.getListenerInfoFor(element)
150 if listener.listenerObject and
151 /\bvim\b|insertmode/i.test(String(listener.listenerObject))
155 isProperLink = (element) ->
156 # `.getAttribute` is used below instead of `.hasAttribute` to exclude `<a
157 # href="">`s used as buttons on some sites.
158 return element.getAttribute?('href') and
159 (element.localName == 'a' or
160 (isXULElement(element) and
161 element.localName == 'label' and
162 element.getAttribute('is') == 'text-link')) and
163 not element.href?.endsWith?('#') and
164 not element.href?.endsWith?('#?') and
165 not element.href?.startsWith?('javascript:')
167 isTextInputElement = (element) ->
168 return (element.localName == 'input' and element.type in [
169 'text', 'search', 'tel', 'url', 'email', 'password', 'number'
171 element.localName in [ 'textarea', 'textbox' ] or
172 isContentEditable(element)
174 isTypingElement = (element) ->
175 return isTextInputElement(element) or
176 # `<select>` elements can also receive text input: You may type the
177 # text of an item to select it.
178 element.localName == 'select' or
179 element instanceof XULMenuListElement
183 # Active/focused element helpers
185 blurActiveBrowserElement = (vim) ->
186 # - Blurring in the next tick allows to pass `<escape>` to the location bar to
187 # reset it, for example.
188 # - Focusing the current browser afterwards allows to pass `<escape>` as well
189 # as unbound keys to the page. However, focusing the browser also triggers
190 # focus events on `document` and `window` in the current page. Many pages
191 # re-focus some text input on those events, making it impossible to blur
192 # those! Therefore we tell the frame script to suppress those events.
194 activeElement = getActiveElement(window)
195 activeElement.closest('tabmodalprompt')?.abortPrompt()
196 vim._send('browserRefocus')
199 window.gBrowser.selectedBrowser.focus()
202 blurActiveElement = (window) ->
203 # Blurring a frame element also blurs any active elements inside it. Recursing
204 # into the frames and blurring the “real” active element directly would give
205 # focus to the `<body>` of its containing frame, while blurring the top-most
206 # frame gives focus to the top-most `<body>`. This allows to blur fancy text
207 # editors which use an `<iframe>` as their text area.
208 # Note that this trick does not work with Web Components; for them, recursing
210 if window.document.activeElement?.shadowRoot?
211 return getActiveElement(window)?.blur()
212 window.document.activeElement?.blur()
214 # Focus an element and tell Firefox that the focus happened because of a user
215 # action (not just because some random programmatic focus). `.FLAG_BYKEY` might
216 # look more appropriate, but it unconditionally selects all text, which
217 # `.FLAG_BYMOUSE` does not.
218 focusElement = (element, options = {}) ->
219 nsIFocusManager.setFocus(element, options.flag ? 'FLAG_BYMOUSE')
220 element.select?() if options.select
222 # NOTE: In frame scripts, `document.activeElement` may be `null` when the page
223 # is loading. Therefore always check if anything was returned, such as:
225 # return unless activeElement = utils.getActiveElement(window)
226 getActiveElement = (window) ->
227 {activeElement} = window.shadowRoot or window.document
228 return null unless activeElement
229 # If the active element is a frame, recurse into it. The easiest way to detect
230 # a frame that works both in browser UI and in web page content is to check
231 # for the presence of `.contentWindow`. However, in non-multi-process,
232 # `<browser>` (sometimes `<xul:browser>`) elements have a `.contentWindow`
233 # pointing to the web page content `window`, which we don’t want to recurse
234 # into. The problem is that there are _some_ `<browser>`s which we _want_ to
235 # recurse into, such as the sidebar (for instance the history sidebar), and
236 # dialogs in `about:preferences`. Checking the `contextmenu` attribute seems
237 # to be a reliable test, catching both the main tab `<browser>`s and bookmarks
238 # opened in the sidebar.
239 # We also want to recurse into the (open) shadow DOM of custom elements.
240 if activeElement.shadowRoot?
241 return getActiveElement(activeElement)
242 else if activeElement.contentWindow and
243 not (activeElement.localName == 'browser' and
244 activeElement.getAttribute?('contextmenu') == 'contentAreaContextMenu')
245 # with Fission enabled, the iframe might be located in a different process
246 # (oop). Then, recursing into it isn't possible (throws SecurityError).
247 return activeElement unless (try activeElement.contentWindow.document)
249 return getActiveElement(activeElement.contentWindow)
253 getFocusType = (element) -> switch
254 when element.tagName in ['FRAME', 'IFRAME'] and
255 not (try element.contentWindow.document)
256 # Encountered an out-of-process iframe, which we can't inspect. We fall
257 # back to insert mode, so any text inputs it may contain are still usable.
259 when isIgnoreModeFocusType(element)
261 when isTypingElement(element)
262 if element.closest?('findbar') then 'findbar' else 'editable'
263 when isActivatable(element)
265 when isAdjustable(element)
274 listen = (element, eventName, listener, useCapture = true) ->
275 element.addEventListener(eventName, listener, useCapture)
277 element.removeEventListener(eventName, listener, useCapture)
280 listenOnce = (element, eventName, listener, useCapture = true) ->
283 element.removeEventListener(eventName, fn, useCapture)
284 listen(element, eventName, fn, useCapture)
286 onRemoved = (element, fn) ->
287 window = element.ownerGlobal
291 return if disconnected
293 mutationObserver.disconnect() unless Cu.isDeadWrapper(mutationObserver)
295 mutationObserver = new window.MutationObserver((changes) ->
296 for change in changes then for removedElement in change.removedNodes
297 if removedElement.contains?(element)
302 mutationObserver.observe(window.document.documentElement, {
306 module.onShutdown(disconnect)
310 contentAreaClick = (data, browser) ->
311 # This function is adapted from the same-named one currently in
312 # mozilla-central/browser/actors/ClickHandlerParent.jsm. Keep in sync!
313 # Note: Our version is shortened substantially and unlike Mozilla, we pass in
314 # the browser object instead of extracting it from the browsingContext.
315 window = browser.ownerGlobal
318 charset: browser.characterSet,
319 referrerInfo: E10SUtils.deserializeReferrerInfo(data.referrerInfo),
320 allowMixedContent: data.allowMixedContent, # <=fx88
321 isContentWindowPrivate: data.isContentWindowPrivate,
322 originPrincipal: data.originPrincipal,
323 originStoragePrincipal: data.originStoragePrincipal,
324 triggeringPrincipal: data.triggeringPrincipal,
325 csp: if data.csp then E10SUtils.deserializeCSP(data.csp) else null,
326 frameOuterWindowID: data.frameOuterWindowID, # <=fx79
327 frameID: data.frameID, # >=fx80
328 allowInheritPrincipal: true,
331 if data.originAttributes.userContextId
332 params.userContextId = data.originAttributes.userContextId
334 try if not PrivateBrowsingUtils.isWindowPrivate(window)
335 PlacesUIUtils.markPageAsFollowedLink(data.href)
337 window.openLinkIn(data.href, window.whereToOpenLink(data), params)
339 simulateMouseEvents = (element, sequence, browserOffset) ->
340 window = element.ownerGlobal
341 rect = element.getBoundingClientRect()
342 topOffset = getTopOffset(element)
344 eventSequence = switch sequence
358 for type in eventSequence
360 when type in EVENTS_CONTEXT
362 when type in EVENTS_CLICK
367 mouseEvent = new window.MouseEvent(type, {
368 # Let the event bubble in order to trigger delegated event listeners.
369 bubbles: type not in ['mouseenter', 'mouseleave']
370 # Make the event cancelable so that `<a href="#">` can be used as a
371 # JavaScript-powered button without scrolling to the top of the page.
372 cancelable: type not in ['mouseenter', 'mouseleave']
373 # These properties are just here for mimicing a real click as much as
378 # `page{X,Y}` are set automatically to the correct values when setting
379 # `client{X,Y}`. `{offset,layer,movement}{X,Y}` are not worth the trouble
382 clientY: rect.top + rect.height / 2
383 screenX: browserOffset.x + topOffset.x
384 screenY: browserOffset.y + topOffset.y + rect.height / 2
387 if type == 'mousemove'
388 # If the below technique is used for this event, the “URL popup” (shown
389 # when hovering or focusing links) does not appear.
390 element.dispatchEvent(mouseEvent)
391 else if isInShadowRoot(element)
392 # click events for links and other clickables inside the shadow DOM are
393 # caught by the callee (.click_marker_element()).
394 element.focus() if type == 'contextmenu' # for <input type=text>
395 element.dispatchEvent(mouseEvent)
398 (window.windowUtils.dispatchDOMEventViaPresShellForTesting or
399 window.windowUtils.dispatchDOMEventViaPresShell # < fx73
400 )(element, mouseEvent)
402 if error.result != Cr.NS_ERROR_UNEXPECTED
407 suppressEvent = (event) ->
408 event.preventDefault()
409 event.stopPropagation()
416 return element.clientWidth * element.clientHeight
418 checkElementOrAncestor = (element, fn) ->
419 window = element.ownerGlobal
420 while element.parentElement
421 return true if fn(element)
422 element = element.parentElement
425 clearSelectionDeep = (window, {blur = true} = {}) ->
426 # The selection might be `null` in hidden frames.
427 selection = window.getSelection()
428 selection?.removeAllRanges()
429 # Note: accessing frameElement fails on oop iframes (fission); skip those.
430 for frame in window.frames when (try frame.frameElement)
431 clearSelectionDeep(frame, {blur})
432 # Allow parents to re-gain control of text selection.
433 frame.frameElement.blur() if blur
436 containsDeep = (parent, element) ->
437 parentWindow = parent.ownerGlobal
438 elementWindow = element.ownerGlobal
440 # Owner windows might be missing when opening the devtools.
441 while elementWindow and parentWindow and
442 elementWindow != parentWindow and elementWindow.top != elementWindow
443 element = elementWindow.frameElement
444 elementWindow = element.ownerGlobal
446 return parent.contains(element)
448 createBox = (document, className = '', parent = null, text = null) ->
449 box = document.createElement('box')
450 box.className = "#{className} vimfx-box"
451 box.textContent = text if text?
452 parent.appendChild(box) if parent?
455 # In quirks mode (when the page lacks a doctype), such as on Hackernews,
456 # `<body>` is considered the root element rather than `<html>`.
457 getRootElement = (document) ->
458 if document.compatMode == 'BackCompat' and document.body?
461 return document.documentElement
463 getText = (element) ->
464 text = element.textContent or element.value or element.placeholder or ''
465 return text.trim().replace(/\s+/, ' ')
467 getTopOffset = (element) ->
468 window = element.ownerGlobal
470 {left: x, top: y} = element.getBoundingClientRect()
471 while window.frameElement
472 frame = window.frameElement
473 frameRect = frame.getBoundingClientRect()
477 computedStyle = frame.ownerGlobal.getComputedStyle(frame)
480 parseFloat(computedStyle.getPropertyValue('border-left-width')) +
481 parseFloat(computedStyle.getPropertyValue('padding-left'))
483 parseFloat(computedStyle.getPropertyValue('border-top-width')) +
484 parseFloat(computedStyle.getPropertyValue('padding-top'))
486 window = window.parent
489 injectTemporaryPopup = (document, contents) ->
490 popup = document.createXULElement('menupopup')
491 popup.appendChild(contents)
492 document.getElementById('mainPopupSet').appendChild(popup)
493 listenOnce(popup, 'popuphidden', popup.remove.bind(popup))
496 insertText = (input, value) ->
497 {selectionStart, selectionEnd} = input
499 input.value[0...selectionStart] + value + input.value[selectionEnd..]
500 input.selectionStart = input.selectionEnd = selectionStart + value.length
502 isDetached = (element) ->
503 return not element.ownerDocument?.documentElement?.contains?(element)
505 isNonEmptyTextNode = (node) ->
506 return node.nodeType == 3 and node.data.trim() != ''
508 querySelectorAllDeep = (window, selector) ->
509 elements = Array.from(window.document.querySelectorAll(selector))
510 for frame in window.frames
511 elements.push(querySelectorAllDeep(frame, selector)...)
514 selectAllSubstringMatches = (element, substring, {caseSensitive = true} = {}) ->
515 window = element.ownerGlobal
516 selection = window.getSelection()
517 {textContent} = element
519 format = (string) -> if caseSensitive then string else string.toLowerCase()
521 getAllNonOverlappingRangeOffsets(format(textContent), format(substring))
522 offsetsLength = offsets.length
523 return if offsetsLength == 0
527 [currentOffset] = offsets
528 searchIndex = currentOffset.start
531 walkTextNodes(element, (textNode) ->
532 {length} = textNode.data
533 return false if length == 0
535 while textIndex + length > searchIndex
537 range = window.document.createRange()
538 range.setStart(start.textNode, start.offset)
539 range.setEnd(textNode, currentOffset.end - textIndex)
540 selection.addRange(range)
543 return true if offsetsIndex >= offsetsLength
544 currentOffset = offsets[offsetsIndex]
547 searchIndex = currentOffset.start
550 start = {textNode, offset: currentOffset.start - textIndex}
551 searchIndex = currentOffset.end - 1
557 selectElement = (element) ->
558 window = element.ownerGlobal
559 selection = window.getSelection()
560 range = window.document.createRange()
561 range.selectNodeContents(element)
562 selection.addRange(range)
564 setAttributes = (element, attributes) ->
565 for attribute, value of attributes
566 element.setAttribute(attribute, value)
569 walkTextNodes = (element, fn) ->
570 for node in element.childNodes then switch node.nodeType
575 stop = walkTextNodes(node, fn)
584 constructor: ({start: @value = 0, @step = 1}) ->
585 tick: -> @value += @step
591 on: (event, listener) ->
592 (@listeners[event] ?= new Set()).add(listener)
594 off: (event, listener) ->
595 @listeners[event]?.delete(listener)
597 emit: (event, data) ->
598 @listeners[event]?.forEach((listener) ->
602 # Returns `[nonMatch, adjacentMatchAfter]`, where `adjacentMatchAfter - nonMatch
603 # == 1`. `fn(n)` is supposed to return `false` for `n <= nonMatch` and `true`
604 # for `n >= adjacentMatchAfter`. Both `nonMatch` and `adjacentMatchAfter` may be
605 # `null` if they cannot be found. Otherwise they’re in the range `min <= n <=
606 # max`. `[null, null]` is returned in non-sensical cases. This function is
607 # intended to be used as a faster alternative to something like this:
609 # adjacentMatchAfter = null
610 # for n in [min..max]
612 # adjacentMatchAfter = n
614 bisect = (min, max, fn) ->
615 return [null, null] unless max - min >= 0 and min % 1 == 0 and max % 1 == 0
618 mid = min + (max - min) // 2
629 when matchMin and matchMax
631 when not matchMin and not matchMax
633 when not matchMin and matchMax
638 getAllNonOverlappingRangeOffsets = (string, substring) ->
640 return [] if length == 0
643 lastOffset = {start: -Infinity, end: -Infinity}
647 index = string.indexOf(substring, index + 1)
649 if index > lastOffset.end
650 lastOffset = {start: index, end: index + length}
651 offsets.push(lastOffset)
653 lastOffset.end = index + length
657 has = (obj, prop) -> Object::hasOwnProperty.call(obj, prop)
659 # Check if `search` exists in `string` (case insensitively). Returns `false` if
660 # `string` doesn’t exist or isn’t a string, such as `<SVG element>.className`.
661 includes = (string, search) ->
662 return false unless typeof string == 'string'
663 return string.toLowerCase().includes(search)
665 # Calls `fn` repeatedly, with at least `interval` ms between each call.
666 interval = (window, interval, fn) ->
668 currentIntervalId = null
671 currentIntervalId = window.setTimeout((-> fn(next)), interval)
674 window.clearTimeout(currentIntervalId)
678 nextTick = (window, fn) -> window.setTimeout((-> fn()) , 0)
680 overlaps = (rectA, rectB) ->
682 Math.round(rectA.right) >= Math.round(rectB.left) and
683 Math.round(rectA.left) <= Math.round(rectB.right) and
684 Math.round(rectA.bottom) >= Math.round(rectB.top) and
685 Math.round(rectA.top) <= Math.round(rectB.bottom)
687 partition = (array, fn) ->
690 for item, index in array
691 if fn(item, index, array)
694 nonMatching.push(item)
695 return [matching, nonMatching]
697 regexEscape = (s) -> s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
699 removeDuplicateChars = (string) -> removeDuplicates(string.split('')).join('')
701 removeDuplicates = (array) -> Array.from(new Set(array))
703 sum = (numbers) -> numbers.reduce(((sum, number) -> sum + number), 0)
709 expandPath = (path) ->
710 if path.startsWith('~/') or path.startsWith('~\\')
711 return OS.Constants.Path.homeDir + path[1..]
715 getCurrentLocation = ->
716 return unless window = getCurrentWindow()
717 return new window.URL(window.gBrowser.selectedBrowser.currentURI.spec)
719 # This function might return `null` on startup.
720 getCurrentWindow = -> nsIWindowMediator.getMostRecentWindow('navigator:browser')
722 # gBrowser getFindBar() used to return the findBar directly, but in recent
723 # versions it returns a promise. This function should be removed once these old
724 # versions are no longer supported.
725 getFindBar = (gBrowser) ->
726 promiseOrFindBar = gBrowser.getFindBar()
727 if promiseOrFindBar instanceof Promise
730 Promise.resolve(promiseOrFindBar)
732 hasEventListeners = (element, type) ->
733 for listener in nsIEventListenerService.getListenerInfoFor(element)
734 if listener.listenerObject and listener.type == type
738 loadCss = (uriString) ->
739 uri = Services.io.newURI(uriString, null, null)
740 method = nsIStyleSheetService.AUTHOR_SHEET
741 unless nsIStyleSheetService.sheetRegistered(uri, method)
742 nsIStyleSheetService.loadAndRegisterSheet(uri, method)
744 nsIStyleSheetService.unregisterSheet(uri, method)
747 observe = (topic, observer) ->
748 observer = {observe: observer} if typeof observer == 'function'
749 Services.obs.addObserver(observer, topic, false)
751 Services.obs.removeObserver(observer, topic, false)
754 # Try to open a button’s dropdown menu, if any.
755 openDropdown = (element) ->
756 if isXULElement(element) and
757 element.getAttribute?('type') == 'menu' and
758 element.open == false # Only change `.open` if it is already a boolean.
761 openPopup = (popup) ->
762 window = popup.ownerGlobal
763 # Show the popup so it gets a height and width.
764 popup.openPopupAtScreen(0, 0)
765 # Center the popup inside the window.
767 window.screenX + window.outerWidth / 2 - popup.clientWidth / 2,
768 window.screenY + window.outerHeight / 2 - popup.clientHeight / 2
771 writeToClipboard = (text) -> nsIClipboardHelper.copyString(text)
782 isDockedDevtoolsElement
785 isIgnoreModeFocusType
792 blurActiveBrowserElement
806 checkElementOrAncestor
818 selectAllSubstringMatches
826 getAllNonOverlappingRangeOffsets