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