Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(split links): run in iframes in Chrome and Violentmonkey Beta #504

Merged
merged 1 commit into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/lib/util/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,21 @@ export function onDocumentLoaded(listener: () => void): void {
}
}

export function onWindowLoaded(listener: () => void, windowInstance: Window = window): void {
if (windowInstance.document.readyState === 'complete') {
export function onWindowLoaded(listener: () => void): void {
if (window.document.readyState === 'complete') {
listener();
} else {
windowInstance.addEventListener('load', listener);
window.addEventListener('load', listener);
}
}

export function onAddEntityDialogLoaded(dialog: HTMLIFrameElement, listener: () => void): void {
// iframe could already have finished loading. We can detect this as the
// absence of the loading div.
if (qsMaybe('.content-loading', dialog.parentElement!) === null) {
listener();
} else {
dialog.addEventListener('load', () => { listener(); });
}
}

Expand Down
15 changes: 5 additions & 10 deletions src/mb_multi_external_links/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { LOGGER } from '@lib/logging/logger';
import { assertDefined } from '@lib/util/assert';
import { logFailure, retryTimes } from '@lib/util/async';
import { createPersistentCheckbox } from '@lib/util/checkboxes';
import { onWindowLoaded, qsa, qsMaybe, setInputValue } from '@lib/util/dom';
import { onAddEntityDialogLoaded, qsa, qsMaybe, setInputValue } from '@lib/util/dom';

import DEBUG_MODE from 'consts:debug-mode';
import USERSCRIPT_ID from 'consts:userscript-id';
Expand Down Expand Up @@ -160,15 +160,10 @@ async function run(windowInstance: Window): Promise<void> {

function onIframeAdded(iframe: HTMLIFrameElement): void {
LOGGER.debug(`Initialising on iframe ${iframe.src}`);
const iframeWindow = iframe.contentWindow;
if (!iframeWindow) return;

// Cannot use onDocumentLoaded even if we make it accept a custom document
// since iframe contentDocument doesn't fire the DOMContentLoaded event in
// Firefox.
onWindowLoaded(() => {
logFailure(run(iframeWindow));
}, iframeWindow);

onAddEntityDialogLoaded(iframe, () => {
logFailure(run(iframe.contentWindow!));
});
}

// Observe for additions of embedded entity creation dialogs and run the link
Expand Down
81 changes: 26 additions & 55 deletions tests/unit/lib/util/dom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,61 +131,32 @@ describe('callback on window loaded', () => {
cb.mockReset();
});

function runTests(windowInstance?: Window): void {
it('does not fire if the window is not loaded', () => {
jest.spyOn(windowInstance?.document ?? document, 'readyState', 'get').mockReturnValue('interactive');
const cb = jest.fn();
onWindowLoaded(cb, windowInstance);

expect(cb).not.toHaveBeenCalled();
});

it('fires if the window was already loaded', () => {
jest.spyOn(windowInstance?.document ?? document, 'readyState', 'get').mockReturnValue('complete');
const cb = jest.fn();
onWindowLoaded(cb, windowInstance);

expect(cb).toHaveBeenCalledOnce();
});

it('fires after the window was loaded', () => {
jest.spyOn(windowInstance?.document ?? document, 'readyState', 'get').mockReturnValue('interactive');
const cb = jest.fn();
onWindowLoaded(cb, windowInstance);

expect(cb).not.toHaveBeenCalled();

(windowInstance ?? window).dispatchEvent(new Event('load'));

expect(cb).toHaveBeenCalledOnce();
});
}

describe('with main window', () => {
// eslint-disable-next-line jest/require-hook
runTests();
});

describe('with custom window', () => {
const customWindow = {
eventListener: null as (() => void) | null,
document: {
get readyState(): string {
return 'uninitialized';
},
},
addEventListener(evt: string, listener: () => void): void {
if (evt === 'load') this.eventListener = listener;
},
dispatchEvent(evt: Event): void {
if (evt.type === 'load') {
this.eventListener?.();
}
},
};

// eslint-disable-next-line jest/require-hook
runTests(customWindow as unknown as Window);
it('does not fire if the window is not loaded', () => {
jest.spyOn(document, 'readyState', 'get').mockReturnValue('interactive');
const cb = jest.fn();
onWindowLoaded(cb);

expect(cb).not.toHaveBeenCalled();
});

it('fires if the window was already loaded', () => {
jest.spyOn(document, 'readyState', 'get').mockReturnValue('complete');
const cb = jest.fn();
onWindowLoaded(cb);

expect(cb).toHaveBeenCalledOnce();
});

it('fires after the window was loaded', () => {
jest.spyOn(document, 'readyState', 'get').mockReturnValue('interactive');
const cb = jest.fn();
onWindowLoaded(cb);

expect(cb).not.toHaveBeenCalled();

window.dispatchEvent(new Event('load'));

expect(cb).toHaveBeenCalledOnce();
});
});

Expand Down