Ensure that blob:
URLs will be revoked when pages are cleaned-up/destroyed
Natively supported JPEG images are sent as-is, using a `blob:` or possibly a `data` URL, to the main-thread for loading/decoding. However there's currently no attempt at releasing these resources, which are held alive by `blob:` URLs, which seems unfortunately given that images can be arbitrarily large. As mentioned in https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL the lifetime of these URLs are tied to the document, hence they are not being removed when a page is cleaned-up/destroyed (e.g. when being removed from the `PDFPageViewBuffer` in the viewer). This is easy to test with the help of `about:memory` (in Firefox), which clearly shows the number of `blob:` URLs becomming arbitrarily large *without* this patch. With this patch however the `blob:` URLs are immediately release upon clean-up as expected, and the memory consumption should thus be considerably reduced for long documents with (simple) JPEG images.
This commit is contained in:
parent
80135378ca
commit
983b25f863
@ -23,7 +23,8 @@ import {
|
|||||||
} from '../shared/util';
|
} from '../shared/util';
|
||||||
import {
|
import {
|
||||||
deprecated, DOMCanvasFactory, DOMCMapReaderFactory, DummyStatTimer,
|
deprecated, DOMCanvasFactory, DOMCMapReaderFactory, DummyStatTimer,
|
||||||
loadScript, PageViewport, RenderingCancelledException, StatTimer
|
loadScript, PageViewport, releaseImageResources, RenderingCancelledException,
|
||||||
|
StatTimer
|
||||||
} from './display_utils';
|
} from './display_utils';
|
||||||
import { FontFaceObject, FontLoader } from './font_loader';
|
import { FontFaceObject, FontLoader } from './font_loader';
|
||||||
import { apiCompatibilityParams } from './api_compatibility';
|
import { apiCompatibilityParams } from './api_compatibility';
|
||||||
@ -1988,11 +1989,14 @@ class WorkerTransport {
|
|||||||
resolve(img);
|
resolve(img);
|
||||||
};
|
};
|
||||||
img.onerror = function() {
|
img.onerror = function() {
|
||||||
reject(new Error('Error during JPEG image loading'));
|
|
||||||
// Note that when the browser image loading/decoding fails,
|
// Note that when the browser image loading/decoding fails,
|
||||||
// we'll fallback to the built-in PDF.js JPEG decoder; see
|
// we'll fallback to the built-in PDF.js JPEG decoder; see
|
||||||
// `PartialEvaluator.buildPaintImageXObject` in the
|
// `PartialEvaluator.buildPaintImageXObject` in the
|
||||||
// `src/core/evaluator.js` file.
|
// `src/core/evaluator.js` file.
|
||||||
|
reject(new Error('Error during JPEG image loading'));
|
||||||
|
|
||||||
|
// Always remember to release the image data if errors occurred.
|
||||||
|
releaseImageResources(img);
|
||||||
};
|
};
|
||||||
img.src = imageData;
|
img.src = imageData;
|
||||||
}).then((img) => {
|
}).then((img) => {
|
||||||
@ -2095,6 +2099,8 @@ class WorkerTransport {
|
|||||||
}
|
}
|
||||||
resolve({ data: buf, width, height, });
|
resolve({ data: buf, width, height, });
|
||||||
|
|
||||||
|
// Immediately release the image data once decoding has finished.
|
||||||
|
releaseImageResources(img);
|
||||||
// Zeroing the width and height cause Firefox to release graphics
|
// Zeroing the width and height cause Firefox to release graphics
|
||||||
// resources immediately, which can greatly reduce memory consumption.
|
// resources immediately, which can greatly reduce memory consumption.
|
||||||
tmpCanvas.width = 0;
|
tmpCanvas.width = 0;
|
||||||
@ -2104,6 +2110,9 @@ class WorkerTransport {
|
|||||||
};
|
};
|
||||||
img.onerror = function() {
|
img.onerror = function() {
|
||||||
reject(new Error('JpegDecode failed to load image'));
|
reject(new Error('JpegDecode failed to load image'));
|
||||||
|
|
||||||
|
// Always remember to release the image data if errors occurred.
|
||||||
|
releaseImageResources(img);
|
||||||
};
|
};
|
||||||
img.src = imageUrl;
|
img.src = imageUrl;
|
||||||
});
|
});
|
||||||
@ -2323,6 +2332,14 @@ class PDFObjects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
|
for (const objId in this._objs) {
|
||||||
|
const { data, } = this._objs[objId];
|
||||||
|
|
||||||
|
if (typeof Image !== 'undefined' && data instanceof Image) {
|
||||||
|
// Always release the image data when clearing out the cached objects.
|
||||||
|
releaseImageResources(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
this._objs = Object.create(null);
|
this._objs = Object.create(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -480,6 +480,16 @@ function deprecated(details) {
|
|||||||
console.log('Deprecated API usage: ' + details);
|
console.log('Deprecated API usage: ' + details);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function releaseImageResources(img) {
|
||||||
|
assert(img instanceof Image, 'Invalid `img` parameter.');
|
||||||
|
|
||||||
|
const url = img.src;
|
||||||
|
if (typeof url === 'string' && url.startsWith('blob:') &&
|
||||||
|
URL.revokeObjectURL) {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
PageViewport,
|
PageViewport,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
@ -496,4 +506,5 @@ export {
|
|||||||
isValidFetchUrl,
|
isValidFetchUrl,
|
||||||
loadScript,
|
loadScript,
|
||||||
deprecated,
|
deprecated,
|
||||||
|
releaseImageResources,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user