1 // Copyright (c) 2012 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
12 return document.querySelector(s);
15 function status(message) {
16 var elem = $('#status');
17 while (elem.firstChild)
18 elem.removeChild(elem.firstChild);
19 elem.appendChild(document.createTextNode(message));
22 function log(message) {
26 function error(message) {
28 console.error(message);
31 function unexpectedErrorCallback(e) {
32 error("Unexpected error callback: (" + e.target.error.name + ") " +
33 e.target.error.message);
36 function unexpectedAbortCallback(e) {
37 error("Unexpected abort callback: (" + e.target.error.name + ") " +
38 e.target.error.message);
41 function unexpectedBlockedCallback(e) {
42 error("Unexpected blocked callback!");
45 var DBNAME = 'endurance-db';
52 var request = indexedDB.deleteDatabase(DBNAME);
53 request.onerror = unexpectedErrorCallback;
54 request.onblocked = unexpectedBlockedCallback;
55 request.onsuccess = function () {
56 request = indexedDB.open(DBNAME, DBVERSION);
57 request.onerror = unexpectedErrorCallback;
58 request.onblocked = unexpectedBlockedCallback;
59 request.onupgradeneeded = function () {
61 request.transaction.onabort = unexpectedAbortCallback;
63 var syncStore = db.createObjectStore(
64 'sync-chunks', {keyPath: 'sequence', autoIncrement: true});
65 syncStore.createIndex('doc-index', 'docid');
67 var docStore = db.createObjectStore(
68 'docs', {keyPath: 'docid'});
70 'owner-index', 'owner', {multiEntry: true});
72 var userEventStore = db.createObjectStore(
73 'user-events', {keyPath: 'sequence', autoIncrement: true});
74 userEventStore.createIndex('doc-index', 'docid');
76 request.onsuccess = function () {
78 $('#offline').disabled = true;
79 $('#online').disabled = false;
85 var worker = new Worker('app-worker.js?cachebust');
86 worker.onmessage = function (event) {
87 var data = event.data;
90 unexpectedAbortCallback({target: {error: data.error}});
93 unexpectedErrorCallback({target: {error: data.error}});
96 unexpectedBlockedCallback({target: {error: data.error}});
99 log('WORKER: ' + data.message);
102 error('WORKER: ' + data.message);
106 worker.onerror = function (event) {
107 error("Error in: " + event.filename + "(" + event.lineno + "): " +
111 $('#offline').addEventListener('click', goOffline);
112 $('#online').addEventListener('click', goOnline);
114 var EVENT_INTERVAL = 100;
115 var eventIntervalId = 0;
117 function goOffline() {
121 $('#offline').disabled = offline;
122 $('#online').disabled = !offline;
123 $('#state').innerHTML = 'offline';
126 worker.postMessage({type: 'offline'});
128 eventIntervalId = setInterval(recordEvent, EVENT_INTERVAL);
131 function goOnline() {
135 $('#offline').disabled = offline;
136 $('#online').disabled = !offline;
137 $('#state').innerHTML = 'online';
140 worker.postMessage({type: 'online'});
142 setTimeout(playbackEvents, 100);
143 clearInterval(eventIntervalId);
147 function recordEvent() {
149 error("Database not initialized");
153 var transaction = db.transaction(['user-events'], 'readwrite');
154 var store = transaction.objectStore('user-events');
156 // 'sequence' key will be generated
157 docid: Math.floor(Math.random() * MAX_DOC_ID),
158 timestamp: new Date(),
159 data: randomString(256)
162 log('putting user event');
163 var request = store.put(record);
164 request.onerror = unexpectedErrorCallback;
165 transaction.onabort = unexpectedAbortCallback;
166 transaction.oncomplete = function () {
167 log('put user event');
171 function sendEvent(record, callback) {
177 var serialization = JSON.stringify(record);
181 Math.random() * 200); // Simulate network jitter
184 var PLAYBACK_NONE = 0;
185 var PLAYBACK_SUCCESS = 1;
186 var PLAYBACK_FAILURE = 2;
188 function playbackEvent(callback) {
189 log('playbackEvent');
191 var transaction = db.transaction(['user-events'], 'readonly');
192 transaction.onabort = unexpectedAbortCallback;
193 var store = transaction.objectStore('user-events');
194 var cursorRequest = store.openCursor();
195 cursorRequest.onerror = unexpectedErrorCallback;
196 cursorRequest.onsuccess = function () {
197 var cursor = cursorRequest.result;
199 var record = cursor.value;
200 var key = cursor.key;
201 // NOTE: sendEvent is asynchronous so transaction should finish
206 // Use another transaction to delete event
207 var transaction = db.transaction(['user-events'], 'readwrite');
208 transaction.onabort = unexpectedAbortCallback;
209 var store = transaction.objectStore('user-events');
210 var deleteRequest = store.delete(key);
211 deleteRequest.onerror = unexpectedErrorCallback;
212 transaction.oncomplete = function () {
213 // successfully sent and deleted event
214 callback(PLAYBACK_SUCCESS);
218 callback(PLAYBACK_FAILURE);
222 callback(PLAYBACK_NONE);
227 var playback = false;
229 function playbackEvents() {
230 log('playbackEvents');
232 error("Database not initialized");
240 log("Playing back events");
242 function nextEvent() {
248 log("Done playing back events");
250 case PLAYBACK_SUCCESS:
251 setTimeout(nextEvent, 0);
253 case PLAYBACK_FAILURE:
255 log("Failure during playback (dropped offline?)");
264 function randomString(len) {
267 s += Math.floor((Math.random() * 36)).toString(36);
271 window.onload = function () {
272 log("initializing...");