diff --git a/web/app.js b/web/app.js index 9ad2a40da..ea1cca5b5 100644 --- a/web/app.js +++ b/web/app.js @@ -27,7 +27,6 @@ import { normalizeWheelEventDirection, parseQueryString, ProgressBar, - RendererType, RenderingStates, ScrollMode, SidebarView, @@ -512,11 +511,6 @@ const PDFViewerApplication = { findController, scriptingManager: AppOptions.get("enableScripting") && pdfScriptingManager, - renderer: - typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || GENERIC") - ? AppOptions.get("renderer") - : null, l10n: this.l10n, textLayerMode: AppOptions.get("textLayerMode"), annotationMode: AppOptions.get("annotationMode"), @@ -1714,17 +1708,7 @@ const PDFViewerApplication = { this.pdfViewer.cleanup(); this.pdfThumbnailViewer?.cleanup(); - if ( - typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || GENERIC") - ) { - // We don't want to remove fonts used by active page SVGs. - this.pdfDocument.cleanup( - /* keepLoadedFonts = */ this.pdfViewer.renderer === RendererType.SVG - ); - } else { - this.pdfDocument.cleanup(); - } + this.pdfDocument.cleanup(); }, forceRendering() { diff --git a/web/app_options.js b/web/app_options.js index 36768194a..ce0f96a85 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -315,11 +315,6 @@ if ( value: navigator.language || "en-US", kind: OptionKind.VIEWER, }; - defaultOptions.renderer = { - /** @type {string} */ - value: "canvas", - kind: OptionKind.VIEWER + OptionKind.PREFERENCE, - }; defaultOptions.sandboxBundleSrc = { /** @type {string} */ value: diff --git a/web/interfaces.js b/web/interfaces.js index 0b6294cb0..579a1f55e 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -146,7 +146,7 @@ class IRenderableView { /** * @returns {Promise} Resolved on draw completion. */ - draw() {} + async draw() {} } /** diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 664669a38..e0b0d5632 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -26,18 +26,15 @@ import { AbortException, AnnotationMode, - createPromiseCapability, PixelsPerInch, RenderingCancelledException, setLayerDimensions, shadow, - SVGGraphics, } from "pdfjs-lib"; import { approximateFraction, DEFAULT_SCALE, OutputScale, - RendererType, RenderingStates, roundToDivide, TextLayerMode, @@ -121,6 +118,8 @@ class PDFPageView { #previousRotation = null; + #renderError = null; + #renderingState = RenderingStates.INITIAL; #useThumbnailCanvas = { @@ -128,6 +127,8 @@ class PDFPageView { regularAnnotations: true, }; + #viewportMap = new WeakMap(); + /** * @param {PDFPageViewOptions} options */ @@ -160,18 +161,10 @@ class PDFPageView { this.eventBus = options.eventBus; this.renderingQueue = options.renderingQueue; - if ( - typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || GENERIC") - ) { - this.renderer = options.renderer || RendererType.CANVAS; - } this.l10n = options.l10n || NullL10n; - this.paintTask = null; - this.paintedViewportMap = new WeakMap(); + this.renderTask = null; this.resume = null; - this._renderError = null; if ( typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || GENERIC") @@ -438,7 +431,7 @@ class PDFPageView { return; } const zoomLayerCanvas = this.zoomLayer.firstChild; - this.paintedViewportMap.delete(zoomLayerCanvas); + this.#viewportMap.delete(zoomLayerCanvas); // Zeroing the width and height causes Firefox to release graphics // resources immediately, which can greatly reduce memory consumption. zoomLayerCanvas.width = 0; @@ -510,7 +503,7 @@ class PDFPageView { if (!zoomLayerNode) { if (this.canvas) { - this.paintedViewportMap.delete(this.canvas); + this.#viewportMap.delete(this.canvas); // Zeroing the width and height causes Firefox to release graphics // resources immediately, which can greatly reduce memory consumption. this.canvas.width = 0; @@ -519,14 +512,6 @@ class PDFPageView { } this._resetZoomLayer(); } - if ( - (typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || GENERIC")) && - this.svg - ) { - this.paintedViewportMap.delete(this.svg); - delete this.svg; - } } update({ @@ -573,35 +558,12 @@ class PDFPageView { ); } - if ( - (typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || GENERIC")) && - this.svg - ) { - this.cssTransform({ - target: this.svg, - redrawAnnotationLayer: true, - redrawAnnotationEditorLayer: true, - redrawXfaLayer: true, - redrawTextLayer: true, - }); - - this.eventBus.dispatch("pagerendered", { - source: this, - pageNumber: this.id, - cssTransform: true, - timestamp: performance.now(), - error: this._renderError, - }); - return; - } - let isScalingRestricted = false; if (this.canvas && this.maxCanvasPixels > 0) { - const outputScale = this.outputScale; + const { width, height } = this.viewport; + const { sx, sy } = this.outputScale; if ( - ((Math.floor(this.viewport.width) * outputScale.sx) | 0) * - ((Math.floor(this.viewport.height) * outputScale.sy) | 0) > + ((Math.floor(width) * sx) | 0) * ((Math.floor(height) * sy) | 0) > this.maxCanvasPixels ) { isScalingRestricted = true; @@ -647,7 +609,7 @@ class PDFPageView { pageNumber: this.id, cssTransform: true, timestamp: performance.now(), - error: this._renderError, + error: this.#renderError, }); return; } @@ -679,9 +641,9 @@ class PDFPageView { keepTextLayer = false, cancelExtraDelay = 0, } = {}) { - if (this.paintTask) { - this.paintTask.cancel(cancelExtraDelay); - this.paintTask = null; + if (this.renderTask) { + this.renderTask.cancel(cancelExtraDelay); + this.renderTask = null; } this.resume = null; @@ -722,29 +684,21 @@ class PDFPageView { redrawTextLayer = false, hideTextLayer = false, }) { - // Scale target (canvas or svg), its wrapper and page container. - - if (target instanceof HTMLCanvasElement) { - if (!target.hasAttribute("zooming")) { - target.setAttribute("zooming", true); - const { style } = target; - style.width = style.height = ""; - } - } else { - const div = this.div; - const { width, height } = this.viewport; - - target.style.width = - target.parentNode.style.width = - div.style.width = - Math.floor(width) + "px"; - target.style.height = - target.parentNode.style.height = - div.style.height = - Math.floor(height) + "px"; + // Scale target (canvas), its wrapper and page container. + if ( + (typeof PDFJSDev === "undefined" || + PDFJSDev.test("!PRODUCTION || TESTING")) && + !(target instanceof HTMLCanvasElement) + ) { + throw new Error("Expected `target` to be a canvas."); + } + if (!target.hasAttribute("zooming")) { + target.setAttribute("zooming", true); + const { style } = target; + style.width = style.height = ""; } - const originalViewport = this.paintedViewportMap.get(target); + const originalViewport = this.#viewportMap.get(target); if (this.viewport !== originalViewport) { // The canvas may have been originally rotated; rotate relative to that. const relativeRotation = @@ -793,16 +747,50 @@ class PDFPageView { return this.viewport.convertToPdfPoint(x, y); } - draw() { + async #finishRenderTask(renderTask, error = null) { + // The renderTask may have been replaced by a new one, so only remove + // the reference to the renderTask if it matches the one that is + // triggering this callback. + if (renderTask === this.renderTask) { + this.renderTask = null; + } + + if (error instanceof RenderingCancelledException) { + this.#renderError = null; + return; + } + this.#renderError = error; + + this.renderingState = RenderingStates.FINISHED; + this._resetZoomLayer(/* removeFromDOM = */ true); + + // Ensure that the thumbnails won't become partially (or fully) blank, + // for documents that contain interactive form elements. + this.#useThumbnailCanvas.regularAnnotations = !renderTask.separateAnnots; + + this.eventBus.dispatch("pagerendered", { + source: this, + pageNumber: this.id, + cssTransform: false, + timestamp: performance.now(), + error: this.#renderError, + }); + + if (error) { + throw error; + } + } + + async draw() { if (this.renderingState !== RenderingStates.INITIAL) { console.error("Must be in new state before drawing"); this.reset(); // Ensure that we reset all state to prevent issues. } - const { div, pdfPage } = this; + const { div, l10n, pageColors, pdfPage, viewport } = this; if (!pdfPage) { this.renderingState = RenderingStates.FINISHED; - return Promise.reject(new Error("pdfPage is not loaded")); + throw new Error("pdfPage is not loaded"); } this.renderingState = RenderingStates.RUNNING; @@ -850,7 +838,7 @@ class PDFPageView { renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS, linkService, downloadManager, - l10n: this.l10n, + l10n, enableScripting, hasJSActionsPromise, fieldObjectsPromise, @@ -859,92 +847,127 @@ class PDFPageView { }); } - let renderContinueCallback = null; - if (this.renderingQueue) { - renderContinueCallback = cont => { - if (!this.renderingQueue.isHighestPriority(this)) { - this.renderingState = RenderingStates.PAUSED; - this.resume = () => { - this.renderingState = RenderingStates.RUNNING; - cont(); - }; - return; - } - cont(); - }; - } - - const finishPaintTask = async (error = null) => { - // The paintTask may have been replaced by a new one, so only remove - // the reference to the paintTask if it matches the one that is - // triggering this callback. - if (paintTask === this.paintTask) { - this.paintTask = null; - } - - if (error instanceof RenderingCancelledException) { - this._renderError = null; + const renderContinueCallback = cont => { + showCanvas?.(false); + if (this.renderingQueue && !this.renderingQueue.isHighestPriority(this)) { + this.renderingState = RenderingStates.PAUSED; + this.resume = () => { + this.renderingState = RenderingStates.RUNNING; + cont(); + }; return; } - this._renderError = error; - - this.renderingState = RenderingStates.FINISHED; - this._resetZoomLayer(/* removeFromDOM = */ true); - - // Ensure that the thumbnails won't become partially (or fully) blank, - // for documents that contain interactive form elements. - this.#useThumbnailCanvas.regularAnnotations = !paintTask.separateAnnots; - - this.eventBus.dispatch("pagerendered", { - source: this, - pageNumber: this.id, - cssTransform: false, - timestamp: performance.now(), - error: this._renderError, - }); - - if (error) { - throw error; - } + cont(); }; - const paintTask = - (typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || GENERIC")) && - this.renderer === RendererType.SVG - ? this.paintOnSvg(canvasWrapper) - : this.paintOnCanvas(canvasWrapper); - paintTask.onRenderContinue = renderContinueCallback; - this.paintTask = paintTask; + const { width, height } = viewport; + const canvas = document.createElement("canvas"); + canvas.setAttribute("role", "presentation"); - const resultPromise = paintTask.promise.then( - () => { - return finishPaintTask(null).then(async () => { - this.#renderTextLayer(); + // Keep the canvas hidden until the first draw callback, or until drawing + // is complete when `!this.renderingQueue`, to prevent black flickering. + canvas.hidden = true; + const hasHCM = !!(pageColors?.background && pageColors?.foreground); - if (this.annotationLayer) { - await this.#renderAnnotationLayer(); + let showCanvas = isLastShow => { + // In HCM, a final filter is applied on the canvas which means that + // before it's applied we've normal colors. Consequently, to avoid to have + // a final flash we just display it once all the drawing is done. + if (!hasHCM || isLastShow) { + canvas.hidden = false; + showCanvas = null; // Only invoke the function once. + } + }; + canvasWrapper.append(canvas); + this.canvas = canvas; + + const ctx = canvas.getContext("2d", { alpha: false }); + const outputScale = (this.outputScale = new OutputScale()); + + if (this.useOnlyCssZoom) { + const actualSizeViewport = viewport.clone({ + scale: PixelsPerInch.PDF_TO_CSS_UNITS, + }); + // Use a scale that makes the canvas have the originally intended size + // of the page. + outputScale.sx *= actualSizeViewport.width / width; + outputScale.sy *= actualSizeViewport.height / height; + } + + if (this.maxCanvasPixels > 0) { + const pixelsInViewport = width * height; + const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport); + if (outputScale.sx > maxScale || outputScale.sy > maxScale) { + outputScale.sx = maxScale; + outputScale.sy = maxScale; + this.hasRestrictedScaling = true; + } else { + this.hasRestrictedScaling = false; + } + } + const sfx = approximateFraction(outputScale.sx); + const sfy = approximateFraction(outputScale.sy); + + canvas.width = roundToDivide(width * outputScale.sx, sfx[0]); + canvas.height = roundToDivide(height * outputScale.sy, sfy[0]); + const { style } = canvas; + style.width = roundToDivide(width, sfx[1]) + "px"; + style.height = roundToDivide(height, sfy[1]) + "px"; + + // Add the viewport so it's known what it was originally drawn with. + this.#viewportMap.set(canvas, viewport); + + // Rendering area + const transform = outputScale.scaled + ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] + : null; + const renderContext = { + canvasContext: ctx, + transform, + viewport, + annotationMode: this.#annotationMode, + optionalContentConfigPromise: this._optionalContentConfigPromise, + annotationCanvasMap: this._annotationCanvasMap, + pageColors, + }; + const renderTask = (this.renderTask = this.pdfPage.render(renderContext)); + renderTask.onContinue = renderContinueCallback; + + const resultPromise = renderTask.promise.then( + async () => { + showCanvas?.(true); + await this.#finishRenderTask(renderTask); + + this.#renderTextLayer(); + + if (this.annotationLayer) { + await this.#renderAnnotationLayer(); + } + + if (!this.annotationEditorLayer) { + const { annotationEditorUIManager } = this.#layerProperties(); + + if (!annotationEditorUIManager) { + return; } - - if (!this.annotationEditorLayer) { - const { annotationEditorUIManager } = this.#layerProperties(); - - if (!annotationEditorUIManager) { - return; - } - this.annotationEditorLayer = new AnnotationEditorLayerBuilder({ - uiManager: annotationEditorUIManager, - pageDiv: div, - pdfPage, - l10n: this.l10n, - accessibilityManager: this._accessibilityManager, - }); - } - this.#renderAnnotationEditorLayer(); - }); + this.annotationEditorLayer = new AnnotationEditorLayerBuilder({ + uiManager: annotationEditorUIManager, + pageDiv: div, + pdfPage, + l10n, + accessibilityManager: this._accessibilityManager, + }); + } + this.#renderAnnotationEditorLayer(); }, - function (reason) { - return finishPaintTask(reason); + error => { + // When zooming with a `drawingDelay` set, avoid temporarily showing + // a black canvas if rendering was cancelled before the `onContinue`- + // callback had been invoked at least once. + if (!(error instanceof RenderingCancelledException)) { + showCanvas?.(true); + } + return this.#finishRenderTask(renderTask, error); } ); @@ -974,180 +997,6 @@ class PDFPageView { return resultPromise; } - paintOnCanvas(canvasWrapper) { - const renderCapability = createPromiseCapability(); - const result = { - promise: renderCapability.promise, - onRenderContinue(cont) { - cont(); - }, - cancel(extraDelay = 0) { - renderTask.cancel(extraDelay); - }, - get separateAnnots() { - return renderTask.separateAnnots; - }, - }; - - const viewport = this.viewport; - const { width, height } = viewport; - const canvas = document.createElement("canvas"); - canvas.setAttribute("role", "presentation"); - - // Keep the canvas hidden until the first draw callback, or until drawing - // is complete when `!this.renderingQueue`, to prevent black flickering. - canvas.hidden = true; - let isCanvasHidden = true; - const hasHCM = !!( - this.pageColors?.background && this.pageColors?.foreground - ); - const showCanvas = function (isLastShow) { - // In HCM, a final filter is applied on the canvas which means that - // before it's applied we've normal colors. Consequently, to avoid to have - // a final flash we just display it once all the drawing is done. - if (isCanvasHidden && (!hasHCM || isLastShow)) { - canvas.hidden = false; - isCanvasHidden = false; - } - }; - - canvasWrapper.append(canvas); - this.canvas = canvas; - - const ctx = canvas.getContext("2d", { alpha: false }); - const outputScale = (this.outputScale = new OutputScale()); - - if (this.useOnlyCssZoom) { - const actualSizeViewport = viewport.clone({ - scale: PixelsPerInch.PDF_TO_CSS_UNITS, - }); - // Use a scale that makes the canvas have the originally intended size - // of the page. - outputScale.sx *= actualSizeViewport.width / width; - outputScale.sy *= actualSizeViewport.height / height; - } - - if (this.maxCanvasPixels > 0) { - const pixelsInViewport = width * height; - const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport); - if (outputScale.sx > maxScale || outputScale.sy > maxScale) { - outputScale.sx = maxScale; - outputScale.sy = maxScale; - this.hasRestrictedScaling = true; - } else { - this.hasRestrictedScaling = false; - } - } - - const sfx = approximateFraction(outputScale.sx); - const sfy = approximateFraction(outputScale.sy); - - canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]); - canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]); - const { style } = canvas; - style.width = roundToDivide(viewport.width, sfx[1]) + "px"; - style.height = roundToDivide(viewport.height, sfy[1]) + "px"; - - // Add the viewport so it's known what it was originally drawn with. - this.paintedViewportMap.set(canvas, viewport); - - // Rendering area - const transform = outputScale.scaled - ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] - : null; - const renderContext = { - canvasContext: ctx, - transform, - viewport, - annotationMode: this.#annotationMode, - optionalContentConfigPromise: this._optionalContentConfigPromise, - annotationCanvasMap: this._annotationCanvasMap, - pageColors: this.pageColors, - }; - const renderTask = this.pdfPage.render(renderContext); - renderTask.onContinue = function (cont) { - showCanvas(false); - if (result.onRenderContinue) { - result.onRenderContinue(cont); - } else { - cont(); - } - }; - - renderTask.promise.then( - function () { - showCanvas(true); - renderCapability.resolve(); - }, - function (error) { - // When zooming with a `drawingDelay` set, avoid temporarily showing - // a black canvas if rendering was cancelled before the `onContinue`- - // callback had been invoked at least once. - if (!(error instanceof RenderingCancelledException)) { - showCanvas(true); - } - renderCapability.reject(error); - } - ); - return result; - } - - paintOnSvg(wrapper) { - if ( - !( - typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || GENERIC") - ) - ) { - throw new Error("Not implemented: paintOnSvg"); - } - let cancelled = false; - const ensureNotCancelled = () => { - if (cancelled) { - throw new RenderingCancelledException( - `Rendering cancelled, page ${this.id}`, - "svg" - ); - } - }; - - const pdfPage = this.pdfPage; - const actualSizeViewport = this.viewport.clone({ - scale: PixelsPerInch.PDF_TO_CSS_UNITS, - }); - const promise = pdfPage - .getOperatorList({ - annotationMode: this.#annotationMode, - }) - .then(opList => { - ensureNotCancelled(); - const svgGfx = new SVGGraphics(pdfPage.commonObjs, pdfPage.objs); - return svgGfx.getSVG(opList, actualSizeViewport).then(svg => { - ensureNotCancelled(); - this.svg = svg; - this.paintedViewportMap.set(svg, actualSizeViewport); - - svg.style.width = wrapper.style.width; - svg.style.height = wrapper.style.height; - this.renderingState = RenderingStates.FINISHED; - wrapper.append(svg); - }); - }); - - return { - promise, - onRenderContinue(cont) { - cont(); - }, - cancel() { - cancelled = true; - }, - get separateAnnots() { - return false; - }, - }; - } - /** * @param {string|null} label */ diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index c990cc467..4fbb4c536 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -258,39 +258,39 @@ class PDFThumbnailView { reducedCanvas.height = 0; } - draw() { + async #finishRenderTask(renderTask, canvas, error = null) { + // The renderTask may have been replaced by a new one, so only remove + // the reference to the renderTask if it matches the one that is + // triggering this callback. + if (renderTask === this.renderTask) { + this.renderTask = null; + } + + if (error instanceof RenderingCancelledException) { + return; + } + this.renderingState = RenderingStates.FINISHED; + this._convertCanvasToImage(canvas); + + if (error) { + throw error; + } + } + + async draw() { if (this.renderingState !== RenderingStates.INITIAL) { console.error("Must be in new state before drawing"); - return Promise.resolve(); + return undefined; } const { pdfPage } = this; if (!pdfPage) { this.renderingState = RenderingStates.FINISHED; - return Promise.reject(new Error("pdfPage is not loaded")); + throw new Error("pdfPage is not loaded"); } this.renderingState = RenderingStates.RUNNING; - const finishRenderTask = async (error = null) => { - // The renderTask may have been replaced by a new one, so only remove - // the reference to the renderTask if it matches the one that is - // triggering this callback. - if (renderTask === this.renderTask) { - this.renderTask = null; - } - - if (error instanceof RenderingCancelledException) { - return; - } - this.renderingState = RenderingStates.FINISHED; - this._convertCanvasToImage(canvas); - - if (error) { - throw error; - } - }; - // Render the thumbnail at a larger size and downsize the canvas (similar // to `setImage`), to improve consistency between thumbnails created by // the `draw` and `setImage` methods (fixes issue 8233). @@ -324,12 +324,8 @@ class PDFThumbnailView { renderTask.onContinue = renderContinueCallback; const resultPromise = renderTask.promise.then( - function () { - return finishRenderTask(null); - }, - function (error) { - return finishRenderTask(error); - } + () => this.#finishRenderTask(renderTask, canvas), + error => this.#finishRenderTask(renderTask, canvas, error) ); resultPromise.finally(() => { // Zeroing the width and height causes Firefox to release graphics diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 6649a3143..d5deed058 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -47,7 +47,6 @@ import { MAX_SCALE, MIN_SCALE, PresentationModeState, - RendererType, RenderingStates, SCROLLBAR_PADDING, scrollIntoView, @@ -266,7 +265,6 @@ class PDFViewer { PDFJSDev.test("!PRODUCTION || GENERIC") ) { this.removePageBorders = options.removePageBorders || false; - this.renderer = options.renderer || RendererType.CANVAS; } this.useOnlyCssZoom = options.useOnlyCssZoom || false; this.isOffscreenCanvasSupported = @@ -775,11 +773,6 @@ class PDFViewer { textLayerMode, annotationMode, imageResourcesPath: this.imageResourcesPath, - renderer: - typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || GENERIC") - ? this.renderer - : null, useOnlyCssZoom: this.useOnlyCssZoom, isOffscreenCanvasSupported: this.isOffscreenCanvasSupported, maxCanvasPixels: this.maxCanvasPixels, diff --git a/web/ui_utils.js b/web/ui_utils.js index 58b12d688..d1f67433e 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -46,14 +46,6 @@ const SidebarView = { LAYERS: 4, }; -const RendererType = - typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || GENERIC") - ? { - CANVAS: "canvas", - SVG: "svg", - } - : null; - const TextLayerMode = { DISABLE: 0, ENABLE: 1, @@ -880,7 +872,6 @@ export { PresentationModeState, ProgressBar, removeNullCharacters, - RendererType, RenderingStates, roundToDivide, SCROLLBAR_PADDING,