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>
|
||||
<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>
|
||||
|
||||
</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.
|
||||
browser: true,
|
||||
// Exclude any dependencies except for puppeteer-core.
|
||||
// `npm install puppeteer-core` # To install puppeteer-core if needed.
|
||||
resolveOnly: ['puppeteer-core'],
|
||||
}),
|
||||
],
|
||||
|
@ -12,9 +12,25 @@ globalThis.testConnect = async url => {
|
||||
const tab = await chrome.tabs.create({
|
||||
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({
|
||||
transport: await ExtensionTransport.connectTab(tab.id),
|
||||
});
|
||||
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.
|
||||
*
|
||||
* @experimental
|
||||
* @internal
|
||||
* @public
|
||||
*/
|
||||
export class ExtensionTransport implements ConnectionTransport {
|
||||
static async connectTab(tabId: number): Promise<ExtensionTransport> {
|
||||
@ -178,5 +178,6 @@ export class ExtensionTransport implements ConnectionTransport {
|
||||
|
||||
close(): void {
|
||||
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();
|
||||
try {
|
||||
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
|
||||
// try prefixing.
|
||||
let prefix = 'function ';
|
||||
|
Loading…
Reference in New Issue
Block a user