aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/mode_insert.coffee
blob: ccd93870bca6e679eb83cb91f075008f64204ee0 (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
class InsertMode extends Mode
  isInsertMode: false

  # 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 but cannot be programmatically
  # unfocused.
  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.
  isActive: ->
    return true if @isInsertMode
    # 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
    @isInsertMode

  activate: ->
    unless @isInsertMode
      @isInsertMode = true
      @badge = "I"
      Mode.updateBadge()

  deactivate: ->
    @isInsertMode = false
    @badge = ""
    Mode.updateBadge()

  generateKeyHandler: (type) ->
    (event) =>
      return @continueBubbling unless @isActive()
      return @stopBubblingAndTrue unless type == "keydown" and KeyboardUtils.isEscape event
      # We're now exiting insert mode.
      if @isEditable(event.srcElement) or @isEmbed 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

  constructor: ->
    super
      name: "insert"
      keydown: @generateKeyHandler "keydown"
      keypress: @generateKeyHandler "keypress"
      keyup: @generateKeyHandler "keyup"

    @handlers.push handlerStack.push
      focus: (event) =>
        handlerStack.alwaysPropagate =>
          if not @isInsertMode and @isFocusable event.target
            @activate()
      blur: (event) =>
        handlerStack.alwaysPropagate =>
          if @isInsertMode and @isFocusable event.target
            @deactivate()

    # We may already have been dropped into insert mode.  So check.
    Mode.updateBadge()

root = exports ? window
root.InsertMode = InsertMode