Skip to content

Commit

Permalink
fix(a11y): screen reader support for view only file preview
Browse files Browse the repository at this point in the history
  • Loading branch information
arturfrombox committed Aug 1, 2023
1 parent 58f278f commit 15bd081
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 11 deletions.
14 changes: 12 additions & 2 deletions src/lib/viewers/doc/DocBaseViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ class DocBaseViewer extends BaseViewer {

this.viewerEl = this.docEl.appendChild(document.createElement('div'));
this.viewerEl.classList.add('pdfViewer');
// Text layer should be rendered for a11y reasons thats why we will block user from selecting content when no download permissions was granted
const isViewOnly = !checkPermission(this.options.file, PERMISSION_DOWNLOAD);
if (isViewOnly) {
this.viewerEl.classList.add('viewOnly');
}

this.loadTimeout = LOAD_TIMEOUT_MS;

this.startPageNum = this.getStartPage(this.startAt);
Expand Down Expand Up @@ -795,7 +801,9 @@ class DocBaseViewer extends BaseViewer {
const { AnnotationMode: PDFAnnotationMode = {} } = this.pdfjsLib;
const assetUrlCreator = createAssetUrlCreator(location);
const hasDownload = checkPermission(file, PERMISSION_DOWNLOAD);
const hasTextLayer = hasDownload && !this.getViewerOption('disableTextLayer');
const enabledTextLayerMode = hasDownload
? PDFJS_TEXT_LAYER_MODE.ENABLE
: PDFJS_TEXT_LAYER_MODE.ENABLE_PERMISSIONS; // This mode will prevent default behavior for copy events in the TextLayerBuilder

return new PdfViewerClass({
annotationMode: PDFAnnotationMode.ENABLE, // Show annotations, but not forms
Expand All @@ -806,7 +814,9 @@ class DocBaseViewer extends BaseViewer {
linkService: this.pdfLinkService,
maxCanvasPixels: this.isMobile ? MOBILE_MAX_CANVAS_SIZE : -1,
renderInteractiveForms: false, // Enabling prevents unverified signatures from being displayed
textLayerMode: hasTextLayer ? PDFJS_TEXT_LAYER_MODE.ENABLE : PDFJS_TEXT_LAYER_MODE.DISABLE,
textLayerMode: this.getViewerOption('disableTextLayer')
? PDFJS_TEXT_LAYER_MODE.DISABLE
: enabledTextLayerMode,
});
}

Expand Down
49 changes: 40 additions & 9 deletions src/lib/viewers/doc/__tests__/DocBaseViewer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,35 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => {
docBase.setup();
expect(docBase.pageTracker).toBeInstanceOf(PageTracker);
});
test('should add view only class if user does not have download permissions', () => {
docBase = new DocBaseViewer({
file: {
id: '0',
},
});
docBase.containerEl = containerEl;

jest.spyOn(file, 'checkPermission').mockReturnValue(false);
docBase.setup();

expect(file.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD);
expect(docBase.viewerEl).toHaveClass('viewOnly');
});

test('should not add view only class if user has download permissions', () => {
docBase = new DocBaseViewer({
file: {
id: '0',
},
});
docBase.containerEl = containerEl;

jest.spyOn(file, 'checkPermission').mockReturnValue(true);
docBase.setup();

expect(file.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD);
expect(docBase.viewerEl).not.toHaveClass('viewOnly');
});
});

describe('Non setup methods', () => {
Expand Down Expand Up @@ -1140,43 +1169,45 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => {
});
});

test('should enable the text layer based on download permissions', () => {
test('should enable the text layer if the user is on mobile', () => {
docBase.isMobile = true;
stubs.checkPermission.mockReturnValueOnce(true);

return docBase.initViewer('').then(() => {
expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD);
expect(stubs.getViewerOption).toBeCalledWith('disableTextLayer');
// Text Layer mode 1 = enabled
expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 1 }));
});
});

test('should enable the text layer if the user is on mobile', () => {
docBase.isMobile = true;
test('should use proper text layer mode if user has download permissions and disableTextLayer viewer option is not set', () => {
stubs.getViewerOption.mockReturnValue(false);
stubs.checkPermission.mockReturnValueOnce(true);

return docBase.initViewer('').then(() => {
expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD);
expect(stubs.getViewerOption).toBeCalledWith('disableTextLayer');
// Text Layer mode 1 = enabled
expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 1 }));
});
});

test('should disable the text layer based on download permissions', () => {
test('should use proper text layer mode if user does not have download permissions and disableTextLayer viewer option is not set', () => {
stubs.getViewerOption.mockReturnValue(false);
stubs.checkPermission.mockReturnValueOnce(false);

return docBase.initViewer('').then(() => {
expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD);
// Text Layer mode 0 = disabled
expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 0 }));
expect(stubs.getViewerOption).toBeCalledWith('disableTextLayer');
// Text Layer mode 2 = enabled permissions
expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 2 }));
});
});

test('should disable the text layer if disableTextLayer viewer option is set', () => {
stubs.checkPermission.mockReturnValueOnce(true);
stubs.getViewerOption.mockReturnValue(true);

return docBase.initViewer('').then(() => {
expect(stubs.checkPermission).toBeCalledWith(docBase.options.file, PERMISSION_DOWNLOAD);
expect(stubs.getViewerOption).toBeCalledWith('disableTextLayer');
// Text Layer mode 0 = disabled
expect(stubs.pdfViewerClass).toBeCalledWith(expect.objectContaining({ textLayerMode: 0 }));
Expand Down
11 changes: 11 additions & 0 deletions src/lib/viewers/doc/_docBase.scss
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,17 @@ $thumbnail-sidebar-width: 191px; // Extra pixel to account for sidebar border
}
}

.pdfViewer {
&.viewOnly {
.textLayer {
span {
cursor: not-allowed;
user-select: none;
}
}
}
}

.textLayer {
caret-color: black; // Required for caret navigation on transparent text
top: $pdfjs-page-padding;
Expand Down

0 comments on commit 15bd081

Please sign in to comment.