mirror of
https://github.com/microsoft/playwright.git
synced 2024-11-24 06:49:04 +03:00
feature(evaluate): support cross-context element handles (#295)
This commit is contained in:
parent
4ef9f84ab5
commit
b378bc7f6c
@ -426,7 +426,7 @@ export class FrameManager implements PageDelegate {
|
|||||||
backendNodeId,
|
backendNodeId,
|
||||||
executionContextId: (to._delegate as ExecutionContextDelegate)._contextId,
|
executionContextId: (to._delegate as ExecutionContextDelegate)._contextId,
|
||||||
}).catch(debugError);
|
}).catch(debugError);
|
||||||
if (!result)
|
if (!result || result.object.subtype === 'null')
|
||||||
throw new Error('Unable to adopt element handle from a different document');
|
throw new Error('Unable to adopt element handle from a different document');
|
||||||
return to._createHandle(result.object).asElement()!;
|
return to._createHandle(result.object).asElement()!;
|
||||||
}
|
}
|
||||||
|
27
src/dom.ts
27
src/dom.ts
@ -25,6 +25,33 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
|||||||
return this._frame;
|
return this._frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _evaluate(returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any> {
|
||||||
|
const needsAdoption = (value: any): boolean => {
|
||||||
|
return typeof value === 'object' && value instanceof ElementHandle && value._context !== this;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!args.some(needsAdoption)) {
|
||||||
|
// Only go through asynchronous calls if required.
|
||||||
|
return this._delegate.evaluate(this, returnByValue, pageFunction, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDispose: Promise<ElementHandle>[] = [];
|
||||||
|
const adopted = await Promise.all(args.map(async arg => {
|
||||||
|
if (!needsAdoption(arg))
|
||||||
|
return arg;
|
||||||
|
const adopted = this._frame._page._delegate.adoptElementHandle(arg, this);
|
||||||
|
toDispose.push(adopted);
|
||||||
|
return adopted;
|
||||||
|
}));
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await this._delegate.evaluate(this, returnByValue, pageFunction, ...adopted);
|
||||||
|
} finally {
|
||||||
|
await Promise.all(toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose())));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
_createHandle(remoteObject: any): js.JSHandle | null {
|
_createHandle(remoteObject: any): js.JSHandle | null {
|
||||||
if (this._frame._page._delegate.isElementHandle(remoteObject))
|
if (this._frame._page._delegate.isElementHandle(remoteObject))
|
||||||
return new ElementHandle(this, remoteObject);
|
return new ElementHandle(this, remoteObject);
|
||||||
|
@ -638,20 +638,9 @@ export class Frame {
|
|||||||
|
|
||||||
async select(selector: string, value: string | dom.ElementHandle | SelectOption | string[] | dom.ElementHandle[] | SelectOption[] | undefined, options?: WaitForOptions): Promise<string[]> {
|
async select(selector: string, value: string | dom.ElementHandle | SelectOption | string[] | dom.ElementHandle[] | SelectOption[] | undefined, options?: WaitForOptions): Promise<string[]> {
|
||||||
const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'any');
|
const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'any');
|
||||||
const toDispose: Promise<dom.ElementHandle>[] = [];
|
|
||||||
const values = value === undefined ? [] : Array.isArray(value) ? value : [value];
|
const values = value === undefined ? [] : Array.isArray(value) ? value : [value];
|
||||||
const context = await this._utilityContext();
|
const result = await handle.select(...values);
|
||||||
const adoptedValues = await Promise.all(values.map(async value => {
|
|
||||||
if (value instanceof dom.ElementHandle && value._context !== context) {
|
|
||||||
const adopted = this._page._delegate.adoptElementHandle(value, context);
|
|
||||||
toDispose.push(adopted);
|
|
||||||
return adopted;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}));
|
|
||||||
const result = await handle.select(...adoptedValues);
|
|
||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
await Promise.all(toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose())));
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,12 +24,16 @@ export class ExecutionContext {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
_evaluate(returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any> {
|
||||||
return this._delegate.evaluate(this, true /* returnByValue */, pageFunction, ...args);
|
return this._delegate.evaluate(this, returnByValue, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluateHandle: types.EvaluateHandle = (pageFunction, ...args) => {
|
evaluate: types.Evaluate = async (pageFunction, ...args) => {
|
||||||
return this._delegate.evaluate(this, false /* returnByValue */, pageFunction, ...args);
|
return this._evaluate(true /* returnByValue */, pageFunction, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {
|
||||||
|
return this._evaluate(false /* returnByValue */, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
_createHandle(remoteObject: any): JSHandle {
|
_createHandle(remoteObject: any): JSHandle {
|
||||||
|
@ -424,7 +424,9 @@ export class FrameManager implements PageDelegate {
|
|||||||
const result = await this._session.send('DOM.resolveNode', {
|
const result = await this._session.send('DOM.resolveNode', {
|
||||||
objectId: toRemoteObject(handle).objectId,
|
objectId: toRemoteObject(handle).objectId,
|
||||||
executionContextId: (to._delegate as ExecutionContextDelegate)._contextId
|
executionContextId: (to._delegate as ExecutionContextDelegate)._contextId
|
||||||
});
|
}).catch(debugError);
|
||||||
|
if (!result || result.object.subtype === 'null')
|
||||||
|
throw new Error('Unable to adopt element handle from a different document');
|
||||||
return to._createHandle(result.object) as dom.ElementHandle<T>;
|
return to._createHandle(result.object) as dom.ElementHandle<T>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,14 +219,6 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
|||||||
await page.evaluate(e => e.textContent, element).catch(e => error = e);
|
await page.evaluate(e => e.textContent, element).catch(e => error = e);
|
||||||
expect(error.message).toContain('JSHandle is disposed');
|
expect(error.message).toContain('JSHandle is disposed');
|
||||||
});
|
});
|
||||||
it('should throw if elementHandles are from other frames', async({page, server}) => {
|
|
||||||
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
|
||||||
const bodyHandle = await page.frames()[1].$('body');
|
|
||||||
let error = null;
|
|
||||||
await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => error = e);
|
|
||||||
expect(error).toBeTruthy();
|
|
||||||
expect(error.message).toContain('JSHandles can be evaluated only in the context they were created');
|
|
||||||
});
|
|
||||||
it('should simulate a user gesture', async({page, server}) => {
|
it('should simulate a user gesture', async({page, server}) => {
|
||||||
const result = await page.evaluate(() => {
|
const result = await page.evaluate(() => {
|
||||||
document.body.appendChild(document.createTextNode('test'));
|
document.body.appendChild(document.createTextNode('test'));
|
||||||
@ -333,5 +325,34 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
|||||||
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
|
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
|
||||||
expect(await mainFrame.evaluate(() => window.location.href)).toContain('127');
|
expect(await mainFrame.evaluate(() => window.location.href)).toContain('127');
|
||||||
});
|
});
|
||||||
|
xit('should allow cross-frame js handles', async({page, server}) => {
|
||||||
|
// TODO: this should be possible because frames script each other, but
|
||||||
|
// protocol implementations do not support this.
|
||||||
|
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
||||||
|
const handle = await page.evaluateHandle(() => {
|
||||||
|
const iframe = document.querySelector('iframe');
|
||||||
|
const foo = { bar: 'baz' };
|
||||||
|
iframe.contentWindow.__foo = foo;
|
||||||
|
return foo;
|
||||||
|
});
|
||||||
|
const childFrame = page.mainFrame().childFrames()[0];
|
||||||
|
const childResult = await childFrame.evaluate(() => window.__foo);
|
||||||
|
expect(childResult).toEqual({ bar: 'baz' });
|
||||||
|
const result = await childFrame.evaluate(foo => foo.bar, handle);
|
||||||
|
expect(result).toBe('baz');
|
||||||
|
});
|
||||||
|
it.skip(FFOX)('should allow cross-frame element handles', async({page, server}) => {
|
||||||
|
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
||||||
|
const bodyHandle = await page.mainFrame().childFrames()[0].$('body');
|
||||||
|
const result = await page.evaluate(body => body.innerHTML, bodyHandle);
|
||||||
|
expect(result.trim()).toBe('<div>Hi, I\'m frame</div>');
|
||||||
|
});
|
||||||
|
it.skip(FFOX)('should not allow cross-frame element handles when frames do not script each other', async({page, server}) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const frame = await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
|
||||||
|
const bodyHandle = await frame.$('body');
|
||||||
|
const error = await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => e);
|
||||||
|
expect(error.message).toContain('Unable to adopt element handle from a different document');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user