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...");