Merge branch 'dev-private-prefs' into dev-exclusions
This commit is contained in:
commit
7eba890a21
|
@ -153,6 +153,14 @@ navigatorUtil.onUrlChange(({url, tabId, frameId}) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
prefs.subscribe([
|
||||||
|
'show-badge',
|
||||||
|
'disableAll',
|
||||||
|
'badgeDisabled',
|
||||||
|
'badgeNormal',
|
||||||
|
'iconset',
|
||||||
|
], () => debounce(updateAllTabsIcon));
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
chrome.runtime.onInstalled.addListener(({reason}) => {
|
chrome.runtime.onInstalled.addListener(({reason}) => {
|
||||||
if (reason !== 'update') return;
|
if (reason !== 'update') return;
|
||||||
|
@ -211,11 +219,10 @@ if (chrome.contextMenus) {
|
||||||
}
|
}
|
||||||
item = Object.assign({id}, item);
|
item = Object.assign({id}, item);
|
||||||
delete item.presentIf;
|
delete item.presentIf;
|
||||||
const prefValue = prefs.readOnlyValues[id];
|
|
||||||
item.title = chrome.i18n.getMessage(item.title);
|
item.title = chrome.i18n.getMessage(item.title);
|
||||||
if (!item.type && typeof prefValue === 'boolean') {
|
if (!item.type && typeof prefs.defaults[id] === 'boolean') {
|
||||||
item.type = 'checkbox';
|
item.type = 'checkbox';
|
||||||
item.checked = prefValue;
|
item.checked = prefs.get(id);
|
||||||
}
|
}
|
||||||
if (!item.contexts) {
|
if (!item.contexts) {
|
||||||
item.contexts = ['browser_action'];
|
item.contexts = ['browser_action'];
|
||||||
|
@ -239,7 +246,7 @@ if (chrome.contextMenus) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const keys = Object.keys(contextMenus);
|
const keys = Object.keys(contextMenus);
|
||||||
prefs.subscribe(keys.filter(id => typeof prefs.readOnlyValues[id] === 'boolean'), toggleCheckmark);
|
prefs.subscribe(keys.filter(id => typeof prefs.defaults[id] === 'boolean'), toggleCheckmark);
|
||||||
prefs.subscribe(keys.filter(id => contextMenus[id].presentIf), togglePresence);
|
prefs.subscribe(keys.filter(id => contextMenus[id].presentIf), togglePresence);
|
||||||
createContextMenus(keys);
|
createContextMenus(keys);
|
||||||
}
|
}
|
||||||
|
@ -307,6 +314,21 @@ window.addEventListener('storageReady', function _() {
|
||||||
|
|
||||||
// FIXME: implement exposeIframes in apply.js
|
// FIXME: implement exposeIframes in apply.js
|
||||||
|
|
||||||
|
// register hotkeys
|
||||||
|
if (FIREFOX && browser.commands && browser.commands.update) {
|
||||||
|
const hotkeyPrefs = Object.keys(prefs.defaults).filter(k => k.startsWith('hotkey.'));
|
||||||
|
prefs.subscribe(hotkeyPrefs, (name, value) => {
|
||||||
|
try {
|
||||||
|
name = name.split('.')[1];
|
||||||
|
if (value.trim()) {
|
||||||
|
browser.commands.update({name, shortcut: value});
|
||||||
|
} else {
|
||||||
|
browser.commands.reset(name);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function webNavUsercssInstallerFF(data) {
|
function webNavUsercssInstallerFF(data) {
|
||||||
const {tabId} = data;
|
const {tabId} = data;
|
||||||
Promise.all([
|
Promise.all([
|
||||||
|
@ -447,3 +469,9 @@ function onRuntimeMessage(msg, sender) {
|
||||||
const context = {msg, sender};
|
const context = {msg, sender};
|
||||||
return fn.apply(context, msg.args);
|
return fn.apply(context, msg.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateAllTabsIcon() {
|
||||||
|
return queryTabs().then(tabs =>
|
||||||
|
tabs.map(t => updateIcon({tab: t}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -48,6 +48,10 @@
|
||||||
window.addEventListener(chrome.runtime.id, orphanCheck, true);
|
window.addEventListener(chrome.runtime.id, orphanCheck, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: does it work with styleViaAPI?
|
||||||
|
prefs.subscribe(['disableAll'], (key, value) => doDisableAll(value));
|
||||||
|
prefs.subscribe(['exposeIframes'], (key, value) => doExposeIframes(value));
|
||||||
|
|
||||||
function getMatchUrl() {
|
function getMatchUrl() {
|
||||||
var matchUrl = location.href;
|
var matchUrl = location.href;
|
||||||
if (!matchUrl.match(/^(http|file|chrome|ftp)/)) {
|
if (!matchUrl.match(/^(http|file|chrome|ftp)/)) {
|
||||||
|
@ -135,7 +139,6 @@
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'urlChanged':
|
case 'urlChanged':
|
||||||
console.log('url changed', location.href);
|
|
||||||
API.getSectionsByUrl(getMatchUrl(), {enabled: true})
|
API.getSectionsByUrl(getMatchUrl(), {enabled: true})
|
||||||
.then(buildSections)
|
.then(buildSections)
|
||||||
.then(replaceAll);
|
.then(replaceAll);
|
||||||
|
|
|
@ -238,6 +238,9 @@ preinit();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
prefs.subscribe(['editor.smartIndent'], (key, value) =>
|
||||||
|
CodeMirror.setOption('smartIndent', value));
|
||||||
|
|
||||||
function preinit() {
|
function preinit() {
|
||||||
// make querySelectorAll enumeration code readable
|
// make querySelectorAll enumeration code readable
|
||||||
// FIXME: don't extend native
|
// FIXME: don't extend native
|
||||||
|
|
52
js/dom.js
52
js/dom.js
|
@ -392,3 +392,55 @@ function moveFocus(rootElement, step) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accepts an array of pref names (values are fetched via prefs.get)
|
||||||
|
// and establishes a two-way connection between the document elements and the actual prefs
|
||||||
|
function setupLivePrefs(
|
||||||
|
IDs = Object.getOwnPropertyNames(prefs.defaults)
|
||||||
|
.filter(id => $('#' + id))
|
||||||
|
) {
|
||||||
|
for (const id of IDs) {
|
||||||
|
const element = $('#' + id);
|
||||||
|
updateElement({id, element, force: true});
|
||||||
|
element.addEventListener('change', onChange);
|
||||||
|
}
|
||||||
|
prefs.subscribe(IDs, (id, value) => updateElement({id, value}));
|
||||||
|
|
||||||
|
function onChange() {
|
||||||
|
const value = getInputValue(this);
|
||||||
|
if (prefs.get(this.id) !== value) {
|
||||||
|
prefs.set(this.id, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updateElement({
|
||||||
|
id,
|
||||||
|
value = prefs.get(id),
|
||||||
|
element = $('#' + id),
|
||||||
|
force,
|
||||||
|
}) {
|
||||||
|
if (!element) {
|
||||||
|
prefs.unsubscribe(IDs, updateElement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInputValue(element, value, force);
|
||||||
|
}
|
||||||
|
function getInputValue(input) {
|
||||||
|
if (input.type === 'checkbox') {
|
||||||
|
return input.checked;
|
||||||
|
}
|
||||||
|
if (input.type === 'number') {
|
||||||
|
return Number(input.value);
|
||||||
|
}
|
||||||
|
return input.value;
|
||||||
|
}
|
||||||
|
function setInputValue(input, value, force = false) {
|
||||||
|
if (force || getInputValue(input) !== value) {
|
||||||
|
if (input.type === 'checkbox') {
|
||||||
|
input.checked = value;
|
||||||
|
} else {
|
||||||
|
input.value = value;
|
||||||
|
}
|
||||||
|
input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -91,12 +91,9 @@ if (IS_BG) {
|
||||||
window.API_METHODS = {};
|
window.API_METHODS = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIREFOX_NO_DOM_STORAGE = FIREFOX && !tryCatch(() => localStorage);
|
// FIXME: `localStorage` and `sessionStorage` may be disabled via dom.storage.enabled
|
||||||
if (FIREFOX_NO_DOM_STORAGE) {
|
// Object.defineProperty(window, 'localStorage', {value: {}});
|
||||||
// may be disabled via dom.storage.enabled
|
// Object.defineProperty(window, 'sessionStorage', {value: {}});
|
||||||
Object.defineProperty(window, 'localStorage', {value: {}});
|
|
||||||
Object.defineProperty(window, 'sessionStorage', {value: {}});
|
|
||||||
}
|
|
||||||
|
|
||||||
function queryTabs(options = {}) {
|
function queryTabs(options = {}) {
|
||||||
return new Promise(resolve =>
|
return new Promise(resolve =>
|
||||||
|
|
314
js/prefs.js
314
js/prefs.js
|
@ -2,8 +2,7 @@
|
||||||
/* exported prefs */
|
/* exported prefs */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const prefs = new function Prefs() {
|
const prefs = (() => {
|
||||||
const BG = undefined;
|
|
||||||
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
|
||||||
|
@ -99,29 +98,33 @@ const 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 => {
|
||||||
|
if (result.settings) {
|
||||||
Object.defineProperties(this, {
|
setAll(result.settings, true);
|
||||||
defaults: {value: deepCopy(defaults)},
|
}
|
||||||
readOnlyValues: {value: {}},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(Prefs.prototype, {
|
chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
|
if (area !== 'sync' || !changes.settings || !changes.settings.newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initializing.then(() => setAll(changes.settings.newValue, true));
|
||||||
|
});
|
||||||
|
|
||||||
|
let timer;
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
@ -134,62 +137,11 @@ const 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;
|
|
||||||
defineReadonlyProperty(this.readOnlyValues, 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
|
||||||
|
@ -209,7 +161,6 @@ const 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) {
|
||||||
|
@ -227,147 +178,75 @@ const 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) => {
|
||||||
const props = {};
|
fn(...args, (...result) => {
|
||||||
for (const id in prefs) {
|
if (chrome.runtime.lastError) {
|
||||||
const value = prefs[id];
|
reject(chrome.runtime.lastError);
|
||||||
values[id] = value;
|
} else if (result.length === 0) {
|
||||||
props[id] = {value: deepCopy(value)};
|
resolve(undefined);
|
||||||
|
} else if (result.length === 1) {
|
||||||
|
resolve(result[0]);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
}
|
}
|
||||||
Object.defineProperties(this.readOnlyValues, props);
|
|
||||||
});
|
});
|
||||||
// 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];
|
|
||||||
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;
|
|
||||||
defineReadonlyProperty(this.readOnlyValues, key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
return;
|
||||||
|
|
||||||
function doBroadcast() {
|
|
||||||
// if (BG && BG === window && !BG.dbExec.initialized) {
|
|
||||||
// window.addEventListener('storageReady', function _() {
|
|
||||||
// window.removeEventListener('storageReady', _);
|
|
||||||
// doBroadcast();
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
const affects = {
|
|
||||||
all: 'disableAll' in broadcastPrefs
|
|
||||||
|| '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');
|
|
||||||
}
|
}
|
||||||
|
values[key] = value;
|
||||||
|
emitChange(key, value);
|
||||||
|
if (synced || timer) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
notifyAllTabs({method: 'prefChanged', prefs: broadcastPrefs, affects});
|
timer = setTimeout(syncPrefs);
|
||||||
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.storage.sync.set({settings: values});
|
||||||
if (Number(storage['editor.lintReportDelay']) === 4500) delete storage['editor.lintReportDelay'];
|
timer = null;
|
||||||
}
|
|
||||||
|
|
||||||
function defineReadonlyProperty(obj, key, value) {
|
|
||||||
const copy = deepCopy(value);
|
|
||||||
if (typeof copy === 'object') {
|
|
||||||
Object.freeze(copy);
|
|
||||||
}
|
|
||||||
Object.defineProperty(obj, key, {value: copy, configurable: true});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function equal(a, b) {
|
function equal(a, b) {
|
||||||
|
@ -390,7 +269,7 @@ const prefs = new function Prefs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function contextDeleteMissing() {
|
function contextDeleteMissing() {
|
||||||
return CHROME && (
|
return /Chrome\/\d+/.test(navigator.userAgent) && (
|
||||||
// detect browsers without Delete by looking at the end of UA string
|
// detect browsers without Delete by looking at the end of UA string
|
||||||
/Vivaldi\/[\d.]+$/.test(navigator.userAgent) ||
|
/Vivaldi\/[\d.]+$/.test(navigator.userAgent) ||
|
||||||
// Chrome and co.
|
// Chrome and co.
|
||||||
|
@ -399,44 +278,17 @@ const prefs = new function Prefs() {
|
||||||
!Array.from(navigator.plugins).some(p => p.name === 'Shockwave Flash')
|
!Array.from(navigator.plugins).some(p => p.name === 'Shockwave Flash')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}();
|
|
||||||
|
|
||||||
|
function deepCopy(obj) {
|
||||||
// Accepts an array of pref names (values are fetched via prefs.get)
|
if (!obj || typeof obj !== 'object') {
|
||||||
// and establishes a two-way connection between the document elements and the actual prefs
|
return obj;
|
||||||
function setupLivePrefs(
|
|
||||||
IDs = Object.getOwnPropertyNames(prefs.readOnlyValues)
|
|
||||||
.filter(id => $('#' + id))
|
|
||||||
) {
|
|
||||||
const checkedProps = {};
|
|
||||||
for (const id of IDs) {
|
|
||||||
const element = $('#' + id);
|
|
||||||
checkedProps[id] = element.type === 'checkbox' ? 'checked' : 'value';
|
|
||||||
updateElement({id, element, force: true});
|
|
||||||
element.addEventListener('change', onChange);
|
|
||||||
}
|
|
||||||
prefs.subscribe(IDs, (id, value) => updateElement({id, value}));
|
|
||||||
|
|
||||||
function onChange() {
|
|
||||||
const value = this[checkedProps[this.id]];
|
|
||||||
if (prefs.get(this.id) !== value) {
|
|
||||||
prefs.set(this.id, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function updateElement({
|
|
||||||
id,
|
|
||||||
value = prefs.get(id),
|
|
||||||
element = $('#' + id),
|
|
||||||
force,
|
|
||||||
}) {
|
|
||||||
if (!element) {
|
|
||||||
prefs.unsubscribe(IDs, updateElement);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const prop = checkedProps[id];
|
|
||||||
if (force || element[prop] !== value) {
|
|
||||||
element[prop] = value;
|
|
||||||
element.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
|
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map(deepCopy);
|
||||||
}
|
}
|
||||||
|
return Object.keys(obj).reduce((output, key) => {
|
||||||
|
output[key] = deepCopy(obj[key]);
|
||||||
|
return output;
|
||||||
|
}, {});
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
|
@ -114,7 +114,7 @@ onDOMready().then(() => {
|
||||||
}
|
}
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
el.lastValue = value;
|
el.lastValue = value;
|
||||||
if (el.id in prefs.readOnlyValues) {
|
if (el.id in prefs.defaults) {
|
||||||
prefs.set(el.id, false);
|
prefs.set(el.id, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -701,8 +701,8 @@ function highlightEditedStyle() {
|
||||||
|
|
||||||
|
|
||||||
function usePrefsDuringPageLoad() {
|
function usePrefsDuringPageLoad() {
|
||||||
for (const id of Object.getOwnPropertyNames(prefs.readOnlyValues)) {
|
for (const id of Object.getOwnPropertyNames(prefs.defaults)) {
|
||||||
const value = prefs.readOnlyValues[id];
|
const value = prefs.get(id);
|
||||||
if (value !== true) continue;
|
if (value !== true) continue;
|
||||||
const el = document.getElementById(id) ||
|
const el = document.getElementById(id) ||
|
||||||
id.includes('expanded') && $(`details[data-pref="${id}"]`);
|
id.includes('expanded') && $(`details[data-pref="${id}"]`);
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"all_frames": true,
|
"all_frames": true,
|
||||||
"match_about_blank": true,
|
"match_about_blank": true,
|
||||||
"js": ["js/promisify.js", "js/msg.js", "content/apply.js"]
|
"js": ["js/promisify.js", "js/msg.js", "js/prefs.js", "content/apply.js"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matches": ["http://userstyles.org/*", "https://userstyles.org/*"],
|
"matches": ["http://userstyles.org/*", "https://userstyles.org/*"],
|
||||||
|
@ -115,5 +115,11 @@
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "options.html",
|
"page": "options.html",
|
||||||
"chrome_style": false
|
"chrome_style": false
|
||||||
|
},
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "{7a7a4a92-a2a0-41d1-9fd7-1e92480d612d}",
|
||||||
|
"strict_min_version": "53"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ document.onclick = e => {
|
||||||
|
|
||||||
case 'reset':
|
case 'reset':
|
||||||
$$('input')
|
$$('input')
|
||||||
.filter(input => input.id in prefs.readOnlyValues)
|
.filter(input => input.id in prefs.defaults)
|
||||||
.forEach(input => prefs.reset(input.id));
|
.forEach(input => prefs.reset(input.id));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"semver-bundle": "^0.1.1",
|
"semver-bundle": "^0.1.1",
|
||||||
"stylelint-bundle": "^8.0.0",
|
"stylelint-bundle": "^8.0.0",
|
||||||
"stylus-lang-bundle": "^0.54.5",
|
"stylus-lang-bundle": "^0.54.5",
|
||||||
"updates": "^4.2.1"
|
"updates": "^4.2.1",
|
||||||
|
"web-ext": "^2.9.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint **/*.js --cache || exit 0",
|
"lint": "eslint **/*.js --cache || exit 0",
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
"update-node": "updates -u && node tools/remove-modules.js && npm install",
|
"update-node": "updates -u && node tools/remove-modules.js && npm install",
|
||||||
"update-codemirror": "node tools/update-libraries.js && node tools/update-codemirror-themes.js",
|
"update-codemirror": "node tools/update-libraries.js && node tools/update-codemirror-themes.js",
|
||||||
"update-versions": "node tools/update-versions",
|
"update-versions": "node tools/update-versions",
|
||||||
"zip": "npm run update-versions && node tools/zip.js"
|
"zip": "npm run update-versions && node tools/zip.js",
|
||||||
|
"start": "web-ext run"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,14 @@ getActiveTab().then(tab =>
|
||||||
|
|
||||||
msg.onExtension(onRuntimeMessage);
|
msg.onExtension(onRuntimeMessage);
|
||||||
|
|
||||||
|
prefs.subscribe(['popup.stylesFirst'], (key, stylesFirst) => {
|
||||||
|
const actions = $('body > .actions');
|
||||||
|
const before = stylesFirst ? actions : actions.nextSibling;
|
||||||
|
document.body.insertBefore(installed, before);
|
||||||
|
});
|
||||||
|
prefs.subscribe(['popupWidth'], (key, value) => setPopupWidth(value));
|
||||||
|
prefs.subscribe(['popup.borders'], (key, value) => toggleSideBorders(value));
|
||||||
|
|
||||||
function onRuntimeMessage(msg) {
|
function onRuntimeMessage(msg) {
|
||||||
switch (msg.method) {
|
switch (msg.method) {
|
||||||
case 'styleAdded':
|
case 'styleAdded':
|
||||||
|
@ -42,18 +50,6 @@ function onRuntimeMessage(msg) {
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
handleDelete(msg.style.id);
|
handleDelete(msg.style.id);
|
||||||
break;
|
break;
|
||||||
case 'prefChanged':
|
|
||||||
if ('popup.stylesFirst' in msg.prefs) {
|
|
||||||
const stylesFirst = msg.prefs['popup.stylesFirst'];
|
|
||||||
const actions = $('body > .actions');
|
|
||||||
const before = stylesFirst ? actions : actions.nextSibling;
|
|
||||||
document.body.insertBefore(installed, before);
|
|
||||||
} else if ('popupWidth' in msg.prefs) {
|
|
||||||
setPopupWidth(msg.prefs.popupWidth);
|
|
||||||
} else if ('popup.borders' in msg.prefs) {
|
|
||||||
toggleSideBorders(msg.prefs['popup.borders']);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
dispatchEvent(new CustomEvent(msg.method, {detail: msg}));
|
dispatchEvent(new CustomEvent(msg.method, {detail: msg}));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user