ESLint: customize the rules; fix the issues
This commit is contained in:
parent
657db366c9
commit
ac4a420e2b
118
.eslintrc
118
.eslintrc
|
@ -1,52 +1,62 @@
|
||||||
# https://github.com/eslint/eslint/blob/master/docs/rules/README.md
|
# https://github.com/eslint/eslint/blob/master/docs/rules/README.md
|
||||||
|
|
||||||
parserOptions:
|
parserOptions:
|
||||||
ecmaVersion: 2017
|
ecmaVersion: 2015
|
||||||
|
|
||||||
env:
|
env:
|
||||||
browser: true
|
browser: true
|
||||||
commonjs: true
|
|
||||||
es6: true
|
es6: true
|
||||||
webextensions: true
|
webextensions: true
|
||||||
|
|
||||||
globals:
|
globals:
|
||||||
CodeMirror: false
|
# messaging.js
|
||||||
runTryCatch: true
|
OWN_ORIGIN: false
|
||||||
getStylesSafe: true
|
KEEP_CHANNEL_OPEN: false
|
||||||
getStyles: true
|
configureCommands: false
|
||||||
updateIcon: true
|
notifyAllTabs: false
|
||||||
saveStyle: true
|
refreshAllTabs: false
|
||||||
invalidateCache: true
|
updateIcon: false
|
||||||
getDatabase: true
|
getActiveTab: false
|
||||||
|
getActiveTabRealURL: false
|
||||||
|
getTabRealURL: false
|
||||||
|
openURL: false
|
||||||
|
activateTab: false
|
||||||
|
stringAsRegExp: false
|
||||||
|
wildcardAsRegExp: false
|
||||||
|
# localization.js
|
||||||
|
template: false
|
||||||
|
t: false
|
||||||
|
o: false
|
||||||
|
tE: false
|
||||||
|
tHTML: false
|
||||||
|
tNodeList: false
|
||||||
|
tDocLoader: false
|
||||||
|
# dom.js
|
||||||
|
onDOMready: false
|
||||||
|
getClickedStyleId: false
|
||||||
|
getClickedStyleElement: false
|
||||||
|
scrollElementIntoView: false
|
||||||
|
animateElement: false
|
||||||
|
$: false
|
||||||
|
$$: false
|
||||||
|
# storage.js
|
||||||
prefs: false
|
prefs: false
|
||||||
reportError: true
|
cachedStyles: false
|
||||||
getActiveTab: true
|
sessionStorageHash: false
|
||||||
t: true
|
getStylesSafe: false
|
||||||
getCodeMirrorThemes: true
|
invalidateCache: false
|
||||||
setupLivePrefs: true
|
saveStyle: false
|
||||||
sessionStorageHash: true
|
enableStyle: false
|
||||||
template: true
|
deleteStyle: false
|
||||||
tE: true
|
fixBoolean: false
|
||||||
tHTML: true
|
getDomains: false
|
||||||
CSSLint: true
|
getType: false
|
||||||
enableStyle: true
|
getApplicableSections: false
|
||||||
deleteStyle: true
|
isCheckbox: false
|
||||||
getType: true
|
runTryCatch: false
|
||||||
importStyles: true
|
setupLivePrefs: false
|
||||||
getActiveTabRealURL: true
|
getCodeMirrorThemes: false
|
||||||
openURL: true
|
styleSectionsEqual: false
|
||||||
$: true
|
|
||||||
$$: true
|
|
||||||
animateElement: true
|
|
||||||
scrollElementIntoView: true
|
|
||||||
getClickedStyleElement: true
|
|
||||||
getClickedStyleId: true
|
|
||||||
onDOMready: true
|
|
||||||
getDomains: true
|
|
||||||
webSqlStorage: true
|
|
||||||
notifyAllTabs: true
|
|
||||||
handleUpdate: true
|
|
||||||
handleDelete: true
|
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
accessor-pairs: [2]
|
accessor-pairs: [2]
|
||||||
|
@ -56,7 +66,7 @@ rules:
|
||||||
arrow-parens: [2, as-needed]
|
arrow-parens: [2, as-needed]
|
||||||
arrow-spacing: [2, {before: true, after: true}]
|
arrow-spacing: [2, {before: true, after: true}]
|
||||||
block-scoped-var: [2]
|
block-scoped-var: [2]
|
||||||
brace-style: [2, 1tbs, {allowSingleLine: true}]
|
brace-style: [2, 1tbs, {allowSingleLine: false}]
|
||||||
camelcase: [2, {properties: never}]
|
camelcase: [2, {properties: never}]
|
||||||
class-methods-use-this: [2]
|
class-methods-use-this: [2]
|
||||||
comma-dangle: [0]
|
comma-dangle: [0]
|
||||||
|
@ -77,16 +87,15 @@ rules:
|
||||||
func-names: [0]
|
func-names: [0]
|
||||||
generator-star-spacing: [2, before]
|
generator-star-spacing: [2, before]
|
||||||
global-require: [0]
|
global-require: [0]
|
||||||
guard-for-in: [2]
|
guard-for-in: [0] # not needed for our non-OOP stuff
|
||||||
handle-callback-err: [2, ^(err|error)$]
|
handle-callback-err: [2, ^(err|error)$]
|
||||||
id-blacklist: [0]
|
id-blacklist: [0]
|
||||||
id-length: [0]
|
id-length: [0]
|
||||||
id-match: [0]
|
id-match: [0]
|
||||||
indent: [2, 2, {VariableDeclarator: 0}]
|
indent: [2, 2, {VariableDeclarator: 0, SwitchCase: 1}]
|
||||||
jsx-quotes: [0]
|
jsx-quotes: [0]
|
||||||
key-spacing: [0]
|
key-spacing: [0]
|
||||||
keyword-spacing: [2]
|
keyword-spacing: [2]
|
||||||
linebreak-style: [2, unix]
|
|
||||||
lines-around-comment: [0]
|
lines-around-comment: [0]
|
||||||
lines-around-directive: [0]
|
lines-around-directive: [0]
|
||||||
max-len: [2, {code: 120, ignoreComments: true, ignoreRegExpLiterals: true}]
|
max-len: [2, {code: 120, ignoreComments: true, ignoreRegExpLiterals: true}]
|
||||||
|
@ -107,7 +116,7 @@ rules:
|
||||||
no-case-declarations: [2]
|
no-case-declarations: [2]
|
||||||
no-class-assign: [2]
|
no-class-assign: [2]
|
||||||
no-cond-assign: [2, except-parens]
|
no-cond-assign: [2, except-parens]
|
||||||
no-confusing-arrow: [2]
|
no-confusing-arrow: [2, {allowParens: true}]
|
||||||
no-const-assign: [2]
|
no-const-assign: [2]
|
||||||
no-constant-condition: [0]
|
no-constant-condition: [0]
|
||||||
no-continue: [0]
|
no-continue: [0]
|
||||||
|
@ -134,11 +143,11 @@ rules:
|
||||||
no-extra-label: [0]
|
no-extra-label: [0]
|
||||||
no-extra-parens: [0]
|
no-extra-parens: [0]
|
||||||
no-extra-semi: [2]
|
no-extra-semi: [2]
|
||||||
no-fallthrough: [2]
|
no-fallthrough: [2, {commentPattern: fallthrough.*}]
|
||||||
no-floating-decimal: [0]
|
no-floating-decimal: [0]
|
||||||
no-func-assign: [2]
|
no-func-assign: [2]
|
||||||
no-global-assign: [2]
|
no-global-assign: [2]
|
||||||
no-implicit-coercion: [2]
|
no-implicit-coercion: [1]
|
||||||
no-implicit-globals: [0]
|
no-implicit-globals: [0]
|
||||||
no-implied-eval: [2]
|
no-implied-eval: [2]
|
||||||
no-inline-comments: [0]
|
no-inline-comments: [0]
|
||||||
|
@ -148,7 +157,7 @@ rules:
|
||||||
no-irregular-whitespace: [2]
|
no-irregular-whitespace: [2]
|
||||||
no-iterator: [2]
|
no-iterator: [2]
|
||||||
no-label-var: [2]
|
no-label-var: [2]
|
||||||
no-labels: [2]
|
no-labels: [2, {allowLoop: true}]
|
||||||
no-lone-blocks: [2]
|
no-lone-blocks: [2]
|
||||||
no-lonely-if: [0]
|
no-lonely-if: [0]
|
||||||
no-loop-func: [0]
|
no-loop-func: [0]
|
||||||
|
@ -158,7 +167,7 @@ rules:
|
||||||
no-mixed-spaces-and-tabs: [2]
|
no-mixed-spaces-and-tabs: [2]
|
||||||
no-multi-spaces: [0]
|
no-multi-spaces: [0]
|
||||||
no-multi-str: [2]
|
no-multi-str: [2]
|
||||||
no-multiple-empty-lines: [2, {max: 1, maxEOF: 0, maxBOF: 0}]
|
no-multiple-empty-lines: [2, {max: 2, maxEOF: 0, maxBOF: 0}]
|
||||||
no-native-reassign: [2]
|
no-native-reassign: [2]
|
||||||
no-negated-condition: [0]
|
no-negated-condition: [0]
|
||||||
no-negated-in-lhs: [2]
|
no-negated-in-lhs: [2]
|
||||||
|
@ -193,7 +202,7 @@ rules:
|
||||||
no-tabs: [2]
|
no-tabs: [2]
|
||||||
no-template-curly-in-string: [2]
|
no-template-curly-in-string: [2]
|
||||||
no-this-before-super: [2]
|
no-this-before-super: [2]
|
||||||
no-throw-literal: [2]
|
no-throw-literal: [0]
|
||||||
no-trailing-spaces: [2]
|
no-trailing-spaces: [2]
|
||||||
no-undef-init: [2]
|
no-undef-init: [2]
|
||||||
no-undef: [2]
|
no-undef: [2]
|
||||||
|
@ -207,29 +216,30 @@ rules:
|
||||||
no-unsafe-negation: [2]
|
no-unsafe-negation: [2]
|
||||||
no-unused-expressions: [2]
|
no-unused-expressions: [2]
|
||||||
no-unused-labels: [0]
|
no-unused-labels: [0]
|
||||||
no-unused-vars: [2, {args: all, varsIgnorePattern: clearError, argsIgnorePattern: ^_}]
|
no-unused-vars: [1, {args: all, vars: local, varsIgnorePattern: clearError, argsIgnorePattern: ^_}]
|
||||||
no-use-before-define: [2, nofunc]
|
no-use-before-define: [2, nofunc]
|
||||||
no-useless-call: [2]
|
no-useless-call: [2]
|
||||||
no-useless-computed-key: [2]
|
no-useless-computed-key: [2]
|
||||||
no-useless-concat: [2]
|
no-useless-concat: [2]
|
||||||
no-useless-constructor: [2]
|
no-useless-constructor: [2]
|
||||||
no-useless-escape: [2]
|
no-useless-escape: [2]
|
||||||
no-var: [0]
|
no-var: [1]
|
||||||
no-warning-comments: [0]
|
no-warning-comments: [0]
|
||||||
no-whitespace-before-property: [2]
|
no-whitespace-before-property: [2]
|
||||||
no-with: [2]
|
no-with: [2]
|
||||||
object-curly-newline: [0]
|
object-curly-newline: [0]
|
||||||
object-curly-spacing: [2, never]
|
object-curly-spacing: [2, never]
|
||||||
object-shorthand: [0]
|
object-shorthand: [0]
|
||||||
one-var-declaration-per-line: [0]
|
one-var-declaration-per-line: [1]
|
||||||
one-var: [0]
|
one-var: [0]
|
||||||
operator-assignment: [2, always]
|
operator-assignment: [2, always]
|
||||||
operator-linebreak: [2, after]
|
operator-linebreak: [2, after, overrides: {"?": ignore, ":": ignore, "&&": ignore, "||": ignore}]
|
||||||
padded-blocks: [2, never]
|
padded-blocks: [2, never]
|
||||||
prefer-numeric-literals: [2]
|
prefer-numeric-literals: [2]
|
||||||
prefer-rest-params: [0]
|
prefer-rest-params: [0]
|
||||||
|
prefer-const: [1, {destructuring: any, ignoreReadBeforeAssign: true}]
|
||||||
quote-props: [0]
|
quote-props: [0]
|
||||||
quotes: [2, double, avoid-escape]
|
quotes: [1, single, avoid-escape]
|
||||||
radix: [2, as-needed]
|
radix: [2, as-needed]
|
||||||
require-jsdoc: [0]
|
require-jsdoc: [0]
|
||||||
require-yield: [2]
|
require-yield: [2]
|
||||||
|
@ -242,7 +252,7 @@ rules:
|
||||||
space-in-parens: [2, never]
|
space-in-parens: [2, never]
|
||||||
space-infix-ops: [2]
|
space-infix-ops: [2]
|
||||||
space-unary-ops: [2]
|
space-unary-ops: [2]
|
||||||
spaced-comment: [2, always, {markers: ["!"]}]
|
spaced-comment: [0, always, {markers: ["!"]}]
|
||||||
strict: [2, global]
|
strict: [2, global]
|
||||||
symbol-description: [2]
|
symbol-description: [2]
|
||||||
template-curly-spacing: [2, never]
|
template-curly-spacing: [2, never]
|
||||||
|
|
633
apply.js
633
apply.js
|
@ -1,373 +1,404 @@
|
||||||
// using ES5 syntax because ES6 is fast only since around Chrome 55
|
// Not using some slow features of ES6, see http://kpdecker.github.io/six-speed/
|
||||||
// so we'll wait until Chrome 60 arguably before converting
|
// like destructring, classes, defaults, spread, calculated key names
|
||||||
|
/* eslint no-var: 0 */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var g_disableAll = false;
|
var disableAll = false;
|
||||||
var g_styleElements = {};
|
var styleElements = new Map();
|
||||||
var iframeObserver;
|
|
||||||
var retiredStyleIds = [];
|
var retiredStyleIds = [];
|
||||||
|
var iframeObserver;
|
||||||
|
|
||||||
initObserver();
|
initObserver();
|
||||||
requestStyles();
|
requestStyles();
|
||||||
|
|
||||||
function requestStyles(options = {}) {
|
|
||||||
// If this is a Stylish page (Edit Style or Manage Styles),
|
|
||||||
// we'll request the styles directly to minimize delay and flicker,
|
|
||||||
// unless Chrome still starts up and the background page isn't fully loaded.
|
|
||||||
// (Note: in this case the function may be invoked again from applyStyles.)
|
|
||||||
var request = Object.assign({
|
|
||||||
method: "getStyles",
|
|
||||||
matchUrl: location.href,
|
|
||||||
enabled: true,
|
|
||||||
asHash: true,
|
|
||||||
}, options);
|
|
||||||
if (typeof getStylesSafe !== 'undefined') {
|
|
||||||
getStylesSafe(request).then(applyStyles);
|
|
||||||
} else {
|
|
||||||
chrome.runtime.sendMessage(request, applyStyles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(applyOnMessage);
|
chrome.runtime.onMessage.addListener(applyOnMessage);
|
||||||
|
|
||||||
|
|
||||||
|
function requestStyles(options) {
|
||||||
|
// If this is a Stylish page (Edit Style or Manage Styles),
|
||||||
|
// we'll request the styles directly to minimize delay and flicker,
|
||||||
|
// unless Chrome still starts up and the background page isn't fully loaded.
|
||||||
|
// (Note: in this case the function may be invoked again from applyStyles.)
|
||||||
|
const request = Object.assign({
|
||||||
|
method: 'getStyles',
|
||||||
|
matchUrl: location.href,
|
||||||
|
enabled: true,
|
||||||
|
asHash: true,
|
||||||
|
}, options);
|
||||||
|
if (typeof getStylesSafe !== 'undefined') {
|
||||||
|
getStylesSafe(request).then(applyStyles);
|
||||||
|
} else {
|
||||||
|
chrome.runtime.sendMessage(request, applyStyles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function applyOnMessage(request, sender, sendResponse) {
|
function applyOnMessage(request, sender, sendResponse) {
|
||||||
// Also handle special request just for the pop-up
|
// Also handle special request just for the pop-up
|
||||||
switch (request.method == "updatePopup" ? request.reason : request.method) {
|
switch (request.method == 'updatePopup' ? request.reason : request.method) {
|
||||||
case "styleDeleted":
|
|
||||||
removeStyle(request.id, document);
|
case 'styleDeleted':
|
||||||
break;
|
removeStyle(request.id, document);
|
||||||
case "styleUpdated":
|
break;
|
||||||
if (request.codeIsUpdated === false) {
|
|
||||||
applyStyleState(request.style.id, request.style.enabled, document);
|
case 'styleUpdated':
|
||||||
break;
|
if (request.codeIsUpdated === false) {
|
||||||
}
|
applyStyleState(request.style.id, request.style.enabled, document);
|
||||||
if (request.style.enabled) {
|
break;
|
||||||
retireStyle(request.style.id);
|
}
|
||||||
// fallthrough to "styleAdded"
|
if (!request.style.enabled) {
|
||||||
} else {
|
removeStyle(request.style.id, document);
|
||||||
removeStyle(request.style.id, document);
|
break;
|
||||||
break;
|
}
|
||||||
}
|
retireStyle(request.style.id);
|
||||||
case "styleAdded":
|
// fallthrough to 'styleAdded'
|
||||||
if (request.style.enabled) {
|
|
||||||
chrome.runtime.sendMessage({method: "getStyles", matchUrl: location.href, enabled: true, id: request.style.id, asHash: true}, applyStyles);
|
case 'styleAdded':
|
||||||
}
|
if (request.style.enabled) {
|
||||||
break;
|
requestStyles({id: request.style.id}, applyStyles);
|
||||||
case "styleApply":
|
}
|
||||||
applyStyles(request.styles);
|
break;
|
||||||
break;
|
|
||||||
case "styleReplaceAll":
|
case 'styleApply':
|
||||||
replaceAll(request.styles, document);
|
applyStyles(request.styles);
|
||||||
break;
|
break;
|
||||||
case "styleDisableAll":
|
|
||||||
disableAll(request.disableAll);
|
case 'styleReplaceAll':
|
||||||
break;
|
replaceAll(request.styles, document);
|
||||||
case "ping":
|
break;
|
||||||
sendResponse(true);
|
|
||||||
break;
|
case 'styleDisableAll':
|
||||||
}
|
doDisableAll(request.disableAll);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ping':
|
||||||
|
sendResponse(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableAll(disable) {
|
|
||||||
if (!disable === !g_disableAll) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
g_disableAll = disable;
|
|
||||||
if (g_disableAll) {
|
|
||||||
iframeObserver.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
disableSheets(g_disableAll, document);
|
function doDisableAll(disable) {
|
||||||
|
if (!disable === !disableAll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
disableAll = disable;
|
||||||
|
if (disableAll) {
|
||||||
|
iframeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
if (!g_disableAll && document.readyState != "loading") {
|
disableSheets(disableAll, document);
|
||||||
iframeObserver.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
function disableSheets(disable, doc) {
|
if (!disableAll && document.readyState != 'loading') {
|
||||||
Array.prototype.forEach.call(doc.styleSheets, function(stylesheet) {
|
iframeObserver.start();
|
||||||
if (stylesheet.ownerNode.classList.contains("stylus")
|
}
|
||||||
&& stylesheet.disabled != disable) {
|
|
||||||
stylesheet.disabled = disable;
|
function disableSheets(disable, doc) {
|
||||||
}
|
Array.prototype.forEach.call(doc.styleSheets, stylesheet => {
|
||||||
});
|
if (stylesheet.ownerNode.classList.contains('stylus')
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
&& stylesheet.disabled != disable) {
|
||||||
if (!disable) {
|
stylesheet.disabled = disable;
|
||||||
// update the IFRAME if it was created while the observer was disconnected
|
}
|
||||||
addDocumentStylesToIFrame(iframe);
|
});
|
||||||
}
|
for (const iframe of getDynamicIFrames(doc)) {
|
||||||
disableSheets(disable, iframe.contentDocument);
|
if (!disable) {
|
||||||
});
|
// update the IFRAME if it was created while the observer was disconnected
|
||||||
}
|
addDocumentStylesToIFrame(iframe);
|
||||||
|
}
|
||||||
|
disableSheets(disable, iframe.contentDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function applyStyleState(id, enabled, doc) {
|
function applyStyleState(id, enabled, doc) {
|
||||||
var e = doc.getElementById("stylus-" + id);
|
const el = doc.getElementById('stylus-' + id);
|
||||||
if (!e) {
|
if (el) {
|
||||||
if (enabled) {
|
el.sheet.disabled = !enabled;
|
||||||
requestStyles({id});
|
processDynamicIFrames(doc, applyStyleState, id, enabled);
|
||||||
}
|
} else if (enabled) {
|
||||||
} else {
|
requestStyles({id});
|
||||||
e.sheet.disabled = !enabled;
|
}
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
|
||||||
applyStyleState(id, iframe.contentDocument);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function removeStyle(id, doc) {
|
function removeStyle(id, doc) {
|
||||||
var e = doc.getElementById("stylus-" + id);
|
styleElements.delete('stylus-' + id);
|
||||||
delete g_styleElements["stylus-" + id];
|
const el = doc.getElementById('stylus-' + id);
|
||||||
if (e) {
|
if (el) {
|
||||||
e.remove();
|
el.remove();
|
||||||
}
|
}
|
||||||
if (doc == document && Object.keys(g_styleElements).length == 0) {
|
if (doc == document && !styleElements.size) {
|
||||||
iframeObserver.disconnect();
|
iframeObserver.disconnect();
|
||||||
}
|
}
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
processDynamicIFrames(doc, removeStyle, id);
|
||||||
removeStyle(id, iframe.contentDocument);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// to avoid page flicker when the style is updated
|
// to avoid page flicker when the style is updated
|
||||||
// instead of removing it immediately we rename its ID and queue it
|
// instead of removing it immediately we rename its ID and queue it
|
||||||
// to be deleted in applyStyles after a new version is fetched and applied
|
// to be deleted in applyStyles after a new version is fetched and applied
|
||||||
function retireStyle(id, doc) {
|
function retireStyle(id, doc) {
|
||||||
var deadID = "ghost-" + id;
|
const deadID = 'ghost-' + id;
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
doc = document;
|
doc = document;
|
||||||
retiredStyleIds.push(deadID);
|
retiredStyleIds.push(deadID);
|
||||||
delete g_styleElements["stylus-" + id];
|
styleElements.delete('stylus-' + id);
|
||||||
// in case something went wrong and new style was never applied
|
// in case something went wrong and new style was never applied
|
||||||
setTimeout(removeStyle.bind(null, deadID, doc), 1000);
|
setTimeout(removeStyle, 1000, deadID, doc);
|
||||||
}
|
}
|
||||||
var e = doc.getElementById("stylus-" + id);
|
const el = doc.getElementById('stylus-' + id);
|
||||||
if (e) {
|
if (el) {
|
||||||
e.id = "stylus-" + deadID;
|
el.id = 'stylus-' + deadID;
|
||||||
}
|
}
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
processDynamicIFrames(doc, retireStyle, id);
|
||||||
retireStyle(id, iframe.contentDocument);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function applyStyles(styleHash) {
|
function applyStyles(styleHash) {
|
||||||
if (!styleHash) { // Chrome is starting up
|
if (!styleHash) { // Chrome is starting up
|
||||||
requestStyles();
|
requestStyles();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ("disableAll" in styleHash) {
|
if ('disableAll' in styleHash) {
|
||||||
disableAll(styleHash.disableAll);
|
doDisableAll(styleHash.disableAll);
|
||||||
delete styleHash.disableAll;
|
delete styleHash.disableAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var styleId in styleHash) {
|
for (const styleId in styleHash) {
|
||||||
applySections(styleId, styleHash[styleId]);
|
applySections(styleId, styleHash[styleId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(g_styleElements).length) {
|
if (styleElements.size) {
|
||||||
// when site response is application/xml Chrome displays our style elements
|
// when site response is application/xml Chrome displays our style elements
|
||||||
// under document.documentElement as plain text so we need to move them into HEAD
|
// under document.documentElement as plain text so we need to move them into HEAD
|
||||||
// (which already is autogenerated at this moment for the xml response)
|
// which is already autogenerated at this moment
|
||||||
if (document.head && document.head.firstChild && document.head.firstChild.id == "xml-viewer-style") {
|
if (document.head && document.head.firstChild && document.head.firstChild.id == 'xml-viewer-style') {
|
||||||
for (var id in g_styleElements) {
|
for (const id of styleElements.keys()) {
|
||||||
document.head.appendChild(document.getElementById(id));
|
document.head.appendChild(document.getElementById(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener("DOMContentLoaded", onDOMContentLoaded);
|
document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retiredStyleIds.length) {
|
if (retiredStyleIds.length) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
while (retiredStyleIds.length) {
|
while (retiredStyleIds.length) {
|
||||||
removeStyle(retiredStyleIds.shift(), document);
|
removeStyle(retiredStyleIds.shift(), document);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function onDOMContentLoaded() {
|
function onDOMContentLoaded() {
|
||||||
addDocumentStylesToAllIFrames();
|
addDocumentStylesToAllIFrames();
|
||||||
iframeObserver.start();
|
iframeObserver.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function applySections(styleId, sections) {
|
function applySections(styleId, sections) {
|
||||||
var styleElement = document.getElementById("stylus-" + styleId);
|
let el = document.getElementById('stylus-' + styleId);
|
||||||
// Already there.
|
// Already there.
|
||||||
if (styleElement) {
|
if (el) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (document.documentElement instanceof SVGSVGElement) {
|
if (document.documentElement instanceof SVGSVGElement) {
|
||||||
// SVG document, make an SVG style element.
|
// SVG document, make an SVG style element.
|
||||||
styleElement = document.createElementNS("http://www.w3.org/2000/svg", "style");
|
el = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
||||||
} else {
|
} else {
|
||||||
// This will make an HTML style element. If there's SVG embedded in an HTML document, this works on the SVG too.
|
// This will make an HTML style element. If there's SVG embedded in an HTML document, this works on the SVG too.
|
||||||
styleElement = document.createElement("style");
|
el = document.createElement('style');
|
||||||
}
|
}
|
||||||
styleElement.setAttribute("id", "stylus-" + styleId);
|
el.setAttribute('id', 'stylus-' + styleId);
|
||||||
styleElement.setAttribute("class", "stylus");
|
el.setAttribute('class', 'stylus');
|
||||||
styleElement.setAttribute("type", "text/css");
|
el.setAttribute('type', 'text/css');
|
||||||
styleElement.appendChild(document.createTextNode(sections.map(function(section) {
|
el.appendChild(document.createTextNode(sections.map(section => section.code).join('\n')));
|
||||||
return section.code;
|
addStyleElement(el, document);
|
||||||
}).join("\n")));
|
styleElements.set(el.id, el);
|
||||||
addStyleElement(styleElement, document);
|
|
||||||
g_styleElements[styleElement.id] = styleElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addStyleElement(styleElement, doc) {
|
|
||||||
if (!doc.documentElement || doc.getElementById(styleElement.id)) {
|
function addStyleElement(el, doc) {
|
||||||
return;
|
if (!doc.documentElement || doc.getElementById(el.id)) {
|
||||||
}
|
return;
|
||||||
doc.documentElement.appendChild(doc.importNode(styleElement, true))
|
}
|
||||||
.disabled = g_disableAll;
|
doc.documentElement.appendChild(doc.importNode(el, true))
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
.disabled = disableAll;
|
||||||
if (iframeIsLoadingSrcDoc(iframe)) {
|
for (const iframe of getDynamicIFrames(doc)) {
|
||||||
addStyleToIFrameSrcDoc(iframe, styleElement);
|
if (iframeIsLoadingSrcDoc(iframe)) {
|
||||||
} else {
|
addStyleToIFrameSrcDoc(iframe, el);
|
||||||
addStyleElement(styleElement, iframe.contentDocument);
|
} else {
|
||||||
}
|
addStyleElement(el, iframe.contentDocument);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function addDocumentStylesToIFrame(iframe) {
|
function addDocumentStylesToIFrame(iframe) {
|
||||||
var doc = iframe.contentDocument;
|
const doc = iframe.contentDocument;
|
||||||
var srcDocIsLoading = iframeIsLoadingSrcDoc(iframe);
|
const srcDocIsLoading = iframeIsLoadingSrcDoc(iframe);
|
||||||
for (var id in g_styleElements) {
|
for (const el of styleElements.values()) {
|
||||||
if (srcDocIsLoading) {
|
if (srcDocIsLoading) {
|
||||||
addStyleToIFrameSrcDoc(iframe, g_styleElements[id]);
|
addStyleToIFrameSrcDoc(iframe, el);
|
||||||
} else {
|
} else {
|
||||||
addStyleElement(g_styleElements[id], doc);
|
addStyleElement(el, doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function addDocumentStylesToAllIFrames() {
|
function addDocumentStylesToAllIFrames() {
|
||||||
getDynamicIFrames(document).forEach(addDocumentStylesToIFrame);
|
getDynamicIFrames(document).forEach(addDocumentStylesToIFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only dynamic iframes get the parent document's styles. Other ones should get styles based on their own URLs.
|
// Only dynamic iframes get the parent document's styles. Other ones should get styles based on their own URLs.
|
||||||
function getDynamicIFrames(doc) {
|
function getDynamicIFrames(doc) {
|
||||||
return Array.prototype.filter.call(doc.getElementsByTagName('iframe'), iframeIsDynamic);
|
return [...doc.getElementsByTagName('iframe')].filter(iframeIsDynamic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function iframeIsDynamic(f) {
|
function iframeIsDynamic(f) {
|
||||||
var href;
|
let href;
|
||||||
try {
|
if (f.src && f.src.startsWith('http') && new URL(f.src).origin != location.origin) {
|
||||||
href = f.contentDocument.location.href;
|
return false;
|
||||||
} catch (ex) {
|
}
|
||||||
// Cross-origin, so it's not a dynamic iframe
|
try {
|
||||||
return false;
|
href = f.contentDocument.location.href;
|
||||||
}
|
} catch (ex) {
|
||||||
return href == document.location.href || href.indexOf("about:") == 0;
|
// Cross-origin, so it's not a dynamic iframe
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return href == document.location.href || href.startsWith('about:');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function processDynamicIFrames(doc, fn, ...args) {
|
||||||
|
for (const iframe of [...doc.getElementsByTagName('iframe')]) {
|
||||||
|
if (iframeIsDynamic(iframe)) {
|
||||||
|
fn(...args, iframe.contentDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function iframeIsLoadingSrcDoc(f) {
|
function iframeIsLoadingSrcDoc(f) {
|
||||||
return f.srcdoc && f.contentDocument.all.length <= 3;
|
return f.srcdoc && f.contentDocument.all.length <= 3;
|
||||||
// 3 nodes or less in total (html, head, body) == new empty iframe about to be overwritten by its 'srcdoc'
|
// 3 nodes or less in total (html, head, body) == new empty iframe about to be overwritten by its 'srcdoc'
|
||||||
}
|
}
|
||||||
|
|
||||||
function addStyleToIFrameSrcDoc(iframe, styleElement) {
|
|
||||||
if (g_disableAll) {
|
function addStyleToIFrameSrcDoc(iframe, el) {
|
||||||
return;
|
if (disableAll) {
|
||||||
}
|
return;
|
||||||
iframe.srcdoc += styleElement.outerHTML;
|
}
|
||||||
// make sure the style is added in case srcdoc was malformed
|
iframe.srcdoc += el.outerHTML;
|
||||||
setTimeout(addStyleElement.bind(null, styleElement, iframe.contentDocument), 100);
|
// make sure the style is added in case srcdoc was malformed
|
||||||
|
setTimeout(addStyleElement, 100, el, iframe.contentDocument);
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceAll(newStyles, doc, pass2) {
|
|
||||||
var oldStyles = [].slice.call(doc.querySelectorAll("STYLE.stylus" + (pass2 ? "[id$='-ghost']" : "")));
|
function replaceAll(newStyles, doc) {
|
||||||
if (!pass2) {
|
const oldStyles = [...doc.querySelectorAll('STYLE.stylus')];
|
||||||
oldStyles.forEach(function(style) { style.id += "-ghost"; });
|
oldStyles.forEach(style => (style.id += '-ghost'));
|
||||||
}
|
processDynamicIFrames(doc, replaceAll, newStyles);
|
||||||
getDynamicIFrames(doc).forEach(function(iframe) {
|
if (doc == document) {
|
||||||
replaceAll(newStyles, iframe.contentDocument, pass2);
|
styleElements.clear();
|
||||||
});
|
applyStyles(newStyles);
|
||||||
if (doc == document && !pass2) {
|
replaceAllpass2(newStyles, doc);
|
||||||
g_styleElements = {};
|
}
|
||||||
applyStyles(newStyles);
|
|
||||||
replaceAll(newStyles, doc, true);
|
|
||||||
}
|
|
||||||
if (pass2) {
|
|
||||||
oldStyles.forEach(function(style) { style.remove(); });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function replaceAllpass2(newStyles, doc) {
|
||||||
|
const oldStyles = [...doc.querySelectorAll('STYLE.stylus[id$="-ghost"]')];
|
||||||
|
processDynamicIFrames(doc, replaceAllpass2, newStyles);
|
||||||
|
oldStyles.forEach(style => style.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Observe dynamic IFRAMEs being added
|
// Observe dynamic IFRAMEs being added
|
||||||
function initObserver() {
|
function initObserver() {
|
||||||
var orphanCheckTimer;
|
let orphanCheckTimer;
|
||||||
|
|
||||||
iframeObserver = new MutationObserver(function(mutations) {
|
iframeObserver = new MutationObserver(function(mutations) {
|
||||||
clearTimeout(orphanCheckTimer);
|
clearTimeout(orphanCheckTimer);
|
||||||
// MutationObserver runs as a microtask so the timer won't fire until all queued mutations are fired
|
// MutationObserver runs as a microtask so the timer won't fire until all queued mutations are fired
|
||||||
orphanCheckTimer = setTimeout(orphanCheck, 0);
|
orphanCheckTimer = setTimeout(orphanCheck, 0);
|
||||||
|
|
||||||
if (mutations.length > 1000) {
|
if (mutations.length > 1000) {
|
||||||
// use a much faster method for very complex pages with 100,000 mutations
|
// use a much faster method for very complex pages with 100,000 mutations
|
||||||
// (observer usually receives 1k-10k mutations per call)
|
// (observer usually receives 1k-10k mutations per call)
|
||||||
addDocumentStylesToAllIFrames();
|
addDocumentStylesToAllIFrames();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// move the check out of current execution context
|
// move the check out of current execution context
|
||||||
// because some same-domain (!) iframes fail to load when their "contentDocument" is accessed (!)
|
// because some same-domain (!) iframes fail to load when their 'contentDocument' is accessed (!)
|
||||||
// namely gmail's old chat iframe talkgadget.google.com
|
// namely gmail's old chat iframe talkgadget.google.com
|
||||||
setTimeout(process.bind(null, mutations), 0);
|
setTimeout(process, 0, mutations);
|
||||||
});
|
});
|
||||||
|
|
||||||
function process(mutations) {
|
function process(mutations) {
|
||||||
for (var m = 0, ml = mutations.length; m < ml; m++) {
|
// var is slightly faster and MutationObserver may run a lot
|
||||||
var mutation = mutations[m];
|
// eslint-disable-next-line no-var
|
||||||
if (mutation.type === "childList") {
|
for (var m = 0, ml = mutations.length; m < ml; m++) {
|
||||||
for (var n = 0, nodes = mutation.addedNodes, nl = nodes.length; n < nl; n++) {
|
const mutation = mutations[m];
|
||||||
var node = nodes[n];
|
if (mutation.type === 'childList') {
|
||||||
if (node.localName === "iframe" && iframeIsDynamic(node)) {
|
// eslint-disable-next-line no-var
|
||||||
addDocumentStylesToIFrame(node);
|
for (var n = 0, nodes = mutation.addedNodes, nl = nodes.length; n < nl; n++) {
|
||||||
}
|
const node = nodes[n];
|
||||||
}
|
if (node.localName === 'iframe' && iframeIsDynamic(node)) {
|
||||||
}
|
addDocumentStylesToIFrame(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
iframeObserver.start = function() {
|
iframeObserver.start = () => {
|
||||||
// will be ignored by browser if already observing
|
// subsequent calls are ignored if already started observing
|
||||||
iframeObserver.observe(document, {childList: true, subtree: true});
|
iframeObserver.observe(document, {childList: true, subtree: true});
|
||||||
}
|
};
|
||||||
|
|
||||||
function orphanCheck() {
|
function orphanCheck() {
|
||||||
orphanCheckTimer = 0;
|
orphanCheckTimer = 0;
|
||||||
var port = chrome.runtime.connect();
|
const port = chrome.runtime.connect();
|
||||||
if (port) {
|
if (port) {
|
||||||
port.disconnect();
|
port.disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we're orphaned due to an extension update
|
// we're orphaned due to an extension update
|
||||||
// we can detach the mutation observer
|
// we can detach the mutation observer
|
||||||
iframeObserver.takeRecords();
|
iframeObserver.takeRecords();
|
||||||
iframeObserver.disconnect();
|
iframeObserver.disconnect();
|
||||||
iframeObserver = null;
|
iframeObserver = null;
|
||||||
// we can detach event listeners
|
// we can detach event listeners
|
||||||
document.removeEventListener("DOMContentLoaded", onDOMContentLoaded);
|
document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
|
||||||
// we can't detach chrome.runtime.onMessage because it's no longer connected internally
|
// we can't detach chrome.runtime.onMessage because it's no longer connected internally
|
||||||
|
|
||||||
// we can destroy global functions in this context to free up memory
|
// we can destroy global functions in this context to free up memory
|
||||||
[
|
[
|
||||||
'addDocumentStylesToAllIFrames',
|
'addDocumentStylesToAllIFrames',
|
||||||
'addDocumentStylesToIFrame',
|
'addDocumentStylesToIFrame',
|
||||||
'addStyleElement',
|
'addStyleElement',
|
||||||
'addStyleToIFrameSrcDoc',
|
'addStyleToIFrameSrcDoc',
|
||||||
'applyOnMessage',
|
'applyOnMessage',
|
||||||
'applySections',
|
'applySections',
|
||||||
'applyStyles',
|
'applyStyles',
|
||||||
'disableAll',
|
'doDisableAll',
|
||||||
'getDynamicIFrames',
|
'getDynamicIFrames',
|
||||||
'iframeIsDynamic',
|
'processDynamicIFrames',
|
||||||
'iframeIsLoadingSrcDoc',
|
'iframeIsDynamic',
|
||||||
'initObserver',
|
'iframeIsLoadingSrcDoc',
|
||||||
'removeStyle',
|
'initObserver',
|
||||||
'replaceAll',
|
'removeStyle',
|
||||||
'requestStyles',
|
'replaceAll',
|
||||||
'retireStyle'
|
'replaceAllpass2',
|
||||||
].forEach(fn => window[fn] = null);
|
'requestStyles',
|
||||||
|
'retireStyle'
|
||||||
|
].forEach(fn => (window[fn] = null));
|
||||||
|
|
||||||
// we can destroy global variables
|
// we can destroy global variables
|
||||||
g_styleElements = iframeObserver = retiredStyleIds = null;
|
styleElements = iframeObserver = retiredStyleIds = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
354
background.js
354
background.js
|
@ -1,43 +1,60 @@
|
||||||
/* globals openURL, wildcardAsRegExp, KEEP_CHANNEL_OPEN */
|
/* global getDatabase, getStyles, reportError */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// This happens right away, sometimes so fast that the content script isn't even ready. That's
|
// This happens right away, sometimes so fast that the content script isn't even ready. That's
|
||||||
// why the content script also asks for this stuff.
|
// why the content script also asks for this stuff.
|
||||||
chrome.webNavigation.onCommitted.addListener(webNavigationListener.bind(this, 'styleApply'));
|
chrome.webNavigation.onCommitted.addListener(data => {
|
||||||
chrome.webNavigation.onHistoryStateUpdated.addListener(webNavigationListener.bind(this, 'styleReplaceAll'));
|
webNavigationListener('styleApply', data);
|
||||||
chrome.webNavigation.onBeforeNavigate.addListener(webNavigationListener.bind(this, null));
|
});
|
||||||
|
chrome.webNavigation.onHistoryStateUpdated.addListener(data => {
|
||||||
|
webNavigationListener('styleReplaceAll', data);
|
||||||
|
});
|
||||||
|
chrome.webNavigation.onBeforeNavigate.addListener(data => {
|
||||||
|
webNavigationListener(null, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
function webNavigationListener(method, data) {
|
function webNavigationListener(method, data) {
|
||||||
getStyles({matchUrl: data.url, enabled: true, asHash: true}, styles => {
|
getStyles({matchUrl: data.url, enabled: true, asHash: true}, styles => {
|
||||||
// we can't inject chrome:// and chrome-extension:// pages except our own
|
// we can't inject chrome:// and chrome-extension:// pages except our own
|
||||||
// that request the styles on their own, so we'll only update the icon
|
// that request the styles on their own, so we'll only update the icon
|
||||||
if (method && !data.url.startsWith('chrome')) {
|
if (method && !data.url.startsWith('chrome')) {
|
||||||
chrome.tabs.sendMessage(data.tabId, {method, styles}, {frameId: data.frameId});
|
chrome.tabs.sendMessage(
|
||||||
}
|
data.tabId,
|
||||||
// main page frame id is 0
|
{method, styles},
|
||||||
if (data.frameId == 0) {
|
{frameId: data.frameId});
|
||||||
updateIcon({id: data.tabId, url: data.url}, styles);
|
}
|
||||||
}
|
// main page frame id is 0
|
||||||
});
|
if (data.frameId == 0) {
|
||||||
|
updateIcon({id: data.tabId, url: data.url}, styles);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// catch direct URL hash modifications not invoked via HTML5 history API
|
// catch direct URL hash modifications not invoked via HTML5 history API
|
||||||
|
const tabUrlHasHash = new Set();
|
||||||
|
|
||||||
var tabUrlHasHash = {};
|
chrome.tabs.onUpdated.addListener((tabId, info, tab) => {
|
||||||
chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
|
if (info.status != 'loading' || !info.url) {
|
||||||
if (info.status == "loading" && info.url) {
|
return;
|
||||||
if (info.url.indexOf('#') > 0) {
|
}
|
||||||
tabUrlHasHash[tabId] = true;
|
if (info.url.includes('#')) {
|
||||||
} else if (tabUrlHasHash[tabId]) {
|
tabUrlHasHash.add(tabId);
|
||||||
delete tabUrlHasHash[tabId];
|
} else if (tabUrlHasHash.has(tabId)) {
|
||||||
} else {
|
tabUrlHasHash.delete(tabId);
|
||||||
// do nothing since the tab neither had # before nor has # now
|
} else {
|
||||||
return;
|
// do nothing since the tab neither had # before nor has # now
|
||||||
}
|
return;
|
||||||
webNavigationListener("styleReplaceAll", {tabId: tabId, frameId: 0, url: info.url});
|
}
|
||||||
}
|
webNavigationListener('styleReplaceAll', {
|
||||||
|
tabId: tabId,
|
||||||
|
frameId: 0,
|
||||||
|
url: info.url,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
chrome.tabs.onRemoved.addListener(function(tabId, info) {
|
|
||||||
delete tabUrlHasHash[tabId];
|
chrome.tabs.onRemoved.addListener(tabId => {
|
||||||
|
tabUrlHasHash.delete(tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
// messaging
|
// messaging
|
||||||
|
@ -45,75 +62,78 @@ chrome.tabs.onRemoved.addListener(function(tabId, info) {
|
||||||
chrome.runtime.onMessage.addListener(onBackgroundMessage);
|
chrome.runtime.onMessage.addListener(onBackgroundMessage);
|
||||||
|
|
||||||
function onBackgroundMessage(request, sender, sendResponse) {
|
function onBackgroundMessage(request, sender, sendResponse) {
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case "getStyles":
|
|
||||||
var styles = getStyles(request, sendResponse);
|
case 'getStyles':
|
||||||
// check if this is a main content frame style enumeration
|
var styles = getStyles(request, sendResponse); // eslint-disable-line no-var
|
||||||
if (request.matchUrl && !request.id
|
// check if this is a main content frame style enumeration
|
||||||
&& sender && sender.tab && sender.frameId == 0
|
if (request.matchUrl && !request.id
|
||||||
&& sender.tab.url == request.matchUrl) {
|
&& sender && sender.tab && sender.frameId == 0
|
||||||
updateIcon(sender.tab, styles);
|
&& sender.tab.url == request.matchUrl) {
|
||||||
}
|
updateIcon(sender.tab, styles);
|
||||||
return KEEP_CHANNEL_OPEN;
|
}
|
||||||
case "saveStyle":
|
return KEEP_CHANNEL_OPEN;
|
||||||
saveStyle(request).then(sendResponse);
|
|
||||||
return KEEP_CHANNEL_OPEN;
|
case 'saveStyle':
|
||||||
case "invalidateCache":
|
saveStyle(request).then(sendResponse);
|
||||||
if (typeof invalidateCache != "undefined") {
|
return KEEP_CHANNEL_OPEN;
|
||||||
invalidateCache(false, request);
|
|
||||||
}
|
case 'invalidateCache':
|
||||||
break;
|
if (typeof invalidateCache != 'undefined') {
|
||||||
case "healthCheck":
|
invalidateCache(false, request);
|
||||||
getDatabase(function() { sendResponse(true); }, function() { sendResponse(false); });
|
}
|
||||||
return KEEP_CHANNEL_OPEN;
|
break;
|
||||||
case "openURL":
|
|
||||||
openURL(request);
|
case 'healthCheck':
|
||||||
break;
|
getDatabase(
|
||||||
case "styleDisableAll":
|
() => sendResponse(true),
|
||||||
// fallthru to prefChanged
|
() => sendResponse(false));
|
||||||
request = {prefName: 'disableAll', value: request.disableAll};
|
return KEEP_CHANNEL_OPEN;
|
||||||
case "prefChanged":
|
|
||||||
if (typeof request.value == 'boolean' && contextMenus[request.prefName]) {
|
case 'styleDisableAll':
|
||||||
chrome.contextMenus.update(request.prefName, {checked: request.value});
|
request = {prefName: 'disableAll', value: request.disableAll};
|
||||||
}
|
// fallthrough to prefChanged
|
||||||
break;
|
|
||||||
case "refreshAllTabs":
|
case 'prefChanged':
|
||||||
refreshAllTabs().then(sendResponse);
|
// eslint-disable-next-line no-use-before-define
|
||||||
return KEEP_CHANNEL_OPEN;
|
if (typeof request.value == 'boolean' && contextMenus[request.prefName]) {
|
||||||
}
|
chrome.contextMenus.update(request.prefName, {checked: request.value});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// commands (global hotkeys)
|
// commands (global hotkeys)
|
||||||
|
|
||||||
const browserCommands = {
|
const browserCommands = {
|
||||||
openManage() {
|
openManage() {
|
||||||
openURL({url: '/manage.html'});
|
openURL({url: '/manage.html'});
|
||||||
},
|
},
|
||||||
styleDisableAll(state) {
|
styleDisableAll(state) {
|
||||||
prefs.set('disableAll',
|
prefs.set('disableAll',
|
||||||
typeof state == 'boolean' ? state : !prefs.get('disableAll'));
|
typeof state == 'boolean' ? state : !prefs.get('disableAll'));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Not available in Firefox - https://bugzilla.mozilla.org/show_bug.cgi?id=1240350
|
// Not available in Firefox - https://bugzilla.mozilla.org/show_bug.cgi?id=1240350
|
||||||
if ('commands' in chrome) {
|
if ('commands' in chrome) {
|
||||||
chrome.commands.onCommand.addListener(command => browserCommands[command]());
|
chrome.commands.onCommand.addListener(command => browserCommands[command]());
|
||||||
}
|
}
|
||||||
|
|
||||||
// context menus
|
// context menus
|
||||||
|
|
||||||
const contextMenus = {
|
const contextMenus = {
|
||||||
'show-badge': {
|
'show-badge': {
|
||||||
title: 'menuShowBadge',
|
title: 'menuShowBadge',
|
||||||
click: info => prefs.set(info.menuItemId, info.checked),
|
click: info => prefs.set(info.menuItemId, info.checked),
|
||||||
},
|
},
|
||||||
'disableAll': {
|
'disableAll': {
|
||||||
title: 'disableAllStyles',
|
title: 'disableAllStyles',
|
||||||
click: browserCommands.styleDisableAll,
|
click: browserCommands.styleDisableAll,
|
||||||
},
|
},
|
||||||
'open-manager': {
|
'open-manager': {
|
||||||
title: 'openStylesManager',
|
title: 'openStylesManager',
|
||||||
click: browserCommands.openManage,
|
click: browserCommands.openManage,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// detect browsers without Delete by looking at the end of UA string
|
// detect browsers without Delete by looking at the end of UA string
|
||||||
|
@ -121,35 +141,35 @@ const contextMenus = {
|
||||||
// but skip CentBrowser: Safari/# plus Shockwave Flash in plugins
|
// but skip CentBrowser: Safari/# plus Shockwave Flash in plugins
|
||||||
// Vivaldi: Vivaldi/#
|
// Vivaldi: Vivaldi/#
|
||||||
if (/Vivaldi\/[\d.]+$/.test(navigator.userAgent)
|
if (/Vivaldi\/[\d.]+$/.test(navigator.userAgent)
|
||||||
|| /Safari\/[\d.]+$/.test(navigator.userAgent)
|
|| /Safari\/[\d.]+$/.test(navigator.userAgent)
|
||||||
&& ![...navigator.plugins].some(p => p.name == 'Shockwave Flash')) {
|
&& ![...navigator.plugins].some(p => p.name == 'Shockwave Flash')) {
|
||||||
contextMenus.editDeleteText = {
|
contextMenus.editDeleteText = {
|
||||||
title: 'editDeleteText',
|
title: 'editDeleteText',
|
||||||
contexts: ['editable'],
|
contexts: ['editable'],
|
||||||
documentUrlPatterns: [OWN_ORIGIN + 'edit*'],
|
documentUrlPatterns: [OWN_ORIGIN + 'edit*'],
|
||||||
click: (info, tab) => {
|
click: (info, tab) => {
|
||||||
chrome.tabs.sendMessage(tab.id, {method: 'editDeleteText'});
|
chrome.tabs.sendMessage(tab.id, {method: 'editDeleteText'});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.contextMenus.onClicked.addListener((info, tab) =>
|
chrome.contextMenus.onClicked.addListener((info, tab) =>
|
||||||
contextMenus[info.menuItemId].click(info, tab));
|
contextMenus[info.menuItemId].click(info, tab));
|
||||||
|
|
||||||
Object.keys(contextMenus).forEach(id => {
|
Object.keys(contextMenus).forEach(id => {
|
||||||
const item = Object.assign({id}, contextMenus[id]);
|
const item = Object.assign({id}, contextMenus[id]);
|
||||||
const prefValue = prefs.readOnlyValues[id];
|
const prefValue = prefs.readOnlyValues[id];
|
||||||
const isBoolean = typeof prefValue == 'boolean';
|
const isBoolean = typeof prefValue == 'boolean';
|
||||||
item.title = chrome.i18n.getMessage(item.title);
|
item.title = chrome.i18n.getMessage(item.title);
|
||||||
if (isBoolean) {
|
if (isBoolean) {
|
||||||
item.type = 'checkbox';
|
item.type = 'checkbox';
|
||||||
item.checked = prefValue;
|
item.checked = prefValue;
|
||||||
}
|
}
|
||||||
if (!item.contexts) {
|
if (!item.contexts) {
|
||||||
item.contexts = ['browser_action'];
|
item.contexts = ['browser_action'];
|
||||||
}
|
}
|
||||||
delete item.click;
|
delete item.click;
|
||||||
chrome.contextMenus.create(item, ignoreChromeError);
|
chrome.contextMenus.create(item, ignoreChromeError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -159,75 +179,75 @@ getDatabase(function() {}, reportError);
|
||||||
|
|
||||||
// When an edit page gets attached or detached, remember its state
|
// When an edit page gets attached or detached, remember its state
|
||||||
// so we can do the same to the next one to open.
|
// so we can do the same to the next one to open.
|
||||||
var editFullUrl = chrome.extension.getURL("edit.html");
|
const editFullUrl = OWN_ORIGIN + 'edit.html';
|
||||||
chrome.tabs.onAttached.addListener(function(tabId, data) {
|
chrome.tabs.onAttached.addListener((tabId, data) => {
|
||||||
chrome.tabs.get(tabId, function(tabData) {
|
chrome.tabs.get(tabId, tabData => {
|
||||||
if (tabData.url.indexOf(editFullUrl) == 0) {
|
if (tabData.url.startsWith(editFullUrl)) {
|
||||||
chrome.windows.get(tabData.windowId, {populate: true}, function(win) {
|
chrome.windows.get(tabData.windowId, {populate: true}, win => {
|
||||||
// If there's only one tab in this window, it's been dragged to new window
|
// If there's only one tab in this window, it's been dragged to new window
|
||||||
prefs.set("openEditInWindow", win.tabs.length == 1);
|
prefs.set('openEditInWindow', win.tabs.length == 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var codeMirrorThemes;
|
var codeMirrorThemes; // eslint-disable-line no-var
|
||||||
getCodeMirrorThemes(function(themes) {
|
getCodeMirrorThemes(themes => (codeMirrorThemes = themes));
|
||||||
codeMirrorThemes = themes;
|
|
||||||
});
|
|
||||||
|
|
||||||
// do not use prefs.get('version', null) as it might not yet be available
|
// do not use prefs.get('version', null) as it might not yet be available
|
||||||
chrome.storage.local.get('version', prefs => {
|
chrome.storage.local.get('version', prefs => {
|
||||||
// Open FAQs page once after installation to guide new users,
|
// Open FAQs page once after installation to guide new users,
|
||||||
// https://github.com/schomery/stylish-chrome/issues/22#issuecomment-279936160
|
// https://github.com/schomery/stylish-chrome/issues/22#issuecomment-279936160
|
||||||
if (!prefs.version) {
|
if (!prefs.version) {
|
||||||
// do not display the FAQs page in development mode
|
// do not display the FAQs page in development mode
|
||||||
if ('update_url' in chrome.runtime.getManifest()) {
|
if ('update_url' in chrome.runtime.getManifest()) {
|
||||||
let version = chrome.runtime.getManifest().version;
|
const version = chrome.runtime.getManifest().version;
|
||||||
chrome.storage.local.set({
|
chrome.storage.local.set({version}, () => {
|
||||||
version
|
window.setTimeout(() => {
|
||||||
}, () => {
|
chrome.tabs.create({
|
||||||
window.setTimeout(() => {
|
url: `http://add0n.com/stylus.html?version=${version}&type=install`
|
||||||
chrome.tabs.create({
|
});
|
||||||
url: 'http://add0n.com/stylus.html?version=' + version + '&type=install'
|
}, 3000);
|
||||||
});
|
});
|
||||||
}, 3000);
|
}
|
||||||
})
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
injectContentScripts();
|
injectContentScripts();
|
||||||
|
|
||||||
function injectContentScripts() {
|
function injectContentScripts() {
|
||||||
const contentScripts = chrome.app.getDetails().content_scripts;
|
const contentScripts = chrome.app.getDetails().content_scripts;
|
||||||
for (let cs of contentScripts) {
|
for (const cs of contentScripts) {
|
||||||
cs.matches = cs.matches.map(m => m == '<all_urls>' ? m : wildcardAsRegExp(m));
|
cs.matches = cs.matches.map(m => (
|
||||||
}
|
m == '<all_urls>' ? m : wildcardAsRegExp(m)
|
||||||
chrome.tabs.query({url: '*://*/*'}, tabs => {
|
));
|
||||||
for (let tab of tabs) {
|
}
|
||||||
for (let cs of contentScripts) {
|
// also inject in chrome://newtab/ page
|
||||||
for (let m of cs.matches) {
|
chrome.tabs.query({url: '*://*/*'}, tabs => {
|
||||||
if (m == '<all_urls>' || tab.url.match(m)) {
|
for (const tab of tabs) {
|
||||||
chrome.tabs.sendMessage(tab.id, {method: 'ping'}, pong => {
|
for (const cs of contentScripts) {
|
||||||
if (!pong) {
|
for (const m of cs.matches) {
|
||||||
chrome.tabs.executeScript(tab.id, {
|
if (m == '<all_urls>' || tab.url.match(m)) {
|
||||||
file: cs.js[0],
|
chrome.tabs.sendMessage(tab.id, {method: 'ping'}, pong => {
|
||||||
runAt: cs.run_at,
|
if (!pong) {
|
||||||
allFrames: cs.all_frames,
|
chrome.tabs.executeScript(tab.id, {
|
||||||
}, ignoreChromeError);
|
file: cs.js[0],
|
||||||
}
|
runAt: cs.run_at,
|
||||||
});
|
allFrames: cs.all_frames,
|
||||||
// inject the content script just once
|
}, ignoreChromeError);
|
||||||
break;
|
}
|
||||||
}
|
});
|
||||||
}
|
// inject the content script just once
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ignoreChromeError() {
|
function ignoreChromeError() {
|
||||||
chrome.runtime.lastError;
|
chrome.runtime.lastError; // eslint-disable-line no-unused-expressions
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/* globals getStyles, saveStyle, invalidateCache, refreshAllTabs */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const STYLISH_DUMP_FILE_EXT = '.txt';
|
const STYLISH_DUMP_FILE_EXT = '.txt';
|
||||||
|
@ -228,7 +227,7 @@ function importFromString(jsonString) {
|
||||||
|
|
||||||
|
|
||||||
$('#file-all-styles').onclick = () => {
|
$('#file-all-styles').onclick = () => {
|
||||||
getStyles({}, function (styles) {
|
getStylesSafe().then(styles => {
|
||||||
const text = JSON.stringify(styles, null, '\t');
|
const text = JSON.stringify(styles, null, '\t');
|
||||||
const fileName = generateFileName();
|
const fileName = generateFileName();
|
||||||
|
|
||||||
|
|
5
edit.js
5
edit.js
|
@ -1,4 +1,5 @@
|
||||||
/* globals stringAsRegExp */
|
/* eslint no-tabs: 0, no-var: 0, indent: [2, tab, {VariableDeclarator: 0, SwitchCase: 1}], quotes: 0 */
|
||||||
|
/* global CodeMirror */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var styleId = null;
|
var styleId = null;
|
||||||
|
@ -29,7 +30,7 @@ Array.prototype.rotate = function(amount) { // negative amount == rotate left
|
||||||
var r = this.slice(-amount, this.length);
|
var r = this.slice(-amount, this.length);
|
||||||
Array.prototype.push.apply(r, this.slice(0, this.length - r.length));
|
Array.prototype.push.apply(r, this.slice(0, this.length - r.length));
|
||||||
return r;
|
return r;
|
||||||
}
|
};
|
||||||
|
|
||||||
Object.defineProperty(Array.prototype, "last", {get: function() { return this[this.length - 1]; }});
|
Object.defineProperty(Array.prototype, "last", {get: function() { return this[this.length - 1]; }});
|
||||||
|
|
||||||
|
|
17
health.js
17
health.js
|
@ -1,11 +1,14 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
setTimeout(healthCheck, 0);
|
setTimeout(healthCheck, 0);
|
||||||
|
|
||||||
function healthCheck() {
|
function healthCheck() {
|
||||||
chrome.runtime.sendMessage({method: "healthCheck"}, function(ok) {
|
chrome.runtime.sendMessage({method: 'healthCheck'}, ok => {
|
||||||
if (ok === undefined) { // Chrome is starting up
|
if (ok === undefined) {
|
||||||
healthCheck();
|
// Chrome is starting up
|
||||||
} else if (!ok && confirm(t("dbError"))) {
|
healthCheck();
|
||||||
window.open("http://userstyles.org/dberror");
|
} else if (!ok && confirm(t('dbError'))) {
|
||||||
}
|
window.open('http://userstyles.org/dberror');
|
||||||
});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
30
install.js
30
install.js
|
@ -1,3 +1,6 @@
|
||||||
|
/* eslint-disable no-tabs, indent, quotes, no-var */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url") || location.href}, function(response) {
|
chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url") || location.href}, function(response) {
|
||||||
if (response.length == 0) {
|
if (response.length == 0) {
|
||||||
sendEvent("styleCanBeInstalledChrome");
|
sendEvent("styleCanBeInstalledChrome");
|
||||||
|
@ -17,7 +20,7 @@ chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url")
|
||||||
} else {
|
} else {
|
||||||
getResource(getMeta("stylish-code-chrome"), function(code) {
|
getResource(getMeta("stylish-code-chrome"), function(code) {
|
||||||
// this would indicate a failure (a style with settings?).
|
// this would indicate a failure (a style with settings?).
|
||||||
if (code == null) {
|
if (code === null) {
|
||||||
sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
|
sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
|
||||||
}
|
}
|
||||||
var json = JSON.parse(code);
|
var json = JSON.parse(code);
|
||||||
|
@ -30,7 +33,7 @@ chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url")
|
||||||
// everything's the same
|
// everything's the same
|
||||||
sendEvent("styleAlreadyInstalledChrome", {updateUrl: installedStyle.updateUrl});
|
sendEvent("styleAlreadyInstalledChrome", {updateUrl: installedStyle.updateUrl});
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
|
sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
|
||||||
});
|
});
|
||||||
|
@ -49,10 +52,12 @@ function sectionsAreEqual(a, b) {
|
||||||
|
|
||||||
function arraysAreEqual(a, b) {
|
function arraysAreEqual(a, b) {
|
||||||
// treat empty array and undefined as equivalent
|
// treat empty array and undefined as equivalent
|
||||||
if (typeof a == "undefined")
|
if (typeof a == "undefined") {
|
||||||
return (typeof b == "undefined") || (b.length == 0);
|
return (typeof b == "undefined") || (b.length == 0);
|
||||||
if (typeof b == "undefined")
|
}
|
||||||
|
if (typeof b == "undefined") {
|
||||||
return (typeof a == "undefined") || (a.length == 0);
|
return (typeof a == "undefined") || (a.length == 0);
|
||||||
|
}
|
||||||
if (a.length != b.length) {
|
if (a.length != b.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +83,7 @@ function stylishInstallChrome() {
|
||||||
// check for old style json
|
// check for old style json
|
||||||
var json = JSON.parse(code);
|
var json = JSON.parse(code);
|
||||||
json.method = "saveStyle";
|
json.method = "saveStyle";
|
||||||
chrome.runtime.sendMessage(json, function(response) {
|
chrome.runtime.sendMessage(json, function() {
|
||||||
sendEvent("styleInstalledChrome");
|
sendEvent("styleInstalledChrome");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -90,7 +95,10 @@ function stylishInstallChrome() {
|
||||||
document.addEventListener("stylishInstallChrome", stylishInstallChrome);
|
document.addEventListener("stylishInstallChrome", stylishInstallChrome);
|
||||||
function stylishUpdateChrome() {
|
function stylishUpdateChrome() {
|
||||||
orphanCheck();
|
orphanCheck();
|
||||||
chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url") || location.href}, function(response) {
|
chrome.runtime.sendMessage({
|
||||||
|
method: "getStyles",
|
||||||
|
url: getMeta("stylish-id-url") || location.href,
|
||||||
|
}, function(response) {
|
||||||
var style = response[0];
|
var style = response[0];
|
||||||
if (confirm(chrome.i18n.getMessage('styleUpdate', [style.name]))) {
|
if (confirm(chrome.i18n.getMessage('styleUpdate', [style.name]))) {
|
||||||
getResource(getMeta("stylish-code-chrome"), function(code) {
|
getResource(getMeta("stylish-code-chrome"), function(code) {
|
||||||
|
@ -123,14 +131,14 @@ function getResource(url, callback) {
|
||||||
if (xhr.status >= 400) {
|
if (xhr.status >= 400) {
|
||||||
callback(null);
|
callback(null);
|
||||||
} else {
|
} else {
|
||||||
callback(xhr.responseText);
|
callback(xhr.responseText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
if (url.length > 2000) {
|
if (url.length > 2000) {
|
||||||
var parts = url.split("?");
|
var parts = url.split("?");
|
||||||
xhr.open("POST", parts[0], true);
|
xhr.open("POST", parts[0], true);
|
||||||
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
|
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||||
xhr.send(parts[1]);
|
xhr.send(parts[1]);
|
||||||
} else {
|
} else {
|
||||||
xhr.open("GET", url, true);
|
xhr.open("GET", url, true);
|
||||||
|
@ -139,7 +147,7 @@ function getResource(url, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* stylish to stylus; https://github.com/schomery/stylish-chrome/issues/12 */
|
/* stylish to stylus; https://github.com/schomery/stylish-chrome/issues/12 */
|
||||||
(function (es) {
|
(function(es) {
|
||||||
es.forEach(e => {
|
es.forEach(e => {
|
||||||
[...e.childNodes].filter(n => n.nodeType == 3).forEach(n => {
|
[...e.childNodes].filter(n => n.nodeType == 3).forEach(n => {
|
||||||
n.nodeValue = n.nodeValue.replace('Stylish', 'Stylus');
|
n.nodeValue = n.nodeValue.replace('Stylish', 'Stylus');
|
||||||
|
@ -176,5 +184,5 @@ function orphanCheck() {
|
||||||
'sendEvent',
|
'sendEvent',
|
||||||
'stylishUpdateChrome',
|
'stylishUpdateChrome',
|
||||||
'stylishInstallChrome'
|
'stylishInstallChrome'
|
||||||
].forEach(fn => window[fn] = null);
|
].forEach(fn => (window[fn] = null));
|
||||||
}
|
}
|
||||||
|
|
153
localization.js
153
localization.js
|
@ -1,91 +1,94 @@
|
||||||
var template = {};
|
'use strict';
|
||||||
|
|
||||||
|
const template = {};
|
||||||
tDocLoader();
|
tDocLoader();
|
||||||
|
|
||||||
|
|
||||||
function t(key, params) {
|
function t(key, params) {
|
||||||
var s = chrome.i18n.getMessage(key, params)
|
const s = chrome.i18n.getMessage(key, params);
|
||||||
if (s == "") {
|
if (s == '') {
|
||||||
throw "Missing string '" + key + "'.";
|
throw `Missing string "${key}"`;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
|
||||||
function o(key) {
|
|
||||||
document.write(t(key));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function tE(id, key, attr, esc) {
|
function tE(id, key, attr, esc) {
|
||||||
if (attr) {
|
if (attr) {
|
||||||
document.getElementById(id).setAttribute(attr, t(key));
|
document.getElementById(id).setAttribute(attr, t(key));
|
||||||
} else if (typeof esc == "undefined" || esc) {
|
} else if (typeof esc == 'undefined' || esc) {
|
||||||
document.getElementById(id).appendChild(document.createTextNode(t(key)));
|
document.getElementById(id).appendChild(document.createTextNode(t(key)));
|
||||||
} else {
|
} else {
|
||||||
document.getElementById(id).innerHTML = t(key);
|
document.getElementById(id).innerHTML = t(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function tHTML(html) {
|
function tHTML(html) {
|
||||||
var node = document.createElement("div");
|
const node = document.createElement('div');
|
||||||
node.innerHTML = html.replace(/>\s+</g, '><'); // spaces are removed; use for an explicit space
|
node.innerHTML = html.replace(/>\s+</g, '><'); // spaces are removed; use for an explicit space
|
||||||
tNodeList(node.querySelectorAll("*"));
|
if (html.includes('i18n-')) {
|
||||||
var child = node.removeChild(node.firstElementChild);
|
tNodeList(node.querySelectorAll('*'));
|
||||||
node.remove();
|
}
|
||||||
return child;
|
return node.firstElementChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function tNodeList(nodes) {
|
function tNodeList(nodes) {
|
||||||
for (var n = 0; n < nodes.length; n++) {
|
for (const node of [...nodes]) {
|
||||||
var node = nodes[n];
|
// skip non-ELEMENT_NODE
|
||||||
if (node.nodeType != 1) { // not an ELEMENT_NODE
|
if (node.nodeType != 1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (node.localName == "template") {
|
if (node.localName == 'template') {
|
||||||
tNodeList(node.content.querySelectorAll("*"));
|
tNodeList(node.content.querySelectorAll('*'));
|
||||||
template[node.dataset.id] = node.content.firstElementChild;
|
template[node.dataset.id] = node.content.firstElementChild;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (var a = node.attributes.length - 1; a >= 0; a--) {
|
for (const attr of [...node.attributes]) {
|
||||||
var attr = node.attributes[a];
|
let name = attr.nodeName;
|
||||||
var name = attr.nodeName;
|
if (name.indexOf('i18n-') != 0) {
|
||||||
if (name.indexOf("i18n-") != 0) {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
name = name.substr(5); // 'i18n-'.length
|
||||||
name = name.substr(5); // "i18n-".length
|
const value = t(attr.value);
|
||||||
var value = t(attr.value);
|
switch (name) {
|
||||||
switch (name) {
|
case 'text':
|
||||||
case "text":
|
node.insertBefore(document.createTextNode(value), node.firstChild);
|
||||||
node.insertBefore(document.createTextNode(value), node.firstChild);
|
break;
|
||||||
break;
|
case 'html':
|
||||||
case "html":
|
node.insertAdjacentHTML('afterbegin', value);
|
||||||
node.insertAdjacentHTML("afterbegin", value);
|
break;
|
||||||
break;
|
default:
|
||||||
default:
|
node.setAttribute(name, value);
|
||||||
node.setAttribute(name, value);
|
}
|
||||||
}
|
node.removeAttribute(attr.nodeName);
|
||||||
node.removeAttribute(attr.nodeName);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function tDocLoader() {
|
function tDocLoader() {
|
||||||
// localize HEAD
|
// localize HEAD
|
||||||
tNodeList(document.all);
|
tNodeList(document.all);
|
||||||
|
|
||||||
// localize BODY
|
// localize BODY
|
||||||
const observer = new MutationObserver(function(mutations) {
|
const observer = new MutationObserver(mutations => {
|
||||||
for (var m = 0; m < mutations.length; m++) {
|
for (const mutation of mutations) {
|
||||||
tNodeList(mutations[m].addedNodes);
|
tNodeList(mutation.addedNodes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const onLoad = () => {
|
||||||
const onLoad = () => {
|
tDocLoader.stop();
|
||||||
tDocLoader.stop();
|
tNodeList(document.all);
|
||||||
tNodeList(document.all);
|
};
|
||||||
};
|
tDocLoader.start = () => {
|
||||||
tDocLoader.start = () => {
|
observer.observe(document, {subtree: true, childList: true});
|
||||||
observer.observe(document, {subtree: true, childList: true});
|
};
|
||||||
};
|
tDocLoader.stop = () => {
|
||||||
tDocLoader.stop = () => {
|
observer.disconnect();
|
||||||
observer.disconnect();
|
document.removeEventListener('DOMContentLoaded', onLoad);
|
||||||
document.removeEventListener('DOMContentLoaded', onLoad);
|
};
|
||||||
};
|
tDocLoader.start();
|
||||||
tDocLoader.start();
|
document.addEventListener('DOMContentLoaded', onLoad);
|
||||||
document.addEventListener('DOMContentLoaded', onLoad);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,6 @@
|
||||||
<script src="manage.js"></script>
|
<script src="manage.js"></script>
|
||||||
<script src="backup/fileSaveLoad.js"></script>
|
<script src="backup/fileSaveLoad.js"></script>
|
||||||
<script src="msgbox/msgbox.js"></script>
|
<script src="msgbox/msgbox.js"></script>
|
||||||
<script src="msgbox/confirm.js"></script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
69
manage.js
69
manage.js
|
@ -1,4 +1,5 @@
|
||||||
/* globals styleSectionsEqual */
|
/* global messageBox */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const installed = $('#installed');
|
const installed = $('#installed');
|
||||||
const TARGET_LABEL = t('appliesDisplay', '').trim();
|
const TARGET_LABEL = t('appliesDisplay', '').trim();
|
||||||
|
@ -11,7 +12,7 @@ getStylesSafe({code: false})
|
||||||
.then(initGlobalEvents);
|
.then(initGlobalEvents);
|
||||||
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener(msg => {
|
||||||
switch (msg.method) {
|
switch (msg.method) {
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
case 'styleAdded':
|
case 'styleAdded':
|
||||||
|
@ -45,7 +46,7 @@ function initGlobalEvents() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// remember scroll position on normal history navigation
|
// remember scroll position on normal history navigation
|
||||||
document.addEventListener('visibilitychange', event => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (document.visibilityState != 'visible') {
|
if (document.visibilityState != 'visible') {
|
||||||
rememberScrollPosition();
|
rememberScrollPosition();
|
||||||
}
|
}
|
||||||
|
@ -72,7 +73,7 @@ function initGlobalEvents() {
|
||||||
function showStyles(styles = []) {
|
function showStyles(styles = []) {
|
||||||
const sorted = styles
|
const sorted = styles
|
||||||
.map(style => ({name: style.name.toLocaleLowerCase(), style}))
|
.map(style => ({name: style.name.toLocaleLowerCase(), style}))
|
||||||
.sort((a, b) => a.name < b.name ? -1 : a.name == b.name ? 0 : 1);
|
.sort((a, b) => (a.name < b.name ? -1 : a.name == b.name ? 0 : 1));
|
||||||
const shouldRenderAll = history.state && history.state.scrollY > innerHeight;
|
const shouldRenderAll = history.state && history.state.scrollY > innerHeight;
|
||||||
const renderBin = document.createDocumentFragment();
|
const renderBin = document.createDocumentFragment();
|
||||||
tDocLoader.stop();
|
tDocLoader.stop();
|
||||||
|
@ -80,24 +81,28 @@ function showStyles(styles = []) {
|
||||||
// TODO: remember how many styles fit one page to display just that portion first next time
|
// TODO: remember how many styles fit one page to display just that portion first next time
|
||||||
function renderStyles(index) {
|
function renderStyles(index) {
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
while (index < sorted.length && (shouldRenderAll || performance.now() - t0 < 10)) {
|
while (index < sorted.length) {
|
||||||
renderBin.appendChild(createStyleElement(sorted[index++].style));
|
renderBin.appendChild(createStyleElement(sorted[index++].style));
|
||||||
|
if (!shouldRenderAll && performance.now() - t0 > 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($('#search').value) {
|
if ($('#search').value) {
|
||||||
// re-apply filtering on history Back
|
// re-apply filtering on history Back
|
||||||
searchStyles(true, renderBin);
|
searchStyles({immediately: true, container: renderBin});
|
||||||
}
|
}
|
||||||
installed.appendChild(renderBin);
|
installed.appendChild(renderBin);
|
||||||
if (index < sorted.length) {
|
if (index < sorted.length) {
|
||||||
setTimeout(renderStyles, 0, index);
|
setTimeout(renderStyles, 0, index);
|
||||||
}
|
} else if (shouldRenderAll && history.state && 'scrollY' in history.state) {
|
||||||
else if (shouldRenderAll && history.state && 'scrollY' in history.state) {
|
|
||||||
setTimeout(() => scrollTo(0, history.state.scrollY));
|
setTimeout(() => scrollTo(0, history.state.scrollY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// silence the inapplicable warning for async code
|
||||||
|
/* eslint no-use-before-define: [2, {"functions": false, "classes": false}] */
|
||||||
function createStyleElement(style) {
|
function createStyleElement(style) {
|
||||||
const entry = template.style.cloneNode(true);
|
const entry = template.style.cloneNode(true);
|
||||||
entry.classList.add(style.enabled ? 'enabled' : 'disabled');
|
entry.classList.add(style.enabled ? 'enabled' : 'disabled');
|
||||||
|
@ -131,9 +136,9 @@ function createStyleElement(style) {
|
||||||
regexpsBefore: '/',
|
regexpsBefore: '/',
|
||||||
regexpsAfter: '/',
|
regexpsAfter: '/',
|
||||||
};
|
};
|
||||||
for (let [name, target] of targets.entries()) {
|
for (const [name, target] of targets.entries()) {
|
||||||
for (let section of style.sections) {
|
for (const section of style.sections) {
|
||||||
for (let targetValue of section[name] || []) {
|
for (const targetValue of section[name] || []) {
|
||||||
target.add(
|
target.add(
|
||||||
(decorations[name + 'Before'] || '') +
|
(decorations[name + 'Before'] || '') +
|
||||||
targetValue.trim() +
|
targetValue.trim() +
|
||||||
|
@ -151,7 +156,7 @@ function createStyleElement(style) {
|
||||||
} else {
|
} else {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let container = appliesTo;
|
let container = appliesTo;
|
||||||
for (let target of targetsList) {
|
for (const target of targetsList) {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
container.appendChild(template.appliesToSeparator.cloneNode(true));
|
container.appendChild(template.appliesToSeparator.cloneNode(true));
|
||||||
}
|
}
|
||||||
|
@ -184,8 +189,10 @@ class EntryOnClick {
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const left = event.button == 0, middle = event.button == 1,
|
const left = event.button == 0;
|
||||||
shift = event.shiftKey, ctrl = event.ctrlKey;
|
const middle = event.button == 1;
|
||||||
|
const shift = event.shiftKey;
|
||||||
|
const ctrl = event.ctrlKey;
|
||||||
const openWindow = left && shift && !ctrl;
|
const openWindow = left && shift && !ctrl;
|
||||||
const openBackgroundTab = (middle && !shift) || (left && ctrl && !shift);
|
const openBackgroundTab = (middle && !shift) || (left && ctrl && !shift);
|
||||||
const openForegroundTab = (middle && shift) || (left && ctrl && shift);
|
const openForegroundTab = (middle && shift) || (left && ctrl && shift);
|
||||||
|
@ -215,10 +222,10 @@ class EntryOnClick {
|
||||||
}
|
}
|
||||||
|
|
||||||
static update(event) {
|
static update(event) {
|
||||||
const updatedCode = getClickedStyleElement(event).updatedCode;
|
const styleElement = getClickedStyleElement(event);
|
||||||
// update everything but name
|
// update everything but name
|
||||||
saveStyle(Object.assign(updatedCode, {
|
saveStyle(Object.assign(styleElement.updatedCode, {
|
||||||
id: element.styleId,
|
id: styleElement.styleId,
|
||||||
name: null,
|
name: null,
|
||||||
reason: 'update',
|
reason: 'update',
|
||||||
}));
|
}));
|
||||||
|
@ -340,10 +347,10 @@ class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkMd5() {
|
checkMd5() {
|
||||||
return this.download(this.md5Url).then(
|
return Updater.download(this.md5Url).then(
|
||||||
md5 => md5.length == 32
|
md5 => (md5.length == 32
|
||||||
? this.decideOnMd5(md5 != this.md5)
|
? this.decideOnMd5(md5 != this.md5)
|
||||||
: this.onFailure(-1),
|
: this.onFailure(-1)),
|
||||||
this.onFailure);
|
this.onFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,7 +362,7 @@ class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkFullCode({forceUpdate = false} = {}) {
|
checkFullCode({forceUpdate = false} = {}) {
|
||||||
return this.download(this.url).then(
|
return Updater.download(this.url).then(
|
||||||
text => this.handleJson(forceUpdate, JSON.parse(text)),
|
text => this.handleJson(forceUpdate, JSON.parse(text)),
|
||||||
this.onFailure);
|
this.onFailure);
|
||||||
}
|
}
|
||||||
|
@ -391,12 +398,12 @@ class Updater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
download(url) {
|
static download(url) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.onloadend = () => xhr.status == 200
|
xhr.onloadend = () => (xhr.status == 200
|
||||||
? resolve(xhr.responseText)
|
? resolve(xhr.responseText)
|
||||||
: reject(xhr.status);
|
: reject(xhr.status));
|
||||||
if (url.length > 2000) {
|
if (url.length > 2000) {
|
||||||
const [mainUrl, query] = url.split('?');
|
const [mainUrl, query] = url.split('?');
|
||||||
xhr.open('POST', mainUrl, true);
|
xhr.open('POST', mainUrl, true);
|
||||||
|
@ -412,19 +419,19 @@ class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function searchStyles(immediately, bin) {
|
function searchStyles({immediately, container}) {
|
||||||
const query = $('#search').value.toLocaleLowerCase();
|
const query = $('#search').value.toLocaleLowerCase();
|
||||||
if (query == (searchStyles.lastQuery || '') && !bin) {
|
if (query == (searchStyles.lastQuery || '') && !container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
searchStyles.lastQuery = query;
|
searchStyles.lastQuery = query;
|
||||||
if (!immediately) {
|
if (!immediately) {
|
||||||
clearTimeout(searchStyles.timeout);
|
clearTimeout(searchStyles.timeout);
|
||||||
searchStyles.timeout = setTimeout(doSearch, 200, true);
|
searchStyles.timeout = setTimeout(searchStyles, 200, {immediately: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let element of (bin || installed).children) {
|
for (const element of (container || installed).children) {
|
||||||
const {style} = cachedStyles.byId.get(element.styleId) || {};
|
const {style} = cachedStyles.byId.get(element.styleId) || {};
|
||||||
if (style) {
|
if (style) {
|
||||||
const isMatching = !query || isMatchingText(style.name) || isMatchingStyle(style);
|
const isMatching = !query || isMatchingText(style.name) || isMatchingStyle(style);
|
||||||
|
@ -433,8 +440,8 @@ function searchStyles(immediately, bin) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMatchingStyle(style) {
|
function isMatchingStyle(style) {
|
||||||
for (let section of style.sections) {
|
for (const section of style.sections) {
|
||||||
for (let prop in section) {
|
for (const prop in section) {
|
||||||
const value = section[prop];
|
const value = section[prop];
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case 'string':
|
case 'string':
|
||||||
|
@ -443,7 +450,7 @@ function searchStyles(immediately, bin) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case 'object':
|
||||||
for (let str of value) {
|
for (const str of value) {
|
||||||
if (isMatchingText(str)) {
|
if (isMatchingText(str)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
297
messaging.js
297
messaging.js
|
@ -1,187 +1,190 @@
|
||||||
|
/* global getStyleWithNoCode, applyOnMessage, onBackgroundMessage, getStyles */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// keep message channel open for sendResponse in chrome.runtime.onMessage listener
|
// keep message channel open for sendResponse in chrome.runtime.onMessage listener
|
||||||
const KEEP_CHANNEL_OPEN = true;
|
const KEEP_CHANNEL_OPEN = true;
|
||||||
const OWN_ORIGIN = chrome.runtime.getURL('');
|
const OWN_ORIGIN = chrome.runtime.getURL('');
|
||||||
|
|
||||||
|
|
||||||
function notifyAllTabs(request) {
|
function notifyAllTabs(request) {
|
||||||
// list all tabs including chrome-extension:// which can be ours
|
// list all tabs including chrome-extension:// which can be ours
|
||||||
if (request.codeIsUpdated === false && request.style) {
|
if (request.codeIsUpdated === false && request.style) {
|
||||||
request = Object.assign({}, request, {
|
request = Object.assign({}, request, {
|
||||||
style: getStyleWithNoCode(request.style)
|
style: getStyleWithNoCode(request.style)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
chrome.tabs.query({}, tabs => {
|
chrome.tabs.query({}, tabs => {
|
||||||
for (let tab of tabs) {
|
for (const tab of tabs) {
|
||||||
chrome.tabs.sendMessage(tab.id, request);
|
chrome.tabs.sendMessage(tab.id, request);
|
||||||
updateIcon(tab);
|
updateIcon(tab);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// notify all open popups
|
// notify all open popups
|
||||||
const reqPopup = Object.assign({}, request, {method: 'updatePopup', reason: request.method});
|
const reqPopup = Object.assign({}, request, {method: 'updatePopup', reason: request.method});
|
||||||
chrome.runtime.sendMessage(reqPopup);
|
chrome.runtime.sendMessage(reqPopup);
|
||||||
// notify self: the message no longer is sent to the origin in new Chrome
|
// notify self: the message no longer is sent to the origin in new Chrome
|
||||||
if (typeof applyOnMessage !== 'undefined') {
|
if (typeof applyOnMessage !== 'undefined') {
|
||||||
applyOnMessage(reqPopup);
|
applyOnMessage(reqPopup);
|
||||||
}
|
}
|
||||||
// notify self: pref changed by background page
|
// notify self: pref changed by background page
|
||||||
if (request.method == 'prefChanged' && typeof onBackgroundMessage !== 'undefined') {
|
if (request.method == 'prefChanged' && typeof onBackgroundMessage !== 'undefined') {
|
||||||
onBackgroundMessage(request);
|
onBackgroundMessage(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function refreshAllTabs() {
|
function refreshAllTabs() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// list all tabs including chrome-extension:// which can be ours
|
// list all tabs including chrome-extension:// which can be ours
|
||||||
chrome.tabs.query({}, tabs => {
|
chrome.tabs.query({}, tabs => {
|
||||||
const lastTab = tabs[tabs.length - 1];
|
const lastTab = tabs[tabs.length - 1];
|
||||||
for (let tab of tabs) {
|
for (const tab of tabs) {
|
||||||
getStyles({matchUrl: tab.url, enabled: true, asHash: true}, styles => {
|
getStyles({matchUrl: tab.url, enabled: true, asHash: true}, styles => {
|
||||||
const message = {method: 'styleReplaceAll', styles};
|
const message = {method: 'styleReplaceAll', styles};
|
||||||
if (tab.url == location.href && typeof applyOnMessage !== 'undefined') {
|
if (tab.url == location.href && typeof applyOnMessage !== 'undefined') {
|
||||||
applyOnMessage(message);
|
applyOnMessage(message);
|
||||||
} else {
|
} else {
|
||||||
chrome.tabs.sendMessage(tab.id, message);
|
chrome.tabs.sendMessage(tab.id, message);
|
||||||
}
|
}
|
||||||
updateIcon(tab, styles);
|
updateIcon(tab, styles);
|
||||||
if (tab == lastTab) {
|
if (tab == lastTab) {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function updateIcon(tab, styles) {
|
function updateIcon(tab, styles) {
|
||||||
// while NTP is still loading only process the request for its main frame with a real url
|
// while NTP is still loading only process the request for its main frame with a real url
|
||||||
// (but when it's loaded we should process style toggle requests from popups, for example)
|
// (but when it's loaded we should process style toggle requests from popups, for example)
|
||||||
if (tab.url == 'chrome://newtab/' && tab.status != 'complete') {
|
if (tab.url == 'chrome://newtab/' && tab.status != 'complete') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (styles) {
|
if (styles) {
|
||||||
// check for not-yet-existing tabs e.g. omnibox instant search
|
// check for not-yet-existing tabs e.g. omnibox instant search
|
||||||
chrome.tabs.get(tab.id, () => {
|
chrome.tabs.get(tab.id, () => {
|
||||||
if (!chrome.runtime.lastError) {
|
if (!chrome.runtime.lastError) {
|
||||||
stylesReceived(styles);
|
stylesReceived(styles);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getTabRealURL(tab).then(url => {
|
getTabRealURL(tab).then(url => {
|
||||||
// if we have access to this, call directly
|
// if we have access to this, call directly
|
||||||
// (Chrome no longer sends messages to the page itself)
|
// (Chrome no longer sends messages to the page itself)
|
||||||
const options = {method: 'getStyles', matchUrl: url, enabled: true, asHash: true};
|
const options = {method: 'getStyles', matchUrl: url, enabled: true, asHash: true};
|
||||||
if (typeof getStyles != 'undefined') {
|
if (typeof getStyles != 'undefined') {
|
||||||
getStyles(options, stylesReceived);
|
getStyles(options, stylesReceived);
|
||||||
} else {
|
} else {
|
||||||
chrome.runtime.sendMessage(options, stylesReceived);
|
chrome.runtime.sendMessage(options, stylesReceived);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function stylesReceived(styles) {
|
function stylesReceived(styles) {
|
||||||
let numStyles = styles.length;
|
let numStyles = styles.length;
|
||||||
if (numStyles === undefined) {
|
if (numStyles === undefined) {
|
||||||
// for 'styles' asHash:true fake the length by counting numeric ids manually
|
// for 'styles' asHash:true fake the length by counting numeric ids manually
|
||||||
numStyles = 0;
|
numStyles = 0;
|
||||||
for (let id of Object.keys(styles)) {
|
for (const id of Object.keys(styles)) {
|
||||||
numStyles += id.match(/^\d+$/) ? 1 : 0;
|
numStyles += id.match(/^\d+$/) ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll');
|
const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll');
|
||||||
const postfix = disableAll ? 'x' : numStyles == 0 ? 'w' : '';
|
const postfix = disableAll ? 'x' : numStyles == 0 ? 'w' : '';
|
||||||
chrome.browserAction.setIcon({
|
chrome.browserAction.setIcon({
|
||||||
path: {
|
path: {
|
||||||
// Material Design 2016 new size is 16px
|
// Material Design 2016 new size is 16px
|
||||||
16: `/images/icon/16${postfix}.png`, 32: `/images/icon/32${postfix}.png`,
|
16: `/images/icon/16${postfix}.png`, 32: `/images/icon/32${postfix}.png`,
|
||||||
// Chromium forks or non-chromium browsers may still use the traditional 19px
|
// Chromium forks or non-chromium browsers may still use the traditional 19px
|
||||||
19: `/images/icon/19${postfix}.png`, 38: `/images/icon/38${postfix}.png`,
|
19: `/images/icon/19${postfix}.png`, 38: `/images/icon/38${postfix}.png`,
|
||||||
},
|
},
|
||||||
tabId: tab.id
|
tabId: tab.id
|
||||||
}, () => {
|
}, () => {
|
||||||
// if the tab was just closed an error may occur,
|
// if the tab was just closed an error may occur,
|
||||||
// e.g. 'windowPosition' pref updated in edit.js::window.onbeforeunload
|
// e.g. 'windowPosition' pref updated in edit.js::window.onbeforeunload
|
||||||
if (!chrome.runtime.lastError) {
|
if (!chrome.runtime.lastError) {
|
||||||
const text = prefs.get('show-badge') && numStyles ? String(numStyles) : '';
|
const text = prefs.get('show-badge') && numStyles ? String(numStyles) : '';
|
||||||
chrome.browserAction.setBadgeText({text, tabId: tab.id});
|
chrome.browserAction.setBadgeText({text, tabId: tab.id});
|
||||||
chrome.browserAction.setBadgeBackgroundColor({
|
chrome.browserAction.setBadgeBackgroundColor({
|
||||||
color: prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal')
|
color: prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getActiveTab() {
|
function getActiveTab() {
|
||||||
return new Promise(resolve =>
|
return new Promise(resolve =>
|
||||||
chrome.tabs.query({currentWindow: true, active: true}, tabs =>
|
chrome.tabs.query({currentWindow: true, active: true}, tabs =>
|
||||||
resolve(tabs[0])));
|
resolve(tabs[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getActiveTabRealURL() {
|
function getActiveTabRealURL() {
|
||||||
return getActiveTab()
|
return getActiveTab()
|
||||||
.then(getTabRealURL);
|
.then(getTabRealURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getTabRealURL(tab) {
|
function getTabRealURL(tab) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (tab.url != 'chrome://newtab/') {
|
if (tab.url != 'chrome://newtab/') {
|
||||||
resolve(tab.url);
|
resolve(tab.url);
|
||||||
} else {
|
} else {
|
||||||
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => {
|
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => {
|
||||||
frame && resolve(frame.url);
|
frame && resolve(frame.url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// opens a tab or activates the already opened one,
|
// opens a tab or activates the already opened one,
|
||||||
// reuses the New Tab page if it's focused now
|
// reuses the New Tab page if it's focused now
|
||||||
function openURL({url, currentWindow = true}) {
|
function openURL({url, currentWindow = true}) {
|
||||||
if (!url.includes('://')) {
|
if (!url.includes('://')) {
|
||||||
url = chrome.runtime.getURL(url);
|
url = chrome.runtime.getURL(url);
|
||||||
}
|
}
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
chrome.tabs.query({url, currentWindow}, tabs => {
|
chrome.tabs.query({url, currentWindow}, tabs => {
|
||||||
if (tabs.length) {
|
if (tabs.length) {
|
||||||
activateTab(tabs[0]).then(resolve);
|
activateTab(tabs[0]).then(resolve);
|
||||||
} else {
|
} else {
|
||||||
getActiveTab().then(tab =>
|
getActiveTab().then(tab => (
|
||||||
tab && tab.url == 'chrome://newtab/'
|
tab && tab.url == 'chrome://newtab/'
|
||||||
? chrome.tabs.update({url}, resolve)
|
? chrome.tabs.update({url}, resolve)
|
||||||
: chrome.tabs.create({url}, resolve)
|
: chrome.tabs.create({url}, resolve)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function activateTab(tab) {
|
function activateTab(tab) {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
chrome.tabs.update(tab.id, {active: true}, resolve);
|
chrome.tabs.update(tab.id, {active: true}, resolve);
|
||||||
}),
|
}),
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
chrome.windows.update(tab.windowId, {focused: true}, resolve);
|
chrome.windows.update(tab.windowId, {focused: true}, resolve);
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function stringAsRegExp(s, flags) {
|
function stringAsRegExp(s, flags) {
|
||||||
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=*!|]/g, '\\$&'), flags);
|
return new RegExp(s.replace(/[{}()[\]/\\.+?^$:=*!|]/g, '\\$&'), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// expands * as .*?
|
// expands * as .*?
|
||||||
function wildcardAsRegExp(s, flags) {
|
function wildcardAsRegExp(s, flags) {
|
||||||
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=!|]/g, '\\$&').replace(/\*/g, '.*?'), flags);
|
return new RegExp(s.replace(/[{}()[\]/\\.+?^$:=!|]/g, '\\$&').replace(/\*/g, '.*?'), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,14 +192,14 @@ function wildcardAsRegExp(s, flags) {
|
||||||
// https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
|
// https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
|
||||||
// * Functions that contain object literals that contain __proto__, or get or set declarations.
|
// * Functions that contain object literals that contain __proto__, or get or set declarations.
|
||||||
const configureCommands = (() => ({
|
const configureCommands = (() => ({
|
||||||
get url () {
|
get url() {
|
||||||
return navigator.userAgent.indexOf('OPR') > -1 ?
|
return navigator.userAgent.includes('OPR')
|
||||||
'opera://settings/configureCommands' :
|
? 'opera://settings/configureCommands'
|
||||||
'chrome://extensions/configureCommands'
|
: 'chrome://extensions/configureCommands';
|
||||||
},
|
},
|
||||||
open: () => {
|
open: () => {
|
||||||
chrome.tabs.create({
|
chrome.tabs.create({
|
||||||
'url': configureCommands.url
|
'url': configureCommands.url
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}))();
|
}))();
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
/* globals configureCommands */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
function restore () {
|
function restore() {
|
||||||
chrome.runtime.getBackgroundPage(bg => {
|
chrome.runtime.getBackgroundPage(bg => {
|
||||||
$('#badgeDisabled').value = bg.prefs.get('badgeDisabled');
|
$('#badgeDisabled').value = bg.prefs.get('badgeDisabled');
|
||||||
$('#badgeNormal').value = bg.prefs.get('badgeNormal');
|
$('#badgeNormal').value = bg.prefs.get('badgeNormal');
|
||||||
|
@ -13,28 +12,28 @@ function restore () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function save () {
|
function save() {
|
||||||
chrome.runtime.getBackgroundPage(bg => {
|
chrome.runtime.getBackgroundPage(bg => {
|
||||||
bg.prefs.set('badgeDisabled', $('#badgeDisabled').value);
|
bg.prefs.set('badgeDisabled', $('#badgeDisabled').value);
|
||||||
bg.prefs.set('badgeNormal', $('#badgeNormal').value);
|
bg.prefs.set('badgeNormal', $('#badgeNormal').value);
|
||||||
localStorage.setItem('popupWidth', enforceValueRange('popupWidth'));
|
localStorage.setItem('popupWidth', enforceValueRange('popupWidth'));
|
||||||
bg.prefs.set(
|
bg.prefs.set(
|
||||||
'updateInterval',
|
'updateInterval',
|
||||||
Math.max(0, +$('#updateInterval').value)
|
Math.max(0, Number($('#updateInterval').value))
|
||||||
);
|
);
|
||||||
// display notification
|
// display notification
|
||||||
let status = $('#status');
|
const status = $('#status');
|
||||||
status.textContent = 'Options saved.';
|
status.textContent = 'Options saved.';
|
||||||
setTimeout(() => status.textContent = '', 750);
|
setTimeout(() => (status.textContent = ''), 750);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function enforceValueRange(id) {
|
function enforceValueRange(id) {
|
||||||
let element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
let value = Number(element.value);
|
|
||||||
const min = Number(element.min);
|
const min = Number(element.min);
|
||||||
const max = Number(element.max);
|
const max = Number(element.max);
|
||||||
|
let value = Number(element.value);
|
||||||
if (value < min || value > max) {
|
if (value < min || value > max) {
|
||||||
value = Math.max(min, Math.min(max, value));
|
value = Math.max(min, Math.min(max, value));
|
||||||
element.value = value;
|
element.value = value;
|
||||||
|
@ -53,40 +52,38 @@ $('[data-cmd="open-keyboard"]').textContent =
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
document.onclick = e => {
|
document.onclick = e => {
|
||||||
let cmd = e.target.dataset.cmd;
|
const cmd = e.target.dataset.cmd;
|
||||||
let total = 0, updated = 0;
|
let total = 0;
|
||||||
|
let updated = 0;
|
||||||
|
|
||||||
function update () {
|
function update() {
|
||||||
$('#update-counter').textContent = `${updated}/${total}`;
|
$('#update-counter').textContent = `${updated}/${total}`;
|
||||||
}
|
}
|
||||||
function done (target) {
|
|
||||||
|
function done(target) {
|
||||||
target.disabled = false;
|
target.disabled = false;
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
$('#update-counter').textContent = '';
|
$('#update-counter').textContent = '';
|
||||||
}, 750);
|
}, 750);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd) {
|
function check() {
|
||||||
|
|
||||||
case 'open-manage':
|
|
||||||
openURL({url: '/manage.html'});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case'check-updates':
|
|
||||||
e.target.disabled = true;
|
|
||||||
chrome.runtime.getBackgroundPage(bg => {
|
chrome.runtime.getBackgroundPage(bg => {
|
||||||
bg.update.perform((cmd, value) => {
|
bg.update.perform((cmd, value) => {
|
||||||
if (cmd === 'count') {
|
switch (cmd) {
|
||||||
total = value;
|
case 'count':
|
||||||
if (!total) {
|
total = value;
|
||||||
done(e.target);
|
if (!total) {
|
||||||
}
|
done(e.target);
|
||||||
}
|
}
|
||||||
else if (cmd === 'single-updated' || cmd === 'single-skipped') {
|
break;
|
||||||
updated += 1;
|
case 'single-updated':
|
||||||
if (total && updated === total) {
|
case 'single-skipped':
|
||||||
done(e.target);
|
updated++;
|
||||||
}
|
if (total && updated === total) {
|
||||||
|
done(e.target);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
|
@ -95,11 +92,21 @@ document.onclick = e => {
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
method: 'resetInterval'
|
method: 'resetInterval'
|
||||||
});
|
});
|
||||||
break;
|
}
|
||||||
|
|
||||||
case 'open-keyboard':
|
switch (cmd) {
|
||||||
configureCommands.open();
|
case 'open-manage':
|
||||||
break;
|
openURL({url: '/manage.html'});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'check-updates':
|
||||||
|
e.target.disabled = true;
|
||||||
|
check();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'open-keyboard':
|
||||||
|
configureCommands.open();
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
23
popup.js
23
popup.js
|
@ -1,11 +1,9 @@
|
||||||
/* globals configureCommands, openURL */
|
'use strict';
|
||||||
|
|
||||||
const RX_SUPPORTED_URLS = new RegExp(
|
|
||||||
`^(file|https?|ftps?):|^${OWN_ORIGIN}`);
|
|
||||||
let installed;
|
let installed;
|
||||||
|
|
||||||
|
|
||||||
getActiveTabRealURL().then(url => {
|
getActiveTabRealURL().then(url => {
|
||||||
|
const RX_SUPPORTED_URLS = new RegExp(`^(file|https?|ftps?):|^${OWN_ORIGIN}`);
|
||||||
const isUrlSupported = RX_SUPPORTED_URLS.test(url);
|
const isUrlSupported = RX_SUPPORTED_URLS.test(url);
|
||||||
Promise.all([
|
Promise.all([
|
||||||
isUrlSupported ? getStylesSafe({matchUrl: url}) : null,
|
isUrlSupported ? getStylesSafe({matchUrl: url}) : null,
|
||||||
|
@ -15,7 +13,7 @@ getActiveTabRealURL().then(url => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener(msg => {
|
||||||
if (msg.method == 'updatePopup') {
|
if (msg.method == 'updatePopup') {
|
||||||
switch (msg.reason) {
|
switch (msg.reason) {
|
||||||
case 'styleAdded':
|
case 'styleAdded':
|
||||||
|
@ -93,7 +91,7 @@ function initPopup(url) {
|
||||||
|
|
||||||
// For domain
|
// For domain
|
||||||
const domains = getDomains(url);
|
const domains = getDomains(url);
|
||||||
for (let domain of domains) {
|
for (const domain of domains) {
|
||||||
// Don't include TLD
|
// Don't include TLD
|
||||||
if (domains.length > 1 && !domain.includes('.')) {
|
if (domains.length > 1 && !domain.includes('.')) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -122,12 +120,13 @@ function showStyles(styles) {
|
||||||
installed.innerHTML = template.noStyles.outerHTML;
|
installed.innerHTML = template.noStyles.outerHTML;
|
||||||
} else {
|
} else {
|
||||||
const enabledFirst = prefs.get('popup.enabledFirst');
|
const enabledFirst = prefs.get('popup.enabledFirst');
|
||||||
styles.sort((a, b) =>
|
styles.sort((a, b) => (
|
||||||
enabledFirst && a.enabled !== b.enabled
|
enabledFirst && a.enabled !== b.enabled
|
||||||
? !(a.enabled < b.enabled) ? -1 : 1
|
? !(a.enabled < b.enabled) ? -1 : 1
|
||||||
: a.name.localeCompare(b.name));
|
: a.name.localeCompare(b.name)
|
||||||
|
));
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (let style of styles) {
|
for (const style of styles) {
|
||||||
fragment.appendChild(createStyleElement(style));
|
fragment.appendChild(createStyleElement(style));
|
||||||
}
|
}
|
||||||
installed.appendChild(fragment);
|
installed.appendChild(fragment);
|
||||||
|
@ -135,6 +134,8 @@ function showStyles(styles) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// silence the inapplicable warning for async code
|
||||||
|
/* eslint no-use-before-define: [2, {"functions": false, "classes": false}] */
|
||||||
function createStyleElement(style) {
|
function createStyleElement(style) {
|
||||||
const entry = template.style.cloneNode(true);
|
const entry = template.style.cloneNode(true);
|
||||||
entry.setAttribute('style-id', style.id);
|
entry.setAttribute('style-id', style.id);
|
||||||
|
@ -206,7 +207,7 @@ class EntryOnClick {
|
||||||
function confirm(ok) {
|
function confirm(ok) {
|
||||||
window.onkeydown = null;
|
window.onkeydown = null;
|
||||||
animateElement(box, {className: 'lights-on'})
|
animateElement(box, {className: 'lights-on'})
|
||||||
.then(() => box.dataset.display = false);
|
.then(() => (box.dataset.display = false));
|
||||||
if (ok) {
|
if (ok) {
|
||||||
deleteStyle(id).then(() => {
|
deleteStyle(id).then(() => {
|
||||||
// update view with 'No styles installed for this site' message
|
// update view with 'No styles installed for this site' message
|
||||||
|
@ -279,7 +280,7 @@ function handleUpdate(style) {
|
||||||
|
|
||||||
|
|
||||||
function handleDelete(id) {
|
function handleDelete(id) {
|
||||||
var styleElement = $(`[style-id="${id}"]`, installed);
|
const styleElement = $(`[style-id="${id}"]`, installed);
|
||||||
if (styleElement) {
|
if (styleElement) {
|
||||||
installed.removeChild(styleElement);
|
installed.removeChild(styleElement);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,169 +1,241 @@
|
||||||
var webSqlStorage = {
|
/* global getDatabase, reportError */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
migrate: function() {
|
const webSqlStorage = {
|
||||||
if (typeof openDatabase == "undefined") {
|
|
||||||
// No WebSQL - no migration!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
webSqlStorage.getStyles(function(styles) {
|
|
||||||
getDatabase(function(db) {
|
|
||||||
var tx = db.transaction(["styles"], "readwrite");
|
|
||||||
var os = tx.objectStore("styles");
|
|
||||||
styles.forEach(function(s) {
|
|
||||||
webSqlStorage.cleanStyle(s)
|
|
||||||
os.add(s);
|
|
||||||
});
|
|
||||||
// While this was running, the styles were loaded from the (empty) indexed db
|
|
||||||
setTimeout(function() {
|
|
||||||
invalidateCache(true);
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
}, null);
|
|
||||||
},
|
|
||||||
|
|
||||||
cleanStyle: function(s) {
|
migrate() {
|
||||||
delete s.id;
|
if (typeof openDatabase == 'undefined') {
|
||||||
s.sections.forEach(function(section) {
|
// No WebSQL - no migration!
|
||||||
delete section.id;
|
return;
|
||||||
["urls", "urlPrefixes", "domains", "regexps"].forEach(function(property) {
|
}
|
||||||
if (!section[property]) {
|
webSqlStorage.getStyles(styles => {
|
||||||
section[property] = [];
|
getDatabase(db => {
|
||||||
}
|
const tx = db.transaction(['styles'], 'readwrite');
|
||||||
});
|
const os = tx.objectStore('styles');
|
||||||
});
|
styles.forEach(s => {
|
||||||
},
|
webSqlStorage.cleanStyle(s);
|
||||||
|
os.add(s);
|
||||||
|
});
|
||||||
|
// While this was running, the styles were loaded from the (empty) indexed db
|
||||||
|
setTimeout(() => invalidateCache(true), 500);
|
||||||
|
});
|
||||||
|
}, null);
|
||||||
|
},
|
||||||
|
|
||||||
getStyles: function(callback) {
|
cleanStyle(s) {
|
||||||
webSqlStorage.getDatabase(function(db) {
|
delete s.id;
|
||||||
if (!db) {
|
s.sections.forEach(section => {
|
||||||
callback([]);
|
delete section.id;
|
||||||
return;
|
['urls', 'urlPrefixes', 'domains', 'regexps'].forEach(property => {
|
||||||
}
|
if (!section[property]) {
|
||||||
db.readTransaction(function (t) {
|
section[property] = [];
|
||||||
var where = "";
|
}
|
||||||
var params = [];
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
t.executeSql('SELECT DISTINCT s.*, se.id section_id, se.code, sm.name metaName, sm.value metaValue FROM styles s LEFT JOIN sections se ON se.style_id = s.id LEFT JOIN section_meta sm ON sm.section_id = se.id WHERE 1' + where + ' ORDER BY s.id, se.id, sm.id', params, function (t, r) {
|
getStyles(callback) {
|
||||||
var styles = [];
|
webSqlStorage.getDatabase(db => {
|
||||||
var currentStyle = null;
|
if (!db) {
|
||||||
var currentSection = null;
|
callback([]);
|
||||||
for (var i = 0; i < r.rows.length; i++) {
|
return;
|
||||||
var values = r.rows.item(i);
|
}
|
||||||
var metaName = null;
|
db.readTransaction(t => {
|
||||||
switch (values.metaName) {
|
const where = '';
|
||||||
case null:
|
const params = [];
|
||||||
break;
|
|
||||||
case "url":
|
|
||||||
metaName = "urls";
|
|
||||||
break;
|
|
||||||
case "url-prefix":
|
|
||||||
metaName = "urlPrefixes";
|
|
||||||
break;
|
|
||||||
case "domain":
|
|
||||||
var metaName = "domains";
|
|
||||||
break;
|
|
||||||
case "regexps":
|
|
||||||
var metaName = "regexps";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
var metaName = values.metaName + "s";
|
|
||||||
}
|
|
||||||
var metaValue = values.metaValue;
|
|
||||||
if (currentStyle == null || currentStyle.id != values.id) {
|
|
||||||
currentStyle = {id: values.id, url: values.url, updateUrl: values.updateUrl, md5Url: values.md5Url, name: values.name, enabled: values.enabled == "true", originalMd5: values.originalMd5, sections: []};
|
|
||||||
styles.push(currentStyle);
|
|
||||||
}
|
|
||||||
if (values.section_id != null) {
|
|
||||||
if (currentSection == null || currentSection.id != values.section_id) {
|
|
||||||
currentSection = {id: values.section_id, code: values.code};
|
|
||||||
currentStyle.sections.push(currentSection);
|
|
||||||
}
|
|
||||||
if (metaName && metaValue) {
|
|
||||||
if (currentSection[metaName]) {
|
|
||||||
currentSection[metaName].push(metaValue);
|
|
||||||
} else {
|
|
||||||
currentSection[metaName] = [metaValue];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback(styles);
|
|
||||||
}, reportError);
|
|
||||||
}, reportError);
|
|
||||||
}, reportError);
|
|
||||||
},
|
|
||||||
|
|
||||||
getDatabase: function(ready, error) {
|
t.executeSql(
|
||||||
try {
|
'SELECT DISTINCT ' +
|
||||||
stylishDb = openDatabase('stylish', '', 'Stylish Styles', 5*1024*1024);
|
's.*, se.id section_id, se.code, sm.name metaName, sm.value metaValue ' +
|
||||||
} catch (ex) {
|
'FROM styles s ' +
|
||||||
error();
|
'LEFT JOIN sections se ON se.style_id = s.id ' +
|
||||||
throw ex;
|
'LEFT JOIN section_meta sm ON sm.section_id = se.id ' +
|
||||||
}
|
'WHERE 1' + where + ' ' +
|
||||||
if (stylishDb.version == "") {
|
'ORDER BY s.id, se.id, sm.id',
|
||||||
// It didn't already exist, we have nothing to migrate.
|
params,
|
||||||
ready(null);
|
(t, r) => {
|
||||||
return;
|
const styles = [];
|
||||||
}
|
let currentStyle = null;
|
||||||
if (stylishDb.version == "1.0") {
|
let currentSection = null;
|
||||||
webSqlStorage.dbV11(stylishDb, error, ready);
|
for (let i = 0; i < r.rows.length; i++) {
|
||||||
} else if (stylishDb.version == "1.1") {
|
const values = r.rows.item(i);
|
||||||
webSqlStorage.dbV12(stylishDb, error, ready);
|
let metaName = null;
|
||||||
} else if (stylishDb.version == "1.2") {
|
switch (values.metaName) {
|
||||||
webSqlStorage.dbV13(stylishDb, error, ready);
|
case null:
|
||||||
} else if (stylishDb.version == "1.3") {
|
break;
|
||||||
webSqlStorage.dbV14(stylishDb, error, ready);
|
case 'url':
|
||||||
} else if (stylishDb.version == "1.4") {
|
metaName = 'urls';
|
||||||
webSqlStorage.dbV15(stylishDb, error, ready);
|
break;
|
||||||
} else {
|
case 'url-prefix':
|
||||||
ready(stylishDb);
|
metaName = 'urlPrefixes';
|
||||||
}
|
break;
|
||||||
},
|
case 'domain':
|
||||||
|
metaName = 'domains';
|
||||||
|
break;
|
||||||
|
case 'regexps':
|
||||||
|
metaName = 'regexps';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
metaName = values.metaName + 's';
|
||||||
|
}
|
||||||
|
const metaValue = values.metaValue;
|
||||||
|
if (currentStyle === null || currentStyle.id != values.id) {
|
||||||
|
currentStyle = {
|
||||||
|
id: values.id,
|
||||||
|
url: values.url,
|
||||||
|
updateUrl: values.updateUrl,
|
||||||
|
md5Url: values.md5Url,
|
||||||
|
name: values.name,
|
||||||
|
enabled: values.enabled == 'true',
|
||||||
|
originalMd5: values.originalMd5,
|
||||||
|
sections: []
|
||||||
|
};
|
||||||
|
styles.push(currentStyle);
|
||||||
|
}
|
||||||
|
if (values.section_id !== null) {
|
||||||
|
if (currentSection === null || currentSection.id != values.section_id) {
|
||||||
|
currentSection = {id: values.section_id, code: values.code};
|
||||||
|
currentStyle.sections.push(currentSection);
|
||||||
|
}
|
||||||
|
if (metaName && metaValue) {
|
||||||
|
if (currentSection[metaName]) {
|
||||||
|
currentSection[metaName].push(metaValue);
|
||||||
|
} else {
|
||||||
|
currentSection[metaName] = [metaValue];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(styles);
|
||||||
|
}, reportError);
|
||||||
|
}, reportError);
|
||||||
|
}, reportError);
|
||||||
|
},
|
||||||
|
|
||||||
dbV11: function(d, error, done) {
|
getDatabase(ready, error) {
|
||||||
d.changeVersion(d.version, '1.1', function (t) {
|
let stylishDb;
|
||||||
t.executeSql('CREATE TABLE styles (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, url TEXT, updateUrl TEXT, md5Url TEXT, name TEXT NOT NULL, code TEXT NOT NULL, enabled INTEGER NOT NULL, originalCode TEXT NULL);');
|
try {
|
||||||
t.executeSql('CREATE TABLE style_meta (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, style_id INTEGER NOT NULL, name TEXT NOT NULL, value TEXT NOT NULL);');
|
stylishDb = openDatabase('stylish', '', 'Stylish Styles', 5 * 1024 * 1024);
|
||||||
t.executeSql('CREATE INDEX style_meta_style_id ON style_meta (style_id);');
|
} catch (ex) {
|
||||||
}, error, function() { webSqlStorage.dbV12(d, error, done)});
|
error();
|
||||||
},
|
throw ex;
|
||||||
|
}
|
||||||
|
if (stylishDb.version == '') {
|
||||||
|
// It didn't already exist, we have nothing to migrate.
|
||||||
|
ready(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (stylishDb.version) {
|
||||||
|
case '1.0': return webSqlStorage.dbV11(stylishDb, error, ready);
|
||||||
|
case '1.1': return webSqlStorage.dbV12(stylishDb, error, ready);
|
||||||
|
case '1.2': return webSqlStorage.dbV13(stylishDb, error, ready);
|
||||||
|
case '1.3': return webSqlStorage.dbV14(stylishDb, error, ready);
|
||||||
|
case '1.4': return webSqlStorage.dbV15(stylishDb, error, ready);
|
||||||
|
default: ready(stylishDb);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
dbV12: function(d, error, done) {
|
dbV11(d, error, done) {
|
||||||
d.changeVersion(d.version, '1.2', function (t) {
|
d.changeVersion(d.version, '1.1', t => {
|
||||||
// add section table
|
t.executeSql(
|
||||||
t.executeSql('CREATE TABLE sections (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, style_id INTEGER NOT NULL, code TEXT NOT NULL);');
|
'CREATE TABLE styles (' +
|
||||||
t.executeSql('INSERT INTO sections (style_id, code) SELECT id, code FROM styles;');
|
'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
|
||||||
// switch meta to sections
|
'url TEXT, ' +
|
||||||
t.executeSql('DROP INDEX style_meta_style_id;');
|
'updateUrl TEXT, ' +
|
||||||
t.executeSql('CREATE TABLE section_meta (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, section_id INTEGER NOT NULL, name TEXT NOT NULL, value TEXT NOT NULL);');
|
'md5Url TEXT, ' +
|
||||||
t.executeSql('INSERT INTO section_meta (section_id, name, value) SELECT s.id, sm.name, sm.value FROM sections s INNER JOIN style_meta sm ON sm.style_id = s.style_id;');
|
'name TEXT NOT NULL, ' +
|
||||||
t.executeSql('CREATE INDEX section_meta_section_id ON section_meta (section_id);');
|
'code TEXT NOT NULL, ' +
|
||||||
t.executeSql('DROP TABLE style_meta;');
|
'enabled INTEGER NOT NULL, ' +
|
||||||
// drop extra fields from styles table
|
'originalCode TEXT NULL);');
|
||||||
t.executeSql('CREATE TABLE newstyles (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, url TEXT, updateUrl TEXT, md5Url TEXT, name TEXT NOT NULL, enabled INTEGER NOT NULL);');
|
t.executeSql(
|
||||||
t.executeSql('INSERT INTO newstyles (id, url, updateUrl, md5Url, name, enabled) SELECT id, url, updateUrl, md5Url, name, enabled FROM styles;');
|
'CREATE TABLE style_meta (' +
|
||||||
t.executeSql('DROP TABLE styles;');
|
'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
|
||||||
t.executeSql('ALTER TABLE newstyles RENAME TO styles;');
|
'style_id INTEGER NOT NULL, ' +
|
||||||
}, error, function() { webSqlStorage.dbV13(d, error, done)});
|
'name TEXT NOT NULL, ' +
|
||||||
},
|
'value TEXT NOT NULL);');
|
||||||
|
t.executeSql('CREATE INDEX style_meta_style_id ON style_meta (style_id);');
|
||||||
|
}, error, () => webSqlStorage.dbV12(d, error, done));
|
||||||
|
},
|
||||||
|
|
||||||
dbV13: function(d, error, done) {
|
dbV12(d, error, done) {
|
||||||
d.changeVersion(d.version, '1.3', function (t) {
|
d.changeVersion(d.version, '1.2', t => {
|
||||||
// clear out orphans
|
// add section table
|
||||||
t.executeSql('DELETE FROM section_meta WHERE section_id IN (SELECT sections.id FROM sections LEFT JOIN styles ON styles.id = sections.style_id WHERE styles.id IS NULL);');
|
t.executeSql(
|
||||||
t.executeSql('DELETE FROM sections WHERE id IN (SELECT sections.id FROM sections LEFT JOIN styles ON styles.id = sections.style_id WHERE styles.id IS NULL);');
|
'CREATE TABLE sections (' +
|
||||||
}, error, function() { webSqlStorage.dbV14(d, error, done)});
|
'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
|
||||||
},
|
'style_id INTEGER NOT NULL, ' +
|
||||||
|
'code TEXT NOT NULL);');
|
||||||
|
t.executeSql(
|
||||||
|
'INSERT INTO sections (style_id, code) SELECT id, code FROM styles;');
|
||||||
|
// switch meta to sections
|
||||||
|
t.executeSql(
|
||||||
|
'DROP INDEX style_meta_style_id;');
|
||||||
|
t.executeSql(
|
||||||
|
'CREATE TABLE section_meta (' +
|
||||||
|
'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
|
||||||
|
'section_id INTEGER NOT NULL, ' +
|
||||||
|
'name TEXT NOT NULL, ' +
|
||||||
|
'value TEXT NOT NULL);');
|
||||||
|
t.executeSql(
|
||||||
|
'INSERT INTO section_meta (section_id, name, value) ' +
|
||||||
|
'SELECT s.id, sm.name, sm.value FROM sections s ' +
|
||||||
|
'INNER JOIN style_meta sm ON sm.style_id = s.style_id;');
|
||||||
|
t.executeSql(
|
||||||
|
'CREATE INDEX section_meta_section_id ON section_meta (section_id);');
|
||||||
|
t.executeSql(
|
||||||
|
'DROP TABLE style_meta;');
|
||||||
|
// drop extra fields from styles table
|
||||||
|
t.executeSql(
|
||||||
|
'CREATE TABLE newstyles (' +
|
||||||
|
'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
|
||||||
|
'url TEXT, ' +
|
||||||
|
'updateUrl TEXT, ' +
|
||||||
|
'md5Url TEXT, ' +
|
||||||
|
'name TEXT NOT NULL, ' +
|
||||||
|
'enabled INTEGER NOT NULL);');
|
||||||
|
t.executeSql(
|
||||||
|
'INSERT INTO newstyles (id, url, updateUrl, md5Url, name, enabled) ' +
|
||||||
|
'SELECT id, url, updateUrl, md5Url, name, enabled FROM styles;');
|
||||||
|
t.executeSql(
|
||||||
|
'DROP TABLE styles;');
|
||||||
|
t.executeSql(
|
||||||
|
'ALTER TABLE newstyles ' +
|
||||||
|
'RENAME TO styles;');
|
||||||
|
}, error, () => webSqlStorage.dbV13(d, error, done));
|
||||||
|
},
|
||||||
|
|
||||||
dbV14: function(d, error, done) {
|
dbV13(d, error, done) {
|
||||||
d.changeVersion(d.version, '1.4', function (t) {
|
d.changeVersion(d.version, '1.3', t => {
|
||||||
t.executeSql('UPDATE styles SET url = null WHERE url = "undefined";');
|
// clear out orphans
|
||||||
}, error, function() { webSqlStorage.dbV15(d, error, done)});
|
t.executeSql(
|
||||||
},
|
'DELETE FROM section_meta ' +
|
||||||
|
'WHERE section_id IN (' +
|
||||||
|
'SELECT sections.id FROM sections ' +
|
||||||
|
'LEFT JOIN styles ON styles.id = sections.style_id ' +
|
||||||
|
'WHERE styles.id IS NULL' +
|
||||||
|
');');
|
||||||
|
t.executeSql(
|
||||||
|
'DELETE FROM sections ' +
|
||||||
|
'WHERE id IN (' +
|
||||||
|
'SELECT sections.id FROM sections ' +
|
||||||
|
'LEFT JOIN styles ON styles.id = sections.style_id ' +
|
||||||
|
'WHERE styles.id IS NULL);');
|
||||||
|
}, error, () => webSqlStorage.dbV14(d, error, done));
|
||||||
|
},
|
||||||
|
|
||||||
dbV15: function(d, error, done) {
|
dbV14(d, error, done) {
|
||||||
d.changeVersion(d.version, '1.5', function (t) {
|
d.changeVersion(d.version, '1.4', t => {
|
||||||
t.executeSql('ALTER TABLE styles ADD COLUMN originalMd5 TEXT NULL;');
|
t.executeSql(
|
||||||
}, error, function() { done(d); });
|
'UPDATE styles SET url = null ' +
|
||||||
}
|
'WHERE url = "undefined";');
|
||||||
}
|
}, error, () => webSqlStorage.dbV15(d, error, done));
|
||||||
|
},
|
||||||
|
|
||||||
|
dbV15(d, error, done) {
|
||||||
|
d.changeVersion(d.version, '1.5', t => {
|
||||||
|
t.executeSql(
|
||||||
|
'ALTER TABLE styles ' +
|
||||||
|
'ADD COLUMN originalMd5 TEXT NULL;');
|
||||||
|
}, error, () => done(d));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
1437
storage.js
1437
storage.js
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,7 @@
|
||||||
/* globals getStyles, saveStyle, prefs */
|
/* globals getStyles */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// TODO: refactor to make usable in manage::Updater
|
||||||
var update = {
|
var update = {
|
||||||
fetch: (resource, callback) => {
|
fetch: (resource, callback) => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
|
@ -30,6 +31,7 @@ var update = {
|
||||||
getStyles({}, (styles) => callback(styles.filter(style => style.updateUrl)));
|
getStyles({}, (styles) => callback(styles.filter(style => style.updateUrl)));
|
||||||
},
|
},
|
||||||
perform: (observe = function () {}) => {
|
perform: (observe = function () {}) => {
|
||||||
|
// TODO: use sectionsAreEqual
|
||||||
// from install.js
|
// from install.js
|
||||||
function arraysAreEqual (a, b) {
|
function arraysAreEqual (a, b) {
|
||||||
// treat empty array and undefined as equivalent
|
// treat empty array and undefined as equivalent
|
||||||
|
|
Loading…
Reference in New Issue
Block a user