SettingsDB.jsm 8.2 KB

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