diff options
| author | Stephen Blott | 2015-06-25 05:28:01 +0100 | 
|---|---|---|
| committer | Stephen Blott | 2015-06-25 05:28:01 +0100 | 
| commit | cb42acfc59c10df4c0fd56f068c83c7509d5a237 (patch) | |
| tree | f5caba89ec0472f42631d653ce21c55a242ecd03 /lib | |
| parent | c404f6799bb748754f91b1d515108706a024dce1 (diff) | |
| parent | e5faeff7d48a206a080a56a6541963d2d3c86da7 (diff) | |
| download | vimium-cb42acfc59c10df4c0fd56f068c83c7509d5a237.tar.bz2 | |
Merge branch 'hud-iframe-input-with-store-all-settings'
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 | 
