@nzz/q-server
Version:
**Maintainer**: [Franco Gervasi](https://github.com/fgervasi)
58 lines • 16.2 kB
JSON
{
"sourceFile": "plugins/screenshot/helpers.js",
"activeCommit": 0,
"commits": [
{
"activePatchIndex": 10,
"patches": [
{
"date": 1698915090622,
"content": "Index: \n===================================================================\n--- \n+++ \n"
},
{
"date": 1701686116953,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -298,12 +298,9 @@\n });\n const graphicElement = await page.$(\"#q-screenshot-service-container\");\n const size = await graphicElement.boundingBox();\n const imageBuffer = await page.pdf({\n- printBackground: true,\n- omitBackground: true,\n- width: size.width,\n- height: size.height,\n+ omitBackground: isTransparent,\n });\n \n await page.close();\n return imageBuffer;\n"
},
{
"date": 1701686476608,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -299,8 +299,10 @@\n const graphicElement = await page.$(\"#q-screenshot-service-container\");\n const size = await graphicElement.boundingBox();\n const imageBuffer = await page.pdf({\n omitBackground: isTransparent,\n+ width: size.width,\n+ height: size.height,\n });\n \n await page.close();\n return imageBuffer;\n"
},
{
"date": 1701686510736,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -300,9 +300,8 @@\n const size = await graphicElement.boundingBox();\n const imageBuffer = await page.pdf({\n omitBackground: isTransparent,\n width: size.width,\n- height: size.height,\n });\n \n await page.close();\n return imageBuffer;\n"
},
{
"date": 1701687250611,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -298,10 +298,12 @@\n });\n const graphicElement = await page.$(\"#q-screenshot-service-container\");\n const size = await graphicElement.boundingBox();\n const imageBuffer = await page.pdf({\n- omitBackground: isTransparent,\n+ printBackground: true,\n+ omitBackground: true,\n width: size.width,\n+ height: size.height,\n });\n \n await page.close();\n return imageBuffer;\n"
},
{
"date": 1701687293917,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -298,9 +298,8 @@\n });\n const graphicElement = await page.$(\"#q-screenshot-service-container\");\n const size = await graphicElement.boundingBox();\n const imageBuffer = await page.pdf({\n- printBackground: true,\n omitBackground: true,\n width: size.width,\n height: size.height,\n });\n"
},
{
"date": 1701687301714,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -300,9 +300,9 @@\n const size = await graphicElement.boundingBox();\n const imageBuffer = await page.pdf({\n omitBackground: true,\n width: size.width,\n- height: size.height,\n+ height: size.height + 20,\n });\n \n await page.close();\n return imageBuffer;\n"
},
{
"date": 1701692472602,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -298,11 +298,12 @@\n });\n const graphicElement = await page.$(\"#q-screenshot-service-container\");\n const size = await graphicElement.boundingBox();\n const imageBuffer = await page.pdf({\n+ printBackground: true,\n omitBackground: true,\n width: size.width,\n- height: size.height + 20,\n+ height: size.height,\n });\n \n await page.close();\n return imageBuffer;\n"
},
{
"date": 1702467412801,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -291,8 +291,9 @@\n stylesheets,\n config\n );\n \n+ console.log(\"page\", page)\n const html = await page.content();\n await page.setContent(html, {\n waitUntil: [\"domcontentloaded\", \"load\", \"networkidle0\"],\n });\n"
},
{
"date": 1702475656676,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -291,9 +291,8 @@\n stylesheets,\n config\n );\n \n- console.log('page', page);\n const html = await page.content();\n await page.setContent(html, {\n waitUntil: ['domcontentloaded', 'load', 'networkidle0'],\n });\n"
},
{
"date": 1725527966039,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -24,9 +24,9 @@\n \n return stats.puppeteer\n .launch({\n timeout: 120000, // Reduce chances for timeouts\n- headless: true, // Not needed with xvfb\n+ headless: false, // Not needed with xvfb\n args: [\n \"--single-process\", // Reduce chances for timeouts\n \"--no-sandbox\",\n \"--disable-dev-shm-usage\",\n"
}
],
"date": 1698915090622,
"name": "Commit-0",
"content": "const puppeteer = require(\"puppeteer\");\nconst fetch = require(\"node-fetch\");\nconst PCR = require(\"puppeteer-chromium-resolver\");\nlet isFirstTime = true;\n\n// start a chromium process here\nlet browserPromise = startPcrChromiumProcess();\n\nasync function startPcrChromiumProcess() {\n const option = {\n revision: \"\",\n detectionPath: \"\",\n folderName: \".chromium-browser-snapshots\",\n defaultHosts: [\n \"https://storage.googleapis.com\",\n \"https://npm.taobao.org/mirrors\",\n ],\n hosts: [],\n cacheRevisions: 2,\n retry: 3,\n silent: false,\n };\n const stats = await PCR(option);\n\n return stats.puppeteer\n .launch({\n timeout: 120000, // Reduce chances for timeouts\n headless: true, // Not needed with xvfb\n args: [\n \"--single-process\", // Reduce chances for timeouts\n \"--no-sandbox\",\n \"--disable-dev-shm-usage\",\n \"--font-render-hinting=none\",\n ],\n executablePath: stats.executablePath,\n devtools: true,\n })\n .catch(function (error) {\n Boom.internal(error.message);\n });\n}\n\n// fetches assets and returns a concatenated string containing everything fetched\nasync function getConcatenatedAssets(assets, userAgent) {\n const contentPromises = [];\n for (let asset of assets) {\n if (asset.content) {\n contentPromises.push(Promise.resolve(asset.content));\n }\n if (asset.url) {\n const promise = fetch(asset.url, {\n headers: {\n \"User-Agent\": userAgent,\n },\n }).then((response) => {\n if (response.ok) {\n return response.text();\n } else {\n return \"\";\n }\n });\n contentPromises.push(promise);\n }\n }\n const contents = await Promise.all(contentPromises);\n return contents.join(\"\\n\");\n}\n\nasync function getFinishedPage(\n emptyPageUrl,\n markup,\n scripts,\n stylesheets,\n config,\n server\n) {\n let browser = await browserPromise;\n\n let page;\n\n // try to open the page, if it doesn't work, launch a new browser\n try {\n page = await browser.newPage();\n } catch (err) {\n if (err.stack) {\n server.log([\"error\"], err.stack);\n }\n if (err.isBoom) {\n throw err;\n } else {\n server.log([\"error\"], err.message);\n }\n\n browserPromise = startPcrChromiumProcess();\n browser = await browserPromise;\n page = await browser.newPage();\n }\n\n try {\n // the height of 16384 is the max height of a GL context in chromium or something\n await page.setViewport({\n width: config.width,\n height: 16384,\n deviceScaleFactor: config.dpr,\n });\n\n // Log the console messages of chromium\n page.on(\"console\", (msg) => {\n console.log(msg);\n });\n\n // Log the GPU information of chromium\n if (isFirstTime) {\n await page\n .goto(\"chrome://gpu\", {\n waitUntil: \"networkidle0\",\n timeout: 20 * 60 * 1000,\n })\n .catch((e) => console.log(e));\n\n const content = await page.content();\n console.log(content);\n server.log([\"info\"], content);\n\n isFirstTime = false;\n }\n\n await page.goto(emptyPageUrl);\n } catch (err) {\n if (err.stack) {\n server.log([\"error\"], err.stack);\n }\n if (err.isBoom) {\n throw err;\n } else {\n server.log([\"error\"], err.message);\n }\n\n throw err;\n }\n\n // use strings instead of functions here as it will break in the tests otherwise.\n const userAgent = await page.evaluate(\"navigator.userAgent\");\n\n const styleContent = await getConcatenatedAssets(stylesheets, userAgent);\n\n let bodyStyle = \"margin: 0; padding: 0;\";\n if (config.background) {\n bodyStyle += `background: ${config.background}`;\n }\n\n const content = `\n <!DOCTYPE html>\n <html>\n <head>\n <style>${styleContent}</style>\n </head>\n <body style=\"${bodyStyle}\">\n <div id=\"q-screenshot-service-container\"\n style=\"padding: ${config.padding}; width: ${config.width}px;; height: 100%\">\n ${markup}\n </div>\n </body>\n </html>`;\n\n await page.setContent(content, {\n waitUntil: [\"domcontentloaded\", \"networkidle0\"],\n });\n\n const scriptContent = await getConcatenatedAssets(scripts, userAgent);\n\n if (scriptContent) {\n await page.mainFrame().addScriptTag({\n content: scriptContent,\n });\n }\n\n // Temporary fix - in the long run we need a solution for all Q tools\n if (config.qTool === \"locator_map\") {\n let retry = 0;\n\n const qId = `_q_locator_map${config.qId}`;\n\n while (retry < 2) {\n try {\n if (retry === 1) {\n await page.reload({\n waitUntil: [\"domcontentloaded\", \"networkidle0\"],\n });\n }\n\n await page.waitForFunction(\n (qId) => window[qId]?.isLoaded === true,\n {\n timeout: 20000,\n },\n qId\n );\n\n return page;\n } catch (err) {\n if (err.name === \"TimeoutError\") {\n retry++;\n if (retry >= 2) {\n throw err;\n }\n } else if (err.stack) {\n server.log([\"error\"], err.stack);\n } else if (err.isBoom) {\n throw err;\n } else {\n server.log([\"error\"], err.message);\n }\n }\n }\n }\n\n // wait for the next idle callback (to have most probably finished all work)\n await page.evaluate(`() => {\n return new Promise((resolve, reject) => {\n requestIdleCallback(resolve);\n });\n }`);\n\n // we support a wait parameter, this is a number in milliseconds to wait for\n if (config.waitBeforeScreenshot) {\n await page.waitForTimeout(config.waitBeforeScreenshot);\n }\n\n return page;\n}\n\nasync function getScreenshotImage(\n emptyPageUrl,\n markup,\n scripts,\n stylesheets,\n config,\n server\n) {\n let isTransparent = false;\n if (!config.background || config.background === \"none\") {\n isTransparent = true;\n }\n\n let imageBuffer;\n\n try {\n const page = await getFinishedPage(\n emptyPageUrl,\n markup,\n scripts,\n stylesheets,\n config,\n server\n );\n\n const graphicElement = await page.$(\"#q-screenshot-service-container\");\n\n imageBuffer = await graphicElement.screenshot({\n omitBackground: isTransparent,\n });\n\n await page.close();\n } catch (err) {\n if (err.stack) {\n server.log([\"error\"], err.stack);\n }\n if (err.isBoom) {\n throw err;\n } else {\n server.log([\"error\"], err.message);\n }\n\n throw err;\n }\n\n return imageBuffer;\n}\n\nasync function getPDF(emptyPageUrl, markup, scripts, stylesheets, config) {\n let isTransparent = false;\n if (!config.background || config.background === \"none\") {\n isTransparent = true;\n }\n\n const page = await getFinishedPage(\n emptyPageUrl,\n markup,\n scripts,\n stylesheets,\n config\n );\n\n const html = await page.content();\n await page.setContent(html, {\n waitUntil: [\"domcontentloaded\", \"load\", \"networkidle0\"],\n });\n const graphicElement = await page.$(\"#q-screenshot-service-container\");\n const size = await graphicElement.boundingBox();\n const imageBuffer = await page.pdf({\n printBackground: true,\n omitBackground: true,\n width: size.width,\n height: size.height,\n });\n\n await page.close();\n return imageBuffer;\n}\n\nasync function getScreenshotInfo(\n emptyPageUrl,\n markup,\n scripts,\n stylesheets,\n config,\n server\n) {\n const page = await getFinishedPage(\n emptyPageUrl,\n markup,\n scripts,\n stylesheets,\n config,\n server\n );\n\n const graphicElement = await page.$(\"#q-screenshot-service-container\");\n const bbox = await graphicElement.boundingBox();\n\n return {\n width: bbox.width,\n height: bbox.height,\n };\n}\n\nfunction getInnerWidth(width, padding) {\n if (!width) {\n return null;\n }\n // if padding is given, we need to know if it's in pixel to calculate with the width\n if (padding !== undefined) {\n // split the padding by space\n const units = padding\n .split(\" \")\n .map((paddingPos) => {\n return paddingPos.match(\n new RegExp(/^$|^(([0-9.]+)(px|em|ex|%|in|cm|mm|pt|pc|vh|vw)?([ ])?)$/)\n );\n })\n .filter((match) => {\n return Array.isArray(match);\n })\n .map((match) => {\n if (match[3] === undefined) {\n // if no unit given, px is default\n return \"px\";\n }\n return match[3]; // the original unit or px if it was undefined before\n })\n .reduce((units, unit) => {\n // unique\n if (!units.includes(unit)) {\n units.push(unit);\n }\n return units;\n }, []);\n\n // if we have only pixels, we can move on\n if (units.length === 1 && units[0] === \"px\") {\n const paddingPos = padding.split(\" \");\n if (paddingPos.length === 1) {\n // if there is one padding, this is for left and right, the regex separates the number and the unit\n const pixelNumber = paddingPos[0].match(\n new RegExp(/^$|^(([0-9.]+)(.*)?)$/)\n )[2];\n width = width - 2 * pixelNumber;\n }\n if (paddingPos.length === 2 || paddingPos.length === 3) {\n // for 2 or 3 paddings, we take the second one, as this is left and right padding\n const pixelNumber = paddingPos[1].match(\n new RegExp(/^$|^(([0-9.]+)(.*)?)$/)\n )[2];\n width = width - 2 * pixelNumber;\n }\n if (paddingPos.length === 4) {\n // if we have 4 paddings, the 2nd and 4th are left and right\n const pixelNumberLeft = paddingPos[1].match(\n new RegExp(/^$|^(([0-9.]+)(.*)?)$/)\n )[2];\n const pixelNumberRight = paddingPos[3].match(\n new RegExp(/^$|^(([0-9.]+)(.*)?)$/)\n )[2];\n width = width - pixelNumberLeft - pixelNumberRight;\n }\n }\n }\n\n return width;\n}\n\nmodule.exports = {\n getScreenshotImage: getScreenshotImage,\n getScreenshotInfo: getScreenshotInfo,\n getInnerWidth: getInnerWidth,\n getPDF,\n};\n"
}
]
}