Skip to content

Commit

Permalink
feat: expose raw devtools protocol connection (puppeteer#1770)
Browse files Browse the repository at this point in the history
feat: expose raw devtools protocol connection

This patch introduces `target.createCDPSession` method that
allows directly communicating with the target over the
Chrome DevTools Protocol.

Fixes puppeteer#31.
  • Loading branch information
aslushnikov authored Jan 11, 2018
1 parent ec8e40f commit 5368051
Show file tree
Hide file tree
Showing 17 changed files with 162 additions and 51 deletions.
48 changes: 48 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
* [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
* [page.setViewport(viewport)](#pagesetviewportviewport)
* [page.tap(selector)](#pagetapselector)
* [page.target()](#pagetarget)
* [page.title()](#pagetitle)
* [page.touchscreen](#pagetouchscreen)
* [page.tracing](#pagetracing)
Expand Down Expand Up @@ -198,9 +199,13 @@
* [response.text()](#responsetext)
* [response.url()](#responseurl)
- [class: Target](#class-target)
* [target.createCDPSession()](#targetcreatecdpsession)
* [target.page()](#targetpage)
* [target.type()](#targettype)
* [target.url()](#targeturl)
- [class: CDPSession](#class-cdpsession)
* [cdpSession.detach()](#cdpsessiondetach)
* [cdpSession.send(method[, params])](#cdpsessionsendmethod-params)
- [class: Coverage](#class-coverage)
* [coverage.startCSSCoverage(options)](#coveragestartcsscoverageoptions)
* [coverage.startJSCoverage(options)](#coveragestartjscoverageoptions)
Expand Down Expand Up @@ -1139,6 +1144,9 @@ In the case of multiple pages in a single browser, each page can have its own vi
This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.touchscreen](#pagetouchscreen) to tap in the center of the element.
If there's no element matching `selector`, the method throws an error.

#### page.target()
- returns: <[Target]> a target this page was created from.

#### page.title()
- returns: <[Promise]<[string]>> Returns page's title.

Expand Down Expand Up @@ -2166,6 +2174,11 @@ Contains the URL of the response.

### class: Target

#### target.createCDPSession()
- returns: <[Promise]<[CDPSession]>>

Creates a Chrome Devtools Protocol session attached to the target.

#### target.page()
- returns: <[Promise]<?[Page]>>

Expand All @@ -2179,6 +2192,40 @@ Identifies what kind of target this is. Can be `"page"`, `"service_worker"`, or
#### target.url()
- returns: <[string]>


### class: CDPSession

* extends: [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter)

The `CDPSession` instances are used to talk raw Chrome Devtools Protocol:
- protocol methods can be called with `session.send` method.
- protocol events can be subscribed to with `session.on` method.

Documentation on DevTools Protocol can be found here: [DevTools Protocol Viewer](https://chromedevtools.github.io/devtools-protocol/).

```js
const client = await page.target().createCDPSession();
await client.send('Animation.enable');
await client.on('Animation.animationCreated', () => console.log('Animation created!'));
const response = await client.send('Animation.getPlaybackRate');
console.log('playback rate is ' + response.playbackRate);
await client.send('Animation.setPlaybackRate', {
playbackRate: response.playbackRate / 2
});
```

#### cdpSession.detach()
- returns: <[Promise]>

Detaches session from target. Once detached, session won't emit any events and can't be used
to send messages.

#### cdpSession.send(method[, params])
- `method` <[string]> protocol method name
- `params` <[Object]> Optional method parameters
- returns: <[Promise]<[Object]>>


### class: Coverage

Coverage gathers information about parts of JavaScript and CSS that were used by the page.
Expand Down Expand Up @@ -2253,6 +2300,7 @@ reported.
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
[stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable"
[CDPSession]: #class-cdpsession "CDPSession"
[Error]: https://nodejs.org/api/errors.html#errors_class_error "Error"
[Frame]: #class-frame "Frame"
[ConsoleMessage]: #class-consolemessage "ConsoleMessage"
Expand Down
14 changes: 11 additions & 3 deletions lib/Browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ class Target {
*/
constructor(browser, targetInfo) {
this._browser = browser;
this._targetId = targetInfo.targetId;
this._targetInfo = targetInfo;
/** @type {?Promise<!Page>} */
this._pagePromise = null;
Expand All @@ -202,13 +203,20 @@ class Target {
this._initializedCallback(true);
}

/**
* @return {!Promise<!Puppeteer.CDPSession>}
*/
createCDPSession() {
return this._browser._connection.createSession(this._targetId);
}

/**
* @return {!Promise<?Page>}
*/
async page() {
if (this._targetInfo.type === 'page' && !this._pagePromise) {
this._pagePromise = this._browser._connection.createSession(this._targetInfo.targetId)
.then(client => Page.create(client, this._browser._ignoreHTTPSErrors, this._browser._appMode, this._browser._screenshotTaskQueue));
this._pagePromise = this._browser._connection.createSession(this._targetId)
.then(client => Page.create(client, this, this._browser._ignoreHTTPSErrors, this._browser._appMode, this._browser._screenshotTaskQueue));
}
return this._pagePromise;
}
Expand Down Expand Up @@ -258,4 +266,4 @@ helper.tracePublicAPI(Target);
* @property {boolean} attached
*/

module.exports = { Browser, TaskQueue };
module.exports = { Browser, TaskQueue, Target };
24 changes: 9 additions & 15 deletions lib/Connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const {helper} = require('./helper');
const debugProtocol = require('debug')('puppeteer:protocol');
const debugSession = require('debug')('puppeteer:session');

Expand Down Expand Up @@ -49,7 +50,7 @@ class Connection extends EventEmitter {
this._ws = ws;
this._ws.on('message', this._onMessage.bind(this));
this._ws.on('close', this._onClose.bind(this));
/** @type {!Map<string, !Session>}*/
/** @type {!Map<string, !CDPSession>}*/
this._sessions = new Map();
}

Expand Down Expand Up @@ -135,17 +136,17 @@ class Connection extends EventEmitter {

/**
* @param {string} targetId
* @return {!Promise<!Session>}
* @return {!Promise<!CDPSession>}
*/
async createSession(targetId) {
const {sessionId} = await this.send('Target.attachToTarget', {targetId});
const session = new Session(this, targetId, sessionId);
const session = new CDPSession(this, targetId, sessionId);
this._sessions.set(sessionId, session);
return session;
}
}

class Session extends EventEmitter {
class CDPSession extends EventEmitter {
/**
* @param {!Connection} connection
* @param {string} targetId
Expand All @@ -161,13 +162,6 @@ class Session extends EventEmitter {
this._sessionId = sessionId;
}

/**
* @return {string}
*/
targetId() {
return this._targetId;
}

/**
* @param {string} method
* @param {!Object=} params
Expand Down Expand Up @@ -211,9 +205,8 @@ class Session extends EventEmitter {
}
}

async dispose() {
console.assert(!!this._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
await this._connection.send('Target.closeTarget', {targetId: this._targetId});
async detach() {
await this._connection.send('Target.detachFromTarget', {sessionId: this._sessionId});
}

_onClosed() {
Expand All @@ -223,6 +216,7 @@ class Session extends EventEmitter {
this._connection = null;
}
}
helper.tracePublicAPI(CDPSession);

/**
* @param {!Error} error
Expand All @@ -234,4 +228,4 @@ function rewriteError(error, message) {
return error;
}

module.exports = {Connection, Session};
module.exports = {Connection, CDPSession};
6 changes: 3 additions & 3 deletions lib/Coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const {helper, debugError} = require('./helper');

class Coverage {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
*/
constructor(client) {
this._jsCoverage = new JSCoverage(client);
Expand Down Expand Up @@ -66,7 +66,7 @@ helper.tracePublicAPI(Coverage);

class JSCoverage {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
*/
constructor(client) {
this._client = client;
Expand Down Expand Up @@ -154,7 +154,7 @@ class JSCoverage {

class CSSCoverage {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
*/
constructor(client) {
this._client = client;
Expand Down
2 changes: 1 addition & 1 deletion lib/Dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {helper} = require('./helper');

class Dialog {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {string} type
* @param {string} message
* @param {(string|undefined)} defaultValue
Expand Down
2 changes: 1 addition & 1 deletion lib/ElementHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const {helper, debugError} = require('./helper');
class ElementHandle extends JSHandle {
/**
* @param {!Puppeteer.ExecutionContext} context
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {!Object} remoteObject
* @param {!Puppeteer.Page} page
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/EmulationManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

class EmulationManager {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
*/
constructor(client) {
this._client = client;
Expand Down
4 changes: 2 additions & 2 deletions lib/ExecutionContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {helper} = require('./helper');

class ExecutionContext {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {!Object} contextPayload
* @param {function(*):!JSHandle} objectHandleFactory
*/
Expand Down Expand Up @@ -117,7 +117,7 @@ class ExecutionContext {
class JSHandle {
/**
* @param {!ExecutionContext} context
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {!Object} remoteObject
*/
constructor(context, client, remoteObject) {
Expand Down
4 changes: 2 additions & 2 deletions lib/FrameManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const readFileAsync = helper.promisify(fs.readFile);

class FrameManager extends EventEmitter {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {{frame: Object, childFrames: ?Array}} frameTree
* @param {!Puppeteer.Page} page
*/
Expand Down Expand Up @@ -226,7 +226,7 @@ FrameManager.Events = {
*/
class Frame {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {?Frame} parentFrame
* @param {string} frameId
*/
Expand Down
6 changes: 3 additions & 3 deletions lib/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const keyDefinitions = require('./USKeyboardLayout');

class Keyboard {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
*/
constructor(client) {
this._client = client;
Expand Down Expand Up @@ -189,7 +189,7 @@ class Keyboard {

class Mouse {
/**
* @param {Puppeteer.Session} client
* @param {Puppeteer.CDPSession} client
* @param {!Keyboard} keyboard
*/
constructor(client, keyboard) {
Expand Down Expand Up @@ -268,7 +268,7 @@ class Mouse {

class Touchscreen {
/**
* @param {Puppeteer.Session} client
* @param {Puppeteer.CDPSession} client
* @param {Keyboard} keyboard
*/
constructor(client, keyboard) {
Expand Down
6 changes: 3 additions & 3 deletions lib/NetworkManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const Multimap = require('./Multimap');

class NetworkManager extends EventEmitter {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {!Puppeteer.FrameManager} frameManager
*/
constructor(client, frameManager) {
Expand Down Expand Up @@ -281,7 +281,7 @@ class NetworkManager extends EventEmitter {

class Request {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {?string} requestId
* @param {string} interceptionId
* @param {boolean} allowInterception
Expand Down Expand Up @@ -479,7 +479,7 @@ helper.tracePublicAPI(Request);

class Response {
/**
* @param {!Puppeteer.Session} client
* @param {!Puppeteer.CDPSession} client
* @param {!Request} request
* @param {number} status
* @param {!Object} headers
Expand Down
Loading

0 comments on commit 5368051

Please sign in to comment.