mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat: support running Puppeteer in extensions (#12459)
This commit is contained in:
parent
c8a64c0f79
commit
3c6f01a31d
@ -215,6 +215,19 @@ The constructor for this class is marked as internal. Third-party code should no
|
|||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td>
|
<tr><td>
|
||||||
|
|
||||||
|
<span id="extensiontransport">[ExtensionTransport](./puppeteer.extensiontransport.md)</span>
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
**_(Experimental)_** Experimental ExtensionTransport allows establishing a connection via chrome.debugger API if Puppeteer runs in an extension. Since Chrome DevTools Protocol is restricted for extensions, the transport implements missing commands and events.
|
||||||
|
|
||||||
|
**Remarks:**
|
||||||
|
|
||||||
|
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ExtensionTransport` class.
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
<tr><td>
|
||||||
|
|
||||||
<span id="filechooser">[FileChooser](./puppeteer.filechooser.md)</span>
|
<span id="filechooser">[FileChooser](./puppeteer.filechooser.md)</span>
|
||||||
|
|
||||||
</td><td>
|
</td><td>
|
||||||
|
17
docs/api/puppeteer.extensiontransport.close.md
Normal file
17
docs/api/puppeteer.extensiontransport.close.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: ExtensionTransport.close
|
||||||
|
---
|
||||||
|
|
||||||
|
# ExtensionTransport.close() method
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ExtensionTransport {
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
|
||||||
|
void
|
44
docs/api/puppeteer.extensiontransport.connecttab.md
Normal file
44
docs/api/puppeteer.extensiontransport.connecttab.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: ExtensionTransport.connectTab
|
||||||
|
---
|
||||||
|
|
||||||
|
# ExtensionTransport.connectTab() method
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ExtensionTransport {
|
||||||
|
static connectTab(tabId: number): Promise<ExtensionTransport>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
<table><thead><tr><th>
|
||||||
|
|
||||||
|
Parameter
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Type
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Description
|
||||||
|
|
||||||
|
</th></tr></thead>
|
||||||
|
<tbody><tr><td>
|
||||||
|
|
||||||
|
tabId
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
number
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
**Returns:**
|
||||||
|
|
||||||
|
Promise<[ExtensionTransport](./puppeteer.extensiontransport.md)>
|
116
docs/api/puppeteer.extensiontransport.md
Normal file
116
docs/api/puppeteer.extensiontransport.md
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: ExtensionTransport
|
||||||
|
---
|
||||||
|
|
||||||
|
# ExtensionTransport class
|
||||||
|
|
||||||
|
Experimental ExtensionTransport allows establishing a connection via chrome.debugger API if Puppeteer runs in an extension. Since Chrome DevTools Protocol is restricted for extensions, the transport implements missing commands and events.
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export declare class ExtensionTransport implements ConnectionTransport
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implements:** [ConnectionTransport](./puppeteer.connectiontransport.md)
|
||||||
|
|
||||||
|
## Remarks
|
||||||
|
|
||||||
|
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ExtensionTransport` class.
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
<table><thead><tr><th>
|
||||||
|
|
||||||
|
Property
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Modifiers
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Type
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Description
|
||||||
|
|
||||||
|
</th></tr></thead>
|
||||||
|
<tbody><tr><td>
|
||||||
|
|
||||||
|
<span id="onclose">onclose</span>
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
`optional`
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
() => void
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
<tr><td>
|
||||||
|
|
||||||
|
<span id="onmessage">onmessage</span>
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
`optional`
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
(message: string) => void
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
<table><thead><tr><th>
|
||||||
|
|
||||||
|
Method
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Modifiers
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Description
|
||||||
|
|
||||||
|
</th></tr></thead>
|
||||||
|
<tbody><tr><td>
|
||||||
|
|
||||||
|
<span id="close">[close()](./puppeteer.extensiontransport.close.md)</span>
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
<tr><td>
|
||||||
|
|
||||||
|
<span id="connecttab">[connectTab(tabId)](./puppeteer.extensiontransport.connecttab.md)</span>
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
`static`
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
<tr><td>
|
||||||
|
|
||||||
|
<span id="send">[send(message)](./puppeteer.extensiontransport.send.md)</span>
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
</tbody></table>
|
44
docs/api/puppeteer.extensiontransport.send.md
Normal file
44
docs/api/puppeteer.extensiontransport.send.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
sidebar_label: ExtensionTransport.send
|
||||||
|
---
|
||||||
|
|
||||||
|
# ExtensionTransport.send() method
|
||||||
|
|
||||||
|
#### Signature:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ExtensionTransport {
|
||||||
|
send(message: string): void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
<table><thead><tr><th>
|
||||||
|
|
||||||
|
Parameter
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Type
|
||||||
|
|
||||||
|
</th><th>
|
||||||
|
|
||||||
|
Description
|
||||||
|
|
||||||
|
</th></tr></thead>
|
||||||
|
<tbody><tr><td>
|
||||||
|
|
||||||
|
message
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
string
|
||||||
|
|
||||||
|
</td><td>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
**Returns:**
|
||||||
|
|
||||||
|
void
|
71
docs/guides/running-puppeteer-in-extensions.md
Normal file
71
docs/guides/running-puppeteer-in-extensions.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Running Puppeteer in Chrome extensions
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
Chrome extensions environment is significantly different from the usual Node.JS environment, therefore, the support for running Puppeteer in chrome.debugger
|
||||||
|
is currently experimental. Please submit issues https://github.com/puppeteer/puppeteer/issues/new/choose if you encounted bugs.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Chrome Extensions allow accessing Chrome DevTools Protocol via [`chrome.debugger`](https://developer.chrome.com/docs/extensions/reference/api/debugger).
|
||||||
|
[`chrome.debugger`](https://developer.chrome.com/docs/extensions/reference/api/debugger) provides a restricted access to CDP and allows attaching to one
|
||||||
|
page at a time. Therefore, Puppeteer requires a different transport to be used and Puppeteer's view is limited to a single page. It means you can
|
||||||
|
interact with a single page and its frames and workers but cannot create new pages using Puppeteer. To create a new page you need to use the
|
||||||
|
[`chrome.tabs`](https://developer.chrome.com/docs/extensions/reference/api/tabs) API and establish a new Puppeteer connection.
|
||||||
|
|
||||||
|
## How to run Puppeteer in the browser
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
See https://github.com/puppeteer/puppeteer/tree/main/examples/puppeteer-in-extension for a complete example.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
To run Puppeteer in the an extension, first you need to produce a browser-compatible build using a bundler such as rollup or webpack:
|
||||||
|
|
||||||
|
1. When importing Puppeteer use the browser-specific entrypoint from puppeteer-core `puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js'`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
connect,
|
||||||
|
ExtensionTransport,
|
||||||
|
} from 'puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js';
|
||||||
|
|
||||||
|
// Create a tab or find a tab to attach to.
|
||||||
|
const tab = await chrome.tabs.create({
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
// Connect Puppeteer using the ExtensionTransport.connectTab.
|
||||||
|
const browser = await connect({
|
||||||
|
transport: await ExtensionTransport.connectTab(tab.id),
|
||||||
|
});
|
||||||
|
// You will have a single page on the browser object, which corresponds
|
||||||
|
// to the tab you connected the transport to.
|
||||||
|
const [page] = await browser.pages();
|
||||||
|
// Perform the usual operations with Puppeteer page.
|
||||||
|
console.log(await page.evaluate('document.title'));
|
||||||
|
browser.disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build your extension using a bundler. For example, the following configuration can be used with rollup:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'main.mjs',
|
||||||
|
output: {
|
||||||
|
format: 'esm',
|
||||||
|
dir: 'out',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
nodeResolve({
|
||||||
|
// Indicate that we target a browser environment.
|
||||||
|
browser: true,
|
||||||
|
// Exclude any dependencies except for puppeteer-core.
|
||||||
|
// `npm install puppeteer-core` # To install puppeteer-core if needed.
|
||||||
|
resolveOnly: ['puppeteer-core'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
@ -53,6 +53,7 @@ export default {
|
|||||||
// Indicate that we target a browser environment.
|
// Indicate that we target a browser environment.
|
||||||
browser: true,
|
browser: true,
|
||||||
// Exclude any dependencies except for puppeteer-core.
|
// Exclude any dependencies except for puppeteer-core.
|
||||||
|
// `npm install puppeteer-core` # To install puppeteer-core if needed.
|
||||||
resolveOnly: ['puppeteer-core'],
|
resolveOnly: ['puppeteer-core'],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -12,9 +12,25 @@ globalThis.testConnect = async url => {
|
|||||||
const tab = await chrome.tabs.create({
|
const tab = await chrome.tabs.create({
|
||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wait for the new tab to load before connecting.
|
||||||
|
await new Promise(resolve => {
|
||||||
|
function listener(tabId, changeInfo) {
|
||||||
|
if (tabId === tab.id && changeInfo.status === 'complete') {
|
||||||
|
chrome.tabs.onUpdated.removeListener(listener);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chrome.tabs.onUpdated.addListener(listener);
|
||||||
|
});
|
||||||
|
|
||||||
const browser = await connect({
|
const browser = await connect({
|
||||||
transport: await ExtensionTransport.connectTab(tab.id),
|
transport: await ExtensionTransport.connectTab(tab.id),
|
||||||
});
|
});
|
||||||
const [page] = await browser.pages();
|
const [page] = await browser.pages();
|
||||||
return await page.evaluate('document.title');
|
const title = await page.evaluate(() => {
|
||||||
|
return document.title;
|
||||||
|
});
|
||||||
|
await browser.disconnect();
|
||||||
|
return title;
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,7 @@ const pageTargetInfo = {
|
|||||||
* implements missing commands and events.
|
* implements missing commands and events.
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
* @internal
|
* @public
|
||||||
*/
|
*/
|
||||||
export class ExtensionTransport implements ConnectionTransport {
|
export class ExtensionTransport implements ConnectionTransport {
|
||||||
static async connectTab(tabId: number): Promise<ExtensionTransport> {
|
static async connectTab(tabId: number): Promise<ExtensionTransport> {
|
||||||
@ -178,5 +178,6 @@ export class ExtensionTransport implements ConnectionTransport {
|
|||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
chrome.debugger.onEvent.removeListener(this.#debuggerEventHandler);
|
chrome.debugger.onEvent.removeListener(this.#debuggerEventHandler);
|
||||||
|
void chrome.debugger.detach({tabId: this.#tabId});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,16 @@ export function stringifyFunction(fn: (...args: never) => unknown): string {
|
|||||||
let value = fn.toString();
|
let value = fn.toString();
|
||||||
try {
|
try {
|
||||||
new Function(`(${value})`);
|
new Function(`(${value})`);
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
(err as Error).message.includes(
|
||||||
|
`Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// The content security policy does not allow Function eval. Let's
|
||||||
|
// assume the value might be valid as is.
|
||||||
|
return value;
|
||||||
|
}
|
||||||
// This means we might have a function shorthand (e.g. `test(){}`). Let's
|
// This means we might have a function shorthand (e.g. `test(){}`). Let's
|
||||||
// try prefixing.
|
// try prefixing.
|
||||||
let prefix = 'function ';
|
let prefix = 'function ';
|
||||||
|
Loading…
Reference in New Issue
Block a user