1
0
mirror of https://github.com/webrecorder/pywb.git synced 2025-03-24 06:59:52 +01:00

auto-fetch: (#484)

- reworked both proxy and non-proxy mode backing workers to no-longer fetch in burst mode but as sent with a maximum of 20 fetches running at a time
 - added just-fetch to non-proxy mode backing worker
 - updated the auto fetch worker abstraction in non-proxy mode used by wombat to exposed like in proxy mode and ensured that value property for the srcset object is used when sending rewritten srcset values to the backing worker
  - combined the backing worker proxy & non-proxy mode into a single file
  - added rollup config for back auto fetch worker
This commit is contained in:
John Berlin 2019-06-28 01:01:45 -04:00 committed by Ilya Kreymer
parent 7cf5828cdb
commit d869f7ef48
16 changed files with 1081 additions and 1022 deletions

View File

@ -1,20 +1,29 @@
'use strict'; 'use strict';
// thanks wombat // thanks wombat
var STYLE_REGEX = /(url\s*\(\s*[\\"']*)([^)'"]+)([\\"']*\s*\))/gi; var STYLE_REGEX = /(url\s*\(\s*[\\"']*)([^)'"]+)([\\"']*\s*\))/gi;
var IMPORT_REGEX = /(@import\s+[\\"']*)([^)'";]+)([\\"']*\s*;?)/gi; var IMPORT_REGEX = /(@import\s*[\\"']*)([^)'";]+)([\\"']*\s*;?)/gi;
var srcsetSplit = /\s*(\S*\s+[\d.]+[wx]),|(?:\s*,(?:\s+|(?=https?:)))/; var srcsetSplit = /\s*(\S*\s+[\d.]+[wx]),|(?:\s*,(?:\s+|(?=https?:)))/;
var DefaultNumImFetches = 30; var MaxRunningFetches = 15;
var FullImgQDrainLen = 10;
var DefaultNumAvFetches = 5;
var FullAVQDrainLen = 5;
var DataURLPrefix = 'data:'; var DataURLPrefix = 'data:';
var seen = {};
// array of URLs to be fetched
var queue = [];
var runningFetches = 0;
// a URL to resolve relative URLs found in the cssText of CSSMedia rules.
var currentResolver = null;
// the autofetcher instance for this worker var config = {
var autofetcher = null; havePromise: typeof self.Promise !== 'undefined',
haveFetch: typeof self.fetch !== 'undefined',
proxyMode: false,
mod: null,
prefix: null,
prefixMod: null,
relative: null,
rwRe: null
};
function noop() {} if (!config.havePromise) {
if (typeof self.Promise === 'undefined') {
// not kewl we must polyfill Promise // not kewl we must polyfill Promise
self.Promise = function(executor) { self.Promise = function(executor) {
executor(noop, noop); executor(noop, noop);
@ -31,157 +40,97 @@ if (typeof self.Promise === 'undefined') {
}; };
} }
if (typeof self.fetch === 'undefined') { if (!config.haveFetch) {
// not kewl we must polyfill fetch. // not kewl we must polyfill fetch.
self.fetch = function(url) { self.fetch = function(url) {
return new Promise(function(resolve) { return new Promise(function(resolve) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('GET', url); xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (!config.havePromise) {
fetchDoneOrErrored();
}
resolve();
}
};
xhr.send(); xhr.send();
resolve();
}); });
}; };
} }
if (location.search.indexOf('init') !== -1) {
(function() {
var init;
if (typeof self.URL === 'function') {
var loc = new self.URL(location.href);
init = JSON.parse(loc.searchParams.get('init'));
} else {
var search = decodeURIComponent(location.search.split('?')[1]).split('&');
init = JSON.parse(search[0].substr(search[0].indexOf('=') + 1));
init.prefix = decodeURIComponent(init.prefix);
init.baseURI = decodeURIComponent(init.prefix);
}
config.prefix = init.prefix;
config.mod = init.mod;
config.prefixMod = init.prefix + init.mod;
config.rwRe = new RegExp(init.rwRe, 'g');
config.relative = init.prefix.split(location.origin)[1];
config.schemeless = '/' + config.relative;
})();
} else {
config.proxyMode = true;
}
self.onmessage = function(event) { self.onmessage = function(event) {
var data = event.data; var data = event.data;
switch (data.type) { switch (data.type) {
case 'values': case 'values':
autofetcher.autoFetch(data); autoFetch(data);
break;
case 'fetch-all':
justFetch(data);
break; break;
} }
}; };
function AutoFetcher(init) { function noop() {}
if (!(this instanceof AutoFetcher)) {
return new AutoFetcher(init); function fetchDoneOrErrored() {
} runningFetches -= 1;
this.prefix = init.prefix; fetchFromQ();
this.mod = init.mod;
this.prefixMod = init.prefix + init.mod;
this.rwRe = new RegExp(init.rwRe);
// relative url, WorkerLocation is set by owning document
this.relative = init.prefix.split(location.origin)[1];
// schemeless url
this.schemeless = '/' + this.relative;
// local cache of URLs fetched, to reduce server load
this.seen = {};
// array of URLs to be fetched
this.queue = [];
this.avQueue = [];
// should we queue a URL or not
this.queuing = false;
this.queuingAV = false;
this.urlExtractor = this.urlExtractor.bind(this);
this.imgFetchDone = this.imgFetchDone.bind(this);
this.avFetchDone = this.avFetchDone.bind(this);
} }
AutoFetcher.prototype.delay = function() { function fetchURL(urlToBeFetched) {
// 2 second delay seem reasonable runningFetches += 1;
return new Promise(function(resolve, reject) { fetch(urlToBeFetched)
setTimeout(resolve, 2000); .then(fetchDoneOrErrored)
}); .catch(fetchDoneOrErrored);
}; }
AutoFetcher.prototype.imgFetchDone = function() { function queueOrFetch(urlToBeFetched) {
if (this.queue.length > 0) { if (
// we have a Q of some length drain it !urlToBeFetched ||
var autofetcher = this; urlToBeFetched.indexOf(DataURLPrefix) === 0 ||
this.delay().then(function() { seen[urlToBeFetched] != null
autofetcher.queuing = false; ) {
autofetcher.fetchImgs();
});
} else {
this.queuing = false;
}
};
AutoFetcher.prototype.avFetchDone = function() {
if (this.avQueue.length > 0) {
// we have a Q of some length drain it
var autofetcher = this;
this.delay().then(function() {
autofetcher.queuingAV = false;
autofetcher.fetchAV();
});
} else {
this.queuingAV = false;
}
};
AutoFetcher.prototype.fetchAV = function() {
if (this.queuingAV || this.avQueue.length === 0) {
return; return;
} }
// the number of fetches is limited to a maximum of DefaultNumAvFetches + FullAVQDrainLen outstanding fetches seen[urlToBeFetched] = true;
// the baseline maximum number of fetches is DefaultNumAvFetches but if the size(avQueue) <= FullAVQDrainLen if (runningFetches >= MaxRunningFetches) {
// we add them to the current batch. Because audio video resources might be big queue.push(urlToBeFetched);
// we limit how many we fetch at a time drastically
this.queuingAV = true;
var runningFetchers = [];
while (
this.avQueue.length > 0 &&
runningFetchers.length <= DefaultNumAvFetches
) {
runningFetchers.push(fetch(this.avQueue.shift()).catch(noop));
}
if (this.avQueue.length <= FullAVQDrainLen) {
while (this.avQueue.length > 0) {
runningFetchers.push(fetch(this.avQueue.shift()).catch(noop));
}
}
Promise.all(runningFetchers)
.then(this.avFetchDone)
.catch(this.avFetchDone);
};
AutoFetcher.prototype.fetchImgs = function() {
if (this.queuing || this.queue.length === 0) {
return; return;
} }
// the number of fetches is limited to a maximum of DefaultNumImFetches + FullImgQDrainLen outstanding fetches fetchURL(urlToBeFetched);
// the baseline maximum number of fetches is DefaultNumImFetches but if the size(queue) <= FullImgQDrainLen }
// we add them to the current batch
this.queuing = true; function fetchFromQ() {
var runningFetchers = []; while (queue.length && runningFetches < MaxRunningFetches) {
while ( fetchURL(queue.shift());
this.queue.length > 0 &&
runningFetchers.length <= DefaultNumImFetches
) {
runningFetchers.push(fetch(this.queue.shift()).catch(noop));
} }
if (this.queue.length <= FullImgQDrainLen) { }
while (this.queue.length > 0) {
runningFetchers.push(fetch(this.queue.shift()).catch(noop));
}
}
Promise.all(runningFetchers)
.then(this.imgFetchDone)
.catch(this.imgFetchDone);
};
AutoFetcher.prototype.queueNonAVURL = function(url) { function maybeResolveURL(url, base) {
// ensure we do not request data urls
if (url.indexOf(DataURLPrefix) === 0) return;
// check to see if we have seen this url before in order
// to lessen the load against the server content is fetched from
if (this.seen[url] != null) return;
this.seen[url] = true;
this.queue.push(url);
};
AutoFetcher.prototype.queueAVURL = function(url) {
// ensure we do not request data urls
if (url.indexOf(DataURLPrefix) === 0) return;
// check to see if we have seen this url before in order
// to lessen the load against the server content is fetched from
if (this.seen[url] != null) return;
this.seen[url] = true;
this.avQueue.push(url);
};
AutoFetcher.prototype.maybeResolveURL = function(url, base) {
// given a url and base url returns a resolved full URL or // given a url and base url returns a resolved full URL or
// null if resolution was unsuccessful // null if resolution was unsuccessful
try { try {
@ -190,99 +139,129 @@ AutoFetcher.prototype.maybeResolveURL = function(url, base) {
} catch (e) { } catch (e) {
return null; return null;
} }
}; }
AutoFetcher.prototype.maybeFixUpRelSchemelessPrefix = function(url) { function safeResolve(url, resolver) {
// Guard against the exception thrown by the URL constructor if the URL or resolver is bad
// if resolver is undefined/null then this function passes url through
var resolvedURL = url;
if (resolver) {
try {
var _url = new URL(url, resolver);
return _url.href;
} catch (e) {
resolvedURL = url;
}
}
return resolvedURL;
}
function maybeFixUpRelSchemelessPrefix(url) {
// attempt to ensure rewritten relative or schemeless URLs become full URLS! // attempt to ensure rewritten relative or schemeless URLs become full URLS!
// otherwise returns null if this did not happen // otherwise returns null if this did not happen
if (url.indexOf(this.relative) === 0) { if (url.indexOf(config.relative) === 0) {
return url.replace(this.relative, this.prefix); return url.replace(config.relative, config.prefix);
} }
if (url.indexOf(this.schemeless) === 0) { if (url.indexOf(config.schemeless) === 0) {
return url.replace(this.schemeless, this.prefix); return url.replace(config.schemeless, config.prefix);
} }
return null; return null;
}; }
AutoFetcher.prototype.maybeFixUpURL = function(url, resolveOpts) { function maybeFixUpURL(url, resolveOpts) {
// attempt to fix up the url and do our best to ensure we can get dat 200 OK! // attempt to fix up the url and do our best to ensure we can get dat 200 OK!
if (this.rwRe.test(url)) { if (config.rwRe.test(url)) {
return url; return url;
} }
var mod = resolveOpts.mod || 'mp_'; var mod = resolveOpts.mod || 'mp_';
// first check for / (relative) or // (schemeless) rewritten urls // first check for / (relative) or // (schemeless) rewritten urls
var maybeFixed = this.maybeFixUpRelSchemelessPrefix(url); var maybeFixed = maybeFixUpRelSchemelessPrefix(url);
if (maybeFixed != null) { if (maybeFixed != null) {
return maybeFixed; return maybeFixed;
} }
// resolve URL against tag src // resolve URL against tag src
if (resolveOpts.tagSrc != null) { if (resolveOpts.tagSrc != null) {
maybeFixed = this.maybeResolveURL(url, resolveOpts.tagSrc); maybeFixed = maybeResolveURL(url, resolveOpts.tagSrc);
if (maybeFixed != null) { if (maybeFixed != null) {
return this.prefix + mod + '/' + maybeFixed; return config.prefix + mod + '/' + maybeFixed;
} }
} }
// finally last attempt resolve the originating documents base URI // finally last attempt resolve the originating documents base URI
if (resolveOpts.docBaseURI) { if (resolveOpts.docBaseURI) {
maybeFixed = this.maybeResolveURL(url, resolveOpts.docBaseURI); maybeFixed = maybeResolveURL(url, resolveOpts.docBaseURI);
if (maybeFixed != null) { if (maybeFixed != null) {
return this.prefix + mod + '/' + maybeFixed; return config.prefix + mod + '/' + maybeFixed;
} }
} }
// not much to do now..... // not much to do now.....
return this.prefixMod + '/' + url; return config.prefixMod + '/' + url;
}; }
AutoFetcher.prototype.urlExtractor = function( function urlExtractor(match, n1, n2, n3, offset, string) {
match,
n1,
n2,
n3,
offset,
string
) {
// Same function as style_replacer in wombat.rewrite_style, n2 is our URL // Same function as style_replacer in wombat.rewrite_style, n2 is our URL
this.queueNonAVURL(n2); queueOrFetch(n2);
return n1 + n2 + n3; return n1 + n2 + n3;
}; }
AutoFetcher.prototype.handleMedia = function(mediaRules) { function urlExtractorProxyMode(match, n1, n2, n3, offset, string) {
// Same function as style_replacer in wombat.rewrite_style, n2 is our URL
// this.currentResolver is set to the URL which the browser would normally
// resolve relative urls with (URL of the stylesheet) in an exceptionless manner
// (resolvedURL will be undefined if an error occurred)
queueOrFetch(safeResolve(n2, currentResolver));
return n1 + n2 + n3;
}
function handleMedia(mediaRules) {
// this is a broken down rewrite_style // this is a broken down rewrite_style
if (mediaRules == null || mediaRules.length === 0) return; if (mediaRules == null || mediaRules.length === 0) return;
// var rules = mediaRules.values;
for (var i = 0; i < mediaRules.length; i++) { for (var i = 0; i < mediaRules.length; i++) {
mediaRules[i] mediaRules[i]
.replace(STYLE_REGEX, this.urlExtractor) .replace(STYLE_REGEX, urlExtractor)
.replace(IMPORT_REGEX, this.urlExtractor); .replace(IMPORT_REGEX, urlExtractor);
} }
}; }
AutoFetcher.prototype.handleSrc = function(srcValues, context) { function handleMediaProxyMode(mediaRules) {
// this is a broken down rewrite_style
if (mediaRules == null || mediaRules.length === 0) return;
for (var i = 0; i < mediaRules.length; i++) {
// set currentResolver to the value of this stylesheets URL, done to ensure we do not have to
// create functions on each loop iteration because we potentially create a new `URL` object
// twice per iteration
currentResolver = mediaRules[i].resolve;
mediaRules[i].cssText
.replace(STYLE_REGEX, urlExtractorProxyMode)
.replace(IMPORT_REGEX, urlExtractorProxyMode);
}
}
function handleSrc(srcValues, context) {
var resolveOpts = { docBaseURI: context.docBaseURI }; var resolveOpts = { docBaseURI: context.docBaseURI };
if (srcValues.value) { if (srcValues.value) {
resolveOpts.mod = srcValues.mod; resolveOpts.mod = srcValues.mod;
if (resolveOpts.mod === 1) { return queueOrFetch(maybeFixUpURL(srcValues.value.trim(), resolveOpts));
return this.queueNonAVURL(
this.maybeFixUpURL(srcValues.value.trim(), resolveOpts)
);
}
return this.queueAVURL(
this.maybeFixUpURL(srcValues.value.trim(), resolveOpts)
);
} }
var len = srcValues.values.length; var len = srcValues.values.length;
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
var value = srcValues.values[i]; var value = srcValues.values[i];
resolveOpts.mod = value.mod; resolveOpts.mod = value.mod;
if (resolveOpts.mod === 'im_') { queueOrFetch(maybeFixUpURL(value.src, resolveOpts));
this.queueNonAVURL(this.maybeFixUpURL(value.src, resolveOpts));
} else {
this.queueAVURL(this.maybeFixUpURL(value.src, resolveOpts));
}
} }
}; }
AutoFetcher.prototype.extractSrcSetNotPreSplit = function(ssV, resolveOpts) { function handleSrcProxyMode(srcValues) {
// preservation worker in proxy mode sends us the value of the srcset attribute of an element
// and a URL to correctly resolve relative URLS. Thus we must recreate rewrite_srcset logic here
if (srcValues == null || srcValues.length === 0) return;
var srcVal;
for (var i = 0; i < srcValues.length; i++) {
srcVal = srcValues[i];
queueOrFetch(safeResolve(srcVal.src, srcVal.resolve));
}
}
function extractSrcSetNotPreSplit(ssV, resolveOpts) {
if (!ssV) return; if (!ssV) return;
// was from extract from local doc so we need to duplicate work // was from extract from local doc so we need to duplicate work
var srcsetValues = ssV.split(srcsetSplit); var srcsetValues = ssV.split(srcsetSplit);
@ -290,41 +269,38 @@ AutoFetcher.prototype.extractSrcSetNotPreSplit = function(ssV, resolveOpts) {
// grab the URL not width/height key // grab the URL not width/height key
if (srcsetValues[i]) { if (srcsetValues[i]) {
var value = srcsetValues[i].trim().split(' ')[0]; var value = srcsetValues[i].trim().split(' ')[0];
var maybeResolvedURL = this.maybeFixUpURL(value.trim(), resolveOpts); var maybeResolvedURL = maybeFixUpURL(value.trim(), resolveOpts);
if (resolveOpts.mod === 'im_') { queueOrFetch(maybeResolvedURL);
this.queueNonAVURL(maybeResolvedURL);
} else {
this.queueAVURL(maybeResolvedURL);
}
} }
} }
}; }
AutoFetcher.prototype.extractSrcset = function(srcsets, context) { function extractSrcset(srcsets) {
// was rewrite_srcset and only need to q // was rewrite_srcset and only need to q
for (var i = 0; i < srcsets.length; i++) { for (var i = 0; i < srcsets.length; i++) {
// grab the URL not width/height key // grab the URL not width/height key
var url = srcsets[i].split(' ')[0]; var url = srcsets[i].split(' ')[0];
if (context.mod === 'im_') { queueOrFetch(url);
this.queueNonAVURL(url);
} else {
this.queueAVURL(url);
}
} }
}; }
AutoFetcher.prototype.handleSrcset = function(srcset, context) { function handleSrcset(srcset, context) {
var resolveOpts = { docBaseURI: context.docBaseURI }; if (srcset == null) return;
var resolveOpts = {
docBaseURI: context.docBaseURI,
mode: null,
tagSrc: null
};
if (srcset.value) { if (srcset.value) {
// we have a single value, this srcset came from either // we have a single value, this srcset came from either
// preserveDataSrcset (not presplit) preserveSrcset (presplit) // preserveDataSrcset (not presplit) preserveSrcset (presplit)
resolveOpts.mod = srcset.mod; resolveOpts.mod = srcset.mod;
if (!srcset.presplit) { if (!srcset.presplit) {
// extract URLs from the srcset string // extract URLs from the srcset string
return this.extractSrcSetNotPreSplit(srcset.value, resolveOpts); return extractSrcSetNotPreSplit(srcset.value, resolveOpts);
} }
// we have an array of srcset URL strings // we have an array of srcset URL strings
return this.extractSrcset(srcset.value, resolveOpts); return extractSrcset(srcset.value);
} }
// we have an array of values, these srcsets came from extractFromLocalDoc // we have an array of values, these srcsets came from extractFromLocalDoc
var len = srcset.values.length; var len = srcset.values.length;
@ -332,38 +308,64 @@ AutoFetcher.prototype.handleSrcset = function(srcset, context) {
var ssv = srcset.values[i]; var ssv = srcset.values[i];
resolveOpts.mod = ssv.mod; resolveOpts.mod = ssv.mod;
resolveOpts.tagSrc = ssv.tagSrc; resolveOpts.tagSrc = ssv.tagSrc;
this.extractSrcSetNotPreSplit(ssv.srcset, resolveOpts); extractSrcSetNotPreSplit(ssv.srcset, resolveOpts);
} }
}; }
AutoFetcher.prototype.autoFetch = function(data) { function handleSrcsetProxyMode(srcsets) {
// preservation worker in proxy mode sends us the value of the srcset attribute of an element
// and a URL to correctly resolve relative URLS. Thus we must recreate rewrite_srcset logic here
if (srcsets == null) return;
var length = srcsets.length;
var extractedSrcSet, srcsetValue, ssSplit, j;
for (var i = 0; i < length; i++) {
extractedSrcSet = srcsets[i];
ssSplit = extractedSrcSet.srcset.split(srcsetSplit);
for (j = 0; j < ssSplit.length; j++) {
if (ssSplit[j]) {
srcsetValue = ssSplit[j].trim();
if (srcsetValue) {
queueOrFetch(
safeResolve(srcsetValue.split(' ')[0], extractedSrcSet.resolve)
);
}
}
}
}
}
function autoFetch(data) {
// we got a message and now we autofetch! // we got a message and now we autofetch!
// these calls turn into no ops if they have no work // these calls turn into no ops if they have no work
if (data.media) { if (data.media) {
this.handleMedia(data.media); if (config.proxyMode) {
handleMediaProxyMode(data.media);
} else {
handleMedia(data.media);
}
} }
if (data.src) { if (data.src) {
this.handleSrc(data.src, data.context || {}); if (config.proxyMode) {
handleSrcProxyMode(data.src);
} else {
handleSrc(data.src, data.context || { docBaseURI: null });
}
} }
if (data.srcset) { if (data.srcset) {
this.handleSrcset(data.srcset, data.context || {}); if (config.proxyMode) {
handleSrcsetProxyMode(data.srcset);
} else {
handleSrcset(data.srcset, data.context || { docBaseURI: null });
}
}
}
function justFetch(data) {
// we got a message containing only urls to be fetched
if (data == null || data.values == null) return;
for (var i = 0; i < data.values.length; ++i) {
queueOrFetch(data.values[i]);
} }
this.fetchImgs();
this.fetchAV();
};
// initialize ourselves from the query params :)
try {
var loc = new self.URL(location.href);
autofetcher = new AutoFetcher(JSON.parse(loc.searchParams.get('init')));
} catch (e) {
// likely we are in an older version of safari
var search = decodeURIComponent(location.search.split('?')[1]).split('&');
var init = JSON.parse(search[0].substr(search[0].indexOf('=') + 1));
init.prefix = decodeURIComponent(init.prefix);
init.baseURI = decodeURIComponent(init.baseURI);
autofetcher = new AutoFetcher(init);
} }

View File

@ -1,303 +0,0 @@
'use strict';
// thanks wombat
var STYLE_REGEX = /(url\s*\(\s*[\\"']*)([^)'"]+)([\\"']*\s*\))/gi;
var IMPORT_REGEX = /(@import\s+[\\"']*)([^)'";]+)([\\"']*\s*;?)/gi;
var srcsetSplit = /\s*(\S*\s+[\d.]+[wx]),|(?:\s*,(?:\s+|(?=https?:)))/;
var DefaultNumImFetches = 30;
var FullImgQDrainLen = 10;
var DefaultNumAvFetches = 5;
var FullAVQDrainLen = 5;
var DataURLPrefix = 'data:';
var FetchDelay = 1000;
// the autofetcher instance for this worker
var autofetcher = null;
function noop() {}
if (typeof self.Promise === 'undefined') {
// not kewl we must polyfill Promise
self.Promise = function(executor) {
executor(noop, noop);
};
self.Promise.prototype.then = function(cb) {
if (cb) cb();
return this;
};
self.Promise.prototype.catch = function() {
return this;
};
self.Promise.all = function(values) {
return new Promise(noop);
};
}
if (typeof self.fetch === 'undefined') {
// not kewl we must polyfill fetch.
self.fetch = function(url) {
return new Promise(function(resolve) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
resolve();
});
};
}
self.onmessage = function(event) {
var data = event.data;
switch (data.type) {
case 'values':
autofetcher.autofetchMediaSrcset(data);
break;
case 'fetch-all':
autofetcher.justFetch(data);
break;
}
};
function AutoFetcher() {
if (!(this instanceof AutoFetcher)) {
return new AutoFetcher();
}
// local cache of URLs fetched, to reduce server load
this.seen = {};
// array of URLs to be fetched
this.queue = [];
this.avQueue = [];
// should we queue a URL or not
this.queuing = false;
// a URL to resolve relative URLs found in the cssText of CSSMedia rules.
this.currentResolver = null;
// should we queue a URL or not
this.queuing = false;
this.queuingAV = false;
this.urlExtractor = this.urlExtractor.bind(this);
this.imgFetchDone = this.imgFetchDone.bind(this);
this.avFetchDone = this.avFetchDone.bind(this);
}
AutoFetcher.prototype.delay = function() {
return new Promise(function(resolve, reject) {
setTimeout(resolve, FetchDelay);
});
};
AutoFetcher.prototype.imgFetchDone = function() {
if (this.queue.length > 0) {
// we have a Q of some length drain it
var autofetcher = this;
this.delay().then(function() {
autofetcher.queuing = false;
autofetcher.fetchImgs();
});
} else {
this.queuing = false;
}
};
AutoFetcher.prototype.avFetchDone = function() {
if (this.avQueue.length > 0) {
// we have a Q of some length drain it
var autofetcher = this;
this.delay().then(function() {
autofetcher.queuingAV = false;
autofetcher.fetchAV();
});
} else {
this.queuingAV = false;
}
};
AutoFetcher.prototype.fetchAV = function() {
if (this.queuingAV || this.avQueue.length === 0) {
return;
}
// the number of fetches is limited to a maximum of DefaultNumAvFetches + FullAVQDrainLen outstanding fetches
// the baseline maximum number of fetches is DefaultNumAvFetches but if the size(avQueue) <= FullAVQDrainLen
// we add them to the current batch. Because audio video resources might be big
// we limit how many we fetch at a time drastically
this.queuingAV = true;
var runningFetchers = [];
while (
this.avQueue.length > 0 &&
runningFetchers.length <= DefaultNumAvFetches
) {
runningFetchers.push(fetch(this.avQueue.shift()).catch(noop));
}
if (this.avQueue.length <= FullAVQDrainLen) {
while (this.avQueue.length > 0) {
runningFetchers.push(fetch(this.avQueue.shift()).catch(noop));
}
}
Promise.all(runningFetchers)
.then(this.avFetchDone)
.catch(this.avFetchDone);
};
AutoFetcher.prototype.fetchImgs = function() {
if (this.queuing || this.queue.length === 0) {
return;
}
// the number of fetches is limited to a maximum of DefaultNumImFetches + FullImgQDrainLen outstanding fetches
// the baseline maximum number of fetches is DefaultNumImFetches but if the size(queue) <= FullImgQDrainLen
// we add them to the current batch
this.queuing = true;
var runningFetchers = [];
while (
this.queue.length > 0 &&
runningFetchers.length <= DefaultNumImFetches
) {
runningFetchers.push(fetch(this.queue.shift()).catch(noop));
}
if (this.queue.length <= FullImgQDrainLen) {
while (this.queue.length > 0) {
runningFetchers.push(fetch(this.queue.shift()).catch(noop));
}
}
Promise.all(runningFetchers)
.then(this.imgFetchDone)
.catch(this.imgFetchDone);
};
AutoFetcher.prototype.queueNonAVURL = function(url) {
// ensure we do not request data urls
if (url.indexOf(DataURLPrefix) === 0) return;
// check to see if we have seen this url before in order
// to lessen the load against the server content is fetched from
if (this.seen[url] != null) return;
this.seen[url] = true;
this.queue.push(url);
};
AutoFetcher.prototype.queueAVURL = function(url) {
// ensure we do not request data urls
if (url.indexOf(DataURLPrefix) === 0) return;
// check to see if we have seen this url before in order
// to lessen the load against the server content is fetched from
if (this.seen[url] != null) return;
this.seen[url] = true;
this.avQueue.push(url);
};
AutoFetcher.prototype.safeResolve = function(url, resolver) {
// Guard against the exception thrown by the URL constructor if the URL or resolver is bad
// if resolver is undefined/null then this function passes url through
var resolvedURL = url;
if (resolver) {
try {
resolvedURL = new URL(url, resolver).href;
} catch (e) {
resolvedURL = url;
}
}
return resolvedURL;
};
AutoFetcher.prototype.urlExtractor = function(
match,
n1,
n2,
n3,
offset,
string
) {
// Same function as style_replacer in wombat.rewrite_style, n2 is our URL
// this.currentResolver is set to the URL which the browser would normally
// resolve relative urls with (URL of the stylesheet) in an exceptionless manner
// (resolvedURL will be undefined if an error occurred)
var resolvedURL = this.safeResolve(n2, this.currentResolver);
if (resolvedURL) {
this.queueNonAVURL(resolvedURL);
}
return n1 + n2 + n3;
};
AutoFetcher.prototype.extractMedia = function(mediaRules) {
// this is a broken down rewrite_style
if (mediaRules == null) return;
for (var i = 0; i < mediaRules.length; i++) {
// set currentResolver to the value of this stylesheets URL, done to ensure we do not have to
// create functions on each loop iteration because we potentially create a new `URL` object
// twice per iteration
this.currentResolver = mediaRules[i].resolve;
mediaRules[i].cssText
.replace(STYLE_REGEX, this.urlExtractor)
.replace(IMPORT_REGEX, this.urlExtractor);
}
};
AutoFetcher.prototype.extractSrcset = function(srcsets) {
// preservation worker in proxy mode sends us the value of the srcset attribute of an element
// and a URL to correctly resolve relative URLS. Thus we must recreate rewrite_srcset logic here
if (srcsets == null) return;
var length = srcsets.length;
var extractedSrcSet, srcsetValue, ssSplit, j;
for (var i = 0; i < length; i++) {
extractedSrcSet = srcsets[i];
ssSplit = extractedSrcSet.srcset.split(srcsetSplit);
console.log(ssSplit);
for (j = 0; j < ssSplit.length; j++) {
if (ssSplit[j]) {
srcsetValue = ssSplit[j].trim();
if (srcsetValue.length > 0) {
// resolve the URL in an exceptionless manner (resolvedURL will be undefined if an error occurred)
var resolvedURL = this.safeResolve(
srcsetValue.split(' ')[0],
extractedSrcSet.resolve
);
if (resolvedURL) {
if (extractedSrcSet.mod === 'im_') {
this.queueNonAVURL(resolvedURL);
} else {
this.queueAVURL(resolvedURL);
}
} else {
console.log(resolvedURL);
}
} else {
console.log(srcsetValue);
}
}
}
}
};
AutoFetcher.prototype.extractSrc = function(srcVals) {
// preservation worker in proxy mode sends us the value of the srcset attribute of an element
// and a URL to correctly resolve relative URLS. Thus we must recreate rewrite_srcset logic here
if (srcVals == null || srcVals.length === 0) return;
var length = srcVals.length;
var srcVal;
for (var i = 0; i < length; i++) {
srcVal = srcVals[i];
var resolvedURL = this.safeResolve(srcVal.src, srcVal.resolve);
if (resolvedURL) {
if (srcVal.mod === 'im_') {
this.queueNonAVURL(resolvedURL);
} else {
this.queueAVURL(resolvedURL);
}
}
}
};
AutoFetcher.prototype.autofetchMediaSrcset = function(data) {
// we got a message and now we autofetch!
// these calls turn into no ops if they have no work
this.extractMedia(data.media);
this.extractSrcset(data.srcset);
this.extractSrc(data.src);
this.fetchImgs();
this.fetchAV();
};
AutoFetcher.prototype.justFetch = function(data) {
// we got a message containing only urls to be fetched
if (data == null || data.values == null) return;
for (var i = 0; i < data.values.length; ++i) {
this.queueNonAVURL(data.values[i]);
}
this.fetchImgs();
};
autofetcher = new AutoFetcher();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/* /*
Copyright(c) 2013-2018 Rhizome and Ilya Kreymer. Released under the GNU General Public License. Copyright(c) 2013-2018 Rhizome and Contributors. Released under the GNU General Public License.
This file is part of pywb, https://github.com/webrecorder/pywb This file is part of pywb, https://github.com/webrecorder/pywb

View File

@ -363,7 +363,7 @@ class TestProxyAutoFetchWorkerEndPoints(BaseTestProxy):
def test_proxy_worker_options_request(self, scheme): def test_proxy_worker_options_request(self, scheme):
expected_origin = '{0}://example.com'.format(scheme) expected_origin = '{0}://example.com'.format(scheme)
res = requests.options('{0}://pywb.proxy/static/autoFetchWorkerProxyMode.js'.format(scheme), res = requests.options('{0}://pywb.proxy/static/autoFetchWorker.js'.format(scheme),
headers=dict(Origin=expected_origin), headers=dict(Origin=expected_origin),
proxies=self.proxies, verify=self.root_ca_file) proxies=self.proxies, verify=self.root_ca_file)
@ -372,7 +372,7 @@ class TestProxyAutoFetchWorkerEndPoints(BaseTestProxy):
def test_proxy_worker_fetch(self, scheme): def test_proxy_worker_fetch(self, scheme):
origin = '{0}://example.com'.format(scheme) origin = '{0}://example.com'.format(scheme)
url = '{0}://pywb.proxy/static/autoFetchWorkerProxyMode.js'.format(scheme) url = '{0}://pywb.proxy/static/autoFetchWorker.js'.format(scheme)
res = requests.get(url, res = requests.get(url,
headers=dict(Origin=origin), headers=dict(Origin=origin),
proxies=self.proxies, verify=self.root_ca_file) proxies=self.proxies, verify=self.root_ca_file)
@ -380,11 +380,11 @@ class TestProxyAutoFetchWorkerEndPoints(BaseTestProxy):
assert res.ok assert res.ok
assert res.headers.get('Content-Type') == 'application/javascript' assert res.headers.get('Content-Type') == 'application/javascript'
assert res.headers.get('Access-Control-Allow-Origin') == origin assert res.headers.get('Access-Control-Allow-Origin') == origin
assert 'AutoFetcher.prototype.safeResolve' in res.text assert 'function handleSrcsetProxyMode' in res.text
res = requests.get(url, proxies=self.proxies, verify=self.root_ca_file) res = requests.get(url, proxies=self.proxies, verify=self.root_ca_file)
assert res.ok assert res.ok
assert res.headers.get('Content-Type') == 'application/javascript' assert res.headers.get('Content-Type') == 'application/javascript'
assert res.headers.get('Access-Control-Allow-Origin') == '*' assert res.headers.get('Access-Control-Allow-Origin') == '*'
assert 'AutoFetcher.prototype.safeResolve' in res.text assert 'function handleSrcsetProxyMode' in res.text

View File

@ -4,25 +4,26 @@
"main": "index.js", "main": "index.js",
"license": "GPL-3.0", "license": "GPL-3.0",
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^7.0.0", "@types/fs-extra": "^8.0.0",
"ava": "^2.1.0", "ava": "^2.1.0",
"chokidar": "^3.0.1", "chokidar": "^3.0.1",
"chrome-remote-interface-extra": "^1.1.1", "chrome-remote-interface-extra": "^1.1.1",
"eslint": "^5.16.0", "eslint": "^6.0.1",
"eslint-config-prettier": "^5.0.0", "eslint-config-prettier": "^6.0.0",
"eslint-plugin-prettier": "^3.1.0", "eslint-plugin-prettier": "^3.1.0",
"fastify": "^2.5.0", "fastify": "^2.6.0",
"fastify-favicon": "^2.0.0", "fastify-favicon": "^2.0.0",
"fastify-graceful-shutdown": "^2.0.1", "fastify-graceful-shutdown": "^2.0.1",
"fastify-static": "^2.5.0", "fastify-static": "^2.5.0",
"fs-extra": "^8.0.1", "fs-extra": "^8.0.1",
"just-launch-chrome": "^1.0.0",
"lodash-es": "^4.17.11", "lodash-es": "^4.17.11",
"prettier": "^1.18.2", "prettier": "^1.18.2",
"rollup": "^1.15.6", "rollup": "^1.16.2",
"rollup-plugin-babel-minify": "^8.0.0", "rollup-plugin-babel-minify": "^8.0.0",
"rollup-plugin-cleanup": "^3.1.1", "rollup-plugin-cleanup": "^3.1.1",
"rollup-plugin-commonjs": "^10.0.0", "rollup-plugin-commonjs": "^10.0.1",
"rollup-plugin-node-resolve": "^5.0.3", "rollup-plugin-node-resolve": "^5.1.0",
"rollup-plugin-uglify": "^6.0.2", "rollup-plugin-uglify": "^6.0.2",
"rollup-plugin-uglify-es": "^0.0.1", "rollup-plugin-uglify-es": "^0.0.1",
"tls-keygen": "^3.7.0" "tls-keygen": "^3.7.0"
@ -58,9 +59,7 @@
] ]
}, },
"resolutions": { "resolutions": {
"*/**/graceful-fs": "~4.1.15" "*/**/graceful-fs": "~4.1.15",
}, "*/**/fs-extra": "~8.0.1"
"dependencies": {
"just-launch-chrome": "^1.0.0"
} }
} }

View File

@ -8,6 +8,14 @@ const noStrict = {
} }
}; };
const watchOptions = {
exclude: 'node_modules/**',
chokidar: {
alwaysStat: true,
usePolling: true
}
};
const wombat = { const wombat = {
input: 'src/wbWombat.js', input: 'src/wbWombat.js',
output: { output: {
@ -16,13 +24,7 @@ const wombat = {
sourcemap: false, sourcemap: false,
format: 'iife' format: 'iife'
}, },
watch: { watch: watchOptions,
exclude: 'node_modules/**',
chokidar: {
alwaysStat: true,
usePolling: true
}
},
plugins: [noStrict] plugins: [noStrict]
}; };
@ -34,13 +36,7 @@ const wombatProxyMode = {
sourcemap: false, sourcemap: false,
format: 'iife' format: 'iife'
}, },
watch: { watch: watchOptions,
exclude: 'node_modules/**',
chokidar: {
alwaysStat: true,
usePolling: true
}
},
plugins: [noStrict] plugins: [noStrict]
}; };
@ -53,24 +49,42 @@ const wombatWorker = {
sourcemap: false, sourcemap: false,
exports: 'none' exports: 'none'
}, },
watch: { watch: watchOptions,
exclude: 'node_modules/**',
chokidar: {
alwaysStat: true,
usePolling: true
}
},
plugins: [noStrict] plugins: [noStrict]
}; };
const wombatAutoFetchWorker = {
input: 'src/autoFetchWorker.js',
output: {
name: 'autoFetchWorker',
file: path.join(basePywbOutput, 'autoFetchWorker.js'),
format: 'es',
sourcemap: false,
exports: 'none'
},
watch: watchOptions,
plugins: [
{
renderChunk(code) {
if (!code.startsWith("'use strict';")) {
return "'use strict';\n" + code;
}
return code;
}
}
]
};
let config; let config;
if (process.env.ALL) { if (process.env.ALL) {
config = [wombat, wombatProxyMode, wombatWorker]; config = [wombat, wombatProxyMode, wombatWorker, wombatAutoFetchWorker];
} else if (process.env.PROXY) { } else if (process.env.PROXY) {
config = wombatProxyMode; config = wombatProxyMode;
} else if (process.env.WORKER) { } else if (process.env.WORKER) {
config = wombatProxyMode; config = wombatProxyMode;
} else if (process.env.AUTO_WORKER) {
config = wombatAutoFetchWorker;
} else { } else {
config = wombat; config = wombat;
} }

View File

@ -2,7 +2,7 @@ import * as path from 'path';
import minify from 'rollup-plugin-babel-minify'; import minify from 'rollup-plugin-babel-minify';
const license = `/* const license = `/*
Copyright(c) 2013-2018 Rhizome and Ilya Kreymer. Released under the GNU General Public License. Copyright(c) 2013-2018 Rhizome and Contributors. Released under the GNU General Public License.
This file is part of pywb, https://github.com/webrecorder/pywb This file is part of pywb, https://github.com/webrecorder/pywb
@ -74,5 +74,25 @@ export default [
sourcemap: false, sourcemap: false,
exports: 'none' exports: 'none'
} }
},
{
input: 'src/autoFetchWorker.js',
plugins: [
{
renderChunk(code) {
if (!code.startsWith("'use strict';")) {
return "'use strict';\n" + code;
}
return code;
}
}
],
output: {
name: 'autoFetchWorker',
file: path.join(basePywbOutput, 'autoFetchWorker.js'),
format: 'es',
sourcemap: false,
exports: 'none'
}
} }
]; ];

633
wombat/src/autoFetchWorker.js Executable file → Normal file
View File

@ -1,296 +1,371 @@
/* eslint-disable camelcase */ 'use strict';
// thanks wombat
var STYLE_REGEX = /(url\s*\(\s*[\\"']*)([^)'"]+)([\\"']*\s*\))/gi;
var IMPORT_REGEX = /(@import\s*[\\"']*)([^)'";]+)([\\"']*\s*;?)/gi;
var srcsetSplit = /\s*(\S*\s+[\d.]+[wx]),|(?:\s*,(?:\s+|(?=https?:)))/;
var MaxRunningFetches = 15;
var DataURLPrefix = 'data:';
var seen = {};
// array of URLs to be fetched
var queue = [];
var runningFetches = 0;
// a URL to resolve relative URLs found in the cssText of CSSMedia rules.
var currentResolver = null;
import { autobind } from './wombatUtils'; var config = {
havePromise: typeof self.Promise !== 'undefined',
haveFetch: typeof self.fetch !== 'undefined',
proxyMode: false,
mod: null,
prefix: null,
prefixMod: null,
relative: null,
rwRe: null
};
/** if (!config.havePromise) {
* @param {Wombat} wombat // not kewl we must polyfill Promise
*/ self.Promise = function(executor) {
export default function AutoFetchWorker(wombat) { executor(noop, noop);
if (!(this instanceof AutoFetchWorker)) { };
return new AutoFetchWorker(wombat); self.Promise.prototype.then = function(cb) {
} if (cb) cb();
// specifically target the elements we desire return this;
this.elemSelector = };
'img[srcset], img[data-srcset], img[data-src], video[srcset], video[data-srcset], video[data-src], audio[srcset], audio[data-srcset], audio[data-src], ' + self.Promise.prototype.catch = function() {
'picture > source[srcset], picture > source[data-srcset], picture > source[data-src], ' + return this;
'video > source[srcset], video > source[data-srcset], video > source[data-src], ' + };
'audio > source[srcset], audio > source[data-srcset], audio > source[data-src]'; self.Promise.all = function(values) {
return new Promise(noop);
this.isTop = wombat.$wbwindow === wombat.$wbwindow.__WB_replay_top; };
/** @type {Wombat} */
this.wombat = wombat;
/** @type {Window} */
this.$wbwindow = wombat.$wbwindow;
/** @type {?Worker|Object} */
this.worker = null;
autobind(this);
this._initWorker();
} }
/** if (!config.haveFetch) {
* Initializes the backing worker IFF the execution context we are in is // not kewl we must polyfill fetch.
* the replay tops otherwise creates a dummy worker that simply bounces the self.fetch = function(url) {
* message that would have been sent to the backing worker to replay top. return new Promise(function(resolve) {
* var xhr = new XMLHttpRequest();
* If creation of the worker fails, likely due to the execution context we xhr.open('GET', url, true);
* are currently in having an null origin, we fallback to dummy worker creation. xhr.onreadystatechange = function() {
* @private if (xhr.readyState === 4) {
*/ if (!config.havePromise) {
AutoFetchWorker.prototype._initWorker = function() { fetchDoneOrErrored();
var wombat = this.wombat; }
if (this.isTop) { resolve();
// we are top and can will own this worker }
// setup URL for the kewl case };
// Normal replay and preservation mode pworker setup, its all one origin so YAY! xhr.send();
var workerURL =
(wombat.wb_info.auto_fetch_worker_prefix ||
wombat.wb_info.static_prefix) +
'autoFetchWorker.js?init=' +
encodeURIComponent(
JSON.stringify({
mod: wombat.wb_info.mod,
prefix: wombat.wb_abs_prefix,
rwRe: wombat.wb_unrewrite_rx
})
);
try {
this.worker = new Worker(workerURL);
return;
} catch (e) {
// it is likely we are in some kind of horrid iframe setup
// and the execution context we are currently in has a null origin
console.error(
'Failed to create auto fetch worker\n',
e,
'\nFalling back to non top behavior'
);
}
}
// add only the portions of the worker interface we use since we are not top
// and if in proxy mode start check polling
this.worker = {
postMessage: function(msg) {
if (!msg.wb_type) {
msg = { wb_type: 'aaworker', msg: msg };
}
wombat.$wbwindow.__WB_replay_top.__orig_postMessage(msg, '*');
},
terminate: function() {}
};
};
/**
* Extracts the media rules from the supplied CSSStyleSheet object if any
* are present and returns an array of the media cssText
* @param {CSSStyleSheet} sheet
* @return {Array<string>}
*/
AutoFetchWorker.prototype.extractMediaRulesFromSheet = function(sheet) {
var rules;
var media = [];
try {
rules = sheet.cssRules || sheet.rules;
} catch (e) {
return media;
}
// loop through each rule of the stylesheet
for (var i = 0; i < rules.length; ++i) {
var rule = rules[i];
if (rule.type === CSSRule.MEDIA_RULE) {
// we are a media rule so get its text
media.push(rule.cssText);
}
}
return media;
};
/**
* Extracts the media rules from the supplied CSSStyleSheet object if any
* are present after a tick of the event loop sending the results of the
* extraction to the backing worker
* @param {CSSStyleSheet|StyleSheet} sheet
*/
AutoFetchWorker.prototype.deferredSheetExtraction = function(sheet) {
var afw = this;
// defer things until next time the Promise.resolve Qs are cleared
Promise.resolve().then(function() {
// loop through each rule of the stylesheet
var media = afw.extractMediaRulesFromSheet(sheet);
if (media.length > 0) {
// we have some media rules to preserve
afw.preserveMedia(media);
}
});
};
/**
* Terminates the backing worker. This is a no op when we are not
* operating in the execution context of replay top
*/
AutoFetchWorker.prototype.terminate = function() {
// terminate the worker, a no op when not replay top
this.worker.terminate();
};
/**
* Sends a message to backing worker. If deferred is true
* the message is sent after one tick of the event loop
* @param {Object} msg
* @param {boolean} [deferred]
*/
AutoFetchWorker.prototype.postMessage = function(msg, deferred) {
if (deferred) {
var afWorker = this;
Promise.resolve().then(function() {
afWorker.worker.postMessage(msg);
}); });
};
}
if (location.search.indexOf('init') !== -1) {
(function() {
var init;
if (typeof self.URL === 'function') {
var loc = new self.URL(location.href);
init = JSON.parse(loc.searchParams.get('init'));
} else {
var search = decodeURIComponent(location.search.split('?')[1]).split('&');
init = JSON.parse(search[0].substr(search[0].indexOf('=') + 1));
init.prefix = decodeURIComponent(init.prefix);
init.baseURI = decodeURIComponent(init.prefix);
}
config.prefix = init.prefix;
config.mod = init.mod;
config.prefixMod = init.prefix + init.mod;
config.rwRe = new RegExp(init.rwRe, 'g');
config.relative = init.prefix.split(location.origin)[1];
config.schemeless = '/' + config.relative;
})();
} else {
config.proxyMode = true;
}
self.onmessage = function(event) {
var data = event.data;
switch (data.type) {
case 'values':
autoFetch(data);
break;
case 'fetch-all':
justFetch(data);
break;
}
};
function noop() {}
function fetchDoneOrErrored() {
runningFetches -= 1;
fetchFromQ();
}
function fetchURL(urlToBeFetched) {
runningFetches += 1;
fetch(urlToBeFetched)
.then(fetchDoneOrErrored)
.catch(fetchDoneOrErrored);
}
function queueOrFetch(urlToBeFetched) {
if (
!urlToBeFetched ||
urlToBeFetched.indexOf(DataURLPrefix) === 0 ||
seen[urlToBeFetched] != null
) {
return; return;
} }
this.worker.postMessage(msg); seen[urlToBeFetched] = true;
}; if (runningFetches >= MaxRunningFetches) {
queue.push(urlToBeFetched);
/** return;
* Sends the supplied srcset value to the backing worker for preservation
* @param {string|Array<string>} srcset
* @param {string} [mod]
*/
AutoFetchWorker.prototype.preserveSrcset = function(srcset, mod) {
// send values from rewriteSrcset to the worker
this.postMessage(
{
type: 'values',
srcset: { values: srcset, mod: mod, presplit: true }
},
true
);
};
/**
* Send the value of the supplied elements data-srcset attribute to the
* backing worker for preservation
* @param {Node} elem
*/
AutoFetchWorker.prototype.preserveDataSrcset = function(elem) {
// send values from rewriteAttr srcset to the worker deferred
// to ensure the page viewer sees the images first
this.postMessage(
{
type: 'values',
srcset: {
value: elem.dataset.srcset,
mod: this.rwMod(elem),
presplit: false
}
},
true
);
};
/**
* Sends the supplied array of cssText from media rules to the backing worker
* @param {Array<string>} media
*/
AutoFetchWorker.prototype.preserveMedia = function(media) {
// send CSSMediaRule values to the worker
this.postMessage({ type: 'values', media: media }, true);
};
/**
* Extracts the value of the srcset property if it exists from the supplied
* element
* @param {Element} elem
* @return {?string}
*/
AutoFetchWorker.prototype.getSrcset = function(elem) {
if (this.wombat.wb_getAttribute) {
return this.wombat.wb_getAttribute.call(elem, 'srcset');
} }
return elem.getAttribute('srcset'); fetchURL(urlToBeFetched);
}; }
/** function fetchFromQ() {
* Returns the correct rewrite modifier for the supplied element while (queue.length && runningFetches < MaxRunningFetches) {
* @param {Element} elem fetchURL(queue.shift());
* @return {string}
*/
AutoFetchWorker.prototype.rwMod = function(elem) {
switch (elem.tagName) {
case 'SOURCE':
if (elem.parentElement && elem.parentElement.tagName === 'PICTURE') {
return 'im_';
}
return 'oe_';
case 'IMG':
return 'im_';
} }
return 'oe_'; }
};
/** function maybeResolveURL(url, base) {
* Extracts the media rules from stylesheets and the (data-)srcset URLs from // given a url and base url returns a resolved full URL or
* image elements the current context's document contains // null if resolution was unsuccessful
*/ try {
AutoFetchWorker.prototype.extractFromLocalDoc = function() { var _url = new URL(url, base);
// get the values to be preserved from the documents stylesheets return _url.href;
// and all img, video, audio elements with (data-)?srcset or data-src } catch (e) {
var afw = this; return null;
Promise.resolve().then(function() { }
var msg = { }
type: 'values',
context: { docBaseURI: document.baseURI } function safeResolve(url, resolver) {
}; // Guard against the exception thrown by the URL constructor if the URL or resolver is bad
var media = []; // if resolver is undefined/null then this function passes url through
var i = 0; var resolvedURL = url;
var sheets = document.styleSheets; if (resolver) {
for (; i < sheets.length; ++i) { try {
media = media.concat(afw.extractMediaRulesFromSheet(sheets[i])); var _url = new URL(url, resolver);
return _url.href;
} catch (e) {
resolvedURL = url;
} }
var elems = document.querySelectorAll(afw.elemSelector); }
var srcset = { values: [], presplit: false }; return resolvedURL;
var src = { values: [] }; }
var elem, srcv, mod;
for (i = 0; i < elems.length; ++i) { function maybeFixUpRelSchemelessPrefix(url) {
elem = elems[i]; // attempt to ensure rewritten relative or schemeless URLs become full URLS!
// we want the original src value in order to resolve URLs in the worker when needed // otherwise returns null if this did not happen
srcv = elem.src ? elem.src : null; if (url.indexOf(config.relative) === 0) {
// a from value of 1 indicates images and a 2 indicates audio/video return url.replace(config.relative, config.prefix);
mod = afw.rwMod(elem); }
if (elem.srcset) { if (url.indexOf(config.schemeless) === 0) {
srcset.values.push({ return url.replace(config.schemeless, config.prefix);
srcset: afw.getSrcset(elem), }
mod: mod, return null;
tagSrc: srcv }
});
} function maybeFixUpURL(url, resolveOpts) {
if (elem.dataset.srcset) { // attempt to fix up the url and do our best to ensure we can get dat 200 OK!
srcset.values.push({ if (config.rwRe.test(url)) {
srcset: elem.dataset.srcset, return url;
mod: mod, }
tagSrc: srcv var mod = resolveOpts.mod || 'mp_';
}); // first check for / (relative) or // (schemeless) rewritten urls
} var maybeFixed = maybeFixUpRelSchemelessPrefix(url);
if (elem.dataset.src) { if (maybeFixed != null) {
src.values.push({ src: elem.dataset.src, mod: mod }); return maybeFixed;
} }
if (elem.tagName === 'SOURCE' && srcv) { // resolve URL against tag src
src.values.push({ src: srcv, mod: mod }); if (resolveOpts.tagSrc != null) {
maybeFixed = maybeResolveURL(url, resolveOpts.tagSrc);
if (maybeFixed != null) {
return config.prefix + mod + '/' + maybeFixed;
}
}
// finally last attempt resolve the originating documents base URI
if (resolveOpts.docBaseURI) {
maybeFixed = maybeResolveURL(url, resolveOpts.docBaseURI);
if (maybeFixed != null) {
return config.prefix + mod + '/' + maybeFixed;
}
}
// not much to do now.....
return config.prefixMod + '/' + url;
}
function urlExtractor(match, n1, n2, n3, offset, string) {
// Same function as style_replacer in wombat.rewrite_style, n2 is our URL
queueOrFetch(n2);
return n1 + n2 + n3;
}
function urlExtractorProxyMode(match, n1, n2, n3, offset, string) {
// Same function as style_replacer in wombat.rewrite_style, n2 is our URL
// this.currentResolver is set to the URL which the browser would normally
// resolve relative urls with (URL of the stylesheet) in an exceptionless manner
// (resolvedURL will be undefined if an error occurred)
queueOrFetch(safeResolve(n2, currentResolver));
return n1 + n2 + n3;
}
function handleMedia(mediaRules) {
// this is a broken down rewrite_style
if (mediaRules == null || mediaRules.length === 0) return;
for (var i = 0; i < mediaRules.length; i++) {
mediaRules[i]
.replace(STYLE_REGEX, urlExtractor)
.replace(IMPORT_REGEX, urlExtractor);
}
}
function handleMediaProxyMode(mediaRules) {
// this is a broken down rewrite_style
if (mediaRules == null || mediaRules.length === 0) return;
for (var i = 0; i < mediaRules.length; i++) {
// set currentResolver to the value of this stylesheets URL, done to ensure we do not have to
// create functions on each loop iteration because we potentially create a new `URL` object
// twice per iteration
currentResolver = mediaRules[i].resolve;
mediaRules[i].cssText
.replace(STYLE_REGEX, urlExtractorProxyMode)
.replace(IMPORT_REGEX, urlExtractorProxyMode);
}
}
function handleSrc(srcValues, context) {
var resolveOpts = { docBaseURI: context.docBaseURI };
if (srcValues.value) {
resolveOpts.mod = srcValues.mod;
return queueOrFetch(maybeFixUpURL(srcValues.value.trim(), resolveOpts));
}
var len = srcValues.values.length;
for (var i = 0; i < len; i++) {
var value = srcValues.values[i];
resolveOpts.mod = value.mod;
queueOrFetch(maybeFixUpURL(value.src, resolveOpts));
}
}
function handleSrcProxyMode(srcValues) {
// preservation worker in proxy mode sends us the value of the srcset attribute of an element
// and a URL to correctly resolve relative URLS. Thus we must recreate rewrite_srcset logic here
if (srcValues == null || srcValues.length === 0) return;
var srcVal;
for (var i = 0; i < srcValues.length; i++) {
srcVal = srcValues[i];
queueOrFetch(safeResolve(srcVal.src, srcVal.resolve));
}
}
function extractSrcSetNotPreSplit(ssV, resolveOpts) {
if (!ssV) return;
// was from extract from local doc so we need to duplicate work
var srcsetValues = ssV.split(srcsetSplit);
for (var i = 0; i < srcsetValues.length; i++) {
// grab the URL not width/height key
if (srcsetValues[i]) {
var value = srcsetValues[i].trim().split(' ')[0];
var maybeResolvedURL = maybeFixUpURL(value.trim(), resolveOpts);
queueOrFetch(maybeResolvedURL);
}
}
}
function extractSrcset(srcsets) {
// was rewrite_srcset and only need to q
for (var i = 0; i < srcsets.length; i++) {
// grab the URL not width/height key
var url = srcsets[i].split(' ')[0];
queueOrFetch(url);
}
}
function handleSrcset(srcset, context) {
if (srcset == null) return;
var resolveOpts = {
docBaseURI: context.docBaseURI,
mode: null,
tagSrc: null
};
if (srcset.value) {
// we have a single value, this srcset came from either
// preserveDataSrcset (not presplit) preserveSrcset (presplit)
resolveOpts.mod = srcset.mod;
if (!srcset.presplit) {
// extract URLs from the srcset string
return extractSrcSetNotPreSplit(srcset.value, resolveOpts);
}
// we have an array of srcset URL strings
return extractSrcset(srcset.value);
}
// we have an array of values, these srcsets came from extractFromLocalDoc
var len = srcset.values.length;
for (var i = 0; i < len; i++) {
var ssv = srcset.values[i];
resolveOpts.mod = ssv.mod;
resolveOpts.tagSrc = ssv.tagSrc;
extractSrcSetNotPreSplit(ssv.srcset, resolveOpts);
}
}
function handleSrcsetProxyMode(srcsets) {
// preservation worker in proxy mode sends us the value of the srcset attribute of an element
// and a URL to correctly resolve relative URLS. Thus we must recreate rewrite_srcset logic here
if (srcsets == null) return;
var length = srcsets.length;
var extractedSrcSet, srcsetValue, ssSplit, j;
for (var i = 0; i < length; i++) {
extractedSrcSet = srcsets[i];
ssSplit = extractedSrcSet.srcset.split(srcsetSplit);
for (j = 0; j < ssSplit.length; j++) {
if (ssSplit[j]) {
srcsetValue = ssSplit[j].trim();
if (srcsetValue) {
queueOrFetch(
safeResolve(srcsetValue.split(' ')[0], extractedSrcSet.resolve)
);
}
} }
} }
if (media.length) { }
msg.media = media; }
function autoFetch(data) {
// we got a message and now we autofetch!
// these calls turn into no ops if they have no work
if (data.media) {
if (config.proxyMode) {
handleMediaProxyMode(data.media);
} else {
handleMedia(data.media);
} }
if (srcset.values.length) { }
msg.srcset = srcset;
if (data.src) {
if (config.proxyMode) {
handleSrcProxyMode(data.src);
} else {
handleSrc(data.src, data.context || { docBaseURI: null });
} }
if (src.values.length) { }
msg.src = src;
if (data.srcset) {
if (config.proxyMode) {
handleSrcsetProxyMode(data.srcset);
} else {
handleSrcset(data.srcset, data.context || { docBaseURI: null });
} }
if (msg.media || msg.srcset || msg.src) { }
afw.postMessage(msg); }
}
}); function justFetch(data) {
}; // we got a message containing only urls to be fetched
if (data == null || data.values == null) return;
for (var i = 0; i < data.values.length; ++i) {
queueOrFetch(data.values[i]);
}
}

292
wombat/src/autoFetcher.js Executable file
View File

@ -0,0 +1,292 @@
/* eslint-disable camelcase */
import { autobind } from './wombatUtils';
/**
* Create a new instance of AutoFetcher
* @param {Wombat} wombat
* @param {{isTop: boolean, workerURL: string}} config
*/
export default function AutoFetcher(wombat, config) {
if (!(this instanceof AutoFetcher)) {
return new AutoFetcher(wombat, config);
}
// specifically target the elements we desire
this.elemSelector =
'img[srcset], img[data-srcset], img[data-src], video[srcset], video[data-srcset], video[data-src], audio[srcset], audio[data-srcset], audio[data-src], ' +
'picture > source[srcset], picture > source[data-srcset], picture > source[data-src], ' +
'video > source[srcset], video > source[data-srcset], video > source[data-src], ' +
'audio > source[srcset], audio > source[data-srcset], audio > source[data-src]';
/** @type {Wombat} */
this.wombat = wombat;
/** @type {Window} */
this.$wbwindow = wombat.$wbwindow;
/** @type {?Worker|Object} */
this.worker = null;
autobind(this);
this._initWorker(config);
}
/**
* Initializes the backing worker IFF the execution context we are in is
* the replay tops otherwise creates a dummy worker that simply bounces the
* message that would have been sent to the backing worker to replay top.
*
* If creation of the worker fails, likely due to the execution context we
* are currently in having an null origin, we fallback to dummy worker creation.
* @param {{isTop: boolean, workerURL: string}} config
* @private
*/
AutoFetcher.prototype._initWorker = function(config) {
var wombat = this.wombat;
if (config.isTop) {
// we are top and can will own this worker
// setup URL for the kewl case
// Normal replay and preservation mode pworker setup, its all one origin so YAY!
try {
this.worker = new Worker(config.workerURL, {
type: 'classic',
credentials: 'include'
});
} catch (e) {
// it is likely we are in some kind of horrid iframe setup
// and the execution context we are currently in has a null origin
console.error('Failed to create auto fetch worker\n', e);
}
return;
}
// add only the portions of the worker interface we use since we are not top
// and if in proxy mode start check polling
this.worker = {
postMessage: function(msg) {
if (!msg.wb_type) {
msg = { wb_type: 'aaworker', msg: msg };
}
wombat.$wbwindow.__WB_replay_top.__orig_postMessage(msg, '*');
},
terminate: function() {}
};
};
/**
* Extracts the media rules from the supplied CSSStyleSheet object if any
* are present and returns an array of the media cssText
* @param {CSSStyleSheet} sheet
* @return {Array<string>}
*/
AutoFetcher.prototype.extractMediaRulesFromSheet = function(sheet) {
var rules;
var media = [];
try {
rules = sheet.cssRules || sheet.rules;
} catch (e) {
return media;
}
// loop through each rule of the stylesheet
for (var i = 0; i < rules.length; ++i) {
var rule = rules[i];
if (rule.type === CSSRule.MEDIA_RULE) {
// we are a media rule so get its text
media.push(rule.cssText);
}
}
return media;
};
/**
* Extracts the media rules from the supplied CSSStyleSheet object if any
* are present after a tick of the event loop sending the results of the
* extraction to the backing worker
* @param {CSSStyleSheet|StyleSheet} sheet
*/
AutoFetcher.prototype.deferredSheetExtraction = function(sheet) {
var afw = this;
// defer things until next time the Promise.resolve Qs are cleared
Promise.resolve().then(function() {
// loop through each rule of the stylesheet
var media = afw.extractMediaRulesFromSheet(sheet);
if (media.length > 0) {
// we have some media rules to preserve
afw.preserveMedia(media);
}
});
};
/**
* Terminates the backing worker. This is a no op when we are not
* operating in the execution context of replay top
*/
AutoFetcher.prototype.terminate = function() {
// terminate the worker, a no op when not replay top
this.worker.terminate();
};
/**
* Sends the supplied array of URLs to the backing worker
* @param {Array<string>} urls
*/
AutoFetcher.prototype.justFetch = function(urls) {
this.worker.postMessage({ type: 'fetch-all', values: urls });
};
/**
* Sends a message to backing worker. If deferred is true
* the message is sent after one tick of the event loop
* @param {Object} msg
* @param {boolean} [deferred]
*/
AutoFetcher.prototype.postMessage = function(msg, deferred) {
if (deferred) {
var afWorker = this;
Promise.resolve().then(function() {
afWorker.worker.postMessage(msg);
});
return;
}
this.worker.postMessage(msg);
};
/**
* Sends the supplied srcset value to the backing worker for preservation
* @param {string|Array<string>} srcset
* @param {string} [mod]
*/
AutoFetcher.prototype.preserveSrcset = function(srcset, mod) {
// send values from rewriteSrcset to the worker
this.postMessage(
{
type: 'values',
srcset: { value: srcset, mod: mod, presplit: true }
},
true
);
};
/**
* Send the value of the supplied elements data-srcset attribute to the
* backing worker for preservation
* @param {Node} elem
*/
AutoFetcher.prototype.preserveDataSrcset = function(elem) {
// send values from rewriteAttr srcset to the worker deferred
// to ensure the page viewer sees the images first
this.postMessage(
{
type: 'values',
srcset: {
value: elem.dataset.srcset,
mod: this.rwMod(elem),
presplit: false
}
},
true
);
};
/**
* Sends the supplied array of cssText from media rules to the backing worker
* @param {Array<string>} media
*/
AutoFetcher.prototype.preserveMedia = function(media) {
// send CSSMediaRule values to the worker
this.postMessage({ type: 'values', media: media }, true);
};
/**
* Extracts the value of the srcset property if it exists from the supplied
* element
* @param {Element} elem
* @return {?string}
*/
AutoFetcher.prototype.getSrcset = function(elem) {
if (this.wombat.wb_getAttribute) {
return this.wombat.wb_getAttribute.call(elem, 'srcset');
}
return elem.getAttribute('srcset');
};
/**
* Returns the correct rewrite modifier for the supplied element
* @param {Element} elem
* @return {string}
*/
AutoFetcher.prototype.rwMod = function(elem) {
switch (elem.tagName) {
case 'SOURCE':
if (elem.parentElement && elem.parentElement.tagName === 'PICTURE') {
return 'im_';
}
return 'oe_';
case 'IMG':
return 'im_';
}
return 'oe_';
};
/**
* Extracts the media rules from stylesheets and the (data-)srcset URLs from
* image elements the current context's document contains
*/
AutoFetcher.prototype.extractFromLocalDoc = function() {
// get the values to be preserved from the documents stylesheets
// and all img, video, audio elements with (data-)?srcset or data-src
var afw = this;
Promise.resolve().then(function() {
var msg = {
type: 'values',
context: { docBaseURI: document.baseURI }
};
var media = [];
var i = 0;
var sheets = document.styleSheets;
for (; i < sheets.length; ++i) {
media = media.concat(afw.extractMediaRulesFromSheet(sheets[i]));
}
var elems = document.querySelectorAll(afw.elemSelector);
var srcset = { values: [], presplit: false };
var src = { values: [] };
var elem, srcv, mod;
for (i = 0; i < elems.length; ++i) {
elem = elems[i];
// we want the original src value in order to resolve URLs in the worker when needed
srcv = elem.src ? elem.src : null;
// a from value of 1 indicates images and a 2 indicates audio/video
mod = afw.rwMod(elem);
if (elem.srcset) {
srcset.values.push({
srcset: afw.getSrcset(elem),
mod: mod,
tagSrc: srcv
});
}
if (elem.dataset.srcset) {
srcset.values.push({
srcset: elem.dataset.srcset,
mod: mod,
tagSrc: srcv
});
}
if (elem.dataset.src) {
src.values.push({ src: elem.dataset.src, mod: mod });
}
if (elem.tagName === 'SOURCE' && srcv) {
src.values.push({ src: srcv, mod: mod });
}
}
if (media.length) {
msg.media = media;
}
if (srcset.values.length) {
msg.srcset = srcset;
}
if (src.values.length) {
msg.src = src;
}
if (msg.media || msg.srcset || msg.src) {
afw.postMessage(msg);
}
});
};

View File

@ -3,65 +3,54 @@ import { autobind } from './wombatUtils';
/** /**
* Create a new instance of AutoFetchWorkerProxyMode * Create a new instance of AutoFetchWorkerProxyMode
* @param {Wombat} wombat * @param {Wombat} wombat
* @param {boolean} isTop * @param {{isTop: boolean, workerURL: string}} config
*/ */
export default function AutoFetchWorkerProxyMode(wombat, isTop) { export default function AutoFetcherProxyMode(wombat, config) {
if (!(this instanceof AutoFetchWorkerProxyMode)) { if (!(this instanceof AutoFetcherProxyMode)) {
return new AutoFetchWorkerProxyMode(wombat, isTop); return new AutoFetcherProxyMode(wombat, config);
} }
/** /** @type {Wombat} */
* @type {Wombat}
*/
this.wombat = wombat; this.wombat = wombat;
/** /** @type {?MutationObserver} */
* @type {boolean}
*/
this.isTop = isTop;
/**
* @type {?MutationObserver}
*/
this.mutationObz = null; this.mutationObz = null;
/** @type {?HTMLStyleElement} */
this.styleTag = null;
// specifically target the elements we desire // specifically target the elements we desire
/** @type {string} */
this.elemSelector = this.elemSelector =
'img[srcset], img[data-srcset], img[data-src], video[srcset], video[data-srcset], video[data-src], audio[srcset], audio[data-srcset], audio[data-src], ' + 'img[srcset], img[data-srcset], img[data-src], video[srcset], video[data-srcset], video[data-src], audio[srcset], audio[data-srcset], audio[data-src], ' +
'picture > source[srcset], picture > source[data-srcset], picture > source[data-src], ' + 'picture > source[srcset], picture > source[data-srcset], picture > source[data-src], ' +
'video > source[srcset], video > source[data-srcset], video > source[data-src], ' + 'video > source[srcset], video > source[data-srcset], video > source[data-src], ' +
'audio > source[srcset], audio > source[data-srcset], audio > source[data-src]'; 'audio > source[srcset], audio > source[data-srcset], audio > source[data-src]';
this.mutationOpts = {
characterData: false,
characterDataOldValue: false,
attributes: true,
attributeOldValue: true,
subtree: true,
childList: true,
attributeFilter: ['src', 'srcset']
};
autobind(this); autobind(this);
this._init(true); this._init(config, true);
} }
/** /**
* Initialize the auto fetch worker * Initialize the auto fetch worker
* @param {{isTop: boolean, workerURL: string}} config
* @param {boolean} [first]
* @private * @private
*/ */
AutoFetchWorkerProxyMode.prototype._init = function(first) { AutoFetcherProxyMode.prototype._init = function(config, first) {
var afwpm = this; var afwpm = this;
var wombat = this.wombat;
if (document.readyState === 'complete') { if (document.readyState === 'complete') {
this.styleTag = document.createElement('style'); this.styleTag = document.createElement('style');
this.styleTag.id = '$wrStyleParser$'; this.styleTag.id = '$wrStyleParser$';
document.head.appendChild(this.styleTag); document.head.appendChild(this.styleTag);
if (this.isTop) { if (config.isTop) {
// Cannot directly load our worker from the proxy origin into the current origin // Cannot directly load our worker from the proxy origin into the current origin
// however we fetch it from proxy origin and can blob it into the current origin :) // however we fetch it from proxy origin and can blob it into the current origin :)
fetch(this.wombat.wbAutoFetchWorkerPrefix).then(function(res) { fetch(config.workerURL).then(function(res) {
res res
.text() .text()
.then(function(text) { .then(function(text) {
var blob = new Blob([text], { type: 'text/javascript' }); var blob = new Blob([text], { type: 'text/javascript' });
afwpm.worker = new afwpm.wombat.$wbwindow.Worker( afwpm.worker = new wombat.$wbwindow.Worker(
URL.createObjectURL(blob) URL.createObjectURL(blob),
{ type: 'classic', credentials: 'include' }
); );
afwpm.startChecking(); afwpm.startChecking();
}) })
@ -79,7 +68,7 @@ AutoFetchWorkerProxyMode.prototype._init = function(first) {
if (!msg.wb_type) { if (!msg.wb_type) {
msg = { wb_type: 'aaworker', msg: msg }; msg = { wb_type: 'aaworker', msg: msg };
} }
afwpm.wombat.$wbwindow.top.postMessage(msg, '*'); wombat.$wbwindow.top.postMessage(msg, '*');
}, },
terminate: function() {} terminate: function() {}
}; };
@ -90,7 +79,7 @@ AutoFetchWorkerProxyMode.prototype._init = function(first) {
if (!first) return; if (!first) return;
var i = setInterval(function() { var i = setInterval(function() {
if (document.readyState === 'complete') { if (document.readyState === 'complete') {
afwpm._init(); afwpm._init(config);
clearInterval(i); clearInterval(i);
} }
}, 1000); }, 1000);
@ -99,16 +88,24 @@ AutoFetchWorkerProxyMode.prototype._init = function(first) {
/** /**
* Initializes the mutation observer * Initializes the mutation observer
*/ */
AutoFetchWorkerProxyMode.prototype.startChecking = function() { AutoFetcherProxyMode.prototype.startChecking = function() {
this.extractFromLocalDoc(); this.extractFromLocalDoc();
this.mutationObz = new MutationObserver(this.mutationCB); this.mutationObz = new MutationObserver(this.mutationCB);
this.mutationObz.observe(document, this.mutationOpts); this.mutationObz.observe(document.documentElement, {
characterData: false,
characterDataOldValue: false,
attributes: true,
attributeOldValue: true,
subtree: true,
childList: true,
attributeFilter: ['src', 'srcset']
});
}; };
/** /**
* Terminate the worker, a no op when not replay top * Terminate the worker, a no op when not replay top
*/ */
AutoFetchWorkerProxyMode.prototype.terminate = function() { AutoFetcherProxyMode.prototype.terminate = function() {
this.worker.terminate(); this.worker.terminate();
}; };
@ -116,7 +113,7 @@ AutoFetchWorkerProxyMode.prototype.terminate = function() {
* Sends the supplied array of URLs to the backing worker * Sends the supplied array of URLs to the backing worker
* @param {Array<string>} urls * @param {Array<string>} urls
*/ */
AutoFetchWorkerProxyMode.prototype.justFetch = function(urls) { AutoFetcherProxyMode.prototype.justFetch = function(urls) {
this.worker.postMessage({ type: 'fetch-all', values: urls }); this.worker.postMessage({ type: 'fetch-all', values: urls });
}; };
@ -124,7 +121,7 @@ AutoFetchWorkerProxyMode.prototype.justFetch = function(urls) {
* Sends the supplied msg to the backing worker * Sends the supplied msg to the backing worker
* @param {Object} msg * @param {Object} msg
*/ */
AutoFetchWorkerProxyMode.prototype.postMessage = function(msg) { AutoFetcherProxyMode.prototype.postMessage = function(msg) {
this.worker.postMessage(msg); this.worker.postMessage(msg);
}; };
@ -136,7 +133,7 @@ AutoFetchWorkerProxyMode.prototype.postMessage = function(msg) {
* @param {boolean} [text] * @param {boolean} [text]
* @return {void} * @return {void}
*/ */
AutoFetchWorkerProxyMode.prototype.handleMutatedStyleElem = function( AutoFetcherProxyMode.prototype.handleMutatedStyleElem = function(
elem, elem,
accum, accum,
text text
@ -166,7 +163,7 @@ AutoFetchWorkerProxyMode.prototype.handleMutatedStyleElem = function(
* @param {*} elem * @param {*} elem
* @param {Object} accum * @param {Object} accum
*/ */
AutoFetchWorkerProxyMode.prototype.handleMutatedElem = function(elem, accum) { AutoFetcherProxyMode.prototype.handleMutatedElem = function(elem, accum) {
var baseURI = document.baseURI; var baseURI = document.baseURI;
if (elem.nodeType === Node.TEXT_NODE) { if (elem.nodeType === Node.TEXT_NODE) {
return this.handleMutatedStyleElem(elem, accum, true); return this.handleMutatedStyleElem(elem, accum, true);
@ -189,10 +186,7 @@ AutoFetchWorkerProxyMode.prototype.handleMutatedElem = function(elem, accum) {
* @param {Array<MutationRecord>} mutationList * @param {Array<MutationRecord>} mutationList
* @param {MutationObserver} observer * @param {MutationObserver} observer
*/ */
AutoFetchWorkerProxyMode.prototype.mutationCB = function( AutoFetcherProxyMode.prototype.mutationCB = function(mutationList, observer) {
mutationList,
observer
) {
var accum = { type: 'values', srcset: [], src: [], media: [], deferred: [] }; var accum = { type: 'values', srcset: [], src: [], media: [], deferred: [] };
for (var i = 0; i < mutationList.length; i++) { for (var i = 0; i < mutationList.length; i++) {
var mutation = mutationList[i]; var mutation = mutationList[i];
@ -212,7 +206,6 @@ AutoFetchWorkerProxyMode.prototype.mutationCB = function(
Promise.all(deferred).then(this.handleDeferredSheetResults); Promise.all(deferred).then(this.handleDeferredSheetResults);
} }
if (accum.srcset.length || accum.src.length || accum.media.length) { if (accum.srcset.length || accum.src.length || accum.media.length) {
console.log('msg', Date.now(), accum);
this.postMessage(accum); this.postMessage(accum);
} }
}; };
@ -222,7 +215,7 @@ AutoFetchWorkerProxyMode.prototype.mutationCB = function(
* @param {StyleSheet} sheet * @param {StyleSheet} sheet
* @return {boolean} * @return {boolean}
*/ */
AutoFetchWorkerProxyMode.prototype.shouldSkipSheet = function(sheet) { AutoFetcherProxyMode.prototype.shouldSkipSheet = function(sheet) {
// we skip extracting rules from sheets if they are from our parsing style or come from pywb // we skip extracting rules from sheets if they are from our parsing style or come from pywb
if (sheet.id === '$wrStyleParser$') return true; if (sheet.id === '$wrStyleParser$') return true;
return !!( return !!(
@ -236,7 +229,7 @@ AutoFetchWorkerProxyMode.prototype.shouldSkipSheet = function(sheet) {
* @param {?string} srcV * @param {?string} srcV
* @return {null|string} * @return {null|string}
*/ */
AutoFetchWorkerProxyMode.prototype.validateSrcV = function(srcV) { AutoFetcherProxyMode.prototype.validateSrcV = function(srcV) {
if (!srcV || srcV.indexOf('data:') === 0 || srcV.indexOf('blob:') === 0) { if (!srcV || srcV.indexOf('data:') === 0 || srcV.indexOf('blob:') === 0) {
return null; return null;
} }
@ -250,7 +243,7 @@ AutoFetchWorkerProxyMode.prototype.validateSrcV = function(srcV) {
* @param {string} cssURL * @param {string} cssURL
* @return {Promise<Array>} * @return {Promise<Array>}
*/ */
AutoFetchWorkerProxyMode.prototype.fetchCSSAndExtract = function(cssURL) { AutoFetcherProxyMode.prototype.fetchCSSAndExtract = function(cssURL) {
var url = var url =
location.protocol + location.protocol +
'//' + '//' +
@ -276,10 +269,7 @@ AutoFetchWorkerProxyMode.prototype.fetchCSSAndExtract = function(cssURL) {
* @param {string} baseURI * @param {string} baseURI
* @return {Array<Object>} * @return {Array<Object>}
*/ */
AutoFetchWorkerProxyMode.prototype.extractMediaRules = function( AutoFetcherProxyMode.prototype.extractMediaRules = function(sheet, baseURI) {
sheet,
baseURI
) {
// We are in proxy mode and must include a URL to resolve relative URLs in media rules // We are in proxy mode and must include a URL to resolve relative URLs in media rules
var results = []; var results = [];
if (!sheet) return results; if (!sheet) return results;
@ -306,7 +296,7 @@ AutoFetchWorkerProxyMode.prototype.extractMediaRules = function(
* @param {Element} elem * @param {Element} elem
* @return {string} * @return {string}
*/ */
AutoFetchWorkerProxyMode.prototype.rwMod = function(elem) { AutoFetcherProxyMode.prototype.rwMod = function(elem) {
switch (elem.tagName) { switch (elem.tagName) {
case 'SOURCE': case 'SOURCE':
if (elem.parentElement && elem.parentElement.tagName === 'PICTURE') { if (elem.parentElement && elem.parentElement.tagName === 'PICTURE') {
@ -326,7 +316,7 @@ AutoFetchWorkerProxyMode.prototype.rwMod = function(elem) {
* @param {string} baseURI * @param {string} baseURI
* @param {?Object} acum * @param {?Object} acum
*/ */
AutoFetchWorkerProxyMode.prototype.handleDomElement = function( AutoFetcherProxyMode.prototype.handleDomElement = function(
elem, elem,
baseURI, baseURI,
acum acum
@ -368,7 +358,7 @@ AutoFetchWorkerProxyMode.prototype.handleDomElement = function(
* @param {string} baseURI * @param {string} baseURI
* @param {Object} [acum] * @param {Object} [acum]
*/ */
AutoFetchWorkerProxyMode.prototype.extractSrcSrcsetFrom = function( AutoFetcherProxyMode.prototype.extractSrcSrcsetFrom = function(
fromElem, fromElem,
baseURI, baseURI,
acum acum
@ -391,9 +381,7 @@ AutoFetchWorkerProxyMode.prototype.extractSrcSrcsetFrom = function(
* Sends the extracted media values to the backing worker * Sends the extracted media values to the backing worker
* @param {Array<Array<string>>} results * @param {Array<Array<string>>} results
*/ */
AutoFetchWorkerProxyMode.prototype.handleDeferredSheetResults = function( AutoFetcherProxyMode.prototype.handleDeferredSheetResults = function(results) {
results
) {
if (results.length === 0) return; if (results.length === 0) return;
var len = results.length; var len = results.length;
var media = []; var media = [];
@ -411,7 +399,7 @@ AutoFetchWorkerProxyMode.prototype.handleDeferredSheetResults = function(
* contexts document object * contexts document object
* @param {?Document} [doc] * @param {?Document} [doc]
*/ */
AutoFetchWorkerProxyMode.prototype.checkStyleSheets = function(doc) { AutoFetcherProxyMode.prototype.checkStyleSheets = function(doc) {
var media = []; var media = [];
var deferredMediaExtraction = []; var deferredMediaExtraction = [];
var styleSheets = (doc || document).styleSheets; var styleSheets = (doc || document).styleSheets;
@ -457,7 +445,7 @@ AutoFetchWorkerProxyMode.prototype.checkStyleSheets = function(doc) {
/** /**
* Performs extraction from the current contexts document * Performs extraction from the current contexts document
*/ */
AutoFetchWorkerProxyMode.prototype.extractFromLocalDoc = function() { AutoFetcherProxyMode.prototype.extractFromLocalDoc = function() {
// check for data-[src,srcset] and auto-fetched elems with srcset first // check for data-[src,srcset] and auto-fetched elems with srcset first
this.extractSrcSrcsetFrom( this.extractSrcSrcsetFrom(
this.wombat.$wbwindow.document, this.wombat.$wbwindow.document,

View File

@ -1,8 +1,4 @@
import { import { addToStringTagToClass, ensureNumber, ThrowExceptions } from './wombatUtils';
addToStringTagToClass,
ensureNumber,
ThrowExceptions
} from './wombatUtils';
/** /**
* A re-implementation of the Storage interface. * A re-implementation of the Storage interface.

View File

@ -2,7 +2,7 @@
import FuncMap from './funcMap'; import FuncMap from './funcMap';
import Storage from './customStorage'; import Storage from './customStorage';
import WombatLocation from './wombatLocation'; import WombatLocation from './wombatLocation';
import AutoFetchWorker from './autoFetchWorker'; import AutoFetcher from './autoFetcher';
import { wrapEventListener, wrapSameOriginEventListener } from './listeners'; import { wrapEventListener, wrapSameOriginEventListener } from './listeners';
import { import {
addToStringTagToClass, addToStringTagToClass,
@ -58,7 +58,7 @@ function Wombat($wbwindow, wbinfo) {
/** @type {function(): string} */ /** @type {function(): string} */
this.wb_funToString = Function.prototype.toString; this.wb_funToString = Function.prototype.toString;
/** @type {AutoFetchWorker} */ /** @type {AutoFetcher} */
this.WBAutoFetchWorker = null; this.WBAutoFetchWorker = null;
/** @type {boolean} */ /** @type {boolean} */
@ -1983,12 +1983,12 @@ Wombat.prototype.rewriteSrcset = function(value, elem) {
var split = value.split(this.srcsetRe); var split = value.split(this.srcsetRe);
var values = []; var values = [];
var mod = this.rwModForElement(elem, 'srcset');
for (var i = 0; i < split.length; i++) { for (var i = 0; i < split.length; i++) {
// Filter removes non-truthy values like null, undefined, and "" // Filter removes non-truthy values like null, undefined, and ""
if (split[i]) { if (split[i]) {
var trimmed = split[i].trim(); var trimmed = split[i].trim();
if (trimmed) values.push(this.rewriteUrl(trimmed)); if (trimmed) values.push(this.rewriteUrl(trimmed, true, mod));
} }
} }
@ -3141,6 +3141,7 @@ Wombat.prototype.overrideFuncArgProxyToObj = function(
if (!cls || !cls.prototype) return; if (!cls || !cls.prototype) return;
var argIndex = argumentIdx || 0; var argIndex = argumentIdx || 0;
var orig = cls.prototype[method]; var orig = cls.prototype[method];
if (!orig) return;
var wombat = this; var wombat = this;
cls.prototype[method] = function deproxyFnArg() { cls.prototype[method] = function deproxyFnArg() {
var args = new Array(arguments.length); var args = new Array(arguments.length);
@ -4782,7 +4783,6 @@ Wombat.prototype.initHashChange = function() {
var wombat = this; var wombat = this;
var receive_hash_change = function receive_hash_change(event) { var receive_hash_change = function receive_hash_change(event) {
if (!event.data || !event.data.from_top) { if (!event.data || !event.data.from_top) {
return; return;
} }
@ -5219,7 +5219,6 @@ Wombat.prototype.initDisableNotificationsGeoLocation = function() {
callback callback
) { ) {
if (callback) { if (callback) {
// eslint-disable-next-line standard/no-callback-literal
callback('denied'); callback('denied');
} }
@ -5448,7 +5447,21 @@ Wombat.prototype.initDocumentObjProxy = function($document) {
*/ */
Wombat.prototype.initAutoFetchWorker = function() { Wombat.prototype.initAutoFetchWorker = function() {
if (!this.wbUseAFWorker) return; if (!this.wbUseAFWorker) return;
this.WBAutoFetchWorker = new AutoFetchWorker(this); var af = new AutoFetcher(this, {
isTop: this.$wbwindow === this.$wbwindow.__WB_replay_top,
workerURL:
(this.wb_info.auto_fetch_worker_prefix || this.wb_info.static_prefix) +
'autoFetchWorker.js?init=' +
encodeURIComponent(
JSON.stringify({
mod: this.wb_info.mod,
prefix: this.wb_abs_prefix,
rwRe: this.wb_unrewrite_rx.source
})
)
});
this.WBAutoFetchWorker = af;
this.$wbwindow.$WBAutoFetchWorker$ = af;
var wombat = this; var wombat = this;
this.utilFns.wbSheetMediaQChecker = function checkStyle() { this.utilFns.wbSheetMediaQChecker = function checkStyle() {
// used only for link[rel='stylesheet'] so we remove our listener // used only for link[rel='stylesheet'] so we remove our listener

View File

@ -1,5 +1,5 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import AutoFetchWorkerProxyMode from './autoFetchWorkerProxyMode'; import AutoFetcherProxyMode from './autoFetcherProxyMode';
/** /**
* Wombat lite for proxy-mode * Wombat lite for proxy-mode
@ -12,9 +12,6 @@ export default function WombatLite($wbwindow, wbinfo) {
this.$wbwindow = $wbwindow; this.$wbwindow = $wbwindow;
this.wb_info.top_host = this.wb_info.top_host || '*'; this.wb_info.top_host = this.wb_info.top_host || '*';
this.wb_info.wombat_opts = this.wb_info.wombat_opts || {}; this.wb_info.wombat_opts = this.wb_info.wombat_opts || {};
this.wbAutoFetchWorkerPrefix =
(this.wb_info.auto_fetch_worker_prefix || this.wb_info.static_prefix) +
'autoFetchWorkerProxyMode.js';
this.WBAutoFetchWorker = null; this.WBAutoFetchWorker = null;
} }
@ -157,7 +154,6 @@ WombatLite.prototype.initDisableNotifications = function() {
callback callback
) { ) {
if (callback) { if (callback) {
// eslint-disable-next-line standard/no-callback-literal
callback('denied'); callback('denied');
} }
@ -201,9 +197,14 @@ WombatLite.prototype.initAutoFetchWorker = function() {
if (!this.$wbwindow.Worker) { if (!this.$wbwindow.Worker) {
return; return;
} }
var isTop = this.$wbwindow.self === this.$wbwindow.top; var config = {
isTop: this.$wbwindow.self === this.$wbwindow.top,
workerURL:
(this.wb_info.auto_fetch_worker_prefix || this.wb_info.static_prefix) +
'autoFetchWorker.js'
};
if (this.$wbwindow.$WBAutoFetchWorker$ == null) { if (this.$wbwindow.$WBAutoFetchWorker$ == null) {
this.WBAutoFetchWorker = new AutoFetchWorkerProxyMode(this, isTop); this.WBAutoFetchWorker = new AutoFetcherProxyMode(this, config);
// expose the WBAutoFetchWorker // expose the WBAutoFetchWorker
Object.defineProperty(this.$wbwindow, '$WBAutoFetchWorker$', { Object.defineProperty(this.$wbwindow, '$WBAutoFetchWorker$', {
enumerable: false, enumerable: false,
@ -212,7 +213,7 @@ WombatLite.prototype.initAutoFetchWorker = function() {
} else { } else {
this.WBAutoFetchWorker = this.$wbwindow.$WBAutoFetchWorker$; this.WBAutoFetchWorker = this.$wbwindow.$WBAutoFetchWorker$;
} }
if (isTop) { if (config.isTop) {
var wombatLite = this; var wombatLite = this;
this.$wbwindow.addEventListener( this.$wbwindow.addEventListener(
'message', 'message',

View File

@ -404,10 +404,10 @@
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
"@types/fs-extra@^7.0.0": "@types/fs-extra@^8.0.0":
version "7.0.0" version "8.0.0"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-7.0.0.tgz#9c4ad9e1339e7448a76698829def1f159c1b636c" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.0.0.tgz#d3e2c313ca29f95059f198dd60d1f774642d4b25"
integrity sha512-ndoMMbGyuToTy4qB6Lex/inR98nPiNHacsgMPvy+zqMLgSxbt8VtWpDArpGp69h1fEDQHn1KB+9DWD++wgbwYA== integrity sha512-bCtL5v9zdbQW86yexOlXWTEGvLNqWxMFyi7gQA7Gcthbezr2cPSOb8SkESVKA937QD5cIwOFLDFt0MQoXOEr9Q==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
@ -457,7 +457,7 @@ acorn@^6.0.7, acorn@^6.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==
ajv@^6.8.1, ajv@^6.9.1, ajv@^6.9.2: ajv@^6.10.0, ajv@^6.8.1, ajv@^6.9.1, ajv@^6.9.2:
version "6.10.0" version "6.10.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==
@ -1150,17 +1150,6 @@ chokidar@^3.0.1:
optionalDependencies: optionalDependencies:
fsevents "^2.0.6" fsevents "^2.0.6"
chrome-launcher@^0.10.7:
version "0.10.7"
resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.10.7.tgz#5e2a9e99f212e0501d9c7024802acd01a812f5d5"
integrity sha512-IoQLp64s2n8OQuvKZwt77CscVj3UlV2Dj7yZtd1EBMld9mSdGcsGy9fN5hd/r4vJuWZR09it78n1+A17gB+AIQ==
dependencies:
"@types/node" "*"
is-wsl "^1.1.0"
lighthouse-logger "^1.0.0"
mkdirp "0.5.1"
rimraf "^2.6.1"
chrome-remote-interface-extra@^1.1.1: chrome-remote-interface-extra@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/chrome-remote-interface-extra/-/chrome-remote-interface-extra-1.1.1.tgz#f82a5c9ee731e9301dd922360d140840f806b64a" resolved "https://registry.yarnpkg.com/chrome-remote-interface-extra/-/chrome-remote-interface-extra-1.1.1.tgz#f82a5c9ee731e9301dd922360d140840f806b64a"
@ -1460,7 +1449,7 @@ date-time@^2.1.0:
dependencies: dependencies:
time-zone "^1.0.0" time-zone "^1.0.0"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@ -1689,10 +1678,10 @@ escape-string-regexp@^2.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
eslint-config-prettier@^5.0.0: eslint-config-prettier@^6.0.0:
version "5.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-5.0.0.tgz#f7a94b2b8ae7cbf25842c36fa96c6d32cd0a697c" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.0.0.tgz#f429a53bde9fc7660e6353910fd996d6284d3c25"
integrity sha512-c17Aqiz5e8LEqoc/QPmYnaxQFAHTx2KlCZBPxXXjEMmNchOLnV/7j0HoPZuC+rL/tDC9bazUYOKJW9bOhftI/w== integrity sha512-vDrcCFE3+2ixNT5H83g28bO/uYAwibJxerXPj+E7op4qzBCsAV36QfvdAyVOoNxKAH2Os/e01T/2x++V0LPukA==
dependencies: dependencies:
get-stdin "^6.0.0" get-stdin "^6.0.0"
@ -1721,13 +1710,13 @@ eslint-visitor-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
eslint@^5.16.0: eslint@^6.0.1:
version "5.16.0" version "6.0.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.0.1.tgz#4a32181d72cb999d6f54151df7d337131f81cda7"
integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== integrity sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "^7.0.0"
ajv "^6.9.1" ajv "^6.10.0"
chalk "^2.1.0" chalk "^2.1.0"
cross-spawn "^6.0.5" cross-spawn "^6.0.5"
debug "^4.0.1" debug "^4.0.1"
@ -1735,18 +1724,19 @@ eslint@^5.16.0:
eslint-scope "^4.0.3" eslint-scope "^4.0.3"
eslint-utils "^1.3.1" eslint-utils "^1.3.1"
eslint-visitor-keys "^1.0.0" eslint-visitor-keys "^1.0.0"
espree "^5.0.1" espree "^6.0.0"
esquery "^1.0.1" esquery "^1.0.1"
esutils "^2.0.2" esutils "^2.0.2"
file-entry-cache "^5.0.1" file-entry-cache "^5.0.1"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
glob "^7.1.2" glob-parent "^3.1.0"
globals "^11.7.0" globals "^11.7.0"
ignore "^4.0.6" ignore "^4.0.6"
import-fresh "^3.0.0" import-fresh "^3.0.0"
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
inquirer "^6.2.2" inquirer "^6.2.2"
js-yaml "^3.13.0" is-glob "^4.0.0"
js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1" json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.3.0" levn "^0.3.0"
lodash "^4.17.11" lodash "^4.17.11"
@ -1754,7 +1744,6 @@ eslint@^5.16.0:
mkdirp "^0.5.1" mkdirp "^0.5.1"
natural-compare "^1.4.0" natural-compare "^1.4.0"
optionator "^0.8.2" optionator "^0.8.2"
path-is-inside "^1.0.2"
progress "^2.0.0" progress "^2.0.0"
regexpp "^2.0.1" regexpp "^2.0.1"
semver "^5.5.1" semver "^5.5.1"
@ -1778,10 +1767,10 @@ espower-location-detector@^1.0.0:
source-map "^0.5.0" source-map "^0.5.0"
xtend "^4.0.0" xtend "^4.0.0"
espree@^5.0.1: espree@^6.0.0:
version "5.0.1" version "6.0.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" resolved "https://registry.yarnpkg.com/espree/-/espree-6.0.0.tgz#716fc1f5a245ef5b9a7fdb1d7b0d3f02322e75f6"
integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== integrity sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==
dependencies: dependencies:
acorn "^6.0.7" acorn "^6.0.7"
acorn-jsx "^5.0.0" acorn-jsx "^5.0.0"
@ -2001,10 +1990,10 @@ fastify-static@^2.5.0:
readable-stream "^3.4.0" readable-stream "^3.4.0"
send "^0.16.0" send "^0.16.0"
fastify@^2.5.0: fastify@^2.6.0:
version "2.5.0" version "2.6.0"
resolved "https://registry.yarnpkg.com/fastify/-/fastify-2.5.0.tgz#24de69394231fc1b2eb82a9d932e909aa89ba56f" resolved "https://registry.yarnpkg.com/fastify/-/fastify-2.6.0.tgz#45397b760f75278633fe0dcb30fcb4a26d1f66f6"
integrity sha512-51z51VlbGw+ZJp8MJeZVqLtwAMMUiobo/YcAgyA7guRzflDa5tyw7yhZUDLfOng2YQIrVWZzWX7jPPvbQPQBxg== integrity sha512-3GxGV2P8731o2S5T6ng5NMJ9S7vFpZA4mk2mJEbMbhQ5aj1HhNGBOe39TYa2gWRrJVJuXxYYYIlY/5cFhiHpNg==
dependencies: dependencies:
abstract-logging "^1.0.0" abstract-logging "^1.0.0"
ajv "^6.9.2" ajv "^6.9.2"
@ -2173,16 +2162,7 @@ fresh@0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
fs-extra@^7.0.0: fs-extra@^7.0.0, fs-extra@^8.0.1, fs-extra@~8.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^8.0.1:
version "8.0.1" version "8.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.0.1.tgz#90294081f978b1f182f347a440a209154344285b" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.0.1.tgz#90294081f978b1f182f347a440a209154344285b"
integrity sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A== integrity sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==
@ -2283,7 +2263,7 @@ glob-to-regexp@^0.3.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
glob@^7.0.3, glob@^7.1.2, glob@^7.1.3: glob@^7.0.3, glob@^7.1.3:
version "7.1.3" version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
@ -2791,11 +2771,6 @@ is-windows@^1.0.2:
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
is-wsl@^2.0.0: is-wsl@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.0.0.tgz#32849d5bf66413883ce07fada2e924f5505ed493" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.0.0.tgz#32849d5bf66413883ce07fada2e924f5505ed493"
@ -2868,10 +2843,10 @@ js-yaml@^3.10.0:
argparse "^1.0.7" argparse "^1.0.7"
esprima "^4.0.0" esprima "^4.0.0"
js-yaml@^3.13.0: js-yaml@^3.13.1:
version "3.13.0" version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ== integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
dependencies: dependencies:
argparse "^1.0.7" argparse "^1.0.7"
esprima "^4.0.0" esprima "^4.0.0"
@ -2987,14 +2962,6 @@ light-my-request@^3.2.0:
ajv "^6.8.1" ajv "^6.8.1"
readable-stream "^3.1.1" readable-stream "^3.1.1"
lighthouse-logger@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz#b76d56935e9c137e86a04741f6bb9b2776e886ca"
integrity sha512-wzUvdIeJZhRsG6gpZfmSCfysaxNEr43i+QT+Hie94wvHDKFLi4n7C2GqZ4sTC+PH5b5iktmXJvU87rWvhP3lHw==
dependencies:
debug "^2.6.8"
marky "^1.2.0"
load-json-file@^4.0.0: load-json-file@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
@ -3162,11 +3129,6 @@ map-visit@^1.0.0:
dependencies: dependencies:
object-visit "^1.0.0" object-visit "^1.0.0"
marky@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.1.tgz#a3fcf82ffd357756b8b8affec9fdbf3a30dc1b02"
integrity sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==
matcher@^2.0.0: matcher@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/matcher/-/matcher-2.0.0.tgz#85fe38d97670dbd2a46590cf099401e2ffb4755c" resolved "https://registry.yarnpkg.com/matcher/-/matcher-2.0.0.tgz#85fe38d97670dbd2a46590cf099401e2ffb4755c"
@ -3313,7 +3275,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2" for-in "^1.0.2"
is-extendable "^1.0.1" is-extendable "^1.0.1"
mkdirp@0.5.1, mkdirp@^0.5.1: mkdirp@^0.5.1:
version "0.5.1" version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@ -3650,7 +3612,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-is-inside@^1.0.1, path-is-inside@^1.0.2: path-is-inside@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
@ -4061,10 +4023,10 @@ resolve@^1.10.0, resolve@^1.3.2:
dependencies: dependencies:
path-parse "^1.0.6" path-parse "^1.0.6"
resolve@^1.10.1, resolve@^1.11.0: resolve@^1.11.0, resolve@^1.11.1:
version "1.11.0" version "1.11.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e"
integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw== integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==
dependencies: dependencies:
path-parse "^1.0.6" path-parse "^1.0.6"
@ -4111,7 +4073,7 @@ rfdc@^1.1.2:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA== integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==
rimraf@2.6.3, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: rimraf@2.6.3, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.6.3" version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@ -4137,27 +4099,27 @@ rollup-plugin-cleanup@^3.1.1:
js-cleanup "^1.0.1" js-cleanup "^1.0.1"
rollup-pluginutils "^2.3.3" rollup-pluginutils "^2.3.3"
rollup-plugin-commonjs@^10.0.0: rollup-plugin-commonjs@^10.0.1:
version "10.0.0" version "10.0.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.0.0.tgz#58901ebe7ca44c2a03f0056de9bf9eb4a2dc8990" resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.0.1.tgz#fbfcadf4ce2e826068e056a9f5c19287d9744ddf"
integrity sha512-B8MoX5GRpj3kW4+YaFO/di2JsZkBxNjVmZ9LWjUoTAjq8N9wc7HObMXPsrvolVV9JXVtYSscflXM14A19dXPNQ== integrity sha512-x0PcCVdEc4J8igv1qe2vttz8JKAKcTs3wfIA3L8xEty3VzxgORLrzZrNWaVMc+pBC4U3aDOb9BnWLAQ8J11vkA==
dependencies: dependencies:
estree-walker "^0.6.0" estree-walker "^0.6.1"
is-reference "^1.1.2" is-reference "^1.1.2"
magic-string "^0.25.2" magic-string "^0.25.2"
resolve "^1.10.1" resolve "^1.11.0"
rollup-pluginutils "^2.7.0" rollup-pluginutils "^2.8.1"
rollup-plugin-node-resolve@^5.0.3: rollup-plugin-node-resolve@^5.1.0:
version "5.0.3" version "5.1.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.0.3.tgz#5e90fbd04a33fa1e4e1ed6d9b54afbe45af944f1" resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.1.0.tgz#49608b6ecaf2b776ab83e317d39b282d65d21b76"
integrity sha512-Mhhmf0x493xgUPEsRELnU1VM+4+WO82knWkAbZ0d2DvZQZJMbhzyQK/hqtpVscoRru1EqlK3TM1kK9ro469wPw== integrity sha512-2hwwHNj0s8UEtUNT+lJq8rFWEznP7yJm3GCHBicadF6hiNX1aRARRZIjz2doeTlTGg/hOvJr4C/8+3k9Y/J5Hg==
dependencies: dependencies:
"@types/resolve" "0.0.8" "@types/resolve" "0.0.8"
builtin-modules "^3.1.0" builtin-modules "^3.1.0"
is-module "^1.0.0" is-module "^1.0.0"
resolve "^1.11.0" resolve "^1.11.1"
rollup-pluginutils "^2.8.0" rollup-pluginutils "^2.8.1"
rollup-plugin-uglify-es@^0.0.1: rollup-plugin-uglify-es@^0.0.1:
version "0.0.1" version "0.0.1"
@ -4184,17 +4146,17 @@ rollup-pluginutils@^2.3.3:
estree-walker "^0.6.0" estree-walker "^0.6.0"
micromatch "^3.1.10" micromatch "^3.1.10"
rollup-pluginutils@^2.7.0, rollup-pluginutils@^2.8.0: rollup-pluginutils@^2.8.1:
version "2.8.1" version "2.8.1"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97"
integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg== integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==
dependencies: dependencies:
estree-walker "^0.6.1" estree-walker "^0.6.1"
rollup@^1.15.6: rollup@^1.16.2:
version "1.15.6" version "1.16.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.15.6.tgz#caf0ed28d2d78e3a59c1398e5a3695fb600a0ef0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.16.2.tgz#959aeae4b06c8e540749bac442d6d37aefb9217d"
integrity sha512-s3Vn3QJQ5YVFfIG4nXoG9VdL1I37IZsft+4ZyeBhxE0df1kCFz9e+4bEAbR4mKH3pvBO9e9xjdxWPhhIp0r9ow== integrity sha512-UAZxaQvH0klYZdF+90xv9nGb+m4p8jdoaow1VL5/RzDK/gN/4CjvaMmJNcOIv1/+gtzswKhAg/467mzF0sLpAg==
dependencies: dependencies:
"@types/estree" "0.0.39" "@types/estree" "0.0.39"
"@types/node" "^12.0.8" "@types/node" "^12.0.8"