diff options
| -rw-r--r-- | content_scripts/mode.coffee | 29 | ||||
| -rw-r--r-- | content_scripts/mode_insert.coffee | 70 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 55 | ||||
| -rw-r--r-- | lib/handler_stack.coffee | 2 | ||||
| -rw-r--r-- | manifest.json | 1 | 
5 files changed, 98 insertions, 59 deletions
| diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee index e4b6017c..88938e79 100644 --- a/content_scripts/mode.coffee +++ b/content_scripts/mode.coffee @@ -3,6 +3,8 @@ class Mode    # Static members.    @modes: []    @current: -> Mode.modes[0] + +  # Constants. Static.    @suppressPropagation = false    @propagate = true @@ -12,8 +14,6 @@ class Mode    keydown: "suppress"  # A function, or "suppress" or "pass"; the latter are replaced with suitable functions.    keypress: "suppress" # A function, or "suppress" or "pass"; the latter are replaced with suitable functions.    keyup: "suppress"    # A function, or "suppress" or "pass"; the latter are replaced with suitable functions. -  onDeactivate: ->     # Called when leaving this mode. -  onReactivate: ->     # Called when this mode is reactivated.    constructor: (options) ->      extend @, options @@ -22,10 +22,6 @@ class Mode        keydown: @checkForBuiltInHandler "keydown", @keydown        keypress: @checkForBuiltInHandler "keypress", @keypress        keyup: @checkForBuiltInHandler "keyup", @keyup -      reactivateMode: => -        @onReactivate() -        Mode.setBadge() -        return Mode.suppressPropagation      Mode.modes.unshift @      Mode.setBadge() @@ -37,12 +33,12 @@ class Mode        when "pass" then @generatePassThrough type        else handler -  # Generate a default handler which always passes through; except Esc, which pops the current mode. +  # Generate a default handler which always passes through to the underlying page; except Esc, which pops the +  # current mode.    generatePassThrough: (type) -> -    me = @ -    (event) -> +    (event) =>        if type == "keydown" and KeyboardUtils.isEscape event -        me.popMode event +        @exit()          return Mode.suppressPropagation        handlerStack.passThrough @@ -51,19 +47,14 @@ class Mode      handler = @generatePassThrough type      (event) -> handler(event) and Mode.suppressPropagation # Always falsy. -  # Leave the current mode; event may or may not be provide.  It is the responsibility of the creator of this -  # object to know whether or not an event will be provided.  Bubble a "reactivateMode" event to notify the -  # now-active mode that it is once again top dog. -  popMode: (event) -> -    Mode.modes = Mode.modes.filter (mode) => mode != @ +  exit: ->      handlerStack.remove @handlerId -    @onDeactivate event -    handlerStack.bubbleEvent "reactivateMode", event +    Mode.modes = Mode.modes.filter (mode) => mode != @ +    Mode.setBadge()    # Set the badge on the browser popup to indicate the current mode; static method.    @setBadge: -> -    badge = Mode.getBadge() -    chrome.runtime.sendMessage({ handler: "setBadge", badge: badge }) +    chrome.runtime.sendMessage({ handler: "setBadge", badge: Mode.getBadge() })    # Static convenience methods.    @is: (mode) -> Mode.current()?.name == mode diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee new file mode 100644 index 00000000..4a1d4349 --- /dev/null +++ b/content_scripts/mode_insert.coffee @@ -0,0 +1,70 @@ + +class InsertMode extends Mode +  userActivated: false + +  # Input or text elements are considered focusable and able to receieve their own keyboard events, and will +  # enter insert mode if focused. Also note that the "contentEditable" attribute can be set on any element +  # which makes it a rich text editor, like the notes on jjot.com. +  isEditable: (element) -> +    return true if element.isContentEditable +    nodeName = element.nodeName?.toLowerCase() +    # Use a blacklist instead of a whitelist because new form controls are still being implemented for html5. +    if nodeName == "input" and element.type and not element.type in ["radio", "checkbox"] +      return true +     nodeName in ["textarea", "select"] + +  # Embedded elements like Flash and quicktime players can obtain focus but cannot be programmatically +  # unfocused. +  isEmbed: (element) -> +    element.nodeName?.toLowerCase() in ["embed", "object"] + +  canEditElement: (element) -> +    element and (@isEditable(element) or @isEmbed element) + +  isActive: -> +    @userActivated or @canEditElement document.activeElement + +  generateKeyHandler: (type) -> +    (event) => +      return Mode.propagate unless @isActive() +      return handlerStack.passThrough unless type == "keydown" and KeyboardUtils.isEscape event +      # We're now exiting insert mode. +      if @canEditElement event.srcElement +        # Remove the focus so the user can't just get himself back into insert mode by typing in the same input +        # box. +        # NOTE(smblott, 2014/12/22) Including embeds for .blur() here is experimental.  It appears to be the +        # right thing to do for most common use cases.  However, it could also cripple flash-based sites and +        # games.  See discussion in #1211 and #1194. +        event.srcElement.blur() +      @userActivated = false +      @updateBadge() +      Mode.suppressPropagation + +  pickBadge: -> +    if @isActive() then "I" else "" + +  updateBadge: -> +    badge = @badge +    @badge = @pickBadge() +    Mode.setBadge() if badge != @badge +    Mode.propagate + +  activate: -> +    @userActivated = true +    @updateBadge() + +  constructor: -> +    super +      name: "insert" +      badge: @pickBadge() +      keydown: @generateKeyHandler "keydown" +      keypress: @generateKeyHandler "keypress" +      keyup: @generateKeyHandler "keyup" + +    handlerStack.push +      DOMActivate: => @updateBadge() +      focus: => @updateBadge() +      blur: => @updateBadge() + +root = exports ? window +root.InsertMode = InsertMode diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index f7ae3a76..6480d511 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -5,6 +5,7 @@  # "domReady".  # +insertMode = null  insertModeLock = null  findMode = false  findModeQuery = { rawQuery: "", matchCount: 0 } @@ -133,6 +134,9 @@ initializePreDomReady = ->      keypress: handlePassKeyEvent      keyup: -> true # Allow event to propagate. +  # Install insert mode. +  insertMode = new InsertMode() +    checkIfEnabledForUrl()    refreshCompletionKeys() @@ -192,9 +196,11 @@ initializeWhenEnabled = (newPassKeys) ->      # can't set handlers to grab the keys before us.      for type in ["keydown", "keypress", "keyup"]        do (type) -> installListener window, type, (event) -> handlerStack.bubbleEvent type, event -    installListener document, "focus", onFocusCapturePhase +    # installListener document, "focus", onFocusCapturePhase # No longer needed.      installListener document, "blur", onBlurCapturePhase      installListener document, "DOMActivate", onDOMActivate +    installListener document, "focus", onFocus +    installListener document, "blur", onBlur      enterInsertModeIfElementIsFocused()      installedListeners = true @@ -244,6 +250,8 @@ enterInsertModeIfElementIsFocused = ->      enterInsertModeWithoutShowingIndicator(document.activeElement)  onDOMActivate = (event) -> handlerStack.bubbleEvent 'DOMActivate', event +onFocus = (event) -> handlerStack.bubbleEvent 'focus', event +onBlur = (event) -> handlerStack.bubbleEvent 'blur', event  executePageCommand = (request) ->    return unless frameId == request.frameId @@ -325,6 +333,9 @@ extend window,      HUD.showForDuration("Yanked URL", 1000) +  enterInsertMode: -> +    insertMode?.activate() +    focusInput: (count) ->      # Focus the first input element on the page, and create overlays to highlight all the input elements, with      # the currently-focused element highlighted specially. Tabbing will shift focus to the next input element. @@ -602,14 +613,6 @@ isEditable = (target) ->    focusableElements.indexOf(nodeName) >= 0  # -# Enters insert mode and show an "Insert mode" message. Showing the UI is only useful when entering insert -# mode manually by pressing "i". In most cases we do not show any UI (enterInsertModeWithoutShowingIndicator) -# -window.enterInsertMode = (target) -> -  enterInsertModeWithoutShowingIndicator(target) -  # HUD.show("Insert mode") # With this proof-of-concept, visual feedback is given via badges on the browser popup. - -#  # We cannot count on 'focus' and 'blur' events to happen sequentially. For example, if blurring element A  # causes element B to come into focus, we may get "B focus" before "A blur". Thus we only leave insert mode  # when the last editable element that came into focus -- which insertModeLock points to -- has been blurred. @@ -618,40 +621,13 @@ window.enterInsertMode = (target) ->  # Note. This returns the truthiness of target, which is required by isInsertMode.  #  enterInsertModeWithoutShowingIndicator = (target) -> -  unless Mode.isInsert() -    insertModeLock = target -    # Install insert-mode handler.  Hereafter, all key events will be passed directly to the underlying page. -    # The current isInsertMode logic in the normal-mode handlers is now redundant.. -    new Mode -      name: "insert" -      badge: "I" -      keydown: "pass" -      keypress: "pass" -      keyup: "pass" -      onDeactivate: (event) -> -        if isEditable(event.srcElement) or isEmbed(event.srcElement) -          # Remove focus so the user can't just get himself back into insert mode by typing in the same input -          # box. -          # NOTE(smblott, 2014/12/22) Including embeds for .blur() etc. here is experimental.  It appears to be -          # the right thing to do for most common use cases.  However, it could also cripple flash-based sites and -          # games.  See discussion in #1211 and #1194. -          event.srcElement.blur() -        insertModeLock = null -        HUD.hide() +  return # Disabled.  exitInsertMode = (target) -> -  #  This assumes that, if insert mode is active at all, then it *must* be the current mode. That is, we -  #  cannot enter any other mode from insert mode. -  if Mode.isInsert() and (target == null or target == insertModeLock) -    Mode.popMode() +  return # Disabled.  isInsertMode = -> -  return true if Mode.isInsert() -  # Some sites (e.g. inbox.google.com) change the contentEditable attribute on the fly (see #1245); and -  # unfortunately, isEditable() is called *before* the change is made.  Therefore, we need to re-check whether -  # the active element is contentEditable. -  document.activeElement and document.activeElement.isContentEditable and -    enterInsertModeWithoutShowingIndicator document.activeElement +  return false # Disabled.  # should be called whenever rawQuery is modified.  updateFindModeQuery = -> @@ -705,6 +681,7 @@ updateFindModeQuery = ->      findModeQuery.matchCount = text.match(pattern)?.length  handleKeyCharForFindMode = (keyChar) -> +  console.log "xxxxxxxxxxxxxxx"    findModeQuery.rawQuery += keyChar    updateFindModeQuery()    performFindInPlace() diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 886a9ece..6f599dc7 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -7,7 +7,7 @@ class HandlerStack      @counter = 0      @passThrough = new Object() # Used only as a constant, distinct from any other value. -  genId: -> @counter = ++@counter & 0xffff +  genId: -> @counter = ++@counter    # Adds a handler to the stack. Returns a unique ID for that handler that can be used to remove it later.    push: (handler) -> diff --git a/manifest.json b/manifest.json index dca01b49..5f810d54 100644 --- a/manifest.json +++ b/manifest.json @@ -44,6 +44,7 @@               "content_scripts/scroller.js",               "content_scripts/marks.js",               "content_scripts/mode.js", +             "content_scripts/mode_insert.js",               "content_scripts/vimium_frontend.js"              ],        "css": ["content_scripts/vimium.css"], | 
