mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-12098] firefox headless race condition viewer fix (#12023)
* [ACS-12098] firefox headless race condition viewer fix * [ACS-12098] review fixes 1 * [ACS-12098] review fixes 2 * [ACS-12098] review fixes 3 * last firefox headless fixes
This commit is contained in:
@@ -32,7 +32,8 @@ import {
|
||||
ViewerMoreActionsComponent,
|
||||
ViewerToolbarActionsComponent,
|
||||
NoopAuthModule,
|
||||
NoopTranslateModule
|
||||
NoopTranslateModule,
|
||||
UnitTestingUtils
|
||||
} from '@alfresco/adf-core';
|
||||
import { NodesApiService } from '../../common/services/nodes-api.service';
|
||||
import { UploadService } from '../../common/services/upload.service';
|
||||
@@ -177,6 +178,7 @@ describe('AlfrescoViewerComponent', () => {
|
||||
let renditionService: RenditionService;
|
||||
let viewUtilService: ViewUtilService;
|
||||
let nodeActionsService: NodeActionsService;
|
||||
let testingUtils: UnitTestingUtils;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -197,6 +199,7 @@ describe('AlfrescoViewerComponent', () => {
|
||||
fixture = TestBed.createComponent(AlfrescoViewerComponent);
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
||||
uploadService = TestBed.inject(UploadService);
|
||||
nodesApiService = TestBed.inject(NodesApiService);
|
||||
dialog = TestBed.inject(MatDialog);
|
||||
@@ -398,12 +401,16 @@ describe('AlfrescoViewerComponent', () => {
|
||||
component.nodeId = 'id1';
|
||||
component.showViewer = true;
|
||||
component.versionId = null;
|
||||
component.ngOnChanges(getSimpleChanges('id1'));
|
||||
|
||||
await fixture.whenStable();
|
||||
await fixture.ngZone.run(async () => {
|
||||
component.ngOnChanges(getSimpleChanges('id1'));
|
||||
await fixture.whenStable();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.fileName).toBe('file1.pdf');
|
||||
expect(component.blobFileContent).toBe(mockBlob);
|
||||
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||
expect(viewer.fileName).toBe('file1.pdf');
|
||||
expect(viewer.blobFile).toBe(mockBlob);
|
||||
});
|
||||
|
||||
it('should change display name every time node`s version changes', fakeAsync(() => {
|
||||
@@ -1063,4 +1070,127 @@ describe('AlfrescoViewerComponent', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PDF blob fetch and atomic state assignment', () => {
|
||||
it('should assign blobFileContent and null urlFileContent when PDF blob fetch succeeds', async () => {
|
||||
const mockBlob = new Blob(['pdf content'], { type: 'application/pdf' });
|
||||
const mockResponse = { ok: true, blob: () => Promise.resolve(mockBlob) } as Response;
|
||||
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse));
|
||||
|
||||
spyOn(component.nodesApi, 'getNode').and.returnValue(
|
||||
Promise.resolve(
|
||||
new NodeEntry({
|
||||
entry: new Node({ name: 'test.pdf', id: 'node-1', content: new ContentInfo({ mimeType: 'application/pdf' }) })
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
component.nodeId = 'node-1';
|
||||
component.showViewer = true;
|
||||
|
||||
await fixture.ngZone.run(async () => {
|
||||
component.ngOnChanges(getSimpleChanges('node-1'));
|
||||
await fixture.whenStable();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||
expect(viewer.blobFile).toBe(mockBlob);
|
||||
expect(viewer.urlFile).toBeFalsy();
|
||||
expect(viewer.mimeType).toBe('application/pdf');
|
||||
});
|
||||
|
||||
it('should fall back to URL-based viewing when PDF blob fetch fails', async () => {
|
||||
spyOn(window, 'fetch').and.returnValue(Promise.reject(new Error('Network error')));
|
||||
spyOn(component.contentApi, 'getContentUrl').and.returnValue('/content/url');
|
||||
|
||||
spyOn(component.nodesApi, 'getNode').and.returnValue(
|
||||
Promise.resolve(
|
||||
new NodeEntry({
|
||||
entry: new Node({
|
||||
name: 'test.pdf',
|
||||
id: 'node-1',
|
||||
content: new ContentInfo({ mimeType: 'application/pdf' }),
|
||||
properties: { 'cm:versionLabel': '1.0' }
|
||||
})
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
component.nodeId = 'node-1';
|
||||
component.showViewer = true;
|
||||
|
||||
await fixture.ngZone.run(async () => {
|
||||
component.ngOnChanges(getSimpleChanges('node-1'));
|
||||
await fixture.whenStable();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||
expect(viewer.blobFile).toBeFalsy();
|
||||
expect(viewer.urlFile).toBeTruthy();
|
||||
expect(viewer.mimeType).toBe('application/pdf');
|
||||
});
|
||||
|
||||
it('should fall back to URL-based viewing when PDF fetch returns non-ok response', async () => {
|
||||
const mockResponse = { ok: false, status: 403 } as Response;
|
||||
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse));
|
||||
spyOn(component.contentApi, 'getContentUrl').and.returnValue('/content/url');
|
||||
|
||||
spyOn(component.nodesApi, 'getNode').and.returnValue(
|
||||
Promise.resolve(
|
||||
new NodeEntry({
|
||||
entry: new Node({
|
||||
name: 'test.pdf',
|
||||
id: 'node-1',
|
||||
content: new ContentInfo({ mimeType: 'application/pdf' }),
|
||||
properties: { 'cm:versionLabel': '1.0' }
|
||||
})
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
component.nodeId = 'node-1';
|
||||
component.showViewer = true;
|
||||
|
||||
await fixture.ngZone.run(async () => {
|
||||
component.ngOnChanges(getSimpleChanges('node-1'));
|
||||
await fixture.whenStable();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||
expect(viewer.blobFile).toBeFalsy();
|
||||
expect(viewer.urlFile).toBeTruthy();
|
||||
expect(viewer.mimeType).toBe('application/pdf');
|
||||
});
|
||||
|
||||
it('should not expose intermediate null state during node setup', async () => {
|
||||
const mockBlob = new Blob(['pdf content'], { type: 'application/pdf' });
|
||||
const mockResponse = { ok: true, blob: () => Promise.resolve(mockBlob) } as Response;
|
||||
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse));
|
||||
|
||||
spyOn(component.nodesApi, 'getNode').and.returnValue(
|
||||
Promise.resolve(
|
||||
new NodeEntry({
|
||||
entry: new Node({ name: 'test.pdf', id: 'node-1', content: new ContentInfo({ mimeType: 'application/pdf' }) })
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
component.nodeId = 'node-1';
|
||||
component.showViewer = true;
|
||||
|
||||
await fixture.ngZone.run(async () => {
|
||||
component.ngOnChanges(getSimpleChanges('node-1'));
|
||||
await fixture.whenStable();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const viewer = testingUtils.getByDirective(ViewerComponent).componentInstance as ViewerComponent<unknown>;
|
||||
expect(viewer.blobFile).toBe(mockBlob);
|
||||
expect(viewer.urlFile).toBeFalsy();
|
||||
expect(viewer.fileName).toBe('test.pdf');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -275,9 +275,9 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
|
||||
private async onNodeUpdated(node: Node) {
|
||||
if (node && node.id === this.nodeId) {
|
||||
this.generateCacheBusterNumber();
|
||||
this.blobFileContent = null;
|
||||
|
||||
await this.setUpNodeFile(node);
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,6 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
|
||||
|
||||
private async setupNode() {
|
||||
try {
|
||||
this.blobFileContent = null;
|
||||
this.nodeEntry = await this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] });
|
||||
if (this.versionId) {
|
||||
this.versionEntry = await this.versionsApi.getVersion(this.nodeId, this.versionId);
|
||||
@@ -318,6 +317,7 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
|
||||
this.canEditNode = this.contentService.hasAllowableOperations(nodeData, 'update');
|
||||
let mimeType: string;
|
||||
let urlFileContent: string;
|
||||
let blobContent: Blob = null;
|
||||
|
||||
if (versionData?.content) {
|
||||
mimeType = versionData.content.mimeType;
|
||||
@@ -352,32 +352,24 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
|
||||
} else if (viewerType === 'media') {
|
||||
this.tracks = await this.renditionService.generateMediaTracksRendition(this.nodeId);
|
||||
} else if (viewerType === 'pdf') {
|
||||
try {
|
||||
const contentUrl = versionData
|
||||
? this.contentApi.getVersionContentUrl(this.nodeId, versionData.id)
|
||||
: this.contentApi.getContentUrl(this.nodeId);
|
||||
const contentUrl = versionData
|
||||
? this.contentApi.getVersionContentUrl(this.nodeId, versionData.id)
|
||||
: this.contentApi.getContentUrl(this.nodeId);
|
||||
|
||||
// Fetch the content as Blob using fetch API with credentials
|
||||
const response = await fetch(contentUrl, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache'
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const blob = await fetch(contentUrl, { credentials: 'include', headers: { 'Cache-Control': 'no-cache' } })
|
||||
.then((response) => (response.ok ? response.blob() : null))
|
||||
.catch(() => null);
|
||||
|
||||
this.blobFileContent = await response.blob();
|
||||
if (blob) {
|
||||
blobContent = blob;
|
||||
urlFileContent = null;
|
||||
} catch (error) {
|
||||
console.error('[ADF DEBUG] Failed to fetch PDF as blob, falling back to URL', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.mimeType = mimeType;
|
||||
this.nodeMimeType = nodeMimeType;
|
||||
this.fileName = versionData ? versionData.name : nodeData.name;
|
||||
this.blobFileContent = blobContent;
|
||||
this.urlFileContent = urlFileContent ? urlFileContent + (this.cacheBusterNumber ? '&' + this.cacheBusterNumber : '') : null;
|
||||
this.sidebarRightTemplateContext.node = nodeData;
|
||||
this.sidebarLeftTemplateContext.node = nodeData;
|
||||
|
||||
Reference in New Issue
Block a user