diff options
Diffstat (limited to 'content_scripts/mode_insert.coffee')
| -rw-r--r-- | content_scripts/mode_insert.coffee | 171 |
1 files changed, 83 insertions, 88 deletions
diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee index 5a0ac9eb..32994aef 100644 --- a/content_scripts/mode_insert.coffee +++ b/content_scripts/mode_insert.coffee @@ -1,107 +1,102 @@ -class InsertMode extends Mode - insertModeActive: false - insertModeLock: null - - # 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 not in ["radio", "checkbox"] - return true - nodeName in ["textarea", "select"] - - # Embedded elements like Flash and quicktime players can obtain focus. - isEmbed: (element) -> - element.nodeName?.toLowerCase() in ["embed", "object"] - - isFocusable: (element) -> - @isEditable(element) or @isEmbed element - - # Check whether insert mode is active. Also, activate insert mode if the current element is content - # editable (and the event is not suppressed). - isActiveOrActivate: (event) -> - return true if @insertModeActive - return false if event.suppressKeydownTrigger - # 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. - @activate() if document.activeElement?.isContentEditable - @insertModeActive - - activate: (target=null) -> - unless @insertModeActive - @insertModeActive = true - @insertModeLock = target - @badge = "I" - Mode.updateBadge() - - deactivate: -> - if @insertModeActive - @insertModeActive = false - @insertModeLock = null - @badge = "" - Mode.updateBadge() - - exit: (event) -> - if event?.source == ExitOnEscapeMode - element = event?.event?.srcElement - if element? and @isFocusable element +# 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 not in ["radio", "checkbox"] + return true + nodeName in ["textarea", "select"] + +# Embedded elements like Flash and quicktime players can obtain focus. +isEmbed =(element) -> + element.nodeName?.toLowerCase() in ["embed", "object"] + +isFocusable =(element) -> + isEditable(element) or isEmbed element + +class InsertMode extends ConstrainedMode + + constructor: (@insertModeLock=null) -> + super @insertModeLock, InsertMode, + name: "insert" + badge: "I" + keydown: (event) => @stopBubblingAndTrue + keypress: (event) => @stopBubblingAndTrue + keyup: (event) => @stopBubblingAndTrue + + @push + focus: (event, extra) => + handlerStack.alwaysContinueBubbling => + # Inform InsertModeTrigger that InsertMode is already active. + extra.insertModeActive = true + + Mode.updateBadge() + + exit: (event=null) -> + if event?.source == ExitOnEscapeMode and event?.event?.srcElement? + element = event.event.srcElement + if isFocusable element # 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. element.blur() - @deactivate() + super() +# Trigger insert mode: +# - On keydown event in a contentEditable element. +# - When a focusable element receives the focus. +# Can be suppressed by setting extra.suppressInsertModeTrigger. +class InsertModeTrigger extends Mode constructor: -> super - name: "insert" - keydown: (event) => - return @continueBubbling unless @isActiveOrActivate event - return @stopBubblingAndTrue unless KeyboardUtils.isEscape event - # We're in insert mode, and now exiting. - if event.srcElement? and @isFocusable 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() - @deactivate() - @suppressEvent - keypress: => if @insertModeActive then @stopBubblingAndTrue else @continueBubbling - keyup: => if @insertModeActive then @stopBubblingAndTrue else @continueBubbling + name: "insert-trigger" + keydown: (event, extra) => + handlerStack.alwaysContinueBubbling => + unless extra.suppressInsertModeTrigger? + # 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 check + # whether the active element is contentEditable. + new InsertMode() if document.activeElement?.isContentEditable @push - focus: (event) => - handlerStack.alwaysContinueBubbling => - if not @insertModeActive and @isFocusable event.target - @activate event.target - blur: (event) => + focus: (event, extra) => handlerStack.alwaysContinueBubbling => - if @insertModeActive and event.target == @insertModeLock - @deactivate() + unless extra.suppressInsertModeTrigger? + new InsertMode event.target if isFocusable event.target - # We may already have focussed something, so check, so check. - @activate document.activeElement if document.activeElement and @isFocusable document.activeElement + # We may already have focussed something, so check. + new InsertMode document.activeElement if document.activeElement and isFocusable document.activeElement - # Used to prevent keydown events from triggering insert mode (following find). - # FIXME(smblott) This is a hack. - @suppressKeydownTrigger: (event) -> - event.suppressKeydownTrigger = true + @suppress: (extra) -> + extra.suppressInsertModeTrigger = true -# Activate this mode to prevent a focused, editable element from triggering insert mode. -class InsertModeSuppressFocusTrigger extends Mode - constructor: -> - super {name: "suppress-insert-mode-focus-trigger"} - @push - focus: => @suppressEvent +# Disables InsertModeTrigger. Used by find mode to prevent unintentionally dropping into insert mode on +# focusable elements. +# If @element is provided, then don't block focus events, and block keydown events only on the indicated +# element. +class InsertModeBlocker extends SingletonMode + constructor: (singleton=InsertModeBlocker, @element=null, options={}) -> + options.name ||= "insert-blocker" + super singleton, options + + unless @element? + @push + focus: (event, extra) => + handlerStack.alwaysContinueBubbling => + InsertModeTrigger.suppress extra + + if @element?.isContentEditable + @push + keydown: (event, extra) => + handlerStack.alwaysContinueBubbling => + InsertModeTrigger.suppress extra if event.srcElement == @element root = exports ? window root.InsertMode = InsertMode -root.InsertModeSuppressFocusTrigger = InsertModeSuppressFocusTrigger +root.InsertModeTrigger = InsertModeTrigger +root.InsertModeBlocker = InsertModeBlocker |
