Commit fde04c8b authored by Eduardo Sanz García's avatar Eduardo Sanz García Committed by Eduardo

Transform `onDocumentReady` on Promise-based function

`onDocumentReady` returns now a Promise instead of calling a callback.
parent 895077ca
......@@ -38,16 +38,15 @@ export default class FrameObserver {
/**
* @param {HTMLIFrameElement} frame
*/
_addFrame(frame) {
async _addFrame(frame) {
if (isAccessible(frame)) {
onDocumentReady(frame, () => {
const frameWindow = /** @type {Window} */ (frame.contentWindow);
frameWindow.addEventListener('unload', () => {
this._removeFrame(frame);
});
this._handledFrames.add(frame);
this._onFrameAdded(frame);
await onDocumentReady(frame);
const frameWindow = /** @type {Window} */ (frame.contentWindow);
frameWindow.addEventListener('unload', () => {
this._removeFrame(frame);
});
this._handledFrames.add(frame);
this._onFrameAdded(frame);
} else {
// Could warn here that frame was not cross origin accessible
}
......@@ -92,20 +91,22 @@ function isAccessible(iframe) {
}
/**
* Triggers a callback when the iframe's DOM is ready (loaded and parsed)
* Resolves a Promise when the iframe's DOM is ready (loaded and parsed)
*
* @param {HTMLIFrameElement} iframe
* @param {() => void} callback
* @return {Promise<void>}
*/
export function onDocumentReady(iframe, callback) {
const iframeDocument = /** @type {Document} */ (iframe.contentDocument);
if (iframeDocument.readyState === 'loading') {
iframeDocument.addEventListener('DOMContentLoaded', () => {
callback();
});
} else {
callback();
}
export function onDocumentReady(iframe) {
return new Promise(resolve => {
const iframeDocument = /** @type {Document} */ (iframe.contentDocument);
if (iframeDocument.readyState === 'loading') {
iframeDocument.addEventListener('DOMContentLoaded', () => {
resolve();
});
} else {
resolve();
}
});
}
/**
......
import { DEBOUNCE_WAIT } from '../../frame-observer';
import { DEBOUNCE_WAIT, onDocumentReady } from '../../frame-observer';
import { HypothesisInjector } from '../../hypothesis-injector';
import { onDocumentReady } from '../../frame-observer';
describe('HypothesisInjector integration test', () => {
let container;
......@@ -12,10 +11,8 @@ describe('HypothesisInjector integration test', () => {
clientUrl: 'data:,', // empty data uri
};
const waitForFrameObserver = cb => {
const wait = DEBOUNCE_WAIT + 10;
return setTimeout(cb, wait);
};
const waitForFrameObserver = () =>
new Promise(resolve => setTimeout(resolve, DEBOUNCE_WAIT + 10));
beforeEach(() => {
fakeBridge = {
......@@ -45,217 +42,162 @@ describe('HypothesisInjector integration test', () => {
return hypothesisInjector;
}
function createAnnotatableIFrame() {
function createAnnotatableIFrame(attribute = 'enable-annotation') {
const frame = document.createElement('iframe');
frame.setAttribute('enable-annotation', '');
frame.setAttribute(attribute, '');
container.appendChild(frame);
return frame;
}
it('detects frames on page', () => {
it('detects frames on page', async () => {
const validFrame = createAnnotatableIFrame();
// Create another that mimics the sidebar frame
// This one should should not be detected
const invalidFrame = document.createElement('iframe');
invalidFrame.className = 'h-sidebar-iframe';
container.appendChild(invalidFrame);
const invalidFrame = createAnnotatableIFrame('dummy-attribute');
// Now initialize
createHypothesisInjector();
const validFramePromise = new Promise(resolve => {
onDocumentReady(validFrame, () => {
assert(
validFrame.contentDocument.body.hasChildNodes(),
'expected valid frame to be modified'
);
resolve();
});
});
const invalidFramePromise = new Promise(resolve => {
onDocumentReady(invalidFrame, () => {
assert(
!invalidFrame.contentDocument.body.hasChildNodes(),
'expected invalid frame to not be modified'
);
resolve();
});
});
return Promise.all([validFramePromise, invalidFramePromise]);
await onDocumentReady(validFrame);
assert(
validFrame.contentDocument.body.hasChildNodes(),
'expected valid frame to be modified'
);
await onDocumentReady(invalidFrame);
assert(
!invalidFrame.contentDocument.body.hasChildNodes(),
'expected invalid frame to not be modified'
);
});
it('detects removed frames', () => {
it('detects removed frames', async () => {
// Create a frame before initializing
const frame = createAnnotatableIFrame();
// Now initialize
createHypothesisInjector();
await onDocumentReady(frame);
// Remove the frame
frame.remove();
await waitForFrameObserver();
assert.calledWith(fakeBridge.call, 'destroyFrame');
});
it('injects embed script in frame', () => {
it('injects embed script in frame', async () => {
const frame = createAnnotatableIFrame();
createHypothesisInjector();
return new Promise(resolve => {
onDocumentReady(frame, () => {
const scriptElement =
frame.contentDocument.querySelector('script[src]');
assert(scriptElement, 'expected embed script to be injected');
assert.equal(
scriptElement.src,
config.clientUrl,
'unexpected embed script source'
);
resolve();
});
});
await onDocumentReady(frame);
const scriptElement = frame.contentDocument.querySelector('script[src]');
assert(scriptElement, 'expected embed script to be injected');
assert.equal(
scriptElement.src,
config.clientUrl,
'unexpected embed script source'
);
});
it('excludes injection from already injected frames', () => {
const frame = document.createElement('iframe');
frame.setAttribute('enable-annotation', '');
container.appendChild(frame);
it('excludes injection from already injected frames', async () => {
const frame = createAnnotatableIFrame();
frame.contentWindow.eval('window.__hypothesis = {}');
createHypothesisInjector();
await onDocumentReady(frame);
return new Promise(resolve => {
onDocumentReady(frame, () => {
const scriptElement =
frame.contentDocument.querySelector('script[src]');
assert.isNull(
scriptElement,
'expected embed script to not be injected'
);
resolve();
});
});
assert.isNull(
frame.contentDocument.querySelector('script[src]'),
'expected embed script to not be injected'
);
});
it('detects dynamically added frames', () => {
it('detects dynamically added frames', async () => {
// Initialize with no initial frame, unlike before
createHypothesisInjector();
// Add a frame to the DOM
const frame = createAnnotatableIFrame();
return new Promise(resolve => {
// Yield to let the DOM and CrossFrame catch up
waitForFrameObserver(() => {
onDocumentReady(frame, () => {
assert(
frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified'
);
resolve();
});
});
});
await waitForFrameObserver();
await onDocumentReady(frame);
assert.isTrue(
frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified'
);
});
it('detects dynamically removed frames', () => {
it('detects dynamically removed frames', async () => {
// Create a frame before initializing
const frame = document.createElement('iframe');
frame.setAttribute('enable-annotation', '');
container.appendChild(frame);
const frame = createAnnotatableIFrame();
// Now initialize
createHypothesisInjector();
await waitForFrameObserver();
await onDocumentReady(frame);
frame.remove();
await waitForFrameObserver();
return new Promise(resolve => {
// Yield to let the DOM and CrossFrame catch up
waitForFrameObserver(() => {
frame.remove();
// Yield again
waitForFrameObserver(() => {
assert.calledWith(fakeBridge.call, 'destroyFrame');
resolve();
});
});
});
assert.calledWith(fakeBridge.call, 'destroyFrame');
});
it('detects a frame dynamically removed, and added again', () => {
// Create a frame before initializing
it('detects a frame dynamically removed, and added again', async () => {
const frame = createAnnotatableIFrame();
// Now initialize
createHypothesisInjector();
await onDocumentReady(frame);
assert.isTrue(
frame.contentDocument.body.hasChildNodes(),
'expected initial frame to be modified'
);
frame.remove();
await waitForFrameObserver();
return new Promise(resolve => {
onDocumentReady(frame, () => {
assert(
frame.contentDocument.body.hasChildNodes(),
'expected initial frame to be modified'
);
frame.remove();
// Yield to let the DOM and CrossFrame catch up
waitForFrameObserver(() => {
// Add the frame again
container.appendChild(frame);
// Yield again
waitForFrameObserver(() => {
onDocumentReady(frame, () => {
assert(
frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified'
);
resolve();
});
});
});
});
});
container.appendChild(frame);
assert.isFalse(frame.contentDocument.body.hasChildNodes());
await waitForFrameObserver();
await onDocumentReady(frame);
assert(
frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified'
);
});
it('detects a frame dynamically added, removed, and added again', () => {
it('detects a frame dynamically added, removed, and added again', async () => {
// Initialize with no initial frame
createHypothesisInjector();
// Add a frame to the DOM
const frame = createAnnotatableIFrame();
return new Promise(resolve => {
// Yield to let the DOM and CrossFrame catch up
waitForFrameObserver(() => {
onDocumentReady(frame, () => {
assert(
frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified'
);
frame.remove();
// Yield again
waitForFrameObserver(() => {
// Add the frame again
container.appendChild(frame);
// Yield
waitForFrameObserver(() => {
onDocumentReady(frame, () => {
assert(
frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified'
);
resolve();
});
});
});
});
});
});
await waitForFrameObserver();
await onDocumentReady(frame);
assert.isTrue(
frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified'
);
frame.remove();
await waitForFrameObserver();
container.appendChild(frame);
assert.isFalse(frame.contentDocument.body.hasChildNodes());
await waitForFrameObserver();
await onDocumentReady(frame);
assert.isTrue(
frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified'
);
});
});
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