aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/mode.coffee
blob: a19c3df08f0820e011ff710d708cd1817a441c5b (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
count = 0

class Mode
  # Static members.
  @modes: []
  @current: -> Mode.modes[0]

  # Constants; readable shortcuts for event-handler return values.
  continueBubbling: true
  suppressEvent: false
  stopBubblingAndTrue: handlerStack.stopBubblingAndTrue
  stopBubblingAndFalse: handlerStack.stopBubblingAndFalse

  # Default values.
  name: ""
  badge: ""
  keydown: (event) => @continueBubbling
  keypress: (event) => @continueBubbling
  keyup: (event) => @continueBubbling

  constructor: (options) ->
    Mode.modes.unshift @
    extend @, options
    @count = ++count
    console.log @count, "create:", @name

    @handlers = []
    @handlers.push handlerStack.push
      keydown: @keydown
      keypress: @keypress
      keyup: @keyup
      updateBadge: (badge) => handlerStack.alwaysContinueBubbling => @chooseBadge badge

  exit: ->
    console.log @count, "exit:", @name
    handlerStack.remove handlerId for handlerId in @handlers
    Mode.modes = Mode.modes.filter (mode) => mode != @
    Mode.updateBadge()

  # The badge is chosen by bubbling an "updateBadge" event down the handler stack allowing each mode the
  # opportunity to choose a badge.  chooseBadge, here, is the default: choose the current mode's badge unless
  # one has already been chosen.  This is overridden in sub-classes.
  chooseBadge: (badge) ->
    badge.badge ||= @badge

  # Static method.  Used externally and internally to initiate bubbling of an updateBadge event and to send
  # the resulting badge to the background page.  We only update the badge if this document has the focus, and
  # the document's body isn't a frameset.
  @updateBadge: ->
    if document.hasFocus()
      unless document.body?.tagName.toLowerCase() == "frameset"
        badge = {badge: ""}
        handlerStack.bubbleEvent "updateBadge", badge
        chrome.runtime.sendMessage({ handler: "setBadge", badge: badge.badge })

  # Temporarily install a mode.
  @runIn: (mode, func) ->
    mode = new mode()
    func()
    mode.exit()

# A SingletonMode is a Mode of which there may be at most one instance (of @singleton) active at any one time.
# New instances cancel previous instances on startup.
class SingletonMode extends Mode
  @instances: {}

  exit: ->
    delete SingletonMode.instances[@singleton]
    super()

  constructor: (@singleton, options={}) ->
    SingletonMode.instances[@singleton].exit() if SingletonMode.instances[@singleton]
    SingletonMode.instances[@singleton] = @
    super options

# MultiMode is a collection of modes which are installed or uninstalled together.
class MultiMode extends Mode
  constructor: (modes...) ->
    @modes = (new mode() for mode in modes)
    super {name: "multimode"}

  exit: ->
    mode.exit() for mode in modes

root = exports ? window
root.Mode = Mode
root.SingletonMode = SingletonMode
root.MultiMode = MultiMode