115 lines
4.4 KiB
JavaScript
115 lines
4.4 KiB
JavaScript
(function () {
|
|
let asleep = false;
|
|
chrome.runtime.onMessage.addListener(
|
|
function (request, sender, sendResponse) {
|
|
if (!request.action) return;
|
|
if (asleep && ACTION_WAKEUP != request.action) {
|
|
sendResponse && sendResponse(undefined);
|
|
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);
|
|
// should not recieve any request until the page & script reload
|
|
asleep = true;
|
|
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
|
|
)
|
|
case ACTION_SLEEP:
|
|
asleep = true;
|
|
return "Content script is sleeping.";
|
|
case ACTION_WAKEUP:
|
|
asleep = false;
|
|
return "Content script is available.";
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
})();
|