jenkins-bot has submitted this change and it was merged. Change subject: Make progress bar less erratic ......................................................................
Make progress bar less erratic Solves the bug, and makes the code slightly cleaner, but it still does not inspire confidence (e.g. use of viewer flags by a bunch of callbacks that can run for a background image). Also, the tests seem underspecified. I'll follow up with some more refactoring. Change-Id: I2557abcec173691ffce21185bf1a939f1644ba8c Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/489 --- M resources/mmv/mmv.js M resources/mmv/ui/mmv.ui.canvas.js M tests/qunit/mmv/mmv.test.js 3 files changed, 117 insertions(+), 52 deletions(-) Approvals: Gilles: Looks good to me, approved jenkins-bot: Verified diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js index ed64043..8457789 100755 --- a/resources/mmv/mmv.js +++ b/resources/mmv/mmv.js @@ -239,9 +239,7 @@ metadataPromise, start, viewer = this, - $initialImage = $( initialImage ), - fileWidth = image.originalWidth, - fileHeight = image.originalHeight; + $initialImage = $( initialImage ); this.currentIndex = image.index; @@ -273,48 +271,13 @@ start = $.now(); - // Reset the progress bar, it could be at any state if we're calling loadImage - // while another image is already loading - viewer.ui.panel.progressBar.percent( 0 ); - imagePromise = this.fetchThumbnailForLightboxImage( image, imageWidths.real ); - // Check that the image hasn't already been loaded - if ( imagePromise.state() === 'pending' ) { - // Animate it to 5 to give a sense to something is happening, even if we're stuck - // waiting for server-side processing, such as thumbnail (re)generation - viewer.ui.panel.progressBar.percent( 5 ); - } + viewer.displayPlaceholderThumbnail( image, $initialImage, imageWidths ); - if ( fileWidth > 0 && fileHeight > 0 ) { - viewer.displayPlaceholderThumbnail( { width : fileWidth , height : fileHeight }, - $initialImage, - imageWidths ); - } else { - this.imageInfoProvider.get( image.filePageTitle ).done( function ( imageInfo ) { - if ( viewer.currentIndex !== image.index ) { - return; - } + this.setupProgressBar( image, imagePromise ); - viewer.displayPlaceholderThumbnail( imageInfo, $initialImage, imageWidths ); - } ); - } - - imagePromise.progress( function ( thumbnailInfoResponse, imageResponse ) { - if ( viewer.currentIndex !== image.index ) { - return; - } - - if ( viewer.ui - && viewer.ui.panel - && imageResponse.length === 2 - && imageResponse[ 1 ] > 5 ) { - viewer.ui.panel.progressBar.percent( imageResponse[ 1 ] ); - } - } ).done( function ( thumbnail, imageElement ) { - // Fallback in case the browser doesn't have fancy progress updates - viewer.ui.panel.progressBar.percent( 100 ); - + imagePromise.done( function ( thumbnail, imageElement ) { if ( viewer.currentIndex !== image.index ) { return; } @@ -406,18 +369,97 @@ /** * Display the blurred thumbnail from the page - * @param {mw.mmv.model.Image} imageInfo + * @param {mw.mmv.LightboxImage} image * @param {jQuery} $initialImage The thumbnail from the page * @param {mw.mmv.model.ThumbnailWidth} imageWidths + * @param {boolean} [recursion=false] for internal use, never set this */ - MMVP.displayPlaceholderThumbnail = function ( imageInfo, $initialImage, imageWidths ) { + MMVP.displayPlaceholderThumbnail = function ( image, $initialImage, imageWidths, recursion ) { + var viewer = this, + size = { width : image.originalWidth, height : image.originalHeight }; + // If the actual image has already been displayed, there's no point showing the blurry one if ( this.realThumbnailShown ) { return; } - this.blurredThumbnailShown = this.ui.canvas.maybeDisplayPlaceholder( - imageInfo, $initialImage, imageWidths ); + // Width/height of the original image are added to the HTML by MediaViewer via a PHP hook, + // and can be missing in exotic circumstances, e. g. when the extension has only been + // enabled recently and the HTML cache has not cleared yet. If that is the case, we need + // to fetch the size from the API first. + if ( !size.width || !size.height ) { + if ( recursion ) { + // this should not be possible, but an infinite recursion is nasty + // business, so we make a sanity check + throw 'MediaViewer internal error: displayPlaceholderThumbnail recursion'; + } + this.imageInfoProvider.get( image.filePageTitle ).done( function ( imageInfo ) { + // Make sure the user has not navigated away while we were waiting for the size + if ( viewer.currentIndex === image.index ) { + image.originalWidth = imageInfo.width; + image.originalHeight = imageInfo.height; + viewer.displayPlaceholderThumbnail( image, $initialImage, imageWidths, true ); + } + } ); + } else { + this.blurredThumbnailShown = this.ui.canvas.maybeDisplayPlaceholder( + size, $initialImage, imageWidths ); + } + }; + + /** + * Displays a progress bar for the image loading, if necessary, and sets up handling of + * all the related callbacks. + * FIXME would be nice to pass a simple promise which only returns a single number + * and does not fire when the image is not visible + * @param {mw.mmv.LightboxImage} image + * @param {jQuery.Promise.<mw.mmv.model.Thumbnail, HTMLImageElement>} imagePromise + */ + MMVP.setupProgressBar = function ( image, imagePromise ) { + var viewer = this; + + // Reset the progress bar, it could be at any state if we're calling loadImage + // while another image is already loading + // FIXME we should probably jump to the current progress instead + viewer.ui.panel.progressBar.percent( 0 ); + + if ( imagePromise.state() !== 'pending' ) { + // image has already loaded (or failed to load) - nothing to do + return; + } + + // FIXME this is all wrong, we might be navigating back to a half-loaded image + + // Animate progress bar to 5 to give a sense to something is happening, even if we're + // stuck waiting for server-side processing, such as thumbnail (re)generation + viewer.ui.panel.progressBar.percent( 5 ); + + imagePromise.progress( function ( thumbnailInfoResponse, imageResponse ) { + // FIXME this should be explained in a comment + var progress = imageResponse[1]; + + if ( viewer.currentIndex !== image.index ) { + return; + } + + // We started from 5, don't move backwards + if ( progress > 5 ) { + viewer.ui.panel.progressBar.percent( progress ); + } + } ).done( function () { + if ( viewer.currentIndex !== image.index ) { + return; + } + + // Fallback in case the browser doesn't have fancy progress updates + viewer.ui.panel.progressBar.percent( 100 ); + } ).fail( function () { + if ( viewer.currentIndex !== image.index ) { + return; + } + // Hide progress bar on error + viewer.ui.panel.progressBar.percent( 0 ); + } ); }; /** @@ -592,6 +634,7 @@ * Loads size-dependent components of a lightbox - the thumbnail model and the image itself. * @param {mw.mmv.LightboxImage} image * @param {number} width the width of the requested thumbnail + * @returns {jQuery.Promise.<mw.mmv.model.Thumbnail, HTMLImageElement>} */ MMVP.fetchThumbnailForLightboxImage = function ( image, width ) { return this.fetchThumbnail( diff --git a/resources/mmv/ui/mmv.ui.canvas.js b/resources/mmv/ui/mmv.ui.canvas.js index e08f5ec..c691b8d 100644 --- a/resources/mmv/ui/mmv.ui.canvas.js +++ b/resources/mmv/ui/mmv.ui.canvas.js @@ -177,23 +177,23 @@ * We set SVG files to the maximum screen size available. * Assumes set function called before. * - * @param {mw.mmv.model.Image} imageInfo + * @param {{width: number, height: number}} size * @param {jQuery} $imagePlaceholder Image placeholder to be displayed while the real image loads. * @param {mw.mmv.model.ThumbnailWidth} imageWidths * @returns {boolean} Whether the image was blured or not */ - C.maybeDisplayPlaceholder = function ( imageInfo, $imagePlaceholder, imageWidths ) { + C.maybeDisplayPlaceholder = function ( size, $imagePlaceholder, imageWidths ) { var targetWidth, targetHeight, blowupFactor, blurredThumbnailShown = false; // Assume natural thumbnail size¸ - targetWidth = imageInfo.width; - targetHeight = imageInfo.height; + targetWidth = size.width; + targetHeight = size.height; // If the image is bigger than the screen we need to resize it - if ( imageInfo.width > imageWidths.cssWidth ) { // This assumes imageInfo.width in CSS units + if ( size.width > imageWidths.cssWidth ) { // This assumes imageInfo.width in CSS units targetWidth = imageWidths.cssWidth; targetHeight = imageWidths.cssHeight; } diff --git a/tests/qunit/mmv/mmv.test.js b/tests/qunit/mmv/mmv.test.js index 57e9f1a..d211bb8 100644 --- a/tests/qunit/mmv/mmv.test.js +++ b/tests/qunit/mmv/mmv.test.js @@ -174,8 +174,30 @@ viewer.ui = { canvas : { maybeDisplayPlaceholder : function() { return true; } } }; + viewer.imageInfoProvider.get = this.sandbox.stub(); - viewer.displayPlaceholderThumbnail( undefined, undefined, undefined); + viewer.displayPlaceholderThumbnail( { originalWidth: 100, originalHeight: 100 }, undefined, undefined); + + assert.ok( viewer.blurredThumbnailShown, 'Placeholder state is correct' ); + assert.ok( !viewer.realThumbnailShown, 'Real thumbnail state is correct' ); + + viewer.displayRealThumbnail(); + + assert.ok( viewer.realThumbnailShown, 'Real thumbnail state is correct' ); + assert.ok( viewer.blurredThumbnailShown, 'Placeholder state is correct' ); + } ); + + QUnit.test( 'Placeholder first, then real thumbnail - missing size', 4, function ( assert ) { + var viewer = new mw.mmv.MultimediaViewer(); + + viewer.currentIndex = 1; + viewer.setImage = $.noop; + viewer.ui = { canvas : { + maybeDisplayPlaceholder : function() { return true; } + } }; + viewer.imageInfoProvider.get = this.sandbox.stub().returns( $.Deferred().resolve( {width: 100, height: 100 } ) ); + + viewer.displayPlaceholderThumbnail( { index: 1 }, undefined, undefined); assert.ok( viewer.blurredThumbnailShown, 'Placeholder state is correct' ); assert.ok( !viewer.realThumbnailShown, 'Real thumbnail state is correct' ); @@ -199,7 +221,7 @@ assert.ok( viewer.realThumbnailShown, 'Real thumbnail state is correct' ); assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' ); - viewer.displayPlaceholderThumbnail( undefined, undefined, undefined); + viewer.displayPlaceholderThumbnail( {}, undefined, undefined); assert.ok( viewer.realThumbnailShown, 'Real thumbnail state is correct' ); assert.ok( !viewer.blurredThumbnailShown, 'Placeholder state is correct' ); -- To view, visit https://gerrit.wikimedia.org/r/129564 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I2557abcec173691ffce21185bf1a939f1644ba8c Gerrit-PatchSet: 4 Gerrit-Project: mediawiki/extensions/MultimediaViewer Gerrit-Branch: master Gerrit-Owner: Gergő Tisza <gti...@wikimedia.org> Gerrit-Reviewer: Gilles <gdu...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits