Update wmts-hidpi, add nicer-api-docs
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a convenient API for data persistence with data
|
||||
* expiration and user-initiated expired key collection.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.CollectableStorage');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.storage.ErrorCode');
|
||||
goog.require('goog.storage.ExpiringStorage');
|
||||
goog.require('goog.storage.RichStorage.Wrapper');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a storage with expirning keys and a collection method.
|
||||
*
|
||||
* @param {!goog.storage.mechanism.IterableMechanism} mechanism The underlying
|
||||
* storage mechanism.
|
||||
* @constructor
|
||||
* @extends {goog.storage.ExpiringStorage}
|
||||
*/
|
||||
goog.storage.CollectableStorage = function(mechanism) {
|
||||
goog.base(this, mechanism);
|
||||
};
|
||||
goog.inherits(goog.storage.CollectableStorage, goog.storage.ExpiringStorage);
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up the storage by removing expired keys.
|
||||
*
|
||||
* @param {boolean=} opt_strict Also remove invalid keys.
|
||||
*/
|
||||
goog.storage.CollectableStorage.prototype.collect = function(opt_strict) {
|
||||
var selfObj = this;
|
||||
var keysToRemove = [];
|
||||
goog.iter.forEach(this.mechanism.__iterator__(true), function(key) {
|
||||
// Get the wrapper.
|
||||
var wrapper;
|
||||
/** @preserveTry */
|
||||
try {
|
||||
wrapper = goog.storage.CollectableStorage.prototype.getWrapper.call(
|
||||
selfObj, key, true);
|
||||
} catch (ex) {
|
||||
if (ex == goog.storage.ErrorCode.INVALID_VALUE) {
|
||||
// Bad wrappers are removed in strict mode.
|
||||
if (opt_strict) {
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
// Skip over bad wrappers and continue.
|
||||
return;
|
||||
}
|
||||
// Unknown error, escalate.
|
||||
throw ex;
|
||||
}
|
||||
goog.asserts.assert(wrapper);
|
||||
// Remove expired objects.
|
||||
if (goog.storage.ExpiringStorage.isExpired(wrapper)) {
|
||||
keysToRemove.push(key);
|
||||
// Continue with the next key.
|
||||
return;
|
||||
}
|
||||
// Objects which can't be decoded are removed in strict mode.
|
||||
if (opt_strict) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
goog.storage.RichStorage.Wrapper.unwrap(wrapper);
|
||||
} catch (ex) {
|
||||
if (ex == goog.storage.ErrorCode.INVALID_VALUE) {
|
||||
keysToRemove.push(key);
|
||||
// Skip over bad wrappers and continue.
|
||||
return;
|
||||
}
|
||||
// Unknown error, escalate.
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
});
|
||||
goog.array.forEach(keysToRemove, function(key) {
|
||||
goog.storage.CollectableStorage.prototype.remove.call(selfObj, key);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a convenient API for data persistence with key and
|
||||
* object encryption. Without a valid secret, the existence of a particular
|
||||
* key can't be verified and values can't be decrypted. The value encryption
|
||||
* is salted, so subsequent writes of the same cleartext result in different
|
||||
* ciphertext. The ciphertext is *not* authenticated, so there is no protection
|
||||
* against data manipulation.
|
||||
*
|
||||
* The metadata is *not* encrypted, so expired keys can be cleaned up without
|
||||
* decrypting them. If sensitive metadata is added in subclasses, it is up
|
||||
* to the subclass to protect this information, perhaps by embedding it in
|
||||
* the object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.EncryptedStorage');
|
||||
|
||||
goog.require('goog.crypt');
|
||||
goog.require('goog.crypt.Arc4');
|
||||
goog.require('goog.crypt.Sha1');
|
||||
goog.require('goog.crypt.base64');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.json.Serializer');
|
||||
goog.require('goog.storage.CollectableStorage');
|
||||
goog.require('goog.storage.ErrorCode');
|
||||
goog.require('goog.storage.RichStorage');
|
||||
goog.require('goog.storage.RichStorage.Wrapper');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides an encrypted storage. The keys are hashed with a secret, so
|
||||
* their existence cannot be verified without the knowledge of the secret.
|
||||
* The values are encrypted using the key, a salt, and the secret, so
|
||||
* stream cipher initialization varies for each stored value.
|
||||
*
|
||||
* @param {!goog.storage.mechanism.IterableMechanism} mechanism The underlying
|
||||
* storage mechanism.
|
||||
* @param {string} secret The secret key used to encrypt the storage.
|
||||
* @constructor
|
||||
* @extends {goog.storage.CollectableStorage}
|
||||
*/
|
||||
goog.storage.EncryptedStorage = function(mechanism, secret) {
|
||||
goog.base(this, mechanism);
|
||||
this.secret_ = goog.crypt.stringToByteArray(secret);
|
||||
this.cleartextSerializer_ = new goog.json.Serializer();
|
||||
};
|
||||
goog.inherits(goog.storage.EncryptedStorage, goog.storage.CollectableStorage);
|
||||
|
||||
|
||||
/**
|
||||
* Metadata key under which the salt is stored.
|
||||
*
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
goog.storage.EncryptedStorage.SALT_KEY = 'salt';
|
||||
|
||||
|
||||
/**
|
||||
* The secret used to encrypt the storage.
|
||||
*
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.EncryptedStorage.prototype.secret_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The JSON serializer used to serialize values before encryption. This can
|
||||
* be potentially different from serializing for the storage mechanism (see
|
||||
* goog.storage.Storage), so a separate serializer is kept here.
|
||||
*
|
||||
* @type {goog.json.Serializer}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.EncryptedStorage.prototype.cleartextSerializer_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Hashes a key using the secret.
|
||||
*
|
||||
* @param {string} key The key.
|
||||
* @return {string} The hash.
|
||||
* @private
|
||||
*/
|
||||
goog.storage.EncryptedStorage.prototype.hashKeyWithSecret_ = function(key) {
|
||||
var sha1 = new goog.crypt.Sha1();
|
||||
sha1.update(goog.crypt.stringToByteArray(key));
|
||||
sha1.update(this.secret_);
|
||||
return goog.crypt.base64.encodeByteArray(sha1.digest(), true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Encrypts a value using a key, a salt, and the secret.
|
||||
*
|
||||
* @param {!Array.<number>} salt The salt.
|
||||
* @param {string} key The key.
|
||||
* @param {string} value The cleartext value.
|
||||
* @return {string} The encrypted value.
|
||||
* @private
|
||||
*/
|
||||
goog.storage.EncryptedStorage.prototype.encryptValue_ = function(
|
||||
salt, key, value) {
|
||||
if (!(salt.length > 0)) {
|
||||
throw Error('Non-empty salt must be provided');
|
||||
}
|
||||
var sha1 = new goog.crypt.Sha1();
|
||||
sha1.update(goog.crypt.stringToByteArray(key));
|
||||
sha1.update(salt);
|
||||
sha1.update(this.secret_);
|
||||
var arc4 = new goog.crypt.Arc4();
|
||||
arc4.setKey(sha1.digest());
|
||||
// Warm up the streamcypher state, see goog.crypt.Arc4 for details.
|
||||
arc4.discard(1536);
|
||||
var bytes = goog.crypt.stringToByteArray(value);
|
||||
arc4.crypt(bytes);
|
||||
return goog.crypt.byteArrayToString(bytes);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Decrypts a value using a key, a salt, and the secret.
|
||||
*
|
||||
* @param {!Array.<number>} salt The salt.
|
||||
* @param {string} key The key.
|
||||
* @param {string} value The encrypted value.
|
||||
* @return {string} The decrypted value.
|
||||
* @private
|
||||
*/
|
||||
goog.storage.EncryptedStorage.prototype.decryptValue_ = function(
|
||||
salt, key, value) {
|
||||
// ARC4 is symmetric.
|
||||
return this.encryptValue_(salt, key, value);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.EncryptedStorage.prototype.set = function(
|
||||
key, value, opt_expiration) {
|
||||
if (!goog.isDef(value)) {
|
||||
goog.storage.EncryptedStorage.prototype.remove.call(this, key);
|
||||
return;
|
||||
}
|
||||
var salt = [];
|
||||
// 64-bit random salt.
|
||||
for (var i = 0; i < 8; ++i) {
|
||||
salt[i] = Math.floor(Math.random() * 0x100);
|
||||
}
|
||||
var wrapper = new goog.storage.RichStorage.Wrapper(
|
||||
this.encryptValue_(salt, key,
|
||||
this.cleartextSerializer_.serialize(value)));
|
||||
wrapper[goog.storage.EncryptedStorage.SALT_KEY] = salt;
|
||||
goog.base(this, 'set', this.hashKeyWithSecret_(key), wrapper, opt_expiration);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.EncryptedStorage.prototype.getWrapper = function(
|
||||
key, opt_expired) {
|
||||
var wrapper = goog.base(this, 'getWrapper',
|
||||
this.hashKeyWithSecret_(key), opt_expired);
|
||||
if (!wrapper) {
|
||||
return undefined;
|
||||
}
|
||||
var value = goog.storage.RichStorage.Wrapper.unwrap(wrapper);
|
||||
var salt = wrapper[goog.storage.EncryptedStorage.SALT_KEY];
|
||||
if (!goog.isString(value) || !goog.isArray(salt) || !salt.length) {
|
||||
throw goog.storage.ErrorCode.INVALID_VALUE;
|
||||
}
|
||||
var json = this.decryptValue_(salt, key, value);
|
||||
/** @preserveTry */
|
||||
try {
|
||||
wrapper[goog.storage.RichStorage.DATA_KEY] = goog.json.parse(json);
|
||||
} catch (e) {
|
||||
throw goog.storage.ErrorCode.DECRYPTION_ERROR;
|
||||
}
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.EncryptedStorage.prototype.remove = function(key) {
|
||||
goog.base(this, 'remove', this.hashKeyWithSecret_(key));
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Defines errors to be thrown by the storage.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.ErrorCode');
|
||||
|
||||
|
||||
/**
|
||||
* Errors thrown by the storage.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.storage.ErrorCode = {
|
||||
INVALID_VALUE: 'Storage: Invalid value was encountered',
|
||||
DECRYPTION_ERROR: 'Storage: The value could not be decrypted'
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a convenient API for data persistence with expiration.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.ExpiringStorage');
|
||||
|
||||
goog.require('goog.storage.RichStorage');
|
||||
goog.require('goog.storage.RichStorage.Wrapper');
|
||||
goog.require('goog.storage.mechanism.Mechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a storage with expirning keys.
|
||||
*
|
||||
* @param {!goog.storage.mechanism.Mechanism} mechanism The underlying
|
||||
* storage mechanism.
|
||||
* @constructor
|
||||
* @extends {goog.storage.RichStorage}
|
||||
*/
|
||||
goog.storage.ExpiringStorage = function(mechanism) {
|
||||
goog.base(this, mechanism);
|
||||
};
|
||||
goog.inherits(goog.storage.ExpiringStorage, goog.storage.RichStorage);
|
||||
|
||||
|
||||
/**
|
||||
* Metadata key under which the expiration time is stored.
|
||||
*
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
goog.storage.ExpiringStorage.EXPIRATION_TIME_KEY = 'expiration';
|
||||
|
||||
|
||||
/**
|
||||
* Metadata key under which the creation time is stored.
|
||||
*
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
goog.storage.ExpiringStorage.CREATION_TIME_KEY = 'creation';
|
||||
|
||||
|
||||
/**
|
||||
* Returns the wrapper creation time.
|
||||
*
|
||||
* @param {!Object} wrapper The wrapper.
|
||||
* @return {number|undefined} Wrapper creation time.
|
||||
*/
|
||||
goog.storage.ExpiringStorage.getCreationTime = function(wrapper) {
|
||||
return wrapper[goog.storage.ExpiringStorage.CREATION_TIME_KEY];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the wrapper expiration time.
|
||||
*
|
||||
* @param {!Object} wrapper The wrapper.
|
||||
* @return {number|undefined} Wrapper expiration time.
|
||||
*/
|
||||
goog.storage.ExpiringStorage.getExpirationTime = function(wrapper) {
|
||||
return wrapper[goog.storage.ExpiringStorage.EXPIRATION_TIME_KEY];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the data item has expired.
|
||||
*
|
||||
* @param {!Object} wrapper The wrapper.
|
||||
* @return {boolean} True if the item has expired.
|
||||
*/
|
||||
goog.storage.ExpiringStorage.isExpired = function(wrapper) {
|
||||
var creation = goog.storage.ExpiringStorage.getCreationTime(wrapper);
|
||||
var expiration = goog.storage.ExpiringStorage.getExpirationTime(wrapper);
|
||||
return !!expiration && expiration < goog.now() ||
|
||||
!!creation && creation > goog.now();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set an item in the storage.
|
||||
*
|
||||
* @param {string} key The key to set.
|
||||
* @param {*} value The value to serialize to a string and save.
|
||||
* @param {number=} opt_expiration The number of miliseconds since epoch
|
||||
* (as in goog.now()) when the value is to expire. If the expiration
|
||||
* time is not provided, the value will persist as long as possible.
|
||||
* @override
|
||||
*/
|
||||
goog.storage.ExpiringStorage.prototype.set = function(
|
||||
key, value, opt_expiration) {
|
||||
var wrapper = goog.storage.RichStorage.Wrapper.wrapIfNecessary(value);
|
||||
if (wrapper) {
|
||||
if (opt_expiration) {
|
||||
if (opt_expiration < goog.now()) {
|
||||
goog.storage.ExpiringStorage.prototype.remove.call(this, key);
|
||||
return;
|
||||
}
|
||||
wrapper[goog.storage.ExpiringStorage.EXPIRATION_TIME_KEY] =
|
||||
opt_expiration;
|
||||
}
|
||||
wrapper[goog.storage.ExpiringStorage.CREATION_TIME_KEY] = goog.now();
|
||||
}
|
||||
goog.base(this, 'set', key, wrapper);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get an item wrapper (the item and its metadata) from the storage.
|
||||
*
|
||||
* @param {string} key The key to get.
|
||||
* @param {boolean=} opt_expired If true, return expired wrappers as well.
|
||||
* @return {(!Object|undefined)} The wrapper, or undefined if not found.
|
||||
* @override
|
||||
*/
|
||||
goog.storage.ExpiringStorage.prototype.getWrapper = function(key, opt_expired) {
|
||||
var wrapper = goog.base(this, 'getWrapper', key);
|
||||
if (!wrapper) {
|
||||
return undefined;
|
||||
}
|
||||
if (!opt_expired && goog.storage.ExpiringStorage.isExpired(wrapper)) {
|
||||
goog.storage.ExpiringStorage.prototype.remove.call(this, key);
|
||||
return undefined;
|
||||
}
|
||||
return wrapper;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Defines error codes to be thrown by storage mechanisms.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.ErrorCode');
|
||||
|
||||
|
||||
/**
|
||||
* Errors thrown by storage mechanisms.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.storage.mechanism.ErrorCode = {
|
||||
INVALID_VALUE: 'Storage mechanism: Invalid value was encountered',
|
||||
QUOTA_EXCEEDED: 'Storage mechanism: Quota exceeded',
|
||||
STORAGE_DISABLED: 'Storage mechanism: Storage disabled'
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Wraps a storage mechanism with a custom error handler.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.ErrorHandlingMechanism');
|
||||
|
||||
goog.require('goog.storage.mechanism.Mechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a storage mechanism with a custom error handler.
|
||||
*
|
||||
* @param {!goog.storage.mechanism.Mechanism} mechanism Underlying storage
|
||||
* mechanism.
|
||||
* @param {goog.storage.mechanism.ErrorHandlingMechanism.ErrorHandler}
|
||||
* errorHandler An error handler.
|
||||
* @constructor
|
||||
* @extends {goog.storage.mechanism.Mechanism}
|
||||
*/
|
||||
goog.storage.mechanism.ErrorHandlingMechanism = function(mechanism,
|
||||
errorHandler) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* The mechanism to be wrapped.
|
||||
* @type {!goog.storage.mechanism.Mechanism}
|
||||
* @private
|
||||
*/
|
||||
this.mechanism_ = mechanism;
|
||||
|
||||
/**
|
||||
* The error handler.
|
||||
* @type {goog.storage.mechanism.ErrorHandlingMechanism.ErrorHandler}
|
||||
* @private
|
||||
*/
|
||||
this.errorHandler_ = errorHandler;
|
||||
};
|
||||
goog.inherits(goog.storage.mechanism.ErrorHandlingMechanism,
|
||||
goog.storage.mechanism.Mechanism);
|
||||
|
||||
|
||||
/**
|
||||
* Valid storage mechanism operations.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.storage.mechanism.ErrorHandlingMechanism.Operation = {
|
||||
SET: 'set',
|
||||
GET: 'get',
|
||||
REMOVE: 'remove'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A function that handles errors raised in goog.storage. Since some places in
|
||||
* the goog.storage codebase throw strings instead of Error objects, we accept
|
||||
* these as a valid parameter type. It supports the following arguments:
|
||||
*
|
||||
* 1) The raised error (either in Error or string form);
|
||||
* 2) The operation name which triggered the error, as defined per the
|
||||
* ErrorHandlingMechanism.Operation enum;
|
||||
* 3) The key that is passed to a storage method;
|
||||
* 4) An optional value that is passed to a storage method (only used in set
|
||||
* operations).
|
||||
*
|
||||
* @typedef {function(
|
||||
* (!Error|string),
|
||||
* goog.storage.mechanism.ErrorHandlingMechanism.Operation,
|
||||
* string,
|
||||
* *=)}
|
||||
*/
|
||||
goog.storage.mechanism.ErrorHandlingMechanism.ErrorHandler;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.ErrorHandlingMechanism.prototype.set = function(key,
|
||||
value) {
|
||||
try {
|
||||
this.mechanism_.set(key, value);
|
||||
} catch (e) {
|
||||
this.errorHandler_(
|
||||
e,
|
||||
goog.storage.mechanism.ErrorHandlingMechanism.Operation.SET,
|
||||
key,
|
||||
value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.ErrorHandlingMechanism.prototype.get = function(key) {
|
||||
try {
|
||||
return this.mechanism_.get(key);
|
||||
} catch (e) {
|
||||
this.errorHandler_(
|
||||
e,
|
||||
goog.storage.mechanism.ErrorHandlingMechanism.Operation.GET,
|
||||
key);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.ErrorHandlingMechanism.prototype.remove = function(key) {
|
||||
try {
|
||||
this.mechanism_.remove(key);
|
||||
} catch (e) {
|
||||
this.errorHandler_(
|
||||
e,
|
||||
goog.storage.mechanism.ErrorHandlingMechanism.Operation.REMOVE,
|
||||
key);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides data persistence using HTML5 local storage
|
||||
* mechanism. Local storage must be available under window.localStorage,
|
||||
* see: http://www.w3.org/TR/webstorage/#the-localstorage-attribute.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.HTML5LocalStorage');
|
||||
|
||||
goog.require('goog.storage.mechanism.HTML5WebStorage');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a storage mechanism that uses HTML5 local storage.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.storage.mechanism.HTML5WebStorage}
|
||||
*/
|
||||
goog.storage.mechanism.HTML5LocalStorage = function() {
|
||||
var storage = null;
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// May throw an exception in cases where the local storage object
|
||||
// is visible but access to it is disabled.
|
||||
storage = window.localStorage || null;
|
||||
} catch (e) {}
|
||||
goog.base(this, storage);
|
||||
};
|
||||
goog.inherits(goog.storage.mechanism.HTML5LocalStorage,
|
||||
goog.storage.mechanism.HTML5WebStorage);
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides data persistence using HTML5 session storage
|
||||
* mechanism. Session storage must be available under window.sessionStorage,
|
||||
* see: http://www.w3.org/TR/webstorage/#the-sessionstorage-attribute.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.HTML5SessionStorage');
|
||||
|
||||
goog.require('goog.storage.mechanism.HTML5WebStorage');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a storage mechanism that uses HTML5 session storage.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.storage.mechanism.HTML5WebStorage}
|
||||
*/
|
||||
goog.storage.mechanism.HTML5SessionStorage = function() {
|
||||
var storage = null;
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// May throw an exception in cases where the session storage object is
|
||||
// visible but access to it is disabled. For example, accessing the file
|
||||
// in local mode in Firefox throws 'Operation is not supported' exception.
|
||||
storage = window.sessionStorage || null;
|
||||
} catch (e) {}
|
||||
goog.base(this, storage);
|
||||
};
|
||||
goog.inherits(goog.storage.mechanism.HTML5SessionStorage,
|
||||
goog.storage.mechanism.HTML5WebStorage);
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Base class that implements functionality common
|
||||
* across both session and local web storage mechanisms.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.HTML5WebStorage');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.iter.StopIteration');
|
||||
goog.require('goog.storage.mechanism.ErrorCode');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a storage mechanism that uses HTML5 Web storage.
|
||||
*
|
||||
* @param {Storage} storage The Web storage object.
|
||||
* @constructor
|
||||
* @extends {goog.storage.mechanism.IterableMechanism}
|
||||
*/
|
||||
goog.storage.mechanism.HTML5WebStorage = function(storage) {
|
||||
goog.base(this);
|
||||
this.storage_ = storage;
|
||||
};
|
||||
goog.inherits(goog.storage.mechanism.HTML5WebStorage,
|
||||
goog.storage.mechanism.IterableMechanism);
|
||||
|
||||
|
||||
/**
|
||||
* The key used to check if the storage instance is available.
|
||||
* @type {string}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.HTML5WebStorage.STORAGE_AVAILABLE_KEY_ = '__sak';
|
||||
|
||||
|
||||
/**
|
||||
* The web storage object (window.localStorage or window.sessionStorage).
|
||||
*
|
||||
* @type {Storage}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.storage_;
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not the mechanism is available.
|
||||
* It works only if the provided web storage object exists and is enabled.
|
||||
*
|
||||
* @return {boolean} True if the mechanism is available.
|
||||
*/
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.isAvailable = function() {
|
||||
if (!this.storage_) {
|
||||
return false;
|
||||
}
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// setItem will throw an exception if we cannot access WebStorage (e.g.,
|
||||
// Safari in private mode).
|
||||
this.storage_.setItem(
|
||||
goog.storage.mechanism.HTML5WebStorage.STORAGE_AVAILABLE_KEY_, '1');
|
||||
this.storage_.removeItem(
|
||||
goog.storage.mechanism.HTML5WebStorage.STORAGE_AVAILABLE_KEY_);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.set = function(key, value) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// May throw an exception if storage quota is exceeded.
|
||||
this.storage_.setItem(key, value);
|
||||
} catch (e) {
|
||||
// In Safari Private mode, conforming to the W3C spec, invoking
|
||||
// Storage.prototype.setItem will allways throw a QUOTA_EXCEEDED_ERR
|
||||
// exception. Since it's impossible to verify if we're in private browsing
|
||||
// mode, we throw a different exception if the storage is empty.
|
||||
if (this.storage_.length == 0) {
|
||||
throw goog.storage.mechanism.ErrorCode.STORAGE_DISABLED;
|
||||
} else {
|
||||
throw goog.storage.mechanism.ErrorCode.QUOTA_EXCEEDED;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.get = function(key) {
|
||||
// According to W3C specs, values can be of any type. Since we only save
|
||||
// strings, any other type is a storage error. If we returned nulls for
|
||||
// such keys, i.e., treated them as non-existent, this would lead to a
|
||||
// paradox where a key exists, but it does not when it is retrieved.
|
||||
// http://www.w3.org/TR/2009/WD-webstorage-20091029/#the-storage-interface
|
||||
var value = this.storage_.getItem(key);
|
||||
if (!goog.isString(value) && !goog.isNull(value)) {
|
||||
throw goog.storage.mechanism.ErrorCode.INVALID_VALUE;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.remove = function(key) {
|
||||
this.storage_.removeItem(key);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.getCount = function() {
|
||||
return this.storage_.length;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.__iterator__ = function(
|
||||
opt_keys) {
|
||||
var i = 0;
|
||||
var storage = this.storage_;
|
||||
var newIter = new goog.iter.Iterator();
|
||||
newIter.next = function() {
|
||||
if (i >= storage.length) {
|
||||
throw goog.iter.StopIteration;
|
||||
}
|
||||
var key = goog.asserts.assertString(storage.key(i++));
|
||||
if (opt_keys) {
|
||||
return key;
|
||||
}
|
||||
var value = storage.getItem(key);
|
||||
// The value must exist and be a string, otherwise it is a storage error.
|
||||
if (!goog.isString(value)) {
|
||||
throw goog.storage.mechanism.ErrorCode.INVALID_VALUE;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
return newIter;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.clear = function() {
|
||||
this.storage_.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the key for a given key index. If an index outside of
|
||||
* [0..this.getCount()) is specified, this function returns null.
|
||||
* @param {number} index A key index.
|
||||
* @return {?string} A storage key, or null if the specified index is out of
|
||||
* range.
|
||||
*/
|
||||
goog.storage.mechanism.HTML5WebStorage.prototype.key = function(index) {
|
||||
return this.storage_.key(index);
|
||||
};
|
||||
@@ -0,0 +1,283 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides data persistence using IE userData mechanism.
|
||||
* UserData uses proprietary Element.addBehavior(), Element.load(),
|
||||
* Element.save(), and Element.XMLDocument() methods, see:
|
||||
* http://msdn.microsoft.com/en-us/library/ms531424(v=vs.85).aspx.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.IEUserData');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.iter.StopIteration');
|
||||
goog.require('goog.storage.mechanism.ErrorCode');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
goog.require('goog.structs.Map');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a storage mechanism using IE userData.
|
||||
*
|
||||
* @param {string} storageKey The key (store name) to store the data under.
|
||||
* @param {string=} opt_storageNodeId The ID of the associated HTML element,
|
||||
* one will be created if not provided.
|
||||
* @constructor
|
||||
* @extends {goog.storage.mechanism.IterableMechanism}
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData = function(storageKey, opt_storageNodeId) {
|
||||
goog.base(this);
|
||||
|
||||
// Tested on IE6, IE7 and IE8. It seems that IE9 introduces some security
|
||||
// features which make persistent (loaded) node attributes invisible from
|
||||
// JavaScript.
|
||||
if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
|
||||
if (!goog.storage.mechanism.IEUserData.storageMap_) {
|
||||
goog.storage.mechanism.IEUserData.storageMap_ = new goog.structs.Map();
|
||||
}
|
||||
this.storageNode_ = /** @type {Element} */ (
|
||||
goog.storage.mechanism.IEUserData.storageMap_.get(storageKey));
|
||||
if (!this.storageNode_) {
|
||||
if (opt_storageNodeId) {
|
||||
this.storageNode_ = document.getElementById(opt_storageNodeId);
|
||||
} else {
|
||||
this.storageNode_ = document.createElement('userdata');
|
||||
// This is a special IE-only method letting us persist data.
|
||||
this.storageNode_['addBehavior']('#default#userData');
|
||||
document.body.appendChild(this.storageNode_);
|
||||
}
|
||||
goog.storage.mechanism.IEUserData.storageMap_.set(
|
||||
storageKey, this.storageNode_);
|
||||
}
|
||||
this.storageKey_ = storageKey;
|
||||
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// Availability check.
|
||||
this.loadNode_();
|
||||
} catch (e) {
|
||||
this.storageNode_ = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.storage.mechanism.IEUserData,
|
||||
goog.storage.mechanism.IterableMechanism);
|
||||
|
||||
|
||||
/**
|
||||
* Encoding map for characters which are not encoded by encodeURIComponent().
|
||||
* See encodeKey_ documentation for encoding details.
|
||||
*
|
||||
* @type {!Object}
|
||||
* @const
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.ENCODE_MAP = {
|
||||
'.': '.2E',
|
||||
'!': '.21',
|
||||
'~': '.7E',
|
||||
'*': '.2A',
|
||||
'\'': '.27',
|
||||
'(': '.28',
|
||||
')': '.29',
|
||||
'%': '.'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Global storageKey to storageNode map, so we save on reloading the storage.
|
||||
*
|
||||
* @type {goog.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.storageMap_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The document element used for storing data.
|
||||
*
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.prototype.storageNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The key to store the data under.
|
||||
*
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.prototype.storageKey_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Encodes anything other than [-a-zA-Z0-9_] using a dot followed by hex,
|
||||
* and prefixes with underscore to form a valid and safe HTML attribute name.
|
||||
*
|
||||
* We use URI encoding to do the initial heavy lifting, then escape the
|
||||
* remaining characters that we can't use. Since a valid attribute name can't
|
||||
* contain the percent sign (%), we use a dot (.) as an escape character.
|
||||
*
|
||||
* @param {string} key The key to be encoded.
|
||||
* @return {string} The encoded key.
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.encodeKey_ = function(key) {
|
||||
// encodeURIComponent leaves - _ . ! ~ * ' ( ) unencoded.
|
||||
return '_' + encodeURIComponent(key).replace(/[.!~*'()%]/g, function(c) {
|
||||
return goog.storage.mechanism.IEUserData.ENCODE_MAP[c];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a dot-encoded and character-prefixed key.
|
||||
* See encodeKey_ documentation for encoding details.
|
||||
*
|
||||
* @param {string} key The key to be decoded.
|
||||
* @return {string} The decoded key.
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.decodeKey_ = function(key) {
|
||||
return decodeURIComponent(key.replace(/\./g, '%')).substr(1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not the mechanism is available.
|
||||
*
|
||||
* @return {boolean} True if the mechanism is available.
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.prototype.isAvailable = function() {
|
||||
return !!this.storageNode_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.IEUserData.prototype.set = function(key, value) {
|
||||
this.storageNode_.setAttribute(
|
||||
goog.storage.mechanism.IEUserData.encodeKey_(key), value);
|
||||
this.saveNode_();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.IEUserData.prototype.get = function(key) {
|
||||
// According to Microsoft, values can be strings, numbers or booleans. Since
|
||||
// we only save strings, any other type is a storage error. If we returned
|
||||
// nulls for such keys, i.e., treated them as non-existent, this would lead
|
||||
// to a paradox where a key exists, but it does not when it is retrieved.
|
||||
// http://msdn.microsoft.com/en-us/library/ms531348(v=vs.85).aspx
|
||||
var value = this.storageNode_.getAttribute(
|
||||
goog.storage.mechanism.IEUserData.encodeKey_(key));
|
||||
if (!goog.isString(value) && !goog.isNull(value)) {
|
||||
throw goog.storage.mechanism.ErrorCode.INVALID_VALUE;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.IEUserData.prototype.remove = function(key) {
|
||||
this.storageNode_.removeAttribute(
|
||||
goog.storage.mechanism.IEUserData.encodeKey_(key));
|
||||
this.saveNode_();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.IEUserData.prototype.getCount = function() {
|
||||
return this.getNode_().attributes.length;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.IEUserData.prototype.__iterator__ = function(opt_keys) {
|
||||
var i = 0;
|
||||
var attributes = this.getNode_().attributes;
|
||||
var newIter = new goog.iter.Iterator();
|
||||
newIter.next = function() {
|
||||
if (i >= attributes.length) {
|
||||
throw goog.iter.StopIteration;
|
||||
}
|
||||
var item = goog.asserts.assert(attributes[i++]);
|
||||
if (opt_keys) {
|
||||
return goog.storage.mechanism.IEUserData.decodeKey_(item.nodeName);
|
||||
}
|
||||
var value = item.nodeValue;
|
||||
// The value must exist and be a string, otherwise it is a storage error.
|
||||
if (!goog.isString(value)) {
|
||||
throw goog.storage.mechanism.ErrorCode.INVALID_VALUE;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
return newIter;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.IEUserData.prototype.clear = function() {
|
||||
var node = this.getNode_();
|
||||
for (var left = node.attributes.length; left > 0; left--) {
|
||||
node.removeAttribute(node.attributes[left - 1].nodeName);
|
||||
}
|
||||
this.saveNode_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads the underlying storage node to the state we saved it to before.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.prototype.loadNode_ = function() {
|
||||
// This is a special IE-only method on Elements letting us persist data.
|
||||
this.storageNode_['load'](this.storageKey_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Saves the underlying storage node.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.prototype.saveNode_ = function() {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// This is a special IE-only method on Elements letting us persist data.
|
||||
// Do not try to assign this.storageNode_['save'] to a variable, it does
|
||||
// not work. May throw an exception when the quota is exceeded.
|
||||
this.storageNode_['save'](this.storageKey_);
|
||||
} catch (e) {
|
||||
throw goog.storage.mechanism.ErrorCode.QUOTA_EXCEEDED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the storage node.
|
||||
*
|
||||
* @return {Element} Storage DOM Element.
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.IEUserData.prototype.getNode_ = function() {
|
||||
// This is a special IE-only property letting us browse persistent data.
|
||||
var doc = /** @type {Document} */ (this.storageNode_['XMLDocument']);
|
||||
return doc.documentElement;
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Interface for storing, retieving and scanning data using some
|
||||
* persistence mechanism.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.IterableMechanism');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.storage.mechanism.Mechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Interface for all iterable storage mechanisms.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.storage.mechanism.Mechanism}
|
||||
*/
|
||||
goog.storage.mechanism.IterableMechanism = function() {
|
||||
goog.base(this);
|
||||
};
|
||||
goog.inherits(goog.storage.mechanism.IterableMechanism,
|
||||
goog.storage.mechanism.Mechanism);
|
||||
|
||||
|
||||
/**
|
||||
* Get the number of stored key-value pairs.
|
||||
*
|
||||
* Could be overridden in a subclass, as the default implementation is not very
|
||||
* efficient - it iterates over all keys.
|
||||
*
|
||||
* @return {number} Number of stored elements.
|
||||
*/
|
||||
goog.storage.mechanism.IterableMechanism.prototype.getCount = function() {
|
||||
var count = 0;
|
||||
goog.iter.forEach(this.__iterator__(true), function(key) {
|
||||
goog.asserts.assertString(key);
|
||||
count++;
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the elements in the storage. Will
|
||||
* throw goog.iter.StopIteration after the last element.
|
||||
*
|
||||
* @param {boolean=} opt_keys True to iterate over the keys. False to iterate
|
||||
* over the values. The default value is false.
|
||||
* @return {!goog.iter.Iterator} The iterator.
|
||||
*/
|
||||
goog.storage.mechanism.IterableMechanism.prototype.__iterator__ =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Remove all key-value pairs.
|
||||
*
|
||||
* Could be overridden in a subclass, as the default implementation is not very
|
||||
* efficient - it iterates over all keys.
|
||||
*/
|
||||
goog.storage.mechanism.IterableMechanism.prototype.clear = function() {
|
||||
var keys = goog.iter.toArray(this.__iterator__(true));
|
||||
var selfObj = this;
|
||||
goog.array.forEach(keys, function(key) {
|
||||
selfObj.remove(key);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Unit tests for the iterable storage mechanism interface.
|
||||
*
|
||||
* These tests should be included in tests of any class extending
|
||||
* goog.storage.mechanism.IterableMechanism.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.iterableMechanismTester');
|
||||
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.setTestOnly('iterableMechanismTester');
|
||||
|
||||
|
||||
var mechanism = null;
|
||||
|
||||
|
||||
function testCount() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
assertEquals(0, mechanism.getCount());
|
||||
mechanism.set('first', 'one');
|
||||
assertEquals(1, mechanism.getCount());
|
||||
mechanism.set('second', 'two');
|
||||
assertEquals(2, mechanism.getCount());
|
||||
mechanism.set('first', 'three');
|
||||
assertEquals(2, mechanism.getCount());
|
||||
}
|
||||
|
||||
|
||||
function testIteratorBasics() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
assertEquals('first', mechanism.__iterator__(true).next());
|
||||
assertEquals('one', mechanism.__iterator__(false).next());
|
||||
var iterator = mechanism.__iterator__();
|
||||
assertEquals('one', iterator.next());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(iterator.next));
|
||||
}
|
||||
|
||||
|
||||
function testIteratorWithTwoValues() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism.set('second', 'two');
|
||||
assertSameElements(['one', 'two'], goog.iter.toArray(mechanism));
|
||||
assertSameElements(['first', 'second'],
|
||||
goog.iter.toArray(mechanism.__iterator__(true)));
|
||||
}
|
||||
|
||||
|
||||
function testClear() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism.set('second', 'two');
|
||||
mechanism.clear();
|
||||
assertNull(mechanism.get('first'));
|
||||
assertNull(mechanism.get('second'));
|
||||
assertEquals(0, mechanism.getCount());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(mechanism.__iterator__(true).next));
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(mechanism.__iterator__(false).next));
|
||||
}
|
||||
|
||||
|
||||
function testClearClear() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.clear();
|
||||
mechanism.clear();
|
||||
assertEquals(0, mechanism.getCount());
|
||||
}
|
||||
|
||||
|
||||
function testIteratorWithWeirdKeys() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set(' ', 'space');
|
||||
mechanism.set('=+!@#$%^&*()-_\\|;:\'",./<>?[]{}~`', 'control');
|
||||
mechanism.set(
|
||||
'\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341', 'ten');
|
||||
assertEquals(3, mechanism.getCount());
|
||||
assertSameElements([
|
||||
' ',
|
||||
'=+!@#$%^&*()-_\\|;:\'",./<>?[]{}~`',
|
||||
'\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341'
|
||||
], goog.iter.toArray(mechanism.__iterator__(true)));
|
||||
mechanism.clear();
|
||||
assertEquals(0, mechanism.getCount());
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Abstract interface for storing and retrieving data using
|
||||
* some persistence mechanism.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.Mechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Basic interface for all storage mechanisms.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.storage.mechanism.Mechanism = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Set a value for a key.
|
||||
*
|
||||
* @param {string} key The key to set.
|
||||
* @param {string} value The string to save.
|
||||
*/
|
||||
goog.storage.mechanism.Mechanism.prototype.set = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Get the value stored under a key.
|
||||
*
|
||||
* @param {string} key The key to get.
|
||||
* @return {?string} The corresponding value, null if not found.
|
||||
*/
|
||||
goog.storage.mechanism.Mechanism.prototype.get = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Remove a key and its value.
|
||||
*
|
||||
* @param {string} key The key to remove.
|
||||
*/
|
||||
goog.storage.mechanism.Mechanism.prototype.remove = goog.abstractMethod;
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides factory methods for selecting the best storage
|
||||
* mechanism, depending on availability and needs.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.mechanismfactory');
|
||||
|
||||
goog.require('goog.storage.mechanism.HTML5LocalStorage');
|
||||
goog.require('goog.storage.mechanism.HTML5SessionStorage');
|
||||
goog.require('goog.storage.mechanism.IEUserData');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
goog.require('goog.storage.mechanism.PrefixedMechanism');
|
||||
|
||||
|
||||
/**
|
||||
* The key to shared userData storage.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.storage.mechanism.mechanismfactory.USER_DATA_SHARED_KEY =
|
||||
'UserDataSharedStore';
|
||||
|
||||
|
||||
/**
|
||||
* Returns the best local storage mechanism, or null if unavailable.
|
||||
* Local storage means that the database is placed on user's computer.
|
||||
* The key-value database is normally shared between all the code paths
|
||||
* that request it, so using an optional namespace is recommended. This
|
||||
* provides separation and makes key collisions unlikely.
|
||||
*
|
||||
* @param {string=} opt_namespace Restricts the visibility to given namespace.
|
||||
* @return {goog.storage.mechanism.IterableMechanism} Created mechanism or null.
|
||||
*/
|
||||
goog.storage.mechanism.mechanismfactory.create = function(opt_namespace) {
|
||||
return goog.storage.mechanism.mechanismfactory.createHTML5LocalStorage(
|
||||
opt_namespace) ||
|
||||
goog.storage.mechanism.mechanismfactory.createIEUserData(opt_namespace);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an HTML5 local storage mechanism, or null if unavailable.
|
||||
* Since the HTML5 local storage does not support namespaces natively,
|
||||
* and the key-value database is shared between all the code paths
|
||||
* that request it, it is recommended that an optional namespace is
|
||||
* used to provide key separation employing a prefix.
|
||||
*
|
||||
* @param {string=} opt_namespace Restricts the visibility to given namespace.
|
||||
* @return {goog.storage.mechanism.IterableMechanism} Created mechanism or null.
|
||||
*/
|
||||
goog.storage.mechanism.mechanismfactory.createHTML5LocalStorage = function(
|
||||
opt_namespace) {
|
||||
var storage = new goog.storage.mechanism.HTML5LocalStorage();
|
||||
if (storage.isAvailable()) {
|
||||
return opt_namespace ? new goog.storage.mechanism.PrefixedMechanism(
|
||||
storage, opt_namespace) : storage;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an HTML5 session storage mechanism, or null if unavailable.
|
||||
* Since the HTML5 session storage does not support namespaces natively,
|
||||
* and the key-value database is shared between all the code paths
|
||||
* that request it, it is recommended that an optional namespace is
|
||||
* used to provide key separation employing a prefix.
|
||||
*
|
||||
* @param {string=} opt_namespace Restricts the visibility to given namespace.
|
||||
* @return {goog.storage.mechanism.IterableMechanism} Created mechanism or null.
|
||||
*/
|
||||
goog.storage.mechanism.mechanismfactory.createHTML5SessionStorage = function(
|
||||
opt_namespace) {
|
||||
var storage = new goog.storage.mechanism.HTML5SessionStorage();
|
||||
if (storage.isAvailable()) {
|
||||
return opt_namespace ? new goog.storage.mechanism.PrefixedMechanism(
|
||||
storage, opt_namespace) : storage;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an IE userData local storage mechanism, or null if unavailable.
|
||||
* Using an optional namespace is recommended to provide separation and
|
||||
* avoid key collisions.
|
||||
*
|
||||
* @param {string=} opt_namespace Restricts the visibility to given namespace.
|
||||
* @return {goog.storage.mechanism.IterableMechanism} Created mechanism or null.
|
||||
*/
|
||||
goog.storage.mechanism.mechanismfactory.createIEUserData = function(
|
||||
opt_namespace) {
|
||||
var storage = new goog.storage.mechanism.IEUserData(opt_namespace ||
|
||||
goog.storage.mechanism.mechanismfactory.USER_DATA_SHARED_KEY);
|
||||
if (storage.isAvailable()) {
|
||||
return storage;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Unit tests for storage mechanism separation.
|
||||
*
|
||||
* These tests should be included by tests of any mechanism which natively
|
||||
* implements namespaces. There is no need to include those tests for mechanisms
|
||||
* extending goog.storage.mechanism.PrefixedMechanism. Make sure a different
|
||||
* namespace is used for each object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.mechanismSeparationTester');
|
||||
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.setTestOnly('goog.storage.mechanism.mechanismSeparationTester');
|
||||
|
||||
|
||||
var mechanism = null;
|
||||
var mechanism_separate = null;
|
||||
|
||||
|
||||
function testSeparateSet() {
|
||||
if (!mechanism || !mechanism_separate) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
assertNull(mechanism_separate.get('first'));
|
||||
assertEquals(0, mechanism_separate.getCount());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(mechanism_separate.__iterator__().next));
|
||||
}
|
||||
|
||||
|
||||
function testSeparateSetInverse() {
|
||||
if (!mechanism || !mechanism_separate) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism_separate.set('first', 'two');
|
||||
assertEquals('one', mechanism.get('first'));
|
||||
assertEquals(1, mechanism.getCount());
|
||||
var iterator = mechanism.__iterator__();
|
||||
assertEquals('one', iterator.next());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(iterator.next));
|
||||
}
|
||||
|
||||
|
||||
function testSeparateRemove() {
|
||||
if (!mechanism || !mechanism_separate) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism_separate.remove('first');
|
||||
assertEquals('one', mechanism.get('first'));
|
||||
assertEquals(1, mechanism.getCount());
|
||||
var iterator = mechanism.__iterator__();
|
||||
assertEquals('one', iterator.next());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(iterator.next));
|
||||
}
|
||||
|
||||
|
||||
function testSeparateClean() {
|
||||
if (!mechanism || !mechanism_separate) {
|
||||
return;
|
||||
}
|
||||
mechanism_separate.set('first', 'two');
|
||||
mechanism.clear();
|
||||
assertEquals('two', mechanism_separate.get('first'));
|
||||
assertEquals(1, mechanism_separate.getCount());
|
||||
var iterator = mechanism_separate.__iterator__();
|
||||
assertEquals('two', iterator.next());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(iterator.next));
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Unit tests for storage mechanism sharing.
|
||||
*
|
||||
* These tests should be included in tests of any storage mechanism in which
|
||||
* separate mechanism instances share the same underlying storage. Most (if
|
||||
* not all) storage mechanisms should have this property. If the mechanism
|
||||
* employs namespaces, make sure the same namespace is used for both objects.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.mechanismSharingTester');
|
||||
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.setTestOnly('goog.storage.mechanism.mechanismSharingTester');
|
||||
|
||||
|
||||
var mechanism = null;
|
||||
var mechanism_shared = null;
|
||||
|
||||
|
||||
function testSharedSet() {
|
||||
if (!mechanism || !mechanism_shared) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
assertEquals('one', mechanism_shared.get('first'));
|
||||
assertEquals(1, mechanism_shared.getCount());
|
||||
var iterator = mechanism_shared.__iterator__();
|
||||
assertEquals('one', iterator.next());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(iterator.next));
|
||||
}
|
||||
|
||||
|
||||
function testSharedSetInverse() {
|
||||
if (!mechanism || !mechanism_shared) {
|
||||
return;
|
||||
}
|
||||
mechanism_shared.set('first', 'two');
|
||||
assertEquals('two', mechanism.get('first'));
|
||||
assertEquals(1, mechanism.getCount());
|
||||
var iterator = mechanism.__iterator__();
|
||||
assertEquals('two', iterator.next());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(iterator.next));
|
||||
}
|
||||
|
||||
|
||||
function testSharedRemove() {
|
||||
if (!mechanism || !mechanism_shared) {
|
||||
return;
|
||||
}
|
||||
mechanism_shared.set('first', 'three');
|
||||
mechanism.remove('first');
|
||||
assertNull(mechanism_shared.get('first'));
|
||||
assertEquals(0, mechanism_shared.getCount());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(mechanism_shared.__iterator__().next));
|
||||
}
|
||||
|
||||
|
||||
function testSharedClean() {
|
||||
if (!mechanism || !mechanism_shared) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'four');
|
||||
mechanism_shared.clear();
|
||||
assertEquals(0, mechanism.getCount());
|
||||
assertEquals(goog.iter.StopIteration,
|
||||
assertThrows(mechanism.__iterator__().next));
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Unit tests for the abstract storage mechanism interface.
|
||||
*
|
||||
* These tests should be included in tests of any class extending
|
||||
* goog.storage.mechanism.Mechanism.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.mechanismTester');
|
||||
|
||||
goog.require('goog.storage.mechanism.ErrorCode');
|
||||
goog.require('goog.storage.mechanism.HTML5LocalStorage');
|
||||
goog.require('goog.storage.mechanism.Mechanism');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.userAgent.product');
|
||||
goog.require('goog.userAgent.product.isVersion');
|
||||
goog.setTestOnly('goog.storage.mechanism.mechanismTester');
|
||||
|
||||
|
||||
var mechanism = null;
|
||||
var minimumQuota = 0;
|
||||
|
||||
|
||||
function testSetGet() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
assertEquals('one', mechanism.get('first'));
|
||||
}
|
||||
|
||||
|
||||
function testChange() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism.set('first', 'two');
|
||||
assertEquals('two', mechanism.get('first'));
|
||||
}
|
||||
|
||||
|
||||
function testRemove() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism.remove('first');
|
||||
assertNull(mechanism.get('first'));
|
||||
}
|
||||
|
||||
|
||||
function testSetRemoveSet() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism.remove('first');
|
||||
mechanism.set('first', 'one');
|
||||
assertEquals('one', mechanism.get('first'));
|
||||
}
|
||||
|
||||
|
||||
function testRemoveRemove() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.remove('first');
|
||||
mechanism.remove('first');
|
||||
assertNull(mechanism.get('first'));
|
||||
}
|
||||
|
||||
|
||||
function testSetTwo() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism.set('second', 'two');
|
||||
assertEquals('one', mechanism.get('first'));
|
||||
assertEquals('two', mechanism.get('second'));
|
||||
}
|
||||
|
||||
|
||||
function testChangeTwo() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism.set('second', 'two');
|
||||
mechanism.set('second', 'three');
|
||||
mechanism.set('first', 'four');
|
||||
assertEquals('four', mechanism.get('first'));
|
||||
assertEquals('three', mechanism.get('second'));
|
||||
}
|
||||
|
||||
|
||||
function testSetRemoveThree() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('first', 'one');
|
||||
mechanism.set('second', 'two');
|
||||
mechanism.set('third', 'three');
|
||||
mechanism.remove('second');
|
||||
assertNull(mechanism.get('second'));
|
||||
assertEquals('one', mechanism.get('first'));
|
||||
assertEquals('three', mechanism.get('third'));
|
||||
mechanism.remove('first');
|
||||
assertNull(mechanism.get('first'));
|
||||
assertEquals('three', mechanism.get('third'));
|
||||
mechanism.remove('third');
|
||||
assertNull(mechanism.get('third'));
|
||||
}
|
||||
|
||||
|
||||
function testEmptyValue() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
mechanism.set('third', '');
|
||||
assertEquals('', mechanism.get('third'));
|
||||
}
|
||||
|
||||
|
||||
function testWeirdKeys() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
// Some weird keys. We leave out some tests for some browsers where they
|
||||
// trigger browser bugs, and where the keys are too obscure to prepare a
|
||||
// workaround.
|
||||
mechanism.set(' ', 'space');
|
||||
mechanism.set('=+!@#$%^&*()-_\\|;:\'",./<>?[]{}~`', 'control');
|
||||
mechanism.set(
|
||||
'\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341', 'ten');
|
||||
mechanism.set('\0', 'null');
|
||||
mechanism.set('\0\0', 'double null');
|
||||
mechanism.set('\0A', 'null A');
|
||||
mechanism.set('', 'zero');
|
||||
assertEquals('space', mechanism.get(' '));
|
||||
assertEquals('control', mechanism.get('=+!@#$%^&*()-_\\|;:\'",./<>?[]{}~`'));
|
||||
assertEquals('ten', mechanism.get(
|
||||
'\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341'));
|
||||
if (!goog.userAgent.IE) {
|
||||
// IE does not properly handle nulls in HTML5 localStorage keys (IE8, IE9).
|
||||
// https://connect.microsoft.com/IE/feedback/details/667799/
|
||||
assertEquals('null', mechanism.get('\0'));
|
||||
assertEquals('double null', mechanism.get('\0\0'));
|
||||
assertEquals('null A', mechanism.get('\0A'));
|
||||
}
|
||||
if (!goog.userAgent.GECKO) {
|
||||
// Firefox does not properly handle the empty key (FF 3.5, 3.6, 4.0).
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=510849
|
||||
assertEquals('zero', mechanism.get(''));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testQuota() {
|
||||
if (!mechanism) {
|
||||
return;
|
||||
}
|
||||
// This test might crash Safari 4, so it is disabled for this version.
|
||||
// It works fine on Safari 3 and Safari 5.
|
||||
if (goog.userAgent.product.SAFARI &&
|
||||
goog.userAgent.product.isVersion(4) &&
|
||||
!goog.userAgent.product.isVersion(5)) {
|
||||
return;
|
||||
}
|
||||
var buffer = '\u03ff'; // 2 bytes
|
||||
var savedBytes = 0;
|
||||
try {
|
||||
while (buffer.length < minimumQuota) {
|
||||
buffer = buffer + buffer;
|
||||
mechanism.set('foo', buffer);
|
||||
savedBytes = buffer.length;
|
||||
}
|
||||
} catch (ex) {
|
||||
if (ex != goog.storage.mechanism.ErrorCode.QUOTA_EXCEEDED) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
mechanism.remove('foo');
|
||||
assertTrue(savedBytes >= minimumQuota);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Wraps an iterable storage mechanism and creates artificial
|
||||
* namespaces using a prefix in the global namespace.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.mechanism.PrefixedMechanism');
|
||||
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.storage.mechanism.IterableMechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wraps an iterable storage mechanism and creates artificial namespaces.
|
||||
*
|
||||
* @param {!goog.storage.mechanism.IterableMechanism} mechanism Underlying
|
||||
* iterable storage mechanism.
|
||||
* @param {string} prefix Prefix for creating an artificial namespace.
|
||||
* @constructor
|
||||
* @extends {goog.storage.mechanism.IterableMechanism}
|
||||
*/
|
||||
goog.storage.mechanism.PrefixedMechanism = function(mechanism, prefix) {
|
||||
goog.base(this);
|
||||
this.mechanism_ = mechanism;
|
||||
this.prefix_ = prefix + '::';
|
||||
};
|
||||
goog.inherits(goog.storage.mechanism.PrefixedMechanism,
|
||||
goog.storage.mechanism.IterableMechanism);
|
||||
|
||||
|
||||
/**
|
||||
* The mechanism to be prefixed.
|
||||
*
|
||||
* @type {goog.storage.mechanism.IterableMechanism}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.PrefixedMechanism.prototype.mechanism_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The prefix for creating artificial namespaces.
|
||||
*
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.mechanism.PrefixedMechanism.prototype.prefix_ = '';
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.PrefixedMechanism.prototype.set = function(key, value) {
|
||||
this.mechanism_.set(this.prefix_ + key, value);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.PrefixedMechanism.prototype.get = function(key) {
|
||||
return this.mechanism_.get(this.prefix_ + key);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.PrefixedMechanism.prototype.remove = function(key) {
|
||||
this.mechanism_.remove(this.prefix_ + key);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.mechanism.PrefixedMechanism.prototype.__iterator__ = function(
|
||||
opt_keys) {
|
||||
var subIter = this.mechanism_.__iterator__(true);
|
||||
var selfObj = this;
|
||||
var newIter = new goog.iter.Iterator();
|
||||
newIter.next = function() {
|
||||
var key = /** @type {string} */ (subIter.next());
|
||||
while (key.substr(0, selfObj.prefix_.length) != selfObj.prefix_) {
|
||||
key = /** @type {string} */ (subIter.next());
|
||||
}
|
||||
return opt_keys ? key.substr(selfObj.prefix_.length) :
|
||||
selfObj.mechanism_.get(key);
|
||||
};
|
||||
return newIter;
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a convenient API for data with attached metadata
|
||||
* persistence. You probably don't want to use this class directly as it
|
||||
* does not save any metadata by itself. It only provides the necessary
|
||||
* infrastructure for subclasses that need to save metadata along with
|
||||
* values stored.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.RichStorage');
|
||||
goog.provide('goog.storage.RichStorage.Wrapper');
|
||||
|
||||
goog.require('goog.storage.ErrorCode');
|
||||
goog.require('goog.storage.Storage');
|
||||
goog.require('goog.storage.mechanism.Mechanism');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a storage for data with attached metadata.
|
||||
*
|
||||
* @param {!goog.storage.mechanism.Mechanism} mechanism The underlying
|
||||
* storage mechanism.
|
||||
* @constructor
|
||||
* @extends {goog.storage.Storage}
|
||||
*/
|
||||
goog.storage.RichStorage = function(mechanism) {
|
||||
goog.base(this, mechanism);
|
||||
};
|
||||
goog.inherits(goog.storage.RichStorage, goog.storage.Storage);
|
||||
|
||||
|
||||
/**
|
||||
* Metadata key under which the actual data is stored.
|
||||
*
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
goog.storage.RichStorage.DATA_KEY = 'data';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a value so metadata can be associated with it. You probably want
|
||||
* to use goog.storage.RichStorage.Wrapper.wrapIfNecessary to avoid multiple
|
||||
* embeddings.
|
||||
*
|
||||
* @param {*} value The value to wrap.
|
||||
* @constructor
|
||||
*/
|
||||
goog.storage.RichStorage.Wrapper = function(value) {
|
||||
this[goog.storage.RichStorage.DATA_KEY] = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for wrapping a value so metadata can be associated with
|
||||
* it. No-op if the value is already wrapped or is undefined.
|
||||
*
|
||||
* @param {*} value The value to wrap.
|
||||
* @return {(!goog.storage.RichStorage.Wrapper|undefined)} The wrapper.
|
||||
*/
|
||||
goog.storage.RichStorage.Wrapper.wrapIfNecessary = function(value) {
|
||||
if (!goog.isDef(value) || value instanceof goog.storage.RichStorage.Wrapper) {
|
||||
return /** @type {(!goog.storage.RichStorage.Wrapper|undefined)} */ (value);
|
||||
}
|
||||
return new goog.storage.RichStorage.Wrapper(value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unwraps a value, any metadata is discarded (not returned). You might want to
|
||||
* use goog.storage.RichStorage.Wrapper.unwrapIfPossible to handle cases where
|
||||
* the wrapper is missing.
|
||||
*
|
||||
* @param {!Object} wrapper The wrapper.
|
||||
* @return {*} The wrapped value.
|
||||
*/
|
||||
goog.storage.RichStorage.Wrapper.unwrap = function(wrapper) {
|
||||
var value = wrapper[goog.storage.RichStorage.DATA_KEY];
|
||||
if (!goog.isDef(value)) {
|
||||
throw goog.storage.ErrorCode.INVALID_VALUE;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for unwrapping a value. Returns undefined if the
|
||||
* wrapper is missing.
|
||||
*
|
||||
* @param {(!Object|undefined)} wrapper The wrapper.
|
||||
* @return {*} The wrapped value or undefined.
|
||||
*/
|
||||
goog.storage.RichStorage.Wrapper.unwrapIfPossible = function(wrapper) {
|
||||
if (!wrapper) {
|
||||
return undefined;
|
||||
}
|
||||
return goog.storage.RichStorage.Wrapper.unwrap(wrapper);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.RichStorage.prototype.set = function(key, value) {
|
||||
goog.base(this, 'set', key,
|
||||
goog.storage.RichStorage.Wrapper.wrapIfNecessary(value));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get an item wrapper (the item and its metadata) from the storage.
|
||||
*
|
||||
* WARNING: This returns an Object, which once used to be
|
||||
* goog.storage.RichStorage.Wrapper. This is due to the fact
|
||||
* that deserialized objects lose type information and it
|
||||
* is hard to do proper typecasting in JavaScript. Be sure
|
||||
* you know what you are doing when using the returned value.
|
||||
*
|
||||
* @param {string} key The key to get.
|
||||
* @return {(!Object|undefined)} The wrapper, or undefined if not found.
|
||||
*/
|
||||
goog.storage.RichStorage.prototype.getWrapper = function(key) {
|
||||
var wrapper = goog.storage.RichStorage.superClass_.get.call(this, key);
|
||||
if (!goog.isDef(wrapper) || wrapper instanceof Object) {
|
||||
return /** @type {(!Object|undefined)} */ (wrapper);
|
||||
}
|
||||
throw goog.storage.ErrorCode.INVALID_VALUE;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.storage.RichStorage.prototype.get = function(key) {
|
||||
return goog.storage.RichStorage.Wrapper.unwrapIfPossible(
|
||||
this.getWrapper(key));
|
||||
};
|
||||
111
nicer-api-docs/closure-library/closure/goog/storage/storage.js
Normal file
111
nicer-api-docs/closure-library/closure/goog/storage/storage.js
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a convenient API for data persistence using a selected
|
||||
* data storage mechanism.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.Storage');
|
||||
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.json.Serializer');
|
||||
goog.require('goog.storage.ErrorCode');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The base implementation for all storage APIs.
|
||||
*
|
||||
* @param {!goog.storage.mechanism.Mechanism} mechanism The underlying
|
||||
* storage mechanism.
|
||||
* @constructor
|
||||
*/
|
||||
goog.storage.Storage = function(mechanism) {
|
||||
this.mechanism = mechanism;
|
||||
this.serializer_ = new goog.json.Serializer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The mechanism used to persist key-value pairs.
|
||||
*
|
||||
* @type {goog.storage.mechanism.Mechanism}
|
||||
* @protected
|
||||
*/
|
||||
goog.storage.Storage.prototype.mechanism = null;
|
||||
|
||||
|
||||
/**
|
||||
* The JSON serializer used to serialize values.
|
||||
*
|
||||
* @type {goog.json.Serializer}
|
||||
* @private
|
||||
*/
|
||||
goog.storage.Storage.prototype.serializer_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Set an item in the data storage.
|
||||
*
|
||||
* @param {string} key The key to set.
|
||||
* @param {*} value The value to serialize to a string and save.
|
||||
*/
|
||||
goog.storage.Storage.prototype.set = function(key, value) {
|
||||
if (!goog.isDef(value)) {
|
||||
this.mechanism.remove(key);
|
||||
return;
|
||||
}
|
||||
this.mechanism.set(key, this.serializer_.serialize(value));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get an item from the data storage.
|
||||
*
|
||||
* @param {string} key The key to get.
|
||||
* @return {*} Deserialized value or undefined if not found.
|
||||
*/
|
||||
goog.storage.Storage.prototype.get = function(key) {
|
||||
var json;
|
||||
try {
|
||||
json = this.mechanism.get(key);
|
||||
} catch (e) {
|
||||
// If, for any reason, the value returned by a mechanism's get method is not
|
||||
// a string, an exception is thrown. In this case, we must fail gracefully
|
||||
// instead of propagating the exception to clients. See b/8095488 for
|
||||
// details.
|
||||
return undefined;
|
||||
}
|
||||
if (goog.isNull(json)) {
|
||||
return undefined;
|
||||
}
|
||||
/** @preserveTry */
|
||||
try {
|
||||
return goog.json.parse(json);
|
||||
} catch (e) {
|
||||
throw goog.storage.ErrorCode.INVALID_VALUE;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove an item from the data storage.
|
||||
*
|
||||
* @param {string} key The key to remove.
|
||||
*/
|
||||
goog.storage.Storage.prototype.remove = function(key) {
|
||||
this.mechanism.remove(key);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Unit tests for the storage interface.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.storage.storage_test');
|
||||
|
||||
goog.require('goog.storage.Storage');
|
||||
goog.require('goog.structs.Map');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.setTestOnly('storage_test');
|
||||
|
||||
|
||||
goog.storage.storage_test.runBasicTests = function(storage) {
|
||||
// Simple Objects.
|
||||
storage.set('first', 'Hello world!');
|
||||
storage.set('second', ['one', 'two', 'three']);
|
||||
storage.set('third', {'a': 97, 'b': 98});
|
||||
assertEquals('Hello world!', storage.get('first'));
|
||||
assertObjectEquals(['one', 'two', 'three'], storage.get('second'));
|
||||
assertObjectEquals({'a': 97, 'b': 98}, storage.get('third'));
|
||||
|
||||
// Some more complex fun with a Map.
|
||||
var map = new goog.structs.Map();
|
||||
map.set('Alice', 'Hello world!');
|
||||
map.set('Bob', ['one', 'two', 'three']);
|
||||
map.set('Cecile', {'a': 97, 'b': 98});
|
||||
storage.set('first', map.toObject());
|
||||
assertObjectEquals(map.toObject(), storage.get('first'));
|
||||
|
||||
// Setting weird values.
|
||||
storage.set('second', null);
|
||||
assertEquals(null, storage.get('second'));
|
||||
storage.set('second', undefined);
|
||||
assertEquals(undefined, storage.get('second'));
|
||||
storage.set('second', '');
|
||||
assertEquals('', storage.get('second'));
|
||||
|
||||
// Clean up.
|
||||
storage.remove('first');
|
||||
storage.remove('second');
|
||||
storage.remove('third');
|
||||
assertUndefined(storage.get('first'));
|
||||
assertUndefined(storage.get('second'));
|
||||
assertUndefined(storage.get('third'));
|
||||
};
|
||||
Reference in New Issue
Block a user