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

feat(ObjectHandles): Implement Object Handles #943

Merged
merged 14 commits into from Oct 6, 2017

Conversation

aslushnikov
Copy link
Contributor

@aslushnikov aslushnikov commented Oct 3, 2017

This patch:

  • introduces ExecutionContext class that incapsulates javascript
    execution context. Examples of execution contexts are workers and
    frames
  • introduces ObjectHandle that holds a references to the javascript
    object in ExecutionContext
  • inherits ElementHandle from ObjectHandle

Fixes #382.

docs/api.md Outdated
If the function, passed to the `page.object`, returns a [Promise], then `page.object` would wait for the promise to resolve and return it's value.

```js
const aHandle = await page.object(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: () => Promise.resolve(window)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
@@ -109,21 +110,44 @@
+ [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
+ [frame.addScriptTag(url)](#frameaddscripttagurl)
+ [frame.childFrames()](#framechildframes)
+ [frame.context()](#framecontext)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

executionContext

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
@@ -55,6 +55,7 @@
+ [page.keyboard](#pagekeyboard)
+ [page.mainFrame()](#pagemainframe)
+ [page.mouse](#pagemouse)
+ [page.object(pageFunction, ...args)](#pageobjectpagefunction-args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evaluateObject

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
+ [frame.injectFile(filePath)](#frameinjectfilefilepath)
+ [frame.isDetached()](#frameisdetached)
+ [frame.name()](#framename)
+ [frame.object(pageFunction, ...args)](#frameobjectpagefunction-args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOne.

docs/api.md Outdated
* [class: ObjectHandle](#class-objecthandle)
+ [objectHandle.asArray()](#objecthandleasarray)
+ [objectHandle.asElement()](#objecthandleaselement)
+ [objectHandle.asJSON()](#objecthandleasjson)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsonValue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
+ [objectHandle.asArray()](#objecthandleasarray)
+ [objectHandle.asElement()](#objecthandleaselement)
+ [objectHandle.asJSON()](#objecthandleasjson)
+ [objectHandle.asObject()](#objecthandleasobject)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getProperties(own) ? Returns Map.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
* [class: ElementHandle](#class-elementhandle)
+ [elementHandle.asArray()](#elementhandleasarray)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return null for everything not array. Don't assign non-indexed property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Killed it.

docs/api.md Outdated
+ [executionContext.evaluate(pageFunction, ...args)](#executioncontextevaluatepagefunction-args)
+ [executionContext.object(pageFunction, ...args)](#executioncontextobjectpagefunction-args)
* [class: ObjectHandle](#class-objecthandle)
+ [objectHandle.asArray()](#objecthandleasarray)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use getProperties instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
* [class: ExecutionContext](#class-executioncontext)
+ [executionContext.evaluate(pageFunction, ...args)](#executioncontextevaluatepagefunction-args)
+ [executionContext.object(pageFunction, ...args)](#executioncontextobjectpagefunction-args)
* [class: ObjectHandle](#class-objecthandle)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, renamed into JSHandle

docs/api.md Outdated
+ [objectHandle.asObject()](#objecthandleasobject)
+ [objectHandle.context()](#objecthandlecontext)
+ [objectHandle.dispose()](#objecthandledispose)
+ [objectHandle.get(propertyName)](#objecthandlegetpropertyname)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getProperty

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

docs/api.md Outdated
+ [objectHandle.dispose()](#objecthandledispose)
+ [objectHandle.get(propertyName)](#objecthandlegetpropertyname)
+ [objectHandle.toString()](#objecthandletostring)
+ [objectHandle.type()](#objecthandletype)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove type, I would add evaluate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Killed type; as per offline discussion, holding off from adding .evaluate in this patch until its necessary

docs/api.md Outdated
- `...args` <...[Serializable]|[ObjectHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[ObjectHandle]>> Resolves to the return value of `pageFunction`

If the function, passed to the `page.object`, returns a [Promise], then `page.object` would wait for the promise to resolve and return it's value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
If the function, passed to the `page.object`, returns a [Promise], then `page.object` would wait for the promise to resolve and return it's value.

```js
const aHandle = await page.object(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated

Returns either `null` or the object handle itself, if the object handle is an instance of [ElementHandle].

#### objectHandle.asJSON()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect this method to return a string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed into objectHandle.jsonValue()

@@ -59,7 +59,7 @@ class MDOutline {
let actualText = element.firstChild.textContent;
let angleIndex = actualText.indexOf('<');
let spaceIndex = actualText.indexOf(' ');
angleIndex = angleIndex === -1 ? angleText.length : angleIndex;
angleIndex = angleIndex === -1 ? actualText.length : angleIndex;
spaceIndex = spaceIndex === -1 ? spaceIndex.length : spaceIndex + 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaceIndex === -1 ? spaceIndex.length looks sketchy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done.

@dgozman
Copy link
Contributor

dgozman commented Oct 5, 2017

ObjectHandle -> JSHandle
object -> evaluateHandle
get -> getProperty
asArray, asObject -> getProperties
asJSON -> jsonValue
handle.context().evaluate(h => h.arr[2].prop, handle) as a best-practice in docs?

Copy link
Contributor Author

@aslushnikov aslushnikov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed all comments

docs/api.md Outdated
@@ -55,6 +55,7 @@
+ [page.keyboard](#pagekeyboard)
+ [page.mainFrame()](#pagemainframe)
+ [page.mouse](#pagemouse)
+ [page.object(pageFunction, ...args)](#pageobjectpagefunction-args)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
@@ -109,21 +110,44 @@
+ [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
+ [frame.addScriptTag(url)](#frameaddscripttagurl)
+ [frame.childFrames()](#framechildframes)
+ [frame.context()](#framecontext)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
+ [frame.injectFile(filePath)](#frameinjectfilefilepath)
+ [frame.isDetached()](#frameisdetached)
+ [frame.name()](#framename)
+ [frame.object(pageFunction, ...args)](#frameobjectpagefunction-args)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOne.

docs/api.md Outdated
* [class: ExecutionContext](#class-executioncontext)
+ [executionContext.evaluate(pageFunction, ...args)](#executioncontextevaluatepagefunction-args)
+ [executionContext.object(pageFunction, ...args)](#executioncontextobjectpagefunction-args)
* [class: ObjectHandle](#class-objecthandle)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, renamed into JSHandle

docs/api.md Outdated
* [class: ObjectHandle](#class-objecthandle)
+ [objectHandle.asArray()](#objecthandleasarray)
+ [objectHandle.asElement()](#objecthandleaselement)
+ [objectHandle.asJSON()](#objecthandleasjson)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -59,7 +59,7 @@ class MDOutline {
let actualText = element.firstChild.textContent;
let angleIndex = actualText.indexOf('<');
let spaceIndex = actualText.indexOf(' ');
angleIndex = angleIndex === -1 ? angleText.length : angleIndex;
angleIndex = angleIndex === -1 ? actualText.length : angleIndex;
spaceIndex = spaceIndex === -1 ? spaceIndex.length : spaceIndex + 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done.

docs/api.md Outdated

Returns either `null` or the object handle itself, if the object handle is an instance of [ElementHandle].

#### objectHandle.asJSON()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed into objectHandle.jsonValue()

docs/api.md Outdated
If the function, passed to the `page.object`, returns a [Promise], then `page.object` would wait for the promise to resolve and return it's value.

```js
const aHandle = await page.object(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
If the function, passed to the `page.object`, returns a [Promise], then `page.object` would wait for the promise to resolve and return it's value.

```js
const aHandle = await page.object(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
- `...args` <...[Serializable]|[ObjectHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[ObjectHandle]>> Resolves to the return value of `pageFunction`

If the function, passed to the `page.object`, returns a [Promise], then `page.object` would wait for the promise to resolve and return it's value.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

@pavelfeldman pavelfeldman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

This patch:
- introduces ExecutionContext class that incapsulates javascript
  execution context. An examples of execution contexts are workers and
  frames
- introduces ObjectHandle that holds a references to the javascript
  object in ExecutionContext
- inherits ElementHandle from ObjectHandle

Fixes puppeteer#382.
docs/api.md Outdated
A string can also be passed in instead of a function.

```js
const aHandle = await page.evaluateHandle('document'); // Handle on the 'document'.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle of the 'document'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went on with "for".

docs/api.md Outdated
@@ -1314,8 +1365,129 @@ puppeteer.launch().then(async browser => {
});
```

### class: ExecutionContext

The class represents a context for javascript execution. Examples of javascript contexts are:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: JavaScript

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
### class: ExecutionContext

The class represents a context for javascript execution. Examples of javascript contexts are:
- each frame has a separate execution context
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated

The class represents a context for javascript execution. Examples of javascript contexts are:
- each frame has a separate execution context
- all kind of workers have their own contexts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

docs/api.md Outdated
console.log(await executionContext.evaluate('1 + 2')); // prints "3"
```

[JSHandle] instances could be passed as arguments to the `frame.evaluate`:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could can

Let's try to use a consistent tense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced here for now. Probably will replace everywhere later.

async getProperties() {
const response = await this._client.send('Runtime.getProperties', {
objectId: this._remoteObject.objectId,
ownProperties: true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want only enumerable and "own" properties? Feels arbitrary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-enumerable might clash with names.
ownProperties is a good call, I removed this flag and added a test.

await helper.releaseObject(this._client, this._remoteObject);
}

/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (!element.ownerDocument.contains(element))
return null;
if (element.nodeType !== HTMLElement.ELEMENT_NODE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we making element handles for nodes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no good way to differentiate for now; as discussed offline, I'll make them throw for elementHandle methods for now.

const context = this._contextIdToContext.get(contextId);
console.assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
if (remoteObject.subtype === 'node')
return new ElementHandle(context, this._client, remoteObject, this._mouse, this._touchscreen);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check if it is an element here, instead of just a node?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't: it requires either an async hop to the page, or breaking change to the protocol. We'd be very unhappy with both.

@@ -59,8 +59,8 @@ class MDOutline {
let actualText = element.firstChild.textContent;
let angleIndex = actualText.indexOf('<');
let spaceIndex = actualText.indexOf(' ');
angleIndex = angleIndex === -1 ? angleText.length : angleIndex;
spaceIndex = spaceIndex === -1 ? spaceIndex.length : spaceIndex + 1;
angleIndex = angleIndex === -1 ? actualText.length : angleIndex;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this fix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no such thing as 'angleText'. This fixes a typo.

test/test.js Outdated
it('should work for TextNodes', SX(async function() {
await page.goto(PREFIX + '/input/button.html');
const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild);
await buttonTextNode.click('button');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

element.click doesn't take a string.

This lies about clicking the text node.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call about a string. Regarding clicking text node: i switched to throwing in case of text nodes as we discussed offline.

const [frame1, frame2] = page.frames();
expect(frame1.executionContext()).toBeTruthy();
expect(frame2.executionContext()).toBeTruthy();
expect(frame1.executionContext() !== frame2.executionContext()).toBeTruthy();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we evaluate something just so this test makes me feel safer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing; done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants