Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce a new resource loading API
This is a first draft at the new resource loading API discussed in #2050. For now the old resource loading API, and the old API in general, are preserved; they will be removed in subsequent commits. This also raises the minimum required version to Node.js v8, as the new resource loader will be part of a major release anyway, and the maintenance burden of supporting that old vm module is getting too high.
- Loading branch information
Showing
30 changed files
with
1,300 additions
and
522 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
"use strict"; | ||
|
||
/** | ||
* AsyncResourceQueue is the queue in charge of run the async scripts | ||
* and notify when they finish. | ||
*/ | ||
module.exports = class AsyncResourceQueue { | ||
constructor() { | ||
this.items = new Set(); | ||
} | ||
|
||
count() { | ||
return this.items.size; | ||
} | ||
|
||
_notify() { | ||
if (this._listener) { | ||
this._listener(); | ||
} | ||
} | ||
|
||
setListener(listener) { | ||
this._listener = listener; | ||
} | ||
|
||
push(request, onLoad, onError) { | ||
const q = this; | ||
|
||
q.items.add(request); | ||
|
||
function check(error, data) { | ||
let promise; | ||
|
||
if (onError && error) { | ||
promise = onError(error); | ||
} else if (onLoad && data) { | ||
promise = onLoad(data); | ||
} | ||
|
||
promise | ||
.then(() => { | ||
q.items.delete(request); | ||
|
||
if (q.count() === 0) { | ||
q._notify(); | ||
} | ||
}); | ||
} | ||
|
||
return request | ||
.then(data => { | ||
if (onLoad) { | ||
return check(null, data); | ||
} | ||
|
||
q.items.delete(request); | ||
|
||
if (q.count() === 0) { | ||
q._notify(); | ||
} | ||
|
||
return null; | ||
}) | ||
.catch(err => { | ||
if (onError) { | ||
return check(err); | ||
} | ||
|
||
q.items.delete(request); | ||
|
||
if (q.count() === 0) { | ||
q._notify(); | ||
} | ||
|
||
return null; | ||
}); | ||
} | ||
}; |
116 changes: 116 additions & 0 deletions
116
lib/jsdom/browser/resources/per-document-resource-loader.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
"use strict"; | ||
const wrapCookieJarForRequest = require("../../living/helpers/wrap-cookie-jar-for-request"); | ||
|
||
module.exports = class PerDocumentResourceLoader { | ||
constructor(document) { | ||
this._document = document; | ||
this._defaultEncoding = document._encoding; | ||
this._resourceLoader = document._defaultView ? document._defaultView._resourceLoader : null; | ||
this._requestManager = document._requestManager; | ||
this._queue = document._queue; | ||
this._deferQueue = document._deferQueue; | ||
this._asyncQueue = document._asyncQueue; | ||
|
||
// TODO: Remove when _hasFeature is no longer necessary | ||
this._implementation = document._implementation; | ||
} | ||
|
||
fetch(url, options) { | ||
const { element } = options; | ||
|
||
if (!this._implementation._hasFeature("FetchExternalResources", element.tagName.toLowerCase())) { | ||
return null; | ||
} | ||
|
||
if (this._implementation._hasFeature("SkipExternalResources", url)) { | ||
return null; | ||
} | ||
|
||
const requestOptions = { | ||
jar: wrapCookieJarForRequest(this._document._cookieJar), | ||
proxy: this._document._proxy, | ||
strictSSL: typeof this._document._strictSSL === "boolean" ? this._document._strictSSL : true | ||
}; | ||
|
||
options = Object.assign({}, options, { requestOptions, referrer: this._document.URL }); | ||
|
||
const request = this._resourceLoader.fetch( | ||
url, | ||
options | ||
); | ||
|
||
this._requestManager.add(request); | ||
|
||
const ev = this._document.createEvent("HTMLEvents"); | ||
|
||
const onError = error => { | ||
this._requestManager.remove(request); | ||
|
||
if (options.onError) { | ||
options.onError(error); | ||
} | ||
|
||
// TODO: Create events in the modern way https://github.com/jsdom/jsdom/pull/2279#discussion_r199969734 | ||
ev.initEvent("error", false, false); | ||
element.dispatchEvent(ev); | ||
|
||
const err = new Error(`Could not load ${element.localName}: "${url}"`); | ||
err.type = "resource loading"; | ||
err.detail = error; | ||
|
||
this._document._defaultView._virtualConsole.emit("jsdomError", err); | ||
|
||
return Promise.resolve(); | ||
}; | ||
|
||
const onLoad = data => { | ||
this._requestManager.remove(request); | ||
|
||
this._addCookies(url, request.response ? request.response.headers : {}); | ||
|
||
try { | ||
const result = options.onLoad ? options.onLoad(data) : undefined; | ||
|
||
return Promise.resolve(result) | ||
.then(() => { | ||
// TODO: Create events in the modern way https://github.com/jsdom/jsdom/pull/2279#discussion_r199969734 | ||
ev.initEvent("load", false, false); | ||
element.dispatchEvent(ev); | ||
|
||
return Promise.resolve(); | ||
}) | ||
.catch(err => { | ||
return onError(err); | ||
}); | ||
} catch (err) { | ||
return onError(err); | ||
} | ||
}; | ||
|
||
if (element.localName === "script" && element.hasAttribute("async")) { | ||
this._asyncQueue.push(request, onLoad, onError); | ||
} else if (element.localName === "script" && element.hasAttribute("defer")) { | ||
this._deferQueue.push(request, onLoad, onError, false, element); | ||
} else { | ||
this._queue.push(request, onLoad, onError, false, element); | ||
} | ||
|
||
return request; | ||
} | ||
|
||
_addCookies(url, headers) { | ||
let cookies = headers["set-cookie"]; | ||
|
||
if (!cookies) { | ||
return; | ||
} | ||
|
||
if (!Array.isArray(cookies)) { | ||
cookies = [cookies]; | ||
} | ||
|
||
cookies.forEach(cookie => { | ||
this._document._cookieJar.setCookieSync(cookie, url, { http: true, ignoreError: true }); | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
"use strict"; | ||
|
||
/** | ||
* Manage all the request and it is able to abort | ||
* all pending request. | ||
*/ | ||
module.exports = class RequestManager { | ||
constructor() { | ||
this.openedRequests = []; | ||
} | ||
|
||
add(req) { | ||
this.openedRequests.push(req); | ||
} | ||
|
||
remove(req) { | ||
const idx = this.openedRequests.indexOf(req); | ||
if (idx !== -1) { | ||
this.openedRequests.splice(idx, 1); | ||
} | ||
} | ||
|
||
close() { | ||
for (const openedRequest of this.openedRequests) { | ||
openedRequest.abort(); | ||
} | ||
this.openedRequests = []; | ||
} | ||
|
||
size() { | ||
return this.openedRequests.length; | ||
} | ||
}; |
Oops, something went wrong.