<% if (type === 'crx') { %>
eventPageRequest = do ->
  callbacks = []
  chrome.runtime.onMessage.addListener (response) ->
    callbacks[response.id] response.data
    delete callbacks[response.id]
  (params, cb) ->
    chrome.runtime.sendMessage params, (id) ->
      callbacks[id] = cb

<% } %>
CrossOrigin =
  binary: (url, cb, headers=$.dict()) ->
    # XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
    url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'
    <% if (type === 'crx') { %>
    eventPageRequest {type: 'ajax', url, headers, responseType: 'arraybuffer'}, ({response, responseHeaderString}) ->
      response = new Uint8Array(response) if response
      cb response, responseHeaderString
    <% } %>
    <% if (type === 'userscript') { %>
    fallback = ->
      $.ajax url, {
        headers
        responseType: 'arraybuffer'
        onloadend: ->
          if @status and @response
            cb new Uint8Array(@response), @getAllResponseHeaders()
          else
            cb null
      }
    unless GM?.xmlHttpRequest? or GM_xmlhttpRequest?
      fallback()
      return
    gmOptions =
      method: "GET"
      url: url
      headers: headers
      responseType: 'arraybuffer'
      overrideMimeType: 'text/plain; charset=x-user-defined'
      onload: (xhr) ->
        if xhr.response instanceof ArrayBuffer
          data = new Uint8Array xhr.response
        else
          r = xhr.responseText
          data = new Uint8Array r.length
          i = 0
          while i < r.length
            data[i] = r.charCodeAt i
            i++
        cb data, xhr.responseHeaders
      onerror: ->
        cb null
      onabort: ->
        cb null
    try
      (GM?.xmlHttpRequest or GM_xmlhttpRequest) gmOptions
    catch
      fallback()
    <% } %>

  file: (url, cb) ->
    CrossOrigin.binary url, (data, headers) ->
      return cb null unless data?
      name = url.match(/([^\/?#]+)\/*(?:$|[?#])/)?[1]
      contentType        = headers.match(/Content-Type:\s*(.*)/i)?[1]
      contentDisposition = headers.match(/Content-Disposition:\s*(.*)/i)?[1]
      mime = contentType?.match(/[^;]*/)[0] or 'application/octet-stream'
      match =
        contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?[1] or
        contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?[1]
      if match
        name = match.replace /\\"/g, '"'
      if /^text\/plain;\s*charset=x-user-defined$/i.test(mime)
        # In JS Blocker (Safari) content type comes back as 'text/plain; charset=x-user-defined'; guess from filename instead.
        mime = $.getOwn(QR.typeFromExtension, name.match(/[^.]*$/)[0].toLowerCase()) or 'application/octet-stream'
      blob = new Blob([data], {type: mime})
      blob.name = name
      cb blob

  Request: class Request
    status: 0
    statusText: ''
    response: null
    responseHeaderString: null
    getResponseHeader: (headerName) ->
      if !@responseHeaders? and @responseHeaderString?
        @responseHeaders = $.dict()
        for header in @responseHeaderString.split('\r\n')
          if (i = header.indexOf(':')) >= 0
            key = header[...i].trim().toLowerCase()
            val = header[i+1..].trim()
            @responseHeaders[key] = val
      @responseHeaders?[headerName.toLowerCase()] ? null
    abort: ->
    onloadend: ->

  # Attempts to fetch `url` using cross-origin privileges, if available.
  # Interface is a subset of that of $.ajax.
  # Options:
  #   `onloadend` - called with the returned object as `this` on success or error/abort/timeout.
  #   `timeout` - time limit for request
  #   `responseType` - expected response type, 'json' by default; 'json' and 'text' supported
  #   `headers` - request headers
  # Returned object properties:
  #   `status` - HTTP status (0 if connection not successful)
  #   `statusText` - HTTP status text
  #   `response` - decoded response body
  #   `abort` - function for aborting the request (silently fails on some platforms)
  #   `getResponseHeader` - function for reading response headers
  ajax: (url, options={}) ->
    {onloadend, timeout, responseType, headers} = options
    responseType ?= 'json'

    <% if (type === 'userscript') { %>
    unless GM?.xmlHttpRequest? or GM_xmlhttpRequest?
      return $.ajax url, options
    <% } %>

    req = new CrossOrigin.Request()
    req.onloadend = onloadend

    <% if (type === 'userscript') { %>
    gmOptions = {
      method: 'GET'
      url
      headers
      timeout
      onload: (xhr) ->
        try
          response = switch responseType
            when 'json'
              if xhr.responseText then JSON.parse(xhr.responseText) else null
            else
              xhr.responseText
          $.extend req, {
            response
            status: xhr.status
            statusText: xhr.statusText
            responseHeaderString: xhr.responseHeaders
          }
        req.onloadend()
      onerror:   -> req.onloadend()
      onabort:   -> req.onloadend()
      ontimeout: -> req.onloadend()
    }
    try
      gmReq = (GM?.xmlHttpRequest or GM_xmlhttpRequest) gmOptions
    catch
      return $.ajax url, options

    if gmReq and typeof gmReq.abort is 'function'
      req.abort = ->
        try
          gmReq.abort()
    <% } %>

    <% if (type === 'crx') { %>
    eventPageRequest {type: 'ajax', url, responseType, headers, timeout}, (result) ->
      if result.status
        $.extend req, result
      req.onloadend()
    <% } %>

    req

  cache: (url, cb) ->
    $.cache url, cb,
      ajax: CrossOrigin.ajax

  <% if (type === 'crx') { %>
  permission: (cb, cbFail, origins) ->
    eventPageRequest {type: 'permission', origins}, (result) ->
      if result
        cb()
      else
        cbFail()
  <% } %>
  <% if (type === 'userscript') { %>
  permission: (cb) ->
    cb()
  <% } %>
