aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/mode_insert.coffee
blob: 7f1d5ddc480bcca5a8a3cf6a8594c92206b4d5fe (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
# This mode is installed when insert mode is active.
class InsertMode extends Mode
  constructor: (@insertModeLock = null) ->
    super
      name: "insert"
      badge: "I"
      singleton: InsertMode
      keydown: (event) => @stopBubblingAndTrue
      keypress: (event) => @stopBubblingAndTrue
      keyup: (event) => @stopBubblingAndTrue
      exitOnEscape: true
      exitOnBlur: @insertModeLock

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

# Automatically trigger insert mode:
#   - On a keydown event in a contentEditable element.
#   - When a focusable element receives the focus.
#
# The trigger can be suppressed via triggerSuppressor; see InsertModeBlocker, below.
# This mode is permanently installed fairly low down on the handler stack.
class InsertModeTrigger extends Mode
  constructor: ->
    super
      name: "insert-trigger"
      keydown: (event) =>
        triggerSuppressor.unlessSuppressed =>
          # 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.
          return @continueBubbling unless document.activeElement?.isContentEditable
          new InsertMode document.activeElement
          @stopBubblingAndTrue

    @push
      focus: (event) =>
        triggerSuppressor.unlessSuppressed =>
          return unless DomUtils.isFocusable event.target
          new InsertMode event.target

    # We may already have focussed an input, so check.
    if document.activeElement and DomUtils.isEditable document.activeElement
      new InsertMode document.activeElement

# Used by InsertModeBlocker to suppress InsertModeTrigger; see below.
triggerSuppressor = new Utils.Suppressor true

# Suppresses InsertModeTrigger.  This is used by various modes (usually by inheritance) to prevent
# unintentionally dropping into insert mode on focusable elements.
class InsertModeBlocker extends Mode
  constructor: (options = {}) ->
    triggerSuppressor.suppress()
    options.name ||= "insert-blocker"
    super options
    @onExit -> triggerSuppressor.unsuppress()

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