mirror of
https://github.com/zhigang1992/now-deployment.git
synced 2026-04-02 09:20:58 +08:00
1372 lines
49 KiB
JavaScript
1372 lines
49 KiB
JavaScript
/**
|
|
* This is a stripped down and bundeled version of
|
|
* - https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-cli/chrome-launcher.ts
|
|
* - https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-cli/chrome-finder.ts
|
|
* It be replaced when the ChromeLauncher becomes a module: https://github.com/GoogleChrome/lighthouse/issues/2092
|
|
* But for now this saves us about 60 MB of modules
|
|
*/
|
|
/**
|
|
* @license
|
|
* Copyright 2016 Google Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
'use strict';
|
|
var ChromeLauncher = (() => {
|
|
var childProcess = require('child_process');
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
const mkdirp = require('mkdirp');
|
|
var net = require('net');
|
|
const rimraf = require('rimraf');
|
|
const spawn = childProcess.spawn;
|
|
const execSync = childProcess.execSync;
|
|
const isWindows = process.platform === 'win32';
|
|
const execFileSync = require('child_process').execFileSync;
|
|
const newLineRegex = /\r?\n/;
|
|
function darwin() {
|
|
const suffixes = ['/Contents/MacOS/Google Chrome Canary', '/Contents/MacOS/Google Chrome'];
|
|
const LSREGISTER = '/System/Library/Frameworks/CoreServices.framework' +
|
|
'/Versions/A/Frameworks/LaunchServices.framework' +
|
|
'/Versions/A/Support/lsregister';
|
|
const installations = [];
|
|
execSync(`${LSREGISTER} -dump` +
|
|
' | grep -i \'google chrome\\( canary\\)\\?.app$\'' +
|
|
' | awk \'{$1=""; print $0}\'')
|
|
.toString()
|
|
.split(newLineRegex)
|
|
.forEach((inst) => {
|
|
suffixes.forEach(suffix => {
|
|
const execPath = path.join(inst.trim(), suffix);
|
|
if (canAccess(execPath)) {
|
|
installations.push(execPath);
|
|
}
|
|
});
|
|
});
|
|
// Retains one per line to maintain readability.
|
|
// clang-format off
|
|
const priorities = [
|
|
{ regex: new RegExp(`^${process.env.HOME}/Applications/.*Chrome.app`), weight: 50 },
|
|
{ regex: new RegExp(`^${process.env.HOME}/Applications/.*Chrome Canary.app`), weight: 51 },
|
|
{ regex: /^\/Applications\/.*Chrome.app/, weight: 100 },
|
|
{ regex: /^\/Applications\/.*Chrome Canary.app/, weight: 101 },
|
|
{ regex: /^\/Volumes\/.*Chrome.app/, weight: -2 },
|
|
{ regex: /^\/Volumes\/.*Chrome Canary.app/, weight: -1 }
|
|
];
|
|
// clang-format on
|
|
return sort(installations, priorities);
|
|
}
|
|
/**
|
|
* Look for linux executables in 3 ways
|
|
* 1. Look into LIGHTHOUSE_CHROMIUM_PATH env variable
|
|
* 2. Look into the directories where .desktop are saved on gnome based distro's
|
|
* 3. Look for google-chrome-stable & google-chrome executables by using the which command
|
|
*/
|
|
function linux() {
|
|
let installations = [];
|
|
// 1. Look into LIGHTHOUSE_CHROMIUM_PATH env variable
|
|
if (canAccess(process.env.LIGHTHOUSE_CHROMIUM_PATH)) {
|
|
installations.push(process.env.LIGHTHOUSE_CHROMIUM_PATH);
|
|
}
|
|
// 2. Look into the directories where .desktop are saved on gnome based distro's
|
|
const desktopInstallationFolders = [
|
|
path.join(require('os').homedir(), '.local/share/applications/'),
|
|
'/usr/share/applications/',
|
|
];
|
|
desktopInstallationFolders.forEach(folder => {
|
|
installations = installations.concat(findChromeExecutables(folder));
|
|
});
|
|
// Look for google-chrome-stable & google-chrome executables by using the which command
|
|
const executables = [
|
|
'google-chrome-stable',
|
|
'google-chrome',
|
|
];
|
|
executables.forEach((executable) => {
|
|
try {
|
|
const chromePath = execFileSync('which', [executable]).toString().split(newLineRegex)[0];
|
|
if (canAccess(chromePath)) {
|
|
installations.push(chromePath);
|
|
}
|
|
}
|
|
catch (e) {
|
|
// Not installed.
|
|
}
|
|
});
|
|
if (!installations.length) {
|
|
throw new Error('The environment variable LIGHTHOUSE_CHROMIUM_PATH must be set to ' +
|
|
'executable of a build of Chromium version 54.0 or later.');
|
|
}
|
|
const priorities = [
|
|
{ regex: /chrome-wrapper$/, weight: 51 }, { regex: /google-chrome-stable$/, weight: 50 },
|
|
{ regex: /google-chrome$/, weight: 49 },
|
|
{ regex: new RegExp(process.env.LIGHTHOUSE_CHROMIUM_PATH), weight: 100 }
|
|
];
|
|
return sort(uniq(installations.filter(Boolean)), priorities);
|
|
}
|
|
function win32() {
|
|
const installations = [];
|
|
const suffixes = [
|
|
'\\Google\\Chrome SxS\\Application\\chrome.exe', '\\Google\\Chrome\\Application\\chrome.exe'
|
|
];
|
|
const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)']];
|
|
if (canAccess(process.env.LIGHTHOUSE_CHROMIUM_PATH)) {
|
|
installations.push(process.env.LIGHTHOUSE_CHROMIUM_PATH);
|
|
}
|
|
prefixes.forEach(prefix => suffixes.forEach(suffix => {
|
|
const chromePath = path.join(prefix, suffix);
|
|
if (canAccess(chromePath)) {
|
|
installations.push(chromePath);
|
|
}
|
|
}));
|
|
return installations;
|
|
}
|
|
function sort(installations, priorities) {
|
|
const defaultPriority = 10;
|
|
return installations
|
|
.map((inst) => {
|
|
for (const pair of priorities) {
|
|
if (pair.regex.test(inst)) {
|
|
return [inst, pair.weight];
|
|
}
|
|
}
|
|
return [inst, defaultPriority];
|
|
})
|
|
.sort((a, b) => b[1] - a[1])
|
|
.map(pair => pair[0]);
|
|
}
|
|
function canAccess(file) {
|
|
if (!file) {
|
|
return false;
|
|
}
|
|
try {
|
|
fs.accessSync(file);
|
|
return true;
|
|
}
|
|
catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
function uniq(arr) {
|
|
return Array.from(new Set(arr));
|
|
}
|
|
function findChromeExecutables(folder) {
|
|
const argumentsRegex = /(^[^ ]+).*/; // Take everything up to the first space
|
|
const chromeExecRegex = '^Exec=\/.*\/(google|chrome|chromium)-.*';
|
|
let installations = [];
|
|
if (canAccess(folder)) {
|
|
// Output of the grep & print looks like:
|
|
// /opt/google/chrome/google-chrome --profile-directory
|
|
// /home/user/Downloads/chrome-linux/chrome-wrapper %U
|
|
let execPaths = execSync(`grep -ER "${chromeExecRegex}" ${folder} | awk -F '=' '{print $2}'`)
|
|
.toString()
|
|
.split(newLineRegex)
|
|
.map((execPath) => execPath.replace(argumentsRegex, '$1'));
|
|
execPaths.forEach((execPath) => canAccess(execPath) && installations.push(execPath));
|
|
}
|
|
return installations;
|
|
}
|
|
var chromeFinder = {
|
|
darwin: darwin,
|
|
linux: linux,
|
|
win32: win32
|
|
};
|
|
class ChromeLauncher {
|
|
constructor(opts = {}) {
|
|
this.prepared = false;
|
|
this.pollInterval = 500;
|
|
// choose the first one (default)
|
|
this.autoSelectChrome = defaults(opts.autoSelectChrome, true);
|
|
this.startingUrl = defaults(opts.startingUrl, 'about:blank');
|
|
this.chromeFlags = defaults(opts.chromeFlags, []);
|
|
this.port = defaults(opts.port, 9222);
|
|
}
|
|
flags() {
|
|
const flags = [
|
|
`--remote-debugging-port=${this.port}`,
|
|
// Disable built-in Google Translate service
|
|
'--disable-translate',
|
|
// Disable all chrome extensions entirely
|
|
'--disable-extensions',
|
|
// Disable various background network services, including extension updating,
|
|
// safe browsing service, upgrade detector, translate, UMA
|
|
'--disable-background-networking',
|
|
// Disable fetching safebrowsing lists, likely redundant due to disable-background-networking
|
|
'--safebrowsing-disable-auto-update',
|
|
// Disable syncing to a Google account
|
|
'--disable-sync',
|
|
// Disable reporting to UMA, but allows for collection
|
|
'--metrics-recording-only',
|
|
// Disable installation of default apps on first run
|
|
'--disable-default-apps',
|
|
// Skip first run wizards
|
|
'--no-first-run',
|
|
// Place Chrome profile in a custom location we'll rm -rf later
|
|
`--user-data-dir=${this.TMP_PROFILE_DIR}`
|
|
];
|
|
if (process.platform === 'linux') {
|
|
flags.push('--disable-setuid-sandbox');
|
|
}
|
|
flags.push(...this.chromeFlags);
|
|
flags.push(this.startingUrl);
|
|
return flags;
|
|
}
|
|
prepare() {
|
|
switch (process.platform) {
|
|
case 'darwin':
|
|
case 'linux':
|
|
this.TMP_PROFILE_DIR = unixTmpDir();
|
|
break;
|
|
case 'win32':
|
|
this.TMP_PROFILE_DIR = win32TmpDir();
|
|
break;
|
|
default:
|
|
throw new Error('Platform ' + process.platform + ' is not supported');
|
|
}
|
|
this.outFile = fs.openSync(`${this.TMP_PROFILE_DIR}/chrome-out.log`, 'a');
|
|
this.errFile = fs.openSync(`${this.TMP_PROFILE_DIR}/chrome-err.log`, 'a');
|
|
// fix for Node4
|
|
// you can't pass a fd to fs.writeFileSync
|
|
this.pidFile = `${this.TMP_PROFILE_DIR}/chrome.pid`;
|
|
console.log('ChromeLauncher', `created ${this.TMP_PROFILE_DIR}`);
|
|
this.prepared = true;
|
|
}
|
|
run() {
|
|
if (!this.prepared) {
|
|
this.prepare();
|
|
}
|
|
return Promise.resolve()
|
|
.then(() => {
|
|
const installations = chromeFinder[process.platform]();
|
|
if (installations.length < 1) {
|
|
return Promise.reject(new Error('No Chrome Installations Found'));
|
|
}
|
|
else if (installations.length === 1 || this.autoSelectChrome) {
|
|
return installations[0];
|
|
}
|
|
//return ask('Choose a Chrome installation to use with Lighthouse', installations);
|
|
})
|
|
.then(execPath => this.spawn(execPath));
|
|
}
|
|
spawn(execPath) {
|
|
const spawnPromise = new Promise(resolve => {
|
|
if (this.chrome) {
|
|
console.log('ChromeLauncher', `Chrome already running with pid ${this.chrome.pid}.`);
|
|
return resolve(this.chrome.pid);
|
|
}
|
|
const chrome = spawn(execPath, this.flags(), { detached: true, stdio: ['ignore', this.outFile, this.errFile] });
|
|
this.chrome = chrome;
|
|
fs.writeFileSync(this.pidFile, chrome.pid.toString());
|
|
console.log('ChromeLauncher', `Chrome running with pid ${chrome.pid} on port ${this.port}.`);
|
|
resolve(chrome.pid);
|
|
});
|
|
return spawnPromise.then(pid => Promise.all([pid, this.waitUntilReady()]));
|
|
}
|
|
cleanup(client) {
|
|
if (client) {
|
|
client.removeAllListeners();
|
|
client.end();
|
|
client.destroy();
|
|
client.unref();
|
|
}
|
|
}
|
|
// resolves if ready, rejects otherwise
|
|
isDebuggerReady() {
|
|
return new Promise((resolve, reject) => {
|
|
const client = net.createConnection(this.port);
|
|
client.once('error', err => {
|
|
this.cleanup(client);
|
|
reject(err);
|
|
});
|
|
client.once('connect', () => {
|
|
this.cleanup(client);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
// resolves when debugger is ready, rejects after 10 polls
|
|
waitUntilReady() {
|
|
const launcher = this;
|
|
return new Promise((resolve, reject) => {
|
|
let retries = 0;
|
|
let waitStatus = 'Waiting for browser.';
|
|
(function poll() {
|
|
if (retries === 0) {
|
|
console.log('ChromeLauncher', waitStatus);
|
|
}
|
|
retries++;
|
|
waitStatus += '..';
|
|
console.log('ChromeLauncher', waitStatus);
|
|
launcher.isDebuggerReady()
|
|
.then(() => {
|
|
console.log('ChromeLauncher', waitStatus);
|
|
resolve();
|
|
})
|
|
.catch(err => {
|
|
if (retries > 10) {
|
|
return reject(err);
|
|
}
|
|
delay(launcher.pollInterval).then(poll);
|
|
});
|
|
})();
|
|
});
|
|
}
|
|
kill() {
|
|
return new Promise(resolve => {
|
|
if (this.chrome) {
|
|
this.chrome.on('close', () => {
|
|
this.destroyTmp().then(resolve);
|
|
});
|
|
console.log('ChromeLauncher', 'Killing all Chrome Instances');
|
|
try {
|
|
if (isWindows) {
|
|
execSync(`taskkill /pid ${this.chrome.pid} /T /F`);
|
|
}
|
|
else {
|
|
process.kill(-this.chrome.pid);
|
|
}
|
|
}
|
|
catch (err) {
|
|
console.log('ChromeLauncher', `Chrome could not be killed ${err.message}`);
|
|
}
|
|
delete this.chrome;
|
|
}
|
|
else {
|
|
// fail silently as we did not start chrome
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
destroyTmp() {
|
|
return new Promise(resolve => {
|
|
if (!this.TMP_PROFILE_DIR) {
|
|
return resolve();
|
|
}
|
|
console.log('ChromeLauncher', `Removing ${this.TMP_PROFILE_DIR}`);
|
|
if (this.outFile) {
|
|
fs.closeSync(this.outFile);
|
|
delete this.outFile;
|
|
}
|
|
if (this.errFile) {
|
|
fs.closeSync(this.errFile);
|
|
delete this.errFile;
|
|
}
|
|
rimraf(this.TMP_PROFILE_DIR, () => resolve());
|
|
});
|
|
}
|
|
}
|
|
;
|
|
function defaults(val, def) {
|
|
return typeof val === 'undefined' ? def : val;
|
|
}
|
|
function delay(time) {
|
|
return new Promise(resolve => setTimeout(resolve, time));
|
|
}
|
|
function unixTmpDir() {
|
|
return execSync('mktemp -d -t ncc.XXXXXXX').toString().trim();
|
|
}
|
|
function win32TmpDir() {
|
|
const winTmpPath = process.env.TEMP || process.env.TMP ||
|
|
(process.env.SystemRoot || process.env.windir) + '\\temp';
|
|
const randomNumber = Math.floor(Math.random() * 9e7 + 1e7);
|
|
const tmpdir = path.join(winTmpPath, 'ncc.' + randomNumber);
|
|
mkdirp.sync(tmpdir);
|
|
return tmpdir;
|
|
}
|
|
return ChromeLauncher;
|
|
})();
|
|
/// <reference path="chrome-launcher.ts" />
|
|
var http = require('http'), fs = require('fs'), ws = require('ws'), path = require('path');
|
|
var DEBUG = false;
|
|
var logger;
|
|
var NCC = Object.defineProperties((options_, callback_) => {
|
|
if (typeof (options_) == 'function') {
|
|
callback_ = options_;
|
|
options_ = null;
|
|
}
|
|
var callback = callback_;
|
|
if (options_)
|
|
for (var key in NCC.options)
|
|
if (options_[key] !== undefined)
|
|
NCC.options[key] = options_[key];
|
|
logger = require('tracer').colorConsole({
|
|
format: "[ncc] {{message}}",
|
|
level: NCC.options.logLevel
|
|
});
|
|
var canvas = NCC.createCanvas(undefined, undefined, true);
|
|
var attempts = 0;
|
|
function connect() {
|
|
var url = `http://localhost:${NCC.options.port}/json`;
|
|
http.get(url, res => {
|
|
var rdJson = '';
|
|
res.on('data', chunk => rdJson += chunk);
|
|
res.on('end', () => {
|
|
var ncc_ = JSON.parse(rdJson).find(i => i.title === "ncc" || path.basename(i.url) === "ncc.html");
|
|
if (!ncc_) {
|
|
if (attempts < NCC.options.retry) {
|
|
attempts++;
|
|
logger.info(`connecting [retry ${attempts}/${NCC.options.retry}]`);
|
|
setTimeout(connect, NCC.options.retryDelay);
|
|
}
|
|
else
|
|
logger.error('connection failed');
|
|
return;
|
|
}
|
|
Object.defineProperties(rdp, { ws: { value: new ws(ncc_.webSocketDebuggerUrl) } });
|
|
rdp.ws.on('open', () => {
|
|
logger.info("connected");
|
|
function checkReadyState() {
|
|
rdp.ws.once('message', (data) => {
|
|
data = JSON.parse(data);
|
|
if (!(data.result && data.result.result.value == "complete"))
|
|
return checkReadyState();
|
|
logger.info(`document.readyState is "complete"`);
|
|
rdp((err, res) => {
|
|
if (err)
|
|
logger.error(`[ncc] error: ${err.message}`);
|
|
if (callback)
|
|
err ? callback(err, null) : callback(null, canvas, rdp);
|
|
});
|
|
});
|
|
rdp.ws.send(`{"id":0,"method":"Runtime.evaluate", "params":{"expression":"document.readyState"}}`, err => err && checkReadyState());
|
|
}
|
|
checkReadyState();
|
|
});
|
|
rdp.ws.on('close', () => logger.info("session closed"));
|
|
});
|
|
});
|
|
}
|
|
var index = path.join(__dirname, 'ncc.html');
|
|
var launcher = new ChromeLauncher({
|
|
port: NCC.options.port,
|
|
autoSelectChrome: true,
|
|
startingUrl: NCC.options.headless ? index : '',
|
|
chromeFlags: NCC.options.headless ?
|
|
['--window-size=0,0', '--disable-gpu', '--headless'] :
|
|
[`--app=${index}`]
|
|
});
|
|
const exitHandler = (err) => {
|
|
rdp.ws.terminate();
|
|
launcher.kill().then(() => process.exit(-1));
|
|
};
|
|
process.on('SIGINT', exitHandler);
|
|
process.on('unhandledRejection', exitHandler);
|
|
process.on('rejectionHandled', exitHandler);
|
|
process.on('uncaughtException', exitHandler);
|
|
launcher.run()
|
|
.then((a) => {
|
|
logger.info("chrome started");
|
|
connect();
|
|
})
|
|
.catch(err => {
|
|
return launcher.kill().then(() => {
|
|
logger.error("failed starting chrome");
|
|
throw err;
|
|
}, logger.error);
|
|
});
|
|
return canvas;
|
|
}, {
|
|
options: {
|
|
enumerable: true,
|
|
writable: true,
|
|
value: {
|
|
logLevel: 'info',
|
|
port: 9222,
|
|
retry: 9,
|
|
retryDelay: 500,
|
|
headless: false
|
|
}
|
|
},
|
|
createCanvas: {
|
|
enumerable: true,
|
|
value: function (width, height, main) {
|
|
if (!main) {
|
|
var uid = NCC.uid('canvas');
|
|
rdp(`var ${uid} = document.createElement('canvas')`);
|
|
}
|
|
var canvas = (callback) => {
|
|
rdp(callback ? (err, res) => {
|
|
err ? callback(err, null) : callback(null, canvas);
|
|
} : undefined);
|
|
return canvas;
|
|
};
|
|
CanvasPDM._uid.value = main ? 'canvas' : uid;
|
|
Object.defineProperties(canvas, CanvasPDM);
|
|
CanvasPDM._uid.value = '';
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
return canvas;
|
|
}
|
|
},
|
|
createImage: {
|
|
enumerable: true,
|
|
value: function (src, onload, onerror) {
|
|
var uid = NCC.uid('image');
|
|
rdp(`var ${uid} = new Image()`);
|
|
var image = (callback) => {
|
|
rdp(callback ? (err, res) => {
|
|
err ? callback(err, null) : callback(null, image);
|
|
} : undefined);
|
|
return image;
|
|
};
|
|
ImagePDM._uid.value = uid;
|
|
Object.defineProperties(image, ImagePDM);
|
|
ImagePDM._uid.value = '';
|
|
image.src = src;
|
|
image.onload = onload;
|
|
image.onerror = onerror;
|
|
return image;
|
|
}
|
|
},
|
|
uid: {
|
|
enumerable: false,
|
|
value: type => `${type}_${Math.random().toString(36).slice(2)}`
|
|
}
|
|
});
|
|
// RDP | Remote Debugging Protocol (the bridge to chrome)
|
|
var rdp = Object.defineProperties((_) => {
|
|
if (typeof _ == 'string') {
|
|
logger.log(`< ${_}`);
|
|
rdp.cmd += `${_};`;
|
|
return rdp;
|
|
}
|
|
if (_ !== null) {
|
|
if (rdp.cmd === '') {
|
|
_();
|
|
return rdp;
|
|
}
|
|
rdp.queue.push({
|
|
cmd: rdp.cmd,
|
|
callback: _
|
|
});
|
|
rdp.cmd = '';
|
|
}
|
|
if (!rdp.queue[0] || rdp.req == rdp.queue[0] || !rdp.ws)
|
|
return rdp;
|
|
rdp.req = rdp.queue[0];
|
|
logger.trace(`> ${rdp.req.cmd.split(';').join(';\n ')}`);
|
|
rdp.ws.once('message', data => {
|
|
data.error && logger.error(data.error);
|
|
!data.error && logger.log(data.result);
|
|
data = JSON.parse(data);
|
|
var err = data.error || data.result.wasThrown ? data.result.result.description : null, res = err ? null : data.result.result;
|
|
if (rdp.req.callback)
|
|
rdp.req.callback(err, res);
|
|
rdp.req = rdp.queue.shift();
|
|
rdp(null);
|
|
});
|
|
rdp.ws.send(`{"id":0,"method":"Runtime.evaluate", "params":{"expression":"${rdp.req.cmd}"}}`, err => err && rdp());
|
|
return rdp;
|
|
}, {
|
|
cmd: { enumerable: DEBUG, writable: true, value: '' },
|
|
queue: { enumerable: DEBUG, value: [] }
|
|
});
|
|
var CanvasPDM = {
|
|
// private properties
|
|
_uid: {
|
|
configurable: true,
|
|
enumerable: DEBUG,
|
|
value: "canvas"
|
|
},
|
|
_remote: {
|
|
enumerable: DEBUG,
|
|
set: function (null_) {
|
|
if (null_ === null) {
|
|
if (this._uid == 'canvas')
|
|
return logger.error('you cannot delete the main canvas');
|
|
rdp(`${this._uid} = null`);
|
|
Object.defineProperty(this, '_uid', { value: null });
|
|
this._ctx = null;
|
|
}
|
|
else
|
|
return logger.error('"_remote" can only be set to "null"');
|
|
}
|
|
},
|
|
_ctx: {
|
|
enumerable: DEBUG,
|
|
writable: true,
|
|
value: null
|
|
},
|
|
// Properties || proxies with defaults
|
|
width_: {
|
|
enumerable: DEBUG,
|
|
writable: true,
|
|
value: 300
|
|
},
|
|
height_: {
|
|
enumerable: DEBUG,
|
|
writable: true,
|
|
value: 150
|
|
},
|
|
// Web API: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
|
|
// Properties || getters/setters || https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement#Properties
|
|
width: {
|
|
enumerable: true,
|
|
get: function () {
|
|
return this.width_;
|
|
},
|
|
set: function (width) {
|
|
if (width === undefined)
|
|
return;
|
|
rdp(`${this._uid}.width = ${width}`);
|
|
return this.width_ = width;
|
|
}
|
|
},
|
|
height: {
|
|
enumerable: true,
|
|
get: function () {
|
|
return this.height_;
|
|
},
|
|
set: function (height) {
|
|
if (height === undefined)
|
|
return;
|
|
rdp(`${this._uid}.height = ${height}`);
|
|
return this.height_ = height;
|
|
}
|
|
},
|
|
// Methods || https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement#Methods
|
|
getContext: {
|
|
enumerable: true,
|
|
value: function (contextId) {
|
|
if (contextId == '2d') {
|
|
var uid = this._uid == 'canvas' ? 'context2d' : NCC.uid('context2d');
|
|
rdp(`var ${uid} = ${this._uid}.getContext('2d')`);
|
|
var context2d = (callback) => {
|
|
rdp(callback ? (err, res) => {
|
|
err ? callback(err, null) : callback(null, context2d);
|
|
} : undefined);
|
|
return context2d;
|
|
};
|
|
context2dPDM._uid.value = uid;
|
|
context2dPDM['canvas'].value = this;
|
|
Object.defineProperties(context2d, context2dPDM);
|
|
context2dPDM._uid.value = '';
|
|
return context2d;
|
|
}
|
|
else
|
|
logger.error(`${contextId} is not implemented`);
|
|
}
|
|
},
|
|
toDataURL: {
|
|
enumerable: true,
|
|
value: function (type, args) {
|
|
rdp(`${this._uid}.toDataURL(${`'${type}'` || ''})`);
|
|
return (callback) => {
|
|
rdp((err, res) => {
|
|
if (err)
|
|
return callback(err, null);
|
|
callback(err, res.value);
|
|
});
|
|
};
|
|
}
|
|
}
|
|
};
|
|
var context2dPDM = {
|
|
// private properties
|
|
_uid: {
|
|
enumerable: DEBUG,
|
|
value: ''
|
|
},
|
|
_remote: {
|
|
enumerable: DEBUG,
|
|
set: function (null_) {
|
|
if (null_ === null) {
|
|
rdp(`${this._uid} = null`);
|
|
Object.defineProperty(this, '_uid', { value: null });
|
|
}
|
|
else
|
|
logger.error('"_remote" can only be set to "null"');
|
|
}
|
|
},
|
|
// Attributes || proxies with defaults
|
|
fillStyle_: { writable: true, enumerable: DEBUG, value: '#000000' },
|
|
font_: { writable: true, enumerable: DEBUG, value: '10px sans-serif' },
|
|
globalAlpha_: { writable: true, enumerable: DEBUG, value: 1.0 },
|
|
globalCompositeOperation_: { writable: true, enumerable: DEBUG, value: 'source-over' },
|
|
lineCap_: { writable: true, enumerable: DEBUG, value: 'butt' },
|
|
lineDashOffset_: { writable: true, enumerable: DEBUG, value: 0 },
|
|
lineJoin_: { writable: true, enumerable: DEBUG, value: 'miter' },
|
|
lineWidth_: { writable: true, enumerable: DEBUG, value: 1.0 },
|
|
miterLimit_: { writable: true, enumerable: DEBUG, value: 10 },
|
|
shadowBlur_: { writable: true, enumerable: DEBUG, value: 0 },
|
|
shadowColor_: { writable: true, enumerable: DEBUG, value: 'rgba(0, 0, 0, 0)' },
|
|
shadowOffsetX_: { writable: true, enumerable: DEBUG, value: 0 },
|
|
shadowOffsetY_: { writable: true, enumerable: DEBUG, value: 0 },
|
|
strokeStyle_: { writable: true, enumerable: DEBUG, value: '#000000' },
|
|
textAlign_: { writable: true, enumerable: DEBUG, value: 'start' },
|
|
textBaseline_: { writable: true, enumerable: DEBUG, value: 'alphabetic' },
|
|
webkitBackingStorePixelRatio_: { writable: true, enumerable: DEBUG, value: 1 },
|
|
webkitImageSmoothingEnabled_: { writable: true, enumerable: DEBUG, value: true },
|
|
// Web API: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingcontext2d
|
|
// Attributes || getters/setters || https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingcontext2d#Attributes
|
|
canvas: {
|
|
enumerable: true, value: null // will be overridden on creation
|
|
},
|
|
fillStyle: {
|
|
enumerable: true, get: function () { return this.fillStyle_; },
|
|
set: function (fillStyle) {
|
|
rdp(`${this._uid}.fillStyle = ${fillStyle._uid || `'${fillStyle}'`}`);
|
|
return this.fillStyle_ = fillStyle;
|
|
}
|
|
},
|
|
font: {
|
|
enumerable: true, get: function () { return this.font_; },
|
|
set: function (font) {
|
|
rdp(`${this._uid}.font = '${font}'`);
|
|
return this.font_ = font;
|
|
}
|
|
},
|
|
globalAlpha: {
|
|
enumerable: true, get: function () { return this.globalAlpha_; },
|
|
set: function (globalAlpha) {
|
|
rdp(`${this._uid}.globalAlpha = ${globalAlpha}`);
|
|
return this.globalAlpha_ = globalAlpha;
|
|
}
|
|
},
|
|
globalCompositeOperation: {
|
|
enumerable: true, get: function () { return this.globalCompositeOperation_; },
|
|
set: function (globalCompositeOperation) {
|
|
rdp(`${this._uid}.globalCompositeOperation = '${globalCompositeOperation}'`);
|
|
return this.globalCompositeOperation_ = globalCompositeOperation;
|
|
}
|
|
},
|
|
lineCap: {
|
|
enumerable: true, get: function () { return this.lineCap_; },
|
|
set: function (lineCap) {
|
|
rdp(`${this._uid}.lineCap = '${lineCap}'`);
|
|
return this.lineCap_ = lineCap;
|
|
}
|
|
},
|
|
lineDashOffset: {
|
|
enumerable: true, get: function () { return this.lineDashOffset_; },
|
|
set: function (lineDashOffset) {
|
|
rdp(`${this._uid}.lineDashOffset = ${lineDashOffset}`);
|
|
return this.lineDashOffset_ = lineDashOffset;
|
|
}
|
|
},
|
|
lineJoin: {
|
|
enumerable: true, get: function () { return this.lineJoin_; },
|
|
set: function (lineJoin) {
|
|
rdp(`${this._uid}.lineJoin = '${lineJoin}'`);
|
|
return this.lineJoin_ = lineJoin;
|
|
}
|
|
},
|
|
lineWidth: {
|
|
enumerable: true, get: function () { return this.lineWidth_; },
|
|
set: function (lineWidth) {
|
|
rdp(`${this._uid}.lineWidth = ${lineWidth}`);
|
|
return this.lineWidth_ = lineWidth;
|
|
}
|
|
},
|
|
miterLimit: {
|
|
enumerable: true, get: function () { return this.miterLimit_; },
|
|
set: function (miterLimit) {
|
|
rdp(`${this._uid}.miterLimit = ${miterLimit}`);
|
|
return this.miterLimit_ = miterLimit;
|
|
}
|
|
},
|
|
shadowBlur: {
|
|
enumerable: true, get: function () { return this.shadowBlur_; },
|
|
set: function (shadowBlur) {
|
|
rdp(`${this._uid}.shadowBlur = ${shadowBlur}`);
|
|
return this.shadowBlur_ = shadowBlur;
|
|
}
|
|
},
|
|
shadowColor: {
|
|
enumerable: true, get: function () { return this.shadowColor; },
|
|
set: function (shadowColor) {
|
|
rdp(`${this._uid}.shadowColor = '${shadowColor}'`);
|
|
return this.shadowColor_ = shadowColor;
|
|
}
|
|
},
|
|
shadowOffsetX: {
|
|
enumerable: true, get: function () { return this.shadowOffsetX_; },
|
|
set: function (shadowOffsetX) {
|
|
rdp(`${this._uid}.shadowOffsetX = ${shadowOffsetX}`);
|
|
return this.shadowOffsetX_ = shadowOffsetX;
|
|
}
|
|
},
|
|
shadowOffsetY: {
|
|
enumerable: true, get: function () { return this.shadowOffsetY_; },
|
|
set: function (shadowOffsetY) {
|
|
rdp(`${this._uid}.shadowOffsetY = ${shadowOffsetY}`);
|
|
return this.shadowOffsetY_ = shadowOffsetY;
|
|
}
|
|
},
|
|
strokeStyle: {
|
|
enumerable: true, get: function () { return this.strokeStyle_; },
|
|
set: function (strokeStyle) {
|
|
rdp(`${this._uid}.strokeStyle = ${strokeStyle._uid || `'${strokeStyle}'`}`);
|
|
return this.strokeStyle_ = strokeStyle;
|
|
}
|
|
},
|
|
textAlign: {
|
|
enumerable: true, get: function () { return this.textAlign_; },
|
|
set: function (textAlign) {
|
|
rdp(`${this._uid}.textAlign = '${textAlign}'`);
|
|
return this.textAlign_ = textAlign;
|
|
}
|
|
},
|
|
textBaseline: {
|
|
enumerable: true, get: function () { return this.textBaseline_; },
|
|
set: function (textBaseline) {
|
|
rdp(`${this._uid}.textBaseline = '${textBaseline}'`);
|
|
return this.textBaseline_ = textBaseline;
|
|
}
|
|
},
|
|
webkitBackingStorePixelRatio: {
|
|
enumerable: true, get: function () { return this.webkitBackingStorePixelRatio_; },
|
|
set: function (webkitBackingStorePixelRatio) {
|
|
rdp(`${this._uid}.webkitBackingStorePixelRatio = ${webkitBackingStorePixelRatio}`);
|
|
return this.webkitBackingStorePixelRatio_ = webkitBackingStorePixelRatio;
|
|
}
|
|
},
|
|
webkitImageSmoothingEnabled: {
|
|
enumerable: true, get: function () { return this.webkitImageSmoothingEnabled_; },
|
|
set: function (webkitImageSmoothingEnabled) {
|
|
rdp(`${this._uid}.webkitImageSmoothingEnabled = ${webkitImageSmoothingEnabled}`);
|
|
return this.webkitImageSmoothingEnabled_ = webkitImageSmoothingEnabled;
|
|
}
|
|
},
|
|
// Methods || https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingcontext2d#Methods
|
|
arc: {
|
|
enumerable: true,
|
|
value: function (x, y, radius, startAngle, endAngle, anticlockwise) {
|
|
return rdp(`${this._uid}.arc(${Array.prototype.slice.call(arguments, 0).join(',')})`);
|
|
}
|
|
},
|
|
arcTo: {
|
|
enumerable: true,
|
|
value: function (x1, y1, x2, y2, radius) {
|
|
return rdp(`${this._uid}.arcTo(${x1},${y1},${x2},${y2},${radius})`);
|
|
}
|
|
},
|
|
beginPath: {
|
|
enumerable: true,
|
|
value: function () {
|
|
return rdp(`${this._uid}.beginPath()`);
|
|
}
|
|
},
|
|
bezierCurveTo: {
|
|
enumerable: true,
|
|
value: function (cp1x, cp1y, cp2x, cp2y, x, y) {
|
|
return rdp(`${this._uid}.bezierCurveTo(${cp1x},${cp1y},${cp2x},${cp2y},${x},${y})`);
|
|
}
|
|
},
|
|
clearRect: {
|
|
enumerable: true,
|
|
value: function (x, y, width, height) {
|
|
return rdp(`${this._uid}.clearRect(${x},${y},${width},${height})`);
|
|
}
|
|
},
|
|
clip: {
|
|
enumerable: true,
|
|
value: function () {
|
|
return rdp(`${this._uid}.clip()`);
|
|
}
|
|
},
|
|
closePath: {
|
|
enumerable: true,
|
|
value: function () {
|
|
return rdp(`${this._uid}.closePath()`);
|
|
}
|
|
},
|
|
createImageData: {
|
|
enumerable: true,
|
|
value: function (width, height) {
|
|
if (width.height != undefined) {
|
|
height = width.height;
|
|
width = width.width;
|
|
}
|
|
return (callback) => {
|
|
callback(null, {
|
|
data: new Uint8ClampedArray(Array.apply(null, new Array(width * height * 4)).map(Number.prototype.valueOf, 0)),
|
|
width: width,
|
|
height: height
|
|
});
|
|
};
|
|
}
|
|
},
|
|
createLinearGradient: {
|
|
enumerable: true,
|
|
value: function (x0, y0, x1, y1) {
|
|
var uid = NCC.uid('linearGradient');
|
|
rdp(`var ${uid} = ${this._uid}.createLinearGradient(${x0},${y0},${x1},${y1})`);
|
|
var linearGradient = (callback) => {
|
|
rdp(callback ? (err, res) => {
|
|
err ? callback(err, null) : callback(null, linearGradient);
|
|
} : undefined);
|
|
return linearGradient;
|
|
};
|
|
GradientPDM._uid.value = uid;
|
|
Object.defineProperties(linearGradient, GradientPDM);
|
|
GradientPDM._uid.value = '';
|
|
return linearGradient;
|
|
}
|
|
},
|
|
createPattern: {
|
|
enumerable: true,
|
|
value: function (image, repetition) {
|
|
var uid = NCC.uid('pattern');
|
|
rdp(`var ${uid} = ${this._uid}.createPattern(${image._uid},'${repetition}')`);
|
|
var pattern = (callback) => {
|
|
rdp(callback ? (err, res) => {
|
|
err ? callback(err, null) : callback(null, pattern);
|
|
} : undefined);
|
|
return pattern;
|
|
};
|
|
PatternPDM._uid.value = uid;
|
|
Object.defineProperties(pattern, PatternPDM);
|
|
PatternPDM._uid.value = '';
|
|
return pattern;
|
|
}
|
|
},
|
|
createRadialGradient: {
|
|
enumerable: true,
|
|
value: function (x0, y0, r0, x1, y1, r1) {
|
|
var uid = NCC.uid('pattern');
|
|
rdp(`var ${uid} = ${this._uid}.createRadialGradient(${x0},${y0},${r0},${x1},${y1},${r1})`);
|
|
var radialGradient = (callback) => {
|
|
rdp(callback ? (err, res) => {
|
|
err ? callback(err, null) : callback(null, radialGradient);
|
|
} : undefined);
|
|
return radialGradient;
|
|
};
|
|
GradientPDM._uid.value = NCC.uid('radialGradient');
|
|
Object.defineProperties(radialGradient, GradientPDM);
|
|
GradientPDM._uid.value = '';
|
|
return radialGradient;
|
|
}
|
|
},
|
|
drawImage: {
|
|
enumerable: true,
|
|
value: function (image, a1, a2, a3, a4, a5, a6, a7, a8) {
|
|
return rdp(`${this._uid}.drawImage(${image._uid}, ${Array.prototype.slice.call(arguments, 1).join(',')})`);
|
|
}
|
|
},
|
|
// no use
|
|
//drawCustomFocusRing: { //RETURN/ boolean //IN/ Element element
|
|
// enumerable:true,
|
|
// value: function (element) {
|
|
// rdp(`${this._uid}.drawCustomFocusRing(" + element + ")`);
|
|
// return this;
|
|
// }
|
|
//},
|
|
// no use
|
|
//drawSystemFocusRing: { //RETURN/ void //IN/ Element element
|
|
// enumerable:true,
|
|
// value: function (element) {
|
|
// rdp(`${this._uid}.drawSystemFocusRinelementg()`);
|
|
// return this;
|
|
// }
|
|
//},
|
|
fill: {
|
|
enumerable: true,
|
|
value: function () {
|
|
return rdp(`${this._uid}.fill()`);
|
|
}
|
|
},
|
|
fillRect: {
|
|
enumerable: true,
|
|
value: function (x, y, width, height) {
|
|
return rdp(`${this._uid}.fillRect(${x},${y},${width},${height})`);
|
|
}
|
|
},
|
|
fillText: {
|
|
enumerable: true,
|
|
value: function (text, x, y, maxWidth) {
|
|
return rdp(`${this._uid}.fillText('${text}',${Array.prototype.slice.call(arguments, 1).join(',')})`);
|
|
}
|
|
},
|
|
getImageData: {
|
|
enumerable: true,
|
|
value: function (x, y, width, height) {
|
|
rdp(`Array.prototype.slice.call(${this._uid}.getImageData(${x},${y},${width},${height}).data).join(',')`);
|
|
return (callback) => {
|
|
rdp((err, res) => {
|
|
if (err)
|
|
return callback(err, null);
|
|
var imageData = {
|
|
data: new Uint8ClampedArray(res.value.split(',')),
|
|
width: width,
|
|
height: height
|
|
};
|
|
callback(null, imageData);
|
|
});
|
|
};
|
|
}
|
|
},
|
|
getLineDash: {
|
|
enumerable: true,
|
|
value: function () {
|
|
rdp(`${this._uid}.getLineDash().join(',')`);
|
|
return (callback) => {
|
|
rdp((err, res) => {
|
|
if (err)
|
|
return callback(err);
|
|
res.value = res.value.split(',');
|
|
for (var i = 0, l = res.value.length; i < l; i++)
|
|
res.value[i] = +res.value[i];
|
|
callback(err, res.value);
|
|
});
|
|
};
|
|
}
|
|
},
|
|
isPointInPath: {
|
|
enumerable: true,
|
|
value: function (x, y) {
|
|
rdp(`${this._uid}.isPointInPath(${x},${y})`);
|
|
return (callback) => {
|
|
rdp((err, res) => {
|
|
callback(err, res.value);
|
|
});
|
|
};
|
|
}
|
|
},
|
|
isPointInStroke: {
|
|
enumerable: true,
|
|
value: function (x, y) {
|
|
rdp(`${this._uid}.isPointInStroke(${x},${y})`);
|
|
return (callback) => {
|
|
rdp((err, res) => {
|
|
callback(err, res.value);
|
|
});
|
|
};
|
|
}
|
|
},
|
|
lineTo: {
|
|
enumerable: true,
|
|
value: function (x, y) {
|
|
return rdp(`${this._uid}.lineTo(${x},${y})`);
|
|
}
|
|
},
|
|
measureText: {
|
|
enumerable: true,
|
|
value: function (text) {
|
|
rdp(`${this._uid}.measureText('${text}').width`);
|
|
return (callback) => {
|
|
rdp((err, res) => {
|
|
if (err)
|
|
return callback(err);
|
|
callback(null, { width: res.value });
|
|
});
|
|
};
|
|
}
|
|
},
|
|
moveTo: {
|
|
enumerable: true,
|
|
value: function (x, y) {
|
|
return rdp(`${this._uid}.moveTo(${x},${y})`);
|
|
}
|
|
},
|
|
putImageData: {
|
|
enumerable: true,
|
|
value: function (imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
|
|
return rdp(`var data = [${Array.prototype.slice.call(imagedata.data).join(',')}]; var iD = ${this._uid}.createImageData(${imagedata.width}, ${imagedata.height}); for (var i = 0, l = iD.data.length; i < l; i++) iD.data[i] = +data[i]; ${this._uid}.putImageData(iD, ${Array.prototype.slice.call(arguments, 1).join(',')})`);
|
|
}
|
|
},
|
|
quadraticCurveTo: {
|
|
enumerable: true,
|
|
value: function (cpx, cpy, x, y) {
|
|
return rdp(`${this._uid}.quadraticCurveTo(${cpx},${cpy},${x},${y})`);
|
|
}
|
|
},
|
|
rect: {
|
|
enumerable: true,
|
|
value: function (x, y, width, height) {
|
|
return rdp(`${this._uid}.rect(${x},${y},${width},${height})`);
|
|
}
|
|
},
|
|
restore: {
|
|
enumerable: true,
|
|
value: function () {
|
|
return rdp(`${this._uid}.restore()`);
|
|
}
|
|
},
|
|
rotate: {
|
|
enumerable: true,
|
|
value: function (angle) {
|
|
return rdp(`${this._uid}.rotate(${angle})`);
|
|
}
|
|
},
|
|
save: {
|
|
enumerable: true,
|
|
value: function () {
|
|
return rdp(`${this._uid}.save()`);
|
|
}
|
|
},
|
|
scale: {
|
|
enumerable: true,
|
|
value: function (x, y) {
|
|
return rdp(`${this._uid}.scale(${x},${y})`);
|
|
}
|
|
},
|
|
// no use
|
|
//scrollPathIntoView: { //RETURN/ void //IN/
|
|
// enumerable: true,
|
|
// value: function () {
|
|
// rdp(`${this._uid}.scrollPathIntoView()`);
|
|
// return this;
|
|
// }
|
|
//},
|
|
setLineDash: {
|
|
enumerable: true,
|
|
value: function (segments) {
|
|
return rdp(`${this._uid}.setLineDash([${segments.join(',')}])`);
|
|
}
|
|
},
|
|
setTransform: {
|
|
enumerable: true,
|
|
value: function (m11, m12, m21, m22, dx, dy) {
|
|
return rdp(`${this._uid}.setTransform(${m11},${m12},${m21},${m22},${dx},${dy})`);
|
|
}
|
|
},
|
|
stroke: {
|
|
enumerable: true,
|
|
value: function () {
|
|
return rdp(`${this._uid}.stroke()`);
|
|
}
|
|
},
|
|
strokeRect: {
|
|
enumerable: true,
|
|
value: function (x, y, w, h) {
|
|
return rdp(`${this._uid}.strokeRect(${x},${y},${w},${h})`);
|
|
}
|
|
},
|
|
strokeText: {
|
|
enumerable: true,
|
|
value: function (text, x, y, maxWidth) {
|
|
rdp(`${this._uid}.strokeText('${text}',${(Array.prototype.slice.call(arguments, 1).join(','))})`);
|
|
return this;
|
|
}
|
|
},
|
|
transform: {
|
|
enumerable: true,
|
|
value: function (m11, m12, m21, m22, dx, dy) {
|
|
return rdp(`${this._uid}.transform(${m11},${m12},${m21},${m22},${dx},${dy})`);
|
|
}
|
|
},
|
|
translate: {
|
|
enumerable: true,
|
|
value: function (x, y) {
|
|
return rdp(`${this._uid}.translate(${x},${y})`);
|
|
}
|
|
}
|
|
};
|
|
var GradientPDM = {
|
|
// private properties
|
|
_uid: {
|
|
enumerable: DEBUG,
|
|
value: ''
|
|
},
|
|
_remote: {
|
|
enumerable: DEBUG,
|
|
set: function (null_) {
|
|
if (null_ === null) {
|
|
rdp(`${this._uid} = null`);
|
|
Object.defineProperty(this, '_uid', { value: null });
|
|
}
|
|
else
|
|
logger.error('"_remote" can only be set to "null"');
|
|
}
|
|
},
|
|
// Web API: https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient
|
|
// Methods
|
|
addColorStop: {
|
|
enumerable: true,
|
|
value: function (offset, color) {
|
|
return rdp(`${this._uid}.addColorStop(${offset},'${color}')`);
|
|
}
|
|
}
|
|
};
|
|
var PatternPDM = {
|
|
// private properties
|
|
_uid: {
|
|
enumerable: DEBUG,
|
|
value: ''
|
|
},
|
|
_remote: {
|
|
enumerable: DEBUG,
|
|
set: function (null_) {
|
|
if (null_ === null) {
|
|
rdp(`${this._uid} = null`);
|
|
Object.defineProperty(this, '_uid', { value: null });
|
|
}
|
|
else
|
|
logger.error('"_remote" can only be set to "null"');
|
|
}
|
|
},
|
|
};
|
|
var mimeMap = {
|
|
png: 'image/png',
|
|
webp: 'image/webp',
|
|
jpeg: 'image/jpeg',
|
|
jpg: 'image/jpeg',
|
|
svg: 'image/svg+xml',
|
|
gif: 'image/gif'
|
|
};
|
|
var regExp_http = new RegExp('^(http:\\/\\/.+)', 'i');
|
|
var regExp_data = new RegExp('^(data:image\\/\\w+;base64,.+)');
|
|
var regExp_type = new RegExp('^data:image\\/(\\w+);base64,');
|
|
var ImagePDM = {
|
|
// private properties
|
|
_uid: {
|
|
enumerable: DEBUG,
|
|
value: ''
|
|
},
|
|
_remote: {
|
|
enumerable: DEBUG,
|
|
set: function (null_) {
|
|
if (null_ === null) {
|
|
rdp(`${this._uid} = null`);
|
|
Object.defineProperty(this, '_uid', { value: null });
|
|
}
|
|
else
|
|
logger.error('"_remote" can only be set to "null"');
|
|
}
|
|
},
|
|
// Properties
|
|
src_: {
|
|
enumerable: DEBUG,
|
|
writable: true,
|
|
value: ''
|
|
},
|
|
width_: {
|
|
enumerable: DEBUG,
|
|
writable: true,
|
|
value: undefined
|
|
},
|
|
height_: {
|
|
enumerable: DEBUG,
|
|
writable: true,
|
|
value: undefined
|
|
},
|
|
_base64_: {
|
|
enumerable: DEBUG,
|
|
writable: true,
|
|
value: null
|
|
},
|
|
_base64: {
|
|
enumerable: DEBUG,
|
|
get: function () {
|
|
return this._base64_;
|
|
},
|
|
set: function (base64) {
|
|
rdp(`${this._uid}.src = '${base64}'`);
|
|
rdp(() => {
|
|
rdp(`${this._uid}.width + '_' + ${this._uid}.height`);
|
|
rdp((err, res) => {
|
|
if (err && this.onerror)
|
|
return this.onerror(err);
|
|
var size = res.value.split('_');
|
|
this.width_ = +size[0];
|
|
this.height_ = +size[1];
|
|
if (this.onload)
|
|
return this.onload(this);
|
|
});
|
|
});
|
|
this._base64_ = base64;
|
|
return this._base64_;
|
|
}
|
|
},
|
|
// Methods
|
|
_toFile: {
|
|
enumerable: DEBUG,
|
|
value: function (filename, callback) {
|
|
var head = regExp_type.exec(this._base64_), type = filename.split('.').pop();
|
|
if (!head || !head[1] || (head[1] != ((type == "jpg") ? "jpeg" : type)))
|
|
if (callback)
|
|
return callback(`type mismatch ${head ? head[1] : "'unknown'"} !> ${type}`);
|
|
else
|
|
throw new Error(`type mismatch ${head ? head[1] : "'unknown'"} !> ${type}`);
|
|
logger.info(`[ncc] writing image to: ${filename}`);
|
|
fs.writeFile(filename, new Buffer(this._base64_.replace(/^data:image\/\w+;base64,/, ''), 'base64'), {}, callback);
|
|
}
|
|
},
|
|
// Web API
|
|
// Properties
|
|
src: {
|
|
enumerable: true,
|
|
get: function () {
|
|
return this.src_;
|
|
},
|
|
set: function (src) {
|
|
var img = this;
|
|
this._src = src;
|
|
if (!src || src === '')
|
|
return;
|
|
if (regExp_data.test(src))
|
|
img._base64 = src;
|
|
else if (regExp_http.test(src)) {
|
|
logger.info(`[ncc] loading image from URL: ${src}`);
|
|
http.get(src, function (res) {
|
|
var data = '';
|
|
res.setEncoding('base64');
|
|
if (res.statusCode != 200) {
|
|
if (img.onerror)
|
|
return img.onerror(`loading image failed with status ${res.statusCode}`);
|
|
else
|
|
logger.error(`loading image failed with status ${res.statusCode}`);
|
|
}
|
|
res.on('data', function (chunk) { data += chunk; });
|
|
res.on('end', function () {
|
|
img._base64 = `data:${(res.headers["content-type"] || mimeMap[src.split('.').pop()])};base64,${data}`;
|
|
logger.info('[ncc] loading image from URL completed');
|
|
});
|
|
}).on('error', this.onerror || function (err) {
|
|
if (img.onerror)
|
|
return img.onerror(err);
|
|
else
|
|
logger.error(`loading image failed with err ${err}`);
|
|
});
|
|
}
|
|
else {
|
|
logger.info(`[ncc] loading image from FS: ${src}`);
|
|
fs.readFile(src, 'base64', function (err, data) {
|
|
if (err) {
|
|
if (img.onerror)
|
|
img.onerror(err);
|
|
else
|
|
logger.error(`loading image failed with err ${err}`);
|
|
}
|
|
img._base64 = `data:${mimeMap[src.split('.').pop()]};base64,${data}`;
|
|
logger.info('[ncc] loading image from FS completed');
|
|
});
|
|
}
|
|
return this.src_;
|
|
}
|
|
},
|
|
onload: {
|
|
writable: true,
|
|
enumerable: true,
|
|
value: undefined
|
|
},
|
|
onerror: {
|
|
writable: true,
|
|
enumerable: true,
|
|
value: undefined
|
|
},
|
|
width: {
|
|
enumerable: true,
|
|
get: function () {
|
|
return this.width_;
|
|
}
|
|
},
|
|
height: {
|
|
enumerable: true,
|
|
get: function () {
|
|
return this.height_;
|
|
}
|
|
}
|
|
};
|
|
module.exports = NCC;
|