Dactyloidae/browser/internaluserscripts/bundled-scripts/readablestream-pipeto-polyfill.user.js
2026-07-13 08:48:40 -07:00

221 lines
6 KiB
JavaScript

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// ==UserScript==
// @name ReadableStream pipeTo Polyfill (best-effort)
// @namespace internal-userscripts
// @description Adds ReadableStream.prototype.pipeTo using reader/writer pumping.
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
"use strict";
var global = typeof window !== "undefined" ? window : null;
if (!global || !global.ReadableStream || !global.ReadableStream.prototype) {
return;
}
var PromiseCtor = global.Promise;
if (typeof PromiseCtor !== "function") {
return;
}
function isObject(value) {
return value !== null && (typeof value === "object" || typeof value === "function");
}
var existingPipeTo = global.ReadableStream.prototype.pipeTo;
if (typeof existingPipeTo === "function") {
if (typeof global.TransformStream !== "function" &&
typeof global.WritableStream !== "function") {
return;
}
try {
var testWritable = null;
if (typeof global.TransformStream === "function") {
testWritable = new global.TransformStream().writable;
} else {
testWritable = new global.WritableStream();
}
var probeStream = new global.ReadableStream({
start: function (controller) {
controller.close();
}
});
probeStream.pipeTo(testWritable, { preventClose: true });
return;
} catch (e) {
if (String(e).indexOf("not yet implemented") === -1) {
return;
}
}
}
function pipeTo(writable, options) {
if (!isObject(this)) {
throw new TypeError("ReadableStream.prototype.pipeTo called on incompatible receiver");
}
if (!isObject(writable) || typeof writable.getWriter !== "function") {
throw new TypeError("pipeTo requires a writable stream");
}
var source = this;
var reader = source.getReader();
var writer = writable.getWriter();
var preventClose = options && options.preventClose;
var preventAbort = options && options.preventAbort;
var preventCancel = options && options.preventCancel;
var signal = options && options.signal;
var abortHandler = null;
var abortReject = null;
var aborted = false;
var abortReason = undefined;
function safeReleaseLock(stream, maybeReader, lockedProp) {
if (!maybeReader) {
return;
}
var isLocked = true;
if (typeof lockedProp === "function") {
try {
var lockedValue = lockedProp();
if (typeof lockedValue === "boolean") {
isLocked = lockedValue;
}
} catch (e) {
isLocked = true;
}
}
if (!isLocked) {
return;
}
var releaseFn = null;
try {
releaseFn = maybeReader.releaseLock;
} catch (e) {
releaseFn = null;
}
if (typeof releaseFn === "function") {
try { releaseFn.call(maybeReader); } catch (e) {}
}
}
function releaseLocks() {
safeReleaseLock(source, reader, function () { return source.locked; });
safeReleaseLock(writable, writer, function () { return writable.locked; });
reader = null;
writer = null;
}
function cleanupAbort() {
if (abortHandler && signal && typeof signal.removeEventListener === "function") {
signal.removeEventListener("abort", abortHandler);
}
}
function abortWith(reason) {
if (aborted) {
return;
}
aborted = true;
abortReason = reason;
if (!preventCancel) {
try { reader.cancel(reason); } catch (e) {}
}
if (!preventAbort) {
try { writer.abort(reason); } catch (e) {}
}
if (abortReject) {
abortReject(reason);
}
}
var abortPromise = null;
if (signal) {
abortPromise = new PromiseCtor(function (_resolve, reject) {
abortReject = reject;
});
abortHandler = function () {
abortWith(signal.reason);
};
if (signal.aborted) {
abortWith(signal.reason);
} else if (typeof signal.addEventListener === "function") {
signal.addEventListener("abort", abortHandler);
}
}
function pump() {
if (aborted) {
return PromiseCtor.reject(abortReason);
}
var readPromise;
try {
readPromise = reader.read();
} catch (e) {
return PromiseCtor.reject(e);
}
return readPromise.then(function (result) {
if (aborted) {
throw abortReason;
}
if (result.done) {
if (!preventClose) {
return writer.close();
}
return undefined;
}
var writePromise;
try {
writePromise = writer.write(result.value);
} catch (e) {
return PromiseCtor.reject(e);
}
return PromiseCtor.resolve(writePromise).then(pump);
});
}
var pipePromise = pump().catch(function (err) {
if (!preventCancel) {
try { reader.cancel(err); } catch (e) {}
}
if (!preventAbort) {
try { writer.abort(err); } catch (e) {}
}
throw err;
});
var finalPromise = abortPromise
? PromiseCtor.race([pipePromise, abortPromise])
: pipePromise;
function settleAndRelease(err) {
return pipePromise.catch(function () {}).then(function () {
cleanupAbort();
releaseLocks();
if (err !== undefined) {
throw err;
}
return undefined;
});
}
return finalPromise.then(function () {
return settleAndRelease();
}, function (err) {
return settleAndRelease(err);
});
}
Object.defineProperty(global.ReadableStream.prototype, "pipeTo", {
value: pipeTo,
writable: true,
configurable: true
});
try {
global.__internalUserscriptsReadableStreamPipeToPolyfill = true;
} catch (e) {}
})();