(function () { chrome.runtime.onMessage.addListener( function (request, sender, sendResponse) { if (!request.action) return; // console.log("Recieved request:",request); doAction(request, sender).then(r => sendResponse && sendResponse(r)); // return true to indicate you wish to send a response asynchronously return true; } ); async function doAction(request, sender) { switch (request.action) { case ACTION_EXTRACT: let data = extract(request.itemsSelector, request.fieldSelectors); return data; case ACTION_GOTO_URL: window.location.replace(request.url); return request.url; case ACTION_REPORT_IN: return request.action; case ACTION_QUERY_URL: return window.location.href; case ACTION_SCROLL_BOTTOM: return executeUntil( () => window.scrollTo(0, document.body.clientHeight), () => document.body.clientHeight - window.scrollY - window.innerHeight < 20, "Scroll to page bottom...", 1000, 10 ) default: break; } } function extract(itemsSelector, fieldSelectors) { // since some elements may be loaded asynchronously. // if one field is never found, we should return undefined, // so that senders can detect to retry until elements loaded. // If user writes wrong selectors, the task retries infinitely. let fieldFound = {}; let items = Array.from(document.querySelectorAll(itemsSelector)); // items may not loaded yet, tell the sender to retry. if (!items.length) return MSG_ELEMENT_NOT_FOUND; let results = items.map( item => { return fieldSelectors.map( selector => { let [cls, attr] = selector.split('@').slice(0, 2); let fieldVals = Array.from(item.querySelectorAll(cls)); if (!fieldVals.length) { return; } fieldFound[selector] = true; return fieldVals.map(find => attr ? find[attr] : find.textContent.trim()).join('\n') } ) } ); // if it exists a field, which is not found in any row, the sender should retry. let shouldWait = fieldSelectors.reduce((p, c) => p || !fieldFound[c], false); return shouldWait ? MSG_ELEMENT_NOT_FOUND : results } /** * Repeatedly execute an function until the the detector returns true. * @param {object} fn the function to execute * @param {object} detector the detector. * @param {string} log messages logged to console. * @param {number} interval interval for detecting * @param {number} limit max execute times of a function * @return {Promise} a promise of the response. */ function executeUntil(fn, detector, log, interval, limit) { interval = interval || 500; let count = 0; return new Promise((resolve, reject) => { loop(); async function loop() { fn(); limit++; if (limit && count >= limit) { reject(false); } setTimeout(() => { let flag = !detector || detector(); if (log) console.log(log, flag ? '(OK)' : '(failed)'); if (flag) { resolve(true); } else { loop(); } }, interval); } }); } })();