aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/mode_insert.coffee
blob: 51ef3d8baf1782426dc905a9bc638cbb78ee329c (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
# 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

# This mode is installed when insert mode is active.
class InsertMode extends ConstrainedMode
  constructor: (@insertModeLock=null) ->
    super @insertModeLock, InsertMode,
      name: "insert"
      badge: "I"
      keydown: (event) => @stopBubblingAndTrue
      keypress: (event) => @stopBubblingAndTrue
      keyup: (event) => @stopBubblingAndTrue

  exit: (extra=null) ->
    if extra?.source == ExitOnEscapeMode and extra?.event?.srcElement?
      element = extra.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()
    super()

# Trigger insert mode:
#   - On a keydown event in a contentEditable element.
#   - When a focusable element receives the focus.
# Can be suppressed by setting extra.suppressInsertModeTrigger.
#
# This mode is permanently installed fairly low down on the handler stack.
class InsertModeTrigger extends Mode
  constructor: ->
    super
      name: "insert-trigger"
      keydown: (event, extra) =>
        @alwaysContinueBubbling =>
          unless extra.suppressInsertModeTrigger?
            # Some sites (e.g. inbox.google.com) change the contentEditable attribute on the fly (see #1245);
            # and unfortunately, the focus event happens *before* the change is made.  Therefore, we need to
            # check again whether the active element is contentEditable.
            new InsertMode() if document.activeElement?.isContentEditable

    @push
      focus: (event, extra) =>
        @alwaysContinueBubbling =>
          unless extra.suppressInsertModeTrigger?
            new InsertMode event.target if isFocusable event.target

    # We may already have focussed something, so check.
    new InsertMode document.activeElement if document.activeElement and isFocusable document.activeElement

  @suppress: (extra) ->
    extra.suppressInsertModeTrigger = true

# Disables InsertModeTrigger.  Used by find mode and findFocus to prevent unintentionally dropping into insert
# mode on focusable elements.
# If @element is provided, then don't suppress focus events, and suppress keydown events only on @element.
class InsertModeBlocker extends SingletonMode
  constructor: (singleton=InsertModeBlocker, @element=null, options={}) ->
    options.name ||= "insert-blocker"
    super singleton, options

    unless @element?
      @push
        focus: (event, extra) =>
          @alwaysContinueBubbling =>
            InsertModeTrigger.suppress extra

    if @element?.isContentEditable
      @push
        keydown: (event, extra) =>
          @alwaysContinueBubbling =>
            InsertModeTrigger.suppress extra if event.srcElement == @element

root = exports ? window
root.InsertMode = InsertMode
root.InsertModeTrigger = InsertModeTrigger
root.InsertModeBlocker = InsertModeBlocker