docs: function splitUserAgent(str) {
    str = str.trim();
    var uaList = [];
    var tokens = '';
    // Split by spaces, while keeping top level parentheses intact, so
    // "Mozilla/5.0 (Linux; Android 6.0.1) Mobile Safari/537.36" becomes
    // ['Mozilla/5.0', '(Linux; Android 6.0.1)', 'Mobile', 'Safari/537.36']
    var parensNesting = 0;
    for(var i = 0; i < str.length; ++i) {
      if (str[i] == ' ' && parensNesting == 0) {
        if (tokens.trim().length != 0) uaList.push(tokens.trim());
        tokens = '';
      } else if (str[i] == '(') ++parensNesting;
      else if (str[i] == ')') --parensNesting;
      tokens = tokens + str[i];
    }
    if (tokens.trim().length > 0) uaList.push(tokens.trim());
  
    // What follows is a number of heuristic adaptations to account for UA strings met in the wild:
  
    // Fuse ['a/ver', '(someinfo)'] together. For example:
    // 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MOB30M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile Safari/537.36'
    // -> fuse 'AppleWebKit/537.36' and '(KHTML, like Gecko)' together
    for(var i = 1; i < uaList.length; ++i) {
      var l = uaList[i];
      if (isEnclosedInParens(l) && !contains(l, ';') && i > 1) {
        uaList[i-1] = uaList[i-1] + ' ' + l;
        uaList[i] = '';
      }
    }
    uaList = removeEmptyElements(uaList);
  
    // Fuse ['foo', 'bar/ver'] together, if 'foo' has only ascii chars. For example:
    // 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MOB30M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile Safari/537.36'
    // -> fuse ['Mobile', 'Safari/537.36'] together
    for(var i = 0; i < uaList.length-1; ++i) {
      var l = uaList[i];
      var next = uaList[i+1];
      if (/^[a-zA-Z]+$/.test(l) && contains(next, '/')) {
        uaList[i+1] = l + ' ' + next;
        uaList[i] = '';
      }
    }
    uaList = removeEmptyElements(uaList);
    return uaList;
  }