|
@@ -1,13 +1,7 @@
|
|
|
import { isFunction } from '#/common';
|
|
|
-import { NS_HTML, createNullObj, getOwnProp, getUniqIdSafe, log, pickIntoThis } from '../util';
|
|
|
import bridge from './bridge';
|
|
|
|
|
|
const idMap = createNullObj();
|
|
|
-const { DOMParser, FileReader, Response } = global;
|
|
|
-const { parseFromString } = DOMParser[PROTO];
|
|
|
-const { blob: resBlob } = Response[PROTO];
|
|
|
-const { get: getHref } = describeProperty(HTMLAnchorElement[PROTO], 'href');
|
|
|
-const { readAsDataURL } = FileReader[PROTO];
|
|
|
|
|
|
bridge.addHandlers({
|
|
|
HttpRequested(msg) {
|
|
@@ -16,7 +10,7 @@ bridge.addHandlers({
|
|
|
},
|
|
|
});
|
|
|
|
|
|
-export function onRequestCreate(opts, context) {
|
|
|
+export function onRequestCreate(opts, context, fileName) {
|
|
|
if (!opts.url) throw new ErrorSafe('Required parameter "url" is missing.');
|
|
|
const scriptId = context.id;
|
|
|
const id = getUniqIdSafe(`VMxhr${scriptId}`);
|
|
@@ -26,7 +20,7 @@ export function onRequestCreate(opts, context) {
|
|
|
scriptId,
|
|
|
opts,
|
|
|
};
|
|
|
- start(req, context);
|
|
|
+ start(req, context, fileName);
|
|
|
return {
|
|
|
abort() {
|
|
|
bridge.post('AbortRequest', id, context);
|
|
@@ -35,33 +29,17 @@ export function onRequestCreate(opts, context) {
|
|
|
}
|
|
|
|
|
|
function parseData(req, msg) {
|
|
|
- let res;
|
|
|
- const { raw, opts: { responseType } } = req;
|
|
|
- if (responseType === 'text') {
|
|
|
- res = raw;
|
|
|
- } else if (responseType === 'json') {
|
|
|
- res = jsonParse(raw);
|
|
|
- } else if (responseType === 'document') {
|
|
|
- // Cutting everything after , or ; and trimming whitespace
|
|
|
- const type = msg.contentType::replace(/[,;].*|\s+/g, '') || 'text/html';
|
|
|
- res = new DOMParser()::parseFromString(raw, type);
|
|
|
- } else if (msg.chunked) {
|
|
|
- // arraybuffer/blob in incognito tabs is transferred as ArrayBuffer encoded in string chunks
|
|
|
- // TODO: move this block in content if the speed is the same for very big data
|
|
|
- const arr = new Uint8ArraySafe(req.dataSize);
|
|
|
- let dstIndex = 0;
|
|
|
- raw::forEach((chunk) => {
|
|
|
- const len = (chunk = window::atobSafe(chunk)).length;
|
|
|
- for (let j = 0; j < len; j += 1, dstIndex += 1) {
|
|
|
- arr[dstIndex] = chunk::charCodeAt(j);
|
|
|
- }
|
|
|
- });
|
|
|
- res = responseType === 'blob'
|
|
|
- ? new BlobSafe([arr], { type: msg.contentType })
|
|
|
- : arr.buffer;
|
|
|
- } else {
|
|
|
- // text, blob, arraybuffer
|
|
|
- res = raw;
|
|
|
+ let res = req.raw;
|
|
|
+ switch (req.opts.responseType) {
|
|
|
+ case 'json':
|
|
|
+ res = jsonParse(res);
|
|
|
+ break;
|
|
|
+ case 'document':
|
|
|
+ res = new DOMParserSafe()::parseFromString(res,
|
|
|
+ // Cutting everything after , or ; and trimming whitespace
|
|
|
+ msg.contentType::replace(/[,;].*|\s+/g, '') || 'text/html');
|
|
|
+ break;
|
|
|
+ default:
|
|
|
}
|
|
|
// `response` is sent only when changed so we need to remember it for response-less events
|
|
|
req.response = res;
|
|
@@ -71,16 +49,9 @@ function parseData(req, msg) {
|
|
|
}
|
|
|
|
|
|
// request object functions
|
|
|
-async function callback(req, msg) {
|
|
|
- if (msg.chunk) {
|
|
|
- receiveChunk(req, msg.chunk);
|
|
|
- return;
|
|
|
- }
|
|
|
- const { chunksPromise, opts } = req;
|
|
|
+function callback(req, msg) {
|
|
|
+ const { opts } = req;
|
|
|
const cb = opts[`on${msg.type}`];
|
|
|
- if (chunksPromise) {
|
|
|
- await chunksPromise;
|
|
|
- }
|
|
|
if (cb) {
|
|
|
const { data } = msg;
|
|
|
const {
|
|
@@ -89,15 +60,9 @@ async function callback(req, msg) {
|
|
|
responseText: text,
|
|
|
} = data;
|
|
|
if (response && !('raw' in req)) {
|
|
|
- req.raw = msg.chunked
|
|
|
- ? receiveAllChunks(req, response, msg)
|
|
|
- : response;
|
|
|
- }
|
|
|
- if (req.raw?.then) {
|
|
|
- req.raw = await req.raw;
|
|
|
+ req.raw = response;
|
|
|
}
|
|
|
defineProperty(data, 'response', {
|
|
|
- configurable: true,
|
|
|
get() {
|
|
|
const value = 'raw' in req ? parseData(req, msg) : req.response;
|
|
|
defineProperty(this, 'response', { value });
|
|
@@ -115,31 +80,7 @@ async function callback(req, msg) {
|
|
|
if (msg.type === 'loadend') delete idMap[req.id];
|
|
|
}
|
|
|
|
|
|
-function receiveAllChunks(req, response, { dataSize, numChunks }) {
|
|
|
- let res = [response];
|
|
|
- req.dataSize = dataSize;
|
|
|
- if (numChunks > 1) {
|
|
|
- req.chunks = res;
|
|
|
- req.chunksPromise = new PromiseSafe(resolve => {
|
|
|
- req.resolve = resolve;
|
|
|
- });
|
|
|
- res = req.chunksPromise;
|
|
|
- }
|
|
|
- return res;
|
|
|
-}
|
|
|
-
|
|
|
-function receiveChunk(req, { data, i, last }) {
|
|
|
- const { chunks } = req;
|
|
|
- chunks[i] = data;
|
|
|
- if (last) {
|
|
|
- req.resolve(chunks);
|
|
|
- delete req.chunksPromise;
|
|
|
- delete req.chunks;
|
|
|
- delete req.resolve;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-async function start(req, context) {
|
|
|
+async function start(req, context, fileName) {
|
|
|
const { id, opts, scriptId } = req;
|
|
|
// withCredentials is for GM4 compatibility and used only if `anonymous` is not set,
|
|
|
// it's true by default per the standard/historical behavior of gmxhr
|
|
@@ -150,6 +91,7 @@ async function start(req, context) {
|
|
|
id,
|
|
|
scriptId,
|
|
|
anonymous,
|
|
|
+ fileName,
|
|
|
data: data == null && []
|
|
|
// `binary` is for TM/GM-compatibility + non-objects = must use a string `data`
|
|
|
|| (opts.binary || typeof data !== 'object') && [`${data}`]
|
|
@@ -168,7 +110,6 @@ async function start(req, context) {
|
|
|
'timeout',
|
|
|
]::filter(key => isFunction(getOwnProp(opts, `on${key}`))),
|
|
|
responseType: getResponseType(opts),
|
|
|
- url: getFullUrl(opts.url),
|
|
|
wantsBlob: opts.responseType === 'blob',
|
|
|
}::pickIntoThis(opts, [
|
|
|
'headers',
|
|
@@ -176,16 +117,11 @@ async function start(req, context) {
|
|
|
'overrideMimeType',
|
|
|
'password',
|
|
|
'timeout',
|
|
|
+ 'url',
|
|
|
'user',
|
|
|
]), context);
|
|
|
}
|
|
|
|
|
|
-function getFullUrl(url) {
|
|
|
- const a = document::createElementNS(NS_HTML, 'a');
|
|
|
- a::setAttribute('href', url);
|
|
|
- return a::getHref();
|
|
|
-}
|
|
|
-
|
|
|
function getResponseType({ responseType = '' }) {
|
|
|
switch (responseType) {
|
|
|
case 'arraybuffer':
|
|
@@ -203,15 +139,19 @@ function getResponseType({ responseType = '' }) {
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
-/** Polyfill for Chrome's inability to send complex types over extension messaging */
|
|
|
+/**
|
|
|
+ * Polyfill for Chrome's inability to send complex types over extension messaging.
|
|
|
+ * We're encoding the body here, not in content, because we want to support FormData
|
|
|
+ * and ReadableStream, which Chrome can't transfer to isolated world via CustomEvent.
|
|
|
+ */
|
|
|
async function encodeBody(body) {
|
|
|
const wasBlob = body::objectToString() === '[object Blob]';
|
|
|
- const blob = wasBlob ? body : await new Response(body)::resBlob();
|
|
|
- const reader = new FileReader();
|
|
|
- return new PromiseSafe((resolve) => {
|
|
|
+ const blob = wasBlob ? body : await new ResponseSafe(body)::safeResponseBlob();
|
|
|
+ const reader = new FileReaderSafe();
|
|
|
+ return new PromiseSafe(resolve => {
|
|
|
reader::on('load', () => resolve([
|
|
|
- reader.result,
|
|
|
- blob.type,
|
|
|
+ reader::getReaderResult(),
|
|
|
+ blob::getBlobType(),
|
|
|
wasBlob,
|
|
|
]));
|
|
|
reader::readAsDataURL(blob);
|