238 lines
12 KiB
JavaScript
238 lines
12 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const util_1 = __importDefault(require("util"));
|
|
const parse5_1 = __importDefault(require("parse5"));
|
|
const class_name_1 = __importDefault(require("../../shadow-ui/class-name"));
|
|
const dom_1 = __importDefault(require("../dom"));
|
|
const parse5_dom_adapter_1 = __importDefault(require("../dom/parse5-dom-adapter"));
|
|
const resource_processor_base_1 = __importDefault(require("./resource-processor-base"));
|
|
const parse5Utils = __importStar(require("../../utils/parse5"));
|
|
const get_bom_1 = __importDefault(require("../../utils/get-bom"));
|
|
const get_storage_key_1 = __importDefault(require("../../utils/get-storage-key"));
|
|
const self_removing_scripts_1 = __importDefault(require("../../utils/self-removing-scripts"));
|
|
const service_routes_1 = __importDefault(require("../../proxy/service-routes"));
|
|
const json_1 = require("../../utils/json");
|
|
const PARSED_BODY_CREATED_EVENT_SCRIPT = parse5_1.default.parseFragment(self_removing_scripts_1.default.onBodyCreated).childNodes[0];
|
|
const PARSED_ORIGIN_FIRST_TITLE_ELEMENT_LOADED_SCRIPT = parse5_1.default.parseFragment(self_removing_scripts_1.default.onOriginFirstTitleLoaded).childNodes[0];
|
|
const PARSED_INIT_SCRIPT_FOR_IFRAME_TEMPLATE = parse5_1.default.parseFragment(self_removing_scripts_1.default.iframeInit).childNodes[0];
|
|
class PageProcessor extends resource_processor_base_1.default {
|
|
constructor() {
|
|
super();
|
|
this.RESTART_PROCESSING = Symbol();
|
|
}
|
|
static _createShadowUIStyleLinkNode(url) {
|
|
return parse5Utils.createElement('link', [
|
|
{ name: 'rel', value: 'stylesheet' },
|
|
{ name: 'type', value: 'text/css' },
|
|
{ name: 'class', value: class_name_1.default.uiStylesheet },
|
|
{ name: 'href', value: url },
|
|
]);
|
|
}
|
|
static _createShadowUIScriptWithUrlNode(url) {
|
|
return parse5Utils.createElement('script', [
|
|
{ name: 'type', value: 'text/javascript' },
|
|
{ name: 'class', value: class_name_1.default.script },
|
|
{ name: 'charset', value: 'UTF-8' },
|
|
{ name: 'src', value: url },
|
|
]);
|
|
}
|
|
static _createShadowUIScriptWithContentNode(content) {
|
|
const scriptAsContentElement = parse5Utils.createElement('script', [
|
|
{ name: 'type', value: 'text/javascript' },
|
|
{ name: 'class', value: class_name_1.default.script },
|
|
{ name: 'charset', value: 'UTF-8' },
|
|
]);
|
|
scriptAsContentElement.childNodes = [parse5Utils.createTextNode(content, scriptAsContentElement)];
|
|
return scriptAsContentElement;
|
|
}
|
|
static _createRestoreStoragesScript(storageKey, storages) {
|
|
const parsedDocumentFragment = parse5_1.default.parseFragment(util_1.default.format(self_removing_scripts_1.default.restoreStorages, storageKey, (0, json_1.stringify)(storages.localStorage), storageKey, (0, json_1.stringify)(storages.sessionStorage)));
|
|
return parsedDocumentFragment.childNodes[0];
|
|
}
|
|
static _getPageProcessingOptions(ctx, urlReplacer) {
|
|
return {
|
|
crossDomainProxyPort: ctx.serverInfo.crossDomainPort,
|
|
isIframe: ctx.isIframe,
|
|
stylesheets: ctx.getInjectableStyles(),
|
|
scripts: ctx.getInjectableScripts(),
|
|
urlReplacer: urlReplacer,
|
|
isIframeWithImageSrc: ctx.contentInfo && ctx.contentInfo.isIframeWithImageSrc,
|
|
};
|
|
}
|
|
static _getPageMetas(metaEls, domAdapter) {
|
|
const metas = [];
|
|
for (let i = 0; i < metaEls.length; i++) {
|
|
metas.push({
|
|
httpEquiv: domAdapter.getAttr(metaEls[i], 'http-equiv'),
|
|
content: domAdapter.getAttr(metaEls[i], 'content'),
|
|
charset: domAdapter.getAttr(metaEls[i], 'charset'),
|
|
});
|
|
}
|
|
return metas;
|
|
}
|
|
static _addPageResources(head, processingOptions, options) {
|
|
const injectedResources = [];
|
|
if (processingOptions.storages && options) {
|
|
const storages = processingOptions.storages;
|
|
const script = PageProcessor._createRestoreStoragesScript((0, get_storage_key_1.default)(options.sessionId, options.host), storages);
|
|
injectedResources.push(script);
|
|
}
|
|
if (processingOptions.stylesheets) {
|
|
processingOptions.stylesheets.forEach(stylesheetUrl => {
|
|
injectedResources.unshift(PageProcessor._createShadowUIStyleLinkNode(stylesheetUrl));
|
|
});
|
|
}
|
|
if (processingOptions.scripts) {
|
|
processingOptions.scripts.forEach(scriptUrl => {
|
|
injectedResources.push(PageProcessor._createShadowUIScriptWithUrlNode(scriptUrl));
|
|
});
|
|
}
|
|
if (processingOptions.embeddedScripts) {
|
|
processingOptions.embeddedScripts.forEach(script => {
|
|
injectedResources.push(PageProcessor._createShadowUIScriptWithContentNode(script));
|
|
});
|
|
}
|
|
if (processingOptions.userScripts) {
|
|
processingOptions.userScripts.forEach(script => {
|
|
injectedResources.push(PageProcessor._createShadowUIScriptWithUrlNode(script));
|
|
});
|
|
}
|
|
for (let i = injectedResources.length - 1; i > -1; i--)
|
|
parse5Utils.insertBeforeFirstScript(injectedResources[i], head);
|
|
return injectedResources;
|
|
}
|
|
static _getTaskScriptNodeIndex(head, ctx) {
|
|
const taskScriptUrls = [
|
|
ctx.resolveInjectableUrl(service_routes_1.default.task),
|
|
ctx.resolveInjectableUrl(service_routes_1.default.iframeTask),
|
|
];
|
|
return parse5Utils.findNodeIndex(head, node => {
|
|
return node.tagName === 'script' &&
|
|
!!node.attrs.find(attr => attr.name === 'class' && attr.value === class_name_1.default.script) &&
|
|
!!node.attrs.find(attr => attr.name === 'src' && taskScriptUrls.includes(attr.value));
|
|
});
|
|
}
|
|
/**
|
|
* Inject the service script after the first title element
|
|
* or after injected resources,
|
|
* if they are placed right after the <title> tag
|
|
**/
|
|
static _addPageOriginFirstTitleParsedScript(head, ctx) {
|
|
const firstTitleNodeIndex = parse5Utils.findNodeIndex(head, node => node.tagName === 'title');
|
|
if (firstTitleNodeIndex === -1)
|
|
return;
|
|
const taskScriptNodeIndex = PageProcessor._getTaskScriptNodeIndex(head, ctx);
|
|
const insertIndex = taskScriptNodeIndex > firstTitleNodeIndex
|
|
? taskScriptNodeIndex + 1
|
|
: firstTitleNodeIndex + 1;
|
|
parse5Utils.appendNode(PARSED_ORIGIN_FIRST_TITLE_ELEMENT_LOADED_SCRIPT, head, insertIndex);
|
|
}
|
|
static _addCharsetInfo(head, charset) {
|
|
parse5Utils.unshiftElement(parse5Utils.createElement('meta', [
|
|
{ name: 'class', value: class_name_1.default.charset },
|
|
{ name: 'charset', value: charset },
|
|
]), head);
|
|
}
|
|
static _changeMetas(metas, domAdapter) {
|
|
if (metas) {
|
|
metas.forEach(meta => {
|
|
// TODO: Figure out how to emulate the tag behavior.
|
|
if (domAdapter.getAttr(meta, 'name') === 'referrer')
|
|
parse5Utils.setAttr(meta, 'content', 'unsafe-url');
|
|
});
|
|
}
|
|
}
|
|
static _prepareHtml(html, processingOpts) {
|
|
if (processingOpts && processingOpts.iframeImageSrc)
|
|
return `<html><body><img src="${processingOpts.iframeImageSrc}" /></body></html>`;
|
|
return html;
|
|
}
|
|
_addRestoreStoragesScript(ctx, head) {
|
|
const storageKey = (0, get_storage_key_1.default)(ctx.session.id, ctx.dest.host);
|
|
const restoreStoragesScript = PageProcessor._createRestoreStoragesScript(storageKey, ctx.restoringStorages);
|
|
parse5Utils.insertBeforeFirstScript(restoreStoragesScript, head);
|
|
}
|
|
static _addBodyCreatedEventScript(body) {
|
|
parse5Utils.unshiftElement(PARSED_BODY_CREATED_EVENT_SCRIPT, body);
|
|
}
|
|
shouldProcessResource(ctx) {
|
|
// NOTE: In some cases, Firefox sends the default accept header for the script.
|
|
// We should not try to process it as a page in this case.
|
|
return (ctx.isPage || ctx.contentInfo.isIframeWithImageSrc) && !ctx.contentInfo.isScript &&
|
|
!ctx.contentInfo.isFileDownload;
|
|
}
|
|
processResource(html, ctx, charset, urlReplacer, isSrcdoc = false) {
|
|
const processingOpts = PageProcessor._getPageProcessingOptions(ctx, urlReplacer);
|
|
const bom = (0, get_bom_1.default)(html);
|
|
if (isSrcdoc)
|
|
processingOpts.isIframe = true;
|
|
html = bom ? html.replace(bom, '') : html;
|
|
PageProcessor._prepareHtml(html, processingOpts);
|
|
const root = parse5_1.default.parse(html);
|
|
const domAdapter = new parse5_dom_adapter_1.default(processingOpts.isIframe, ctx, charset, urlReplacer);
|
|
const elements = parse5Utils.findElementsByTagNames(root, ['base', 'meta', 'head', 'body', 'frameset']);
|
|
const base = elements.base ? elements.base[0] : null;
|
|
const baseUrl = base ? domAdapter.getAttr(base, 'href') : '';
|
|
const metas = elements.meta;
|
|
const head = elements.head[0];
|
|
const body = elements.body ? elements.body[0] : elements.frameset[0];
|
|
if (!isSrcdoc && metas && charset.fromMeta(PageProcessor._getPageMetas(metas, domAdapter)))
|
|
return this.RESTART_PROCESSING;
|
|
const domProcessor = new dom_1.default(domAdapter);
|
|
const replacer = (resourceUrl, resourceType, charsetAttrValue, isCrossDomain = false, isUrlsSet = false) => urlReplacer(resourceUrl, resourceType, charsetAttrValue, baseUrl, isCrossDomain, isUrlsSet);
|
|
domProcessor.forceProxySrcForImage = ctx.session.requestHookEventProvider.hasRequestEventListeners();
|
|
domProcessor.allowMultipleWindows = ctx.session.options.allowMultipleWindows;
|
|
parse5Utils.walkElements(root, el => domProcessor.processElement(el, replacer));
|
|
if (isSrcdoc)
|
|
parse5Utils.unshiftElement(PARSED_INIT_SCRIPT_FOR_IFRAME_TEMPLATE, head);
|
|
else if (!ctx.isHtmlImport) {
|
|
PageProcessor._addPageResources(head, processingOpts);
|
|
PageProcessor._addPageOriginFirstTitleParsedScript(head, ctx);
|
|
PageProcessor._addBodyCreatedEventScript(body);
|
|
if (ctx.restoringStorages && !processingOpts.isIframe)
|
|
this._addRestoreStoragesScript(ctx, head);
|
|
}
|
|
PageProcessor._changeMetas(metas, domAdapter);
|
|
PageProcessor._addCharsetInfo(head, charset.get());
|
|
return (bom || '') + parse5_1.default.serialize(root);
|
|
}
|
|
// NOTE: API for new implementation without request pipeline
|
|
injectResources(html, resources, options) {
|
|
const root = parse5_1.default.parse(html);
|
|
const elements = parse5Utils.findElementsByTagNames(root, ['head', 'body']);
|
|
const head = elements.head[0];
|
|
const body = elements.body[0];
|
|
PageProcessor._addPageResources(head, resources, options);
|
|
PageProcessor._addBodyCreatedEventScript(body);
|
|
return parse5_1.default.serialize(root);
|
|
}
|
|
}
|
|
exports.default = new PageProcessor();module.exports = exports.default;
|
|
|