Enhance: make prefs use storage.sync
This commit is contained in:
		
							parent
							
								
									282bdf7706
								
							
						
					
					
						commit
						874a2da33e
					
				| 
						 | 
					@ -312,6 +312,21 @@ window.addEventListener('storageReady', function _() {
 | 
				
			||||||
  updateAPI(null, prefs.get('exposeIframes'));
 | 
					  updateAPI(null, prefs.get('exposeIframes'));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// register hotkeys
 | 
				
			||||||
 | 
					if (FIREFOX && browser.commands && browser.commands.update) {
 | 
				
			||||||
 | 
					  const hotkeyPrefs = Object.keys(prefs.defaults).filter(k => k.startsWith('hotkey.'));
 | 
				
			||||||
 | 
					  this.subscribe(hotkeyPrefs, (name, value) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      name = name.split('.')[1];
 | 
				
			||||||
 | 
					      if (value.trim()) {
 | 
				
			||||||
 | 
					        browser.commands.update({name, shortcut: value}).catch(ignoreChromeError);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        browser.commands.reset(name).catch(ignoreChromeError);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {}
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// *************************************************************************
 | 
					// *************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function webNavigationListener(method, {url, tabId, frameId}) {
 | 
					function webNavigationListener(method, {url, tabId, frameId}) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										241
									
								
								js/prefs.js
									
									
									
									
									
								
							
							
						
						
									
										241
									
								
								js/prefs.js
									
									
									
									
									
								
							| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line no-var
 | 
					// eslint-disable-next-line no-var
 | 
				
			||||||
var prefs = new function Prefs() {
 | 
					var prefs = (() => {
 | 
				
			||||||
  const defaults = {
 | 
					  const defaults = {
 | 
				
			||||||
    'openEditInWindow': false,      // new editor opens in a own browser window
 | 
					    'openEditInWindow': false,      // new editor opens in a own browser window
 | 
				
			||||||
    'windowPosition': {},           // detached window position
 | 
					    'windowPosition': {},           // detached window position
 | 
				
			||||||
| 
						 | 
					@ -98,28 +98,27 @@ var prefs = new function Prefs() {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  const values = deepCopy(defaults);
 | 
					  const values = deepCopy(defaults);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const affectsIcon = [
 | 
					 | 
				
			||||||
    'show-badge',
 | 
					 | 
				
			||||||
    'disableAll',
 | 
					 | 
				
			||||||
    'badgeDisabled',
 | 
					 | 
				
			||||||
    'badgeNormal',
 | 
					 | 
				
			||||||
    'iconset',
 | 
					 | 
				
			||||||
  ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onChange = {
 | 
					  const onChange = {
 | 
				
			||||||
    any: new Set(),
 | 
					    any: new Set(),
 | 
				
			||||||
    specific: new Map(),
 | 
					    specific: new Map(),
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // coalesce multiple pref changes in broadcast
 | 
					  const initializing = promisify(chrome.storage.sync.get.bind(chrome.storage.sync))('settings')
 | 
				
			||||||
  let broadcastPrefs = {};
 | 
					    .then(result => setAll(result.settings, true));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Object.defineProperties(this, {
 | 
					  chrome.storage.onChanged.addListener((changes, area) => {
 | 
				
			||||||
    defaults: {value: deepCopy(defaults)}
 | 
					    if (area !== 'sync' || !changes.settings || !changes.settings.newValue) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    initializing.then(() => setAll(changes.settings.newValue, true));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Object.assign(Prefs.prototype, {
 | 
					  // coalesce multiple pref changes in broadcast
 | 
				
			||||||
 | 
					  // let changes = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    initializing,
 | 
				
			||||||
 | 
					    defaults,
 | 
				
			||||||
    get(key, defaultValue) {
 | 
					    get(key, defaultValue) {
 | 
				
			||||||
      if (key in values) {
 | 
					      if (key in values) {
 | 
				
			||||||
        return values[key];
 | 
					        return values[key];
 | 
				
			||||||
| 
						 | 
					@ -132,61 +131,11 @@ var prefs = new function Prefs() {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      console.warn("No default preference for '%s'", key);
 | 
					      console.warn("No default preference for '%s'", key);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					 | 
				
			||||||
    getAll() {
 | 
					    getAll() {
 | 
				
			||||||
      return deepCopy(values);
 | 
					      return deepCopy(values);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    set,
 | 
				
			||||||
    set(key, value, {broadcast = true, sync = true, fromBroadcast} = {}) {
 | 
					    reset: key => set(key, deepCopy(defaults[key])),
 | 
				
			||||||
      const oldValue = values[key];
 | 
					 | 
				
			||||||
      switch (typeof defaults[key]) {
 | 
					 | 
				
			||||||
        case typeof value:
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case 'string':
 | 
					 | 
				
			||||||
          value = String(value);
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case 'number':
 | 
					 | 
				
			||||||
          value |= 0;
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case 'boolean':
 | 
					 | 
				
			||||||
          value = value === true || value === 'true';
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      values[key] = value;
 | 
					 | 
				
			||||||
      const hasChanged = !equal(value, oldValue);
 | 
					 | 
				
			||||||
      if (!fromBroadcast || FIREFOX_NO_DOM_STORAGE) {
 | 
					 | 
				
			||||||
        localStorage[key] = typeof defaults[key] === 'object'
 | 
					 | 
				
			||||||
          ? JSON.stringify(value)
 | 
					 | 
				
			||||||
          : value;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (!fromBroadcast && broadcast && hasChanged) {
 | 
					 | 
				
			||||||
        this.broadcast(key, value, {sync});
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (hasChanged) {
 | 
					 | 
				
			||||||
        const specific = onChange.specific.get(key);
 | 
					 | 
				
			||||||
        if (typeof specific === 'function') {
 | 
					 | 
				
			||||||
          specific(key, value);
 | 
					 | 
				
			||||||
        } else if (specific instanceof Set) {
 | 
					 | 
				
			||||||
          for (const listener of specific.values()) {
 | 
					 | 
				
			||||||
            listener(key, value);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        for (const listener of onChange.any.values()) {
 | 
					 | 
				
			||||||
          listener(key, value);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    reset: key => this.set(key, deepCopy(defaults[key])),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    broadcast(key, value, {sync = true} = {}) {
 | 
					 | 
				
			||||||
      broadcastPrefs[key] = value;
 | 
					 | 
				
			||||||
      debounce(doBroadcast);
 | 
					 | 
				
			||||||
      if (sync) {
 | 
					 | 
				
			||||||
        debounce(doSyncSet);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    subscribe(keys, listener) {
 | 
					    subscribe(keys, listener) {
 | 
				
			||||||
      // keys:     string[] ids
 | 
					      // keys:     string[] ids
 | 
				
			||||||
      //           or a falsy value to subscribe to everything
 | 
					      //           or a falsy value to subscribe to everything
 | 
				
			||||||
| 
						 | 
					@ -206,7 +155,6 @@ var prefs = new function Prefs() {
 | 
				
			||||||
        onChange.any.add(listener);
 | 
					        onChange.any.add(listener);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					 | 
				
			||||||
    unsubscribe(keys, listener) {
 | 
					    unsubscribe(keys, listener) {
 | 
				
			||||||
      if (keys) {
 | 
					      if (keys) {
 | 
				
			||||||
        for (const key of keys) {
 | 
					        for (const key of keys) {
 | 
				
			||||||
| 
						 | 
					@ -224,134 +172,75 @@ var prefs = new function Prefs() {
 | 
				
			||||||
        onChange.all.remove(listener);
 | 
					        onChange.all.remove(listener);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {
 | 
					  function promisify(fn) {
 | 
				
			||||||
    const importFromBG = () =>
 | 
					    return (...args) =>
 | 
				
			||||||
      API.getPrefs().then(prefs => {
 | 
					      new Promise((resolve, reject) => {
 | 
				
			||||||
        for (const id in prefs) {
 | 
					        fn(...args, (...result) => {
 | 
				
			||||||
          this.set(id, prefs[id], {fromBroadcast: true});
 | 
					          if (chrome.runtime.lastError) {
 | 
				
			||||||
 | 
					            reject(chrome.runtime.lastError);
 | 
				
			||||||
 | 
					          } else if (result.length === 0) {
 | 
				
			||||||
 | 
					            resolve(undefined);
 | 
				
			||||||
 | 
					          } else if (result.length === 1) {
 | 
				
			||||||
 | 
					            resolve(result[0]);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            resolve(result);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    // Unlike chrome.storage or messaging, HTML5 localStorage is synchronous and always ready,
 | 
					      });
 | 
				
			||||||
    // so we'll mirror the prefs to avoid using the wrong defaults during the startup phase
 | 
					  }
 | 
				
			||||||
    const importFromLocalStorage = () => {
 | 
					
 | 
				
			||||||
      forgetOutdatedDefaults(localStorage);
 | 
					  function setAll(settings, synced) {
 | 
				
			||||||
      for (const key in defaults) {
 | 
					    for (const [key, value] of Object.entries(settings)) {
 | 
				
			||||||
        const defaultValue = defaults[key];
 | 
					      set(key, value, synced);
 | 
				
			||||||
        let value = localStorage[key];
 | 
					    }
 | 
				
			||||||
        if (typeof value === 'string') {
 | 
					  }
 | 
				
			||||||
          switch (typeof defaultValue) {
 | 
					
 | 
				
			||||||
            case 'boolean':
 | 
					  function set(key, value, synced = false) {
 | 
				
			||||||
              value = value.toLowerCase() === 'true';
 | 
					    const oldValue = values[key];
 | 
				
			||||||
 | 
					    switch (typeof defaults[key]) {
 | 
				
			||||||
 | 
					      case typeof value:
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 'string':
 | 
				
			||||||
 | 
					        value = String(value);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case 'number':
 | 
					      case 'number':
 | 
				
			||||||
        value |= 0;
 | 
					        value |= 0;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
            case 'object':
 | 
					      case 'boolean':
 | 
				
			||||||
              value = tryJSONparse(value) || defaultValue;
 | 
					        value = value === true || value === 'true';
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        } else if (FIREFOX_NO_DOM_STORAGE && BG) {
 | 
					    if (equal(value, oldValue)) {
 | 
				
			||||||
          value = BG.localStorage[key];
 | 
					      return;
 | 
				
			||||||
          value = value === undefined ? defaultValue : value;
 | 
					 | 
				
			||||||
          localStorage[key] = value;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          value = defaultValue;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        if (BG === window) {
 | 
					 | 
				
			||||||
          // when in bg page, .set() will write to localStorage
 | 
					 | 
				
			||||||
          this.set(key, value, {broadcast: false, sync: false});
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
    values[key] = value;
 | 
					    values[key] = value;
 | 
				
			||||||
        }
 | 
					    emitChange(key, value);
 | 
				
			||||||
      }
 | 
					    if (synced) {
 | 
				
			||||||
      return Promise.resolve();
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    (FIREFOX_NO_DOM_STORAGE && !BG ? importFromBG() : importFromLocalStorage()).then(() => {
 | 
					 | 
				
			||||||
      if (BG && BG !== window) return;
 | 
					 | 
				
			||||||
      if (BG === window) {
 | 
					 | 
				
			||||||
        affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false}));
 | 
					 | 
				
			||||||
        chromeSync.getValue('settings').then(settings => importFromSync.call(this, settings));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      chrome.storage.onChanged.addListener((changes, area) => {
 | 
					 | 
				
			||||||
        if (area === 'sync' && 'settings' in changes) {
 | 
					 | 
				
			||||||
          importFromSync.call(this, changes.settings.newValue);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // any access to chrome API takes time due to initialization of bindings
 | 
					 | 
				
			||||||
  window.addEventListener('load', function _() {
 | 
					 | 
				
			||||||
    window.removeEventListener('load', _);
 | 
					 | 
				
			||||||
    chrome.runtime.onMessage.addListener(msg => {
 | 
					 | 
				
			||||||
      if (msg.prefs) {
 | 
					 | 
				
			||||||
        for (const id in msg.prefs) {
 | 
					 | 
				
			||||||
          prefs.set(id, msg.prefs[id], {fromBroadcast: true});
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // register hotkeys
 | 
					 | 
				
			||||||
  if (FIREFOX && (browser.commands || {}).update) {
 | 
					 | 
				
			||||||
    const hotkeyPrefs = Object.keys(values).filter(k => k.startsWith('hotkey.'));
 | 
					 | 
				
			||||||
    this.subscribe(hotkeyPrefs, (name, value) => {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        name = name.split('.')[1];
 | 
					 | 
				
			||||||
        if (value.trim()) {
 | 
					 | 
				
			||||||
          browser.commands.update({name, shortcut: value}).catch(ignoreChromeError);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          browser.commands.reset(name).catch(ignoreChromeError);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } catch (e) {}
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function doBroadcast() {
 | 
					 | 
				
			||||||
    if (BG && BG === window && !BG.dbExec.initialized) {
 | 
					 | 
				
			||||||
      window.addEventListener('storageReady', function _() {
 | 
					 | 
				
			||||||
        window.removeEventListener('storageReady', _);
 | 
					 | 
				
			||||||
        doBroadcast();
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const affects = {
 | 
					    // changes[key] = value;
 | 
				
			||||||
      all: 'disableAll' in broadcastPrefs
 | 
					    debounce(syncPrefs);
 | 
				
			||||||
        || 'exposeIframes' in broadcastPrefs,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    if (!affects.all) {
 | 
					 | 
				
			||||||
      for (const key in broadcastPrefs) {
 | 
					 | 
				
			||||||
        affects.icon = affects.icon || affectsIcon.includes(key);
 | 
					 | 
				
			||||||
        affects.popup = affects.popup || key.startsWith('popup');
 | 
					 | 
				
			||||||
        affects.editor = affects.editor || key.startsWith('editor');
 | 
					 | 
				
			||||||
        affects.manager = affects.manager || key.startsWith('manage');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    notifyAllTabs({method: 'prefChanged', prefs: broadcastPrefs, affects});
 | 
					 | 
				
			||||||
    broadcastPrefs = {};
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function doSyncSet() {
 | 
					  function emitChange(key, value) {
 | 
				
			||||||
    chromeSync.setValue('settings', values);
 | 
					    const specific = onChange.specific.get(key);
 | 
				
			||||||
 | 
					    if (typeof specific === 'function') {
 | 
				
			||||||
 | 
					      specific(key, value);
 | 
				
			||||||
 | 
					    } else if (specific instanceof Set) {
 | 
				
			||||||
 | 
					      for (const listener of specific.values()) {
 | 
				
			||||||
 | 
					        listener(key, value);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  function importFromSync(synced = {}) {
 | 
					 | 
				
			||||||
    forgetOutdatedDefaults(synced);
 | 
					 | 
				
			||||||
    for (const key in defaults) {
 | 
					 | 
				
			||||||
      if (key in synced) {
 | 
					 | 
				
			||||||
        this.set(key, synced[key], {sync: false});
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    for (const listener of onChange.any.values()) {
 | 
				
			||||||
 | 
					      listener(key, value);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function forgetOutdatedDefaults(storage) {
 | 
					  function syncPrefs() {
 | 
				
			||||||
    // our linter runs as a worker so we can reduce the delay and forget the old default values
 | 
					    // FIXME: we always set the entire object? Ideally, this should only use `changes`.
 | 
				
			||||||
    if (Number(storage['editor.lintDelay']) === 500) delete storage['editor.lintDelay'];
 | 
					    chrome.sync.set('settings', values);
 | 
				
			||||||
    if (Number(storage['editor.lintReportDelay']) === 4500) delete storage['editor.lintReportDelay'];
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function equal(a, b) {
 | 
					  function equal(a, b) {
 | 
				
			||||||
| 
						 | 
					@ -383,7 +272,7 @@ var prefs = new function Prefs() {
 | 
				
			||||||
      !Array.from(navigator.plugins).some(p => p.name === 'Shockwave Flash')
 | 
					      !Array.from(navigator.plugins).some(p => p.name === 'Shockwave Flash')
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Accepts an array of pref names (values are fetched via prefs.get)
 | 
					// Accepts an array of pref names (values are fetched via prefs.get)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user