UNPKG

11.7 kBJavaScriptView Raw
1'use strict';
2// thanks wombat
3var STYLE_REGEX = /(url\s*\(\s*[\\"']*)([^)'"]+)([\\"']*\s*\))/gi;
4var IMPORT_REGEX = /(@import\s*[\\"']*)([^)'";]+)([\\"']*\s*;?)/gi;
5var srcsetSplit = /\s*(\S*\s+[\d.]+[wx]),|(?:\s*,(?:\s+|(?=https?:)))/;
6var MaxRunningFetches = 15;
7var DataURLPrefix = 'data:';
8var seen = {};
9// array of URLs to be fetched
10var queue = [];
11var runningFetches = 0;
12// a URL to resolve relative URLs found in the cssText of CSSMedia rules.
13var currentResolver = null;
14
15var config = {
16 havePromise: typeof self.Promise !== 'undefined',
17 haveFetch: typeof self.fetch !== 'undefined',
18 proxyMode: false,
19 mod: null,
20 prefix: null,
21 prefixMod: null,
22 relative: null,
23 rwRe: null,
24 defaultFetchOptions: {
25 cache: 'force-cache',
26 mode: 'cors'
27 }
28};
29
30if (!config.havePromise) {
31 // not kewl we must polyfill Promise
32 self.Promise = function(executor) {
33 executor(noop, noop);
34 };
35 self.Promise.prototype.then = function(cb) {
36 if (cb) cb();
37 return this;
38 };
39 self.Promise.prototype.catch = function() {
40 return this;
41 };
42 self.Promise.all = function(values) {
43 return new Promise(noop);
44 };
45}
46
47if (!config.haveFetch) {
48 // not kewl we must polyfill fetch.
49 self.fetch = function(url) {
50 return new Promise(function(resolve) {
51 var xhr = new XMLHttpRequest();
52 xhr.open('GET', url, true);
53 xhr.onreadystatechange = function() {
54 if (xhr.readyState === 4) {
55 if (!config.havePromise) {
56 fetchDone();
57 }
58 resolve();
59 }
60 };
61 xhr.send();
62 });
63 };
64}
65
66if (location.search.indexOf('init') !== -1) {
67 (function() {
68 var init;
69 if (typeof self.URL === 'function') {
70 var loc = new self.URL(location.href);
71 init = JSON.parse(loc.searchParams.get('init'));
72 } else {
73 var search = decodeURIComponent(location.search.split('?')[1]).split('&');
74 init = JSON.parse(search[0].substr(search[0].indexOf('=') + 1));
75 init.prefix = decodeURIComponent(init.prefix);
76 init.baseURI = decodeURIComponent(init.prefix);
77 }
78 config.prefix = init.prefix;
79 config.mod = init.mod;
80 config.prefixMod = init.prefix + init.mod;
81 config.rwRe = new RegExp(init.rwRe);
82 config.relative = init.prefix.split(location.origin)[1];
83 config.schemeless = '/' + config.relative;
84 })();
85} else {
86 config.proxyMode = true;
87 config.defaultFetchOptions.mode = 'no-cors';
88}
89
90self.onmessage = function(event) {
91 var data = event.data;
92 switch (data.type) {
93 case 'values':
94 autoFetch(data);
95 break;
96 case 'fetch-all':
97 justFetch(data);
98 break;
99 }
100};
101
102function noop() {}
103
104function fetchDone() {
105 runningFetches -= 1;
106 fetchFromQ();
107}
108
109function fetchErrored(err) {
110 console.warn('Fetch Failed: ' + err);
111 fetchDone();
112}
113
114/**
115 * Fetches the supplied URL and increments the {@link runningFetches} variable
116 * to represent an inflight request.
117 * If the url to be fetched is an object then its a fetch-as-page and the
118 * fetch is configured using its supplied options and url properties.
119 *
120 * Otherwise, the fetch is made using cache mode force-cache and if we
121 * are operating in proxy mode the fetch mode no-cors is used.
122 * @param {string|Object} toBeFetched - The URL to be fetched
123 */
124function fetchURL(toBeFetched) {
125 runningFetches += 1;
126
127 var url;
128 var options = config.defaultFetchOptions;
129
130 if (typeof toBeFetched === 'object') {
131 url = toBeFetched.url;
132 options = toBeFetched.options;
133 } else {
134 url = toBeFetched;
135 }
136
137 fetch(url, options)
138 .then(fetchDone)
139 .catch(fetchErrored);
140}
141
142function queueOrFetch(toBeFetched) {
143 var url = typeof toBeFetched === 'object' ? toBeFetched.url : toBeFetched;
144 if (!url || url.indexOf(DataURLPrefix) === 0 || seen[url] != null) {
145 return;
146 }
147 seen[url] = true;
148 if (runningFetches >= MaxRunningFetches) {
149 queue.push(toBeFetched);
150 return;
151 }
152 fetchURL(toBeFetched);
153}
154
155function fetchFromQ() {
156 while (queue.length && runningFetches < MaxRunningFetches) {
157 fetchURL(queue.shift());
158 }
159}
160
161function maybeResolveURL(url, base) {
162 // given a url and base url returns a resolved full URL or
163 // null if resolution was unsuccessful
164 try {
165 var _url = new URL(url, base);
166 return _url.href;
167 } catch (e) {
168 return null;
169 }
170}
171
172function safeResolve(url, resolver) {
173 // Guard against the exception thrown by the URL constructor if the URL or resolver is bad
174 // if resolver is undefined/null then this function passes url through
175 var resolvedURL = url;
176 if (resolver) {
177 try {
178 var _url = new URL(url, resolver);
179 return _url.href;
180 } catch (e) {
181 resolvedURL = url;
182 }
183 }
184 return resolvedURL;
185}
186
187function maybeFixUpRelSchemelessPrefix(url) {
188 // attempt to ensure rewritten relative or schemeless URLs become full URLS!
189 // otherwise returns null if this did not happen
190 if (url.indexOf(config.relative) === 0) {
191 return url.replace(config.relative, config.prefix);
192 }
193 if (url.indexOf(config.schemeless) === 0) {
194 return url.replace(config.schemeless, config.prefix);
195 }
196 return null;
197}
198
199function maybeFixUpURL(url, resolveOpts) {
200 // attempt to fix up the url and do our best to ensure we can get dat 200 OK!
201 if (config.rwRe.test(url)) {
202 return url;
203 }
204 var mod = resolveOpts.mod || 'mp_';
205 // first check for / (relative) or // (schemeless) rewritten urls
206 var maybeFixed = maybeFixUpRelSchemelessPrefix(url);
207 if (maybeFixed != null) {
208 return maybeFixed;
209 }
210 // resolve URL against tag src
211 if (resolveOpts.tagSrc != null) {
212 maybeFixed = maybeResolveURL(url, resolveOpts.tagSrc);
213 if (maybeFixed != null) {
214 return config.prefix + mod + '/' + maybeFixed;
215 }
216 }
217 // finally last attempt resolve the originating documents base URI
218 if (resolveOpts.docBaseURI) {
219 maybeFixed = maybeResolveURL(url, resolveOpts.docBaseURI);
220 if (maybeFixed != null) {
221 return config.prefix + mod + '/' + maybeFixed;
222 }
223 }
224 // not much to do now.....
225 return config.prefixMod + '/' + url;
226}
227
228function urlExtractor(match, n1, n2, n3, offset, string) {
229 // Same function as style_replacer in wombat.rewrite_style, n2 is our URL
230 queueOrFetch(n2);
231 return n1 + n2 + n3;
232}
233
234function urlExtractorProxyMode(match, n1, n2, n3, offset, string) {
235 // Same function as style_replacer in wombat.rewrite_style, n2 is our URL
236 // this.currentResolver is set to the URL which the browser would normally
237 // resolve relative urls with (URL of the stylesheet) in an exceptionless manner
238 // (resolvedURL will be undefined if an error occurred)
239 queueOrFetch(safeResolve(n2, currentResolver));
240 return n1 + n2 + n3;
241}
242
243function handleMedia(mediaRules) {
244 // this is a broken down rewrite_style
245 if (mediaRules == null || mediaRules.length === 0) return;
246 for (var i = 0; i < mediaRules.length; i++) {
247 mediaRules[i]
248 .replace(STYLE_REGEX, urlExtractor)
249 .replace(IMPORT_REGEX, urlExtractor);
250 }
251}
252
253function handleMediaProxyMode(mediaRules) {
254 // this is a broken down rewrite_style
255 if (mediaRules == null || mediaRules.length === 0) return;
256 for (var i = 0; i < mediaRules.length; i++) {
257 // set currentResolver to the value of this stylesheets URL, done to ensure we do not have to
258 // create functions on each loop iteration because we potentially create a new `URL` object
259 // twice per iteration
260 currentResolver = mediaRules[i].resolve;
261 mediaRules[i].cssText
262 .replace(STYLE_REGEX, urlExtractorProxyMode)
263 .replace(IMPORT_REGEX, urlExtractorProxyMode);
264 }
265}
266
267function handleSrc(srcValues, context) {
268 var resolveOpts = { docBaseURI: context.docBaseURI, mod: null };
269 if (srcValues.value) {
270 resolveOpts.mod = srcValues.mod;
271 return queueOrFetch(maybeFixUpURL(srcValues.value.trim(), resolveOpts));
272 }
273 var len = srcValues.values.length;
274 for (var i = 0; i < len; i++) {
275 var value = srcValues.values[i];
276 resolveOpts.mod = value.mod;
277 queueOrFetch(maybeFixUpURL(value.src, resolveOpts));
278 }
279}
280
281function handleSrcProxyMode(srcValues) {
282 // preservation worker in proxy mode sends us the value of the srcset attribute of an element
283 // and a URL to correctly resolve relative URLS. Thus we must recreate rewrite_srcset logic here
284 if (srcValues == null || srcValues.length === 0) return;
285 var srcVal;
286 for (var i = 0; i < srcValues.length; i++) {
287 srcVal = srcValues[i];
288 queueOrFetch(safeResolve(srcVal.src, srcVal.resolve));
289 }
290}
291
292function extractSrcSetNotPreSplit(ssV, resolveOpts) {
293 if (!ssV) return;
294 // was from extract from local doc so we need to duplicate work
295 var srcsetValues = ssV.split(srcsetSplit);
296 for (var i = 0; i < srcsetValues.length; i++) {
297 // grab the URL not width/height key
298 if (srcsetValues[i]) {
299 var value = srcsetValues[i].trim().split(' ')[0];
300 var maybeResolvedURL = maybeFixUpURL(value.trim(), resolveOpts);
301 queueOrFetch(maybeResolvedURL);
302 }
303 }
304}
305
306function extractSrcset(srcsets) {
307 // was rewrite_srcset and only need to q
308 for (var i = 0; i < srcsets.length; i++) {
309 // grab the URL not width/height key
310 var url = srcsets[i].split(' ')[0];
311 queueOrFetch(url);
312 }
313}
314
315function handleSrcset(srcset, context) {
316 if (srcset == null) return;
317 var resolveOpts = {
318 docBaseURI: context.docBaseURI,
319 mod: null,
320 tagSrc: null
321 };
322 if (srcset.value) {
323 // we have a single value, this srcset came from either
324 // preserveDataSrcset (not presplit) preserveSrcset (presplit)
325 resolveOpts.mod = srcset.mod;
326 if (!srcset.presplit) {
327 // extract URLs from the srcset string
328 return extractSrcSetNotPreSplit(srcset.value, resolveOpts);
329 }
330 // we have an array of srcset URL strings
331 return extractSrcset(srcset.value);
332 }
333 // we have an array of values, these srcsets came from extractFromLocalDoc
334 var len = srcset.values.length;
335 for (var i = 0; i < len; i++) {
336 var ssv = srcset.values[i];
337 resolveOpts.mod = ssv.mod;
338 resolveOpts.tagSrc = ssv.tagSrc;
339 extractSrcSetNotPreSplit(ssv.srcset, resolveOpts);
340 }
341}
342
343function handleSrcsetProxyMode(srcsets) {
344 // preservation worker in proxy mode sends us the value of the srcset attribute of an element
345 // and a URL to correctly resolve relative URLS. Thus we must recreate rewrite_srcset logic here
346 if (srcsets == null) return;
347 var length = srcsets.length;
348 var extractedSrcSet, srcsetValue, ssSplit, j;
349 for (var i = 0; i < length; i++) {
350 extractedSrcSet = srcsets[i];
351 ssSplit = extractedSrcSet.srcset.split(srcsetSplit);
352 for (j = 0; j < ssSplit.length; j++) {
353 if (ssSplit[j]) {
354 srcsetValue = ssSplit[j].trim();
355 if (srcsetValue) {
356 queueOrFetch(
357 safeResolve(srcsetValue.split(' ')[0], extractedSrcSet.resolve)
358 );
359 }
360 }
361 }
362 }
363}
364
365function autoFetch(data) {
366 // we got a message and now we autofetch!
367 // these calls turn into no ops if they have no work
368 if (data.media) {
369 if (config.proxyMode) {
370 handleMediaProxyMode(data.media);
371 } else {
372 handleMedia(data.media);
373 }
374 }
375
376 if (data.src) {
377 if (config.proxyMode) {
378 handleSrcProxyMode(data.src);
379 } else {
380 handleSrc(data.src, data.context || { docBaseURI: null });
381 }
382 }
383
384 if (data.srcset) {
385 if (config.proxyMode) {
386 handleSrcsetProxyMode(data.srcset);
387 } else {
388 handleSrcset(data.srcset, data.context || { docBaseURI: null });
389 }
390 }
391}
392
393function justFetch(data) {
394 // we got a message containing only urls to be fetched
395 if (data == null || data.values == null) return;
396 for (var i = 0; i < data.values.length; ++i) {
397 queueOrFetch(data.values[i]);
398 }
399}