aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/dom_utils.coffee12
-rw-r--r--lib/find_mode_history.coffee50
-rw-r--r--lib/settings.coffee88
-rw-r--r--lib/utils.coffee9
4 files changed, 125 insertions, 34 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 842f7618..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
@@ -150,6 +163,7 @@ Settings =
settingsVersion: Utils.getCurrentVersion()
helpDialog_showAdvancedCommands: false
+ optionsPage_showAdvancedOptions: false
Settings.init()
@@ -169,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
diff --git a/lib/utils.coffee b/lib/utils.coffee
index 93045f32..d4beff03 100644
--- a/lib/utils.coffee
+++ b/lib/utils.coffee
@@ -323,10 +323,11 @@ class SimpleCache
null
rotate: (force = false) ->
- if force or @entries < Object.keys(@cache).length or @expiry < new Date() - @lastRotation
- @lastRotation = new Date()
- @previous = @cache
- @cache = {}
+ Utils.nextTick =>
+ if force or @entries < Object.keys(@cache).length or @expiry < new Date() - @lastRotation
+ @lastRotation = new Date()
+ @previous = @cache
+ @cache = {}
clear: ->
@rotate true