1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/settings/SettingsDB.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,205 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +let Cc = Components.classes; 1.11 +let Ci = Components.interfaces; 1.12 +let Cu = Components.utils; 1.13 + 1.14 +Cu.import("resource://gre/modules/Services.jsm"); 1.15 +Cu.import("resource://gre/modules/ObjectWrapper.jsm"); 1.16 + 1.17 +this.EXPORTED_SYMBOLS = ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"]; 1.18 + 1.19 +const DEBUG = false; 1.20 +function debug(s) { 1.21 + if (DEBUG) dump("-*- SettingsDB: " + s + "\n"); 1.22 +} 1.23 + 1.24 +this.SETTINGSDB_NAME = "settings"; 1.25 +this.SETTINGSDB_VERSION = 3; 1.26 +this.SETTINGSSTORE_NAME = "settings"; 1.27 + 1.28 +Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); 1.29 +Cu.import("resource://gre/modules/FileUtils.jsm"); 1.30 +Cu.import("resource://gre/modules/NetUtil.jsm"); 1.31 + 1.32 +this.SettingsDB = function SettingsDB() {} 1.33 + 1.34 +SettingsDB.prototype = { 1.35 + 1.36 + __proto__: IndexedDBHelper.prototype, 1.37 + 1.38 + upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) { 1.39 + let objectStore; 1.40 + if (aOldVersion == 0) { 1.41 + objectStore = aDb.createObjectStore(SETTINGSSTORE_NAME, { keyPath: "settingName" }); 1.42 + if (DEBUG) debug("Created object stores"); 1.43 + } else if (aOldVersion == 1) { 1.44 + if (DEBUG) debug("Get object store for upgrade and remove old index"); 1.45 + objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME); 1.46 + objectStore.deleteIndex("settingValue"); 1.47 + } else { 1.48 + if (DEBUG) debug("Get object store for upgrade"); 1.49 + objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME); 1.50 + } 1.51 + 1.52 + // Loading resource://app/defaults/settings.json doesn't work because 1.53 + // settings.json is not in the omnijar. 1.54 + // So we look for the app dir instead and go from here... 1.55 + let settingsFile = FileUtils.getFile("DefRt", ["settings.json"], false); 1.56 + if (!settingsFile || (settingsFile && !settingsFile.exists())) { 1.57 + // On b2g desktop builds the settings.json file is moved in the 1.58 + // profile directory by the build system. 1.59 + settingsFile = FileUtils.getFile("ProfD", ["settings.json"], false); 1.60 + if (!settingsFile || (settingsFile && !settingsFile.exists())) { 1.61 + return; 1.62 + } 1.63 + } 1.64 + 1.65 + let chan = NetUtil.newChannel(settingsFile); 1.66 + let stream = chan.open(); 1.67 + // Obtain a converter to read from a UTF-8 encoded input stream. 1.68 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.69 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.70 + converter.charset = "UTF-8"; 1.71 + let rawstr = converter.ConvertToUnicode(NetUtil.readInputStreamToString( 1.72 + stream, 1.73 + stream.available()) || ""); 1.74 + let settings; 1.75 + try { 1.76 + settings = JSON.parse(rawstr); 1.77 + } catch(e) { 1.78 + if (DEBUG) debug("Error parsing " + settingsFile.path + " : " + e); 1.79 + return; 1.80 + } 1.81 + stream.close(); 1.82 + 1.83 + objectStore.openCursor().onsuccess = function(event) { 1.84 + let cursor = event.target.result; 1.85 + if (cursor) { 1.86 + let value = cursor.value; 1.87 + if (value.settingName in settings) { 1.88 + if (DEBUG) debug("Upgrade " +settings[value.settingName]); 1.89 + value.defaultValue = this.prepareValue(settings[value.settingName]); 1.90 + delete settings[value.settingName]; 1.91 + if ("settingValue" in value) { 1.92 + value.userValue = this.prepareValue(value.settingValue); 1.93 + delete value.settingValue; 1.94 + } 1.95 + cursor.update(value); 1.96 + } else if ("userValue" in value || "settingValue" in value) { 1.97 + value.defaultValue = undefined; 1.98 + if (aOldVersion == 1 && value.settingValue) { 1.99 + value.userValue = this.prepareValue(value.settingValue); 1.100 + delete value.settingValue; 1.101 + } 1.102 + cursor.update(value); 1.103 + } else { 1.104 + cursor.delete(); 1.105 + } 1.106 + cursor.continue(); 1.107 + } else { 1.108 + for (let name in settings) { 1.109 + let value = this.prepareValue(settings[name]); 1.110 + if (DEBUG) debug("Set new:" + name +", " + value); 1.111 + objectStore.add({ settingName: name, defaultValue: value, userValue: undefined }); 1.112 + } 1.113 + } 1.114 + }.bind(this); 1.115 + }, 1.116 + 1.117 + // If the value is a data: uri, convert it to a Blob. 1.118 + convertDataURIToBlob: function(aValue) { 1.119 + /* base64 to ArrayBuffer decoding, from 1.120 + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding 1.121 + */ 1.122 + function b64ToUint6 (nChr) { 1.123 + return nChr > 64 && nChr < 91 ? 1.124 + nChr - 65 1.125 + : nChr > 96 && nChr < 123 ? 1.126 + nChr - 71 1.127 + : nChr > 47 && nChr < 58 ? 1.128 + nChr + 4 1.129 + : nChr === 43 ? 1.130 + 62 1.131 + : nChr === 47 ? 1.132 + 63 1.133 + : 1.134 + 0; 1.135 + } 1.136 + 1.137 + function base64DecToArr(sBase64, nBlocksSize) { 1.138 + let sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), 1.139 + nInLen = sB64Enc.length, 1.140 + nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize 1.141 + : nInLen * 3 + 1 >> 2, 1.142 + taBytes = new Uint8Array(nOutLen); 1.143 + 1.144 + for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { 1.145 + nMod4 = nInIdx & 3; 1.146 + nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; 1.147 + if (nMod4 === 3 || nInLen - nInIdx === 1) { 1.148 + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { 1.149 + taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; 1.150 + } 1.151 + nUint24 = 0; 1.152 + } 1.153 + } 1.154 + return taBytes; 1.155 + } 1.156 + 1.157 + // Check if we have a data: uri, and if it's base64 encoded. 1.158 + // data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA... 1.159 + if (typeof aValue == "string" && aValue.startsWith("data:")) { 1.160 + try { 1.161 + let uri = Services.io.newURI(aValue, null, null); 1.162 + // XXX: that would be nice to reuse the c++ bits of the data: 1.163 + // protocol handler instead. 1.164 + let mimeType = "application/octet-stream"; 1.165 + let mimeDelim = aValue.indexOf(";"); 1.166 + if (mimeDelim !== -1) { 1.167 + mimeType = aValue.substring(5, mimeDelim); 1.168 + } 1.169 + let start = aValue.indexOf(",") + 1; 1.170 + let isBase64 = ((aValue.indexOf("base64") + 7) == start); 1.171 + let payload = aValue.substring(start); 1.172 + 1.173 + return new Blob([isBase64 ? base64DecToArr(payload) : payload], 1.174 + { type: mimeType }); 1.175 + } catch(e) { 1.176 + dump(e); 1.177 + } 1.178 + } 1.179 + return aValue 1.180 + }, 1.181 + 1.182 + // Makes sure any property that is a data: uri gets converted to a Blob. 1.183 + prepareValue: function(aObject) { 1.184 + let kind = ObjectWrapper.getObjectKind(aObject); 1.185 + if (kind == "array") { 1.186 + let res = []; 1.187 + aObject.forEach(function(aObj) { 1.188 + res.push(this.prepareValue(aObj)); 1.189 + }, this); 1.190 + return res; 1.191 + } else if (kind == "file" || kind == "blob" || kind == "date") { 1.192 + return aObject; 1.193 + } else if (kind == "primitive") { 1.194 + return this.convertDataURIToBlob(aObject); 1.195 + } 1.196 + 1.197 + // Fall-through, we now have a dictionary object. 1.198 + for (let prop in aObject) { 1.199 + aObject[prop] = this.prepareValue(aObject[prop]); 1.200 + } 1.201 + return aObject; 1.202 + }, 1.203 + 1.204 + init: function init() { 1.205 + this.initDBHelper(SETTINGSDB_NAME, SETTINGSDB_VERSION, 1.206 + [SETTINGSSTORE_NAME]); 1.207 + } 1.208 +}