196 lines
6.6 KiB
JavaScript
196 lines
6.6 KiB
JavaScript
import {DEBUG as debug} from '../src/common.js';
|
|
|
|
const DEBUG = debug || false;
|
|
|
|
export function getInjection({sessionId}) {
|
|
// Notes:
|
|
// say() function
|
|
// why aliased? Resistant to page overwriting
|
|
// just a precaution as we are already in an isolated world here, but this makes
|
|
// this script more portable if it were introduced globally as well as robust
|
|
// against API or behaviour changes of the browser or its remote debugging protocol
|
|
// in future
|
|
return `
|
|
{
|
|
const X = 1;
|
|
const DEBUG = ${JSON.stringify(DEBUG, null, 2)};
|
|
const MIN_CHECK_TEXT = 3000; // min time between checking documentElement.innerText
|
|
const MIN_NOTIFY = 5000; // min time between telling controller text maybe changed
|
|
const MAX_NOTIFICATIONS = 13; // max times we will tell controller text maybe changed
|
|
const OBSERVER_OPTS = {
|
|
subtree: true,
|
|
childList: true,
|
|
characterData: true
|
|
};
|
|
const Top = globalThis.top;
|
|
let lastInnerText;
|
|
|
|
if ( Top === globalThis ) {
|
|
const ConsoleInfo = console.info.bind(console);
|
|
const JSONStringify = JSON.stringify.bind(JSON);
|
|
const TITLE_CHANGES = 10;
|
|
const INITIAL_CHECK_TIME = 500;
|
|
const TIME_MULTIPLIER = Math.E;
|
|
const sessionId = "${sessionId}";
|
|
const sleep = ms => new Promise(res => setTimeout(res, ms));
|
|
const handler = throttle(handleFrameMessage, MIN_NOTIFY);
|
|
let count = 0;
|
|
|
|
installTop();
|
|
|
|
async function installTop() {
|
|
console.log("Installing in top frame...");
|
|
self.startUrl = location.href;
|
|
say({install: { sessionId, startUrl }});
|
|
await sleep(500);
|
|
beginTitleChecks();
|
|
beginTextNotifications();
|
|
console.log("Installed.");
|
|
}
|
|
|
|
function beginTitleChecks() {
|
|
let lastTitle = null;
|
|
let checker;
|
|
let timeToNextCheck = INITIAL_CHECK_TIME;
|
|
let changesLogged = 0;
|
|
|
|
check();
|
|
console.log('Begun logging title changes.');
|
|
|
|
function check() {
|
|
clearTimeout(checker);
|
|
const currentTitle = document.title;
|
|
if ( lastTitle !== currentTitle ) {
|
|
say({titleChange: {lastTitle, currentTitle, url: location.href, sessionId}});
|
|
lastTitle = currentTitle;
|
|
changesLogged++;
|
|
} else {
|
|
// increase check time if there's no change
|
|
timeToNextCheck *= TIME_MULTIPLIER;
|
|
}
|
|
if ( changesLogged < TITLE_CHANGES ) {
|
|
checker = setTimeout(check, timeToNextCheck);
|
|
} else {
|
|
console.log('Finished logging title changes.');
|
|
}
|
|
}
|
|
}
|
|
|
|
function say(thing) {
|
|
ConsoleInfo(JSONStringify(thing));
|
|
}
|
|
|
|
function beginTextNotifications() {
|
|
// listen for {textChange:true} messages
|
|
// throttle them
|
|
// on leading throttle edge send message to controller with
|
|
// console.info(JSON.stringify({textChange:...}));
|
|
self.addEventListener('message', messageParser);
|
|
|
|
console.log('Begun notifying of text changes.');
|
|
|
|
function messageParser({data, origin}) {
|
|
let source;
|
|
try {
|
|
({source} = data.frameTextChangeNotification);
|
|
if ( count > MAX_NOTIFICATIONS ) {
|
|
self.removeEventListener('message', messageParser);
|
|
return;
|
|
}
|
|
count++;
|
|
handler({textChange:{source}});
|
|
} catch(e) {
|
|
DEBUG.verboseSlow && console.warn('could not parse message', data, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleFrameMessage({textChange}) {
|
|
const {source} = textChange;
|
|
console.log('Telling controller that text changed');
|
|
say({textChange:{source, sessionId, count}});
|
|
}
|
|
}
|
|
|
|
beginTextMutationChecks();
|
|
|
|
function beginTextMutationChecks() {
|
|
// create mutation observer for text
|
|
// throttle output
|
|
|
|
const observer = new MutationObserver(throttle(check, MIN_CHECK_TEXT));
|
|
observer.observe(document.documentElement || document, OBSERVER_OPTS);
|
|
|
|
console.log('Begun observing text changes.');
|
|
|
|
function check() {
|
|
console.log('check');
|
|
const textMutated = document.documentElement.innerText !== lastInnerText;
|
|
if ( textMutated ) {
|
|
DEBUG.verboseSlow && console.log('Text changed');
|
|
lastInnerText = document.documentElement.innerText;
|
|
Top.postMessage({frameTextChangeNotification:{source:location.href}}, '*');
|
|
}
|
|
}
|
|
}
|
|
|
|
// javascript throttle function
|
|
// source: https://stackoverflow.com/a/59378445
|
|
/*
|
|
function throttle(func, timeFrame) {
|
|
var lastTime = 0;
|
|
return function (...args) {
|
|
var now = new Date();
|
|
if (now - lastTime >= timeFrame) {
|
|
func.apply(this, args);
|
|
lastTime = now;
|
|
}
|
|
};
|
|
}
|
|
*/
|
|
|
|
// alternate throttle function with trailing edge call
|
|
// source: https://stackoverflow.com/a/27078401
|
|
///*
|
|
// Notes
|
|
// Returns a function, that, when invoked, will only be triggered at most once
|
|
// during a given window of time. Normally, the throttled function will run
|
|
// as much as it can, without ever going more than once per \`wait\` duration;
|
|
// but if you'd like to disable the execution on the leading edge, pass
|
|
// \`{leading: false}\`. To disable execution on the trailing edge, ditto.
|
|
function throttle(func, wait, options) {
|
|
var context, args, result;
|
|
var timeout = null;
|
|
var previous = 0;
|
|
if (!options) options = {};
|
|
var later = function() {
|
|
previous = options.leading === false ? 0 : Date.now();
|
|
timeout = null;
|
|
result = func.apply(context, args);
|
|
if (!timeout) context = args = null;
|
|
};
|
|
return function() {
|
|
var now = Date.now();
|
|
if (!previous && options.leading === false) previous = now;
|
|
var remaining = wait - (now - previous);
|
|
context = this;
|
|
args = arguments;
|
|
if (remaining <= 0 || remaining > wait) {
|
|
if (timeout) {
|
|
clearTimeout(timeout);
|
|
timeout = null;
|
|
}
|
|
previous = now;
|
|
result = func.apply(context, args);
|
|
if (!timeout) context = args = null;
|
|
} else if (!timeout && options.trailing !== false) {
|
|
timeout = setTimeout(later, remaining);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
//*/
|
|
}
|
|
`;
|
|
}
|