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"], |
