1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This file simulates a typical foreground process of an offline-capable
6 // authoring application. When in an "offline" state, simulated user actions
7 // are recorded for later playback in an IDB data store. When in an "online"
8 // state, the recorded actions are drained from the store (as if being sent
11 self.indexedDB = self.indexedDB || self.webkitIndexedDB ||
13 self.IDBKeyRange = self.IDBKeyRange || self.webkitIDBKeyRange;
16 return document.querySelector(s);
19 function status(message) {
20 var elem = $('#status');
21 while (elem.firstChild)
22 elem.removeChild(elem.firstChild);
23 elem.appendChild(document.createTextNode(message));
26 function log(message) {
30 function error(message) {
32 console.error(message);
35 function unexpectedErrorCallback(e) {
36 error("Unexpected error callback: (" + e.target.error.name + ") " +
37 e.target.webkitErrorMessage);
40 function unexpectedAbortCallback(e) {
41 error("Unexpected abort callback: (" + e.target.error.name + ") " +
42 e.target.webkitErrorMessage);
45 function unexpectedBlockedCallback(e) {
46 error("Unexpected blocked callback!");
49 var DBNAME = 'endurance-db';
56 var request = indexedDB.deleteDatabase(DBNAME);
57 request.onerror = unexpectedErrorCallback;
58 request.onblocked = unexpectedBlockedCallback;
59 request.onsuccess = function () {
60 request = indexedDB.open(DBNAME, DBVERSION);
61 request.onerror = unexpectedErrorCallback;
62 request.onblocked = unexpectedBlockedCallback;
63 request.onupgradeneeded = function () {
65 request.transaction.onabort = unexpectedAbortCallback;
67 var syncStore = db.createObjectStore(
68 'sync-chunks', {keyPath: 'sequence', autoIncrement: true});
69 syncStore.createIndex('doc-index', 'docid');
71 var docStore = db.createObjectStore(
72 'docs', {keyPath: 'docid'});
74 'owner-index', 'owner', {multiEntry: true});
76 var userEventStore = db.createObjectStore(
77 'user-events', {keyPath: 'sequence', autoIncrement: true});
78 userEventStore.createIndex('doc-index', 'docid');
80 request.onsuccess = function () {
82 $('#offline').disabled = true;
83 $('#online').disabled = false;
89 var worker = new Worker('indexeddb_app_worker.js?cachebust');
90 worker.onmessage = function (event) {
91 var data = event.data;
94 unexpectedAbortCallback(
97 webkitErrorMessage: data.webkitErrorMessage}});
100 unexpectedErrorCallback(
103 webkitErrorMessage: data.webkitErrorMessage}});
106 unexpectedBlockedCallback(
109 webkitErrorMessage: data.webkitErrorMessage}});
112 log('WORKER: ' + data.message);
115 error('WORKER: ' + data.message);
119 worker.onerror = function (event) {
120 error("Error in: " + event.filename + "(" + event.lineno + "): " +
124 $('#offline').addEventListener('click', goOffline);
125 $('#online').addEventListener('click', goOnline);
127 var EVENT_INTERVAL = 100;
128 var eventIntervalId = 0;
130 function goOffline() {
134 $('#offline').disabled = offline;
135 $('#online').disabled = !offline;
136 $('#state').innerHTML = 'offline';
139 worker.postMessage({type: 'offline'});
141 eventIntervalId = setInterval(recordEvent, EVENT_INTERVAL);
144 function goOnline() {
148 $('#offline').disabled = offline;
149 $('#online').disabled = !offline;
150 $('#state').innerHTML = 'online';
153 worker.postMessage({type: 'online'});
155 setTimeout(playbackEvents, 100);
156 clearInterval(eventIntervalId);
160 function recordEvent() {
162 error("Database not initialized");
166 var transaction = db.transaction(['user-events'], 'readwrite');
167 var store = transaction.objectStore('user-events');
169 // 'sequence' key will be generated
170 docid: Math.floor(Math.random() * MAX_DOC_ID),
171 timestamp: new Date(),
172 data: randomString(256)
175 log('putting user event');
176 var request = store.put(record);
177 request.onerror = unexpectedErrorCallback;
178 transaction.onabort = unexpectedAbortCallback;
179 transaction.oncomplete = function () {
180 log('put user event');
184 function sendEvent(record, callback) {
190 var serialization = JSON.stringify(record);
194 Math.random() * 200); // Simulate network jitter
197 var PLAYBACK_NONE = 0;
198 var PLAYBACK_SUCCESS = 1;
199 var PLAYBACK_FAILURE = 2;
201 function playbackEvent(callback) {
202 log('playbackEvent');
204 var transaction = db.transaction(['user-events'], 'readonly');
205 transaction.onabort = unexpectedAbortCallback;
206 var store = transaction.objectStore('user-events');
207 var cursorRequest = store.openCursor();
208 cursorRequest.onerror = unexpectedErrorCallback;
209 cursorRequest.onsuccess = function () {
210 var cursor = cursorRequest.result;
212 var record = cursor.value;
213 var key = cursor.key;
214 // NOTE: sendEvent is asynchronous so transaction should finish
219 // Use another transaction to delete event
220 var transaction = db.transaction(['user-events'], 'readwrite');
221 transaction.onabort = unexpectedAbortCallback;
222 var store = transaction.objectStore('user-events');
223 var deleteRequest = store.delete(key);
224 deleteRequest.onerror = unexpectedErrorCallback;
225 transaction.oncomplete = function () {
226 // successfully sent and deleted event
227 callback(PLAYBACK_SUCCESS);
231 callback(PLAYBACK_FAILURE);
235 callback(PLAYBACK_NONE);
240 var playback = false;
242 function playbackEvents() {
243 log('playbackEvents');
245 error("Database not initialized");
253 log("Playing back events");
255 function nextEvent() {
261 log("Done playing back events");
263 case PLAYBACK_SUCCESS:
264 setTimeout(nextEvent, 0);
266 case PLAYBACK_FAILURE:
268 log("Failure during playback (dropped offline?)");
277 function randomString(len) {
280 s += Math.floor((Math.random() * 36)).toString(36);
284 window.onload = function () {
285 log("initializing...");