blob: f51963b733411008edaba516970222e92a70e286 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file simulates a typical foreground process of an offline-capable
// authoring application. When in an "offline" state, simulated user actions
// are recorded for later playback in an IDB data store. When in an "online"
// state, the recorded actions are drained from the store (as if being sent
// to the server).
var $ = function(s) {
return document.querySelector(s);
};
function status(message) {
var elem = $('#status');
while (elem.firstChild)
elem.removeChild(elem.firstChild);
elem.appendChild(document.createTextNode(message));
}
function log(message) {
status(message);
}
function error(message) {
status(message);
console.error(message);
}
function unexpectedErrorCallback(e) {
error("Unexpected error callback: (" + e.target.error.name + ") " +
e.target.error.message);
}
function unexpectedAbortCallback(e) {
error("Unexpected abort callback: (" + e.target.error.name + ") " +
e.target.error.message);
}
function unexpectedBlockedCallback(e) {
error("Unexpected blocked callback!");
}
var DBNAME = 'endurance-db';
var DBVERSION = 1;
var MAX_DOC_ID = 25;
var db;
function initdb() {
var request = indexedDB.deleteDatabase(DBNAME);
request.onerror = unexpectedErrorCallback;
request.onblocked = unexpectedBlockedCallback;
request.onsuccess = function () {
request = indexedDB.open(DBNAME, DBVERSION);
request.onerror = unexpectedErrorCallback;
request.onblocked = unexpectedBlockedCallback;
request.onupgradeneeded = function () {
db = request.result;
request.transaction.onabort = unexpectedAbortCallback;
var syncStore = db.createObjectStore(
'sync-chunks', {keyPath: 'sequence', autoIncrement: true});
syncStore.createIndex('doc-index', 'docid');
var docStore = db.createObjectStore(
'docs', {keyPath: 'docid'});
docStore.createIndex(
'owner-index', 'owner', {multiEntry: true});
var userEventStore = db.createObjectStore(
'user-events', {keyPath: 'sequence', autoIncrement: true});
userEventStore.createIndex('doc-index', 'docid');
};
request.onsuccess = function () {
log('initialized');
$('#offline').disabled = true;
$('#online').disabled = false;
};
};
}
var offline = true;
var worker = new Worker('indexeddb_app_worker.js?cachebust');
worker.onmessage = function (event) {
var data = event.data;
switch (data.type) {
case 'ABORT':
unexpectedAbortCallback({target: {error: data.error}});
break;
case 'ERROR':
unexpectedErrorCallback({target: {error: data.error}});
break;
case 'BLOCKED':
unexpectedBlockedCallback({target: {error: data.error}});
break;
case 'LOG':
log('WORKER: ' + data.message);
break;
case 'ERROR':
error('WORKER: ' + data.message);
break;
}
};
worker.onerror = function (event) {
error("Error in: " + event.filename + "(" + event.lineno + "): " +
event.message);
};
$('#offline').addEventListener('click', goOffline);
$('#online').addEventListener('click', goOnline);
var EVENT_INTERVAL = 100;
var eventIntervalId = 0;
function goOffline() {
if (offline)
return;
offline = true;
$('#offline').disabled = offline;
$('#online').disabled = !offline;
$('#state').innerHTML = 'offline';
log('offline');
worker.postMessage({type: 'offline'});
eventIntervalId = setInterval(recordEvent, EVENT_INTERVAL);
}
function goOnline() {
if (!offline)
return;
offline = false;
$('#offline').disabled = offline;
$('#online').disabled = !offline;
$('#state').innerHTML = 'online';
log('online');
worker.postMessage({type: 'online'});
setTimeout(playbackEvents, 100);
clearInterval(eventIntervalId);
eventIntervalId = 0;
};
function recordEvent() {
if (!db) {
error("Database not initialized");
return;
}
var transaction = db.transaction(['user-events'], 'readwrite');
var store = transaction.objectStore('user-events');
var record = {
// 'sequence' key will be generated
docid: Math.floor(Math.random() * MAX_DOC_ID),
timestamp: new Date(),
data: randomString(256)
};
log('putting user event');
var request = store.put(record);
request.onerror = unexpectedErrorCallback;
transaction.onabort = unexpectedAbortCallback;
transaction.oncomplete = function () {
log('put user event');
};
}
function sendEvent(record, callback) {
setTimeout(
function () {
if (offline)
callback(false);
else {
var serialization = JSON.stringify(record);
callback(true);
}
},
Math.random() * 200); // Simulate network jitter
}
var PLAYBACK_NONE = 0;
var PLAYBACK_SUCCESS = 1;
var PLAYBACK_FAILURE = 2;
function playbackEvent(callback) {
log('playbackEvent');
var result = false;
var transaction = db.transaction(['user-events'], 'readonly');
transaction.onabort = unexpectedAbortCallback;
var store = transaction.objectStore('user-events');
var cursorRequest = store.openCursor();
cursorRequest.onerror = unexpectedErrorCallback;
cursorRequest.onsuccess = function () {
var cursor = cursorRequest.result;
if (cursor) {
var record = cursor.value;
var key = cursor.key;
// NOTE: sendEvent is asynchronous so transaction should finish
sendEvent(
record,
function (success) {
if (success) {
// Use another transaction to delete event
var transaction = db.transaction(['user-events'], 'readwrite');
transaction.onabort = unexpectedAbortCallback;
var store = transaction.objectStore('user-events');
var deleteRequest = store.delete(key);
deleteRequest.onerror = unexpectedErrorCallback;
transaction.oncomplete = function () {
// successfully sent and deleted event
callback(PLAYBACK_SUCCESS);
};
} else {
// No progress made
callback(PLAYBACK_FAILURE);
}
});
} else {
callback(PLAYBACK_NONE);
}
};
}
var playback = false;
function playbackEvents() {
log('playbackEvents');
if (!db) {
error("Database not initialized");
return;
}
if (playback)
return;
playback = true;
log("Playing back events");
function nextEvent() {
playbackEvent(
function (result) {
switch (result) {
case PLAYBACK_NONE:
playback = false;
log("Done playing back events");
return;
case PLAYBACK_SUCCESS:
setTimeout(nextEvent, 0);
return;
case PLAYBACK_FAILURE:
playback = false;
log("Failure during playback (dropped offline?)");
return;
}
});
}
nextEvent();
}
function randomString(len) {
var s = '';
while (len--)
s += Math.floor((Math.random() * 36)).toString(36);
return s;
}
window.onload = function () {
log("initializing...");
initdb();
};