aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/mode_insert.coffee
blob: 5a0ac9eba8a63ebf155aa6ab87ac3a188d8282d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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
        # 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()

  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

    @push
      focus: (event) =>
        handlerStack.alwaysContinueBubbling =>
          if not @insertModeActive and @isFocusable event.target
            @activate event.target
      blur: (event) =>
        handlerStack.alwaysContinueBubbling =>
          if @insertModeActive and event.target == @insertModeLock
            @deactivate()

    # We may already have focussed something, so check, so check.
    @activate 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

# 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

root = exports ? window
root.InsertMode = InsertMode
root.InsertModeSuppressFocusTrigger = InsertModeSuppressFocusTrigger