summaryrefslogtreecommitdiffstats
path: root/dom/settings/SettingsDB.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'dom/settings/SettingsDB.jsm')
-rw-r--r--dom/settings/SettingsDB.jsm249
1 files changed, 249 insertions, 0 deletions
diff --git a/dom/settings/SettingsDB.jsm b/dom/settings/SettingsDB.jsm
new file mode 100644
index 000000000..b7d867a48
--- /dev/null
+++ b/dom/settings/SettingsDB.jsm
@@ -0,0 +1,249 @@
+/* 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]);
+ }
+}