// Whether the store is in use on this page.
enabled: null,
- // Modules whose string representation exceeds 100 kB are
- // ineligible for storage. See bug T66721.
- MODULE_SIZE_MAX: 100 * 1000,
+ // Modules whose serialised form exceeds 100 kB won't be stored (T66721).
+ MODULE_SIZE_MAX: 1e5,
// The contents of the store, mapping '[name]@[version]' keys
// to module implementations.
* @return {Object} Module store contents.
*/
toJSON: function () {
- return { items: mw.loader.store.items, vary: mw.loader.store.vary };
+ return {
+ items: mw.loader.store.items,
+ vary: mw.loader.store.vary,
+ // Store with 1e7 ms accuracy (1e4 seconds, or ~ 2.7 hours),
+ // which is enough for the purpose of expiring after ~ 30 days.
+ asOf: Math.ceil( Date.now() / 1e7 )
+ };
},
/**
this.enabled = true;
// If null, JSON.parse() will cast to string and re-parse, still null.
data = JSON.parse( raw );
- if ( data && typeof data.items === 'object' && data.vary === this.vary ) {
+ if ( data &&
+ typeof data.items === 'object' &&
+ data.vary === this.vary &&
+ // Only use if it's been less than 30 days since the data was written
+ // 30 days = 2,592,000 s = 2,592,000,000 ms = ± 259e7 ms
+ Date.now() < ( data.asOf * 1e7 ) + 259e7
+ ) {
+ // The data is not corrupt, matches our vary context, and has not expired.
this.items = data.items;
return;
}
window.Set = this.nativeSet;
mw.redefineFallbacksForTest();
}
+ if ( this.resetStoreKey ) {
+ localStorage.removeItem( mw.loader.store.key );
+ }
// Remove any remaining temporary statics
// exposed for cross-file mocks.
delete mw.loader.testCallback;
} );
} );
+ QUnit.test( 'mw.loader.store.init - Invalid JSON', function ( assert ) {
+ // Reset
+ this.sandbox.stub( mw.loader.store, 'enabled', null );
+ this.sandbox.stub( mw.loader.store, 'items', {} );
+ this.resetStoreKey = true;
+ localStorage.setItem( mw.loader.store.key, 'invalid' );
+
+ mw.loader.store.init();
+ assert.strictEqual( mw.loader.store.enabled, true, 'Enabled' );
+ assert.strictEqual(
+ $.isEmptyObject( mw.loader.store.items ),
+ true,
+ 'Items starts fresh'
+ );
+ } );
+
+ QUnit.test( 'mw.loader.store.init - Wrong JSON', function ( assert ) {
+ // Reset
+ this.sandbox.stub( mw.loader.store, 'enabled', null );
+ this.sandbox.stub( mw.loader.store, 'items', {} );
+ this.resetStoreKey = true;
+ localStorage.setItem( mw.loader.store.key, JSON.stringify( { wrong: true } ) );
+
+ mw.loader.store.init();
+ assert.strictEqual( mw.loader.store.enabled, true, 'Enabled' );
+ assert.strictEqual(
+ $.isEmptyObject( mw.loader.store.items ),
+ true,
+ 'Items starts fresh'
+ );
+ } );
+
+ QUnit.test( 'mw.loader.store.init - Expired JSON', function ( assert ) {
+ // Reset
+ this.sandbox.stub( mw.loader.store, 'enabled', null );
+ this.sandbox.stub( mw.loader.store, 'items', {} );
+ this.resetStoreKey = true;
+ localStorage.setItem( mw.loader.store.key, JSON.stringify( {
+ items: { use: 'not me' },
+ vary: mw.loader.store.vary,
+ asOf: 130161 // 2011-04-01 12:00
+ } ) );
+
+ mw.loader.store.init();
+ assert.strictEqual( mw.loader.store.enabled, true, 'Enabled' );
+ assert.strictEqual(
+ $.isEmptyObject( mw.loader.store.items ),
+ true,
+ 'Items starts fresh'
+ );
+ } );
+
+ QUnit.test( 'mw.loader.store.init - Good JSON', function ( assert ) {
+ // Reset
+ this.sandbox.stub( mw.loader.store, 'enabled', null );
+ this.sandbox.stub( mw.loader.store, 'items', {} );
+ this.resetStoreKey = true;
+ localStorage.setItem( mw.loader.store.key, JSON.stringify( {
+ items: { use: 'me' },
+ vary: mw.loader.store.vary,
+ asOf: Math.ceil( Date.now() / 1e7 ) - 5 // ~ 13 hours ago
+ } ) );
+
+ mw.loader.store.init();
+ assert.strictEqual( mw.loader.store.enabled, true, 'Enabled' );
+ assert.deepEqual(
+ mw.loader.store.items,
+ { use: 'me' },
+ 'Stored items are loaded'
+ );
+ } );
+
QUnit.test( 'require()', function ( assert ) {
mw.loader.register( [
[ 'test.require1', '0' ],