diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/dom_utils.coffee | 12 | ||||
| -rw-r--r-- | lib/find_mode_history.coffee | 50 | ||||
| -rw-r--r-- | lib/settings.coffee | 87 |
3 files changed, 119 insertions, 30 deletions
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 7c47179c..9658df2b 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -326,6 +326,18 @@ DomUtils = document.body.removeChild div coordinates + getSelectionFocusElement: -> + sel = window.getSelection() + if not sel.focusNode? + null + else if sel.focusNode == sel.anchorNode and sel.focusOffset == sel.anchorOffset + # The selection either *is* an element, or is inside an opaque element (eg. <input>). + sel.focusNode.childNodes[sel.focusOffset] + else if sel.focusNode.nodeType != sel.focusNode.ELEMENT_NODE + sel.focusNode.parentElement + else + sel.focusNode + # Get the text content of an element (and its descendents), but omit the text content of previously-visited # nodes. See #1514. # NOTE(smblott). This is currently O(N^2) (when called on N elements). An alternative would be to mark diff --git a/lib/find_mode_history.coffee b/lib/find_mode_history.coffee new file mode 100644 index 00000000..ff660bd2 --- /dev/null +++ b/lib/find_mode_history.coffee @@ -0,0 +1,50 @@ +# NOTE(mrmr1993): This is under lib/ since it is used by both content scripts and iframes from pages/. +# This implements find-mode query history (using the "findModeRawQueryList" setting) as a list of raw queries, +# most recent first. +FindModeHistory = + storage: chrome?.storage.local # Guard against chrome being undefined (in the HUD iframe). + key: "findModeRawQueryList" + max: 50 + rawQueryList: null + + init: -> + @isIncognitoMode = chrome?.extension.inIncognitoContext + + return unless @isIncognitoMode? # chrome is undefined in the HUD iframe during tests, so we do nothing. + + unless @rawQueryList + @rawQueryList = [] # Prevent repeated initialization. + @key = "findModeRawQueryListIncognito" if @isIncognitoMode + @storage.get @key, (items) => + unless chrome.runtime.lastError + @rawQueryList = items[@key] if items[@key] + if @isIncognitoMode and not items[@key] + # This is the first incognito tab, so we need to initialize the incognito-mode query history. + @storage.get "findModeRawQueryList", (items) => + unless chrome.runtime.lastError + @rawQueryList = items.findModeRawQueryList + @storage.set findModeRawQueryListIncognito: @rawQueryList + + chrome.storage.onChanged.addListener (changes, area) => + @rawQueryList = changes[@key].newValue if changes[@key] + + getQuery: (index = 0) -> + @rawQueryList[index] or "" + + saveQuery: (query) -> + if 0 < query.length + @rawQueryList = @refreshRawQueryList query, @rawQueryList + newSetting = {}; newSetting[@key] = @rawQueryList + @storage.set newSetting + # If there are any active incognito-mode tabs, then propagte this query to those tabs too. + unless @isIncognitoMode + @storage.get "findModeRawQueryListIncognito", (items) => + if not chrome.runtime.lastError and items.findModeRawQueryListIncognito + @storage.set + findModeRawQueryListIncognito: @refreshRawQueryList query, items.findModeRawQueryListIncognito + + refreshRawQueryList: (query, rawQueryList) -> + ([ query ].concat rawQueryList.filter (q) => q != query)[0..@max] + +root = exports ? window +root.FindModeHistory = FindModeHistory diff --git a/lib/settings.coffee b/lib/settings.coffee index c1caecd3..99a20963 100644 --- a/lib/settings.coffee +++ b/lib/settings.coffee @@ -1,5 +1,17 @@ +# A "setting" is a stored key/value pair. An "option" is a setting which has a default value and whose value +# can be changed on the options page. +# +# Option values which have never been changed by the user are in Settings.defaults. +# +# Settings whose values have been changed are: +# 1. stored either in chrome.storage.sync or in chrome.storage.local (but never both), and +# 2. cached in Settings.cache; on extension pages, Settings.cache uses localStorage (so it persists). +# +# In all cases except Settings.defaults, values are stored as jsonified strings. + Settings = + debug: false storage: chrome.storage.sync cache: {} isLoaded: false @@ -11,18 +23,21 @@ Settings = @cache = if Utils.isBackgroundPage() then localStorage else extend {}, localStorage @onLoaded() - @storage.get null, (items) => - unless chrome.runtime.lastError - @handleUpdateFromChromeStorage key, value for own key, value of items + chrome.storage.local.get null, (localItems) => + localItems = {} if chrome.runtime.lastError + @storage.get null, (syncedItems) => + unless chrome.runtime.lastError + @handleUpdateFromChromeStorage key, value for own key, value of extend localItems, syncedItems - chrome.storage.onChanged.addListener (changes, area) => - @propagateChangesFromChromeStorage changes if area == "sync" + chrome.storage.onChanged.addListener (changes, area) => + @propagateChangesFromChromeStorage changes if area == "sync" - @onLoaded() + @onLoaded() # 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() @@ -33,46 +48,40 @@ Settings = @handleUpdateFromChromeStorage key, change?.newValue for own key, change of changes handleUpdateFromChromeStorage: (key, value) -> + @log "handleUpdateFromChromeStorage: #{key}" # Note: value here is either null or a JSONified string. Therefore, even falsy settings values (like # false, 0 or "") are truthy here. Only null is falsy. if @shouldSyncKey key unless value and key of @cache and @cache[key] == value - defaultValue = @defaults[key] - defaultValueJSON = JSON.stringify defaultValue - - if value and value != defaultValueJSON - # Key/value has been changed to a non-default value. - @cache[key] = value - @performPostUpdateHook key, JSON.parse value - else - # The key has been reset to its default value. - delete @cache[key] if key of @cache - @performPostUpdateHook key, defaultValue + value ?= JSON.stringify @defaults[key] + @set key, JSON.parse(value), false get: (key) -> console.log "WARNING: Settings have not loaded yet; using the default value for #{key}." unless @isLoaded if key of @cache and @cache[key]? then JSON.parse @cache[key] else @defaults[key] - set: (key, value) -> - # Don't store the value if it is equal to the default, so we can change the defaults in the future. - if JSON.stringify(value) == JSON.stringify @defaults[key] - @clear key - else - jsonValue = JSON.stringify value - @cache[key] = jsonValue - if @shouldSyncKey key - setting = {}; setting[key] = jsonValue + 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 - @performPostUpdateHook key, value + 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) -> - delete @cache[key] if @has key - @storage.remove key if @shouldSyncKey key - @performPostUpdateHook key, @get 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 @@ -80,6 +89,10 @@ Settings = postUpdateHooks: {} performPostUpdateHook: (key, value) -> @postUpdateHooks[key]? value + # For development only. + log: (args...) -> + console.log "settings:", args... if @debug + # Default values for all settings. defaults: scrollStepSize: 60 @@ -170,5 +183,19 @@ if Utils.isBackgroundPage() rawQuery = Settings.get "findModeRawQuery" chrome.storage.local.set findModeRawQueryList: (if rawQuery then [ rawQuery ] else []) + # Migration (after 1.51, 2015/6/17). + # Copy options with non-default values (and which are not in synced storage) to chrome.storage.local; + # thereby making these settings accessible within content scripts. + do (migrationKey = "copyNonDefaultsToChromeStorage-20150717") -> + unless localStorage[migrationKey] + chrome.storage.sync.get null, (items) -> + unless chrome.runtime.lastError + updates = {} + for own key of localStorage + if Settings.shouldSyncKey(key) and not items[key] + updates[key] = localStorage[key] + chrome.storage.local.set updates, -> + localStorage[migrationKey] = not chrome.runtime.lastError + root = exports ? window root.Settings = Settings |
