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  | 
