   # Called after @cache has been initialized.  On extension pages, this will be called twice, but that does
   # not matter because it's idempotent.
   onLoaded: ->
+    @log "onLoaded: #{@onLoadedCallbacks.length} callback(s)"
     @isLoaded = true
     callback() while callback = @onLoadedCallbacks.pop()
 

# In all cases except Settings.defaults, values are stored as jsonified strings.

Settings =
  storage: chrome.storage.sync
  cache: {}
  isLoaded: false


  set: (key, value, shouldSetInSyncedStorage = true) ->
    @cache[key] = JSON.stringify value
    @log "set: #{key} (length=#{@cache[key].length}, shouldSetInSyncedStorage=#{shouldSetInSyncedStorage})"
    if @shouldSyncKey key
      if shouldSetInSyncedStorage
        setting = {}; setting[key] = @cache[key]
        @log "   chrome.storage.sync.set(#{key})"
        @storage.set setting
      if Utils.isBackgroundPage()
        # Remove options installed by the "copyNonDefaultsToChromeStorage-20150717" migration; see below.
        @log "   chrome.storage.local.remove(#{key})"
        chrome.storage.local.remove key
    @performPostUpdateHook key, value

  clear: (key) ->
    @log "clear: #{key}"
    @set key, @defaults[key]

  has: (key) -> key of @cache

  use: (key, callback) ->
    @log "use: #{key} (isLoaded=#{@isLoaded})"
    invokeCallback = => callback @get key
    if @isLoaded then invokeCallback() else @onLoadedCallbacks.push invokeCallback

  # For settings which require action when their value changes, add hooks to this object.
  postUpdateHooks: {}
  performPostUpdateHook: (key, value) -> @postUpdateHooks[key]? value

  # For development only.
  log: (args...) ->
    console.log "settings:", args... if @debug

  # Default values for all settings.
  defaults:
