Bumping manifests a=b2g-bump
[gecko.git] / dom / settings / SettingsDB.jsm
blob112952814530079eceb8e63a04007cf6d5daa5a2
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 let Cc = Components.classes;
8 let Ci = Components.interfaces;
9 let Cu = Components.utils;
11 Cu.import("resource://gre/modules/Services.jsm");
13 this.EXPORTED_SYMBOLS = ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"];
15 let DEBUG = false;
16 let VERBOSE = false;
18 try {
19   DEBUG   =
20     Services.prefs.getBoolPref("dom.mozSettings.SettingsDB.debug.enabled");
21   VERBOSE =
22     Services.prefs.getBoolPref("dom.mozSettings.SettingsDB.verbose.enabled");
23 } catch (ex) { }
25 function debug(s) {
26   dump("-*- SettingsDB: " + s + "\n");
29 const TYPED_ARRAY_THINGS = new Set([
30   "Int8Array",
31   "Uint8Array",
32   "Uint8ClampedArray",
33   "Int16Array",
34   "Uint16Array",
35   "Int32Array",
36   "Uint32Array",
37   "Float32Array",
38   "Float64Array",
39 ]);
41 this.SETTINGSDB_NAME = "settings";
42 this.SETTINGSDB_VERSION = 5;
43 this.SETTINGSSTORE_NAME = "settings";
45 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
46 Cu.import("resource://gre/modules/FileUtils.jsm");
47 Cu.import("resource://gre/modules/NetUtil.jsm");
49 this.SettingsDB = function SettingsDB() {}
51 SettingsDB.prototype = {
53   __proto__: IndexedDBHelper.prototype,
55   upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
56     let objectStore;
57     if (aOldVersion == 0) {
58       objectStore = aDb.createObjectStore(SETTINGSSTORE_NAME, { keyPath: "settingName" });
59       if (VERBOSE) debug("Created object stores");
60     } else if (aOldVersion == 1) {
61       if (VERBOSE) debug("Get object store for upgrade and remove old index");
62       objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME);
63       objectStore.deleteIndex("settingValue");
64     } else {
65       if (VERBOSE) debug("Get object store for upgrade");
66       objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME);
67     }
69     // Loading resource://app/defaults/settings.json doesn't work because
70     // settings.json is not in the omnijar.
71     // So we look for the app dir instead and go from here...
72     let settingsFile = FileUtils.getFile("DefRt", ["settings.json"], false);
73     if (!settingsFile || (settingsFile && !settingsFile.exists())) {
74       // On b2g desktop builds the settings.json file is moved in the
75       // profile directory by the build system.
76       settingsFile = FileUtils.getFile("ProfD", ["settings.json"], false);
77       if (!settingsFile || (settingsFile && !settingsFile.exists())) {
78         return;
79       }
80     }
82     let chan = NetUtil.newChannel(settingsFile);
83     let stream = chan.open();
84     // Obtain a converter to read from a UTF-8 encoded input stream.
85     let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
86                     .createInstance(Ci.nsIScriptableUnicodeConverter);
87     converter.charset = "UTF-8";
88     let rawstr = converter.ConvertToUnicode(NetUtil.readInputStreamToString(
89                                             stream,
90                                             stream.available()) || "");
91     let settings;
92     try {
93       settings = JSON.parse(rawstr);
94     } catch(e) {
95       if (DEBUG) debug("Error parsing " + settingsFile.path + " : " + e);
96       return;
97     }
98     stream.close();
100     objectStore.openCursor().onsuccess = function(event) {
101       let cursor = event.target.result;
102       if (cursor) {
103         let value = cursor.value;
104         if (value.settingName in settings) {
105           if (VERBOSE) debug("Upgrade " +settings[value.settingName]);
106           value.defaultValue = this.prepareValue(settings[value.settingName]);
107           delete settings[value.settingName];
108           if ("settingValue" in value) {
109             value.userValue = this.prepareValue(value.settingValue);
110             delete value.settingValue;
111           }
112           cursor.update(value);
113         } else if ("userValue" in value || "settingValue" in value) {
114           value.defaultValue = undefined;
115           if (aOldVersion == 1 && value.settingValue) {
116             value.userValue = this.prepareValue(value.settingValue);
117             delete value.settingValue;
118           }
119           cursor.update(value);
120         } else {
121           cursor.delete();
122         }
123         cursor.continue();
124       } else {
125         for (let name in settings) {
126           let value = this.prepareValue(settings[name]);
127           if (VERBOSE) debug("Set new:" + name +", " + value);
128           objectStore.add({ settingName: name, defaultValue: value, userValue: undefined });
129         }
130       }
131     }.bind(this);
132   },
134   // If the value is a data: uri, convert it to a Blob.
135   convertDataURIToBlob: function(aValue) {
136     /* base64 to ArrayBuffer decoding, from
137        https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
138     */
139     function b64ToUint6 (nChr) {
140       return nChr > 64 && nChr < 91 ?
141           nChr - 65
142         : nChr > 96 && nChr < 123 ?
143           nChr - 71
144         : nChr > 47 && nChr < 58 ?
145           nChr + 4
146         : nChr === 43 ?
147           62
148         : nChr === 47 ?
149           63
150         :
151           0;
152     }
154     function base64DecToArr(sBase64, nBlocksSize) {
155       let sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
156           nInLen = sB64Enc.length,
157           nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize
158                                 : nInLen * 3 + 1 >> 2,
159           taBytes = new Uint8Array(nOutLen);
161       for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
162         nMod4 = nInIdx & 3;
163         nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
164         if (nMod4 === 3 || nInLen - nInIdx === 1) {
165           for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
166             taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
167           }
168           nUint24 = 0;
169         }
170       }
171       return taBytes;
172     }
174     // Check if we have a data: uri, and if it's base64 encoded.
175     // data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA...
176     if (typeof aValue == "string" && aValue.startsWith("data:")) {
177       try {
178         let uri = Services.io.newURI(aValue, null, null);
179         // XXX: that would be nice to reuse the c++ bits of the data:
180         // protocol handler instead.
181         let mimeType = "application/octet-stream";
182         let mimeDelim = aValue.indexOf(";");
183         if (mimeDelim !== -1) {
184           mimeType = aValue.substring(5, mimeDelim);
185         }
186         let start = aValue.indexOf(",") + 1;
187         let isBase64 = ((aValue.indexOf("base64") + 7) == start);
188         let payload = aValue.substring(start);
190         return new Blob([isBase64 ? base64DecToArr(payload) : payload],
191                         { type: mimeType });
192       } catch(e) {
193         dump(e);
194       }
195     }
196     return aValue
197   },
199   getObjectKind: function(aObject) {
200     if (aObject === null || aObject === undefined) {
201       return "primitive";
202     } else if (Array.isArray(aObject)) {
203       return "array";
204     } else if (aObject instanceof Ci.nsIDOMFile) {
205       return "file";
206     } else if (aObject instanceof Ci.nsIDOMBlob) {
207       return "blob";
208     } else if (aObject.constructor.name == "Date") {
209       return "date";
210     } else if (TYPED_ARRAY_THINGS.has(aObject.constructor.name)) {
211       return aObject.constructor.name;
212     } else if (typeof aObject == "object") {
213       return "object";
214     } else {
215       return "primitive";
216     }
217   },
219   // Makes sure any property that is a data: uri gets converted to a Blob.
220   prepareValue: function(aObject) {
221     let kind = this.getObjectKind(aObject);
222     if (kind == "array") {
223       let res = [];
224       aObject.forEach(function(aObj) {
225         res.push(this.prepareValue(aObj));
226       }, this);
227       return res;
228     } else if (kind == "file" || kind == "blob" || kind == "date") {
229       return aObject;
230     } else if (kind == "primitive") {
231       return this.convertDataURIToBlob(aObject);
232     }
234     // Fall-through, we now have a dictionary object.
235     let res = {};
236     for (let prop in aObject) {
237       res[prop] = this.prepareValue(aObject[prop]);
238     }
239     return res;
240   },
242   init: function init() {
243     this.initDBHelper(SETTINGSDB_NAME, SETTINGSDB_VERSION,
244                       [SETTINGSSTORE_NAME]);
245   }