diff options
Diffstat (limited to 'lib/dom_utils.coffee')
| -rw-r--r-- | lib/dom_utils.coffee | 116 |
1 files changed, 97 insertions, 19 deletions
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 82c13287..67d5a44c 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -5,7 +5,7 @@ DomUtils = documentReady: do -> [isReady, callbacks] = [document.readyState != "loading", []] unless isReady - window.addEventListener "DOMContentLoaded", onDOMContentLoaded = -> + window.addEventListener "DOMContentLoaded", onDOMContentLoaded = forTrusted -> window.removeEventListener "DOMContentLoaded", onDOMContentLoaded isReady = true callback() for callback in callbacks @@ -16,7 +16,7 @@ DomUtils = documentComplete: do -> [isComplete, callbacks] = [document.readyState == "complete", []] unless isComplete - window.addEventListener "load", onLoad = -> + window.addEventListener "load", onLoad = forTrusted -> window.removeEventListener "load", onLoad isComplete = true callback() for callback in callbacks @@ -219,7 +219,7 @@ DomUtils = node = selection.anchorNode node and @isDOMDescendant element, node else - if selection.type == "Range" and selection.isCollapsed + if DomUtils.getSelectionType(selection) == "Range" and selection.isCollapsed # The selection is inside the Shadow DOM of a node. We can check the node it registers as being # before, since this represents the node whose Shadow DOM it's inside. containerNode = selection.anchorNode.childNodes[selection.anchorOffset] @@ -249,7 +249,11 @@ DomUtils = simulateClick: (element, modifiers) -> eventSequence = ["mouseover", "mousedown", "mouseup", "click"] for event in eventSequence - @simulateMouseEvent event, element, modifiers + defaultActionShouldTrigger = @simulateMouseEvent event, element, modifiers + if event == "click" and defaultActionShouldTrigger and Utils.isFirefox() + # Firefox doesn't (currently) trigger the default action for modified keys. + DomUtils.simulateClickDefaultAction element, modifiers + defaultActionShouldTrigger # return the values returned by each @simulateMouseEvent call. simulateMouseEvent: do -> lastHoveredElement = undefined @@ -272,6 +276,29 @@ DomUtils = # but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately element.dispatchEvent(mouseEvent) + simulateClickDefaultAction: (element, modifiers = {}) -> + return unless element.tagName?.toLowerCase() == "a" and element.href? + + {ctrlKey, shiftKey, metaKey, altKey} = modifiers + + # Mac uses a different new tab modifier (meta vs. ctrl). + if KeyboardUtils.platform == "Mac" + newTabModifier = metaKey == true and ctrlKey == false + else + newTabModifier = metaKey == false and ctrlKey == true + + if newTabModifier + # Open in new tab. Shift determines whether the tab is focused when created. Alt is ignored. + chrome.runtime.sendMessage {handler: "openUrlInNewTab", url: element.href, active: + shiftKey == true} + else if shiftKey == true and metaKey == false and ctrlKey == false and altKey == false + # Open in new window. + chrome.runtime.sendMessage {handler: "openUrlInNewWindow", url: element.href} + else if element.target == "_blank" + chrome.runtime.sendMessage {handler: "openUrlInNewTab", url: element.href, active: true} + + return + addFlashRect: (rect) -> flashEl = @createElement "div" flashEl.classList.add "vimiumReset" @@ -289,11 +316,23 @@ DomUtils = setTimeout((-> DomUtils.removeElement flashEl), 400) getViewportTopLeft: -> - if getComputedStyle(document.documentElement).position == "static" - top: window.scrollY, left: window.scrollX + box = document.documentElement + style = getComputedStyle box + rect = box.getBoundingClientRect() + if style.position == "static" and not /content|paint|strict/.test(style.contain or "") + # The margin is included in the client rect, so we need to subtract it back out. + marginTop = parseInt style.marginTop + marginLeft = parseInt style.marginLeft + top: -rect.top + marginTop, left: -rect.left + marginLeft else - rect = document.documentElement.getBoundingClientRect() - top: -rect.top, left: -rect.left + if Utils.isFirefox() + # These are always 0 for documentElement on Firefox, so we derive them from CSS border. + clientTop = parseInt style.borderTopWidth + clientLeft = parseInt style.borderLeftWidth + else + {clientTop, clientLeft} = box + top: -rect.top - clientTop, left: -rect.left - clientLeft + suppressPropagation: (event) -> event.stopImmediatePropagation() @@ -302,20 +341,50 @@ DomUtils = event.preventDefault() @suppressPropagation(event) - # Suppress the next keyup event for Escape. - suppressKeyupAfterEscape: (handlerStack) -> - handlerStack.push - _name: "dom_utils/suppressKeyupAfterEscape" - keyup: (event) -> - return true unless KeyboardUtils.isEscape event - @remove() - false + consumeKeyup: do -> + handlerId = null + + (event, callback = null, suppressPropagation) -> + unless event.repeat + handlerStack.remove handlerId if handlerId? + code = event.code + handlerId = handlerStack.push + _name: "dom_utils/consumeKeyup" + keyup: (event) -> + return handlerStack.continueBubbling unless event.code == code + @remove() + if suppressPropagation + DomUtils.suppressPropagation event + else + DomUtils.suppressEvent event + handlerStack.continueBubbling + # We cannot track keyup events if we lose the focus. + blur: (event) -> + @remove() if event.target == window + handlerStack.continueBubbling + callback?() + if suppressPropagation + DomUtils.suppressPropagation event + handlerStack.suppressPropagation + else + DomUtils.suppressEvent event + handlerStack.suppressEvent + + # Polyfill for selection.type (which is not available in Firefox). + getSelectionType: (selection = document.getSelection()) -> + selection.type or do -> + if selection.rangeCount == 0 + "None" + else if selection.isCollapsed + "Caret" + else + "Range" # Adapted from: http://roysharon.com/blog/37. # This finds the element containing the selection focus. getElementWithFocus: (selection, backwards) -> r = t = selection.getRangeAt 0 - if selection.type == "Range" + if DomUtils.getSelectionType(selection) == "Range" r = t.cloneRange() r.collapse backwards t = r.startContainer @@ -341,11 +410,20 @@ DomUtils = # If the element is rendered in a shadow DOM via a <content> element, the <content> element will be # returned, so the shadow DOM is traversed rather than passed over. getContainingElement: (element) -> - element.getDestinationInsertionPoints()[0] or element.parentElement + element.getDestinationInsertionPoints?()[0] or element.parentElement # This tests whether a window is too small to be useful. windowIsTooSmall: -> return window.innerWidth < 3 or window.innerHeight < 3 -root = exports ? window + # Inject user styles manually. This is only necessary for our chrome-extension:// pages and frames. + injectUserCss: -> + Settings.onLoaded -> + style = document.createElement "style" + style.type = "text/css" + style.textContent = Settings.get "userDefinedLinkHintCss" + document.head.appendChild style + +root = exports ? (window.root ?= {}) root.DomUtils = DomUtils +extend window, root unless exports? |
