filterRequest_(type, request, context) {
    let filterOperation = shaka.util.AbortableOperation.completed(undefined);

    const applyFilter = (requestFilter) => {
      filterOperation = filterOperation.chain(() => {
        if (request.body) {
          // TODO: For v4.0 we should remove this or change to always pass a
          // Uint8Array.  To make it easier for apps to write filters, it may be
          // better to always pass a Uint8Array so they know what they are
          // getting; but we shouldn't use ArrayBuffer since that would require
          // copying buffers if this is a partial view.
          request.body = shaka.util.BufferUtils.toArrayBuffer(request.body);
        }
        return requestFilter(type, request, context);
      });
    };

    if (this.onRequest_) {
      applyFilter(this.onRequest_);
    }

    for (const requestFilter of this.requestFilters_) {
      // Request filters are run sequentially.
      applyFilter(requestFilter);
    }

    // Catch any errors thrown by request filters, and substitute
    // them with a Shaka-native error.
    return filterOperation.chain(undefined, (e) => {
      if (e instanceof shaka.util.Error &&
          e.code == shaka.util.Error.Code.OPERATION_ABORTED) {
        // Don't change anything if the operation was aborted.
        throw e;
      }

      throw new shaka.util.Error(
          shaka.util.Error.Severity.CRITICAL,
          shaka.util.Error.Category.NETWORK,
          shaka.util.Error.Code.REQUEST_FILTER_ERROR, e);
    });
  }
@param {shaka.net.NetworkingEngine.RequestType} type
@param {shaka.extern.Request} request
@param {shaka.extern.RequestContext=} context
@return {!shaka.util.AbortableOperation.<undefined>}
@private