diff options
Diffstat (limited to 'dom/settings/SettingsDB.jsm')
-rw-r--r-- | dom/settings/SettingsDB.jsm | 249 |
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]); + } +} |