Commit 8658a2c7 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '328220-optimized-blob-view' into 'master'

Optimized blob view loading in repository

See merge request gitlab-org/gitlab!65859
parents 22b36201 d1e175f8
......@@ -6,6 +6,8 @@ import {
REPO_BLOB_LOAD_VIEWER_START,
REPO_BLOB_LOAD_VIEWER_FINISH,
REPO_BLOB_LOAD_VIEWER,
REPO_BLOB_SWITCH_TO_VIEWER_START,
REPO_BLOB_SWITCH_VIEWER,
} from '~/performance/constants';
import { performanceMarkAndMeasure } from '~/performance/utils';
import { fixTitle } from '~/tooltips';
......@@ -49,6 +51,9 @@ export const handleBlobRichViewer = (viewer, type) => {
export default class BlobViewer {
constructor() {
performanceMarkAndMeasure({
mark: REPO_BLOB_LOAD_VIEWER_START,
});
const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
const type = viewer?.dataset?.richType;
BlobViewer.initAuxiliaryViewer();
......@@ -141,7 +146,7 @@ export default class BlobViewer {
switchToViewer(name) {
performanceMarkAndMeasure({
mark: REPO_BLOB_LOAD_VIEWER_START,
mark: REPO_BLOB_SWITCH_TO_VIEWER_START,
});
const newViewer = this.$fileHolder[0].querySelector(`.blob-viewer[data-type='${name}']`);
if (this.activeViewer === newViewer) return;
......@@ -171,11 +176,15 @@ export default class BlobViewer {
BlobViewer.loadViewer(newViewer)
.then((viewer) => {
$(viewer).renderGFM();
window.requestIdleCallback(() => {
this.$fileHolder.trigger('highlight:line');
handleLocationHash();
viewer.setAttribute('data-loaded', 'true');
this.toggleCopyButtonState();
eventHub.$emit('showBlobInteractionZones', viewer.dataset.path);
});
performanceMarkAndMeasure({
mark: REPO_BLOB_LOAD_VIEWER_FINISH,
measures: [
......@@ -183,6 +192,10 @@ export default class BlobViewer {
name: REPO_BLOB_LOAD_VIEWER,
start: REPO_BLOB_LOAD_VIEWER_START,
},
{
name: REPO_BLOB_SWITCH_VIEWER,
start: REPO_BLOB_SWITCH_TO_VIEWER_START,
},
],
});
})
......@@ -205,9 +218,10 @@ export default class BlobViewer {
return axios.get(url).then(({ data }) => {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');
eventHub.$emit('showBlobInteractionZones', viewer.dataset.path);
window.requestIdleCallback(() => {
viewer.removeAttribute('data-loading');
});
return viewer;
});
......
......@@ -83,7 +83,9 @@ export const PIPELINES_DETAIL_LINKS_JOB_RATIO = 'pipeline_graph_links_per_job_ra
// Marks
export const REPO_BLOB_LOAD_VIEWER_START = 'blobviewer-load-viewer-start';
export const REPO_BLOB_SWITCH_TO_VIEWER_START = 'blobviewer-switch-to-viewerr-start';
export const REPO_BLOB_LOAD_VIEWER_FINISH = 'blobviewer-load-viewer-finish';
// Measures
export const REPO_BLOB_LOAD_VIEWER = 'Repository File Viewer: loading the content';
export const REPO_BLOB_LOAD_VIEWER = 'Repository File Viewer: loading the viewer';
export const REPO_BLOB_SWITCH_VIEWER = 'Repository File Viewer: switching the viewer';
......@@ -508,3 +508,25 @@ span.idiff {
}
}
}
//
// IMPORTANT PERFORMANCE OPTIMIZATION BELOW
//
// * :nth-of-type(1n+70) - makes sure we do not render lines 71+ right
// away. Even though the HTML is injected in the DOM, as long as we do
// not render those lines, the browser doesn't need to spend resources
// calculating and repainting what's hidden.
//
// * :not(:last-of-type) makes sure that we output the last line of the
// blob's snippet. This is important because the column with the line
// numbers has auto width and is expanding based on the content in it.
// This leads to unnecessary layout shift when the last lines of the
// snippet are longer than two (2) digits.
// EXAMPLE: Let's say, we have a blob with 100 lines. If we output 70
// lines, and then, the remaining 30 (incl the line 100), it will lead
// to the layout reflow and styles recalculation when we output line
// 100 (because the width of '100' is always bigger than '70'). By
// outputting the last line right away, we prevent that as the column
// will always be expanded to the maximum needed width.
.blob-viewer[data-loading] .file-content.code .line:nth-of-type(1n+70):not(:last-of-type),
.blob-viewer[data-loading] .file-content.code .file-line-num:nth-of-type(1n+70):not(:last-of-type) {display: none !important;}
......@@ -6,6 +6,10 @@ import { setTestTimeout } from 'helpers/timeout';
import BlobViewer from '~/blob/viewer/index';
import axios from '~/lib/utils/axios_utils';
const execImmediately = (callback) => {
callback();
};
describe('Blob viewer', () => {
let blob;
let mock;
......@@ -17,6 +21,7 @@ describe('Blob viewer', () => {
setTestTimeout(2000);
beforeEach(() => {
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
$.fn.extend(jQueryMock);
mock = new MockAdapter(axios);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment