From The Compiler, 6 Years ago, written in CoffeeScript.
Embed
  1. #### dom_utils.coffee ####
  2. DomUtils =
  3.   # ...
  4.   #
  5.   # Takes an array of XPath selectors, adds the necessary namespaces (currently only XHTML), and applies them
  6.   # to the document root. The namespaceResolver in evaluateXPath should be kept in sync with the namespaces
  7.   # here.
  8.   #
  9.   makeXPath: (elementArray) ->
  10.     xpath = []
  11.     for i of elementArray
  12.       xpath.push("//" + elementArray[i], "//xhtml:" + elementArray[i])
  13.     xpath.join(" | ")
  14.  
  15.   evaluateXPath: (xpath, resultType) ->
  16.     namespaceResolver = (namespace) ->
  17.       if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null
  18.     document.evaluate(xpath, document.documentElement, namespaceResolver, resultType, null)
  19.  
  20.   # ...
  21.  
  22.   simulateClick: (element, modifiers) ->
  23.     modifiers ||= {}
  24.  
  25.     eventSequence = ["mouseover", "mousedown", "mouseup", "click"]
  26.     for event in eventSequence
  27.       mouseEvent = document.createEvent("MouseEvents")
  28.       mouseEvent.initMouseEvent(event, true, true, window, 1, 0, 0, 0, 0, modifiers.ctrlKey, false, false,
  29.           modifiers.metaKey, 0, null)
  30.       # Debugging note: Firefox will not execute the element's default action if we dispatch this click event,
  31.       # but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately
  32.       element.dispatchEvent(mouseEvent)
  33.  
  34.   # ...
  35.  
  36. root = exports ? window
  37. root.DomUtils = DomUtils
  38.  
  39. #### vimium_frontend.coffee ####
  40. window.goPrevious = ->
  41.   previousPatterns = settings.get("previousPatterns") || ""
  42.   previousStrings = previousPatterns.split(",").filter((s) -> s.length)
  43.   findAndFollowRel("prev") || findAndFollowLink(previousStrings)
  44.  
  45. window.goNext = ->
  46.   nextPatterns = settings.get("nextPatterns") || ""
  47.   nextStrings = nextPatterns.split(",").filter((s) -> s.length)
  48.   findAndFollowRel("next") || findAndFollowLink(nextStrings)
  49.  
  50. findAndFollowRel = (value) ->
  51.   relTags = ["link", "a", "area"]
  52.   for tag in relTags
  53.     elements = document.getElementsByTagName(tag)
  54.     for element in elements
  55.       if (element.hasAttribute("rel") && element.rel == value)
  56.         followLink(element)
  57.         return true
  58.  
  59. # used by the findAndFollow* functions.
  60. followLink = (linkElement) ->
  61.   if (linkElement.nodeName.toLowerCase() == "link")
  62.     window.location.href = linkElement.href
  63.   else
  64.     # if we can click on it, don't simply set location.href: some next/prev links are meant to trigger AJAX
  65.     # calls, like the 'more' button on GitHub's newsfeed.
  66.     linkElement.scrollIntoView()
  67.     linkElement.focus()
  68.     DomUtils.simulateClick(linkElement)
  69.  
  70. #
  71. # Find and follow a link which matches any one of a list of strings. If there are multiple such links, they
  72. # are prioritized for shortness, by their position in :linkStrings, how far down the page they are located,
  73. # and finally by whether the match is exact. Practically speaking, this means we favor 'next page' over 'the
  74. # next big thing', and 'more' over 'nextcompany', even if 'next' occurs before 'more' in :linkStrings.
  75. #
  76. findAndFollowLink = (linkStrings) ->
  77.   linksXPath = DomUtils.makeXPath(["a", "*[@onclick or @role='link' or contains(@class, 'button')]"])
  78.   links = DomUtils.evaluateXPath(linksXPath, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE)
  79.   candidateLinks = []
  80.  
  81.   # at the end of this loop, candidateLinks will contain all visible links that match our patterns
  82.   # links lower in the page are more likely to be the ones we want, so we loop through the snapshot backwards
  83.   for i in [(links.snapshotLength - 1)..0] by -1
  84.     link = links.snapshotItem(i)
  85.  
  86.     # ensure link is visible (we don't mind if it is scrolled offscreen)
  87.     boundingClientRect = link.getBoundingClientRect()
  88.     if (boundingClientRect.width == 0 || boundingClientRect.height == 0)
  89.       continue
  90.     computedStyle = window.getComputedStyle(link, null)
  91.     if (computedStyle.getPropertyValue("visibility") != "visible" ||
  92.         computedStyle.getPropertyValue("display") == "none")
  93.       continue
  94.  
  95.     linkMatches = false
  96.     for linkString in linkStrings
  97.       if (link.innerText.toLowerCase().indexOf(linkString) != -1)
  98.         linkMatches = true
  99.         break
  100.     continue unless linkMatches
  101.  
  102.     candidateLinks.push(link)
  103.  
  104.   return if (candidateLinks.length == 0)
  105.  
  106.   for link in candidateLinks
  107.     link.wordCount = link.innerText.trim().split(/\s+/).length
  108.  
  109.   # We can use this trick to ensure that Array.sort is stable. We need this property to retain the reverse
  110.   # in-page order of the links.
  111.  
  112.   candidateLinks.forEach((a,i) -> a.originalIndex = i)
  113.  
  114.   # favor shorter links, and ignore those that are more than one word longer than the shortest link
  115.   candidateLinks =
  116.     candidateLinks
  117.       .sort((a, b) ->
  118.         if (a.wordCount == b.wordCount) then a.originalIndex - b.originalIndex else a.wordCount - b.wordCount
  119.       )
  120.       .filter((a) -> a.wordCount <= candidateLinks[0].wordCount + 1)
  121.  
  122.   for linkString in linkStrings
  123.     exactWordRegex =
  124.       if /\b/.test(linkString[0]) or /\b/.test(linkString[linkString.length - 1])
  125.         new RegExp "\\b" + linkString + "\\b", "i"
  126.       else
  127.         new RegExp linkString, "i"
  128.     for candidateLink in candidateLinks
  129.       if (exactWordRegex.test(candidateLink.innerText))
  130.         followLink(candidateLink)
  131.         return true
  132.   false
  133.  
  134.  
  135. #### from settings.coffee ####
  136. # "\bprev\b,\bprevious\b,\bback\b,<,←,«,≪,<<"
  137. previousPatterns: "prev,previous,back,<,\u2190,\xab,\u226a,<<"
  138. # "\bnext\b,\bmore\b,>,→,»,≫,>>"
  139. nextPatterns: "next,more,>,\u2192,\xbb,\u226b,>>"