2020-06-17 11:36:09 +00:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
* (c) Copyright Ascensio System Limited 2010-2020
|
|
|
|
*
|
|
|
|
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
|
|
|
|
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
|
|
|
|
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
|
|
|
|
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
|
|
|
|
*
|
|
|
|
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
|
|
|
|
*
|
|
|
|
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
|
|
|
|
*
|
|
|
|
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
|
|
|
|
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
|
|
|
|
*
|
|
|
|
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
|
|
|
|
* relevant author attributions when distributing the software. If the display of the logo in its graphic
|
|
|
|
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
|
|
|
|
* in every copy of the program you distribute.
|
|
|
|
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2020-06-17 11:00:28 +00:00
|
|
|
var system = require('system')
|
|
|
|
, page = require('webpage').create()
|
|
|
|
, fs = require('fs')
|
|
|
|
, optUtils = require('./options');
|
|
|
|
|
|
|
|
// Read in arguments
|
|
|
|
var options = JSON.parse(system.args[1]);
|
|
|
|
var site = system.args[2];
|
|
|
|
var path = system.args[3];
|
|
|
|
var streaming = options.streaming;
|
|
|
|
|
|
|
|
page.viewportSize = {
|
|
|
|
width: options.windowSize.width
|
|
|
|
, height: options.windowSize.height
|
|
|
|
};
|
|
|
|
|
|
|
|
// Capture JS errors and write them to stderr
|
|
|
|
page.onError = function(msg, trace) {
|
|
|
|
var msgStack = ['ERROR: ' + msg];
|
|
|
|
|
|
|
|
if (trace && trace.length) {
|
|
|
|
msgStack.push('TRACE:');
|
|
|
|
trace.forEach(function(t) {
|
|
|
|
msgStack.push(' -> ' + t.file + ': ' + t.line +
|
|
|
|
(t.function ? ' (in function "' + t.function +'")' : ''));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
system.stderr.write(msgStack.join('\n'));
|
|
|
|
};
|
|
|
|
|
|
|
|
if (options.errorIfStatusIsNot200) {
|
|
|
|
page.onResourceReceived = function(response) {
|
|
|
|
|
|
|
|
// If request to the page is not 200 status, fail.
|
|
|
|
if (response.url === site && response.status !== 200) {
|
|
|
|
system.stderr.write('Status must be 200; is ' + response.status);
|
|
|
|
page.close();
|
|
|
|
phantom.exit(0);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle cookies
|
|
|
|
if (Array.isArray(options.cookies)) {
|
|
|
|
for (var i=0; i<options.cookies.length; ++i) {
|
|
|
|
phantom.addCookie(options.cookies[i]);
|
|
|
|
}
|
|
|
|
} else if (options.cookies === null) {
|
|
|
|
phantom.cookiesEnabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register user-provided callbacks
|
|
|
|
optUtils.phantomCallback.forEach(function(cbName) {
|
|
|
|
var cb = options[cbName];
|
|
|
|
|
|
|
|
if (cbName === 'onCallback' && options.takeShotOnCallback) return;
|
|
|
|
if (cbName === 'onLoadFinished' && !options.takeShotOnCallback) return;
|
|
|
|
|
|
|
|
if (cb) {
|
|
|
|
page[cbName] = buildEvaluationFn(cb.fn, cb.context);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// Set the phantom page properties
|
|
|
|
var toOverwrite = optUtils.mergeObjects(
|
|
|
|
optUtils.filterObject(options, optUtils.phantomPage)
|
|
|
|
, page);
|
|
|
|
|
|
|
|
optUtils.phantomPage.forEach(function(key) {
|
|
|
|
if (toOverwrite[key]) page[key] = toOverwrite[key];
|
|
|
|
});
|
|
|
|
|
|
|
|
// The function that actually performs the screen rendering
|
|
|
|
var _takeScreenshot = function(status) {
|
|
|
|
if (status === 'fail') {
|
|
|
|
page.close();
|
|
|
|
phantom.exit(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait `options.renderDelay` seconds for the page's JS to kick in
|
|
|
|
window.setTimeout(function () {
|
|
|
|
|
|
|
|
// Handle customCSS option
|
|
|
|
if (options.customCSS) {
|
|
|
|
page.evaluate(function(customCSS) {
|
|
|
|
var style = document.createElement('style');
|
|
|
|
var text = document.createTextNode(customCSS);
|
|
|
|
style.setAttribute('type', 'text/css');
|
|
|
|
style.appendChild(text);
|
|
|
|
document.head.insertBefore(style, document.head.firstChild);
|
|
|
|
}, options.customCSS);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.captureSelector) {
|
|
|
|
|
|
|
|
// Handle captureSelector option
|
|
|
|
page.clipRect = page.evaluate(function(selector, zoomFactor) {
|
|
|
|
try {
|
|
|
|
var selectorClipRect =
|
|
|
|
document.querySelector(selector).getBoundingClientRect();
|
|
|
|
|
|
|
|
return {
|
|
|
|
top: selectorClipRect.top * zoomFactor
|
|
|
|
, left: selectorClipRect.left * zoomFactor
|
|
|
|
, width: selectorClipRect.width * zoomFactor
|
|
|
|
, height: selectorClipRect.height * zoomFactor
|
|
|
|
};
|
|
|
|
} catch (e) {
|
|
|
|
throw new Error("Unable to fetch bounds for element " + selector);
|
|
|
|
}
|
|
|
|
}, options.captureSelector, options.zoomFactor);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
//Set the rectangle of the page to render
|
|
|
|
page.clipRect = {
|
|
|
|
top: options.shotOffset.top
|
|
|
|
, left: options.shotOffset.left
|
|
|
|
, width: pixelCount(page, 'width', options.shotSize.width)
|
|
|
|
- options.shotOffset.right
|
|
|
|
, height: pixelCount(page, 'height', options.shotSize.height)
|
|
|
|
- options.shotOffset.bottom
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle defaultWhiteBackgroud option
|
|
|
|
if (options.defaultWhiteBackground) {
|
|
|
|
page.evaluate(function() {
|
|
|
|
var style = document.createElement('style');
|
|
|
|
var text = document.createTextNode('body { background: #fff }');
|
|
|
|
style.setAttribute('type', 'text/css');
|
|
|
|
style.appendChild(text);
|
|
|
|
document.head.insertBefore(style, document.head.firstChild);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render, clean up, and exit
|
|
|
|
if (!streaming) {
|
|
|
|
page.render(path, {quality: options.quality});
|
|
|
|
} else {
|
|
|
|
console.log(page.renderBase64(options.streamType));
|
|
|
|
}
|
|
|
|
|
|
|
|
page.close();
|
|
|
|
phantom.exit(0);
|
|
|
|
}, options.renderDelay);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Avoid overwriting the user-provided onPageLoaded or onCallback options
|
|
|
|
var takeScreenshot;
|
|
|
|
|
|
|
|
if (options.onCallback && options.takeShotOnCallback) {
|
|
|
|
takeScreenshot = function(data) {
|
|
|
|
buildEvaluationFn(
|
|
|
|
options.onCallback.fn
|
|
|
|
, options.onCallback.context)(data);
|
|
|
|
|
|
|
|
if (data == 'takeShot') {
|
|
|
|
_takeScreenshot();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} else if (options.onLoadFinished && !options.takeShotOnCallback) {
|
|
|
|
takeScreenshot = function(status) {
|
|
|
|
buildEvaluationFn(
|
|
|
|
options.onLoadFinished.fn
|
|
|
|
, options.onLoadFinished.context)(status);
|
|
|
|
_takeScreenshot(status);
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
takeScreenshot = _takeScreenshot;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kick off the page loading
|
|
|
|
if (options.siteType == 'url') {
|
|
|
|
if (options.takeShotOnCallback) {
|
|
|
|
page.onCallback = takeScreenshot;
|
|
|
|
page.open(site);
|
|
|
|
} else {
|
|
|
|
page.open(site, takeScreenshot);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
try {
|
|
|
|
var f = fs.open(site, 'r');
|
|
|
|
var pageContent = f.read();
|
|
|
|
f.close();
|
|
|
|
|
|
|
|
page[options.takeShotOnCallback
|
|
|
|
? 'onCallback'
|
|
|
|
: 'onLoadFinished'] = takeScreenshot;
|
|
|
|
|
|
|
|
// Set content to be provided HTML
|
|
|
|
page.setContent(pageContent, '');
|
|
|
|
|
|
|
|
// Issue reload to pull down any CSS or JS
|
|
|
|
page.reload();
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
phantom.exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Given a shotSize dimension, return the actual number of pixels in the
|
|
|
|
* dimension that phantom should render.
|
|
|
|
*
|
|
|
|
* @param (Object) page
|
|
|
|
* @param (String) dimension
|
|
|
|
* @param (String or Number) value
|
|
|
|
*/
|
|
|
|
function pixelCount(page, dimension, value) {
|
|
|
|
|
|
|
|
// Determine the page's dimensions
|
|
|
|
var pageDimensions = page.evaluate(function(zoomFactor) {
|
|
|
|
var body = document.body || {};
|
|
|
|
var documentElement = document.documentElement || {};
|
|
|
|
return {
|
|
|
|
width: Math.max(
|
|
|
|
body.offsetWidth
|
|
|
|
, body.scrollWidth
|
|
|
|
, documentElement.clientWidth
|
|
|
|
, documentElement.scrollWidth
|
|
|
|
, documentElement.offsetWidth
|
|
|
|
) * zoomFactor
|
|
|
|
, height: Math.max(
|
|
|
|
body.offsetHeight
|
|
|
|
, body.scrollHeight
|
|
|
|
, documentElement.clientHeight
|
|
|
|
, documentElement.scrollHeight
|
|
|
|
, documentElement.offsetHeight
|
|
|
|
) * zoomFactor
|
|
|
|
};
|
|
|
|
}, options.zoomFactor || 1);
|
|
|
|
|
|
|
|
var x = {
|
|
|
|
window: page.viewportSize[dimension]
|
|
|
|
, all: pageDimensions[dimension]
|
|
|
|
}[value] || value;
|
|
|
|
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Bind the function `fn` to the context `context` in a serializable manner.
|
|
|
|
* A tiny bit of a hack.
|
|
|
|
*
|
|
|
|
* @param (String) fn
|
|
|
|
* @param (Object) context
|
|
|
|
*/
|
|
|
|
function buildEvaluationFn(fn, context) {
|
|
|
|
return function() {
|
|
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
page.evaluate(function(fn, context, args) {
|
|
|
|
eval('(' + fn + ')').apply(context, args);
|
|
|
|
}, fn, context, args);
|
|
|
|
};
|
|
|
|
}
|