// ==UserScript==
// @name AbemaTV Screen Comment Scroller
// @namespace knoa.jp
// @description AbemaTV のコメントをニコニコ風にスクロールさせます。
// @include https://abema.tv/*
// @version 1.3.7
// @grant none
// ==/UserScript==
// console.log('AbemaTV? => hireMe()');
(function(){
const SCRIPTNAME = 'ScreenCommentScroller';
const DEBUG = false;//アベマの仕様変更に対応しました。
// delete localStorage['ScreenCommentScroller-configs'];
if(window === top) console.time(SCRIPTNAME);
const CONFIGS = [
/*スクロールコメント*/
{KEY: 'color', DEFAULT: '#ffffff', TYPE: 'string'},/*色*/
{KEY: 'ocolor', DEFAULT: '#000000', TYPE: 'string'},/*縁取り色*/
{KEY: 'owidth', DEFAULT: 0.05, TYPE: 'float' },/*縁取りの太さ(比率)*/
{KEY: 'maxlines', DEFAULT: 10, TYPE: 'int' },/*最大行数*/
{KEY: 'linemargin', DEFAULT: 0.2, TYPE: 'float' },/*行間(比率)*/
{KEY: 'opacity', DEFAULT: 0.50, TYPE: 'float' },/*不透明度*/
{KEY: 'hopacity', DEFAULT: 0.50, TYPE: 'float' },/*不透明度(マウスオーバー時)*/
/*一覧コメント*/
{KEY: 'lt_opacity', DEFAULT: 0.75, TYPE: 'float' },/*文字の不透明度*/
{KEY: 'lt_hopacity', DEFAULT: 1.00, TYPE: 'float' },/*文字の不透明度(マウスオーバー時)*/
{KEY: 'lb_opacity', DEFAULT: 0.25, TYPE: 'float' },/*背景の不透明度*/
{KEY: 'lb_hopacity', DEFAULT: 0.50, TYPE: 'float' },/*背景の不透明度(マウスオーバー時)*/
/*アニメーション*/
{KEY: 'duration', DEFAULT: 5, TYPE: 'float' },/*横断にかける秒数*/
{KEY: 'fps', DEFAULT: 60, TYPE: 'int' },/*秒間コマ数*/
];
const AINTERVAL = 5;/*AbemaTVのコメント取得間隔の仕様値*/
const ADELAYS = {/*AbemaTVのコメント取得時の投稿時刻を(AINTERVAL)まで用意しておく*/
'今': 0,
'1秒前': 1,
'2秒前': 2,
'3秒前': 3,
'4秒前': 4,
'5秒前': 5,
};
/* サイト定義 */
let site = {
targets: [
/* 構造 */
function header(){let header = document.querySelector('body > div > div > header'); return (header) ? site.use(header) : null;},
function footer(){let fullscreen = document.querySelector('button[aria-label="フルスクリーン表示"]'); return (fullscreen) ? site.use(fullscreen.parentNode.parentNode) : null;},
function board(){let board = document.querySelector('div[aria-hidden] form + div > div'); return (board) ? site.use(board) : null;},
function screen(){let loading = document.querySelector('img[src="/images/misc/feed_loading.gif"]'); return (loading) ? site.use(loading.parentNode.parentNode) : null;},
/* ペイン */
function commentPane(){let form = document.querySelector('form:not([role="search"])'); return (form) ? site.use(form.parentNode.parentNode) : null;},
function channelPane(){let list = document.querySelector('use[*|href^="/images/icons/list.svg"]'); return (list) ? site.use(list.parentNode.parentNode.parentNode.parentNode.nextElementSibling) : null;},
function programPane(){let list = document.querySelector('use[*|href^="/images/icons/list.svg"]'); return (list) ? site.use(list.parentNode.parentNode.parentNode.parentNode.nextElementSibling.nextElementSibling) : null;},
/* ボタン */
function channelButtons(){let list = document.querySelector('use[*|href^="/images/icons/list.svg"]'); return (list) ? site.use(list.parentNode.parentNode.parentNode.parentNode) : null;},
function channelButton(){let list = document.querySelector('use[*|href^="/images/icons/list.svg"]'); return (list) ? site.use(list.parentNode.parentNode) : null;},
function commentButton(){let svg = document.querySelector('use[*|href^="/images/icons/comment.svg"]'); return (svg) ? site.use(svg.parentNode.parentNode) : null;},
function programButton(){let button = document.querySelector('button[aria-label="フルスクリーン表示"] + div + div > div > div'); return (button) ? site.use(button) : null;},
function fullscreenButton(){let fullscreen = document.querySelector('button[aria-label="フルスクリーン表示"]'); return (fullscreen) ? site.use(fullscreen) : null;},
function closer(){let commentForm = document.querySelector('form:not([role="search"])'); return (commentForm) ? site.use(commentForm.parentNode.parentNode.nextElementSibling) : null;},
/* 表示要素 */
function audience(){let loading = document.querySelector('img[src="/images/misc/feed_loading.gif"]'); return (loading) ? site.use(loading.parentNode.parentNode.firstChild.firstChild) : null;},
function programName(){let name = document.querySelector('button[aria-label="フルスクリーン表示"] + div + div div > p > span > span:last-child'); return (name) ? site.use(name) : null;},
function notice(){let header = document.querySelector('header'); return (header) ? site.use(header.nextElementSibling) : null;},
],
getComments: function(target){return (target.querySelectorAll) ? target.querySelectorAll('div[aria-hidden] form + div > div > div > div > div > p:first-child') : null},
use: function use(target){
const cid = 'selectorId'/*camelCase*/, sid = 'selector-id'/*snake-case*/;
target.dataset[cid] = use.caller.name;
selectors[use.caller.name] = `${target.localName}[data-${sid}="${use.caller.name}"]`;
elements[use.caller.name] = target;
return true;
},
};
/* 処理本体 */
let elements = {}, selectors = {}, canvas, context, lines = [], fontsize, interval, configButton, configPanel, configs = {}, style;
let core = {
/* 初期化 */
initialize: function(){
let previousUrl = '';
/* 一度だけ */
html = document.documentElement;
core.config.read();
window.addEventListener('resize', setTimeout.bind(null, core.modify, 1000));
/* URLの変化を見守る */
setInterval(function(){
if(location.href === previousUrl) return;/*URLが変わってない*/
/* テレビ視聴ページ */
if(location.href.startsWith('https://abema.tv/now-on-air/')){
if(previousUrl.startsWith('https://abema.tv/now-on-air/')) return;/*チャンネルを変えただけ*/
html.classList.add(SCRIPTNAME);
core.ready();
/* テレビ視聴ページではない */
}else{
html.classList.remove(SCRIPTNAME);
}
previousUrl = location.href;
}, 1000);
},
/* URLが変わるたびに呼ぶ */
ready: function(e){
/* 必要な要素が出揃うまで粘る */
for(let i = 0; site.targets[i]; i++) if(site.targets[i]() === null) return setTimeout(core.ready, 1000);
core.addStyle();
core.config.createButton();
/* 開けるようになったら自動で開く */
observe(elements.commentButton, function(records){
if(getComputedStyle(this).cursor === 'pointer'){
this.click();
}
}, {attributes: true});
/* 設定画面を用意する */
core.config.createButton();
/* コメントをスクロールさせるCanvasの設置 */
/* (描画処理の軽さは HTML5 Canvas, CSS Position Left, CSS Transition の順) */
core.createCanvas();
/* メイン処理 */
core.listenComments();
core.scrollComments();
},
/* canvas作成 */
createCanvas: function(){
if(canvas) return;
canvas = document.createElement('canvas');
canvas.id = SCRIPTNAME;
elements.screen.appendChild(canvas);
context = canvas.getContext('2d');
core.modify();
},
/* スクリーンサイズに変化があればcanvasも変化させる */
modify: function(){
canvas.width = elements.screen.offsetWidth;
canvas.height = elements.screen.offsetHeight;
fontsize = (canvas.height / configs.maxlines) / (1 + configs.linemargin);
context.font = 'bold ' + (fontsize) + 'px sans-serif';
context.fillStyle = configs.color;
context.strokeStyle = configs.ocolor;
context.lineWidth = fontsize * configs.owidth;
},
/* コメントの新規追加を見守る */
listenComments: function(){
if(elements.board.isListening) return;
elements.board.isListening = true;
elements.board.addEventListener('DOMNodeInserted', function(e){
let comments = site.getComments(e.target);
if(!comments || !comments.length) return;/*新着コメントの追加でなければ終了*/
/*投稿経過時間に合わせた時間差を付けることで自然に流す*/
let earliest = ADELAYS[comments[comments.length - 1].nextElementSibling.textContent];/*同時取得の中で最初に投稿されたコメントの経過時間*/
if(earliest === undefined) earliest = AINTERVAL;
for(let i = 0; comments[i]; i++){
let current = ADELAYS[comments[i].nextElementSibling.textContent];
if(current === undefined) current = AINTERVAL;
window.setTimeout(function(){
core.attachComment(comments[i]);
}, 1000 * (earliest - current));
}
});
},
/* コメントが追加されるたびにスクロールキューに追加 */
attachComment: function(comment){
let record = {};
record.text = comment.textContent;/*流れる文字列*/
record.width = context.measureText(record.text).width;/*文字列の幅*/
record.ppms = (canvas.width + record.width) / (configs.duration * 1000);/*ミリ秒あたり移動距離*/
record.start = Date.now();/*開始時刻*/
record.reveal = record.start + (record.width / record.ppms);/*文字列が右端から抜ける時刻*/
record.touch = record.start + (canvas.width / record.ppms);/*文字列が左端に触れる時刻*/
record.end = record.start + (configs.duration * 1000);/*終了時刻*/
record.left = canvas.width;/*左端からの距離(描画位置)*/
/* 追加されたコメントをどの行に流すかを決定する */
for(let i=0; i < configs.maxlines; i++){
let length = lines[i] ? lines[i].length : 0;/*同じ行に詰め込まれているコメント数*/
switch(true){
/* 行がなければ行を追加して流す */
case(length === 0):
lines[i] = [];
/* ひとつ先行するコメントより遅い(短い)文字列なら、現時点で先行コメントがすでに右端から抜けていれば流す */
case(record.ppms < lines[i][length - 1].ppms && lines[i][length - 1].reveal < record.start):
/* ひとつ先行するコメントより速い(長い)文字列なら、左端に触れる瞬間までに先行コメントが終了するなら流す */
case(lines[i][length - 1].ppms < record.ppms && lines[i][length - 1].end < record.touch):
break;/*条件に当てはまればswitch文を抜けて行に追加*/
default:
continue;/*条件に当てはまらなければforループを回して次の行に入れられるかの判定へ*/
}
record.top = ((canvas.height / configs.maxlines) * i) + fontsize;
lines[i].push(record);
break;
}
},
/* FPSタイマー駆動 */
scrollComments: function(){
if(interval) clearInterval(interval);
interval = window.setInterval(function(){
context.clearRect(0, 0, canvas.width, canvas.height);
/* Canvas描画 */
let now = Date.now();
for(let i=0; lines[i]; i++){
for(let j=0; lines[i][j]; j++){
/* 視認性を向上させるスクロール文字の縁取りは、幸いにもパフォーマンスにほぼ影響しない */
context.strokeText(lines[i][j].text, lines[i][j].left, lines[i][j].top);
context.fillText(lines[i][j].text, lines[i][j].left, lines[i][j].top);
/* 次の描画位置を計算 */
lines[i][j].left = canvas.width - ((now - lines[i][j].start) * lines[i][j].ppms);
}
if(lines[i][0] && lines[i][0].end < now) lines[i].shift();
}
}, 1000 / configs.fps);
},
/* 設定 */
config: {
read: function(){
/* 保存済みの設定を読む */
let ls = localStorage[SCRIPTNAME + '-configs'];
if(ls) configs = JSON.parse(ls);
/* 未定義項目をデフォルト値で上書きしていく */
for(let i = 0; CONFIGS[i]; i++) if(configs[CONFIGS[i].KEY] === undefined) configs[CONFIGS[i].KEY] = CONFIGS[i].DEFAULT;
},
save: function(new_config){
/* CONFIGSを元に文字列を型評価して値を格納していく */
for(let i = 0; CONFIGS[i]; i++){
/* 値がなければデフォルト値 */
if(new_config[CONFIGS[i].KEY] === ""){
configs[CONFIGS[i].KEY] = CONFIGS[i].DEFAULT;
continue;
}
switch(CONFIGS[i].TYPE){
case 'int':
configs[CONFIGS[i].KEY] = parseInt(new_config[CONFIGS[i].KEY]);
break;
case 'float':
configs[CONFIGS[i].KEY] = parseFloat(new_config[CONFIGS[i].KEY]);
break;
case 'string':
default:
configs[CONFIGS[i].KEY] = new_config[CONFIGS[i].KEY];
break;
}
}
localStorage[SCRIPTNAME + '-configs'] = JSON.stringify(configs);
},
createButton: function(){
if(configButton) return;
/* フルスクリーンボタンを元に設定ボタンを追加する */
configButton = document.createElement('button');
configButton.className = elements.fullscreenButton.className;
configButton.classList.add('hidden');
configButton.id = SCRIPTNAME + '-config-button';
configButton.innerHTML = core.config.buttonHtml();/*歯車*/
configButton.setAttribute('title', SCRIPTNAME + '設定');
configButton.addEventListener('click', core.config.togglePanel, true);
elements.fullscreenButton.parentNode.insertBefore(configButton, elements.fullscreenButton);
animate(function(){configButton.classList.remove('hidden')});
},
togglePanel: function(){
if(configPanel) return core.config.closePanel();
configPanel = document.createElement('div');
configPanel.id = SCRIPTNAME + '-config-panel';
configPanel.classList.add('hidden');
configPanel.innerHTML = core.config.panelHtml();
configPanel.querySelector('button.cancel').addEventListener('click', core.config.closePanel, true);
configPanel.querySelector('button.save').addEventListener('click', function(){
let inputs = configPanel.querySelectorAll('input'), new_configs = {};
for(let i = 0; inputs[i]; i++) new_configs[inputs[i].name] = inputs[i].value;
core.config.save(new_configs);
/* 新しい設定値で再スタイリング */
core.modify();
core.addStyle();
core.scrollComments();
core.config.closePanel();
}, true);
document.body.appendChild(configPanel);
animate(function(){configPanel.classList.remove('hidden')});
},
closePanel: function(){
configPanel.classList.add('hidden');
configPanel.addEventListener('transitionend', function(){
document.body.removeChild(configPanel);
configPanel = null;
}, {once: true});
},
buttonHtml: function(){
/* https://www.onlinewebfonts.com/icon/347 */
return innerHTML = `<!-- iCon by oNlineWebFonts.Com --> <img src="data:image/svg+xml;base64,CjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMDAgMTAwMCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTAwMCAxMDAwIiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPG1ldGFkYXRhPiBTdmcgVmVjdG9yIEljb25zIDogaHR0cDovL3d3dy5vbmxpbmV3ZWJmb250cy5jb20vaWNvbiA8L21ldGFkYXRhPgogIDxnPjxnPjxwYXRoIGQ9Ik00OTkuOSwzMjIuOGMtOTcuOCwwLTE3Ny4xLDc5LjMtMTc3LjEsMTc3LjJjMCw5Ny44LDc5LjMsMTc3LjMsMTc3LjEsMTc3LjNjOTcuOCwwLDE3Ni42LTc5LjUsMTc2LjYtMTc3LjNDNjc2LjUsNDAyLjEsNTk3LjgsMzIyLjgsNDk5LjksMzIyLjh6IE04NTUuMSw2MDEuOGwtMzEuOSw3Ni45bDY0LjUsMTI2LjZsLTc5LDc5bC0xMjkuNS02MS4ybC03Ni45LDMxLjZsLTM5LDExOS41bC01LDE1LjlINDQ2LjZsLTQ4LjMtMTM0LjlsLTc2LjktMzEuN2wtMTI2LjgsNjQuMmwtNzguOS03OC45bDYxLjEtMTI5LjZsLTMxLjctNzYuOEwxMCw1NTguMlY0NDYuNmwxMzUtNDguNGwzMS43LTc2LjhsLTU2LjgtMTEyLjFsLTcuNS0xNC43bDc4LjgtNzguOEwzMjAuOSwxNzdsNzYuOC0zMS44bDM5LTExOS40bDUtMTUuOGgxMTEuNmw0OC4zLDEzNWw3Ni43LDMxLjhsMTI2LjktNjQuM2w3OC45LDc4LjhsLTYxLjEsMTI5LjVsMzEuNiw3Ni45bDEzNS40LDQ0djExMS41TDg1NS4xLDYwMS44TDg1NS4xLDYwMS44eiIgc3R5bGU9ImZpbGw6I0ZGRkZGRiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+CiAg" width="22" height="22">`;
},
panelHtml: function(){
return innerHTML = `
<h1>${SCRIPTNAME}設定</h1>
<fieldset>
<legend>スクロールコメント</legend>
<p><label>色: <input type="color" name="color" value="${configs.color}"></label></p>
<p><label>縁取り色: <input type="color" name="ocolor" value="${configs.ocolor}"></label></p>
<p><label>縁取りの太さ(比率): <input type="number" name="owidth" value="${configs.owidth}" min="0" max="0.2" step="0.01"></label></p>
<p><label>最大行数: <input type="number" name="maxlines" value="${configs.maxlines}" min="1" max="25" step="1"></label></p>
<p><label>行間(比率): <input type="number" name="linemargin" value="${configs.linemargin}" min="0" max="1" step="0.05"></label></p>
<p><label>不透明度: <input type="number" name="opacity" value="${configs.opacity}" min="0" max="1" step="0.05"></label></p>
<p><label>不透明度(マウスオーバー時): <input type="number" name="hopacity" value="${configs.hopacity}" min="0" max="1" step="0.05"></label></p>
</fieldset>
<fieldset>
<legend>一覧コメント</legend>
<p><label>文字の不透明度: <input type="number" name="lt_opacity" value="${configs.lt_opacity}" min="0" max="1" step="0.05"></label></p>
<p><label>文字の不透明度(マウスオーバー時): <input type="number" name="lt_hopacity" value="${configs.lt_hopacity}" min="0" max="1" step="0.05"></label></p>
<p><label>背景の不透明度: <input type="number" name="lb_opacity" value="${configs.lb_opacity}" min="0" max="1" step="0.05"></label></p>
<p><label>背景の不透明度(マウスオーバー時): <input type="number" name="lb_hopacity" value="${configs.lb_hopacity}" min="0" max="1" step="0.05"></label></p>
</fieldset>
<fieldset>
<legend>アニメーション</legend>
<p><label>横断にかける秒数: <input type="number" name="duration" value="${configs.duration}" min="1" max="10" step="1"></label></p>
<p><label>秒間コマ数: <input type="number" name="fps" value="${configs.fps}" min="1" max="240" step="1"></label></p>
</fieldset>
<p class="buttons"><button class="cancel">キャンセル</button><button class="save">保存</button></p>
<p class="license">Icon made from <a href="http://www.onlinewebfonts.com/icon">Icon Fonts</a> is licensed by CC BY 3.0</p>
`;
},
},
addStyle: function(){
if(style) document.head.removeChild(style);
(function(css){
style = document.createElement('style');
style.type = 'text/css';
style.textContent = css.replace(/^<style>([^]*)<\/style>$/, '$1');
document.head.appendChild(style);
})(innerHTML = `<style>
/* スクロールコメント */
canvas#${SCRIPTNAME}{
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: ${configs.opacity};
transition: 500ms ease 0ms;
}
body:hover canvas#${SCRIPTNAME}{
opacity: ${configs.hopacity};
}
/* コメントを表示させても映像を画面いっぱいに */
${selectors.screen},
${selectors.screen} > div{
width: 100% !important;
height: 100% !important;
}
/* 右コメント一覧を透明に */
${selectors.commentPane}{
mix-blend-mode: hard-light;/*https://stackoverflow.com/questions/15597167/css3-opacity-gradient*/
background: rgba(0,0,0,${configs.lb_opacity});
transition: 500ms ease 0ms;
z-index: 9;/*右側に表示される番組情報や右下のコントローラより下層に*/
}
${selectors.commentPane}:hover{
background: rgba(0,0,0,${configs.lb_hopacity});
}
${selectors.commentPane}::after{
pointer-events: none;
position: absolute;
content: "";
left: 0px;
top: 0px;
height: 100%;
width: 100%;
background: linear-gradient(transparent 50%, gray);
}
${selectors.commentPane} *{
background: transparent;
color: rgba(255,255,255,${configs.lt_opacity});
}
${selectors.commentPane}:hover *{
color: rgba(255,255,255,${configs.lt_hopacity});
}
/* 右コメント一覧のスクロールバーを美しく */
${selectors.commentPane} > div > div{
overflow-y: hidden;
}
${selectors.commentPane}:hover > div > div{
overflow-y: auto;
}
${selectors.commentPane} > div > div::-webkit-scrollbar{
background: rgba(255,255,255,0);
}
${selectors.commentPane} > div > div::-webkit-scrollbar-thumb{
background: rgba(255,255,255,${configs.lt_hopacity/2});
}
/* マウスオーバー時だけナビゲーションを表示させる */
body ${selectors.footer}{
transform: translateY(200%);
}
body:hover ${selectors.footer}{
transform: translateY(0%);
visibility: visible;
}
/* 設定 */
#${SCRIPTNAME}-config-button{
right: 125px;
transition: 500ms ease 0ms;
}
#${SCRIPTNAME}-config-button.hidden,
div[aria-hidden="false"] #${SCRIPTNAME}-config-button/*コメント非表示の時*/{
bottom: -22px;
}
#${SCRIPTNAME}-config-panel{
position: fixed;
width: 360px;
left: 50%;
bottom: 50%;
transform: translate(-50%, 50%);
z-index: 100;
background: rgba(0,0,0,.75);
transition: 500ms ease 0ms;
padding: 5px 0;
}
#${SCRIPTNAME}-config-panel.hidden{
bottom: 0;
transform: translate(-50%, 100%);
}
#${SCRIPTNAME}-config-panel h1,
#${SCRIPTNAME}-config-panel legend,
#${SCRIPTNAME}-config-panel p{
color: rgba(255,255,255,1);
font-size: 14px;
padding: 4px 10px;
line-height:20px;
}
#${SCRIPTNAME}-config-panel fieldset p{
padding-left: 30px;
}
#${SCRIPTNAME}-config-panel fieldset p:hover{
background: rgba(255,255,255,.25);
}
#${SCRIPTNAME}-config-panel input{
width: 80px;
height: 20px;
position: absolute;
right: 10px;
}
#${SCRIPTNAME}-config-panel p.buttons{
text-align: right;
}
#${SCRIPTNAME}-config-panel button{
width: 120px;
padding: 5px 10px;
margin-left: 10px;
border-radius: 5px;
color: rgba(255,255,255,1);
background: rgba(64,64,64,1);
border: 1px solid rgba(255,255,255,1);
}
#${SCRIPTNAME}-config-panel button.save{
font-weight: bold;
background: rgba(0,0,0,1);
}
#${SCRIPTNAME}-config-panel button:hover{
background: rgba(128,128,128,1);
}
#${SCRIPTNAME}-config-panel p.license,
#${SCRIPTNAME}-config-panel p.license a{
font-size: 10px;
color: rgba(255,255,255,.25);
}
</style>`);
},
};
let animate = function(callback, ...params){requestAnimationFrame(() => requestAnimationFrame(() => callback(...params)))};
let innerHTML = '';/*trick for syntax highlighting, waiting js engines support html template*/
let observe = function(element, callback, config = {childList: true}){
let observer = new MutationObserver(callback.bind(element));
observer.observe(element, config);
return observer;
};
let log = (DEBUG) ? function(){
let l = log.last = log.now || new Date(), n = log.now = new Date();
console.log(
SCRIPTNAME + ':',
/* 00:00:00.000 */ n.toLocaleTimeString() + '.' + n.getTime().toString().slice(-3),
/* +0.000s */ '+' + ((n-l)/1000).toFixed(3) + 's',
/* :00 */ ':' + new Error().stack.match(/:[0-9]+:[0-9]+/g)[1].split(':')[1],/*LINE*/
/* caller */ log.caller ? log.caller.name : '',
...arguments
);
if(arguments.length === 1) return arguments[0];
} : function(){};
core.initialize();
if(window === top) console.timeEnd(SCRIPTNAME);
})();