Merge pull request #937 from mathuo/936-handle-browser-blocked-popups

936 handle browser blocked popups
This commit is contained in:
mathuo 2025-06-19 21:32:43 +01:00 committed by GitHub
commit 588fe9b28c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 16 deletions

View File

@ -5852,6 +5852,54 @@ describe('dockviewComponent', () => {
]); ]);
}); });
describe('when browsers block popups', () => {
let container: HTMLDivElement;
let dockview: DockviewComponent;
let panel: DockviewPanel;
beforeEach(() => {
jest.spyOn(window, 'open').mockReturnValue(null);
container = document.createElement('div');
dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
dockview.layout(1000, 500);
panel = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
});
test('onDidOpenPoputWindowFail event is emitted', async () => {
const onDidBlockPopoutHandler = jest.fn();
dockview.onDidOpenPopoutWindowFail(onDidBlockPopoutHandler);
await dockview.addPopoutGroup(panel.group);
expect(onDidBlockPopoutHandler).toHaveBeenCalledTimes(1);
});
test('popout group is restored to its original position', async () => {
await dockview.addPopoutGroup(panel.group);
expect(panel.group.api.location.type).toBe('grid');
});
});
test('dispose of dockview instance when popup is open', async () => { test('dispose of dockview instance when popup is open', async () => {
const container = document.createElement('div'); const container = document.createElement('div');

View File

@ -749,6 +749,10 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
return this.component.onDidPopoutGroupPositionChange; return this.component.onDidPopoutGroupPositionChange;
} }
get onDidOpenPopoutWindowFail(): Event<void> {
return this.component.onDidOpenPopoutWindowFail;
}
/** /**
* All panel objects. * All panel objects.
*/ */

View File

@ -227,6 +227,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly onDidMaximizedGroupChange: Event<DockviewMaximizedGroupChanged>; readonly onDidMaximizedGroupChange: Event<DockviewMaximizedGroupChanged>;
readonly onDidPopoutGroupSizeChange: Event<PopoutGroupChangeSizeEvent>; readonly onDidPopoutGroupSizeChange: Event<PopoutGroupChangeSizeEvent>;
readonly onDidPopoutGroupPositionChange: Event<PopoutGroupChangePositionEvent>; readonly onDidPopoutGroupPositionChange: Event<PopoutGroupChangePositionEvent>;
readonly onDidOpenPopoutWindowFail: Event<void>;
readonly options: DockviewComponentOptions; readonly options: DockviewComponentOptions;
updateOptions(options: DockviewOptions): void; updateOptions(options: DockviewOptions): void;
moveGroupOrPanel(options: MoveGroupOrPanelOptions): void; moveGroupOrPanel(options: MoveGroupOrPanelOptions): void;
@ -320,6 +321,10 @@ export class DockviewComponent
readonly onDidPopoutGroupPositionChange: Event<PopoutGroupChangePositionEvent> = readonly onDidPopoutGroupPositionChange: Event<PopoutGroupChangePositionEvent> =
this._onDidPopoutGroupPositionChange.event; this._onDidPopoutGroupPositionChange.event;
private readonly _onDidOpenPopoutWindowFail = new Emitter<void>();
readonly onDidOpenPopoutWindowFail: Event<void> =
this._onDidOpenPopoutWindowFail.event;
private readonly _onDidLayoutFromJSON = new Emitter<void>(); private readonly _onDidLayoutFromJSON = new Emitter<void>();
readonly onDidLayoutFromJSON: Event<void> = this._onDidLayoutFromJSON.event; readonly onDidLayoutFromJSON: Event<void> = this._onDidLayoutFromJSON.event;
@ -506,6 +511,7 @@ export class DockviewComponent
this._onDidOptionsChange, this._onDidOptionsChange,
this._onDidPopoutGroupSizeChange, this._onDidPopoutGroupSizeChange,
this._onDidPopoutGroupPositionChange, this._onDidPopoutGroupPositionChange,
this._onDidOpenPopoutWindowFail,
this.onDidViewVisibilityChangeMicroTaskQueue(() => { this.onDidViewVisibilityChangeMicroTaskQueue(() => {
this.updateWatermark(); this.updateWatermark();
}), }),
@ -714,19 +720,6 @@ export class DockviewComponent
return false; return false;
} }
if (popoutContainer === null) {
popoutWindowDisposable.dispose();
return false;
}
const gready = document.createElement('div');
gready.className = 'dv-overlay-render-container';
const overlayRenderContainer = new OverlayRenderContainer(
gready,
this
);
const referenceGroup = options?.referenceGroup const referenceGroup = options?.referenceGroup
? options.referenceGroup ? options.referenceGroup
: itemToPopout instanceof DockviewPanel : itemToPopout instanceof DockviewPanel
@ -736,7 +729,7 @@ export class DockviewComponent
const referenceLocation = itemToPopout.api.location.type; const referenceLocation = itemToPopout.api.location.type;
/** /**
* The group that is being added doesn't already exist within the DOM, the most likely occurance * The group that is being added doesn't already exist within the DOM, the most likely occurrence
* of this case is when being called from the `fromJSON(...)` method * of this case is when being called from the `fromJSON(...)` method
*/ */
const isGroupAddedToDom = const isGroupAddedToDom =
@ -750,9 +743,44 @@ export class DockviewComponent
group = options.overridePopoutGroup; group = options.overridePopoutGroup;
} else { } else {
group = this.createGroup({ id: groupId }); group = this.createGroup({ id: groupId });
this._onDidAddGroup.fire(group);
if (popoutContainer) {
this._onDidAddGroup.fire(group);
}
} }
if (popoutContainer === null) {
console.error(
'dockview: failed to create popout. perhaps you need to allow pop-ups for this website'
);
popoutWindowDisposable.dispose();
this._onDidOpenPopoutWindowFail.fire();
// if the popout window was blocked, we need to move the group back to the reference group
// and set it to visible
this.movingLock(() =>
moveGroupWithoutDestroying({
from: group,
to: referenceGroup,
})
);
if (!referenceGroup.api.isVisible) {
referenceGroup.api.setVisible(true);
}
return false;
}
const gready = document.createElement('div');
gready.className = 'dv-overlay-render-container';
const overlayRenderContainer = new OverlayRenderContainer(
gready,
this
);
group.model.renderContainer = overlayRenderContainer; group.model.renderContainer = overlayRenderContainer;
group.layout( group.layout(
_window.window!.innerWidth, _window.window!.innerWidth,
@ -968,7 +996,7 @@ export class DockviewComponent
return true; return true;
}) })
.catch((err) => { .catch((err) => {
console.error('dockview: failed to create popout window', err); console.error('dockview: failed to create popout.', err);
return false; return false;
}); });
} }

View File

@ -143,6 +143,9 @@ export const App = (props: { theme?: string }) => {
const load = (api: DockviewApi) => { const load = (api: DockviewApi) => {
api.clear(); api.clear();
api.onDidOpenPopoutWindowFail(() => {
console.log('Popout blocked');
});
if (layout) { if (layout) {
try { try {
api.fromJSON(layout); api.fromJSON(layout);