diff --git a/background.js b/background.js index 2a0ce538..a5f0e8cf 100644 --- a/background.js +++ b/background.js @@ -1,299 +1,299 @@ -chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { - switch (request.method) { - case "getStyles": - getStyles(request, function(r) { - sendResponse(r); - if (localStorage["show-badge"] == "true") { - if (request.updateBadge) { - var t = getBadgeText(r); - console.log("Tab " + sender.tab.id + " (" + sender.tab.url + ") badge text set to '" + t + "'."); - chrome.browserAction.setBadgeText({text: t, tabId: sender.tab.id}); - } else { - console.log("Tab " + sender.tab.id + " (" + sender.tab.url + ") doesn't get badge text."); - } - } - }); - return true; - case "getStyleApplies": - sendResponse(getApplicableSections(request.style, request.url)); - return true; - case "saveStyle": - saveStyle(request, sendResponse); - return true; - case "styleChanged": - cachedStyles = null; - break; - case "healthCheck": - getDatabase(function() { sendResponse(true); }, function() { sendResponse(false); }); - break; - } -}); - -function getStyles(options, callback) { - - var enabled = fixBoolean(options.enabled); - var url = "url" in options ? options.url : null; - var id = "id" in options ? options.id : null; - var matchUrl = "matchUrl" in options ? options.matchUrl : null; - - var callCallback = function() { - callback(cachedStyles.filter(function(style) { - if (enabled != null && fixBoolean(style.enabled) != enabled) { - return false; - } - if (url != null && style.url != url) { - return false; - } - if (id != null && style.id != id) { - return false; - } - if (matchUrl != null && getApplicableSections(style, matchUrl) == 0) { - return false; - } - return true; - })); - } - - if (cachedStyles) { - callCallback(); - return; - } - - getDatabase(function(db) { - db.readTransaction(function (t) { - 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) { - cachedStyles = []; - var currentStyle = null; - var currentSection = null; - for (var i = 0; i < r.rows.length; i++) { - var values = r.rows.item(i); - var metaName = null; - switch (values.metaName) { - case null: - 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, originalMd5: values.originalMd5, sections: []}; - cachedStyles.push(currentStyle); - } - 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]; - } - } - } - callCallback(); - }, reportError); - }, reportError); - }, reportError); -} - -function fixBoolean(b) { - if (typeof b != "undefined") { - return b != "false"; - } - return null; -} - -const namespacePattern = /^\s*@namespace\s+([a-zA-Z]+\s+)?url\(\"?http:\/\/www.w3.org\/1999\/xhtml\"?\);?\s*$/; -function getApplicableSections(style, url) { - var sections = style.sections.filter(function(section) { - return sectionAppliesToUrl(section, url); - }); - // ignore if it's just a namespace - if (sections.length == 1 && namespacePattern.test(sections[0].code)) { - return []; - } - return sections; -} - -function sectionAppliesToUrl(section, url) { - // only http and https allowed - if (url.indexOf("http") != 0) { - return false; - } - if (!section.urls && !section.domains && !section.urlPrefixes && !section.regexps) { - console.log(section.id + " is global"); - return true; - } - if (section.urls && section.urls.indexOf(url) != -1) { - console.log(section.id + " applies to " + url + " due to URL rules"); - return true; - } - if (section.urlPrefixes && 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 && 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 && section.regexps.some(function(regexp) { - // we want to match the full url, so add ^ and $ if not already present - if (regexp[0] != "^") { - regexp = "^" + regexp; - } - if (regexp[regexp.length - 1] != "$") { - regexp += "$"; - } - try { - var re = new RegExp(regexp); - } catch (ex) { - console.log(section.id + "'s regexp '" + regexp + "' is not valid"); - return false; - } - return (re).test(url); - })) { - console.log(section.id + " applies to " + url + " due to regexp rules"); - return true; - } - console.log(section.id + " does not apply due to " + url); - return false; -} - -var cachedStyles = null; - -function saveStyle(o, callback) { - getDatabase(function(db) { - db.transaction(function(t) { - if (o.id) { - // update whatever's been passed - if ("name" in o) { - t.executeSql('UPDATE styles SET name = ? WHERE id = ?;', [o.name, o.id]); - } - if ("enabled" in o) { - t.executeSql('UPDATE styles SET enabled = ? WHERE id = ?;', [o.enabled, o.id]); - } - if ("url" in o) { - t.executeSql('UPDATE styles SET url = ? WHERE id = ?;', [o.url, o.id]); - } - if ("updateUrl" in o) { - t.executeSql('UPDATE styles SET updateUrl = ? WHERE id = ?;', [o.updateUrl, o.id]); - } - if ("md5Url" in o) { - t.executeSql('UPDATE styles SET md5Url = ? WHERE id = ?;', [o.md5Url, o.id]); - } - if ("originalMd5" in o) { - t.executeSql('UPDATE styles SET originalMd5 = ? WHERE id = ?;', [o.originalMd5, o.id]); - } - } else { - // create a new record - // set optional things to null if they're undefined - ["updateUrl", "md5Url", "url", "originalMd5"].filter(function(att) { - return !(att in o); - }).forEach(function(att) { - o[att] = null; - }); - t.executeSql('INSERT INTO styles (name, enabled, url, updateUrl, md5Url, originalMd5) VALUES (?, ?, ?, ?, ?, ?);', [o.name, true, o.url, o.updateUrl, o.md5Url, o.originalMd5]); - } - - if ("sections" in o) { - if (o.id) { - // clear existing records - t.executeSql('DELETE FROM section_meta WHERE section_id IN (SELECT id FROM sections WHERE style_id = ?);', [o.id]); - t.executeSql('DELETE FROM sections WHERE style_id = ?;', [o.id]); - } - - o.sections.forEach(function(section) { - if (o.id) { - t.executeSql('INSERT INTO sections (style_id, code) VALUES (?, ?);', [o.id, section.code]); - } else { - t.executeSql('INSERT INTO sections (style_id, code) SELECT id, ? FROM styles ORDER BY id DESC LIMIT 1;', [section.code]); - } - if (section.urls) { - section.urls.forEach(function(u) { - t.executeSql("INSERT INTO section_meta (section_id, name, value) SELECT id, 'url', ? FROM sections ORDER BY id DESC LIMIT 1;", [u]); - }); - } - if (section.urlPrefixes) { - section.urlPrefixes.forEach(function(u) { - t.executeSql("INSERT INTO section_meta (section_id, name, value) SELECT id, 'url-prefix', ? FROM sections ORDER BY id DESC LIMIT 1;", [u]); - }); - } - if (section.domains) { - section.domains.forEach(function(u) { - t.executeSql("INSERT INTO section_meta (section_id, name, value) SELECT id, 'domain', ? FROM sections ORDER BY id DESC LIMIT 1;", [u]); - }); - } - if (section.regexps) { - section.regexps.forEach(function(u) { - t.executeSql("INSERT INTO section_meta (section_id, name, value) SELECT id, 'regexp', ? FROM sections ORDER BY id DESC LIMIT 1;", [u]); - }); - } - }); - } - }, reportError, function() {saveFromJSONComplete(o.id, callback)}); - }, reportError); -} - -function saveFromJSONComplete(id, callback) { - cachedStyles = null; - - if (id) { - getStyles({method: "getStyles", id: id}, function(styles) { - saveFromJSONStyleReloaded("styleUpdated", styles[0], callback); - }); - return; - } - - // we need to load the id for new ones - getDatabase(function(db) { - db.readTransaction(function (t) { - t.executeSql('SELECT id FROM styles ORDER BY id DESC LIMIT 1', [], function(t, r) { - var id = r.rows.item(0).id; - getStyles({method: "getStyles", id: id}, function(styles) { - saveFromJSONStyleReloaded("styleAdded", styles[0], callback); - }); - }, reportError) - }, reportError) - }); - -} - -function saveFromJSONStyleReloaded(updateType, style, callback) { - notifyAllTabs({name:updateType, style: style}); - if (callback) { - callback(style); - } -} - -function getDomains(url) { - var d = /.*?:\/*([^\/]+)/.exec(url)[1]; - var domains = [d]; - while (d.indexOf(".") != -1) { - d = d.substring(d.indexOf(".") + 1); - domains.push(d); - } - return domains; -} - -// Get the DB so that any first run actions will be performed immediately when the background page loads. -getDatabase(function() {}, reportError); +chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { + switch (request.method) { + case "getStyles": + getStyles(request, function(r) { + sendResponse(r); + if (localStorage["show-badge"] == "true") { + if (request.updateBadge) { + var t = getBadgeText(r); + console.log("Tab " + sender.tab.id + " (" + sender.tab.url + ") badge text set to '" + t + "'."); + chrome.browserAction.setBadgeText({text: t, tabId: sender.tab.id}); + } else { + console.log("Tab " + sender.tab.id + " (" + sender.tab.url + ") doesn't get badge text."); + } + } + }); + return true; + case "getStyleApplies": + sendResponse(getApplicableSections(request.style, request.url)); + return true; + case "saveStyle": + saveStyle(request, sendResponse); + return true; + case "styleChanged": + cachedStyles = null; + break; + case "healthCheck": + getDatabase(function() { sendResponse(true); }, function() { sendResponse(false); }); + break; + } +}); + +function getStyles(options, callback) { + + var enabled = fixBoolean(options.enabled); + var url = "url" in options ? options.url : null; + var id = "id" in options ? options.id : null; + var matchUrl = "matchUrl" in options ? options.matchUrl : null; + + var callCallback = function() { + callback(cachedStyles.filter(function(style) { + if (enabled != null && fixBoolean(style.enabled) != enabled) { + return false; + } + if (url != null && style.url != url) { + return false; + } + if (id != null && style.id != id) { + return false; + } + if (matchUrl != null && getApplicableSections(style, matchUrl) == 0) { + return false; + } + return true; + })); + } + + if (cachedStyles) { + callCallback(); + return; + } + + getDatabase(function(db) { + db.readTransaction(function (t) { + 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) { + cachedStyles = []; + var currentStyle = null; + var currentSection = null; + for (var i = 0; i < r.rows.length; i++) { + var values = r.rows.item(i); + var metaName = null; + switch (values.metaName) { + case null: + 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, originalMd5: values.originalMd5, sections: []}; + cachedStyles.push(currentStyle); + } + 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]; + } + } + } + callCallback(); + }, reportError); + }, reportError); + }, reportError); +} + +function fixBoolean(b) { + if (typeof b != "undefined") { + return b != "false"; + } + return null; +} + +const namespacePattern = /^\s*@namespace\s+([a-zA-Z]+\s+)?url\(\"?http:\/\/www.w3.org\/1999\/xhtml\"?\);?\s*$/; +function getApplicableSections(style, url) { + var sections = style.sections.filter(function(section) { + return sectionAppliesToUrl(section, url); + }); + // ignore if it's just a namespace + if (sections.length == 1 && namespacePattern.test(sections[0].code)) { + return []; + } + return sections; +} + +function sectionAppliesToUrl(section, url) { + // only http and https allowed + if (url.indexOf("http") != 0) { + return false; + } + if (!section.urls && !section.domains && !section.urlPrefixes && !section.regexps) { + console.log(section.id + " is global"); + return true; + } + if (section.urls && section.urls.indexOf(url) != -1) { + console.log(section.id + " applies to " + url + " due to URL rules"); + return true; + } + if (section.urlPrefixes && 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 && 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 && section.regexps.some(function(regexp) { + // we want to match the full url, so add ^ and $ if not already present + if (regexp[0] != "^") { + regexp = "^" + regexp; + } + if (regexp[regexp.length - 1] != "$") { + regexp += "$"; + } + try { + var re = new RegExp(regexp); + } catch (ex) { + console.log(section.id + "'s regexp '" + regexp + "' is not valid"); + return false; + } + return (re).test(url); + })) { + console.log(section.id + " applies to " + url + " due to regexp rules"); + return true; + } + console.log(section.id + " does not apply due to " + url); + return false; +} + +var cachedStyles = null; + +function saveStyle(o, callback) { + getDatabase(function(db) { + db.transaction(function(t) { + if (o.id) { + // update whatever's been passed + if ("name" in o) { + t.executeSql('UPDATE styles SET name = ? WHERE id = ?;', [o.name, o.id]); + } + if ("enabled" in o) { + t.executeSql('UPDATE styles SET enabled = ? WHERE id = ?;', [o.enabled, o.id]); + } + if ("url" in o) { + t.executeSql('UPDATE styles SET url = ? WHERE id = ?;', [o.url, o.id]); + } + if ("updateUrl" in o) { + t.executeSql('UPDATE styles SET updateUrl = ? WHERE id = ?;', [o.updateUrl, o.id]); + } + if ("md5Url" in o) { + t.executeSql('UPDATE styles SET md5Url = ? WHERE id = ?;', [o.md5Url, o.id]); + } + if ("originalMd5" in o) { + t.executeSql('UPDATE styles SET originalMd5 = ? WHERE id = ?;', [o.originalMd5, o.id]); + } + } else { + // create a new record + // set optional things to null if they're undefined + ["updateUrl", "md5Url", "url", "originalMd5"].filter(function(att) { + return !(att in o); + }).forEach(function(att) { + o[att] = null; + }); + t.executeSql('INSERT INTO styles (name, enabled, url, updateUrl, md5Url, originalMd5) VALUES (?, ?, ?, ?, ?, ?);', [o.name, true, o.url, o.updateUrl, o.md5Url, o.originalMd5]); + } + + if ("sections" in o) { + if (o.id) { + // clear existing records + t.executeSql('DELETE FROM section_meta WHERE section_id IN (SELECT id FROM sections WHERE style_id = ?);', [o.id]); + t.executeSql('DELETE FROM sections WHERE style_id = ?;', [o.id]); + } + + o.sections.forEach(function(section) { + if (o.id) { + t.executeSql('INSERT INTO sections (style_id, code) VALUES (?, ?);', [o.id, section.code]); + } else { + t.executeSql('INSERT INTO sections (style_id, code) SELECT id, ? FROM styles ORDER BY id DESC LIMIT 1;', [section.code]); + } + if (section.urls) { + section.urls.forEach(function(u) { + t.executeSql("INSERT INTO section_meta (section_id, name, value) SELECT id, 'url', ? FROM sections ORDER BY id DESC LIMIT 1;", [u]); + }); + } + if (section.urlPrefixes) { + section.urlPrefixes.forEach(function(u) { + t.executeSql("INSERT INTO section_meta (section_id, name, value) SELECT id, 'url-prefix', ? FROM sections ORDER BY id DESC LIMIT 1;", [u]); + }); + } + if (section.domains) { + section.domains.forEach(function(u) { + t.executeSql("INSERT INTO section_meta (section_id, name, value) SELECT id, 'domain', ? FROM sections ORDER BY id DESC LIMIT 1;", [u]); + }); + } + if (section.regexps) { + section.regexps.forEach(function(u) { + t.executeSql("INSERT INTO section_meta (section_id, name, value) SELECT id, 'regexp', ? FROM sections ORDER BY id DESC LIMIT 1;", [u]); + }); + } + }); + } + }, reportError, function() {saveFromJSONComplete(o.id, callback)}); + }, reportError); +} + +function saveFromJSONComplete(id, callback) { + cachedStyles = null; + + if (id) { + getStyles({method: "getStyles", id: id}, function(styles) { + saveFromJSONStyleReloaded("styleUpdated", styles[0], callback); + }); + return; + } + + // we need to load the id for new ones + getDatabase(function(db) { + db.readTransaction(function (t) { + t.executeSql('SELECT id FROM styles ORDER BY id DESC LIMIT 1', [], function(t, r) { + var id = r.rows.item(0).id; + getStyles({method: "getStyles", id: id}, function(styles) { + saveFromJSONStyleReloaded("styleAdded", styles[0], callback); + }); + }, reportError) + }, reportError) + }); + +} + +function saveFromJSONStyleReloaded(updateType, style, callback) { + notifyAllTabs({name:updateType, style: style}); + if (callback) { + callback(style); + } +} + +function getDomains(url) { + var d = /.*?:\/*([^\/]+)/.exec(url)[1]; + var domains = [d]; + while (d.indexOf(".") != -1) { + d = d.substring(d.indexOf(".") + 1); + domains.push(d); + } + return domains; +} + +// Get the DB so that any first run actions will be performed immediately when the background page loads. +getDatabase(function() {}, reportError); diff --git a/manifest.json b/manifest.json index ade01ecf..25634105 100644 --- a/manifest.json +++ b/manifest.json @@ -1,41 +1,41 @@ -{ - "name": "Stylish", - "version": "1.2.2", - "description": "__MSG_description__", - "homepage_url": "http://userstyles.org", - "manifest_version": 2, - "icons": { - "16": "16.png", - "48": "48.png", - "128": "128.png" - }, - "permissions": [ - "tabs", - "http://userstyles.org/", - "https://userstyles.org/" - ], - "background": { - "page": "background.html" - }, - "content_scripts": [ - { - "matches": ["http://*/*", "https://*/*"], - "run_at": "document_start", - "all_frames": true, - "js": ["apply.js"] - }, - { - "matches": ["http://userstyles.org/*", "https://userstyles.org/*"], - "run_at": "document_end", - "all_frames": false, - "js": ["install.js"] - } - ], - "options_page": "manage.html", - "browser_action": { - "default_icon": "19.png", - "default_title": "Stylish", - "default_popup": "popup.html" - }, - "default_locale": "en" -} +{ + "name": "Stylish", + "version": "1.2.2", + "description": "__MSG_description__", + "homepage_url": "http://userstyles.org", + "manifest_version": 2, + "icons": { + "16": "16.png", + "48": "48.png", + "128": "128.png" + }, + "permissions": [ + "tabs", + "http://userstyles.org/", + "https://userstyles.org/" + ], + "background": { + "page": "background.html" + }, + "content_scripts": [ + { + "matches": ["http://*/*", "https://*/*"], + "run_at": "document_start", + "all_frames": true, + "js": ["apply.js"] + }, + { + "matches": ["http://userstyles.org/*", "https://userstyles.org/*"], + "run_at": "document_end", + "all_frames": false, + "js": ["install.js"] + } + ], + "options_page": "manage.html", + "browser_action": { + "default_icon": "19.png", + "default_title": "Stylish", + "default_popup": "popup.html" + }, + "default_locale": "en" +}