add support for saving prefs and bookmarks in an indexedDB (retrieving is not done...
[mandelbrot-web.git] / js / mandelbrot.js
CommitLineData
d0244cd3
RK
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/. */
6a7aa57d 4
7cde05f7
RK
5// Get the best-available indexedDB object.
6var iDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
7var mainDB;
8
51442fd4 9var gMainCanvas, gMainContext;
6a7aa57d
RK
10var gColorPalette = [];
11var gStartTime = 0;
becdac35
RK
12var gCurrentImageData;
13var gLastImageData;
14
15function Startup() {
7cde05f7
RK
16 initDB();
17
51442fd4
RK
18 gMainCanvas = document.getElementById("mbrotImage");
19 gMainContext = gMainCanvas.getContext("2d");
20
21 gMainCanvas.addEventListener("mouseup", imgEvHandler, false);
22 gMainCanvas.addEventListener("mousedown", imgEvHandler, false);
23 gMainCanvas.addEventListener("mousemove", imgEvHandler, false);
24 gMainCanvas.addEventListener("touchstart", imgEvHandler, false);
25 gMainCanvas.addEventListener("touchend", imgEvHandler, false);
26 gMainCanvas.addEventListener("touchcancel", imgEvHandler, false);
27 gMainCanvas.addEventListener("touchleave", imgEvHandler, false);
28 gMainCanvas.addEventListener("touchmove", imgEvHandler, false);
29
30 var initTile = new Image();
31 initTile.src = "style/initial-overview.png";
32 gMainContext.drawImage(initTile, 0, 0);
becdac35
RK
33}
34
7cde05f7
RK
35function initDB() {
36 // Open DB.
37 var request = iDB.open("MainDB", 1);
38 request.onerror = function(event) {
39 // Errors can be handled here. Error codes explain in:
40 // https://developer.mozilla.org/en/IndexedDB/IDBDatabaseException#Constants
41 //document.getElementById("debug").textContent =
42 // "error opening mainDB: " + event.target.errorCode;
43 };
44 request.onsuccess = function(event) {
45 //document.getElementById("debug").textContent = "mainDB opened.";
46 mainDB = request.result;
47 };
48 request.onupgradeneeded = function(event) {
49 mainDB = request.result;
50 //document.getElementById("debug").textContent = "mainDB upgraded.";
51 // Create a "prefs" objectStore.
52 var prefsStore = mainDB.createObjectStore("prefs");
53 // Create a "bookmarks" objectStore.
54 var bmStore = mainDB.createObjectStore("bookmarks");
55 mainDB.onversionchange = function(event) {
56 mainDB.close();
57 mainDB = undefined;
58 initDB();
59 };
60 };
61}
62
becdac35
RK
63function getAdjustVal(aName) {
64 var value;
65 switch (aName) {
66 case "image.width":
67 case "image.height":
68 value = 0;
69 try {
70 value = document.getElementById(aName.replace(".", "_")).value;
71 }
72 catch (e) { }
73 if ((value < 10) || (value > 5000)) {
74 value = 300;
7cde05f7 75 gPrefs.set(prefname, value);
becdac35
RK
76 //document.getElementById(aName.replace(".", "_")).value = value;
77 }
78 return value;
79 case "last_image.Cr_*":
80 var Cr_min = -2.0;
81 var Cr_max = 1.0;
82 try {
83 Cr_min = parseFloat(document.getElementById("Cr_min").value);
84 Cr_max = parseFloat(document.getElementById("Cr_max").value);
85 }
86 catch (e) { }
87 if ((Cr_min < -3) || (Cr_min > 2) ||
88 (Cr_max < -3) || (Cr_max > 2) || (Cr_min >= Cr_max)) {
89 Cr_min = -2.0; Cr_max = 1.0;
90 }
7cde05f7
RK
91 gPrefs.set("Cr_min", Cr_min);
92 gPrefs.set("Cr_max", Cr_max);
becdac35
RK
93 document.getElementById("Cr_min").value = Cr_min;
94 document.getElementById("Cr_max").value = Cr_max;
95 return {Cr_min: Cr_min, Cr_max: Cr_max};
96 case "last_image.Ci_*":
97 var Ci_min = -1.5;
98 var Ci_max = 1.5;
99 try {
100 Ci_min = parseFloat(document.getElementById("Ci_min").value);
101 Ci_max = parseFloat(document.getElementById("Ci_max").value);
102 }
103 catch (e) { }
104 if ((Ci_min < -2.5) || (Ci_min > 2.5) ||
105 (Ci_max < -2.5) || (Ci_max > 2.5) || (Ci_min >= Ci_max)) {
106 Ci_min = -1.5; Ci_max = 1.5;
107 }
7cde05f7
RK
108 gPrefs.set("Ci_min", Ci_min);
109 gPrefs.set("Ci_max", Ci_max);
becdac35
RK
110 document.getElementById("Ci_min").value = Ci_min;
111 document.getElementById("Ci_max").value = Ci_max;
112 return {Ci_min: Ci_min, Ci_max: Ci_max};
113 case "iteration_max":
114 value = 500;
115 try {
116 value = document.getElementById("iterMax").value;
117 }
118 catch (e) {
119 setIter(value);
120 }
121 if (value < 10 || value > 10000) {
122 value = 500;
123 setIter(value);
124 }
125 return value;
126 case "use_algorithm":
127 value = "numeric";
128 try {
129 value = document.getElementById("algorithm").value;
130 }
131 catch (e) {
132 setAlgorithm(value);
133 }
134 return value;
135 case "color_palette":
136 value = "kairo";
137 try {
138 value = document.getElementById("palette").value;
139 }
140 catch(e) {
141 setPalette(value);
142 }
143 return value;
144 case "syncProportions":
145 value = true;
146 try {
147 value = document.getElementById("proportional").value;
148 }
149 catch(e) {
7cde05f7 150 gPrefs.set(prefname, value);
becdac35
RK
151 document.getElementById("proportional").value = value;
152 }
153 return value;
154 default:
155 return false;
156 }
157}
158
159function setVal(aName, aValue) {
160 switch (aName) {
161 case "image.width":
162 case "image.height":
7cde05f7 163 gPrefs.set(aName, value);
becdac35
RK
164 document.getElementById(aName.replace(".", "_")).value = value;
165 break;
166 case "last_image.Cr_*":
7cde05f7
RK
167 gPrefs.set("Cr_min", Cr_min);
168 gPrefs.set("Cr_max", Cr_max);
becdac35
RK
169 document.getElementById("Cr_min").value = aValue.Cr_min;
170 document.getElementById("Cr_max").value = aValue.Cr_max;
171 break;
172 case "last_image.Ci_*":
7cde05f7
RK
173 gPrefs.set("Ci_min", Ci_min);
174 gPrefs.set("Ci_max", Ci_max);
becdac35
RK
175 document.getElementById("Ci_min").value = aValue.Ci_min;
176 document.getElementById("Ci_max").value = aValue.Ci_max;
177 break;
178 case "iteration_max":
179 setIter(aValue);
180 break;
181 case "use_algorithm":
182 setAlgorithm(aValue);
183 break;
184 case "color_palette":
185 setPalette(valueaValue);
186 break;
187 case "syncProportions":
7cde05f7 188 gPrefs.set(aName, value);
becdac35
RK
189 document.getElementById("proportional").value = aValue;
190 break;
191 }
192}
193
194function adjustCoordsAndDraw(aC_min, aC_max) {
195 var iWidth = getAdjustVal("image.width");
196 var iHeight = getAdjustVal("image.height");
197
198 // correct coordinates
199 if (aC_min.r < -2)
200 aC_min.r = -2;
201 if (aC_max.r > 2)
202 aC_max.r = 2;
203 if ((aC_min.r > 2) || (aC_max.r < -2) || (aC_min.r >= aC_max.r)) {
204 aC_min.r = -2.0; aC_max.r = 1.0;
205 }
206 if (aC_min.i < -2)
207 aC_min.i = -2;
208 if (aC_max.i > 2)
209 aC_max.i = 2;
210 if ((aC_min.i > 2) || (aC_max.i < -2) || (aC_min.i >= aC_max.i)) {
211 aC_min.i = -1.3; aC_max.i = 1.3;
212 }
213
214 var CWidth = aC_max.r - aC_min.r;
215 var CHeight = aC_max.i - aC_min.i;
216 var C_mid = new complex(aC_min.r + CWidth / 2, aC_min.i + CHeight / 2);
217
218 var CRatio = Math.max(CWidth / iWidth, CHeight / iHeight);
219
220 setVal("last_image.Cr_*", {Cr_min: C_mid.r - iWidth * CRatio / 2,
221 Cr_max: C_mid.r + iWidth * CRatio / 2});
222 setVal("last_image.Ci_*", {Ci_min: C_mid.i - iHeight * CRatio / 2,
223 Ci_max: C_mid.i + iHeight * CRatio / 2});
224
225 drawImage();
226}
6a7aa57d
RK
227
228function drawImage() {
51442fd4
RK
229 var canvas = gMainCanvas;
230 var context = gMainContext;
6a7aa57d
RK
231
232 document.getElementById("calcTime").textContent = "--";
233
becdac35
RK
234 if (gCurrentImageData) {
235 gLastImageData = gCurrentImageData;
95d05599 236 document.getElementById("backButton").disabled = false;
becdac35
RK
237 }
238
6a7aa57d
RK
239 gColorPalette = getColorPalette(document.getElementById("palette").value);
240
becdac35
RK
241 var Cr_vals = getAdjustVal("last_image.Cr_*");
242 var Cr_min = Cr_vals.Cr_min;
243 var Cr_max = Cr_vals.Cr_max;
6a7aa57d 244
becdac35
RK
245 var Ci_vals = getAdjustVal("last_image.Ci_*");
246 var Ci_min = Ci_vals.Ci_min;
247 var Ci_max = Ci_vals.Ci_max;
248
249 var iterMax = getAdjustVal("iteration_max");
250 var algorithm = getAdjustVal("use_algorithm");
6a7aa57d
RK
251
252 var iWidth = canvas.width;
253 if ((iWidth < 10) || (iWidth > 5000)) {
254 iWidth = 300;
255 canvas.width = iWidth;
256 }
257 var iHeight = canvas.height;
258 if ((iHeight < 10) || (iHeight > 5000)) {
259 iHeight = 300;
260 canvas.height = iHeight;
261 }
262
becdac35
RK
263 gCurrentImageData = {
264 C_min: new complex(Cr_min, Ci_min),
265 C_max: new complex(Cr_max, Ci_max),
266 iWidth: iWidth,
267 iHeight: iHeight,
268 iterMax: iterMax
269 };
270
6a7aa57d
RK
271 context.fillStyle = "rgba(255, 255, 255, 127)";
272 context.fillRect(0, 0, canvas.width, canvas.height);
273
274 gStartTime = new Date();
275
276 drawLine(0, [Cr_min, Cr_max, Ci_min, Ci_max],
277 canvas, context, iterMax, algorithm);
278}
279
280function drawLine(line, dimensions, canvas, context, iterMax, algorithm) {
281 var Cr_min = dimensions[0];
282 var Cr_max = dimensions[1];
283 var Cr_scale = Cr_max - Cr_min;
284
285 var Ci_min = dimensions[2];
286 var Ci_max = dimensions[3];
287 var Ci_scale = Ci_max - Ci_min;
288
289 var lines = Math.min(canvas.height - line, 8);
290 var imageData = context.createImageData(canvas.width, lines);
291 var pixels = imageData.data;
292 var idx = 0;
293 for (var img_y = line; img_y < canvas.height && img_y < line+8; img_y++)
294 for (var img_x = 0; img_x < canvas.width; img_x++) {
295 var C = new complex(Cr_min + (img_x / canvas.width) * Cr_scale,
296 Ci_min + (img_y / canvas.height) * Ci_scale);
297 var colors = drawPoint(context, img_x, img_y, C, iterMax, algorithm);
298 pixels[idx++] = colors[0];
299 pixels[idx++] = colors[1];
300 pixels[idx++] = colors[2];
301 pixels[idx++] = colors[3];
302 }
303 context.putImageData(imageData, 0, line);
304
305 if (img_y < canvas.height)
306 setTimeout(drawLine, 0, img_y, dimensions, canvas, context, iterMax, algorithm);
307 else if (gStartTime)
308 EndCalc();
309}
310
311function EndCalc() {
312 var endTime = new Date();
313 var timeUsed = (endTime.getTime() - gStartTime.getTime()) / 1000;
314 document.getElementById("calcTime").textContent = timeUsed.toFixed(3) + " seconds";
315}
316
317function complex(aReal, aImag) {
318 this.r = aReal;
319 this.i = aImag;
320}
321complex.prototype = {
322 square: function() {
323 return new complex(this.r * this.r - this.i * this.i,
324 2 * this.r * this.i);
325 },
326 dist: function() {
327 return Math.sqrt(this.r * this.r + this.i * this.i);
328 },
329 add: function(aComplex) {
330 return new complex(this.r + aComplex.r, this.i + aComplex.i);
331 }
332}
333
334function mandelbrotValueOO (aC, aIterMax) {
335 // this would be nice code in general but it looks like JS objects are too heavy for normal use.
336 var Z = new complex(0.0, 0.0);
337 for (var iter = 0; iter < aIterMax; iter++) {
338 Z = Z.square().add(aC);
339 if (Z.r * Z.r + Z.i * Z.i > 256) { break; }
340 }
341 return iter;
342}
343
344function mandelbrotValueNumeric (aC, aIterMax) {
345 // optimized numeric code for fast calculation
346 var Cr = aC.r, Ci = aC.i;
347 var Zr = 0.0, Zi = 0.0;
348 var Zr2 = Zr * Zr, Zi2 = Zi * Zi;
349 for (var iter = 0; iter < aIterMax; iter++) {
350 Zi = 2 * Zr * Zi + Ci;
351 Zr = Zr2 - Zi2 + Cr;
352
353 Zr2 = Zr * Zr; Zi2 = Zi * Zi;
354 if (Zr2 + Zi2 > 256) { break; }
355 }
356 return iter;
357}
358
359function getColor(aIterValue, aIterMax) {
360 var standardizedValue = Math.round(aIterValue * 1024 / aIterMax);
361 if (gColorPalette && gColorPalette.length)
362 return gColorPalette[standardizedValue];
363
364 // fallback to simple b/w if for some reason we don't have a palette
365 if (aIterValue == aIterMax)
366 return [0, 0, 0, 255];
367 else
368 return [255, 255, 255, 255];
369}
370
371function getColorPalette(palName) {
372 var palette = [];
373 switch (palName) {
374 case 'bw':
375 for (var i = 0; i < 1024; i++) {
376 palette[i] = [255, 255, 255, 255];
377 }
378 palette[1024] = [0, 0, 0, 255];
379 break;
380 case 'kairo':
381 // outer areas
382 for (var i = 0; i < 32; i++) {
383 var cc1 = Math.floor(i * 127 / 31);
384 var cc2 = 170 - Math.floor(i * 43 / 31);
385 palette[i] = [cc1, cc2, cc1, 255];
386 }
387 // inner areas
388 for (var i = 0; i < 51; i++) {
389 var cc = Math.floor(i * 170 / 50);
390 palette[32 + i] = [cc, 0, (170-cc), 255];
391 }
392 // corona
393 for (var i = 0; i < 101; i++) {
394 var cc = Math.floor(i * 200 / 100);
395 palette[83 + i] = [255, cc, 0, 255];
396 }
397 // inner corona
398 for (var i = 0; i < 201; i++) {
399 var cc1 = 255 - Math.floor(i * 85 / 200);
400 var cc2 = 200 - Math.floor(i * 30 / 200);
401 var cc3 = Math.floor(i * 170 / 200);
402 palette[184 + i] = [cc1, cc2, cc3, 255];
403 }
404 for (var i = 0; i < 301; i++) {
405 var cc1 = 170 - Math.floor(i * 43 / 300);
406 var cc2 = 170 + Math.floor(i * 85 / 300);
407 palette[385 + i] = [cc1, cc1, cc2, 255];
408 }
409 for (var i = 0; i < 338; i++) {
410 var cc = 127 + Math.floor(i * 128 / 337);
411 palette[686 + i] = [cc, cc, 255, 255];
412 }
413 palette[1024] = [0, 0, 0, 255];
414 break;
415 case 'rainbow-linear1':
416 for (var i = 0; i < 256; i++) {
417 palette[i] = [i, 0, 0, 255];
418 palette[256 + i] = [255, i, 0, 255];
419 palette[512 + i] = [255 - i, 255, i, 255];
420 palette[768 + i] = [i, 255-i, 255, 255];
421 }
422 palette[1024] = [0, 0, 0, 255];
423 break;
becdac35
RK
424 case 'rainbow-squared1':
425 for (var i = 0; i < 34; i++) {
426 var cc = Math.floor(i * 255 / 33);
427 palette[i] = [cc, 0, 0, 255];
428 }
429 for (var i = 0; i < 137; i++) {
430 var cc = Math.floor(i * 255 / 136);
431 palette[34 + i] = [255, cc, 0, 255];
432 }
433 for (var i = 0; i < 307; i++) {
434 var cc = Math.floor(i * 255 / 306);
435 palette[171 + i] = [255 - cc, 255, cc, 255];
436 }
437 for (var i = 0; i < 546; i++) {
438 var cc = Math.floor(i * 255 / 545);
439 palette[478 + i] = [cc, 255 - cc, 255, 255];
440 }
441 palette[1024] = [0, 0, 0, 255];
442 break;
443 case 'rainbow-linear2':
444 for (var i = 0; i < 205; i++) {
445 var cc = Math.floor(i * 255 / 204);
446 palette[i] = [255, cc, 0, 255];
447 palette[204 + i] = [255 - cc, 255, 0, 255];
448 palette[409 + i] = [0, 255, cc, 255];
449 palette[614 + i] = [0, 255 - cc, 255, 255];
450 palette[819 + i] = [cc, 0, 255, 255];
451 }
452 palette[1024] = [0, 0, 0, 255];
453 break;
454 case 'rainbow-squared2':
455 for (var i = 0; i < 19; i++) {
456 var cc = Math.floor(i * 255 / 18);
457 palette[i] = [255, cc, 0, 255];
458 }
459 for (var i = 0; i < 74; i++) {
460 var cc = Math.floor(i * 255 / 73);
461 palette[19 + i] = [255 - cc, 255, 0, 255];
462 }
463 for (var i = 0; i < 168; i++) {
464 var cc = Math.floor(i * 255 / 167);
465 palette[93 + i] = [0, 255, cc, 255];
466 }
467 for (var i = 0; i < 298; i++) {
468 var cc = Math.floor(i * 255 / 297);
469 palette[261 + i] = [0, 255 - cc, 255, 255];
470 }
471 for (var i = 0; i < 465; i++) {
472 var cc = Math.floor(i * 255 / 464);
473 palette[559 + i] = [cc, 0, 255, 255];
474 }
475 palette[1024] = [0, 0, 0, 255];
476 break;
6a7aa57d
RK
477 }
478 return palette;
479}
480
481function drawPoint(context, img_x, img_y, C, iterMax, algorithm) {
482 var itVal;
483 switch (algorithm) {
484 case 'oo':
485 itVal = mandelbrotValueOO(C, iterMax);
486 break;
487 case 'numeric':
488 default:
489 itVal = mandelbrotValueNumeric(C, iterMax);
490 break;
491 }
492 return getColor(itVal, iterMax);
493}
494
495// ########## UI functions ##########
496
becdac35
RK
497var zoomstart;
498var imgBackup;
499var zoomTouchID;
500
501var imgEvHandler = {
502 handleEvent: function(aEvent) {
503 var canvas = document.getElementById("mbrotImage");
504 var context = canvas.getContext("2d");
505 var touchEvent = aEvent.type.indexOf('touch') != -1;
506
507 // Bail out if this is neither a touch nor left-click.
508 if (!touchEvent && aEvent.button != 0)
509 return;
510
511 // Bail out if the started touch can't be found.
512 if (touchEvent && zoomstart &&
513 !aEvent.changedTouches.identifiedTouch(zoomTouchID))
514 return;
515
516 var coordObj = touchEvent ?
517 aEvent.changedTouches.identifiedTouch(zoomTouchID) :
518 aEvent;
519
520 switch (aEvent.type) {
521 case 'mousedown':
522 case 'touchstart':
523 if (touchEvent) {
524 zoomTouchID = aEvent.changedTouches.item(0).identifier;
525 coordObj = aEvent.changedTouches.identifiedTouch(zoomTouchID);
526 }
527 // left button - start dragzoom
528 zoomstart = {x: coordObj.clientX - canvas.offsetLeft,
529 y: coordObj.clientY - canvas.offsetTop};
530 imgBackup = context.getImageData(0, 0, canvas.width, canvas.height);
531 break;
532 case 'mouseup':
533 case 'touchend':
534 if (zoomstart) {
535 context.putImageData(imgBackup, 0, 0);
536 var zoomend = {x: coordObj.clientX - canvas.offsetLeft,
537 y: coordObj.clientY - canvas.offsetTop};
538
539 // make sure zoomend is bigger than zoomstart
540 if ((zoomend.x == zoomstart.x) || (zoomend.y == zoomstart.y)) {
541 // cannot zoom what has no area, discard it
542 zoomstart = undefined;
543 return;
544 }
545 if (zoomend.x < zoomstart.x)
546 [zoomend.x, zoomstart.x] = [zoomstart.x, zoomend.x];
547 if (zoomend.y < zoomstart.y)
548 [zoomend.y, zoomstart.y] = [zoomstart.y, zoomend.y];
549
95d05599
RK
550 if (gCurrentImageData) {
551 // determine new "coordinates"
552 var CWidth = gCurrentImageData.C_max.r - gCurrentImageData.C_min.r;
553 var CHeight = gCurrentImageData.C_max.i - gCurrentImageData.C_min.i;
554 var newC_min = new complex(
555 gCurrentImageData.C_min.r + zoomstart.x / gCurrentImageData.iWidth * CWidth,
556 gCurrentImageData.C_min.i + zoomstart.y / gCurrentImageData.iHeight * CHeight);
557 var newC_max = new complex(
558 gCurrentImageData.C_min.r + zoomend.x / gCurrentImageData.iWidth * CWidth,
559 gCurrentImageData.C_min.i + zoomend.y / gCurrentImageData.iHeight * CHeight);
560 }
561 else {
562 var newC_min = new complex(-2, -1.5);
563 var newC_max = new complex(1, 1.5);
564 }
becdac35
RK
565
566 adjustCoordsAndDraw(newC_min, newC_max);
567 }
568 zoomstart = undefined;
569 break;
570 case 'mousemove':
571 case 'touchmove':
572 if (zoomstart) {
573 context.putImageData(imgBackup, 0, 0);
574 context.strokeStyle = "rgb(255,255,31)";
575 context.strokeRect(zoomstart.x, zoomstart.y,
576 coordObj.clientX - canvas.offsetLeft - zoomstart.x,
577 coordObj.clientY - canvas.offsetTop - zoomstart.y);
578 }
95d05599 579 break;
becdac35
RK
580 }
581 }
582};
583
95d05599
RK
584function drawIfEmpty() {
585 if (!gCurrentImageData) {
586 drawImage();
587 }
588}
589
6a7aa57d
RK
590function toggleSettings() {
591 var fs = document.getElementById("settings");
592 if (fs.style.display != "block") {
593 fs.style.display = "block";
594 }
595 else {
596 fs.style.display = "none";
597 }
becdac35
RK
598}
599
600function goBack() {
601 if (gLastImageData) {
602 document.getElementById("iterMax").value = gLastImageData.iterMax;
603 // use gLastImageData.iWidth, gLastImageData.iHeight ???
604 adjustCoordsAndDraw(gLastImageData.C_min, gLastImageData.C_max);
605 gLastImageData = undefined;
95d05599 606 document.getElementById("backButton").disabled = true;
becdac35
RK
607 }
608}
609
610function setIter(aIter) {
7cde05f7 611 gPrefs.set("iteration_max", aIter);
becdac35
RK
612 document.getElementById("iterMax").value = aIter;
613}
614
615function setPalette(aPaletteID) {
7cde05f7 616 gPrefs.set("color_palette", aPaletteID);
becdac35
RK
617 document.getElementById("palette").value = aPaletteID;
618 gColorPalette = getColorPalette(aPaletteID);
619}
620
621function setAlgorithm(algoID) {
7cde05f7 622 gPrefs.set("use_algorithm", algoID);
becdac35
RK
623 //document.getElementById("algorithm").value = algoID;
624}
7cde05f7
RK
625
626var gPrefs = {
627 objStore: "prefs",
628
629 get: function(aKey, aCallback) {
630 if (!mainDB)
631 return;
632 var transaction = mainDB.transaction([this.objStore]);
633 var request = transaction.objectStore(this.objStore).get(aKey);
634 request.onsuccess = function(event) {
635 aCallback(request.result, event);
636 };
637 request.onerror = function(event) {
638 // Errors can be handled here.
639 aCallback(undefined, event);
640 };
641 },
642
643 set: function(aKey, aValue, aCallback) {
644 if (!mainDB)
645 return;
646 var success = false;
647 var transaction = mainDB.transaction([this.objStore], "readwrite");
648 var objStore = transaction.objectStore(this.objStore);
649 var request = objStore.put(aValue, aKey);
650 request.onsuccess = function(event) {
651 success = true;
652 if (aCallback)
653 aCallback(success, event);
654 };
655 request.onerror = function(event) {
656 // Errors can be handled here.
657 if (aCallback)
658 aCallback(success, event);
659 };
660 },
661
662 unset: function(aKey, aCallback) {
663 if (!mainDB)
664 return;
665 var success = false;
666 var transaction = mainDB.transaction([this.objStore], "readwrite");
667 var request = transaction.objectStore(this.objStore).delete(aKey);
668 request.onsuccess = function(event) {
669 success = true;
670 if (aCallback)
671 aCallback(success, event);
672 };
673 request.onerror = function(event) {
674 // Errors can be handled here.
675 if (aCallback)
676 aCallback(success, event);
677 }
678 }
679};
680
681var gBMStore = {
682 objStore: "bookmarks",
683
684 getList: function(aCallback) {
685 if (!mainDB)
686 return;
687 var transaction = mainDB.transaction([this.objStore]);
688 var objStore = transaction.objectStore(this.objStore);
689 if (objStore.getAll) { // currently Mozilla-specific
690 objStore.getAll().onsuccess = function(event) {
691 aCallback(event.target.result);
692 };
693 }
694 else { // Use cursor (standard method).
695 var BMs = {};
696 objStore.openCursor().onsuccess = function(event) {
697 var cursor = event.target.result;
698 if (cursor) {
699 BMs[cursor.key] = cursor.value;
700 cursor.continue();
701 }
702 else {
703 aCallback(BMs);
704 }
705 };
706 }
707 },
708
709 get: function(aKey, aCallback) {
710 if (!mainDB)
711 return;
712 var transaction = mainDB.transaction([this.objStore]);
713 var request = transaction.objectStore(this.objStore).get(aKey);
714 request.onsuccess = function(event) {
715 aCallback(request.result, event);
716 };
717 request.onerror = function(event) {
718 // Errors can be handled here.
719 aCallback(undefined, event);
720 };
721 },
722
723 set: function(aKey, aValue, aCallback) {
724 if (!mainDB)
725 return;
726 var success = false;
727 var transaction = mainDB.transaction([this.objStore], "readwrite");
728 var objStore = transaction.objectStore(this.objStore);
729 var request = objStore.put(aValue, aKey);
730 request.onsuccess = function(event) {
731 success = true;
732 if (aCallback)
733 aCallback(success, event);
734 };
735 request.onerror = function(event) {
736 // Errors can be handled here.
737 if (aCallback)
738 aCallback(success, event);
739 };
740 },
741
742 unset: function(aKey, aCallback) {
743 if (!mainDB)
744 return;
745 var success = false;
746 var transaction = mainDB.transaction([this.objStore], "readwrite");
747 var request = transaction.objectStore(this.objStore).delete(aKey);
748 request.onsuccess = function(event) {
749 success = true;
750 if (aCallback)
751 aCallback(success, event);
752 };
753 request.onerror = function(event) {
754 // Errors can be handled here.
755 if (aCallback)
756 aCallback(success, event);
757 }
758 },
759
760 clear: function(aCallback) {
761 if (!mainDB)
762 return;
763 var success = false;
764 var transaction = mainDB.transaction([this.objStore], "readwrite");
765 var request = transaction.objectStore(this.objStore).clear();
766 request.onsuccess = function(event) {
767 success = true;
768 if (aCallback)
769 aCallback(success, event);
770 };
771 request.onerror = function(event) {
772 // Errors can be handled here.
773 if (aCallback)
774 aCallback(success, event);
775 }
776 }
777};