Add simplified storage API

Provide a standard mechanism for accessing localStorage.

It may seems simplistic right now, but to give an idea of the why:
* We already have jquery.jStorage.js which is a much more heavyweight
approach to storing non-essential values.
* We are repeating ourselves a lot in extensions by having
to do localStorage detection and then deal with full localStorage.
In MobileFrontend we have a settings module. This is one of the reasons Gather
depends on MobileFrontend and I'm keen to remove that dependency.
* We might want to move to indexdb in future. Having a single API makes moving
this easier - we don't have to update everywhere that uses localStorage
* Saving non-string support would be useful. The API could be adjusted to take a
mixed second parameter that stringifys JSON objects.
* Cookie fallbacks are a possible alternative when localStorage is not supported. This allows
us to be agnostic of the storage mechanism going forward.

Note:
This doesn't reuse the handling in mediawiki.js as at this point
I am not sure there is value. mw.loader.store.enabled is false when
$wgResourceLoaderStorageEnabled is not true and this should work even
without that case. We can review this at a latter point.

See:
Id5c32bb7a662dda8d153490f7c47e972cabc1efd
I3fd44b0ae6633a7053aee247bc3c4704ba987bc8

Bug: T96155
Change-Id: Idb37352acecd745beb53aa8d77ea050851448e0d
This commit is contained in:
jdlrobson 2015-08-11 11:16:21 -07:00 committed by Ori.livneh
parent 6147de1182
commit 7daab75414
5 changed files with 129 additions and 0 deletions

View file

@ -27,6 +27,7 @@
"mw.messagePoster.*",
"mw.notification",
"mw.Notification_",
"mw.storage",
"mw.user",
"mw.util",
"mw.plugin.*",

View file

@ -1092,6 +1092,10 @@ return array(
'styles' => 'resources/src/mediawiki/mediawiki.sectionAnchor.css',
'targets' => array( 'desktop', 'mobile' ),
),
'mediawiki.storage' => array(
'scripts' => 'resources/src/mediawiki/mediawiki.storage.js',
'targets' => array( 'desktop', 'mobile' ),
),
'mediawiki.Title' => array(
'scripts' => 'resources/src/mediawiki/mediawiki.Title.js',
'dependencies' => array(

View file

@ -0,0 +1,69 @@
( function ( mw ) {
'use strict';
var storage;
/**
* Library for storing device specific information. It should be used for storing simple
* strings and is not suitable for storing large chunks of data.
* @class mw.storage
* @singleton
*/
storage = {
isLocalStorageSupported: false,
/**
* Retrieve value from device storage.
*
* @param {String} key of item to retrieve
* @returns {String|Boolean} false when localStorage not available, otherwise string
*/
get: function ( key ) {
if ( this.isLocalStorageSupported ) {
return localStorage.getItem( key );
} else {
return false;
}
},
/**
* Set a value in device storage.
*
* @param {String} key key name to store under.
* @param {String} value to be stored.
* @returns {Boolean} whether the save succeeded or not.
*/
set: function ( key, value ) {
try {
localStorage.setItem( key, value );
return true;
} catch ( e ) {
return false;
}
},
/**
* Remove a value from device storage.
*
* @param {String} key of item to remove.
* @returns {Boolean} whether the save succeeded or not.
*/
remove: function ( key ) {
if ( this.isLocalStorageSupported ) {
localStorage.removeItem( key );
return true;
} else {
return false;
}
}
};
mw.storage = storage;
// See if local storage is supported
try {
localStorage.setItem( 'localStorageTest', 'localStorageTest' );
localStorage.removeItem( 'localStorageTest' );
storage.isLocalStorageSupported = true;
} catch ( e ) {
// Already set. No body needed.
}
}( mediaWiki ) );

View file

@ -68,6 +68,7 @@ return array(
'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
@ -113,6 +114,7 @@ return array(
'mediawiki.jqueryMsg',
'mediawiki.messagePoster',
'mediawiki.RegExp',
'mediawiki.storage',
'mediawiki.Title',
'mediawiki.toc',
'mediawiki.Uri',

View file

@ -0,0 +1,53 @@
( function ( mw ) {
QUnit.module( 'mediawiki.storage: normal case.', {
setup: function () {
this.sandbox.stub( mw.storage, 'isLocalStorageSupported', true );
this.spy = this.sandbox.spy( localStorage, 'setItem' );
this.sandbox.stub( localStorage, 'getItem' )
.withArgs( 'foo' ).returns( 'test' )
.withArgs( 'bar' ).returns( null );
}
} );
QUnit.test( 'set/get with localStorage', 4, function ( assert ) {
mw.storage.set( 'foo', 'test' );
assert.strictEqual( this.spy.calledOnce, true, 'Check localStorage called.' );
assert.strictEqual( this.spy.calledWith( 'foo', 'test' ), true,
'Check prefixed.' );
assert.strictEqual( mw.storage.get( 'foo' ), 'test', 'Check value gets stored.' );
assert.strictEqual( mw.storage.get( 'bar' ), null, 'Unset values are null.' );
} );
QUnit.module( 'mediawiki.storage: localStorage does not exist', {
setup: function () {
this.sandbox.stub( mw.storage, 'isLocalStorageSupported', false );
this.sandbox.stub( localStorage, 'setItem' ).throws();
}
} );
QUnit.test( 'set/get without localStorage', 3, function ( assert ) {
assert.strictEqual( mw.storage.set( 'foo', 'test' ), false,
'When localStorage not available save fails.' );
assert.strictEqual( mw.storage.remove( 'foo', 'test' ), false,
'When localStorage not available remove fails.' );
assert.strictEqual( mw.storage.get( 'foo' ), false,
'Inability to retrieve values return false to differentiate from null (not set).' );
} );
QUnit.module( 'mediawiki.storage: localStorage exhausted', {
setup: function () {
this.sandbox.stub( mw.storage, 'isLocalStorageSupported', true );
this.sandbox.stub( localStorage, 'setItem' ).throws();
this.sandbox.stub( localStorage, 'getItem' ).returns( null );
}
} );
QUnit.test( 'set/get without localStorage', 2, function ( assert ) {
assert.strictEqual( mw.storage.set( 'foo', 'test' ), false,
'When localStorage not available inform user with false.' );
assert.strictEqual( mw.storage.get( 'foo' ), null, 'No value registered.' );
} );
}( mediaWiki ) );