If you are unwilling to make assumptions about property names/structure (totally reasonable), then you are basically left with querying the Autocomplete object itself for the object that has the property className:"pac-container" (or some other identifying feature).
There are libraries that can help with this, such as JSONPath, JSONQuery, and many more that could help you do this. Without adding any additional libraries, though, we can roll our own breadth-first-search through the Autocomplete object's nested hierarchy with the caveat that I am not a Javascript expert, and that while this works right now, I can't promise that I didn't miss an edge case that will break this in the future.
Unfortunately, due to the timing issue you pointed out, you will need to run this at some point after the initial instantiation of the Autocomplete object. The good news is that if you don't immediately need the pac-container object reference, you can defer finding it until you need it, so long as you keep the Autocomplete object reference in scope.
The function ends up looking like this:
var autocompleteDropdown = breadthFirstSearch(autocomplete, function(val) {
return val.className === "pac-container";
}
Where the breadthFirstSearch function is defined as:
function breadthFirstSearch(object, matchesCriteria) {
var queue = [];
var visited = [];
queue.push(object);
visited.push(object);
while (queue.length) {
var val = queue.shift();
if (val && typeof val == "object") {
if (matchesCriteria(val)) {
return val;
}
if (Object.prototype.toString.call(val) == "[object Array]") {
for (var i=0; i<val.length; i++) {
breadthFirstSearchProcessValue(val[i], queue, visited);
}
}
else if (val instanceof Node) {
if (val.hasChildNodes()) {
var children = val.childNodes;
for (var i=0; i<children.length; i++) {
breadthFirstSearchProcessValue(children[i], queue, visited);
}
}
}
else {
for (var property in val) {
breadthFirstSearchProcessValue(val[property], queue, visited);
}
}
}
}
}
function breadthFirstSearchProcessValue(val, queue, visited) {
for (var i=0; i<visited.length; i++) {
if (visited[i] === val) {
return;
}
}
queue.push(val);
visited.push(val);
}
In sum, this is obviously a pretty intensive way of just accessing a property (which Google should make visible natively, grumble grumble). I would recommend trying one of the more "naive" but faster methods from the StackOverflow question you linked, and fall back to this when that method returns null/empty.