// Generated by CoffeeScript 1.12.7
(function() {
  var AlphabetHints, COPY_LINK_URL, DOWNLOAD_LINK_URL, FilterHints, HintCoordinator, LinkHints, LinkHintsMode, LocalHints, OPEN_INCOGNITO, OPEN_IN_CURRENT_TAB, OPEN_IN_NEW_BG_TAB, OPEN_IN_NEW_FG_TAB, OPEN_WITH_QUEUE, TypingProtector, WaitForEnter, availableModes, isMac, root, spanWrap,
    hasProp = {}.hasOwnProperty,
    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
    slice = [].slice,
    extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };

  isMac = KeyboardUtils.platform === "Mac";

  OPEN_IN_CURRENT_TAB = {
    name: "curr-tab",
    indicator: "Open link in current tab"
  };

  OPEN_IN_NEW_BG_TAB = {
    name: "bg-tab",
    indicator: "Open link in new tab",
    clickModifiers: {
      metaKey: isMac,
      ctrlKey: !isMac
    }
  };

  OPEN_IN_NEW_FG_TAB = {
    name: "fg-tab",
    indicator: "Open link in new tab and switch to it",
    clickModifiers: {
      shiftKey: true,
      metaKey: isMac,
      ctrlKey: !isMac
    }
  };

  OPEN_WITH_QUEUE = {
    name: "queue",
    indicator: "Open multiple links in new tabs",
    clickModifiers: {
      metaKey: isMac,
      ctrlKey: !isMac
    }
  };

  COPY_LINK_URL = {
    name: "link",
    indicator: "Copy link URL to Clipboard",
    linkActivator: function(link) {
      var url;
      if (link.href != null) {
        chrome.runtime.sendMessage({
          handler: "copyToClipboard",
          data: link.href
        });
        url = link.href;
        if (28 < url.length) {
          url = url.slice(0, 26) + "....";
        }
        return HUD.showForDuration("Yanked " + url, 2000);
      } else {
        return HUD.showForDuration("No link to yank.", 2000);
      }
    }
  };

  OPEN_INCOGNITO = {
    name: "incognito",
    indicator: "Open link in incognito window",
    linkActivator: function(link) {
      return chrome.runtime.sendMessage({
        handler: 'openUrlInIncognito',
        url: link.href
      });
    }
  };

  DOWNLOAD_LINK_URL = {
    name: "download",
    indicator: "Download link URL",
    clickModifiers: {
      altKey: true,
      ctrlKey: false,
      metaKey: false
    }
  };

  availableModes = [OPEN_IN_CURRENT_TAB, OPEN_IN_NEW_BG_TAB, OPEN_IN_NEW_FG_TAB, OPEN_WITH_QUEUE, COPY_LINK_URL, OPEN_INCOGNITO, DOWNLOAD_LINK_URL];

  HintCoordinator = {
    onExit: [],
    localHints: null,
    suppressKeyboardEvents: null,
    sendMessage: function(messageType, request) {
      if (request == null) {
        request = {};
      }
      return Frame.postMessage("linkHintsMessage", extend(request, {
        messageType: messageType
      }));
    },
    prepareToActivateMode: function(mode, onExit) {
      var suppressKeyboardEvents;
      this.suppressKeyboardEvents = suppressKeyboardEvents = new SuppressAllKeyboardEvents({
        name: "link-hints/suppress-keyboard-events",
        singleton: "link-hints-mode",
        indicator: "Collecting hints...",
        exitOnEscape: true
      });
      Utils.setTimeout(1000, function() {
        if (suppressKeyboardEvents != null ? suppressKeyboardEvents.modeIsActive : void 0) {
          return suppressKeyboardEvents.exit();
        }
      });
      this.onExit = [onExit];
      return this.sendMessage("prepareToActivateMode", {
        modeIndex: availableModes.indexOf(mode),
        isVimiumHelpDialog: window.isVimiumHelpDialog
      });
    },
    getHintDescriptors: function(arg) {
      var isVimiumHelpDialog, modeIndex;
      modeIndex = arg.modeIndex, isVimiumHelpDialog = arg.isVimiumHelpDialog;
      return DomUtils.documentReady((function(_this) {
        return function() {
          return Settings.onLoaded(function() {
            var ref, requireHref;
            requireHref = (ref = availableModes[modeIndex]) === COPY_LINK_URL || ref === OPEN_INCOGNITO;
            _this.localHints = isVimiumHelpDialog && !window.isVimiumHelpDialog ? [] : LocalHints.getLocalHints(requireHref);
            _this.localHintDescriptors = _this.localHints.map(function(arg1, localIndex) {
              var linkText;
              linkText = arg1.linkText;
              return {
                frameId: frameId,
                localIndex: localIndex,
                linkText: linkText
              };
            });
            return _this.sendMessage("postHintDescriptors", {
              hintDescriptors: _this.localHintDescriptors
            });
          });
        };
      })(this));
    },
    activateMode: function(arg) {
      var fId, hintDescriptors, modeIndex, originatingFrameId, ref, ref1;
      hintDescriptors = arg.hintDescriptors, modeIndex = arg.modeIndex, originatingFrameId = arg.originatingFrameId;
      ref = [this.localHintDescriptors, null], hintDescriptors[frameId] = ref[0], this.localHintDescriptors = ref[1];
      hintDescriptors = (ref1 = []).concat.apply(ref1, (function() {
        var i, len, ref1, results;
        ref1 = ((function() {
          var results1;
          results1 = [];
          for (fId in hintDescriptors) {
            if (!hasProp.call(hintDescriptors, fId)) continue;
            results1.push(fId);
          }
          return results1;
        })()).sort();
        results = [];
        for (i = 0, len = ref1.length; i < len; i++) {
          fId = ref1[i];
          results.push(hintDescriptors[fId]);
        }
        return results;
      })());
      return DomUtils.documentReady((function(_this) {
        return function() {
          return Settings.onLoaded(function() {
            var ref2;
            if ((ref2 = _this.suppressKeyboardEvents) != null ? ref2.modeIsActive : void 0) {
              _this.suppressKeyboardEvents.exit();
            }
            _this.suppressKeyboardEvents = null;
            if (frameId !== originatingFrameId) {
              _this.onExit = [];
            }
            return _this.linkHintsMode = new LinkHintsMode(hintDescriptors, availableModes[modeIndex]);
          });
        };
      })(this));
    },
    updateKeyState: function(request) {
      return this.linkHintsMode.updateKeyState(request);
    },
    rotateHints: function() {
      return this.linkHintsMode.rotateHints();
    },
    setOpenLinkMode: function(arg) {
      var modeIndex;
      modeIndex = arg.modeIndex;
      return this.linkHintsMode.setOpenLinkMode(availableModes[modeIndex], false);
    },
    activateActiveHintMarker: function() {
      return this.linkHintsMode.activateLink(this.linkHintsMode.markerMatcher.activeHintMarker);
    },
    getLocalHintMarker: function(hint) {
      if (hint.frameId === frameId) {
        return this.localHints[hint.localIndex];
      } else {
        return null;
      }
    },
    exit: function(arg) {
      var isSuccess, ref;
      isSuccess = arg.isSuccess;
      if ((ref = this.linkHintsMode) != null) {
        ref.deactivateMode();
      }
      while (0 < this.onExit.length) {
        this.onExit.pop()(isSuccess);
      }
      return this.linkHintsMode = this.localHints = null;
    }
  };

  LinkHints = {
    activateMode: function(count, arg) {
      var mode;
      if (count == null) {
        count = 1;
      }
      mode = arg.mode;
      if (mode == null) {
        mode = OPEN_IN_CURRENT_TAB;
      }
      if (0 < count || mode === OPEN_WITH_QUEUE) {
        return HintCoordinator.prepareToActivateMode(mode, function(isSuccess) {
          if (isSuccess) {
            return Utils.nextTick(function() {
              return LinkHints.activateMode(count - 1, {
                mode: mode
              });
            });
          }
        });
      }
    },
    activateModeToOpenInNewTab: function(count) {
      return this.activateMode(count, {
        mode: OPEN_IN_NEW_BG_TAB
      });
    },
    activateModeToOpenInNewForegroundTab: function(count) {
      return this.activateMode(count, {
        mode: OPEN_IN_NEW_FG_TAB
      });
    },
    activateModeToCopyLinkUrl: function(count) {
      return this.activateMode(count, {
        mode: COPY_LINK_URL
      });
    },
    activateModeWithQueue: function() {
      return this.activateMode(1, {
        mode: OPEN_WITH_QUEUE
      });
    },
    activateModeToOpenIncognito: function(count) {
      return this.activateMode(count, {
        mode: OPEN_INCOGNITO
      });
    },
    activateModeToDownloadLink: function(count) {
      return this.activateMode(count, {
        mode: DOWNLOAD_LINK_URL
      });
    }
  };

  LinkHintsMode = (function() {
    LinkHintsMode.prototype.hintMarkerContainingDiv = null;

    LinkHintsMode.prototype.mode = void 0;

    LinkHintsMode.prototype.linkActivator = void 0;

    LinkHintsMode.prototype.hintMode = null;

    LinkHintsMode.prototype.tabCount = 0;

    function LinkHintsMode(hintDescriptors, mode1) {
      var desc, marker;
      this.mode = mode1 != null ? mode1 : OPEN_IN_CURRENT_TAB;
      if (!document.documentElement) {
        return;
      }
      if (hintDescriptors.length === 0) {
        HUD.showForDuration("No links to select.", 2000);
        return;
      }
      this.stableSortCount = 0;
      this.hintMarkers = (function() {
        var i, len, results;
        results = [];
        for (i = 0, len = hintDescriptors.length; i < len; i++) {
          desc = hintDescriptors[i];
          results.push(this.createMarkerFor(desc));
        }
        return results;
      }).call(this);
      this.markerMatcher = new (Settings.get("filterLinkHints") ? FilterHints : AlphabetHints);
      this.markerMatcher.fillInMarkers(this.hintMarkers, this.getNextZIndex.bind(this));
      this.hintMode = new Mode({
        name: "hint/" + this.mode.name,
        indicator: false,
        singleton: "link-hints-mode",
        passInitialKeyupEvents: true,
        suppressAllKeyboardEvents: true,
        suppressTrailingKeyEvents: true,
        exitOnEscape: true,
        exitOnClick: true,
        keydown: this.onKeyDownInMode.bind(this)
      });
      this.hintMode.onExit((function(_this) {
        return function(event) {
          if ((event != null ? event.type : void 0) === "click" || ((event != null ? event.type : void 0) === "keydown" && (KeyboardUtils.isEscape(event) || KeyboardUtils.isBackspace(event)))) {
            return HintCoordinator.sendMessage("exit", {
              isSuccess: false
            });
          }
        };
      })(this));
      this.hintMarkerContainingDiv = DomUtils.addElementList((function() {
        var i, len, ref, results;
        ref = this.hintMarkers;
        results = [];
        for (i = 0, len = ref.length; i < len; i++) {
          marker = ref[i];
          if (marker.isLocalMarker) {
            results.push(marker);
          }
        }
        return results;
      }).call(this), {
        id: "vimiumHintMarkerContainer",
        className: "vimiumReset"
      });
      this.setIndicator();
    }

    LinkHintsMode.prototype.setOpenLinkMode = function(mode1, shouldPropagateToOtherFrames) {
      this.mode = mode1;
      if (shouldPropagateToOtherFrames == null) {
        shouldPropagateToOtherFrames = true;
      }
      if (shouldPropagateToOtherFrames) {
        return HintCoordinator.sendMessage("setOpenLinkMode", {
          modeIndex: availableModes.indexOf(this.mode)
        });
      } else {
        return this.setIndicator();
      }
    };

    LinkHintsMode.prototype.setIndicator = function() {
      var indicator, ref, ref1, typedCharacters;
      if (windowIsFocused()) {
        typedCharacters = (ref = (ref1 = this.markerMatcher.linkTextKeystrokeQueue) != null ? ref1.join("") : void 0) != null ? ref : "";
        indicator = this.mode.indicator + (typedCharacters ? ": \"" + typedCharacters + "\"" : "") + ".";
        return this.hintMode.setIndicator(indicator);
      }
    };

    LinkHintsMode.prototype.getNextZIndex = (function() {
      var baseZIndex;
      baseZIndex = 2140000000;
      return function() {
        return baseZIndex += 1;
      };
    })();

    LinkHintsMode.prototype.createMarkerFor = function(desc) {
      var el, localHintDescriptor, marker;
      marker = desc.frameId === frameId ? (localHintDescriptor = HintCoordinator.getLocalHintMarker(desc), el = DomUtils.createElement("div"), el.rect = localHintDescriptor.rect, el.style.left = el.rect.left + "px", el.style.top = el.rect.top + "px", el.style.zIndex = this.getNextZIndex(), extend(el, {
        className: "vimiumReset internalVimiumHintMarker vimiumHintMarker",
        showLinkText: localHintDescriptor.showLinkText,
        localHintDescriptor: localHintDescriptor
      })) : {};
      return extend(marker, {
        hintDescriptor: desc,
        isLocalMarker: desc.frameId === frameId,
        linkText: desc.linkText,
        stableSortCount: ++this.stableSortCount
      });
    };

    LinkHintsMode.prototype.onKeyDownInMode = function(event) {
      var handlerId, key, keyChar, modifiers, previousMode, previousTabCount, ref, ref1;
      if (event.repeat) {
        return;
      }
      previousTabCount = this.tabCount;
      this.tabCount = 0;
      modifiers = ["Shift"];
      if (!Settings.get("filterLinkHints")) {
        modifiers.push("Control");
      }
      if ((ref = event.key, indexOf.call(modifiers, ref) >= 0) && ((ref1 = this.mode) === OPEN_IN_CURRENT_TAB || ref1 === OPEN_WITH_QUEUE || ref1 === OPEN_IN_NEW_BG_TAB || ref1 === OPEN_IN_NEW_FG_TAB)) {
        this.tabCount = previousTabCount;
        previousMode = this.mode;
        key = event.key;
        switch (key) {
          case "Shift":
            this.setOpenLinkMode(this.mode === OPEN_IN_CURRENT_TAB ? OPEN_IN_NEW_BG_TAB : OPEN_IN_CURRENT_TAB);
            break;
          case "Control":
            this.setOpenLinkMode(this.mode === OPEN_IN_NEW_FG_TAB ? OPEN_IN_NEW_BG_TAB : OPEN_IN_NEW_FG_TAB);
        }
        handlerId = handlerStack.push({
          keyup: (function(_this) {
            return function(event) {
              if (event.key === key) {
                handlerStack.remove();
                _this.setOpenLinkMode(previousMode);
              }
              return true;
            };
          })(this)
        });
        this.hintMode.onExit(function() {
          return handlerStack.remove(handlerId);
        });
      } else if (KeyboardUtils.isBackspace(event)) {
        if (this.markerMatcher.popKeyChar()) {
          this.updateVisibleMarkers();
        } else {
          this.hintMode.exit(event);
        }
      } else if (event.key === "Enter") {
        if (this.markerMatcher.activeHintMarker) {
          HintCoordinator.sendMessage("activateActiveHintMarker");
        }
      } else if (event.key === "Tab") {
        this.tabCount = previousTabCount + (event.shiftKey ? -1 : 1);
        this.updateVisibleMarkers(this.tabCount);
      } else if (event.key === " " && this.markerMatcher.shouldRotateHints(event)) {
        this.tabCount = previousTabCount;
        HintCoordinator.sendMessage("rotateHints");
      } else {
        if (event.ctrlKey || event.metaKey || event.altKey) {
          this.tabCount = previousTabCount;
        }
        if (!event.repeat) {
          if (keyChar = KeyboardUtils.getKeyChar(event).toLowerCase()) {
            if (keyChar === "space") {
              keyChar = " ";
            }
            if (keyChar.length === 1) {
              this.markerMatcher.pushKeyChar(keyChar);
              this.updateVisibleMarkers();
            }
            DomUtils.consumeKeyup(event);
          }
        }
        return;
      }
      return DomUtils.suppressEvent(event);
    };

    LinkHintsMode.prototype.updateVisibleMarkers = function(tabCount) {
      var hintKeystrokeQueue, linkTextKeystrokeQueue, ref;
      if (tabCount == null) {
        tabCount = 0;
      }
      ref = this.markerMatcher, hintKeystrokeQueue = ref.hintKeystrokeQueue, linkTextKeystrokeQueue = ref.linkTextKeystrokeQueue;
      return HintCoordinator.sendMessage("updateKeyState", {
        hintKeystrokeQueue: hintKeystrokeQueue,
        linkTextKeystrokeQueue: linkTextKeystrokeQueue,
        tabCount: tabCount
      });
    };

    LinkHintsMode.prototype.updateKeyState = function(arg) {
      var hintKeystrokeQueue, i, k, len, len1, linkTextKeystrokeQueue, linksMatched, marker, matched, ref, ref1, tabCount, userMightOverType;
      hintKeystrokeQueue = arg.hintKeystrokeQueue, linkTextKeystrokeQueue = arg.linkTextKeystrokeQueue, tabCount = arg.tabCount;
      extend(this.markerMatcher, {
        hintKeystrokeQueue: hintKeystrokeQueue,
        linkTextKeystrokeQueue: linkTextKeystrokeQueue
      });
      ref = this.markerMatcher.getMatchingHints(this.hintMarkers, tabCount, this.getNextZIndex.bind(this)), linksMatched = ref.linksMatched, userMightOverType = ref.userMightOverType;
      if (linksMatched.length === 0) {
        this.deactivateMode();
      } else if (linksMatched.length === 1) {
        this.activateLink(linksMatched[0], userMightOverType != null ? userMightOverType : false);
      } else {
        ref1 = this.hintMarkers;
        for (i = 0, len = ref1.length; i < len; i++) {
          marker = ref1[i];
          this.hideMarker(marker);
        }
        for (k = 0, len1 = linksMatched.length; k < len1; k++) {
          matched = linksMatched[k];
          this.showMarker(matched, this.markerMatcher.hintKeystrokeQueue.length);
        }
      }
      return this.setIndicator();
    };

    LinkHintsMode.prototype.rotateHints = (function() {
      var markerOverlapsStack;
      markerOverlapsStack = function(marker, stack) {
        var i, len, otherMarker;
        for (i = 0, len = stack.length; i < len; i++) {
          otherMarker = stack[i];
          if (Rect.rectsOverlap(marker.markerRect, otherMarker.markerRect)) {
            return true;
          }
        }
        return false;
      };
      return function() {
        var i, index, k, l, len, len1, len2, len3, localHintMarkers, m, marker, markerOverlapsThisStack, stack, stackForThisMarker, stacks, zIndexes;
        localHintMarkers = this.hintMarkers.filter(function(marker) {
          return marker.isLocalMarker && marker.style.display !== "none";
        });
        for (i = 0, len = localHintMarkers.length; i < len; i++) {
          marker = localHintMarkers[i];
          if (marker.markerRect == null) {
            marker.markerRect = marker.getClientRects()[0];
          }
        }
        stacks = [];
        for (k = 0, len1 = localHintMarkers.length; k < len1; k++) {
          marker = localHintMarkers[k];
          stackForThisMarker = null;
          stacks = (function() {
            var l, len2, results;
            results = [];
            for (l = 0, len2 = stacks.length; l < len2; l++) {
              stack = stacks[l];
              markerOverlapsThisStack = markerOverlapsStack(marker, stack);
              if (markerOverlapsThisStack && (stackForThisMarker == null)) {
                stack.push(marker);
                results.push(stackForThisMarker = stack);
              } else if (markerOverlapsThisStack && (stackForThisMarker != null)) {
                stackForThisMarker.push.apply(stackForThisMarker, stack);
                continue;
              } else {
                results.push(stack);
              }
            }
            return results;
          })();
          if (stackForThisMarker == null) {
            stacks.push([marker]);
          }
        }
        for (l = 0, len2 = stacks.length; l < len2; l++) {
          stack = stacks[l];
          if (1 < stack.length) {
            zIndexes = (function() {
              var len3, m, results;
              results = [];
              for (m = 0, len3 = stack.length; m < len3; m++) {
                marker = stack[m];
                results.push(marker.style.zIndex);
              }
              return results;
            })();
            zIndexes.push(zIndexes[0]);
            for (index = m = 0, len3 = stack.length; m < len3; index = ++m) {
              marker = stack[index];
              marker.style.zIndex = zIndexes[index + 1];
            }
          }
        }
        return null;
      };
    })();

    LinkHintsMode.prototype.activateLink = function(linkMatched, userMightOverType) {
      var clickEl, installKeyboardBlocker, localHintDescriptor;
      if (userMightOverType == null) {
        userMightOverType = false;
      }
      this.removeHintMarkers();
      if (linkMatched.isLocalMarker) {
        localHintDescriptor = linkMatched.localHintDescriptor;
        clickEl = localHintDescriptor.element;
        HintCoordinator.onExit.push((function(_this) {
          return function(isSuccess) {
            var clickActivator, linkActivator, ref, ref1, ref2;
            if (isSuccess) {
              if (localHintDescriptor.reason === "Frame.") {
                return Utils.nextTick(function() {
                  return focusThisFrame({
                    highlight: true
                  });
                });
              } else if (localHintDescriptor.reason === "Scroll.") {
                return handlerStack.bubbleEvent("DOMActivate", {
                  target: clickEl
                });
              } else if (localHintDescriptor.reason === "Open.") {
                return clickEl.open = !clickEl.open;
              } else if (DomUtils.isSelectable(clickEl)) {
                window.focus();
                return DomUtils.simulateSelect(clickEl);
              } else {
                clickActivator = function(modifiers) {
                  return function(link) {
                    return DomUtils.simulateClick(link, modifiers);
                  };
                };
                linkActivator = (ref = _this.mode.linkActivator) != null ? ref : clickActivator(_this.mode.clickModifiers);
                if (((ref1 = clickEl.nodeName.toLowerCase()) === "input" || ref1 === "select") && ((ref2 = clickEl.type) !== "button" && ref2 !== "submit")) {
                  clickEl.focus();
                }
                return linkActivator(clickEl);
              }
            }
          };
        })(this));
      }
      installKeyboardBlocker = function(startKeyboardBlocker) {
        var flashEl, fn, i, len, rect, ref, ref1, viewportLeft, viewportTop;
        if (linkMatched.isLocalMarker) {
          ref = DomUtils.getViewportTopLeft(), viewportTop = ref.top, viewportLeft = ref.left;
          ref1 = (function() {
            var k, len, ref1, results;
            ref1 = clickEl.getClientRects();
            results = [];
            for (k = 0, len = ref1.length; k < len; k++) {
              rect = ref1[k];
              results.push(Rect.copy(rect));
            }
            return results;
          })();
          fn = function(flashEl) {
            return HintCoordinator.onExit.push(function() {
              return DomUtils.removeElement(flashEl);
            });
          };
          for (i = 0, len = ref1.length; i < len; i++) {
            rect = ref1[i];
            extend(rect, {
              top: rect.top + viewportTop,
              left: rect.left + viewportLeft
            });
            flashEl = DomUtils.addFlashRect(rect);
            fn(flashEl);
          }
        }
        if (windowIsFocused()) {
          return startKeyboardBlocker(function(isSuccess) {
            return HintCoordinator.sendMessage("exit", {
              isSuccess: isSuccess
            });
          });
        }
      };
      if (userMightOverType && Settings.get("waitForEnterForFilteredHints")) {
        return installKeyboardBlocker(function(callback) {
          return new WaitForEnter(callback);
        });
      } else if (userMightOverType) {
        return installKeyboardBlocker(function(callback) {
          return new TypingProtector(200, callback);
        });
      } else if (linkMatched.isLocalMarker) {
        DomUtils.flashRect(linkMatched.rect);
        return HintCoordinator.sendMessage("exit", {
          isSuccess: true
        });
      }
    };

    LinkHintsMode.prototype.showMarker = function(linkMarker, matchingCharCount) {
      var i, j, ref, results;
      if (!linkMarker.isLocalMarker) {
        return;
      }
      linkMarker.style.display = "";
      results = [];
      for (j = i = 0, ref = linkMarker.childNodes.length; 0 <= ref ? i < ref : i > ref; j = 0 <= ref ? ++i : --i) {
        if (j < matchingCharCount) {
          results.push(linkMarker.childNodes[j].classList.add("matchingCharacter"));
        } else {
          results.push(linkMarker.childNodes[j].classList.remove("matchingCharacter"));
        }
      }
      return results;
    };

    LinkHintsMode.prototype.hideMarker = function(linkMarker) {
      if (linkMarker.isLocalMarker) {
        return linkMarker.style.display = "none";
      }
    };

    LinkHintsMode.prototype.deactivateMode = function() {
      var ref;
      this.removeHintMarkers();
      return (ref = this.hintMode) != null ? ref.exit() : void 0;
    };

    LinkHintsMode.prototype.removeHintMarkers = function() {
      if (this.hintMarkerContainingDiv) {
        DomUtils.removeElement(this.hintMarkerContainingDiv);
      }
      return this.hintMarkerContainingDiv = null;
    };

    return LinkHintsMode;

  })();

  AlphabetHints = (function() {
    function AlphabetHints() {
      this.linkHintCharacters = Settings.get("linkHintCharacters").toLowerCase();
      this.hintKeystrokeQueue = [];
    }

    AlphabetHints.prototype.fillInMarkers = function(hintMarkers) {
      var hintStrings, i, idx, len, marker, results;
      hintStrings = this.hintStrings(hintMarkers.length);
      results = [];
      for (idx = i = 0, len = hintMarkers.length; i < len; idx = ++i) {
        marker = hintMarkers[idx];
        marker.hintString = hintStrings[idx];
        if (marker.isLocalMarker) {
          results.push(marker.innerHTML = spanWrap(marker.hintString.toUpperCase()));
        } else {
          results.push(void 0);
        }
      }
      return results;
    };

    AlphabetHints.prototype.hintStrings = function(linkCount) {
      var ch, hint, hints, i, len, offset, ref;
      hints = [""];
      offset = 0;
      while (hints.length - offset < linkCount || hints.length === 1) {
        hint = hints[offset++];
        ref = this.linkHintCharacters;
        for (i = 0, len = ref.length; i < len; i++) {
          ch = ref[i];
          hints.push(ch + hint);
        }
      }
      hints = hints.slice(offset, offset + linkCount);
      return hints.sort().map(function(str) {
        return str.reverse();
      });
    };

    AlphabetHints.prototype.getMatchingHints = function(hintMarkers) {
      var matchString;
      matchString = this.hintKeystrokeQueue.join("");
      return {
        linksMatched: hintMarkers.filter(function(linkMarker) {
          return linkMarker.hintString.startsWith(matchString);
        })
      };
    };

    AlphabetHints.prototype.pushKeyChar = function(keyChar) {
      return this.hintKeystrokeQueue.push(keyChar);
    };

    AlphabetHints.prototype.popKeyChar = function() {
      return this.hintKeystrokeQueue.pop();
    };

    AlphabetHints.prototype.shouldRotateHints = function() {
      return true;
    };

    return AlphabetHints;

  })();

  FilterHints = (function() {
    function FilterHints() {
      this.linkHintNumbers = Settings.get("linkHintNumbers");
      this.hintKeystrokeQueue = [];
      this.linkTextKeystrokeQueue = [];
      this.activeHintMarker = null;
      this.splitRegexp = new RegExp("[\\W" + (Utils.escapeRegexSpecialCharacters(this.linkHintNumbers)) + "]+");
    }

    FilterHints.prototype.generateHintString = function(linkHintNumber) {
      var base, hint;
      base = this.linkHintNumbers.length;
      hint = [];
      while (0 < linkHintNumber) {
        hint.push(this.linkHintNumbers[Math.floor(linkHintNumber % base)]);
        linkHintNumber = Math.floor(linkHintNumber / base);
      }
      return hint.reverse().join("");
    };

    FilterHints.prototype.renderMarker = function(marker) {
      var linkText;
      linkText = marker.linkText;
      if (35 < linkText.length) {
        linkText = linkText.slice(0, 33) + "...";
      }
      return marker.innerHTML = spanWrap(marker.hintString + (marker.showLinkText ? ": " + linkText : ""));
    };

    FilterHints.prototype.fillInMarkers = function(hintMarkers, getNextZIndex) {
      var i, len, marker;
      for (i = 0, len = hintMarkers.length; i < len; i++) {
        marker = hintMarkers[i];
        if (marker.isLocalMarker) {
          this.renderMarker(marker);
        }
      }
      return this.getMatchingHints(hintMarkers, 0, getNextZIndex);
    };

    FilterHints.prototype.getMatchingHints = function(hintMarkers, tabCount, getNextZIndex) {
      var linksMatched, matchString, ref, ref1, ref2, ref3, ref4, ref5;
      matchString = this.hintKeystrokeQueue.join("");
      linksMatched = this.filterLinkHints(hintMarkers);
      linksMatched = linksMatched.filter(function(linkMarker) {
        return linkMarker.hintString.startsWith(matchString);
      });
      tabCount = ((linksMatched.length * Math.abs(tabCount)) + tabCount) % linksMatched.length;
      if ((ref = this.activeHintMarker) != null) {
        if ((ref1 = ref.classList) != null) {
          ref1.remove("vimiumActiveHintMarker");
        }
      }
      this.activeHintMarker = linksMatched[tabCount];
      if ((ref2 = this.activeHintMarker) != null) {
        if ((ref3 = ref2.classList) != null) {
          ref3.add("vimiumActiveHintMarker");
        }
      }
      if ((ref4 = this.activeHintMarker) != null) {
        if ((ref5 = ref4.style) != null) {
          ref5.zIndex = getNextZIndex();
        }
      }
      return {
        linksMatched: linksMatched,
        userMightOverType: this.hintKeystrokeQueue.length === 0 && 0 < this.linkTextKeystrokeQueue.length
      };
    };

    FilterHints.prototype.pushKeyChar = function(keyChar) {
      if (0 <= this.linkHintNumbers.indexOf(keyChar)) {
        return this.hintKeystrokeQueue.push(keyChar);
      } else if (keyChar === " " || !this.splitRegexp.test(keyChar)) {
        this.hintKeystrokeQueue = [];
        return this.linkTextKeystrokeQueue.push(keyChar);
      }
    };

    FilterHints.prototype.popKeyChar = function() {
      return this.hintKeystrokeQueue.pop() || this.linkTextKeystrokeQueue.pop();
    };

    FilterHints.prototype.filterLinkHints = function(hintMarkers) {
      var i, len, linkHintNumber, linkMarker, matchingHintMarkers, results, scoreFunction;
      scoreFunction = this.scoreLinkHint(this.linkTextKeystrokeQueue.join(""));
      matchingHintMarkers = hintMarkers.filter((function(_this) {
        return function(linkMarker) {
          linkMarker.score = scoreFunction(linkMarker);
          return 0 === _this.linkTextKeystrokeQueue.length || 0 < linkMarker.score;
        };
      })(this)).sort(function(a, b) {
        if (b.score === a.score) {
          return b.stableSortCount - a.stableSortCount;
        } else {
          return b.score - a.score;
        }
      });
      if (matchingHintMarkers.length === 0 && this.hintKeystrokeQueue.length === 0 && 0 < this.linkTextKeystrokeQueue.length) {
        this.linkTextKeystrokeQueue.pop();
        return this.filterLinkHints(hintMarkers);
      } else {
        linkHintNumber = 1;
        results = [];
        for (i = 0, len = matchingHintMarkers.length; i < len; i++) {
          linkMarker = matchingHintMarkers[i];
          linkMarker.hintString = this.generateHintString(linkHintNumber++);
          this.renderMarker(linkMarker);
          results.push(linkMarker);
        }
        return results;
      }
    };

    FilterHints.prototype.scoreLinkHint = function(linkSearchString) {
      var searchWords;
      searchWords = linkSearchString.trim().toLowerCase().split(this.splitRegexp);
      return (function(_this) {
        return function(linkMarker) {
          var addFunc, idx, linkWord, linkWordScores, linkWords, position, score, searchWord, searchWordScores;
          if (!(0 < searchWords.length)) {
            return 0;
          }
          linkWords = linkMarker.linkWords != null ? linkMarker.linkWords : linkMarker.linkWords = linkMarker.linkText.toLowerCase().split(_this.splitRegexp).filter(function(term) {
            return term;
          });
          searchWordScores = (function() {
            var i, len, results;
            results = [];
            for (i = 0, len = searchWords.length; i < len; i++) {
              searchWord = searchWords[i];
              linkWordScores = (function() {
                var k, len1, results1;
                results1 = [];
                for (idx = k = 0, len1 = linkWords.length; k < len1; idx = ++k) {
                  linkWord = linkWords[idx];
                  position = linkWord.indexOf(searchWord);
                  if (position < 0) {
                    results1.push(0);
                  } else if (position === 0 && searchWord.length === linkWord.length) {
                    if (idx === 0) {
                      results1.push(8);
                    } else {
                      results1.push(6);
                    }
                  } else if (position === 0) {
                    if (idx === 0) {
                      results1.push(4);
                    } else {
                      results1.push(2);
                    }
                  } else {
                    results1.push(1);
                  }
                }
                return results1;
              })();
              results.push(Math.max.apply(Math, linkWordScores));
            }
            return results;
          })();
          if (indexOf.call(searchWordScores, 0) >= 0) {
            return 0;
          } else {
            addFunc = function(a, b) {
              return a + b;
            };
            score = searchWordScores.reduce(addFunc, 0);
            return score / Math.log(1 + (linkMarker.linkText.length || 100));
          }
        };
      })(this);
    };

    FilterHints.prototype.shouldRotateHints = function(event) {
      return event.ctrlKey || event.altKey || event.metaKey;
    };

    return FilterHints;

  })();

  spanWrap = function(hintString) {
    var char, i, innerHTML, len;
    innerHTML = [];
    for (i = 0, len = hintString.length; i < len; i++) {
      char = hintString[i];
      innerHTML.push("<span class='vimiumReset'>" + char + "</span>");
    }
    return innerHTML.join("");
  };

  LocalHints = {
    getVisibleClickable: function(element) {
      var actionName, areas, areasAndRects, base1, clientRect, eventType, i, imgClientRects, isClickable, jsactionRule, jsactionRules, len, map, mapName, namespace, onlyHasTabIndex, possibleFalsePositive, reason, ref, ref1, ref10, ref11, ref12, ref13, ref14, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, ruleSplit, tabIndex, tabIndexValue, tagName, visibleElements;
      tagName = (ref = typeof (base1 = element.tagName).toLowerCase === "function" ? base1.toLowerCase() : void 0) != null ? ref : "";
      isClickable = false;
      onlyHasTabIndex = false;
      possibleFalsePositive = false;
      visibleElements = [];
      reason = null;
      if (tagName === "img") {
        mapName = element.getAttribute("usemap");
        if (mapName) {
          imgClientRects = element.getClientRects();
          mapName = mapName.replace(/^#/, "").replace("\"", "\\\"");
          map = document.querySelector("map[name=\"" + mapName + "\"]");
          if (map && imgClientRects.length > 0) {
            areas = map.getElementsByTagName("area");
            areasAndRects = DomUtils.getClientRectsForAreas(imgClientRects[0], areas);
            visibleElements.push.apply(visibleElements, areasAndRects);
          }
        }
      }
      if (((ref1 = (ref2 = element.getAttribute("aria-hidden")) != null ? ref2.toLowerCase() : void 0) === "" || ref1 === "true") || ((ref3 = (ref4 = element.getAttribute("aria-disabled")) != null ? ref4.toLowerCase() : void 0) === "" || ref3 === "true")) {
        return [];
      }
      if (this.checkForAngularJs == null) {
        this.checkForAngularJs = (function() {
          var angularElements, i, k, len, len1, ngAttributes, prefix, ref5, ref6, separator;
          angularElements = document.getElementsByClassName("ng-scope");
          if (angularElements.length === 0) {
            return function() {
              return false;
            };
          } else {
            ngAttributes = [];
            ref5 = ['', 'data-', 'x-'];
            for (i = 0, len = ref5.length; i < len; i++) {
              prefix = ref5[i];
              ref6 = ['-', ':', '_'];
              for (k = 0, len1 = ref6.length; k < len1; k++) {
                separator = ref6[k];
                ngAttributes.push(prefix + "ng" + separator + "click");
              }
            }
            return function(element) {
              var attribute, l, len2;
              for (l = 0, len2 = ngAttributes.length; l < len2; l++) {
                attribute = ngAttributes[l];
                if (element.hasAttribute(attribute)) {
                  return true;
                }
              }
              return false;
            };
          }
        })();
      }
      isClickable || (isClickable = this.checkForAngularJs(element));
      if (element.hasAttribute("onclick") || ((ref5 = (ref6 = element.getAttribute("role")) != null ? ref6.toLowerCase() : void 0) === "button" || ref5 === "link") || ((ref7 = (ref8 = element.getAttribute("contentEditable")) != null ? ref8.toLowerCase() : void 0) === "" || ref7 === "contentEditable" || ref7 === "true")) {
        isClickable = true;
      }
      if (!isClickable && element.hasAttribute("jsaction")) {
        jsactionRules = element.getAttribute("jsaction").split(";");
        for (i = 0, len = jsactionRules.length; i < len; i++) {
          jsactionRule = jsactionRules[i];
          ruleSplit = jsactionRule.trim().split(":");
          if ((1 <= (ref9 = ruleSplit.length) && ref9 <= 2)) {
            ref10 = ruleSplit.length === 1 ? ["click"].concat(slice.call(ruleSplit[0].trim().split(".")), ["_"]) : [ruleSplit[0]].concat(slice.call(ruleSplit[1].trim().split(".")), ["_"]), eventType = ref10[0], namespace = ref10[1], actionName = ref10[2];
            isClickable || (isClickable = eventType === "click" && namespace !== "none" && actionName !== "_");
          }
        }
      }
      switch (tagName) {
        case "a":
          isClickable = true;
          break;
        case "textarea":
          isClickable || (isClickable = !element.disabled && !element.readOnly);
          break;
        case "input":
          isClickable || (isClickable = !(((ref11 = element.getAttribute("type")) != null ? ref11.toLowerCase() : void 0) === "hidden" || element.disabled || (element.readOnly && DomUtils.isSelectable(element))));
          break;
        case "button":
        case "select":
          isClickable || (isClickable = !element.disabled);
          break;
        case "label":
          isClickable || (isClickable = (element.control != null) && !element.control.disabled && (this.getVisibleClickable(element.control)).length === 0);
          break;
        case "body":
          isClickable || (isClickable = element === document.body && !windowIsFocused() && window.innerWidth > 3 && window.innerHeight > 3 && ((ref12 = document.body) != null ? ref12.tagName.toLowerCase() : void 0) !== "frameset" ? reason = "Frame." : void 0);
          isClickable || (isClickable = element === document.body && windowIsFocused() && Scroller.isScrollableElement(element) ? reason = "Scroll." : void 0);
          break;
        case "img":
          isClickable || (isClickable = (ref13 = element.style.cursor) === "zoom-in" || ref13 === "zoom-out");
          break;
        case "div":
        case "ol":
        case "ul":
          isClickable || (isClickable = element.clientHeight < element.scrollHeight && Scroller.isScrollableElement(element) ? reason = "Scroll." : void 0);
          break;
        case "details":
          isClickable = true;
          reason = "Open.";
      }
      if (!isClickable && 0 <= ((ref14 = element.getAttribute("class")) != null ? ref14.toLowerCase().indexOf("button") : void 0)) {
        possibleFalsePositive = isClickable = true;
      }
      tabIndexValue = element.getAttribute("tabindex");
      tabIndex = tabIndexValue === "" ? 0 : parseInt(tabIndexValue);
      if (!(isClickable || isNaN(tabIndex) || tabIndex < 0)) {
        isClickable = onlyHasTabIndex = true;
      }
      if (isClickable) {
        clientRect = DomUtils.getVisibleClientRect(element, true);
        if (clientRect !== null) {
          visibleElements.push({
            element: element,
            rect: clientRect,
            secondClassCitizen: onlyHasTabIndex,
            possibleFalsePositive: possibleFalsePositive,
            reason: reason
          });
        }
      }
      return visibleElements;
    },
    getLocalHints: function(requireHref) {
      var descendantsToCheck, element, elements, hint, i, k, l, left, len, len1, len2, localHints, negativeRect, nonOverlappingElements, position, rects, ref, ref1, top, visibleElement, visibleElements;
      if (!document.documentElement) {
        return [];
      }
      elements = document.documentElement.getElementsByTagName("*");
      visibleElements = [];
      for (i = 0, len = elements.length; i < len; i++) {
        element = elements[i];
        if (!(requireHref && !element.href)) {
          visibleElement = this.getVisibleClickable(element);
          visibleElements.push.apply(visibleElements, visibleElement);
        }
      }
      visibleElements = visibleElements.reverse();
      descendantsToCheck = [1, 2, 3];
      visibleElements = (function() {
        var k, len1, results;
        results = [];
        for (position = k = 0, len1 = visibleElements.length; k < len1; position = ++k) {
          element = visibleElements[position];
          if (element.possibleFalsePositive && (function() {
            var _, candidateDescendant, index, l, len2;
            index = Math.max(0, position - 6);
            while (index < position) {
              candidateDescendant = visibleElements[index].element;
              for (l = 0, len2 = descendantsToCheck.length; l < len2; l++) {
                _ = descendantsToCheck[l];
                candidateDescendant = candidateDescendant != null ? candidateDescendant.parentElement : void 0;
                if (candidateDescendant === element.element) {
                  return true;
                }
              }
              index += 1;
            }
            return false;
          })()) {
            continue;
          }
          results.push(element);
        }
        return results;
      })();
      localHints = nonOverlappingElements = [];
      while (visibleElement = visibleElements.pop()) {
        rects = [visibleElement.rect];
        for (k = 0, len1 = visibleElements.length; k < len1; k++) {
          negativeRect = visibleElements[k].rect;
          rects = (ref = []).concat.apply(ref, rects.map(function(rect) {
            return Rect.subtract(rect, negativeRect);
          }));
        }
        if (rects.length > 0) {
          nonOverlappingElements.push(extend(visibleElement, {
            rect: rects[0]
          }));
        } else {
          if (!visibleElement.secondClassCitizen) {
            nonOverlappingElements.push(visibleElement);
          }
        }
      }
      ref1 = DomUtils.getViewportTopLeft(), top = ref1.top, left = ref1.left;
      for (l = 0, len2 = nonOverlappingElements.length; l < len2; l++) {
        hint = nonOverlappingElements[l];
        hint.rect.top += top;
        hint.rect.left += left;
      }
      if (Settings.get("filterLinkHints")) {
        this.withLabelMap((function(_this) {
          return function(labelMap) {
            var len3, m, results;
            results = [];
            for (m = 0, len3 = localHints.length; m < len3; m++) {
              hint = localHints[m];
              results.push(extend(hint, _this.generateLinkText(labelMap, hint)));
            }
            return results;
          };
        })(this));
      }
      return localHints;
    },
    withLabelMap: function(callback) {
      var forElement, i, label, labelMap, labelText, labels, len;
      labelMap = {};
      labels = document.querySelectorAll("label");
      for (i = 0, len = labels.length; i < len; i++) {
        label = labels[i];
        forElement = label.getAttribute("for");
        if (forElement) {
          labelText = label.textContent.trim();
          if (labelText[labelText.length - 1] === ":") {
            labelText = labelText.substr(0, labelText.length - 1);
          }
          labelMap[forElement] = labelText;
        }
      }
      return callback(labelMap);
    },
    generateLinkText: function(labelMap, hint) {
      var element, linkText, nodeName, showLinkText;
      element = hint.element;
      linkText = "";
      showLinkText = false;
      nodeName = element.nodeName.toLowerCase();
      if (nodeName === "input") {
        if (labelMap[element.id]) {
          linkText = labelMap[element.id];
          showLinkText = true;
        } else if (element.type !== "password") {
          linkText = element.value;
          if (!linkText && 'placeholder' in element) {
            linkText = element.placeholder;
          }
        }
      } else if (nodeName === "a" && !element.textContent.trim() && element.firstElementChild && element.firstElementChild.nodeName.toLowerCase() === "img") {
        linkText = element.firstElementChild.alt || element.firstElementChild.title;
        if (linkText) {
          showLinkText = true;
        }
      } else if (hint.reason != null) {
        linkText = hint.reason;
        showLinkText = true;
      } else if (0 < element.textContent.length) {
        linkText = element.textContent.slice(0, 256);
      } else if (element.hasAttribute("title")) {
        linkText = element.getAttribute("title");
      } else {
        linkText = element.innerHTML.slice(0, 256);
      }
      return {
        linkText: linkText.trim(),
        showLinkText: showLinkText
      };
    }
  };

  TypingProtector = (function(superClass) {
    extend1(TypingProtector, superClass);

    function TypingProtector(delay, callback) {
      var resetExitTimer;
      this.timer = Utils.setTimeout(delay, (function(_this) {
        return function() {
          return _this.exit();
        };
      })(this));
      resetExitTimer = (function(_this) {
        return function(event) {
          clearTimeout(_this.timer);
          return _this.timer = Utils.setTimeout(delay, function() {
            return _this.exit();
          });
        };
      })(this);
      TypingProtector.__super__.constructor.call(this, {
        name: "hint/typing-protector",
        suppressAllKeyboardEvents: true,
        keydown: resetExitTimer,
        keypress: resetExitTimer
      });
      this.onExit(function() {
        return callback(true);
      });
    }

    return TypingProtector;

  })(Mode);

  WaitForEnter = (function(superClass) {
    extend1(WaitForEnter, superClass);

    function WaitForEnter(callback) {
      WaitForEnter.__super__.constructor.call(this, {
        name: "hint/wait-for-enter",
        suppressAllKeyboardEvents: true,
        indicator: "Hit <Enter> to proceed..."
      });
      this.push({
        keydown: (function(_this) {
          return function(event) {
            if (event.key === "Enter") {
              _this.exit();
              return callback(true);
            } else if (KeyboardUtils.isEscape(event)) {
              _this.exit();
              return callback(false);
            }
          };
        })(this)
      });
    }

    return WaitForEnter;

  })(Mode);

  root = typeof exports !== "undefined" && exports !== null ? exports : window;

  root.LinkHints = LinkHints;

  root.HintCoordinator = HintCoordinator;

  extend(root, {
    LinkHintsMode: LinkHintsMode,
    LocalHints: LocalHints,
    AlphabetHints: AlphabetHints,
    WaitForEnter: WaitForEnter
  });

}).call(this);
