Merge "resourceloader: Purge localStorage blob if last written 30+ days ago"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 28 Aug 2019 20:57:07 +0000 (20:57 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 28 Aug 2019 20:57:07 +0000 (20:57 +0000)
resources/src/startup/mediawiki.js
tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js

index a3249de..a4ee488 100644 (file)
                                        // 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;
                                                        }
index 894dd19..3258f8e 100644 (file)
@@ -21,6 +21,9 @@
                                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' ],