async function queryAutocomplete (bg, ctx, onResults) {
  var queryId = ++ctx.queryIdCounter
  var finalResults
  
  var searchEngines = await ctx.searchEnginesPromise
  var searchEngine = searchEngines.find(se => se.selected) || searchEngines[0]
  var {vWithProtocol, vSearch, isProbablyUrl, isGuessingTheScheme} = examineLocationInput(ctx.inputValue || '/')

  // optimistically set the input-based results to ensure responsiveness
  {
    if (ctx.results) ctx.results = ctx.results.filter(r => !r.search && !r.isGoto)
    ctx.results = ctx.results || []
    let gotoResult = { url: vWithProtocol, title: 'Go to ' + (ctx.inputValue || '/'), isGuessingTheScheme, isGoto: true }
    let searchResult = {
      search: ctx.inputValue,
      title: `Search ${searchEngine.name} for "${ctx.inputValue}"`,
      url: searchEngine.url + vSearch
    }
    if (ctx.inputValue.includes(' ')) ctx.results.unshift(searchResult)
    else if (isProbablyUrl) ctx.results = [gotoResult, searchResult].concat(ctx.results)
    else ctx.results = [searchResult, gotoResult].concat(ctx.results)
    onResults(true)
  }
  
  var [historyResults, bookmarks] = await Promise.all([
    ctx.inputValue ? bg.history.search(ctx.inputValue) : [],
    ctx.bookmarksFetch
  ])

  // abort if changes to the input have occurred since triggering these queries
  if (queryId !== ctx.queryIdCounter) return

  // decorate results with bolded regions
  var searchTerms = ctx.inputValue.replace(/[:^*-./]/g, ' ').split(' ').filter(Boolean)
  var searchTermsRe = new RegExp(`(${searchTerms.join('|')})`, 'gi')
  historyResults.forEach(r => highlightHistoryResult(searchTerms, r))

  if (ctx.inputValue) {
    finalResults = _uniqWith(historyResults, (a, b) => normalizeURL(a.url) === normalizeURL(b.url)) // remove duplicates
    finalResults = finalResults.slice(0, 10) // apply limit
  } else {
    finalResults = bookmarks
  }

  // see if we have any URL guesses
  // we only do this if the input changed, in case the user deleted our suggested guess
  ctx.urlGuess = undefined
  if (ctx.lastInputValue !== ctx.inputValue) {
    for (let res of finalResults) {
      let start = 0
      if (!ctx.inputValue.includes('://')) {
        start = res.url.indexOf('://') + 3 // skip the scheme
      }
      if (!ctx.inputValue.includes('www.') && res.url.slice(start).startsWith('www.')) {
        start += 4 // skip the www.
      }
      let index = res.url.indexOf(ctx.inputValue, start)
      if (index === start) {
        // match, guess up to the next path segment
        let nextSlashIndex = res.url.indexOf('/', start + ctx.inputValue.length)
        ctx.urlGuess = {
          input: res.url.slice(start, nextSlashIndex === -1 ? undefined : nextSlashIndex),
          url: nextSlashIndex === -1 ? res.url : res.url.slice(0, nextSlashIndex)
        }
        break
      }
    }
  }

  // set the top results accordingly
  var gotoResult
  var searchResult = {
    search: ctx.inputValue,
    title: `Search ${searchEngine.name} for "${ctx.inputValue}"`,
    url: searchEngine.url + vSearch
  }
  if (ctx.urlGuess) {
    gotoResult = { url: ctx.urlGuess.url, title: 'Go to ' + (ctx.urlGuess.input), isGuessingTheScheme: false, isGoto: true }
    isProbablyUrl = true
  } else {
    gotoResult = { url: vWithProtocol, title: 'Go to ' + (ctx.inputValue || '/'), isGuessingTheScheme, isGoto: true }
  }
  if (ctx.inputValue.includes(' ')) finalResults = [searchResult].concat(finalResults)
  else if (isProbablyUrl) finalResults = [gotoResult, searchResult].concat(finalResults)
  else finalResults = [searchResult, gotoResult].concat(finalResults)

  // render
  ctx.results = finalResults
  ctx.lastInputValue = ctx.inputValue
  onResults()
}
Used by ../shell-window/navbar/location.js
Mainly put here to keep that file from growing too large

@param {Object} bg 
@param {Object} ctx 
@param {Function} onResults