@jsenv/ast 5.0.1 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/html/html_analysis.js +4 -0
- package/src/html/html_inline_content_url.js +50 -0
- package/src/inline_content_url.js +33 -0
- package/src/js/apply_babel_plugins.js +13 -3
- package/src/js/js_inline_content_url.js +19 -0
- package/src/js/js_static_analysis/helpers.js +2 -2
- package/src/js/js_static_analysis/json_parse.js +5 -1
- package/src/js/js_static_analysis/new_blob.js +7 -3
- package/src/js/js_static_analysis/new_inline_content.js +8 -3
- package/src/js/js_static_analysis/web_worker_entry_point.js +2 -2
- package/src/js/parse_js_urls.js +46 -0
- package/src/js/parse_js_with_acorn.js +10 -0
- package/src/main.js +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/ast",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Parse and transform HTML,CSS and JS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@babel/parser": "7.22.7",
|
|
37
|
+
"@jsenv/urls": "2.2.0",
|
|
37
38
|
"acorn": "8.10.0",
|
|
38
39
|
"acorn-import-assertions": "1.9.0",
|
|
39
40
|
"acorn-walk": "8.2.0",
|
|
40
41
|
"parse5": "7.1.2",
|
|
41
|
-
"postcss": "8.4.
|
|
42
|
+
"postcss": "8.4.27",
|
|
42
43
|
"postcss-value-parser": "4.2.0"
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -6,18 +6,21 @@ export const analyzeScriptNode = (scriptNode) => {
|
|
|
6
6
|
return {
|
|
7
7
|
type: "js_classic",
|
|
8
8
|
contentType: "text/javascript",
|
|
9
|
+
extension: ".js",
|
|
9
10
|
};
|
|
10
11
|
}
|
|
11
12
|
if (typeAttribute === "module") {
|
|
12
13
|
return {
|
|
13
14
|
type: "js_module",
|
|
14
15
|
contentType: "text/javascript",
|
|
16
|
+
extension: ".js",
|
|
15
17
|
};
|
|
16
18
|
}
|
|
17
19
|
if (typeAttribute === "importmap") {
|
|
18
20
|
return {
|
|
19
21
|
type: "importmap",
|
|
20
22
|
contentType: "application/importmap+json",
|
|
23
|
+
extension: ".importmap",
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
// jsx
|
|
@@ -74,6 +77,7 @@ export const analyzeScriptNode = (scriptNode) => {
|
|
|
74
77
|
return {
|
|
75
78
|
type: "text",
|
|
76
79
|
contentType: "text/plain",
|
|
80
|
+
extension: ".txt",
|
|
77
81
|
};
|
|
78
82
|
};
|
|
79
83
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { injectQueryParams, urlToExtension, urlToBasename } from "@jsenv/urls";
|
|
2
|
+
|
|
3
|
+
import { generateUrlForInlineContent } from "../inline_content_url.js";
|
|
4
|
+
import { getHtmlNodeAttribute } from "./html_node_attributes.js";
|
|
5
|
+
import { getHtmlNodePosition } from "./html_node_position.js";
|
|
6
|
+
import { analyzeScriptNode } from "./html_analysis.js";
|
|
7
|
+
|
|
8
|
+
export const getUrlForContentInsideHtml = (node, { htmlUrl, url }) => {
|
|
9
|
+
let externalSpecifierAttributeName;
|
|
10
|
+
let basename;
|
|
11
|
+
let extension;
|
|
12
|
+
|
|
13
|
+
if (node.nodeName === "script") {
|
|
14
|
+
externalSpecifierAttributeName = "inlined-from-src";
|
|
15
|
+
const scriptAnalysisResult = analyzeScriptNode(node);
|
|
16
|
+
extension = scriptAnalysisResult.extension;
|
|
17
|
+
} else if (node.nodeName === "style") {
|
|
18
|
+
externalSpecifierAttributeName = "inlined-from-href";
|
|
19
|
+
extension = ".css";
|
|
20
|
+
} else if (node.nodeName === "link") {
|
|
21
|
+
basename = urlToBasename(url);
|
|
22
|
+
extension = urlToExtension(url);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (externalSpecifierAttributeName) {
|
|
26
|
+
const externalSpecifier = getHtmlNodeAttribute(
|
|
27
|
+
node,
|
|
28
|
+
externalSpecifierAttributeName,
|
|
29
|
+
);
|
|
30
|
+
if (externalSpecifier) {
|
|
31
|
+
const inlineContentUrl = injectQueryParams(externalSpecifier, {
|
|
32
|
+
inlined: "",
|
|
33
|
+
});
|
|
34
|
+
return inlineContentUrl;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const { line, column, lineEnd, columnEnd } = getHtmlNodePosition(node, {
|
|
38
|
+
preferOriginal: true,
|
|
39
|
+
});
|
|
40
|
+
const inlineContentUrl = generateUrlForInlineContent({
|
|
41
|
+
url: htmlUrl,
|
|
42
|
+
basename,
|
|
43
|
+
extension,
|
|
44
|
+
line,
|
|
45
|
+
column,
|
|
46
|
+
lineEnd,
|
|
47
|
+
columnEnd,
|
|
48
|
+
});
|
|
49
|
+
return inlineContentUrl;
|
|
50
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { urlToFilename, urlToExtension } from "@jsenv/urls";
|
|
2
|
+
|
|
3
|
+
export const generateUrlForInlineContent = ({
|
|
4
|
+
url,
|
|
5
|
+
extension,
|
|
6
|
+
basename,
|
|
7
|
+
line,
|
|
8
|
+
column,
|
|
9
|
+
lineEnd,
|
|
10
|
+
columnEnd,
|
|
11
|
+
}) => {
|
|
12
|
+
if (extension === undefined) {
|
|
13
|
+
extension = urlToExtension(url);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let generatedName = "";
|
|
17
|
+
if (basename !== undefined) {
|
|
18
|
+
generatedName += basename;
|
|
19
|
+
}
|
|
20
|
+
if (line !== undefined && column !== undefined) {
|
|
21
|
+
generatedName += `L${line}C${column}`;
|
|
22
|
+
if (lineEnd !== undefined && columnEnd !== undefined) {
|
|
23
|
+
generatedName += `-L${lineEnd}C${columnEnd}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const filenameRaw = urlToFilename(url);
|
|
28
|
+
const filename = `${filenameRaw}@${generatedName}${extension}`;
|
|
29
|
+
// ideally we should keep query params from url
|
|
30
|
+
// maybe we could use a custom scheme like "inline:"
|
|
31
|
+
const inlineContentUrl = new URL(filename, url).href;
|
|
32
|
+
return inlineContentUrl;
|
|
33
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* - https://astexplorer.net/
|
|
4
4
|
* - https://bvaughn.github.io/babel-repl
|
|
5
5
|
*/
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
8
|
import { createJsParseError } from "./js_parse_error.js";
|
|
9
9
|
|
|
@@ -63,10 +63,10 @@ export const applyBabelPlugins = async ({
|
|
|
63
63
|
try {
|
|
64
64
|
if (ast) {
|
|
65
65
|
const result = await transformFromAstAsync(ast, input, options);
|
|
66
|
-
return result;
|
|
66
|
+
return normalizeResult(result);
|
|
67
67
|
}
|
|
68
68
|
const result = await transformAsync(input, options);
|
|
69
|
-
return result;
|
|
69
|
+
return normalizeResult(result);
|
|
70
70
|
} catch (error) {
|
|
71
71
|
if (error && error.code === "BABEL_PARSE_ERROR") {
|
|
72
72
|
throw createJsParseError({
|
|
@@ -82,6 +82,16 @@ export const applyBabelPlugins = async ({
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
const normalizeResult = (result) => {
|
|
86
|
+
const { map } = result;
|
|
87
|
+
if (map) {
|
|
88
|
+
map.sources.forEach((source, index) => {
|
|
89
|
+
map.sources[index] = pathToFileURL(source).href;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
};
|
|
94
|
+
|
|
85
95
|
const useTypeScriptExtension = (url) => {
|
|
86
96
|
const { pathname } = new URL(url);
|
|
87
97
|
return pathname.endsWith(".ts") || pathname.endsWith(".tsx");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { injectQueryParams } from "@jsenv/urls";
|
|
2
|
+
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
|
|
3
|
+
|
|
4
|
+
import { generateUrlForInlineContent } from "../inline_content_url.js";
|
|
5
|
+
|
|
6
|
+
export const getUrlForContentInsideJs = (inlineReferenceInfo, { url }) => {
|
|
7
|
+
const { inlinedFromUrl } = inlineReferenceInfo;
|
|
8
|
+
if (inlinedFromUrl) {
|
|
9
|
+
return injectQueryParams(inlinedFromUrl, { inlined: "" });
|
|
10
|
+
}
|
|
11
|
+
return generateUrlForInlineContent({
|
|
12
|
+
url,
|
|
13
|
+
extension: CONTENT_TYPE.asFileExtension(inlineReferenceInfo.contentType),
|
|
14
|
+
line: inlineReferenceInfo.line,
|
|
15
|
+
column: inlineReferenceInfo.column,
|
|
16
|
+
lineEnd: inlineReferenceInfo.lineEnd,
|
|
17
|
+
columnEnd: inlineReferenceInfo.columnEnd,
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const findPropertyNodeByName = (node, name) => {
|
|
2
2
|
if (node.type !== "ObjectExpression") {
|
|
3
3
|
return null;
|
|
4
4
|
}
|
|
@@ -7,7 +7,7 @@ export const getTypePropertyNode = (node) => {
|
|
|
7
7
|
return (
|
|
8
8
|
property.type === "Property" &&
|
|
9
9
|
property.key.type === "Identifier" &&
|
|
10
|
-
property.key.name ===
|
|
10
|
+
property.key.name === name
|
|
11
11
|
);
|
|
12
12
|
});
|
|
13
13
|
};
|
|
@@ -11,13 +11,17 @@ export const isJSONParseCall = (node) => {
|
|
|
11
11
|
);
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export const analyzeJSONParseCall = (
|
|
14
|
+
export const analyzeJSONParseCall = (
|
|
15
|
+
node,
|
|
16
|
+
{ onInlineContent, readInlinedFromUrl },
|
|
17
|
+
) => {
|
|
15
18
|
const nodeHoldingContent = node.arguments[0];
|
|
16
19
|
const contentInfo = extractContentInfo(nodeHoldingContent);
|
|
17
20
|
if (contentInfo) {
|
|
18
21
|
onInlineContent({
|
|
19
22
|
type: "json_parse_first_arg",
|
|
20
23
|
contentType: "application/json",
|
|
24
|
+
inlinedFromUrl: readInlinedFromUrl(node),
|
|
21
25
|
start: nodeHoldingContent.start,
|
|
22
26
|
end: nodeHoldingContent.end,
|
|
23
27
|
line: nodeHoldingContent.loc.start.line,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isStringLiteralNode,
|
|
3
|
-
|
|
3
|
+
findPropertyNodeByName,
|
|
4
4
|
extractContentInfo,
|
|
5
5
|
} from "./helpers.js";
|
|
6
6
|
|
|
@@ -11,7 +11,10 @@ export const isNewBlobCall = (node) => {
|
|
|
11
11
|
node.callee.name === "Blob"
|
|
12
12
|
);
|
|
13
13
|
};
|
|
14
|
-
export const analyzeNewBlobCall = (
|
|
14
|
+
export const analyzeNewBlobCall = (
|
|
15
|
+
node,
|
|
16
|
+
{ onInlineContent, readInlinedFromUrl },
|
|
17
|
+
) => {
|
|
15
18
|
const [firstArg, secondArg] = node.arguments;
|
|
16
19
|
if (!firstArg) {
|
|
17
20
|
return;
|
|
@@ -25,7 +28,7 @@ export const analyzeNewBlobCall = (node, { onInlineContent }) => {
|
|
|
25
28
|
if (firstArg.elements.length !== 1) {
|
|
26
29
|
return;
|
|
27
30
|
}
|
|
28
|
-
const typePropertyNode =
|
|
31
|
+
const typePropertyNode = findPropertyNodeByName(secondArg, "type");
|
|
29
32
|
if (!typePropertyNode) {
|
|
30
33
|
return;
|
|
31
34
|
}
|
|
@@ -40,6 +43,7 @@ export const analyzeNewBlobCall = (node, { onInlineContent }) => {
|
|
|
40
43
|
onInlineContent({
|
|
41
44
|
type: "new_blob_first_arg",
|
|
42
45
|
contentType,
|
|
46
|
+
inlinedFromUrl: readInlinedFromUrl(node),
|
|
43
47
|
start: nodeHoldingContent.start,
|
|
44
48
|
end: nodeHoldingContent.end,
|
|
45
49
|
line: nodeHoldingContent.loc.start.line,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isStringLiteralNode,
|
|
3
|
-
|
|
3
|
+
findPropertyNodeByName,
|
|
4
4
|
extractContentInfo,
|
|
5
5
|
} from "./helpers.js";
|
|
6
6
|
|
|
@@ -11,7 +11,10 @@ export const isNewInlineContentCall = (node) => {
|
|
|
11
11
|
node.callee.name === "__InlineContent__"
|
|
12
12
|
);
|
|
13
13
|
};
|
|
14
|
-
export const analyzeNewInlineContentCall = (
|
|
14
|
+
export const analyzeNewInlineContentCall = (
|
|
15
|
+
node,
|
|
16
|
+
{ onInlineContent, readInlinedFromUrl },
|
|
17
|
+
) => {
|
|
15
18
|
const [firstArg, secondArg] = node.arguments;
|
|
16
19
|
if (!firstArg) {
|
|
17
20
|
return;
|
|
@@ -19,7 +22,7 @@ export const analyzeNewInlineContentCall = (node, { onInlineContent }) => {
|
|
|
19
22
|
if (!secondArg) {
|
|
20
23
|
return;
|
|
21
24
|
}
|
|
22
|
-
const typePropertyNode =
|
|
25
|
+
const typePropertyNode = findPropertyNodeByName(secondArg, "type");
|
|
23
26
|
if (!typePropertyNode) {
|
|
24
27
|
return;
|
|
25
28
|
}
|
|
@@ -27,6 +30,7 @@ export const analyzeNewInlineContentCall = (node, { onInlineContent }) => {
|
|
|
27
30
|
if (!isStringLiteralNode(typePropertyValueNode)) {
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
33
|
+
|
|
30
34
|
const nodeHoldingContent = firstArg;
|
|
31
35
|
const contentType = typePropertyValueNode.value;
|
|
32
36
|
const contentInfo = extractContentInfo(nodeHoldingContent);
|
|
@@ -34,6 +38,7 @@ export const analyzeNewInlineContentCall = (node, { onInlineContent }) => {
|
|
|
34
38
|
onInlineContent({
|
|
35
39
|
type: "new_inline_content_first_arg",
|
|
36
40
|
contentType,
|
|
41
|
+
inlinedFromUrl: readInlinedFromUrl(node),
|
|
37
42
|
start: nodeHoldingContent.start,
|
|
38
43
|
end: nodeHoldingContent.end,
|
|
39
44
|
line: nodeHoldingContent.loc.start.line,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findPropertyNodeByName, isStringLiteralNode } from "./helpers.js";
|
|
2
2
|
import { isNewUrlCall, analyzeNewUrlCall } from "./new_url.js";
|
|
3
3
|
import {
|
|
4
4
|
isImportMetaResolveCall,
|
|
@@ -102,7 +102,7 @@ const analyzeWorkerCallArguments = (
|
|
|
102
102
|
let typePropertyNode;
|
|
103
103
|
const secondArgNode = node.arguments[1];
|
|
104
104
|
if (secondArgNode) {
|
|
105
|
-
typePropertyNode =
|
|
105
|
+
typePropertyNode = findPropertyNodeByName(secondArgNode, "type");
|
|
106
106
|
if (typePropertyNode) {
|
|
107
107
|
const typePropertyValueNode = typePropertyNode.value;
|
|
108
108
|
if (isStringLiteralNode(typePropertyValueNode)) {
|
package/src/js/parse_js_urls.js
CHANGED
|
@@ -73,6 +73,49 @@ export const parseJsUrls = ({
|
|
|
73
73
|
...inlineContentInfo,
|
|
74
74
|
});
|
|
75
75
|
};
|
|
76
|
+
|
|
77
|
+
const getCommentBeforeClosingParenthesis = (
|
|
78
|
+
// either new InlineContent() or JSON.parse() for instance
|
|
79
|
+
callNode,
|
|
80
|
+
) => {
|
|
81
|
+
const args = callNode.arguments;
|
|
82
|
+
let commentMustStartAfter;
|
|
83
|
+
let commentMustEndBefore;
|
|
84
|
+
if (args.length === 0) {
|
|
85
|
+
commentMustStartAfter = callNode.start;
|
|
86
|
+
commentMustEndBefore = callNode.end - 1;
|
|
87
|
+
} else {
|
|
88
|
+
const lastArg = args[args.length - 1];
|
|
89
|
+
commentMustStartAfter = lastArg.start;
|
|
90
|
+
commentMustEndBefore = callNode.end - 1;
|
|
91
|
+
}
|
|
92
|
+
for (const comment of ast.comments) {
|
|
93
|
+
if (
|
|
94
|
+
comment.start > commentMustStartAfter &&
|
|
95
|
+
comment.end < commentMustEndBefore
|
|
96
|
+
) {
|
|
97
|
+
return comment;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const readInlinedFromUrl = (node) => {
|
|
104
|
+
let inlinedFromUrl;
|
|
105
|
+
const commentBeforeClosingParenthesis =
|
|
106
|
+
getCommentBeforeClosingParenthesis(node);
|
|
107
|
+
if (commentBeforeClosingParenthesis) {
|
|
108
|
+
const text = commentBeforeClosingParenthesis.text;
|
|
109
|
+
const inlinedFromUrlIndex = text.indexOf("inlinedFromUrl=");
|
|
110
|
+
if (inlinedFromUrlIndex > -1) {
|
|
111
|
+
inlinedFromUrl = text.slice(
|
|
112
|
+
inlinedFromUrlIndex + "inlinedFromUrl=".length,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return inlinedFromUrl;
|
|
117
|
+
};
|
|
118
|
+
|
|
76
119
|
ancestor(ast, {
|
|
77
120
|
ImportDeclaration: (node) => {
|
|
78
121
|
analyzeImportDeclaration(node, { onUrl });
|
|
@@ -125,6 +168,7 @@ export const parseJsUrls = ({
|
|
|
125
168
|
if (inlineContent && isJSONParseCall(node)) {
|
|
126
169
|
analyzeJSONParseCall(node, {
|
|
127
170
|
onInlineContent,
|
|
171
|
+
readInlinedFromUrl,
|
|
128
172
|
});
|
|
129
173
|
return;
|
|
130
174
|
}
|
|
@@ -163,12 +207,14 @@ export const parseJsUrls = ({
|
|
|
163
207
|
if (inlineContent && isNewInlineContentCall(node)) {
|
|
164
208
|
analyzeNewInlineContentCall(node, {
|
|
165
209
|
onInlineContent,
|
|
210
|
+
readInlinedFromUrl,
|
|
166
211
|
});
|
|
167
212
|
return;
|
|
168
213
|
}
|
|
169
214
|
if (inlineContent && isNewBlobCall(node)) {
|
|
170
215
|
analyzeNewBlobCall(node, {
|
|
171
216
|
onInlineContent,
|
|
217
|
+
readInlinedFromUrl,
|
|
172
218
|
});
|
|
173
219
|
return;
|
|
174
220
|
}
|
|
@@ -5,6 +5,7 @@ import { createJsParseError } from "./js_parse_error.js";
|
|
|
5
5
|
|
|
6
6
|
export const parseJsWithAcorn = ({ js, url, isJsModule }) => {
|
|
7
7
|
const AcornParser = Parser.extend(importAssertions);
|
|
8
|
+
const comments = [];
|
|
8
9
|
|
|
9
10
|
try {
|
|
10
11
|
// https://github.com/acornjs/acorn/tree/master/acorn#interface
|
|
@@ -13,7 +14,16 @@ export const parseJsWithAcorn = ({ js, url, isJsModule }) => {
|
|
|
13
14
|
allowAwaitOutsideFunction: true,
|
|
14
15
|
sourceType: isJsModule ? "module" : "script",
|
|
15
16
|
ecmaVersion: 2022,
|
|
17
|
+
onComment: (block, text, start, end) => {
|
|
18
|
+
comments.push({
|
|
19
|
+
block,
|
|
20
|
+
text,
|
|
21
|
+
start,
|
|
22
|
+
end,
|
|
23
|
+
});
|
|
24
|
+
},
|
|
16
25
|
});
|
|
26
|
+
jsAst.comments = comments;
|
|
17
27
|
return jsAst;
|
|
18
28
|
} catch (e) {
|
|
19
29
|
if (e && e.name === "SyntaxError") {
|
package/src/main.js
CHANGED
|
@@ -33,6 +33,7 @@ export {
|
|
|
33
33
|
getHtmlNodePosition,
|
|
34
34
|
getHtmlNodeAttributePosition,
|
|
35
35
|
} from "./html/html_node_position.js";
|
|
36
|
+
export { getUrlForContentInsideHtml } from "./html/html_inline_content_url.js";
|
|
36
37
|
|
|
37
38
|
// css
|
|
38
39
|
export { parseCssUrls } from "./css/parse_css_urls.js";
|
|
@@ -43,3 +44,6 @@ export { injectJsImport } from "./js/babel_utils.js";
|
|
|
43
44
|
export { parseJsWithAcorn } from "./js/parse_js_with_acorn.js";
|
|
44
45
|
export { parseJsUrls } from "./js/parse_js_urls.js";
|
|
45
46
|
export { visitJsAstUntil } from "./js/visit_js_ast_until.js";
|
|
47
|
+
export { getUrlForContentInsideJs } from "./js/js_inline_content_url.js";
|
|
48
|
+
|
|
49
|
+
export { generateUrlForInlineContent } from "./inline_content_url.js";
|