// 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 pipeThrough Polyfill (best-effort)
// @namespace    internal-userscripts
// @description  Adds ReadableStream.prototype.pipeThrough using pipeTo or reader/writer pumping when missing.
// @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 existingPipeThrough = global.ReadableStream.prototype.pipeThrough;
  if (typeof existingPipeThrough === "function") {
    if (typeof global.TransformStream !== "function") {
      return;
    }
    try {
      var probeStream = new global.ReadableStream({
        start: function (controller) {
          controller.close();
        }
      });
      probeStream.pipeThrough(new global.TransformStream(), { preventClose: true });
      return;
    } catch (e) {
      if (String(e).indexOf("not yet implemented") === -1) {
        return;
      }
    }
  }

  var PromiseCtor = global.Promise;
  if (typeof PromiseCtor !== "function") {
    return;
  }

  function isObject(value) {
    return value !== null && (typeof value === "object" || typeof value === "function");
  }

  function getTransformStream(transform, name) {
    var stream = transform[name];
    if (!isObject(stream)) {
      throw new TypeError("pipeThrough requires a transform stream with " + name);
    }
    return stream;
  }

  function startPipeWithReader(source, writable, options) {
    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;

    function cleanupAbort() {
      if (abortHandler && signal && typeof signal.removeEventListener === "function") {
        signal.removeEventListener("abort", abortHandler);
      }
    }

    if (signal) {
      abortHandler = function () {
        var reason = signal.reason;
        if (!preventCancel) {
          try { reader.cancel(reason); } catch (e) {}
        }
        if (!preventAbort) {
          try { writer.abort(reason); } catch (e) {}
        }
      };
      if (signal.aborted) {
        abortHandler();
        cleanupAbort();
        return;
      }
      if (typeof signal.addEventListener === "function") {
        signal.addEventListener("abort", abortHandler);
      }
    }

    function pump() {
      return reader.read().then(function (result) {
        if (result.done) {
          if (!preventClose) {
            return writer.close();
          }
          return undefined;
        }
        return PromiseCtor.resolve(writer.write(result.value)).then(pump);
      });
    }

    pump().catch(function (err) {
      if (!preventCancel) {
        try { reader.cancel(err); } catch (e) {}
      }
      if (!preventAbort) {
        try { writer.abort(err); } catch (e) {}
      }
    }).then(cleanupAbort, cleanupAbort);
  }

  function pipeThrough(transform, options) {
    if (!isObject(this)) {
      throw new TypeError("ReadableStream.prototype.pipeThrough called on incompatible receiver");
    }
    if (!isObject(transform)) {
      throw new TypeError("pipeThrough requires a transform stream");
    }
    var readable = getTransformStream(transform, "readable");
    var writable = getTransformStream(transform, "writable");
    var source = this;

    if (typeof source.pipeTo === "function") {
      var pipePromise = source.pipeTo(writable, options);
      if (pipePromise && typeof pipePromise.catch === "function") {
        pipePromise.catch(function () {});
      }
    } else if (typeof source.getReader === "function" && typeof writable.getWriter === "function") {
      startPipeWithReader(source, writable, options);
    } else {
      throw new TypeError("ReadableStream.prototype.pipeThrough is not supported");
    }

    return readable;
  }

  Object.defineProperty(global.ReadableStream.prototype, "pipeThrough", {
    value: pipeThrough,
    writable: true,
    configurable: true
  });

  try {
    global.__internalUserscriptsReadableStreamPipeThroughPolyfill = true;
  } catch (e) {}
})();
