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

chore: migrate addScriptTag #8878

Merged
merged 8 commits into from Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/api/puppeteer.frame.addscripttag.md
Expand Up @@ -26,4 +26,4 @@ class Frame {

Promise<[ElementHandle](./puppeteer.elementhandle.md)<HTMLScriptElement>>

a promise that resolves to the added tag when the script's `onload` event fires or when the script content was injected into the frame.
An [element handle](./puppeteer.elementhandle.md) to the injected `<script>` element.
2 changes: 1 addition & 1 deletion docs/api/puppeteer.frameaddscripttagoptions.content.md
Expand Up @@ -4,7 +4,7 @@ sidebar_label: FrameAddScriptTagOptions.content

# FrameAddScriptTagOptions.content property

Raw JavaScript content to be injected into the frame.
JavaScript to be injected into the frame.

**Signature:**

Expand Down
15 changes: 15 additions & 0 deletions docs/api/puppeteer.frameaddscripttagoptions.id.md
@@ -0,0 +1,15 @@
---
sidebar_label: FrameAddScriptTagOptions.id
---

# FrameAddScriptTagOptions.id property

Sets the `id` of the script.

**Signature:**

```typescript
interface FrameAddScriptTagOptions {
id?: string;
}
```
13 changes: 7 additions & 6 deletions docs/api/puppeteer.frameaddscripttagoptions.md
Expand Up @@ -12,9 +12,10 @@ export interface FrameAddScriptTagOptions

## Properties

| Property | Modifiers | Type | Description |
| ----------------------------------------------------------- | --------- | ------ | ---------------------------------------------------------------------------------------------------------------- |
| [content?](./puppeteer.frameaddscripttagoptions.content.md) | | string | <i>(Optional)</i> Raw JavaScript content to be injected into the frame. |
| [path?](./puppeteer.frameaddscripttagoptions.path.md) | | string | <i>(Optional)</i> The path to a JavaScript file to be injected into the frame. |
| [type?](./puppeteer.frameaddscripttagoptions.type.md) | | string | <i>(Optional)</i> Set the script's <code>type</code>. Use <code>module</code> in order to load an ES2015 module. |
| [url?](./puppeteer.frameaddscripttagoptions.url.md) | | string | <i>(Optional)</i> the URL of the script to be added. |
| Property | Modifiers | Type | Description |
| ----------------------------------------------------------- | --------- | ------ | ---------------------------------------------------------------------------------------------------------------------- |
| [content?](./puppeteer.frameaddscripttagoptions.content.md) | | string | <i>(Optional)</i> JavaScript to be injected into the frame. |
| [id?](./puppeteer.frameaddscripttagoptions.id.md) | | string | <i>(Optional)</i> Sets the <code>id</code> of the script. |
| [path?](./puppeteer.frameaddscripttagoptions.path.md) | | string | <i>(Optional)</i> Path to a JavaScript file to be injected into the frame. |
| [type?](./puppeteer.frameaddscripttagoptions.type.md) | | string | <i>(Optional)</i> Sets the <code>type</code> of the script. Use <code>module</code> in order to load an ES2015 module. |
| [url?](./puppeteer.frameaddscripttagoptions.url.md) | | string | <i>(Optional)</i> URL of the script to be added. |
2 changes: 1 addition & 1 deletion docs/api/puppeteer.frameaddscripttagoptions.path.md
Expand Up @@ -4,7 +4,7 @@ sidebar_label: FrameAddScriptTagOptions.path

# FrameAddScriptTagOptions.path property

The path to a JavaScript file to be injected into the frame.
Path to a JavaScript file to be injected into the frame.

**Signature:**

Expand Down
2 changes: 1 addition & 1 deletion docs/api/puppeteer.frameaddscripttagoptions.type.md
Expand Up @@ -4,7 +4,7 @@ sidebar_label: FrameAddScriptTagOptions.type

# FrameAddScriptTagOptions.type property

Set the script's `type`. Use `module` in order to load an ES2015 module.
Sets the `type` of the script. Use `module` in order to load an ES2015 module.

**Signature:**

Expand Down
2 changes: 1 addition & 1 deletion docs/api/puppeteer.frameaddscripttagoptions.url.md
Expand Up @@ -4,7 +4,7 @@ sidebar_label: FrameAddScriptTagOptions.url

# FrameAddScriptTagOptions.url property

the URL of the script to be added.
URL of the script to be added.

**Signature:**

Expand Down
18 changes: 7 additions & 11 deletions docs/api/puppeteer.page.addscripttag.md
Expand Up @@ -10,27 +10,23 @@ Adds a `<script>` tag into the page with the desired URL or content.

```typescript
class Page {
addScriptTag(options: {
url?: string;
path?: string;
content?: string;
type?: string;
id?: string;
}): Promise<ElementHandle<HTMLScriptElement>>;
addScriptTag(
options: FrameAddScriptTagOptions
): Promise<ElementHandle<HTMLScriptElement>>;
}
```

## Parameters

| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------------------ | ----------- |
| options | { url?: string; path?: string; content?: string; type?: string; id?: string; } | |
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------------- | ----------------------- |
| options | [FrameAddScriptTagOptions](./puppeteer.frameaddscripttagoptions.md) | Options for the script. |

**Returns:**

Promise&lt;[ElementHandle](./puppeteer.elementhandle.md)&lt;HTMLScriptElement&gt;&gt;

Promise which resolves to the added tag when the script's onload fires or when the script content was injected into frame.
An [element handle](./puppeteer.elementhandle.md) to the injected `<script>` element.

## Remarks

Expand Down
68 changes: 60 additions & 8 deletions src/common/Frame.ts
Expand Up @@ -49,24 +49,29 @@ export interface FrameWaitForFunctionOptions {
*/
export interface FrameAddScriptTagOptions {
/**
* the URL of the script to be added.
* URL of the script to be added.
*/
url?: string;
/**
* The path to a JavaScript file to be injected into the frame.
* Path to a JavaScript file to be injected into the frame.
*
* @remarks
* If `path` is a relative path, it is resolved relative to the current
* working directory (`process.cwd()` in Node.js).
*/
path?: string;
/**
* Raw JavaScript content to be injected into the frame.
* JavaScript to be injected into the frame.
*/
content?: string;
/**
* Set the script's `type`. Use `module` in order to load an ES2015 module.
* Sets the `type` of the script. Use `module` in order to load an ES2015 module.
*/
type?: string;
/**
* Sets the `id` of the script.
*/
id?: string;
}

/**
Expand Down Expand Up @@ -736,14 +741,61 @@ export class Frame {
* Adds a `<script>` tag into the page with the desired url or content.
*
* @param options - Options for the script.
* @returns a promise that resolves to the added tag when the script's
* `onload` event fires or when the script content was injected into the
* frame.
* @returns An {@link ElementHandle | element handle} to the injected
* `<script>` element.
*/
async addScriptTag(
options: FrameAddScriptTagOptions
): Promise<ElementHandle<HTMLScriptElement>> {
return this.worlds[MAIN_WORLD].addScriptTag(options);
let {content} = options;
const {path} = options;
if (options.url && path && content) {
throw new Error(
'Exactly one of `url`, `path`, or `content` may be specified.'
);
}
if (path) {
let fs;
try {
fs = (await import('fs')).promises;
} catch (error) {
if (error instanceof TypeError) {
throw new Error(
'Can only pass a filepath to addScriptTag in a Node-like environment.'
);
}
throw error;
}
content = await fs.readFile(path, 'utf8');
content += `//# sourceURL=${path.replace(/\n/g, '')}`;
options.content = content;
}

return this.worlds[PUPPETEER_WORLD].evaluateHandle(
async ({url, id, type, content}) => {
const script = document.createElement('script');
if (url) {
script.src = url;
}
if (id) {
script.id = id;
}
if (type) {
script.type = type;
}
if (content) {
script.text = content;
}
const promise = new Promise((res, rej) => {
script.onload = res;
script.onerror = rej;
});
document.head.appendChild(script);
await promise;
return script;
},
options
);
}

/**
Expand Down
111 changes: 6 additions & 105 deletions src/common/IsolatedWorld.ts
Expand Up @@ -117,7 +117,7 @@ export class IsolatedWorld {
#frame: Frame;
#injected: boolean;
#document?: ElementHandle<Document>;
#contextPromise = createDeferredPromise<ExecutionContext>();
#context = createDeferredPromise<ExecutionContext>();
OrKoN marked this conversation as resolved.
Show resolved Hide resolved
#detached = false;

// Set of bindings that have been registered in the current context.
Expand Down Expand Up @@ -165,22 +165,22 @@ export class IsolatedWorld {

clearContext(): void {
this.#document = undefined;
this.#contextPromise = createDeferredPromise();
this.#context = createDeferredPromise();
}

setContext(context: ExecutionContext): void {
if (this.#injected) {
context.evaluate(injectedSource).catch(debugError);
}
this.#ctxBindings.clear();
this.#contextPromise.resolve(context);
this.#context.resolve(context);
for (const waitTask of this._waitTasks) {
waitTask.rerun();
}
}

hasContext(): boolean {
return this.#contextPromise.resolved();
return this.#context.resolved();
}

_detach(): void {
Expand All @@ -199,10 +199,10 @@ export class IsolatedWorld {
`Execution context is not available in detached frame "${this.#frame.url()}" (are you trying to evaluate?)`
);
}
if (this.#contextPromise === null) {
if (this.#context === null) {
throw new Error(`Execution content promise is missing`);
}
return this.#contextPromise;
return this.#context;
}

async evaluateHandle<
Expand Down Expand Up @@ -334,105 +334,6 @@ export class IsolatedWorld {
}
}

/**
* Adds a script tag into the current context.
*
* @remarks
* You can pass a URL, filepath or string of contents. Note that when running Puppeteer
* in a browser environment you cannot pass a filepath and should use either
* `url` or `content`.
*/
async addScriptTag(options: {
url?: string;
path?: string;
content?: string;
id?: string;
type?: string;
}): Promise<ElementHandle<HTMLScriptElement>> {
const {
url = null,
path = null,
content = null,
id = '',
type = '',
} = options;
if (url !== null) {
try {
const context = await this.executionContext();
return await context.evaluateHandle(addScriptUrl, url, id, type);
} catch (error) {
throw new Error(`Loading script from ${url} failed`);
}
}

if (path !== null) {
let fs;
try {
fs = (await import('fs')).promises;
} catch (error) {
if (error instanceof TypeError) {
throw new Error(
'Can only pass a filepath to addScriptTag in a Node-like environment.'
);
}
throw error;
}
let contents = await fs.readFile(path, 'utf8');
contents += '//# sourceURL=' + path.replace(/\n/g, '');
const context = await this.executionContext();
return await context.evaluateHandle(addScriptContent, contents, id, type);
}

if (content !== null) {
const context = await this.executionContext();
return await context.evaluateHandle(addScriptContent, content, id, type);
}

throw new Error(
'Provide an object with a `url`, `path` or `content` property'
);

async function addScriptUrl(url: string, id: string, type: string) {
const script = document.createElement('script');
script.src = url;
if (id) {
script.id = id;
}
if (type) {
script.type = type;
}
const promise = new Promise((res, rej) => {
script.onload = res;
script.onerror = rej;
});
document.head.appendChild(script);
await promise;
return script;
}

function addScriptContent(
content: string,
id: string,
type = 'text/javascript'
) {
const script = document.createElement('script');
script.type = type;
script.text = content;
if (id) {
script.id = id;
}
let error = null;
script.onerror = e => {
return (error = e);
};
document.head.appendChild(script);
if (error) {
throw error;
}
return script;
}
}

/**
* Adds a style tag into the current context.
*
Expand Down