gecko-dev/dom/settings/SettingsDB.jsm

250 lines
8.2 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
Cu.importGlobalProperties(['Blob', 'File']);
Cu.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"];
var DEBUG = false;
var VERBOSE = false;
try {
DEBUG =
Services.prefs.getBoolPref("dom.mozSettings.SettingsDB.debug.enabled");
VERBOSE =
Services.prefs.getBoolPref("dom.mozSettings.SettingsDB.verbose.enabled");
} catch (ex) { }
function debug(s) {
dump("-*- SettingsDB: " + s + "\n");
}
const TYPED_ARRAY_THINGS = new Set([
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
]);
this.SETTINGSDB_NAME = "settings";
this.SETTINGSDB_VERSION = 8;
this.SETTINGSSTORE_NAME = "settings";
Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
this.SettingsDB = function SettingsDB() {}
SettingsDB.prototype = {
__proto__: IndexedDBHelper.prototype,
upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
let objectStore;
if (aOldVersion == 0) {
objectStore = aDb.createObjectStore(SETTINGSSTORE_NAME, { keyPath: "settingName" });
if (VERBOSE) debug("Created object stores");
} else if (aOldVersion == 1) {
if (VERBOSE) debug("Get object store for upgrade and remove old index");
objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME);
objectStore.deleteIndex("settingValue");
} else {
if (VERBOSE) debug("Get object store for upgrade");
objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME);
}
// Loading resource://app/defaults/settings.json doesn't work because
// settings.json is not in the omnijar.
// So we look for the app dir instead and go from here...
let settingsFile = FileUtils.getFile("DefRt", ["settings.json"], false);
if (!settingsFile || (settingsFile && !settingsFile.exists())) {
// On b2g desktop builds the settings.json file is moved in the
// profile directory by the build system.
settingsFile = FileUtils.getFile("ProfD", ["settings.json"], false);
if (!settingsFile || (settingsFile && !settingsFile.exists())) {
return;
}
}
let chan = NetUtil.newChannel({
uri: NetUtil.newURI(settingsFile),
loadUsingSystemPrincipal: true});
let stream = chan.open2();
// Obtain a converter to read from a UTF-8 encoded input stream.
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let rawstr = converter.ConvertToUnicode(NetUtil.readInputStreamToString(
stream,
stream.available()) || "");
let settings;
try {
settings = JSON.parse(rawstr);
} catch(e) {
if (DEBUG) debug("Error parsing " + settingsFile.path + " : " + e);
return;
}
stream.close();
objectStore.openCursor().onsuccess = function(event) {
let cursor = event.target.result;
if (cursor) {
let value = cursor.value;
if (value.settingName in settings) {
if (VERBOSE) debug("Upgrade " +settings[value.settingName]);
value.defaultValue = this.prepareValue(settings[value.settingName]);
delete settings[value.settingName];
if ("settingValue" in value) {
value.userValue = this.prepareValue(value.settingValue);
delete value.settingValue;
}
cursor.update(value);
} else if ("userValue" in value || "settingValue" in value) {
value.defaultValue = undefined;
if (aOldVersion == 1 && value.settingValue) {
value.userValue = this.prepareValue(value.settingValue);
delete value.settingValue;
}
cursor.update(value);
} else {
cursor.delete();
}
cursor.continue();
} else {
for (let name in settings) {
let value = this.prepareValue(settings[name]);
if (VERBOSE) debug("Set new:" + name +", " + value);
objectStore.add({ settingName: name, defaultValue: value, userValue: undefined });
}
}
}.bind(this);
},
// If the value is a data: uri, convert it to a Blob.
convertDataURIToBlob: function(aValue) {
/* base64 to ArrayBuffer decoding, from
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
*/
function b64ToUint6 (nChr) {
return nChr > 64 && nChr < 91 ?
nChr - 65
: nChr > 96 && nChr < 123 ?
nChr - 71
: nChr > 47 && nChr < 58 ?
nChr + 4
: nChr === 43 ?
62
: nChr === 47 ?
63
:
0;
}
function base64DecToArr(sBase64, nBlocksSize) {
let sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
nInLen = sB64Enc.length,
nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize
: nInLen * 3 + 1 >> 2,
taBytes = new Uint8Array(nOutLen);
for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
}
nUint24 = 0;
}
}
return taBytes;
}
// Check if we have a data: uri, and if it's base64 encoded.
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA...
if (typeof aValue == "string" && aValue.startsWith("data:")) {
try {
let uri = Services.io.newURI(aValue, null, null);
// XXX: that would be nice to reuse the c++ bits of the data:
// protocol handler instead.
let mimeType = "application/octet-stream";
let mimeDelim = aValue.indexOf(";");
if (mimeDelim !== -1) {
mimeType = aValue.substring(5, mimeDelim);
}
let start = aValue.indexOf(",") + 1;
let isBase64 = ((aValue.indexOf("base64") + 7) == start);
let payload = aValue.substring(start);
return new Blob([isBase64 ? base64DecToArr(payload) : payload],
{ type: mimeType });
} catch(e) {
dump(e);
}
}
return aValue
},
getObjectKind: function(aObject) {
if (aObject === null || aObject === undefined) {
return "primitive";
} else if (Array.isArray(aObject)) {
return "array";
} else if (aObject instanceof File) {
return "file";
} else if (aObject instanceof Ci.nsIDOMBlob) {
return "blob";
} else if (aObject.constructor.name == "Date") {
return "date";
} else if (TYPED_ARRAY_THINGS.has(aObject.constructor.name)) {
return aObject.constructor.name;
} else if (typeof aObject == "object") {
return "object";
} else {
return "primitive";
}
},
// Makes sure any property that is a data: uri gets converted to a Blob.
prepareValue: function(aObject) {
let kind = this.getObjectKind(aObject);
if (kind == "array") {
let res = [];
aObject.forEach(function(aObj) {
res.push(this.prepareValue(aObj));
}, this);
return res;
} else if (kind == "file" || kind == "blob" || kind == "date") {
return aObject;
} else if (kind == "primitive") {
return this.convertDataURIToBlob(aObject);
}
// Fall-through, we now have a dictionary object.
let res = {};
for (let prop in aObject) {
res[prop] = this.prepareValue(aObject[prop]);
}
return res;
},
init: function init() {
this.initDBHelper(SETTINGSDB_NAME, SETTINGSDB_VERSION,
[SETTINGSSTORE_NAME]);
}
}