blob: b5de929db716e84a4d9c66777b48001c470ceb49 [file] [log] [blame]
// ==ClosureCompiler==
// @output_file_name fetch.js
// @compilation_level SIMPLE_OPTIMIZATIONS
// @language_out ES5_STRICT
// ==/ClosureCompiler==
// Copyright (c) 2014-2016 GitHub, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Contact GitHub API Training Shop Blog About
// Fetch API: https://fetch.spec.whatwg.org/
(function(self) {
'use strict';
if (self.fetch) {
return
}
const Array = self.Array
const ArrayBuffer = self.ArrayBuffer
const create = self.Object.create
const defineProperties = self.Object.defineProperties
const defineProperty = self.Object.defineProperty
const Error = self.Error
const Symbol = self.Symbol
const Symbol_iterator = Symbol.iterator
const Map = self.Map
const RangeError = self.RangeError
const TypeError = self.TypeError
const Uint8Array = self.Uint8Array
const Promise = self.Promise
const ReadableStream = self.ReadableStream
const ReadableStreamTee = self.ReadableStreamTee
const IsReadableStreamDisturbed = self.IsReadableStreamDisturbed
const IsReadableStreamLocked = self.IsReadableStreamLocked
const ABORT_ERROR = 'AbortError'
const ABORT_MESSAGE = 'Aborted'
const ERROR_INVALID_HEADERS_INIT =
'Constructing Headers with invalid parameters'
const ERROR_NETWORK_REQUEST_FAILED = 'Network request failed'
// Internal slots for objects.
const BODY_SLOT = Symbol('body')
const BODY_USED_SLOT = Symbol('bodyUsed')
const CACHE_SLOT = Symbol('cache')
const CREDENTIALS_SLOT = Symbol('credentials')
const GUARD_CALLBACK_SLOT = Symbol('guardCallback')
const HEADERS_SLOT = Symbol('headers')
const INTEGRITY_SLOT = Symbol('integrity')
const MAP_SLOT = Symbol('map')
const METHOD_SLOT = Symbol('method')
const MODE_SLOT = Symbol('mode')
const OK_SLOT = Symbol('ok')
const REDIRECT_SLOT = Symbol('redirect')
const STATUS_SLOT = Symbol('status')
const STATUS_TEXT_SLOT = Symbol('statusText')
const TYPE_SLOT = Symbol('type')
const URL_SLOT = Symbol('url')
const IS_ABORTED_SLOT = Symbol('is_aborted')
const SIGNAL_SLOT = Symbol('signal')
// Forbidden headers corresponding to various header guard types.
const INVALID_HEADERS_REQUEST = [
'accept-charset',
'accept-encoding',
'access-control-request-headers',
'access-control-request-method',
'connection',
'content-length',
'cookie',
'cookie2',
'date',
'dnt',
'expect',
'host',
'keep-alive',
'origin',
'referer', // [sic]
'te',
'trailer',
'transfer-encoding',
'upgrade',
'via'
]
const INVALID_HEADERS_RESPONSE = [
'set-cookie',
'set-cookie2'
]
// The header guard for no-cors requests is special. Only these headers are
// allowed. And only certain values for content-type are allowed.
const VALID_HEADERS_NOCORS = [
'accept',
'accept-language',
'content-language'
// 'content-type' is treated specially.
]
const VALID_HEADERS_NOCORS_CONTENT_TYPE = [
'application/x-www-form-urlencoded',
'multipart/form-data',
'text/plain'
]
// Values that are allowed for Request cache mode.
const VALID_CACHE_MODES = [
'default',
'no-store',
'reload',
'no-cache',
'force-cache',
'only-if-cached'
]
// Values that are allowed for Request credentials.
const VALID_CREDENTIALS = ['omit', 'same-origin', 'include']
// HTTP methods whose capitalization should be normalized.
const VALID_METHODS = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
// Methods that are allowed for Request mode: no-cors.
const VALID_METHODS_NOCORS = ['GET', 'HEAD', 'POST']
// Modes that are allowed for RequestInit. Although 'navigate' is a valid
// request mode, it is not allowed in the RequestInit parameter.
const VALID_MODES = ['same-origin', 'no-cors', 'cors']
// Values that are allowed for Request redirect mode.
const VALID_REDIRECT_MODES = ['follow', 'error', 'manual']
// Body is not allowed in responses with a null body status.
const NULL_BODY_STATUSES = [101, 204, 205, 304 ]
// Statuses that are allowed for Response.redirect.
const REDIRECT_STATUSES = [301, 302, 303, 307, 308]
const ARRAY_BUFFER_VIEW_CLASSES = [
'[object Int8Array]',
'[object Uint8Array]',
'[object Uint8ClampedArray]',
'[object Int16Array]',
'[object Uint16Array]',
'[object Int32Array]',
'[object Uint32Array]',
'[object Float32Array]',
'[object Float64Array]'
]
var isArrayBufferView = ArrayBuffer.isView || function(obj) {
return obj && ARRAY_BUFFER_VIEW_CLASSES.indexOf(
Object.prototype.toString.call(obj)) > -1
}
function normalizeName(name) {
if (typeof name !== 'string') {
name = String(name)
}
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
throw new TypeError('Invalid character in header field name')
}
return name.toLowerCase()
}
function normalizeValue(value) {
if (typeof value !== 'string') {
value = String(value)
}
// https://fetch.spec.whatwg.org/#concept-header-value
// The web platform tests don't quite abide by the current spec. Use a
// permissive approach that passes the tests while following the spirit
// of the spec. Essentially, leading & trailing HTTP whitespace bytes
// are trimmed first before checking validity.
var c, first, last, i, len
//value = value.replace(/^[ \t\n\r]+|[ \t\n\r]+$/g, '')
for (first = 0, len = value.length; first < len; first++) {
c = value.charCodeAt(first)
if (c !== 9 && c !== 10 && c !== 13 && c !== 32) {
break
}
}
for (last = value.length - 1; last > first; last--) {
c = value.charCodeAt(last)
if (c !== 9 && c !== 10 && c !== 13 && c !== 32) {
break
}
}
value = value.substring(first, last + 1)
// Follow the chromium implementation of IsValidHTTPHeaderValue(). This
// should be updated once the web platform test abides by the fetch spec.
for (i = 0, len = value.length; i < len; i++) {
c = value.charCodeAt(i)
if (c >= 256 || c === 0 || c === 10 || c === 13) {
throw new TypeError('Invalid character in header field value')
}
}
return value
}
// Callbacks to determine whether a given header name & value are forbidden
// for various header guard types.
function guardImmutableCallback(name, value) {
throw new TypeError('Immutable header cannot be modified')
}
function guardNoneCallback(name, value) {
return false
}
function guardRequestCallback(name, value) {
var nameLower = name.toLowerCase()
if (INVALID_HEADERS_REQUEST.indexOf(nameLower) > -1 ||
nameLower.startsWith('proxy-') || nameLower.startsWith('sec-')) {
return true
}
return false
}
function guardRequestNoCorsCallback(name, value) {
var nameLower = name.toLowerCase()
if (VALID_HEADERS_NOCORS.indexOf(nameLower) > -1) {
return false
}
if (nameLower === 'content-type') {
var mimeType = value.split(';')[0].toLowerCase()
if (VALID_HEADERS_NOCORS_CONTENT_TYPE.indexOf(mimeType) > -1) {
return false
}
}
return true
}
function guardResponseCallback(name, value) {
if (INVALID_HEADERS_RESPONSE.indexOf(name.toLowerCase()) > -1) {
return true
}
return false
}
// https://fetch.spec.whatwg.org/#headers-class
function Headers(init) {
this[MAP_SLOT] = new Map();
if (this[GUARD_CALLBACK_SLOT] === undefined) {
this[GUARD_CALLBACK_SLOT] = guardNoneCallback
}
if (init === undefined) {
return
} else if (init === null || typeof init !== 'object') {
throw new TypeError(ERROR_INVALID_HEADERS_INIT)
} else if (init instanceof Headers) {
// TODO: Use for..of in case |init| has a custom Symbol.iterator.
// However, this results in the ClosureCompiler polyfilling Symbol, so
// use forEach until ClosureCompiler supports ES6 output.
init.forEach(function(value, name) {
this.append(name, value)
}, this)
} else if (Array.isArray(init)) {
init.forEach(function(header) {
if (header.length !== 2) {
throw new TypeError(ERROR_INVALID_HEADERS_INIT)
}
this.append(header[0], header[1])
}, this)
} else {
Object.getOwnPropertyNames(init).forEach(function(name) {
this.append(name, init[name])
}, this)
}
}
function CreateHeadersWithGuard(headersInit, guardCallback) {
var headers = create(Headers.prototype)
headers[GUARD_CALLBACK_SLOT] = guardCallback
Headers.call(headers, headersInit)
return headers
}
Headers.prototype.append = function(name, value) {
if (arguments.length !== 2) {
throw TypeError('Invalid parameters to append')
}
name = normalizeName(name)
value = normalizeValue(value)
if (this[GUARD_CALLBACK_SLOT](name, value)) {
return
}
if (this[MAP_SLOT].has(name)) {
this[MAP_SLOT].set(name, this[MAP_SLOT].get(name) + ', ' + value)
} else {
this[MAP_SLOT].set(name, value)
}
}
Headers.prototype['delete'] = function(name) {
if (arguments.length !== 1) {
throw TypeError('Invalid parameters to delete')
}
if (this[GUARD_CALLBACK_SLOT](name, 'invalid')) {
return
}
this[MAP_SLOT].delete(normalizeName(name))
}
Headers.prototype.get = function(name) {
if (arguments.length !== 1) {
throw TypeError('Invalid parameters to get')
}
name = normalizeName(name)
var value = this[MAP_SLOT].get(name)
return value !== undefined ? value : null
}
Headers.prototype.has = function(name) {
if (arguments.length !== 1) {
throw TypeError('Invalid parameters to has')
}
return this[MAP_SLOT].has(normalizeName(name))
}
Headers.prototype.set = function(name, value) {
if (arguments.length !== 2) {
throw TypeError('Invalid parameters to set')
}
name = normalizeName(name)
value = normalizeValue(value)
if (this[GUARD_CALLBACK_SLOT](name, value)) {
return
}
this[MAP_SLOT].set(name, value)
}
Headers.prototype.forEach = function(callback, thisArg) {
var sorted_array = Array.from(this[MAP_SLOT].entries()).sort()
var that = this
sorted_array.forEach(function(value) {
callback.call(thisArg, value[1], value[0], that)
})
}
Headers.prototype.keys = function() {
var sorted_map = new Map(Array.from(this[MAP_SLOT].entries()).sort())
return sorted_map.keys()
}
Headers.prototype.values = function() {
var sorted_map = new Map(Array.from(this[MAP_SLOT].entries()).sort())
return sorted_map.values()
}
Headers.prototype.entries = function() {
var sorted_map = new Map(Array.from(this[MAP_SLOT].entries()).sort())
return sorted_map.entries()
}
Headers.prototype[Symbol_iterator] = Headers.prototype.entries
function consumeBodyAsUint8Array(body) {
if (body.bodyUsed) {
return Promise.reject(new TypeError('Body was already read'))
}
if (body.body === null) {
return Promise.resolve(new Uint8Array(0))
}
if (IsReadableStreamLocked(body.body)) {
return Promise.reject(new TypeError('ReadableStream was already locked'))
}
var reader = body.body.getReader()
var results = []
var resultsLength = 0
return reader.read().then(function addResult(result) {
if (result.done) {
var data
if (results.length === 0) {
data = new Uint8Array(0)
} else if (results.length === 1) {
// Create a view of the ArrayBuffer. No copy is made.
data = new Uint8Array(results[0].buffer)
} else {
data = new Uint8Array(resultsLength)
for (var i = 0, len = results.length, offset = 0; i < len; i++) {
data.set(results[i], offset)
offset += results[i].length
}
}
return data
} else if (result.value instanceof Uint8Array) {
resultsLength += result.value.length
results.push(result.value)
return reader.read().then(addResult)
} else {
return Promise.reject(new TypeError('Invalid stream data type'))
}
})
}
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
function extractBody(controller, data, errorString) {
// Copy the incoming data. This is to prevent subsequent changes to |data|
// from impacting the Body's contents, and also to prevent changes to the
// Body's contents from impacting the original data.
if (!data) {
} else if (typeof data === 'string') {
controller.enqueue(FetchInternal.encodeToUTF8(data))
} else if (ArrayBuffer.prototype.isPrototypeOf(data)) {
controller.enqueue(new Uint8Array(data.slice(0)))
} else if (isArrayBufferView(data)) {
controller.enqueue(new Uint8Array(data.buffer.slice(0)))
} else if (data instanceof Blob) {
controller.enqueue(new Uint8Array(FetchInternal.blobToArrayBuffer(data)))
} else {
throw new TypeError(errorString)
}
}
// https://fetch.spec.whatwg.org/#body-mixin
// However, our engine does not fully support URLSearchParams, FormData, nor
// Blob types. So only support text(), arrayBuffer(), and json().
function Body() {
this._initBody = function(body) {
this[BODY_USED_SLOT] = false
if (body === null || body === undefined) {
this[BODY_SLOT] = null
} else if (body instanceof ReadableStream) {
this[BODY_SLOT] = body
} else {
this[BODY_SLOT] = new ReadableStream({
start(controller) {
extractBody(controller, body, 'Unsupported BodyInit type')
controller.close()
}
})
}
if (!this[HEADERS_SLOT].get('content-type')) {
if (typeof body === 'string') {
this[HEADERS_SLOT].set('content-type', 'text/plain;charset=UTF-8')
} else if (body instanceof Blob && body.type !== "") {
this[HEADERS_SLOT].set('content-type', body.type)
}
}
}
defineProperties(this, {
'body': { get: function() { return this[BODY_SLOT] } },
'bodyUsed': {
get: function() {
if (this[BODY_USED_SLOT]) {
return true
} else if (this[BODY_SLOT]) {
return !!IsReadableStreamDisturbed(this[BODY_SLOT])
} else {
return false
}
}
}
})
this.arrayBuffer = function() {
if (this[IS_ABORTED_SLOT]) {
return Promise.reject(new DOMException(ABORT_MESSAGE, ABORT_ERROR))
}
return consumeBodyAsUint8Array(this).then(function(data) {
return data.buffer
})
}
this.text = function() {
if (this[IS_ABORTED_SLOT]) {
return Promise.reject(new DOMException(ABORT_MESSAGE, ABORT_ERROR))
}
return consumeBodyAsUint8Array(this).then(function(data) {
return FetchInternal.decodeFromUTF8(data)
})
}
this.json = function() {
if (this[IS_ABORTED_SLOT]) {
return Promise.reject(new DOMException(ABORT_MESSAGE, ABORT_ERROR))
}
return this.text().then(JSON.parse)
}
return this
}
function normalizeMethod(method) {
var upcased = method.toUpperCase()
if (VALID_METHODS.indexOf(upcased) === -1) {
throw new TypeError('Invalid request method')
}
return upcased
}
// https://fetch.spec.whatwg.org/#request-class
function Request(input, init) {
// When cloning a request, |init| will have the non-standard member
// |cloneBody|. This signals that the "!hasInit" path should be used.
var hasInit = init !== undefined && init !== null &&
init.cloneBody === undefined
init = init || {}
var body = init.body || init.cloneBody
var headersInit = init.headers
// AbortSignal cannot be constructed directly, so create a temporary
// AbortController and steal its signal. This signal is only used to follow
// another signal, so the controller isn't needed.
var tempController = new AbortController()
this[SIGNAL_SLOT] = tempController.signal
// Let signal be null.
var signal = null
if (input instanceof Request) {
this[URL_SLOT] = input.url
this[CACHE_SLOT] = input.cache
this[CREDENTIALS_SLOT] = input.credentials
if (headersInit === undefined) {
headersInit = input.headers
}
this[INTEGRITY_SLOT] = input.integrity
this[METHOD_SLOT] = input.method
this[MODE_SLOT] = input.mode
if (hasInit && this[MODE_SLOT] === 'navigate') {
this[MODE_SLOT] = 'same-origin'
}
this[REDIRECT_SLOT] = input.redirect
if (!body && input.body !== null) {
// Take ownership of the stream and mark |input| as disturbed.
body = input.body
input[BODY_USED_SLOT] = true
}
// Set signal to input's signal.
signal = input[SIGNAL_SLOT]
} else {
this[URL_SLOT] = String(input)
if (!FetchInternal.isUrlValid(this[URL_SLOT],
false /* allowCredentials */)) {
throw new TypeError('Invalid request URL')
}
this[MODE_SLOT] = 'cors'
this[CREDENTIALS_SLOT] = 'same-origin'
}
if (init.window !== undefined && init.window !== null) {
throw new TypeError('Invalid request window')
}
this[CACHE_SLOT] = init.cache || this[CACHE_SLOT] || 'default'
if (VALID_CACHE_MODES.indexOf(this[CACHE_SLOT]) === -1) {
throw new TypeError('Invalid request cache mode')
}
this[CREDENTIALS_SLOT] = init.credentials || this[CREDENTIALS_SLOT] ||
'same-origin'
if (VALID_CREDENTIALS.indexOf(this[CREDENTIALS_SLOT]) === -1) {
throw new TypeError('Invalid request credentials')
}
if (init.integrity !== undefined) {
this[INTEGRITY_SLOT] = init.integrity
} else if (this[INTEGRITY_SLOT] === undefined) {
this[INTEGRITY_SLOT] = ''
}
this[METHOD_SLOT] = normalizeMethod(init.method || this[METHOD_SLOT] ||
'GET')
if (init.mode && VALID_MODES.indexOf(init.mode) === -1) {
throw new TypeError('Invalid request mode')
}
this[MODE_SLOT] = init.mode || this[MODE_SLOT] || 'no-cors'
if (this[MODE_SLOT] === 'no-cors') {
if (VALID_METHODS_NOCORS.indexOf(this[METHOD_SLOT]) === -1) {
throw new TypeError('Invalid request method for no-cors')
}
if (this[INTEGRITY_SLOT] !== '') {
throw new TypeError(
'Request integrity data is not allowed with no-cors')
}
}
if (this[MODE_SLOT] !== 'same-origin' &&
this[CACHE_SLOT] === 'only-if-cached') {
throw new TypeError(
'Request mode must be same-origin for only-if-cached')
}
this[REDIRECT_SLOT] = init.redirect || this[REDIRECT_SLOT] || 'follow'
if (VALID_REDIRECT_MODES.indexOf(this[REDIRECT_SLOT]) === -1) {
throw new TypeError('Invalid request redirect mode')
}
if (this[MODE_SLOT] === 'no-cors') {
this[HEADERS_SLOT] =
CreateHeadersWithGuard(headersInit, guardRequestNoCorsCallback)
} else {
this[HEADERS_SLOT] =
CreateHeadersWithGuard(headersInit, guardRequestCallback)
}
if ((this[METHOD_SLOT] === 'GET' || this[METHOD_SLOT] === 'HEAD') && body) {
throw new TypeError('Request body is not allowed for GET or HEAD')
}
// If init["signal"] exists, then set signal to it.
if ('signal' in init) {
signal = init.signal
}
// If signal is not null, then make r's signal follow signal.
if (signal) {
this[SIGNAL_SLOT].follow(signal)
}
this._initBody(body)
}
Request.prototype.clone = function() {
var cloneBody = null
if (this[BODY_SLOT] !== null) {
var streams = ReadableStreamTee(this[BODY_SLOT],
true /* cloneForBranch2 */)
this[BODY_SLOT] = streams[0]
cloneBody = streams[1]
}
return new Request(this, { cloneBody: cloneBody, signal: this[SIGNAL_SLOT] })
}
defineProperties(Request.prototype, {
'cache': { get: function() { return this[CACHE_SLOT] } },
'credentials': { get: function() { return this[CREDENTIALS_SLOT] } },
'headers': { get: function() { return this[HEADERS_SLOT] } },
'integrity': { get: function() { return this[INTEGRITY_SLOT] } },
'method': { get: function() { return this[METHOD_SLOT] } },
'mode': { get: function() { return this[MODE_SLOT] } },
'redirect': { get: function() { return this[REDIRECT_SLOT] } },
'url': { get: function() { return this[URL_SLOT] } },
'signal': { get: function() { return this[SIGNAL_SLOT] } }
})
function parseHeaders(rawHeaders, guardCallback) {
var headers = CreateHeadersWithGuard(undefined, guardCallback)
// Replace instances of \r\n and \n followed by at least one space or
// horizontal tab with a space.
// https://tools.ietf.org/html/rfc7230#section-3.2
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ')
preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
var parts = line.split(':')
var key = parts.shift().trim()
if (key) {
var value = parts.join(':').trim()
headers.append(key, value)
}
})
return headers
}
Body.call(Request.prototype)
// Status text must be a reason-phrase token.
// https://tools.ietf.org/html/rfc7230#section-3.1.2
function parseStatusText(text) {
for (var i = 0, len = text.length, c; i < len; i++) {
c = text.charCodeAt(i)
if (c !== 9 && (c < 32 || c > 255 || c === 127)) {
throw TypeError('Invalid response status text')
}
}
return text
}
// https://fetch.spec.whatwg.org/#response-class
function Response(body, init) {
if (!init) {
init = {}
}
this[TYPE_SLOT] = 'default'
this[STATUS_SLOT] = 'status' in init ? init.status : 200
if (this[STATUS_SLOT] < 200 || this[STATUS_SLOT] > 599) {
throw new RangeError('Invalid response status')
}
this[OK_SLOT] = this[STATUS_SLOT] >= 200 && this[STATUS_SLOT] < 300
this[STATUS_TEXT_SLOT] = 'statusText' in init ?
parseStatusText(init.statusText) : 'OK'
this[HEADERS_SLOT] =
CreateHeadersWithGuard(init.headers, guardResponseCallback)
this[URL_SLOT] = init.url || ''
if (body && NULL_BODY_STATUSES.indexOf(this[STATUS_SLOT]) > -1) {
throw new TypeError(
'Response body is not allowed with a null body status')
}
this[IS_ABORTED_SLOT] = init.is_aborted || false
this._initBody(body)
}
Body.call(Response.prototype)
Response.prototype.clone = function() {
var cloneBody = null
if (this[BODY_SLOT] !== null) {
var streams = ReadableStreamTee(this[BODY_SLOT],
true /* cloneForBranch2 */)
this[BODY_SLOT] = streams[0]
cloneBody = streams[1]
}
return new Response(cloneBody, {
status: this[STATUS_SLOT],
statusText: this[STATUS_TEXT_SLOT],
headers: CreateHeadersWithGuard(this[HEADERS_SLOT],
guardResponseCallback),
url: this[URL_SLOT],
is_aborted: this[IS_ABORTED_SLOT]
})
}
defineProperties(Response.prototype, {
'headers': { get: function() { return this[HEADERS_SLOT] } },
'ok': { get: function() { return this[OK_SLOT] } },
'status': { get: function() { return this[STATUS_SLOT] } },
'statusText': { get: function() { return this[STATUS_TEXT_SLOT] } },
'type': { get: function() { return this[TYPE_SLOT] } },
'url': { get: function() { return this[URL_SLOT] } }
})
Response.error = function() {
var response = new Response(null)
response[HEADERS_SLOT][GUARD_CALLBACK_SLOT] = guardImmutableCallback
response[TYPE_SLOT] = 'error'
response[STATUS_SLOT] = 0
response[STATUS_TEXT_SLOT] = ''
return response
}
Response.redirect = function(url, status) {
if (!FetchInternal.isUrlValid(url, true /* allowCredentials */)) {
throw new TypeError('Invalid URL for response redirect')
}
if (status === undefined) {
status = 302
}
if (REDIRECT_STATUSES.indexOf(status) === -1) {
throw new RangeError('Invalid status code for response redirect')
}
return new Response(null, {status: status, headers: {location: url}})
}
self.Headers = Headers
self.Request = Request
self.Response = Response
// Algorithm steps for https://fetch.spec.whatwg.org/#fetch-method
self.fetch = function(input, init) {
// 1 Let p be a new promise.
// ..
// 10. Return p
return new Promise(function(resolve, reject) {
var cancelled = false
var isCORSMode = false
// 2. Let requestObject be the result of invoking the initial value of Request
// as constructor
// 3. Let request be requestObjects request.
var request = new Request(input, init)
var xhr = new XMLHttpRequest()
var responseStreamController = null
// 4. If requestObjects signals aborted flag is set, then:
// Abort fetch with p, request, and null.
// Return p.
if (request.signal.aborted) {
return reject(new DOMException(ABORT_MESSAGE, ABORT_ERROR))
}
// 5. If requests clients global object is a ServiceWorkerGlobalScope object,
// then set requests service-workers mode to "none".
// This step is skipped, as serviceWorkers are not supported
var responseStream = new ReadableStream({
start(controller) {
responseStreamController = controller
},
cancel(controller) {
cancelled = true
xhr.abort()
}
})
var handleAbort = function() {
if (!cancelled) {
cancelled = true
responseStream.cancel()
if(responseStreamController) {
try {
ReadableStreamDefaultControllerError(responseStreamController,
new DOMException(ABORT_MESSAGE, ABORT_ERROR))
} catch(_) {}
}
setTimeout(function() {
try {
xhr.abort()
} catch(_) {}
}, 0)
}
}
xhr.onload = function() {
responseStreamController.close()
}
xhr.onreadystatechange = function() {
if (xhr.readyState === xhr.HEADERS_RECEIVED) {
var init = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '',
guardResponseCallback)
}
init.url = 'responseURL' in xhr ?
xhr.responseURL : init.headers.get('X-Request-URL')
try {
// 6. Let responseObject be a new Response object
var response = new Response(responseStream, init)
// 7. Let locallyAborted be false - done in response constructor
request[SIGNAL_SLOT].addEventListener('abort',() => {
// 8.1 Set locallyAborted to true.
// 8.2 Abort fetch with p, request, and responseObject.
// 8.3 Terminate the ongoing fetch with the aborted flag set.
response[IS_ABORTED_SLOT] = true
handleAbort()
reject(new DOMException(ABORT_MESSAGE, ABORT_ERROR))
})
response[TYPE_SLOT] = isCORSMode ? 'cors' : 'basic'
resolve(response)
} catch (err) {
reject(err)
}
}
}
// 9. Run the following in parallel:
// Fetch request.
// To process response for response, run these substeps:
// 9.1 If locallyAborted is true, terminate these substeps
// 9.2 If responses aborted flag is set, then abort fetch
// - Above are handled in responseBody methods
xhr.onerror = function() {
// 9.3 If response is a network error, then reject p with a TypeError and terminate these substeps.
responseStreamController.error(
new TypeError(ERROR_NETWORK_REQUEST_FAILED))
reject(new TypeError(ERROR_NETWORK_REQUEST_FAILED))
}
xhr.ontimeout = function() {
responseStreamController.error(
new TypeError(ERROR_NETWORK_REQUEST_FAILED))
reject(new TypeError(ERROR_NETWORK_REQUEST_FAILED))
}
xhr.open(request.method, request.url, true)
if (request.credentials === 'include') {
xhr.withCredentials = true
}
request.headers.forEach(function(value, name) {
xhr.setRequestHeader(name, value)
})
var fetchUpdate = function(data) {
if (!cancelled) {
// Data is already a Uint8Array. Enqueue a reference to this, rather
// than make a copy of it, since caller no longer references it.
responseStreamController.enqueue(data)
}
}
var modeUpdate = function(iscorsmode) {
isCORSMode = iscorsmode
}
if (request.body === null) {
xhr.fetch(fetchUpdate, modeUpdate, null)
} else {
consumeBodyAsUint8Array(request).then(function(data) {
xhr.fetch(fetchUpdate, modeUpdate, data)
})
}
})
}
self.fetch.polyfill = true
})(this);