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._handledFrames.add(frame);
this._onFrameAdded(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) {
return new Promise(resolve => {
const iframeDocument = /** @type {Document} */ (iframe.contentDocument); const iframeDocument = /** @type {Document} */ (iframe.contentDocument);
if (iframeDocument.readyState === 'loading') { if (iframeDocument.readyState === 'loading') {
iframeDocument.addEventListener('DOMContentLoaded', () => { iframeDocument.addEventListener('DOMContentLoaded', () => {
callback(); resolve();
}); });
} else { } else {
callback(); 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);
});
const invalidFramePromise = new Promise(resolve => {
onDocumentReady(invalidFrame, () => {
assert( assert(
!invalidFrame.contentDocument.body.hasChildNodes(), !invalidFrame.contentDocument.body.hasChildNodes(),
'expected invalid frame to not be modified' 'expected invalid frame to not be modified'
); );
resolve();
});
}); });
return Promise.all([validFramePromise, invalidFramePromise]); it('detects removed frames', async () => {
});
it('detects removed frames', () => {
// 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 => { const scriptElement = frame.contentDocument.querySelector('script[src]');
onDocumentReady(frame, () => {
const scriptElement =
frame.contentDocument.querySelector('script[src]');
assert(scriptElement, 'expected embed script to be injected'); assert(scriptElement, 'expected embed script to be injected');
assert.equal( assert.equal(
scriptElement.src, scriptElement.src,
config.clientUrl, config.clientUrl,
'unexpected embed script source' '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 => {
onDocumentReady(frame, () => {
const scriptElement =
frame.contentDocument.querySelector('script[src]');
assert.isNull( assert.isNull(
scriptElement, frame.contentDocument.querySelector('script[src]'),
'expected embed script to not be injected' '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, () => {
assert(
frame.contentDocument.body.hasChildNodes(), frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified' '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);
return new Promise(resolve => {
// Yield to let the DOM and CrossFrame catch up
waitForFrameObserver(() => {
frame.remove(); frame.remove();
await waitForFrameObserver();
// Yield again
waitForFrameObserver(() => {
assert.calledWith(fakeBridge.call, 'destroyFrame'); 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);
return new Promise(resolve => { assert.isTrue(
onDocumentReady(frame, () => {
assert(
frame.contentDocument.body.hasChildNodes(), frame.contentDocument.body.hasChildNodes(),
'expected initial frame to be modified' 'expected initial frame to be modified'
); );
frame.remove(); frame.remove();
await waitForFrameObserver();
// Yield to let the DOM and CrossFrame catch up
waitForFrameObserver(() => {
// Add the frame again
container.appendChild(frame); container.appendChild(frame);
assert.isFalse(frame.contentDocument.body.hasChildNodes());
await waitForFrameObserver();
await onDocumentReady(frame);
// Yield again
waitForFrameObserver(() => {
onDocumentReady(frame, () => {
assert( assert(
frame.contentDocument.body.hasChildNodes(), frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified' '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
waitForFrameObserver(() => {
// Add the frame again
container.appendChild(frame); container.appendChild(frame);
assert.isFalse(frame.contentDocument.body.hasChildNodes());
// Yield await waitForFrameObserver();
waitForFrameObserver(() => { await onDocumentReady(frame);
onDocumentReady(frame, () => {
assert( assert.isTrue(
frame.contentDocument.body.hasChildNodes(), frame.contentDocument.body.hasChildNodes(),
'expected dynamically added frame to be modified' '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