|
|
/**
|
|
|
* P0:Autocomplete 实例拓扑 + place_changed 监听器指纹
|
|
|
* P2:全局 fetch 劫持(axel/location/place)
|
|
|
* 纯字符串 initScript,供 addInitScript 注入。
|
|
|
*/
|
|
|
|
|
|
export const P0_PLACE_DIAG_INIT = `(() => {
|
|
|
if (window.__rpaPlaceDiagP0) return;
|
|
|
window.__rpaPlaceDiagP0 = true;
|
|
|
window.__rpaPlaceDiag = window.__rpaPlaceDiag || {
|
|
|
instances: [],
|
|
|
listeners: [],
|
|
|
events: [],
|
|
|
fetchLog: [],
|
|
|
mark: "init",
|
|
|
nextInstanceId: 1,
|
|
|
nextListenerId: 1,
|
|
|
};
|
|
|
var diag = window.__rpaPlaceDiag;
|
|
|
|
|
|
function inputMeta(el) {
|
|
|
if (!el) return null;
|
|
|
var gmKeys = Object.keys(el).filter(function(k) { return k.indexOf("gm_") === 0; });
|
|
|
return {
|
|
|
tagName: el.tagName || "",
|
|
|
id: el.id || "",
|
|
|
className: (el.className || "").slice(0, 80),
|
|
|
placeholder: el.placeholder || "",
|
|
|
pacTarget: el.classList && el.classList.contains("pac-target-input"),
|
|
|
valuePreview: ((el.value || el.textContent || "") + "").replace(/\\s+/g, " ").trim().slice(0, 100),
|
|
|
gmKeyCount: gmKeys.length,
|
|
|
gmKeys: gmKeys.slice(0, 6),
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function resolveInstanceFromInput(el) {
|
|
|
if (!el) return null;
|
|
|
for (var i = 0; i < Object.keys(el).length; i += 1) {
|
|
|
var key = Object.keys(el)[i];
|
|
|
if (key.indexOf("gm_") !== 0) continue;
|
|
|
var val = el[key];
|
|
|
if (val && typeof val.getPlace === "function") return val;
|
|
|
}
|
|
|
for (var j = 0; j < diag.instances.length; j += 1) {
|
|
|
if (diag.instances[j].inputEl === el) return diag.instances[j].instance;
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
function instanceIdFor(inst, inputEl) {
|
|
|
for (var i = 0; i < diag.instances.length; i += 1) {
|
|
|
if (diag.instances[i].instance === inst) return diag.instances[i].id;
|
|
|
}
|
|
|
var id = "ac-" + diag.nextInstanceId++;
|
|
|
diag.instances.push({
|
|
|
id: id,
|
|
|
instance: inst,
|
|
|
inputEl: inputEl || inst.__rpaInputEl || null,
|
|
|
input: inputMeta(inputEl || inst.__rpaInputEl || null),
|
|
|
createdAt: Date.now(),
|
|
|
listenerIds: [],
|
|
|
});
|
|
|
return id;
|
|
|
}
|
|
|
|
|
|
function fingerprintHandler(handler, source) {
|
|
|
var src = "";
|
|
|
try { src = Function.prototype.toString.call(handler); } catch (e) { src = String(handler); }
|
|
|
var compact = src.replace(/\\s+/g, " ").trim();
|
|
|
return {
|
|
|
source: source,
|
|
|
length: compact.length,
|
|
|
preview: compact.slice(0, 240),
|
|
|
hasFetch: /\\bfetch\\s*\\(/.test(compact),
|
|
|
hasAxelPlace: /axel\\/location\\/place/.test(compact),
|
|
|
hasGetPlace: /getPlace\\s*\\(/.test(compact),
|
|
|
hasXMLHttpRequest: /XMLHttpRequest|\\.open\\s*\\(/.test(compact),
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function safeGetPlace(inst) {
|
|
|
if (!inst || typeof inst.getPlace !== "function") {
|
|
|
return { ok: false, reason: "no-getPlace" };
|
|
|
}
|
|
|
try {
|
|
|
var p = inst.getPlace();
|
|
|
if (!p) return { ok: true, empty: true };
|
|
|
var loc = p.geometry && p.geometry.location;
|
|
|
var lat = null;
|
|
|
var lng = null;
|
|
|
if (loc) {
|
|
|
lat = typeof loc.lat === "function" ? loc.lat() : loc.lat;
|
|
|
lng = typeof loc.lng === "function" ? loc.lng() : loc.lng;
|
|
|
}
|
|
|
return {
|
|
|
ok: true,
|
|
|
place_id: p.place_id || null,
|
|
|
formatted_address: (p.formatted_address || "").slice(0, 160),
|
|
|
name: (p.name || "").slice(0, 80),
|
|
|
hasGeometry: Boolean(loc),
|
|
|
lat: lat,
|
|
|
lng: lng,
|
|
|
};
|
|
|
} catch (e2) {
|
|
|
return { ok: false, reason: "getPlace-threw", error: String(e2) };
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function wrapHandler(inst, handler, source, inputEl) {
|
|
|
var instId = instanceIdFor(inst, inputEl);
|
|
|
var fp = fingerprintHandler(handler, source);
|
|
|
var lid = "lh-" + diag.nextListenerId++;
|
|
|
diag.listeners.push({
|
|
|
id: lid,
|
|
|
instanceId: instId,
|
|
|
registeredAt: Date.now(),
|
|
|
fingerprint: fp,
|
|
|
});
|
|
|
for (var i = 0; i < diag.instances.length; i += 1) {
|
|
|
if (diag.instances[i].id === instId) {
|
|
|
diag.instances[i].listenerIds.push(lid);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
return function wrappedPlaceChangedHandler() {
|
|
|
diag.events.push({
|
|
|
type: "place_changed_handler",
|
|
|
mark: diag.mark,
|
|
|
at: Date.now(),
|
|
|
instanceId: instId,
|
|
|
listenerId: lid,
|
|
|
fingerprint: fp,
|
|
|
getPlaceBefore: safeGetPlace(inst),
|
|
|
});
|
|
|
return handler.apply(this, arguments);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function registerPlaceChanged(inst, handler, source, inputEl) {
|
|
|
if (!inst || typeof handler !== "function") return handler;
|
|
|
return wrapHandler(inst, handler, source, inputEl);
|
|
|
}
|
|
|
|
|
|
window.__rpaAutocompleteInstances = window.__rpaAutocompleteInstances || [];
|
|
|
window.__rpaPlaceChangedHandlers = window.__rpaPlaceChangedHandlers || [];
|
|
|
|
|
|
var waitForGoogle = function() {
|
|
|
if (typeof google === "undefined" || !google.maps || !google.maps.places || !google.maps.places.Autocomplete) {
|
|
|
setTimeout(waitForGoogle, 200);
|
|
|
return;
|
|
|
}
|
|
|
if (window.__rpaAutocompletePatched) return;
|
|
|
window.__rpaAutocompletePatched = true;
|
|
|
|
|
|
var AutocompleteCtor = google.maps.places.Autocomplete;
|
|
|
google.maps.places.Autocomplete = function() {
|
|
|
var args = arguments;
|
|
|
var instance = Reflect.construct(AutocompleteCtor, args);
|
|
|
var inputEl = args[0] || null;
|
|
|
instance.__rpaInputEl = inputEl;
|
|
|
window.__rpaAutocompleteInstances.push(instance);
|
|
|
instanceIdFor(instance, inputEl);
|
|
|
|
|
|
if (typeof instance.addListener === "function") {
|
|
|
var origInstAdd = instance.addListener.bind(instance);
|
|
|
instance.addListener = function(eventName, handler) {
|
|
|
var h = handler;
|
|
|
if (eventName === "place_changed" && typeof handler === "function") {
|
|
|
h = registerPlaceChanged(instance, handler, "instance.addListener", inputEl);
|
|
|
window.__rpaPlaceChangedHandlers.push({ instance: instance, handler: h, inputEl: inputEl });
|
|
|
}
|
|
|
return origInstAdd(eventName, h);
|
|
|
};
|
|
|
}
|
|
|
return instance;
|
|
|
};
|
|
|
google.maps.places.Autocomplete.prototype = AutocompleteCtor.prototype;
|
|
|
|
|
|
if (google.maps.event && typeof google.maps.event.addListener === "function") {
|
|
|
var origMapsAdd = google.maps.event.addListener;
|
|
|
google.maps.event.addListener = function(instance, eventName, handler) {
|
|
|
var h = handler;
|
|
|
if (
|
|
|
eventName === "place_changed" &&
|
|
|
instance &&
|
|
|
typeof instance.getPlace === "function" &&
|
|
|
typeof handler === "function"
|
|
|
) {
|
|
|
h = registerPlaceChanged(instance, handler, "maps.event.addListener", instance.__rpaInputEl || null);
|
|
|
window.__rpaPlaceChangedHandlers.push({
|
|
|
instance: instance,
|
|
|
handler: h,
|
|
|
inputEl: instance.__rpaInputEl || null,
|
|
|
});
|
|
|
}
|
|
|
return origMapsAdd(instance, eventName, h);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
if (google.maps.event && typeof google.maps.event.trigger === "function") {
|
|
|
var origTrigger = google.maps.event.trigger;
|
|
|
google.maps.event.trigger = function(instance, eventName) {
|
|
|
var instId = instance && typeof instance.getPlace === "function"
|
|
|
? instanceIdFor(instance, instance.__rpaInputEl || null)
|
|
|
: null;
|
|
|
if (eventName === "place_changed") {
|
|
|
diag.events.push({
|
|
|
type: "maps_event_trigger",
|
|
|
mark: diag.mark,
|
|
|
at: Date.now(),
|
|
|
instanceId: instId,
|
|
|
getPlaceAtTrigger: instance ? safeGetPlace(instance) : null,
|
|
|
});
|
|
|
}
|
|
|
return origTrigger.apply(this, arguments);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
diag.events.push({ type: "p0-ready", at: Date.now(), mark: diag.mark });
|
|
|
};
|
|
|
waitForGoogle();
|
|
|
})();`;
|
|
|
|
|
|
export const P2_FETCH_HIJACK_INIT = `(() => {
|
|
|
if (window.__rpaPlaceFetchP2) return;
|
|
|
window.__rpaPlaceFetchP2 = true;
|
|
|
window.__rpaPlaceDiag = window.__rpaPlaceDiag || { fetchLog: [], events: [], mark: "init" };
|
|
|
var diag = window.__rpaPlaceDiag;
|
|
|
diag.fetchLog = diag.fetchLog || [];
|
|
|
|
|
|
var origFetch = window.fetch;
|
|
|
if (typeof origFetch === "function") {
|
|
|
window.fetch = function() {
|
|
|
var url = "";
|
|
|
try {
|
|
|
var first = arguments[0];
|
|
|
url = typeof first === "string" ? first : (first && first.url) || "";
|
|
|
} catch (e) {}
|
|
|
if (url && url.indexOf("axel/location/place") >= 0) {
|
|
|
var stack = "";
|
|
|
try { stack = (new Error("place-fetch")).stack || ""; } catch (e2) {}
|
|
|
diag.fetchLog.push({
|
|
|
mark: diag.mark,
|
|
|
at: Date.now(),
|
|
|
url: url.slice(0, 240),
|
|
|
stackPreview: stack.split("\\n").slice(0, 8).join(" | "),
|
|
|
});
|
|
|
diag.events.push({ type: "fetch_place", mark: diag.mark, at: Date.now(), url: url.slice(0, 240) });
|
|
|
}
|
|
|
return origFetch.apply(this, arguments);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
var XHR = window.XMLHttpRequest;
|
|
|
if (XHR && XHR.prototype && XHR.prototype.open) {
|
|
|
var origOpen = XHR.prototype.open;
|
|
|
XHR.prototype.open = function(method, url) {
|
|
|
this.__rpaUrl = url;
|
|
|
return origOpen.apply(this, arguments);
|
|
|
};
|
|
|
var origSend = XHR.prototype.send;
|
|
|
XHR.prototype.send = function() {
|
|
|
var url = this.__rpaUrl || "";
|
|
|
if (url && String(url).indexOf("axel/location/place") >= 0) {
|
|
|
diag.fetchLog.push({
|
|
|
mark: diag.mark,
|
|
|
at: Date.now(),
|
|
|
url: String(url).slice(0, 240),
|
|
|
transport: "xhr",
|
|
|
});
|
|
|
diag.events.push({ type: "xhr_place", mark: diag.mark, at: Date.now(), url: String(url).slice(0, 240) });
|
|
|
}
|
|
|
return origSend.apply(this, arguments);
|
|
|
};
|
|
|
}
|
|
|
})();`;
|
|
|
|
|
|
/** 设置诊断阶段标记并清空本阶段事件 */
|
|
|
export const PLACE_DIAG_SET_MARK = `(mark) => {
|
|
|
window.__rpaPlaceDiag = window.__rpaPlaceDiag || { events: [], mark: "init" };
|
|
|
window.__rpaPlaceDiag.mark = mark || "phase";
|
|
|
return { ok: true, mark: window.__rpaPlaceDiag.mark };
|
|
|
}`;
|
|
|
|
|
|
/** 采集实例拓扑 + 监听器指纹 + 事件 + fetch 日志(可序列化) */
|
|
|
export const PLACE_DIAG_COLLECT_SNAPSHOT = `() => {
|
|
|
try {
|
|
|
var diag = window.__rpaPlaceDiag;
|
|
|
if (!diag) return { ok: false, reason: "no-diag" };
|
|
|
|
|
|
function inputMeta(el) {
|
|
|
if (!el) return null;
|
|
|
var gmKeys = Object.keys(el).filter(function(k) { return k.indexOf("gm_") === 0; });
|
|
|
return {
|
|
|
tagName: el.tagName || "",
|
|
|
placeholder: el.placeholder || "",
|
|
|
pacTarget: el.classList && el.classList.contains("pac-target-input"),
|
|
|
valuePreview: ((el.value || "") + "").replace(/\\s+/g, " ").trim().slice(0, 100),
|
|
|
gmKeyCount: gmKeys.length,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function resolveFromInput(el) {
|
|
|
if (!el) return null;
|
|
|
for (var i = 0; i < Object.keys(el).length; i += 1) {
|
|
|
var key = Object.keys(el)[i];
|
|
|
if (key.indexOf("gm_") !== 0) continue;
|
|
|
var val = el[key];
|
|
|
if (val && typeof val.getPlace === "function") return val;
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
var widget = document.querySelector("#react-quoter-landing-plugin");
|
|
|
var inputProbes = [];
|
|
|
if (widget) {
|
|
|
var inputs = widget.querySelectorAll("input[type='text'], input:not([type])");
|
|
|
for (var i = 0; i < inputs.length; i += 1) {
|
|
|
var inp = inputs[i];
|
|
|
var inst = resolveFromInput(inp);
|
|
|
inputProbes.push({
|
|
|
index: i,
|
|
|
meta: inputMeta(inp),
|
|
|
hasGmInstance: Boolean(inst),
|
|
|
getPlace: inst && typeof inst.getPlace === "function"
|
|
|
? (function() {
|
|
|
try {
|
|
|
var p = inst.getPlace();
|
|
|
return {
|
|
|
place_id: p && p.place_id,
|
|
|
formatted_address: p && p.formatted_address,
|
|
|
hasGeometry: Boolean(p && p.geometry && p.geometry.location),
|
|
|
};
|
|
|
} catch (e) { return { error: String(e) }; }
|
|
|
})()
|
|
|
: null,
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var instances = (diag.instances || []).map(function(row) {
|
|
|
var inst = row.instance;
|
|
|
var getPlace = null;
|
|
|
if (inst && typeof inst.getPlace === "function") {
|
|
|
try {
|
|
|
var p = inst.getPlace();
|
|
|
getPlace = {
|
|
|
place_id: p && p.place_id,
|
|
|
formatted_address: p && p.formatted_address,
|
|
|
hasGeometry: Boolean(p && p.geometry && p.geometry.location),
|
|
|
};
|
|
|
} catch (e2) {
|
|
|
getPlace = { error: String(e2) };
|
|
|
}
|
|
|
}
|
|
|
var listenerRows = (diag.listeners || []).filter(function(l) { return l.instanceId === row.id; });
|
|
|
return {
|
|
|
id: row.id,
|
|
|
input: row.input,
|
|
|
listenerCount: listenerRows.length,
|
|
|
listenerFingerprints: listenerRows.map(function(l) { return l.fingerprint; }),
|
|
|
getPlace: getPlace,
|
|
|
};
|
|
|
});
|
|
|
|
|
|
var anyListenerHasFetch = (diag.listeners || []).some(function(l) {
|
|
|
return l.fingerprint && l.fingerprint.hasFetch;
|
|
|
});
|
|
|
var anyListenerHasAxel = (diag.listeners || []).some(function(l) {
|
|
|
return l.fingerprint && l.fingerprint.hasAxelPlace;
|
|
|
});
|
|
|
|
|
|
var handlerEvents = (diag.events || []).filter(function(e) {
|
|
|
return e.type === "place_changed_handler" || e.type === "maps_event_trigger";
|
|
|
});
|
|
|
var fetchEvents = (diag.fetchLog || []).slice(-20);
|
|
|
|
|
|
return {
|
|
|
ok: true,
|
|
|
mark: diag.mark,
|
|
|
topology: {
|
|
|
instanceCount: instances.length,
|
|
|
instances: instances,
|
|
|
inputProbes: inputProbes,
|
|
|
monkeyPatchInstances: (window.__rpaAutocompleteInstances || []).length,
|
|
|
capturedHandlers: (window.__rpaPlaceChangedHandlers || []).length,
|
|
|
},
|
|
|
listeners: {
|
|
|
total: (diag.listeners || []).length,
|
|
|
anyHasFetch: anyListenerHasFetch,
|
|
|
anyHasAxelPlace: anyListenerHasAxel,
|
|
|
rows: (diag.listeners || []).map(function(l) {
|
|
|
return { id: l.id, instanceId: l.instanceId, fingerprint: l.fingerprint };
|
|
|
}),
|
|
|
},
|
|
|
events: (diag.events || []).slice(-40),
|
|
|
handlerEvents: handlerEvents,
|
|
|
fetchLog: fetchEvents,
|
|
|
activeElement: (function() {
|
|
|
var el = document.activeElement;
|
|
|
if (!el) return null;
|
|
|
return inputMeta(el);
|
|
|
})(),
|
|
|
};
|
|
|
} catch (err) {
|
|
|
return { ok: false, reason: "snapshot-error", error: String(err) };
|
|
|
}
|
|
|
}`;
|