blob: a5de852f79fe54f05d1a5d9516180ce81b57034c [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @unrestricted
*/
Resources.IndexedDBModel = class extends SDK.SDKModel {
/**
* @param {!SDK.Target} target
* @param {!SDK.SecurityOriginManager} securityOriginManager
*/
constructor(target, securityOriginManager) {
super(Resources.IndexedDBModel, target);
this._securityOriginManager = securityOriginManager;
this._agent = target.indexedDBAgent();
/** @type {!Map.<!Resources.IndexedDBModel.DatabaseId, !Resources.IndexedDBModel.Database>} */
this._databases = new Map();
/** @type {!Object.<string, !Array.<string>>} */
this._databaseNamesBySecurityOrigin = {};
}
/**
* @param {*} idbKey
* @return {({
* array: (!Array<?>|undefined),
* date: (number|undefined),
* number: (number|undefined),
* string: (string|undefined),
* type: !Protocol.IndexedDB.KeyType<string>
* }|undefined)}
*/
static keyFromIDBKey(idbKey) {
if (typeof(idbKey) === 'undefined' || idbKey === null)
return undefined;
var type;
var key = {};
switch (typeof(idbKey)) {
case 'number':
key.number = idbKey;
type = Resources.IndexedDBModel.KeyTypes.NumberType;
break;
case 'string':
key.string = idbKey;
type = Resources.IndexedDBModel.KeyTypes.StringType;
break;
case 'object':
if (idbKey instanceof Date) {
key.date = idbKey.getTime();
type = Resources.IndexedDBModel.KeyTypes.DateType;
} else if (Array.isArray(idbKey)) {
key.array = [];
for (var i = 0; i < idbKey.length; ++i)
key.array.push(Resources.IndexedDBModel.keyFromIDBKey(idbKey[i]));
type = Resources.IndexedDBModel.KeyTypes.ArrayType;
}
break;
default:
return undefined;
}
key.type = /** @type {!Protocol.IndexedDB.KeyType<string>} */ (type);
return key;
}
/**
* @param {?IDBKeyRange=} idbKeyRange
* @return {?Protocol.IndexedDB.KeyRange}
* eturn {?{lower: ?Object, upper: ?Object, lowerOpen: *, upperOpen: *}}
*/
static keyRangeFromIDBKeyRange(idbKeyRange) {
if (typeof idbKeyRange === 'undefined' || idbKeyRange === null)
return null;
var keyRange = {};
keyRange.lower = Resources.IndexedDBModel.keyFromIDBKey(idbKeyRange.lower);
keyRange.upper = Resources.IndexedDBModel.keyFromIDBKey(idbKeyRange.upper);
keyRange.lowerOpen = !!idbKeyRange.lowerOpen;
keyRange.upperOpen = !!idbKeyRange.upperOpen;
return keyRange;
}
/**
* @param {!Protocol.IndexedDB.KeyPath} keyPath
* @return {?string|!Array.<string>|undefined}
*/
static idbKeyPathFromKeyPath(keyPath) {
var idbKeyPath;
switch (keyPath.type) {
case Resources.IndexedDBModel.KeyPathTypes.NullType:
idbKeyPath = null;
break;
case Resources.IndexedDBModel.KeyPathTypes.StringType:
idbKeyPath = keyPath.string;
break;
case Resources.IndexedDBModel.KeyPathTypes.ArrayType:
idbKeyPath = keyPath.array;
break;
}
return idbKeyPath;
}
/**
* @param {?string|!Array.<string>|undefined} idbKeyPath
* @return {?string}
*/
static keyPathStringFromIDBKeyPath(idbKeyPath) {
if (typeof idbKeyPath === 'string')
return '"' + idbKeyPath + '"';
if (idbKeyPath instanceof Array)
return '["' + idbKeyPath.join('", "') + '"]';
return null;
}
/**
* @param {!SDK.Target} target
* @return {!Resources.IndexedDBModel}
*/
static fromTarget(target) {
var model = /** @type {?Resources.IndexedDBModel} */ (target.model(Resources.IndexedDBModel));
if (!model)
model = new Resources.IndexedDBModel(target, SDK.SecurityOriginManager.fromTarget(target));
return model;
}
enable() {
if (this._enabled)
return;
this._agent.enable();
this._securityOriginManager.addEventListener(
SDK.SecurityOriginManager.Events.SecurityOriginAdded, this._securityOriginAdded, this);
this._securityOriginManager.addEventListener(
SDK.SecurityOriginManager.Events.SecurityOriginRemoved, this._securityOriginRemoved, this);
for (var securityOrigin of this._securityOriginManager.securityOrigins())
this._addOrigin(securityOrigin);
this._enabled = true;
}
/**
* @param {string} origin
*/
clearForOrigin(origin) {
if (!this._enabled)
return;
this._removeOrigin(origin);
this._addOrigin(origin);
}
refreshDatabaseNames() {
for (var securityOrigin in this._databaseNamesBySecurityOrigin)
this._loadDatabaseNames(securityOrigin);
}
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
*/
refreshDatabase(databaseId) {
this._loadDatabase(databaseId);
}
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
* @param {string} objectStoreName
* @param {function()} callback
*/
clearObjectStore(databaseId, objectStoreName, callback) {
this._agent.clearObjectStore(databaseId.securityOrigin, databaseId.name, objectStoreName, callback);
}
/**
* @param {!Common.Event} event
*/
_securityOriginAdded(event) {
var securityOrigin = /** @type {string} */ (event.data);
this._addOrigin(securityOrigin);
}
/**
* @param {!Common.Event} event
*/
_securityOriginRemoved(event) {
var securityOrigin = /** @type {string} */ (event.data);
this._removeOrigin(securityOrigin);
}
/**
* @param {string} securityOrigin
*/
_addOrigin(securityOrigin) {
console.assert(!this._databaseNamesBySecurityOrigin[securityOrigin]);
this._databaseNamesBySecurityOrigin[securityOrigin] = [];
this._loadDatabaseNames(securityOrigin);
}
/**
* @param {string} securityOrigin
*/
_removeOrigin(securityOrigin) {
console.assert(this._databaseNamesBySecurityOrigin[securityOrigin]);
for (var i = 0; i < this._databaseNamesBySecurityOrigin[securityOrigin].length; ++i)
this._databaseRemoved(securityOrigin, this._databaseNamesBySecurityOrigin[securityOrigin][i]);
delete this._databaseNamesBySecurityOrigin[securityOrigin];
}
/**
* @param {string} securityOrigin
* @param {!Array.<string>} databaseNames
*/
_updateOriginDatabaseNames(securityOrigin, databaseNames) {
var newDatabaseNames = new Set(databaseNames);
var oldDatabaseNames = new Set(this._databaseNamesBySecurityOrigin[securityOrigin]);
this._databaseNamesBySecurityOrigin[securityOrigin] = databaseNames;
for (var databaseName of oldDatabaseNames) {
if (!newDatabaseNames.has(databaseName))
this._databaseRemoved(securityOrigin, databaseName);
}
for (var databaseName of newDatabaseNames) {
if (!oldDatabaseNames.has(databaseName))
this._databaseAdded(securityOrigin, databaseName);
}
}
/**
* @return {!Array.<!Resources.IndexedDBModel.DatabaseId>}
*/
databases() {
var result = [];
for (var securityOrigin in this._databaseNamesBySecurityOrigin) {
var databaseNames = this._databaseNamesBySecurityOrigin[securityOrigin];
for (var i = 0; i < databaseNames.length; ++i) {
result.push(new Resources.IndexedDBModel.DatabaseId(securityOrigin, databaseNames[i]));
}
}
return result;
}
/**
* @param {string} securityOrigin
* @param {string} databaseName
*/
_databaseAdded(securityOrigin, databaseName) {
var databaseId = new Resources.IndexedDBModel.DatabaseId(securityOrigin, databaseName);
this.dispatchEventToListeners(Resources.IndexedDBModel.Events.DatabaseAdded, databaseId);
}
/**
* @param {string} securityOrigin
* @param {string} databaseName
*/
_databaseRemoved(securityOrigin, databaseName) {
var databaseId = new Resources.IndexedDBModel.DatabaseId(securityOrigin, databaseName);
this.dispatchEventToListeners(Resources.IndexedDBModel.Events.DatabaseRemoved, databaseId);
}
/**
* @param {string} securityOrigin
*/
_loadDatabaseNames(securityOrigin) {
/**
* @param {?Protocol.Error} error
* @param {!Array.<string>} databaseNames
* @this {Resources.IndexedDBModel}
*/
function callback(error, databaseNames) {
if (error) {
console.error('IndexedDBAgent error: ' + error);
return;
}
if (!this._databaseNamesBySecurityOrigin[securityOrigin])
return;
this._updateOriginDatabaseNames(securityOrigin, databaseNames);
}
this._agent.requestDatabaseNames(securityOrigin, callback.bind(this));
}
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
*/
_loadDatabase(databaseId) {
/**
* @param {?Protocol.Error} error
* @param {!Protocol.IndexedDB.DatabaseWithObjectStores} databaseWithObjectStores
* @this {Resources.IndexedDBModel}
*/
function callback(error, databaseWithObjectStores) {
if (error) {
console.error('IndexedDBAgent error: ' + error);
return;
}
if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin])
return;
var databaseModel = new Resources.IndexedDBModel.Database(databaseId, databaseWithObjectStores.version);
this._databases.set(databaseId, databaseModel);
for (var i = 0; i < databaseWithObjectStores.objectStores.length; ++i) {
var objectStore = databaseWithObjectStores.objectStores[i];
var objectStoreIDBKeyPath = Resources.IndexedDBModel.idbKeyPathFromKeyPath(objectStore.keyPath);
var objectStoreModel = new Resources.IndexedDBModel.ObjectStore(
objectStore.name, objectStoreIDBKeyPath, objectStore.autoIncrement);
for (var j = 0; j < objectStore.indexes.length; ++j) {
var index = objectStore.indexes[j];
var indexIDBKeyPath = Resources.IndexedDBModel.idbKeyPathFromKeyPath(index.keyPath);
var indexModel =
new Resources.IndexedDBModel.Index(index.name, indexIDBKeyPath, index.unique, index.multiEntry);
objectStoreModel.indexes[indexModel.name] = indexModel;
}
databaseModel.objectStores[objectStoreModel.name] = objectStoreModel;
}
this.dispatchEventToListeners(Resources.IndexedDBModel.Events.DatabaseLoaded, databaseModel);
}
this._agent.requestDatabase(databaseId.securityOrigin, databaseId.name, callback.bind(this));
}
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
* @param {string} objectStoreName
* @param {?IDBKeyRange} idbKeyRange
* @param {number} skipCount
* @param {number} pageSize
* @param {function(!Array.<!Resources.IndexedDBModel.Entry>, boolean)} callback
*/
loadObjectStoreData(databaseId, objectStoreName, idbKeyRange, skipCount, pageSize, callback) {
this._requestData(databaseId, databaseId.name, objectStoreName, '', idbKeyRange, skipCount, pageSize, callback);
}
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
* @param {string} objectStoreName
* @param {string} indexName
* @param {?IDBKeyRange} idbKeyRange
* @param {number} skipCount
* @param {number} pageSize
* @param {function(!Array.<!Resources.IndexedDBModel.Entry>, boolean)} callback
*/
loadIndexData(databaseId, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback) {
this._requestData(
databaseId, databaseId.name, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback);
}
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
* @param {string} databaseName
* @param {string} objectStoreName
* @param {string} indexName
* @param {?IDBKeyRange} idbKeyRange
* @param {number} skipCount
* @param {number} pageSize
* @param {function(!Array.<!Resources.IndexedDBModel.Entry>, boolean)} callback
*/
_requestData(databaseId, databaseName, objectStoreName, indexName, idbKeyRange, skipCount, pageSize, callback) {
/**
* @param {?Protocol.Error} error
* @param {!Array.<!Protocol.IndexedDB.DataEntry>} dataEntries
* @param {boolean} hasMore
* @this {Resources.IndexedDBModel}
*/
function innerCallback(error, dataEntries, hasMore) {
if (error) {
console.error('IndexedDBAgent error: ' + error);
return;
}
if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin])
return;
var entries = [];
for (var i = 0; i < dataEntries.length; ++i) {
var key = this.target().runtimeModel.createRemoteObject(dataEntries[i].key);
var primaryKey = this.target().runtimeModel.createRemoteObject(dataEntries[i].primaryKey);
var value = this.target().runtimeModel.createRemoteObject(dataEntries[i].value);
entries.push(new Resources.IndexedDBModel.Entry(key, primaryKey, value));
}
callback(entries, hasMore);
}
var keyRange = Resources.IndexedDBModel.keyRangeFromIDBKeyRange(idbKeyRange);
this._agent.requestData(
databaseId.securityOrigin, databaseName, objectStoreName, indexName, skipCount, pageSize,
keyRange ? keyRange : undefined, innerCallback.bind(this));
}
};
Resources.IndexedDBModel.KeyTypes = {
NumberType: 'number',
StringType: 'string',
DateType: 'date',
ArrayType: 'array'
};
Resources.IndexedDBModel.KeyPathTypes = {
NullType: 'null',
StringType: 'string',
ArrayType: 'array'
};
/** @enum {symbol} */
Resources.IndexedDBModel.Events = {
DatabaseAdded: Symbol('DatabaseAdded'),
DatabaseRemoved: Symbol('DatabaseRemoved'),
DatabaseLoaded: Symbol('DatabaseLoaded')
};
/**
* @unrestricted
*/
Resources.IndexedDBModel.Entry = class {
/**
* @param {!SDK.RemoteObject} key
* @param {!SDK.RemoteObject} primaryKey
* @param {!SDK.RemoteObject} value
*/
constructor(key, primaryKey, value) {
this.key = key;
this.primaryKey = primaryKey;
this.value = value;
}
};
/**
* @unrestricted
*/
Resources.IndexedDBModel.DatabaseId = class {
/**
* @param {string} securityOrigin
* @param {string} name
*/
constructor(securityOrigin, name) {
this.securityOrigin = securityOrigin;
this.name = name;
}
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
* @return {boolean}
*/
equals(databaseId) {
return this.name === databaseId.name && this.securityOrigin === databaseId.securityOrigin;
}
};
/**
* @unrestricted
*/
Resources.IndexedDBModel.Database = class {
/**
* @param {!Resources.IndexedDBModel.DatabaseId} databaseId
* @param {number} version
*/
constructor(databaseId, version) {
this.databaseId = databaseId;
this.version = version;
this.objectStores = {};
}
};
/**
* @unrestricted
*/
Resources.IndexedDBModel.ObjectStore = class {
/**
* @param {string} name
* @param {*} keyPath
* @param {boolean} autoIncrement
*/
constructor(name, keyPath, autoIncrement) {
this.name = name;
this.keyPath = keyPath;
this.autoIncrement = autoIncrement;
this.indexes = {};
}
/**
* @return {string}
*/
get keyPathString() {
return /** @type {string}*/ (
Resources.IndexedDBModel.keyPathStringFromIDBKeyPath(/** @type {string}*/ (this.keyPath)));
}
};
/**
* @unrestricted
*/
Resources.IndexedDBModel.Index = class {
/**
* @param {string} name
* @param {*} keyPath
* @param {boolean} unique
* @param {boolean} multiEntry
*/
constructor(name, keyPath, unique, multiEntry) {
this.name = name;
this.keyPath = keyPath;
this.unique = unique;
this.multiEntry = multiEntry;
}
/**
* @return {string}
*/
get keyPathString() {
return /** @type {string}*/ (
Resources.IndexedDBModel.keyPathStringFromIDBKeyPath(/** @type {string}*/ (this.keyPath)));
}
};