Fix linting problems and more readable code

This commit is contained in:
DecentM 2017-01-20 11:31:15 +01:00
parent 970d48b17a
commit 560f5b666e
No known key found for this signature in database
GPG Key ID: C342F139246AF4CD
12 changed files with 3855 additions and 13379 deletions

View File

@ -1,3 +1,4 @@
/*jshint undef:false*/
var g_disableAll = false;
var g_styleElements = {};
var iframeObserver;
@ -11,8 +12,13 @@ function requestStyles() {
// 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 = {method: "getStyles", matchUrl: location.href, enabled: true, asHash: true};
if (location.href.indexOf(chrome.extension.getURL("")) == 0) {
var request = {
method: "getStyles",
matchUrl: location.href,
enabled: true,
asHash: true
};
if (location.href.indexOf(chrome.extension.getURL("")) === 0) {
var bg = chrome.extension.getBackgroundPage();
if (bg && bg.getStyles) {
// apply styles immediately, then proceed with a normal request that will update the icon
@ -22,8 +28,20 @@ function requestStyles() {
chrome.runtime.sendMessage(request, applyStyles);
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
// Also handle special request just for the pop-up
function styleAdded() {
if (request.style.enabled) {
chrome.runtime.sendMessage({
method: "getStyles",
matchUrl: location.href,
enabled: true,
id: request.style.id,
asHash: true
}, applyStyles);
}
}
switch (request.method == "updatePopup" ? request.reason : request.method) {
case "styleDeleted":
removeStyle(request.id, document);
@ -31,15 +49,13 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
case "styleUpdated":
if (request.style.enabled) {
retireStyle(request.style.id);
// fallthrough to "styleAdded"
styleAdded();
} else {
removeStyle(request.style.id, document);
}
break;
}
case "styleAdded":
if (request.style.enabled) {
chrome.runtime.sendMessage({method: "getStyles", matchUrl: location.href, enabled: true, id: request.style.id, asHash: true}, applyStyles);
}
styleAdded();
break;
case "styleApply":
applyStyles(request.styles);
@ -69,12 +85,12 @@ function disableAll(disable) {
}
function disableSheets(disable, doc) {
Array.prototype.forEach.call(doc.styleSheets, function(stylesheet) {
Array.prototype.forEach.call(doc.styleSheets, function (stylesheet) {
if (stylesheet.ownerNode.classList.contains("stylish")) {
stylesheet.disabled = disable;
}
});
getDynamicIFrames(doc).forEach(function(iframe) {
getDynamicIFrames(doc).forEach(function (iframe) {
if (!disable) {
// update the IFRAME if it was created while the observer was disconnected
addDocumentStylesToIFrame(iframe);
@ -90,10 +106,10 @@ function removeStyle(id, doc) {
if (e) {
e.remove();
}
if (doc == document && Object.keys(g_styleElements).length == 0) {
if (doc == document && Object.keys(g_styleElements).length === 0) {
iframeObserver.disconnect();
}
getDynamicIFrames(doc).forEach(function(iframe) {
getDynamicIFrames(doc).forEach(function (iframe) {
removeStyle(id, iframe.contentDocument);
});
}
@ -114,7 +130,7 @@ function retireStyle(id, doc) {
if (e) {
e.id = "stylish-" + deadID;
}
getDynamicIFrames(doc).forEach(function(iframe) {
getDynamicIFrames(doc).forEach(function (iframe) {
retireStyle(id, iframe.contentDocument);
});
}
@ -142,14 +158,14 @@ function applyStyles(styleHash) {
document.head.appendChild(document.getElementById(id));
}
}
document.addEventListener("DOMContentLoaded", function() {
document.addEventListener("DOMContentLoaded", function () {
addDocumentStylesToAllIFrames();
iframeObserver.start();
});
}
if (retiredStyleIds.length) {
setTimeout(function() {
setTimeout(function () {
while (retiredStyleIds.length) {
removeStyle(retiredStyleIds.shift(), document);
}
@ -173,7 +189,7 @@ function applySections(styleId, sections) {
styleElement.setAttribute("id", "stylish-" + styleId);
styleElement.setAttribute("class", "stylish");
styleElement.setAttribute("type", "text/css");
styleElement.appendChild(document.createTextNode(sections.map(function(section) {
styleElement.appendChild(document.createTextNode(sections.map(function (section) {
return section.code;
}).join("\n")));
addStyleElement(styleElement, document);
@ -186,7 +202,7 @@ function addStyleElement(styleElement, doc) {
}
doc.documentElement.appendChild(doc.importNode(styleElement, true))
.disabled = g_disableAll;
getDynamicIFrames(doc).forEach(function(iframe) {
getDynamicIFrames(doc).forEach(function (iframe) {
if (iframeIsLoadingSrcDoc(iframe)) {
addStyleToIFrameSrcDoc(iframe, styleElement);
} else {
@ -224,7 +240,7 @@ function iframeIsDynamic(f) {
// Cross-origin, so it's not a dynamic iframe
return false;
}
return href == document.location.href || href.indexOf("about:") == 0;
return href == document.location.href || href.indexOf("about:") === 0;
}
function iframeIsLoadingSrcDoc(f) {
@ -244,9 +260,11 @@ function addStyleToIFrameSrcDoc(iframe, styleElement) {
function replaceAll(newStyles, doc, pass2) {
var oldStyles = [].slice.call(doc.querySelectorAll("STYLE.stylish" + (pass2 ? "[id$='-ghost']" : "")));
if (!pass2) {
oldStyles.forEach(function(style) { style.id += "-ghost"; });
oldStyles.forEach(function (style) {
style.id += "-ghost";
});
}
getDynamicIFrames(doc).forEach(function(iframe) {
getDynamicIFrames(doc).forEach(function (iframe) {
replaceAll(newStyles, iframe.contentDocument, pass2);
});
if (doc == document && !pass2) {
@ -255,13 +273,15 @@ function replaceAll(newStyles, doc, pass2) {
replaceAll(newStyles, doc, true);
}
if (pass2) {
oldStyles.forEach(function(style) { style.remove(); });
oldStyles.forEach(function (style) {
style.remove();
});
}
}
// Observe dynamic IFRAMEs being added
function initObserver() {
iframeObserver = new MutationObserver(function(mutations) {
iframeObserver = new MutationObserver(function (mutations) {
if (mutations.length > 1000) {
// use a much faster method for very complex pages with 100,000 mutations
// (observer usually receives 1k-10k mutations per call)
@ -288,8 +308,11 @@ function initObserver() {
}
}
iframeObserver.start = function() {
iframeObserver.start = function () {
// will be ignored by browser if already observing
iframeObserver.observe(document, {childList: true, subtree: true});
}
iframeObserver.observe(document, {
childList: true,
subtree: true
});
};
}

View File

@ -1,6 +1,9 @@
/*jshint undef:false*/
var frameIdMessageable;
runTryCatch(function() {
chrome.tabs.sendMessage(0, {}, {frameId: 0}, function() {
runTryCatch(function () {
chrome.tabs.sendMessage(0, {}, {
frameId: 0
}, function () {
var clearError = chrome.runtime.lastError;
frameIdMessageable = true;
});
@ -14,28 +17,41 @@ if ("onHistoryStateUpdated" in chrome.webNavigation) {
chrome.webNavigation.onHistoryStateUpdated.addListener(webNavigationListener.bind(this, "styleReplaceAll"));
}
chrome.webNavigation.onBeforeNavigate.addListener(webNavigationListener.bind(this, null));
function webNavigationListener(method, data) {
// Until Chrome 41, we can't target a frame with a message
// (https://developer.chrome.com/extensions/tabs#method-sendMessage)
// so a style affecting a page with an iframe will affect the main page as well.
// Skip doing this for frames in pre-41 to prevent page flicker.
if (data.frameId != 0 && !frameIdMessageable) {
if (data.frameId !== 0 && !frameIdMessageable) {
return;
}
getStyles({matchUrl: data.url, enabled: true, asHash: true}, function(styleHash) {
getStyles({
matchUrl: data.url,
enabled: true,
asHash: true
}, function (styleHash) {
if (method) {
chrome.tabs.sendMessage(data.tabId, {method: method, styles: styleHash},
frameIdMessageable ? {frameId: data.frameId} : undefined);
chrome.tabs.sendMessage(data.tabId, {
method: method,
styles: styleHash
},
frameIdMessageable ? {
frameId: data.frameId
} : undefined);
}
if (data.frameId == 0) {
updateIcon({id: data.tabId, url: data.url}, styleHash);
if (data.frameId === 0) {
updateIcon({
id: data.tabId,
url: data.url
}, styleHash);
}
});
}
// catch direct URL hash modifications not invoked via HTML5 history API
var tabUrlHasHash = {};
chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
chrome.tabs.onUpdated.addListener(function (tabId, info, tab) {
if (info.status == "loading" && info.url) {
if (info.url.indexOf('#') > 0) {
tabUrlHasHash[tabId] = true;
@ -45,21 +61,23 @@ chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
// 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) {
chrome.tabs.onRemoved.addListener(function (tabId, info) {
delete tabUrlHasHash[tabId];
});
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
switch (request.method) {
case "getStyles":
var styles = getStyles(request, sendResponse);
// check if this is a main content frame style enumeration
if (request.matchUrl && !request.id
&& sender && sender.tab && sender.frameId == 0
&& sender.tab.url == request.matchUrl) {
if (request.matchUrl && !request.id && sender && sender.tab && sender.frameId === 0 && sender.tab.url == request.matchUrl) {
updateIcon(sender.tab, styles);
}
return true;
@ -72,17 +90,25 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
}
break;
case "healthCheck":
getDatabase(function() { sendResponse(true); }, function() { sendResponse(false); });
getDatabase(function () {
sendResponse(true);
}, function () {
sendResponse(false);
});
return true;
case "openURL":
openURL(request);
break;
case "styleDisableAll":
chrome.contextMenus.update("disableAll", {checked: request.disableAll});
chrome.contextMenus.update("disableAll", {
checked: request.disableAll
});
break;
case "prefChanged":
if (request.prefName == "show-badge") {
chrome.contextMenus.update("show-badge", {checked: request.value});
chrome.contextMenus.update("show-badge", {
checked: request.value
});
}
break;
}
@ -91,14 +117,18 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
// Not available in Firefox - https://bugzilla.mozilla.org/show_bug.cgi?id=1240350
if ("commands" in chrome) {
chrome.commands.onCommand.addListener(function(command) {
chrome.commands.onCommand.addListener(function (command) {
switch (command) {
case "openManage":
openURL({url: chrome.extension.getURL("manage.html")});
openURL({
url: chrome.extension.getURL("manage.html")
});
break;
case "styleDisableAll":
disableAllStylesToggle();
chrome.contextMenus.update("disableAll", {checked: prefs.get("disableAll")});
chrome.contextMenus.update("disableAll", {
checked: prefs.get("disableAll")
});
break;
}
});
@ -106,18 +136,28 @@ if ("commands" in chrome) {
// contextMenus API is present in ancient Chrome but it throws an exception
// upon encountering the unsupported parameter value "browser_action", so we have to catch it.
runTryCatch(function() {
runTryCatch(function () {
chrome.contextMenus.create({
id: "show-badge", title: chrome.i18n.getMessage("menuShowBadge"),
type: "checkbox", contexts: ["browser_action"], checked: prefs.get("show-badge")
}, function() { var clearError = chrome.runtime.lastError });
id: "show-badge",
title: chrome.i18n.getMessage("menuShowBadge"),
type: "checkbox",
contexts: ["browser_action"],
checked: prefs.get("show-badge")
}, function () {
var clearError = chrome.runtime.lastError;
});
chrome.contextMenus.create({
id: "disableAll", title: chrome.i18n.getMessage("disableAllStyles"),
type: "checkbox", contexts: ["browser_action"], checked: prefs.get("disableAll")
}, function() { var clearError = chrome.runtime.lastError });
id: "disableAll",
title: chrome.i18n.getMessage("disableAllStyles"),
type: "checkbox",
contexts: ["browser_action"],
checked: prefs.get("disableAll")
}, function () {
var clearError = chrome.runtime.lastError;
});
});
chrome.contextMenus.onClicked.addListener(function(info, tab) {
chrome.contextMenus.onClicked.addListener(function (info, tab) {
if (info.menuItemId == "disableAll") {
disableAllStylesToggle(info.checked);
} else {
@ -133,14 +173,16 @@ function disableAllStylesToggle(newState) {
}
// Get the DB so that any first run actions will be performed immediately when the background page loads.
getDatabase(function() {}, reportError);
getDatabase(function () {}, reportError);
// When an edit page gets attached or detached, remember its state so we can do the same to the next one to open.
var editFullUrl = chrome.extension.getURL("edit.html");
chrome.tabs.onAttached.addListener(function(tabId, data) {
chrome.tabs.get(tabId, function(tabData) {
if (tabData.url.indexOf(editFullUrl) == 0) {
chrome.windows.get(tabData.windowId, {populate: true}, function(win) {
chrome.tabs.onAttached.addListener(function (tabId, data) {
chrome.tabs.get(tabId, function (tabData) {
if (tabData.url.indexOf(editFullUrl) === 0) {
chrome.windows.get(tabData.windowId, {
populate: true
}, function (win) {
// If there's only one tab in this window, it's been dragged to new window
prefs.set("openEditInWindow", win.tabs.length == 1);
});
@ -149,13 +191,19 @@ chrome.tabs.onAttached.addListener(function(tabId, data) {
});
function openURL(options) {
chrome.tabs.query({currentWindow: true, url: options.url}, function(tabs) {
chrome.tabs.query({
currentWindow: true,
url: options.url
}, function (tabs) {
// switch to an existing tab with the requested url
if (tabs.length) {
chrome.tabs.highlight({windowId: tabs[0].windowId, tabs: tabs[0].index}, function (window) {});
chrome.tabs.highlight({
windowId: tabs[0].windowId,
tabs: tabs[0].index
}, function (window) {});
} else {
delete options.method;
getActiveTab(function(tab) {
getActiveTab(function (tab) {
// re-use an active new tab page
chrome.tabs[tab.url == "chrome://newtab/" ? "update" : "create"](options);
});
@ -164,6 +212,6 @@ function openURL(options) {
}
var codeMirrorThemes;
getCodeMirrorThemes(function(themes) {
getCodeMirrorThemes(function (themes) {
codeMirrorThemes = themes;
});

File diff suppressed because it is too large Load Diff

752
edit.js

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
/*jshint undef:false*/
healthCheck();
function healthCheck() {

View File

@ -1,5 +1,9 @@
chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url") || location.href}, function(response) {
if (response.length == 0) {
/*jshint undef:false*/
chrome.runtime.sendMessage({
method: "getStyles",
url: getMeta("stylish-id-url") || location.href
}, function (response) {
if (response.length === 0) {
sendEvent("styleCanBeInstalledChrome");
} else {
var installedStyle = response[0];
@ -7,32 +11,42 @@ chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url")
// use the md5 if available
var md5Url = getMeta("stylish-md5-url");
if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) {
getResource(md5Url, function(md5) {
getResource(md5Url, function (md5) {
if (md5 == installedStyle.originalMd5) {
sendEvent("styleAlreadyInstalledChrome", {updateUrl: installedStyle.updateUrl});
sendEvent("styleAlreadyInstalledChrome", {
updateUrl: installedStyle.updateUrl
});
} else {
sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
sendEvent("styleCanBeUpdatedChrome", {
updateUrl: installedStyle.updateUrl
});
}
});
} else {
getResource(getMeta("stylish-code-chrome"), function(code) {
getResource(getMeta("stylish-code-chrome"), function (code) {
// this would indicate a failure (a style with settings?).
if (code == null) {
sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
if (code === null) {
sendEvent("styleCanBeUpdatedChrome", {
updateUrl: installedStyle.updateUrl
});
}
var json = JSON.parse(code);
if (json.sections.length == installedStyle.sections.length) {
if (json.sections.every(function(section) {
return installedStyle.sections.some(function(installedSection) {
if (json.sections.every(function (section) {
return installedStyle.sections.some(function (installedSection) {
return sectionsAreEqual(section, installedSection);
});
})) {
// everything's the same
sendEvent("styleAlreadyInstalledChrome", {updateUrl: installedStyle.updateUrl});
sendEvent("styleAlreadyInstalledChrome", {
updateUrl: installedStyle.updateUrl
});
return;
};
}
sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
}
sendEvent("styleCanBeUpdatedChrome", {
updateUrl: installedStyle.updateUrl
});
});
}
}
@ -42,7 +56,7 @@ function sectionsAreEqual(a, b) {
if (a.code != b.code) {
return false;
}
return ["urls", "urlPrefixes", "domains", "regexps"].every(function(attribute) {
return ["urls", "urlPrefixes", "domains", "regexps"].every(function (attribute) {
return arraysAreEqual(a[attribute], b[attribute]);
});
}
@ -50,13 +64,13 @@ function sectionsAreEqual(a, b) {
function arraysAreEqual(a, b) {
// treat empty array and undefined as equivalent
if (typeof a == "undefined")
return (typeof b == "undefined") || (b.length == 0);
return (typeof b == "undefined") || (b.length === 0);
if (typeof b == "undefined")
return (typeof a == "undefined") || (a.length == 0);
return (typeof a == "undefined") || (a.length === 0);
if (a.length != b.length) {
return false;
}
return a.every(function(entry) {
return a.every(function (entry) {
return b.indexOf(entry) != -1;
});
}
@ -65,18 +79,20 @@ function sendEvent(type, data) {
if (typeof data == "undefined") {
data = null;
}
var stylishEvent = new CustomEvent(type, {detail: data});
var stylishEvent = new CustomEvent(type, {
detail: data
});
document.dispatchEvent(stylishEvent);
}
document.addEventListener("stylishInstallChrome", function() {
getResource(getMeta("stylish-description"), function(name) {
document.addEventListener("stylishInstallChrome", function () {
getResource(getMeta("stylish-description"), function (name) {
if (confirm(chrome.i18n.getMessage('styleInstall', [name]))) {
getResource(getMeta("stylish-code-chrome"), function(code) {
getResource(getMeta("stylish-code-chrome"), function (code) {
// check for old style json
var json = JSON.parse(code);
json.method = "saveStyle";
chrome.runtime.sendMessage(json, function(response) {
chrome.runtime.sendMessage(json, function (response) {
sendEvent("styleInstalledChrome");
});
});
@ -85,15 +101,18 @@ document.addEventListener("stylishInstallChrome", function() {
});
}, false);
document.addEventListener("stylishUpdateChrome", function() {
chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url") || location.href}, function(response) {
document.addEventListener("stylishUpdateChrome", function () {
chrome.runtime.sendMessage({
method: "getStyles",
url: getMeta("stylish-id-url") || location.href
}, function (response) {
var style = response[0];
if (confirm(chrome.i18n.getMessage('styleUpdate', [style.name]))) {
getResource(getMeta("stylish-code-chrome"), function(code) {
getResource(getMeta("stylish-code-chrome"), function (code) {
var json = JSON.parse(code);
json.method = "saveStyle";
json.id = style.id;
chrome.runtime.sendMessage(json, function() {
chrome.runtime.sendMessage(json, function () {
sendEvent("styleInstalledChrome");
});
});
@ -108,14 +127,14 @@ function getMeta(name) {
}
function getResource(url, callback) {
if (url.indexOf("#") == 0) {
if (url.indexOf("#") === 0) {
if (callback) {
callback(document.getElementById(url.substring(1)).innerText);
}
return;
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && callback) {
if (xhr.status >= 400) {
callback(null);
@ -123,11 +142,11 @@ function getResource(url, callback) {
callback(xhr.responseText);
}
}
}
};
if (url.length > 2000) {
var parts = url.split("?");
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]);
} else {
xhr.open("GET", url, true);

View File

@ -1,9 +1,10 @@
/*jshint undef:false*/
var template = {};
tDocLoader();
function t(key, params) {
var s = chrome.i18n.getMessage(key, params)
if (s == "") {
var s = chrome.i18n.getMessage(key, params);
if (s === "") {
throw "Missing string '" + key + "'.";
}
return s;
@ -44,7 +45,7 @@ function tNodeList(nodes) {
for (var a = node.attributes.length - 1; a >= 0; a--) {
var attr = node.attributes[a];
var name = attr.nodeName;
if (name.indexOf("i18n-") != 0) {
if (name.indexOf("i18n-") !== 0) {
continue;
}
name = name.substr(5); // "i18n-".length

153
manage.js
View File

@ -1,3 +1,4 @@
/*jshint undef:false*/
var lastUpdatedStyleId = null;
var installed;
@ -5,11 +6,15 @@ var appliesToExtraTemplate = document.createElement("span");
appliesToExtraTemplate.className = "applies-to-extra";
appliesToExtraTemplate.innerHTML = " " + t('appliesDisplayTruncatedSuffix');
chrome.runtime.sendMessage({method: "getStyles"}, showStyles);
chrome.runtime.sendMessage({
method: "getStyles"
}, showStyles);
function showStyles(styles) {
if (!styles) { // Chrome is starting up
chrome.runtime.sendMessage({method: "getStyles"}, showStyles);
chrome.runtime.sendMessage({
method: "getStyles"
}, showStyles);
return;
}
if (!installed) {
@ -18,8 +23,10 @@ function showStyles(styles) {
document.stylishStyles = styles;
return;
}
styles.sort(function(a, b) { return a.name.localeCompare(b.name)});
styles.map(createStyleElement).forEach(function(e) {
styles.sort(function (a, b) {
return a.name.localeCompare(b.name);
});
styles.map(createStyleElement).forEach(function (e) {
installed.appendChild(e);
});
if (history.state) {
@ -44,23 +51,24 @@ function createStyleElement(style) {
var styleName = e.querySelector(".style-name");
styleName.appendChild(document.createTextNode(style.name));
if (style.url) {
var homepage = template.styleHomepage.cloneNode(true)
var homepage = template.styleHomepage.cloneNode(true);
homepage.setAttribute("href", style.url);
styleName.appendChild(document.createTextNode(" " ));
styleName.appendChild(document.createTextNode(" "));
styleName.appendChild(homepage);
}
var domains = [];
var urls = [];
var urlPrefixes = [];
var regexps = [];
function add(array, property) {
style.sections.forEach(function(section) {
style.sections.forEach(function (section) {
if (section[property]) {
section[property].filter(function(value) {
section[property].filter(function (value) {
return array.indexOf(value) == -1;
}).forEach(function(value) {
}).forEach(function (value) {
array.push(value);
});;
});
}
});
}
@ -74,12 +82,16 @@ function createStyleElement(style) {
if (urls)
appliesToToShow = appliesToToShow.concat(urls);
if (urlPrefixes)
appliesToToShow = appliesToToShow.concat(urlPrefixes.map(function(u) { return u + "*"; }));
appliesToToShow = appliesToToShow.concat(urlPrefixes.map(function (u) {
return u + "*";
}));
if (regexps)
appliesToToShow = appliesToToShow.concat(regexps.map(function(u) { return "/" + u + "/"; }));
appliesToToShow = appliesToToShow.concat(regexps.map(function (u) {
return "/" + u + "/";
}));
var appliesToString = "";
var showAppliesToExtra = false;
if (appliesToToShow.length == "")
if (appliesToToShow.length === "")
appliesToString = t('appliesToEverything');
else if (appliesToToShow.length <= 10)
appliesToString = appliesToToShow.join(", ");
@ -93,10 +105,12 @@ function createStyleElement(style) {
}
var editLink = e.querySelector(".style-edit-link");
editLink.setAttribute("href", editLink.getAttribute("href") + style.id);
editLink.addEventListener("click", function(event) {
editLink.addEventListener("click", function (event) {
if (!event.altKey) {
var left = event.button == 0, middle = event.button == 1,
shift = event.shiftKey, ctrl = event.ctrlKey;
var left = event.button === 0,
middle = event.button == 1,
shift = event.shiftKey,
ctrl = event.ctrlKey;
var openWindow = left && shift && !ctrl;
var openBackgroundTab = (middle && !shift) || (left && ctrl && !shift);
var openForegroundTab = (middle && shift) || (left && ctrl && shift);
@ -116,16 +130,22 @@ function createStyleElement(style) {
});
}
} else {
history.replaceState({scrollY: window.scrollY}, document.title);
getActiveTab(function(tab) {
history.replaceState({
scrollY: window.scrollY
}, document.title);
getActiveTab(function (tab) {
sessionStorageHash("manageStylesHistory").set(tab.id, url);
location.href = url;
});
}
}
});
e.querySelector(".enable").addEventListener("click", function(event) { enable(event, true); }, false);
e.querySelector(".disable").addEventListener("click", function(event) { enable(event, false); }, false);
e.querySelector(".enable").addEventListener("click", function (event) {
enable(event, true);
}, false);
e.querySelector(".disable").addEventListener("click", function (event) {
enable(event, false);
}, false);
e.querySelector(".check-update").addEventListener("click", doCheckUpdate, false);
e.querySelector(".update").addEventListener("click", doUpdate, false);
e.querySelector(".delete").addEventListener("click", doDelete, false);
@ -160,7 +180,7 @@ function getStyleElement(event) {
return null;
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
switch (request.method) {
case "styleUpdated":
handleUpdate(request.style);
@ -181,7 +201,7 @@ function handleUpdate(style) {
lastUpdatedStyleId = null;
element.className = element.className += " update-done";
element.querySelector(".update-note").innerHTML = t('updateCompleted');
};
}
}
function handleDelete(id) {
@ -198,12 +218,12 @@ function doCheckUpdate(event) {
function applyUpdateAll() {
var btnApply = document.getElementById("apply-all-updates");
btnApply.disabled = true;
setTimeout(function() {
setTimeout(function () {
btnApply.style.display = "none";
btnApply.disabled = false;
}, 1000);
Array.prototype.forEach.call(document.querySelectorAll(".can-update .update"), function(button) {
Array.prototype.forEach.call(document.querySelectorAll(".can-update .update"), function (button) {
button.click();
});
}
@ -220,18 +240,18 @@ function checkUpdateAll() {
var elements = document.querySelectorAll("[style-update-url]");
var toCheckCount = elements.length;
var updatableCount = 0;
Array.prototype.forEach.call(elements, function(element) {
checkUpdate(element, function(success) {
Array.prototype.forEach.call(elements, function (element) {
checkUpdate(element, function (success) {
if (success) {
++updatableCount;
}
if (--toCheckCount == 0) {
if (--toCheckCount === 0) {
btnCheck.disabled = false;
if (updatableCount) {
btnApply.classList.remove("hidden");
} else {
noUpdates.classList.remove("hidden");
setTimeout(function() {
setTimeout(function () {
noUpdates.classList.add("hidden");
}, 10000);
}
@ -249,7 +269,10 @@ function checkUpdate(element, callback) {
var originalMd5 = element.getAttribute("style-original-md5");
function handleSuccess(forceUpdate, serverJson) {
chrome.runtime.sendMessage({method: "getStyles", id: id}, function(styles) {
chrome.runtime.sendMessage({
method: "getStyles",
id: id
}, function (styles) {
var style = styles[0];
var needsUpdate = false;
if (!forceUpdate && codeIsEqual(style.sections, serverJson.sections)) {
@ -265,7 +288,7 @@ function checkUpdate(element, callback) {
}
function handleFailure(status) {
if (status == 0) {
if (status === 0) {
handleNeedsUpdate(t('updateCheckFailServerUnreachable'), id, null);
} else {
handleNeedsUpdate(t('updateCheckFailBadResponseCode', [status]), id, null);
@ -276,9 +299,9 @@ function checkUpdate(element, callback) {
}
if (!md5Url || !originalMd5) {
checkUpdateFullCode(url, false, handleSuccess, handleFailure)
checkUpdateFullCode(url, false, handleSuccess, handleFailure);
} else {
checkUpdateMd5(originalMd5, md5Url, function(needsUpdate) {
checkUpdateMd5(originalMd5, md5Url, function (needsUpdate) {
if (needsUpdate) {
// If the md5 shows a change we will update regardless of whether the code looks different
checkUpdateFullCode(url, true, handleSuccess, handleFailure);
@ -293,13 +316,13 @@ function checkUpdate(element, callback) {
}
function checkUpdateFullCode(url, forceUpdate, successCallback, failureCallback) {
download(url, function(responseText) {
download(url, function (responseText) {
successCallback(forceUpdate, JSON.parse(responseText));
}, failureCallback);
}
function checkUpdateMd5(originalMd5, md5Url, successCallback, failureCallback) {
download(md5Url, function(responseText) {
download(md5Url, function (responseText) {
if (responseText.length != 32) {
failureCallback(-1);
return;
@ -313,16 +336,16 @@ function download(url, successCallback, failureCallback) {
xhr.onreadystatechange = function (aEvt) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
successCallback(xhr.responseText)
successCallback(xhr.responseText);
} else {
failureCallback(xhr.status);
}
}
}
};
if (url.length > 2000) {
var parts = url.split("?");
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]);
} else {
xhr.open("GET", url, true);
@ -371,7 +394,7 @@ function codeIsEqual(a, b) {
for (var i = 0; i < a.length; i++) {
var found = false;
for (var j = 0; j < b.length; j++) {
var allEquals = properties.every(function(property) {
var allEquals = properties.every(function (property) {
return jsonEquals(a[i], b[j], property);
});
if (allEquals) {
@ -387,11 +410,13 @@ function codeIsEqual(a, b) {
}
function jsonEquals(a, b, property) {
var aProp = a[property], typeA = getType(aProp);
var bProp = b[property], typeB = getType(bProp);
var aProp = a[property],
typeA = getType(aProp);
var bProp = b[property],
typeB = getType(bProp);
if (typeA != typeB) {
// consider empty arrays equivalent to lack of property
if ((typeA == "undefined" || (typeA == "array" && aProp.length == 0)) && (typeB == "undefined" || (typeB == "array" && bProp.length == 0))) {
if ((typeA == "undefined" || (typeA == "array" && aProp.length === 0)) && (typeB == "undefined" || (typeB == "array" && bProp.length === 0))) {
return true;
}
return false;
@ -427,9 +452,12 @@ function searchStyles(immediately) {
clearTimeout(searchStyles.timeout);
searchStyles.timeout = setTimeout(doSearch, 100);
}
function doSearch() {
chrome.runtime.sendMessage({method: "getStyles"}, function(styles) {
styles.forEach(function(style) {
chrome.runtime.sendMessage({
method: "getStyles"
}, function (styles) {
styles.forEach(function (style) {
var el = document.querySelector("[style-id='" + style.id + "']");
if (el) {
el.style.display = !query || isMatchingText(style.name) || isMatchingStyle(style) ? "" : "none";
@ -437,43 +465,49 @@ function searchStyles(immediately) {
});
});
}
function isMatchingStyle(style) {
return style.sections.some(function(section) {
return Object.keys(section).some(function(key) {
return style.sections.some(function (section) {
return Object.keys(section).some(function (key) {
var value = section[key];
switch (typeof value) {
case "string": return isMatchingText(value);
case "object": return value.some(isMatchingText);
case "string":
return isMatchingText(value);
case "object":
return value.some(isMatchingText);
}
});
});
}
function isMatchingText(text) {
return text.toLocaleLowerCase().indexOf(query) >= 0;
}
}
function onFilterChange (className, event) {
function onFilterChange(className, event) {
installed.classList.toggle(className, event.target.checked);
}
function initFilter(className, node) {
node.addEventListener("change", onFilterChange.bind(undefined, className), false);
onFilterChange(className, {target: node});
onFilterChange(className, {
target: node
});
}
function importStyles (e) {
function importStyles(e) {
var file = e.target.files[0];
var reader = new FileReader();
var styles = [];
function save () {
function save() {
var style = styles.shift();
if (style) {
delete style.id;
saveStyle(style, save);
}
else {
window.location.reload()
} else {
window.location.reload();
}
}
@ -481,22 +515,21 @@ function importStyles (e) {
try {
styles = JSON.parse(evt.target.result);
save();
}
catch (e) {
} catch (e) {
window.alert(e.message);
}
};
reader.onerror = function (e) {
window.alert(e.message);
}
reader.readAsText(file)
};
reader.readAsText(file);
}
function selectAll () {
function selectAll() {
document.execCommand('selectAll');
}
document.addEventListener("DOMContentLoaded", function() {
document.addEventListener("DOMContentLoaded", function () {
installed = document.getElementById("installed");
if (document.stylishStyles) {
showStyles(document.stylishStyles);

View File

@ -1,14 +1,20 @@
/*jshint undef:false*/
function notifyAllTabs(request) {
chrome.windows.getAll({populate: true}, function(windows) {
windows.forEach(function(win) {
win.tabs.forEach(function(tab) {
chrome.windows.getAll({
populate: true
}, function (windows) {
windows.forEach(function (win) {
win.tabs.forEach(function (tab) {
chrome.tabs.sendMessage(tab.id, request);
updateIcon(tab);
});
});
});
// notify all open popups
var reqPopup = shallowMerge({}, request, {method: "updatePopup", reason: request.method});
var reqPopup = shallowMerge({}, request, {
method: "updatePopup",
reason: request.method
});
chrome.runtime.sendMessage(reqPopup);
}
@ -20,7 +26,7 @@ function updateIcon(tab, styles) {
}
if (styles) {
// check for not-yet-existing tabs e.g. omnibox instant search
chrome.tabs.get(tab.id, function() {
chrome.tabs.get(tab.id, function () {
if (!chrome.runtime.lastError) {
// for 'styles' asHash:true fake the length by counting numeric ids manually
if (styles.length === undefined) {
@ -34,33 +40,47 @@ function updateIcon(tab, styles) {
});
return;
}
getTabRealURL(tab, function(url) {
getTabRealURL(tab, function (url) {
// if we have access to this, call directly. a page sending a message to itself doesn't seem to work right.
if (typeof getStyles != "undefined") {
getStyles({matchUrl: url, enabled: true}, stylesReceived);
getStyles({
matchUrl: url,
enabled: true
}, stylesReceived);
} else {
chrome.runtime.sendMessage({method: "getStyles", matchUrl: url, enabled: true}, stylesReceived);
chrome.runtime.sendMessage({
method: "getStyles",
matchUrl: url,
enabled: true
}, stylesReceived);
}
});
function stylesReceived(styles) {
var disableAll = "disableAll" in styles ? styles.disableAll : prefs.get("disableAll");
var postfix = styles.length == 0 || disableAll ? "w" : "";
var postfix = styles.length === 0 || disableAll ? "w" : "";
chrome.browserAction.setIcon({
path: {
// Material Design 2016 new size is 16px
16: "16" + postfix + ".png", 32: "32" + postfix + ".png",
16: "16" + postfix + ".png",
32: "32" + postfix + ".png",
// Chromium forks or non-chromium browsers may still use the traditional 19px
19: "19" + postfix + ".png", 38: "38" + postfix + ".png",
19: "19" + postfix + ".png",
38: "38" + postfix + ".png",
},
tabId: tab.id
}, function() {
}, function () {
// if the tab was just closed an error may occur,
// e.g. 'windowPosition' pref updated in edit.js::window.onbeforeunload
if (!chrome.runtime.lastError) {
var t = prefs.get("show-badge") && styles.length ? ("" + styles.length) : "";
chrome.browserAction.setBadgeText({text: t, tabId: tab.id});
chrome.browserAction.setBadgeBackgroundColor({color: disableAll ? "#aaa" : [0, 0, 0, 0]});
chrome.browserAction.setBadgeText({
text: t,
tabId: tab.id
});
chrome.browserAction.setBadgeBackgroundColor({
color: disableAll ? "#aaa" : [0, 0, 0, 0]
});
}
});
//console.log("Tab " + tab.id + " (" + tab.url + ") badge text set to '" + t + "'.");
@ -68,13 +88,16 @@ function updateIcon(tab, styles) {
}
function getActiveTab(callback) {
chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
chrome.tabs.query({
currentWindow: true,
active: true
}, function (tabs) {
callback(tabs[0]);
});
}
function getActiveTabRealURL(callback) {
getActiveTab(function(tab) {
getActiveTab(function (tab) {
getTabRealURL(tab, callback);
});
}
@ -83,7 +106,11 @@ function getTabRealURL(tab, callback) {
if (tab.url != "chrome://newtab/") {
callback(tab.url);
} else {
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, function(frame) {
chrome.webNavigation.getFrame({
tabId: tab.id,
frameId: 0,
processId: -1
}, function (frame) {
frame && callback(frame.url);
});
}

View File

@ -1,3 +1,4 @@
/*jshint undef:false*/
var writeStyleTemplate = document.createElement("a");
writeStyleTemplate.className = "write-style-link";
@ -17,7 +18,10 @@ function updatePopUp(url) {
return;
}
chrome.runtime.sendMessage({method: "getStyles", matchUrl: url}, showStyles);
chrome.runtime.sendMessage({
method: "getStyles",
matchUrl: url
}, showStyles);
document.querySelector("#find-styles a").href = "https://userstyles.org/styles/browse/all/" + encodeURIComponent("file" === urlWillWork[1] ? "file:" : url);
// Write new style links
@ -29,23 +33,29 @@ function updatePopUp(url) {
var urlLink = writeStyleTemplate.cloneNode(true);
urlLink.href = "edit.html?url-prefix=" + encodeURIComponent(url);
urlLink.appendChild(document.createTextNode( // switchable; default="this&nbsp;URL"
!prefs.get("popup.breadcrumbs.usePath")
? t("writeStyleForURL").replace(/ /g, "\u00a0")
: /\/\/[^/]+\/(.*)/.exec(url)[1]
!prefs.get("popup.breadcrumbs.usePath") ? t("writeStyleForURL").replace(/ /g, "\u00a0") : /\/\/[^/]+\/(.*)/.exec(url)[1]
));
urlLink.title = "url-prefix(\"$\")".replace("$", url);
writeStyleLinks.push(urlLink);
document.querySelector("#write-style").appendChild(urlLink)
document.querySelector("#write-style").appendChild(urlLink);
if (prefs.get("popup.breadcrumbs")) { // switchable; default=enabled
urlLink.addEventListener("mouseenter", function(event) { this.parentNode.classList.add("url()") }, false);
urlLink.addEventListener("focus", function(event) { this.parentNode.classList.add("url()") }, false);
urlLink.addEventListener("mouseleave", function(event) { this.parentNode.classList.remove("url()") }, false);
urlLink.addEventListener("blur", function(event) { this.parentNode.classList.remove("url()") }, false);
urlLink.addEventListener("mouseenter", function (event) {
this.parentNode.classList.add("url()");
}, false);
urlLink.addEventListener("focus", function (event) {
this.parentNode.classList.add("url()");
}, false);
urlLink.addEventListener("mouseleave", function (event) {
this.parentNode.classList.remove("url()");
}, false);
urlLink.addEventListener("blur", function (event) {
this.parentNode.classList.remove("url()");
}, false);
}
// For domain
var domains = getDomains(url)
domains.forEach(function(domain) {
var domains = getDomains(url);
domains.forEach(function (domain) {
// Don't include TLD
if (domains.length > 1 && domain.indexOf(".") == -1) {
return;
@ -59,7 +69,7 @@ function updatePopUp(url) {
});
var writeStyle = document.querySelector("#write-style");
writeStyleLinks.forEach(function(link, index) {
writeStyleLinks.forEach(function (link, index) {
link.addEventListener("click", openLinkInTabOrWindow, false);
container.appendChild(link);
});
@ -72,14 +82,14 @@ function updatePopUp(url) {
function showStyles(styles) {
var enabledFirst = prefs.get("popup.enabledFirst");
styles.sort(function(a, b) {
styles.sort(function (a, b) {
if (enabledFirst && a.enabled !== b.enabled) return !(a.enabled < b.enabled) ? -1 : 1;
return a.name.localeCompare(b.name);
});
if (styles.length == 0) {
if (styles.length === 0) {
installed.innerHTML = "<div class='entry' id='no-styles'>" + t('noStylesForSite') + "</div>";
}
styles.map(createStyleElement).forEach(function(e) {
styles.map(createStyleElement).forEach(function (e) {
installed.appendChild(e);
});
}
@ -100,13 +110,24 @@ function createStyleElement(style) {
editLink.setAttribute("href", editLink.getAttribute("href") + style.id);
editLink.addEventListener("click", openLinkInTabOrWindow, false);
styleName.addEventListener("click", function() { this.checkbox.click(); event.preventDefault(); });
styleName.addEventListener("click", function () {
this.checkbox.click();
event.preventDefault();
});
// clicking the checkbox will toggle it, and this will run after that happens
checkbox.addEventListener("click", function() { enable(event, event.target.checked); }, false);
e.querySelector(".enable").addEventListener("click", function() { enable(event, true); }, false);
e.querySelector(".disable").addEventListener("click", function() { enable(event, false); }, false);
checkbox.addEventListener("click", function () {
enable(event, event.target.checked);
}, false);
e.querySelector(".enable").addEventListener("click", function () {
enable(event, true);
}, false);
e.querySelector(".disable").addEventListener("click", function () {
enable(event, false);
}, false);
e.querySelector(".delete").addEventListener("click", function() { doDelete(event, false); }, false);
e.querySelector(".delete").addEventListener("click", function () {
doDelete(event, false);
}, false);
return e;
}
@ -147,7 +168,9 @@ function getId(event) {
function openLinkInTabOrWindow(event) {
event.preventDefault();
if (prefs.get("openEditInWindow", false)) {
var options = {url: event.target.href}
var options = {
url: event.target.href
};
var wp = prefs.get("windowPosition", {});
for (var k in wp) options[k] = wp[k];
chrome.windows.create(options);
@ -159,7 +182,10 @@ function openLinkInTabOrWindow(event) {
function openLink(event) {
event.preventDefault();
chrome.runtime.sendMessage({method: "openURL", url: event.target.href});
chrome.runtime.sendMessage({
method: "openURL",
url: event.target.href
});
close();
}
@ -168,7 +194,7 @@ function handleUpdate(style) {
if (styleElement) {
installed.replaceChild(createStyleElement(style), styleElement);
} else {
getActiveTabRealURL(function(url) {
getActiveTabRealURL(function (url) {
if (chrome.extension.getBackgroundPage().getApplicableSections(style, url).length) {
// a new style for the current url is installed
document.getElementById("unavailable").style.display = "none";
@ -185,7 +211,7 @@ function handleDelete(id) {
}
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.method == "updatePopup") {
switch (request.reason) {
case "styleAdded":
@ -199,11 +225,11 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
}
});
["find-styles-link", "open-manage-link"].forEach(function(id) {
["find-styles-link", "open-manage-link"].forEach(function (id) {
document.getElementById(id).addEventListener("click", openLink, false);
});
document.getElementById("disableAll").addEventListener("change", function(event) {
document.getElementById("disableAll").addEventListener("change", function (event) {
installed.classList.toggle("disabled", prefs.get("disableAll"));
});
setupLivePrefs(["disableAll"]);

View File

@ -1,31 +1,32 @@
/*jshint undef:false*/
var webSqlStorage = {
migrate: function() {
migrate: function () {
if (typeof openDatabase == "undefined") {
// No WebSQL - no migration!
return;
}
webSqlStorage.getStyles(function(styles) {
getDatabase(function(db) {
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)
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() {
setTimeout(function () {
invalidateCache(true);
}, 500);
});
}, null);
},
cleanStyle: function(s) {
cleanStyle: function (s) {
delete s.id;
s.sections.forEach(function(section) {
s.sections.forEach(function (section) {
delete section.id;
["urls", "urlPrefixes", "domains", "regexps"].forEach(function(property) {
["urls", "urlPrefixes", "domains", "regexps"].forEach(function (property) {
if (!section[property]) {
section[property] = [];
}
@ -33,8 +34,8 @@ var webSqlStorage = {
});
},
getStyles: function(callback) {
webSqlStorage.getDatabase(function(db) {
getStyles: function (callback) {
webSqlStorage.getDatabase(function (db) {
if (!db) {
callback([]);
return;
@ -44,12 +45,13 @@ var webSqlStorage = {
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) {
var styles = [];
var currentStyle = null;
var currentSection = null;
var styles = [],
currentStyle = null,
currentSection = null,
metaName;
for (var i = 0; i < r.rows.length; i++) {
var values = r.rows.item(i);
var metaName = null;
metaName = null;
switch (values.metaName) {
case null:
break;
@ -60,22 +62,34 @@ var webSqlStorage = {
metaName = "urlPrefixes";
break;
case "domain":
var metaName = "domains";
metaName = "domains";
break;
case "regexps":
var metaName = "regexps";
metaName = "regexps";
break;
default:
var metaName = values.metaName + "s";
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: []};
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};
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) {
@ -93,14 +107,14 @@ var webSqlStorage = {
}, reportError);
},
getDatabase: function(ready, error) {
getDatabase: function (ready, error) {
try {
stylishDb = openDatabase('stylish', '', 'Stylish Styles', 5*1024*1024);
stylishDb = openDatabase('stylish', '', 'Stylish Styles', 5 * 1024 * 1024);
} catch (ex) {
error();
throw ex;
}
if (stylishDb.version == "") {
if (stylishDb.version === "") {
// It didn't already exist, we have nothing to migrate.
ready(null);
return;
@ -120,15 +134,17 @@ var webSqlStorage = {
}
},
dbV11: function(d, error, done) {
dbV11: function (d, error, done) {
d.changeVersion(d.version, '1.1', function (t) {
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);');
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);');
t.executeSql('CREATE INDEX style_meta_style_id ON style_meta (style_id);');
}, error, function() { webSqlStorage.dbV12(d, error, done)});
}, error, function () {
webSqlStorage.dbV12(d, error, done);
});
},
dbV12: function(d, error, done) {
dbV12: function (d, error, done) {
d.changeVersion(d.version, '1.2', function (t) {
// add section table
t.executeSql('CREATE TABLE sections (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, style_id INTEGER NOT NULL, code TEXT NOT NULL);');
@ -144,26 +160,34 @@ var webSqlStorage = {
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, function() { webSqlStorage.dbV13(d, error, done)});
}, error, function () {
webSqlStorage.dbV13(d, error, done);
});
},
dbV13: function(d, error, done) {
dbV13: function (d, error, done) {
d.changeVersion(d.version, '1.3', function (t) {
// clear out orphans
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, function() { webSqlStorage.dbV14(d, error, done)});
}, error, function () {
webSqlStorage.dbV14(d, error, done);
});
},
dbV14: function(d, error, done) {
dbV14: function (d, error, done) {
d.changeVersion(d.version, '1.4', function (t) {
t.executeSql('UPDATE styles SET url = null WHERE url = "undefined";');
}, error, function() { webSqlStorage.dbV15(d, error, done)});
}, error, function () {
webSqlStorage.dbV15(d, error, done);
});
},
dbV15: function(d, error, done) {
dbV15: function (d, error, done) {
d.changeVersion(d.version, '1.5', function (t) {
t.executeSql('ALTER TABLE styles ADD COLUMN originalMd5 TEXT NULL;');
}, error, function() { done(d); });
}, error, function () {
done(d);
});
}
}
};

View File

@ -1,37 +1,43 @@
/*jshint undef:false*/
function getDatabase(ready, error) {
var dbOpenRequest = window.indexedDB.open("stylish", 2);
dbOpenRequest.onsuccess = function(e) {
dbOpenRequest.onsuccess = function (e) {
ready(e.target.result);
};
dbOpenRequest.onerror = function(event) {
dbOpenRequest.onerror = function (event) {
console.log(event.target.errorCode);
if (error) {
error(event);
}
};
dbOpenRequest.onupgradeneeded = function(event) {
if (event.oldVersion == 0) {
var os = event.target.result.createObjectStore("styles", {keyPath: 'id', autoIncrement: true});
dbOpenRequest.onupgradeneeded = function (event) {
if (event.oldVersion === 0) {
var os = event.target.result.createObjectStore("styles", {
keyPath: 'id',
autoIncrement: true
});
webSqlStorage.migrate();
}
}
};
};
}
var cachedStyles = null,
request;
var cachedStyles = null;
function getStyles(options, callback) {
if (cachedStyles != null) {
if (cachedStyles !== null) {
callback(filterStyles(cachedStyles, options));
return;
}
getDatabase(function(db) {
getDatabase(function (db) {
var tx = db.transaction(["styles"], "readonly");
var os = tx.objectStore("styles");
var all = [];
os.openCursor().onsuccess = function(event) {
os.openCursor().onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
var s = cursor.value
s.id = cursor.key
var s = cursor.value;
s.id = cursor.key;
all.push(cursor.value);
cursor.continue();
} else {
@ -45,7 +51,9 @@ function getStyles(options, callback) {
function invalidateCache(andNotify) {
cachedStyles = null;
if (andNotify) {
chrome.runtime.sendMessage({method: "invalidateCache"});
chrome.runtime.sendMessage({
method: "invalidateCache"
});
}
}
@ -55,27 +63,29 @@ function filterStyles(styles, options) {
var id = "id" in options ? Number(options.id) : null;
var matchUrl = "matchUrl" in options ? options.matchUrl : null;
if (enabled != null) {
styles = styles.filter(function(style) {
if (enabled !== null) {
styles = styles.filter(function (style) {
return style.enabled == enabled;
});
}
if (url != null) {
styles = styles.filter(function(style) {
if (url !== null) {
styles = styles.filter(function (style) {
return style.url == url;
});
}
if (id != null) {
styles = styles.filter(function(style) {
if (id !== null) {
styles = styles.filter(function (style) {
return style.id == id;
});
}
if (matchUrl != null) {
if (matchUrl !== null) {
// Return as a hash from style to applicable sections? Can only be used with matchUrl.
var asHash = "asHash" in options ? options.asHash : false;
if (asHash) {
var h = {disableAll: prefs.get("disableAll", false)};
styles.forEach(function(style) {
var h = {
disableAll: prefs.get("disableAll", false)
};
styles.forEach(function (style) {
var applicableSections = getApplicableSections(style, matchUrl);
if (applicableSections.length > 0) {
h[style.id] = applicableSections;
@ -83,7 +93,7 @@ function filterStyles(styles, options) {
});
return h;
}
styles = styles.filter(function(style) {
styles = styles.filter(function (style) {
var applicableSections = getApplicableSections(style, matchUrl);
return applicableSections.length > 0;
});
@ -92,14 +102,14 @@ function filterStyles(styles, options) {
}
function saveStyle(o, callback) {
getDatabase(function(db) {
getDatabase(function (db) {
var tx = db.transaction(["styles"], "readwrite");
var os = tx.objectStore("styles");
// Update
if (o.id) {
var request = os.get(Number(o.id));
request.onsuccess = function(event) {
request = os.get(Number(o.id));
request.onsuccess = function (event) {
var style = request.result;
for (var prop in o) {
if (prop == "id") {
@ -108,8 +118,11 @@ function saveStyle(o, callback) {
style[prop] = o[prop];
}
request = os.put(style);
request.onsuccess = function(event) {
notifyAllTabs({method: "styleUpdated", style: style});
request.onsuccess = function (event) {
notifyAllTabs({
method: "styleUpdated",
style: style
});
invalidateCache(true);
if (callback) {
callback(style);
@ -121,14 +134,14 @@ function saveStyle(o, callback) {
// Create
// Set optional things to null if they're undefined
["updateUrl", "md5Url", "url", "originalMd5"].filter(function(att) {
["updateUrl", "md5Url", "url", "originalMd5"].filter(function (att) {
return !(att in o);
}).forEach(function(att) {
}).forEach(function (att) {
o[att] = null;
});
// Set other optional things to empty array if they're undefined
o.sections.forEach(function(section) {
["urls", "urlPrefixes", "domains", "regexps"].forEach(function(property) {
o.sections.forEach(function (section) {
["urls", "urlPrefixes", "domains", "regexps"].forEach(function (property) {
if (!section[property]) {
section[property] = [];
}
@ -139,13 +152,16 @@ function saveStyle(o, callback) {
o.enabled = true;
}
// Make sure it's not null - that makes indexeddb sad
delete o["id"];
var request = os.add(o);
request.onsuccess = function(event) {
delete o.id;
request = os.add(o);
request.onsuccess = function (event) {
invalidateCache(true);
// Give it the ID that was generated
o.id = event.target.result;
notifyAllTabs({method: "styleAdded", style: o});
notifyAllTabs({
method: "styleAdded",
style: o
});
if (callback) {
callback(o);
}
@ -154,26 +170,36 @@ function saveStyle(o, callback) {
}
function enableStyle(id, enabled) {
saveStyle({id: id, enabled: enabled}, function(style) {
saveStyle({
id: id,
enabled: enabled
}, function (style) {
handleUpdate(style);
notifyAllTabs({method: "styleUpdated", style: style});
notifyAllTabs({
method: "styleUpdated",
style: style
});
});
}
function deleteStyle(id) {
getDatabase(function(db) {
getDatabase(function (db) {
var tx = db.transaction(["styles"], "readwrite");
var os = tx.objectStore("styles");
var request = os.delete(Number(id));
request.onsuccess = function(event) {
request = os.delete(Number(id));
request.onsuccess = function (event) {
handleDelete(id);
invalidateCache(true);
notifyAllTabs({method: "styleDeleted", id: id});
notifyAllTabs({
method: "styleDeleted",
id: id
});
};
});
}
function reportError() {
var i;
for (i in arguments) {
if ("message" in arguments[i]) {
//alert(arguments[i].message);
@ -190,7 +216,7 @@ function fixBoolean(b) {
}
function getDomains(url) {
if (url.indexOf("file:") == 0) {
if (url.indexOf("file:") === 0) {
return [];
}
var d = /.*?:\/*([^\/:]+)/.exec(url)[1];
@ -213,8 +239,9 @@ function getType(o) {
}
var namespacePattern = /^\s*(@namespace[^;]+;\s*)+$/;
function getApplicableSections(style, url) {
var sections = style.sections.filter(function(section) {
var sections = style.sections.filter(function (section) {
return sectionAppliesToUrl(section, url);
});
// ignore if it's just namespaces
@ -226,14 +253,14 @@ function getApplicableSections(style, url) {
function sectionAppliesToUrl(section, url) {
// only http, https, file, and chrome-extension allowed
if (url.indexOf("http") != 0 && url.indexOf("file") != 0 && url.indexOf("chrome-extension") != 0 && url.indexOf("ftp") != 0) {
if (url.indexOf("http") !== 0 && url.indexOf("file") !== 0 && url.indexOf("chrome-extension") !== 0 && url.indexOf("ftp") !== 0) {
return false;
}
// other extensions can't be styled
if (url.indexOf("chrome-extension") == 0 && url.indexOf(chrome.extension.getURL("")) != 0) {
if (url.indexOf("chrome-extension") === 0 && url.indexOf(chrome.extension.getURL("")) !== 0) {
return false;
}
if (section.urls.length == 0 && section.domains.length == 0 && section.urlPrefixes.length == 0 && section.regexps.length == 0) {
if (section.urls.length === 0 && section.domains.length === 0 && section.urlPrefixes.length === 0 && section.regexps.length === 0) {
//console.log(section.id + " is global");
return true;
}
@ -241,19 +268,19 @@ function sectionAppliesToUrl(section, url) {
//console.log(section.id + " applies to " + url + " due to URL rules");
return true;
}
if (section.urlPrefixes.some(function(prefix) {
return url.indexOf(prefix) == 0;
if (section.urlPrefixes.some(function (prefix) {
return url.indexOf(prefix) === 0;
})) {
//console.log(section.id + " applies to " + url + " due to URL prefix rules");
return true;
}
if (section.domains.length > 0 && getDomains(url).some(function(domain) {
if (section.domains.length > 0 && getDomains(url).some(function (domain) {
return section.domains.indexOf(domain) != -1;
})) {
//console.log(section.id + " applies due to " + url + " due to domain rules");
return true;
}
if (section.regexps.some(function(regexp) {
if (section.regexps.some(function (regexp) {
// we want to match the full url, so add ^ and $ if not already present
if (regexp[0] != "^") {
regexp = "^" + regexp;
@ -261,7 +288,9 @@ function sectionAppliesToUrl(section, url) {
if (regexp[regexp.length - 1] != "$") {
regexp += "$";
}
var re = runTryCatch(function() { return new RegExp(regexp) });
var re = runTryCatch(function () {
return new RegExp(regexp);
});
if (re) {
return (re).test(url);
} else {
@ -282,29 +311,34 @@ function isCheckbox(el) {
// js engine can't optimize the entire function if it contains try-catch
// so we should keep it isolated from normal code in a minimal wrapper
function runTryCatch(func) {
try { return func() }
catch(e) {}
try {
return func();
} catch (e) {}
}
// Accepts an array of pref names (values are fetched via prefs.get)
// and establishes a two-way connection between the document elements and the actual prefs
function setupLivePrefs(IDs) {
var localIDs = {};
IDs.forEach(function(id) {
IDs.forEach(function (id) {
localIDs[id] = true;
updateElement(id).addEventListener("change", function() {
updateElement(id).addEventListener("change", function () {
prefs.set(this.id, isCheckbox(this) ? this.checked : this.value);
});
});
chrome.runtime.onMessage.addListener(function(request) {
chrome.runtime.onMessage.addListener(function (request) {
if (request.prefName in localIDs) {
updateElement(request.prefName);
}
});
function updateElement(id) {
var el = document.getElementById(id);
el[isCheckbox(el) ? "checked" : "value"] = prefs.get(id);
el.dispatchEvent(new Event("change", {bubbles: true, cancelable: true}));
el.dispatchEvent(new Event("change", {
bubbles: true,
cancelable: true
}));
return el;
}
}
@ -349,9 +383,11 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() {
var syncTimeout; // see broadcast() function below
Object.defineProperty(this, "readOnlyValues", {value: {}});
Object.defineProperty(this, "readOnlyValues", {
value: {}
});
Prefs.prototype.get = function(key, defaultValue) {
Prefs.prototype.get = function (key, defaultValue) {
if (key in values) {
return values[key];
}
@ -364,11 +400,11 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() {
console.warn("No default preference for '%s'", key);
};
Prefs.prototype.getAll = function(key) {
Prefs.prototype.getAll = function (key) {
return deepCopy(values);
};
Prefs.prototype.set = function(key, value, options) {
Prefs.prototype.set = function (key, value, options) {
var oldValue = deepCopy(values[key]);
values[key] = value;
defineReadonlyProperty(this.readOnlyValues, key, value);
@ -377,32 +413,47 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() {
}
};
Prefs.prototype.remove = function(key) { me.set(key, undefined) };
Prefs.prototype.remove = function (key) {
me.set(key, undefined);
};
Prefs.prototype.broadcast = function(key, value, options) {
var message = {method: "prefChanged", prefName: key, value: value};
Prefs.prototype.broadcast = function (key, value, options) {
var message = {
method: "prefChanged",
prefName: key,
value: value
};
notifyAllTabs(message);
chrome.runtime.sendMessage(message);
if (key == "disableAll") {
notifyAllTabs({method: "styleDisableAll", disableAll: value});
notifyAllTabs({
method: "styleDisableAll",
disableAll: value
});
}
if (!options || !options.noSync) {
clearTimeout(syncTimeout);
syncTimeout = setTimeout(function() {
getSync().set({"settings": values});
syncTimeout = setTimeout(function () {
getSync().set({
"settings": values
});
}, 0);
}
};
Object.keys(defaults).forEach(function(key) {
me.set(key, defaults[key], {noBroadcast: true});
Object.keys(defaults).forEach(function (key) {
me.set(key, defaults[key], {
noBroadcast: true
});
});
getSync().get("settings", function(result) {
getSync().get("settings", function (result) {
var synced = result.settings;
for (var key in defaults) {
if (synced && (key in synced)) {
me.set(key, synced[key], {noSync: true});
me.set(key, synced[key], {
noSync: true
});
} else {
var value = tryMigrating(key);
if (value !== undefined) {
@ -412,18 +463,23 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() {
}
});
chrome.storage.onChanged.addListener(function(changes, area) {
chrome.storage.onChanged.addListener(function (changes, area) {
if (area == "sync" && "settings" in changes) {
var synced = changes.settings.newValue;
var synced = changes.settings.newValue,
key;
if (synced) {
for (key in defaults) {
if (key in synced) {
me.set(key, synced[key], {noSync: true});
me.set(key, synced[key], {
noSync: true
});
}
}
} else {
// user manually deleted our settings, we'll recreate them
getSync().set({"settings": values});
getSync().set({
"settings": values
});
}
}
});
@ -443,24 +499,30 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() {
case "object":
try {
return JSON.parse(value);
} catch(e) {
} catch (e) {
console.log("Cannot migrate from localStorage %s = '%s': %o", key, value, e);
return undefined;
}
}
return value;
}
};
}();
function getCodeMirrorThemes(callback) {
chrome.runtime.getPackageDirectoryEntry(function(rootDir) {
rootDir.getDirectory("codemirror/theme", {create: false}, function(themeDir) {
themeDir.createReader().readEntries(function(entries) {
chrome.runtime.getPackageDirectoryEntry(function (rootDir) {
rootDir.getDirectory("codemirror/theme", {
create: false
}, function (themeDir) {
themeDir.createReader().readEntries(function (entries) {
var themes = [chrome.i18n.getMessage("defaultTheme")];
entries
.filter(function(entry) { return entry.isFile })
.sort(function(a, b) { return a.name < b.name ? -1 : 1 })
.forEach(function(entry) {
.filter(function (entry) {
return entry.isFile;
})
.sort(function (a, b) {
return a.name < b.name ? -1 : 1;
})
.forEach(function (entry) {
themes.push(entry.name.replace(/\.css$/, ""));
});
if (callback) {
@ -474,14 +536,24 @@ function getCodeMirrorThemes(callback) {
function sessionStorageHash(name) {
var hash = {
value: {},
set: function(k, v) { this.value[k] = v; this.updateStorage(); },
unset: function(k) { delete this.value[k]; this.updateStorage(); },
updateStorage: function() {
set: function (k, v) {
this.value[k] = v;
this.updateStorage();
},
unset: function (k) {
delete this.value[k];
this.updateStorage();
},
updateStorage: function () {
sessionStorage[this.name] = JSON.stringify(this.value);
}
};
try { hash.value = JSON.parse(sessionStorage[name]); } catch(e) {}
Object.defineProperty(hash, "name", {value: name});
try {
hash.value = JSON.parse(sessionStorage[name]);
} catch (e) {}
Object.defineProperty(hash, "name", {
value: name
});
return hash;
}
@ -494,7 +566,7 @@ function deepCopy(obj) {
}
}
function deepMerge(target, obj1 /* plus any number of object arguments */) {
function deepMerge(target, obj1 /* plus any number of object arguments */ ) {
for (var i = 1; i < arguments.length; i++) {
var obj = arguments[i];
for (var k in obj) {
@ -512,7 +584,7 @@ function deepMerge(target, obj1 /* plus any number of object arguments */) {
return target;
}
function shallowMerge(target, obj1 /* plus any number of object arguments */) {
function shallowMerge(target, obj1 /* plus any number of object arguments */ ) {
for (var i = 1; i < arguments.length; i++) {
var obj = arguments[i];
for (var k in obj) {
@ -544,7 +616,10 @@ function defineReadonlyProperty(obj, key, value) {
if (typeof copy == "object") {
Object.freeze(copy);
}
Object.defineProperty(obj, key, {value: copy, configurable: true})
Object.defineProperty(obj, key, {
value: copy,
configurable: true
});
}
// Polyfill, can be removed when Firefox gets this - https://bugzilla.mozilla.org/show_bug.cgi?id=1220494
@ -554,10 +629,10 @@ function getSync() {
}
crappyStorage = {};
return {
get: function(key, callback) {
get: function (key, callback) {
callback(crappyStorage[key] || {});
},
set: function(source, callback) {
set: function (source, callback) {
for (var property in source) {
if (source.hasOwnProperty(property)) {
crappyStorage[property] = source[property];
@ -565,5 +640,5 @@ function getSync() {
}
callback();
}
}
};
}