Switch to IndexedDB #167

This commit is contained in:
Jason 2016-03-06 20:27:17 -06:00
parent ddb6f400e7
commit 2973cac28f
8 changed files with 363 additions and 372 deletions

View File

@ -29,7 +29,7 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
removeStyle(request.id, document);
break;
case "styleUpdated":
if (request.style.enabled == "true") {
if (request.style.enabled) {
retireStyle(request.style.id);
// fallthrough to "styleAdded"
} else {
@ -37,7 +37,7 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
break;
}
case "styleAdded":
if (request.style.enabled == "true") {
if (request.style.enabled) {
chrome.runtime.sendMessage({method: "getStyles", matchUrl: location.href, enabled: true, id: request.style.id, asHash: true}, applyStyles);
}
break;

View File

@ -66,9 +66,6 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
case "saveStyle":
saveStyle(request, sendResponse);
return true;
case "styleChanged":
cachedStyles = null;
break;
case "healthCheck":
getDatabase(function() { sendResponse(true); }, function() { sendResponse(false); });
break;
@ -126,279 +123,6 @@ function disableAllStylesToggle(newState) {
prefs.set("disableAll", newState);
}
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;
// Return as a hash from style to applicable sections? Can only be used with matchUrl.
var asHash = "asHash" in options ? options.asHash : false;
var callCallback = function() {
var styles = asHash ? {disableAll: prefs.get("disableAll", false)} : [];
cachedStyles.forEach(function(style) {
if (enabled != null && fixBoolean(style.enabled) != enabled) {
return;
}
if (url != null && style.url != url) {
return;
}
if (id != null && style.id != id) {
return;
}
if (matchUrl != null) {
var applicableSections = getApplicableSections(style, matchUrl);
if (applicableSections.length > 0) {
if (asHash) {
styles[style.id] = applicableSections;
} else {
styles.push(style)
}
}
} else {
styles.push(style);
}
});
callback(styles);
return styles;
}
if (cachedStyles) {
return callCallback();
}
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 (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];
}
}
}
}
callCallback();
}, reportError);
}, reportError);
}, reportError);
}
function fixBoolean(b) {
if (typeof b != "undefined") {
return b != "false";
}
return null;
}
var namespacePattern = /^\s*(@namespace[^;]+;\s*)+$/;
function getApplicableSections(style, url) {
var sections = style.sections.filter(function(section) {
return sectionAppliesToUrl(section, url);
});
// ignore if it's just namespaces
if (sections.length == 1 && namespacePattern.test(sections[0].code)) {
return [];
}
return sections;
}
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) {
return false;
}
// other extensions can't be styled
if (url.indexOf("chrome-extension") == 0 && url.indexOf(chrome.extension.getURL("")) != 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 += "$";
}
var re = runTryCatch(function() { return new RegExp(regexp) });
if (re) {
return (re).test(url);
} else {
console.log(section.id + "'s regexp '" + regexp + "' is not valid");
}
})) {
//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({method: updateType, style: style});
if (callback) {
callback(style);
}
}
// Get the DB so that any first run actions will be performed immediately when the background page loads.
getDatabase(function() {}, reportError);
@ -430,13 +154,6 @@ function openURL(options) {
});
}
// 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) {}
}
var codeMirrorThemes;
getCodeMirrorThemes(function(themes) {
codeMirrorThemes = themes;

View File

@ -1093,7 +1093,7 @@ function init() {
function initWithStyle(style) {
document.getElementById("name").value = style.name;
document.getElementById("enabled").checked = style.enabled == "true";
document.getElementById("enabled").checked = style.enabled;
document.getElementById("url").href = style.url;
// if this was done in response to an update, we need to clear existing sections
getSections().forEach(function(div) { div.remove(); });

View File

@ -29,7 +29,7 @@ function showStyles(styles) {
function createStyleElement(style) {
var e = template.style.cloneNode(true);
e.setAttribute("class", style.enabled == "true" ? "enabled" : "disabled");
e.setAttribute("class", style.enabled ? "enabled" : "disabled");
e.setAttribute("style-id", style.id);
if (style.updateUrl) {
e.setAttribute("style-update-url", style.updateUrl);
@ -190,7 +190,10 @@ function handleUpdate(style) {
}
function handleDelete(id) {
installed.removeChild(installed.querySelector("[style-id='" + id + "']"));
var node = installed.querySelector("[style-id='" + id + "']");
if (node) {
installed.removeChild(node);
}
}
function doCheckUpdate(event) {

View File

@ -23,7 +23,7 @@
"https://userstyles.org/"
],
"background": {
"scripts": ["messaging.js", "storage.js", "background.js"]
"scripts": ["messaging.js", "storage-websql.js", "storage.js", "background.js"]
},
"commands": {
"openManage": {

View File

@ -88,9 +88,9 @@ function createStyleElement(style) {
var e = template.style.cloneNode(true);
var checkbox = e.querySelector(".checker");
checkbox.id = "style-" + style.id;
checkbox.checked = style.enabled == "true";
checkbox.checked = style.enabled;
e.setAttribute("class", "entry " + (style.enabled == "true" ? "enabled" : "disabled"));
e.setAttribute("class", "entry " + (style.enabled ? "enabled" : "disabled"));
e.setAttribute("style-id", style.id);
var styleName = e.querySelector(".style-name");
styleName.appendChild(document.createTextNode(style.name));

152
storage-websql.js Normal file
View File

@ -0,0 +1,152 @@
var webSqlStorage = {
migrate: function() {
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);
});
});
}, null);
},
cleanStyle: function(s) {
delete s.id;
s.sections.forEach(function(section) {
delete section.id;
["urls", "urlPrefixes", "domains", "regexps"].forEach(function(property) {
if (!section[property]) {
section[property] = [];
}
});
});
},
getStyles: function(callback) {
webSqlStorage.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) {
var styles = [];
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 == "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) {
try {
stylishDb = openDatabase('stylish', '', 'Stylish Styles', 5*1024*1024);
} catch (ex) {
error();
throw ex;
}
if (stylishDb.version == "1.0" || stylishDb.version == "") {
webSqlStorage.dbV11(stylishDb, error, ready);
} else if (stylishDb.version == "1.1") {
webSqlStorage.dbV12(stylishDb, error, ready);
} else if (stylishDb.version == "1.2") {
webSqlStorage.dbV13(stylishDb, error, ready);
} else if (stylishDb.version == "1.3") {
webSqlStorage.dbV14(stylishDb, error, ready);
} else if (stylishDb.version == "1.4") {
webSqlStorage.dbV15(stylishDb, error, ready);
} else {
ready(stylishDb);
}
},
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)});
},
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);');
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, function() { webSqlStorage.dbV13(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)});
},
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)});
},
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); });
}
}

View File

@ -1,102 +1,144 @@
var stylishDb = null;
function getDatabase(ready, error) {
if (stylishDb != null && stylishDb.version == "1.5") {
ready(stylishDb);
return;
}
try {
stylishDb = openDatabase('stylish', '', 'Stylish Styles', 5*1024*1024);
} catch (ex) {
error();
throw ex;
}
if (stylishDb.version == "1.0" || stylishDb.version == "") {
dbV11(stylishDb, error, ready);
} else if (stylishDb.version == "1.1") {
dbV12(stylishDb, error, ready);
} else if (stylishDb.version == "1.2") {
dbV13(stylishDb, error, ready);
} else if (stylishDb.version == "1.3") {
dbV14(stylishDb, error, ready);
} else if (stylishDb.version == "1.4") {
dbV15(stylishDb, error, ready);
} else {
ready(stylishDb);
var dbOpenRequest = window.indexedDB.open("stylish", 2);
dbOpenRequest.onsuccess = function(e) {
ready(e.target.result);
};
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});
webSqlStorage.migrate();
}
}
};
function getStyles(options, callback) {
getDatabase(function(db) {
var tx = db.transaction(["styles"], "readonly");
var os = tx.objectStore("styles");
var all = [];
os.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
var s = cursor.value
s.id = cursor.key
all.push(cursor.value);
cursor.continue();
} else {
callback(filterStyles(all, options));
}
};
}, null);
}
function dbV11(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() {dbV12(d, error, done)});
function filterStyles(unfilteredStyles, options) {
var enabled = fixBoolean(options.enabled);
var url = "url" in options ? options.url : null;
var id = "id" in options ? Number(options.id) : null;
var matchUrl = "matchUrl" in options ? options.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;
var styles = asHash ? {disableAll: prefs.get("disableAll", false)} : [];
unfilteredStyles.forEach(function(style) {
if (enabled != null && style.enabled != enabled) {
return;
}
if (url != null && style.url != url) {
return;
}
if (id != null && style.id != id) {
return;
}
if (matchUrl != null) {
var applicableSections = getApplicableSections(style, matchUrl);
if (applicableSections.length > 0) {
if (asHash) {
styles[style.id] = applicableSections;
} else {
styles.push(style)
}
}
} else {
styles.push(style);
}
});
return styles;
}
function dbV12(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);');
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, function() {dbV13(d, error, done)});
}
function saveStyle(o, callback) {
getDatabase(function(db) {
var tx = db.transaction(["styles"], "readwrite");
var os = tx.objectStore("styles");
function dbV13(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() { dbV14(d, error, done)});
}
// Update
if (o.id) {
var request = os.get(Number(o.id));
request.onsuccess = function(event) {
var style = request.result;
for (var prop in o) {
if (prop == "id") {
continue;
}
style[prop] = o[prop];
}
request = os.put(style);
request.onsuccess = function(event) {
notifyAllTabs({method: "styleUpdated", style: style});
if (callback) {
callback(style);
}
};
};
return;
}
function dbV14(d, error, done) {
d.changeVersion(d.version, '1.4', function (t) {
t.executeSql('UPDATE styles SET url = null WHERE url = "undefined";');
}, error, function() { dbV15(d, error, done)});
}
function dbV15(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); });
// Create
// 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;
});
// Set to enabled if not set
if (!("enabled" in o)) {
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) {
// Give it the ID that was generated
o.id = event.target.result;
notifyAllTabs({method: "styleAdded", style: o});
if (callback) {
callback(o);
}
};
});
}
function enableStyle(id, enabled) {
getDatabase(function(db) {
db.transaction(function (t) {
t.executeSql("UPDATE styles SET enabled = ? WHERE id = ?;", [enabled, id]);
}, reportError, function() {
chrome.runtime.sendMessage({method: "styleChanged"});
chrome.runtime.sendMessage({method: "getStyles", id: id}, function(styles) {
handleUpdate(styles[0]);
notifyAllTabs({method: "styleUpdated", style: styles[0]});
});
});
saveStyle({id: id, enabled: enabled}, function(style) {
handleUpdate(style);
notifyAllTabs({method: "styleUpdated", style: style});
});
}
function deleteStyle(id) {
getDatabase(function(db) {
db.transaction(function (t) {
t.executeSql('DELETE FROM section_meta WHERE section_id IN (SELECT id FROM sections WHERE style_id = ?);', [id]);
t.executeSql('DELETE FROM sections WHERE style_id = ?;', [id]);
t.executeSql("DELETE FROM styles WHERE id = ?;", [id]);
}, reportError, function() {
chrome.runtime.sendMessage({method: "styleChanged"});
var tx = db.transaction(["styles"], "readwrite");
var os = tx.objectStore("styles");
var request = os.delete(Number(id));
request.onsuccess = function(event) {
handleDelete(id);
notifyAllTabs({method: "styleDeleted", id: id});
});
};
});
}
@ -109,6 +151,13 @@ function reportError() {
}
}
function fixBoolean(b) {
if (typeof b != "undefined") {
return b != "false";
}
return null;
}
function getDomains(url) {
if (url.indexOf("file:") == 0) {
return [];
@ -132,10 +181,80 @@ function getType(o) {
throw "Not supported - " + o;
}
var namespacePattern = /^\s*(@namespace[^;]+;\s*)+$/;
function getApplicableSections(style, url) {
var sections = style.sections.filter(function(section) {
return sectionAppliesToUrl(section, url);
});
// ignore if it's just namespaces
if (sections.length == 1 && namespacePattern.test(sections[0].code)) {
return [];
}
return sections;
}
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) {
return false;
}
// other extensions can't be styled
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) {
//console.log(section.id + " is global");
return true;
}
if (section.urls.indexOf(url) != -1) {
//console.log(section.id + " applies to " + url + " due to URL rules");
return true;
}
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) {
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) {
// 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 += "$";
}
var re = runTryCatch(function() { return new RegExp(regexp) });
if (re) {
return (re).test(url);
} else {
console.log(section.id + "'s regexp '" + regexp + "' is not valid");
}
})) {
//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;
}
function isCheckbox(el) {
return el.nodeName.toLowerCase() == "input" && "checkbox" == el.type.toLowerCase();
}
// 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) {}
}
// 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) {