Is 欧路翻译 - 沉浸网页划词翻译工具 safe?
欧路翻译 is medium risk. When you browse to a site matching the rule list, the extension fetches page rules from api.frdic.com and applies CSS from them to matching elements. Dynamic analysis observed a rule for m.youtube.com; the content script runs on all URLs.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Server-provided CSS can change matched webpages
When you browse to a site matching the rule list, the extension fetches page rules from api.frdic.com and applies CSS from them to matching elements.
Dynamic analysis observed a rule for m.youtube.com; the content script runs on all URLs.
You open a webpage where the extension's content script is active.
The manifest grants the content script access to all URL patterns listed in the extension package.
The extension checks server-provided page rules and can apply CSS when the current page matches.
The rule engine uses match patterns, selectors, global style strings, and injected CSS strings.
| Field | Value | Why it matters | |
|---|---|---|---|
Matched website | m.youtube.com | Lets the rule decide which sites can receive page changes while you browse. | |
Element selector | .comment-text | Lets the rule pick which parts of the page can be restyled. | |
Element CSS | max-height:unset; | Lets the rule add style text directly to matching page elements. | |
Injected page CSS | eudic-chrome-extension-page-style | Lets the rule add a style element that affects the matched page. |
| Content-Type | application/json |
The shipped rule fetch and CSS application paths
apiGetExtensionRule() {
const t = `${(0,s.uh)()}/api/v2/appsupport/ExtensionConfig`;
return this.fetchApi(t, {
method: "GET",
headers: {
Authorization: (0, s.jL)("", ""),
"Content-Type": "application/json"
}
}, void 0, !0);
}fetchRulesForPage() {
const e = this.getCurrentUrl();
let o;
return this.extensionRule && (o = this.extensionRule.rules.find(u => {
if (u.matches && 0 !== u.matches.length) return this.getMatchedUrl(e, u.matches);
})), o;
}
prepareActionForPage() {
if (this.isActionForPagePrepared) return;
if (this.isActionForPagePrepared = !0, this._doc.querySelector("div.ql-editor[contenteditable='false']")) {
const s = this._doc.body.innerHTML;
this._doc.body.innerHTML = "", this._doc.body.innerHTML = s;
}
for (const s of this.commonExcludeSelectors) {
const u = this._doc.querySelectorAll(s);
for (let h = 0; h < u.length; h++) {
const g = u[h];
if (g instanceof HTMLElement) {
const v = g.getAttribute(nt.h.kEusoftTranslateIgnoreAttribute);
(!v || "1" !== v) && g.setAttribute(nt.h.kEusoftTranslateIgnoreAttribute, "1");
}
}
}
const o = this.fetchRulesForPage();
if (o) {
if (o.globalStyles) for (const s in o.globalStyles) {
const u = this._doc.querySelectorAll(s);
for (let h = 0; h < u.length; h++) {
const g = u[h];
if (g instanceof HTMLElement) {
const v = g.getAttribute(nt.h.kEusoftTranslateDidAddGlobalStyleAttribute);
(!v || "1" !== v) && (g.setAttribute(nt.h.kEusoftTranslateDidAddGlobalStyleAttribute, "1"), g.style.cssText = g.style.cssText + o.globalStyles[s]);
}
}
}
if (o.injectedCss) {
let s;
if (s = Array.isArray(o.injectedCss) ? o.injectedCss.join("\n") : o.injectedCss, !document.getElementById(nt.h.pageInjectedStyleId)) {
const u = document.createElement("style");
u.textContent = s, u.id = nt.h.pageInjectedStyleId, document.head.appendChild(u);
}
}
}
}- api.frdic.com
Receives the ExtensionConfig request and returns page-rule configuration used by the extension.
Selected text context is sent for translation
When you select text on a webpage, the content script computes nearby text as context and sends it to Eudic translation endpoints with the word.
Captured traffic lacked a body, but the shipped code places word and context into requests.
You select text on a webpage to use the translation feature.
The content script listens for selection-driven capture flows on the pages where it runs.
The extension collects the selected text and nearby page context for translation.
The context helper can return up to 1,000 characters around the selected text.
| Field | Value | Why it matters | |
|---|---|---|---|
Selected text | quarterly revenue | Sends the word or phrase you selected so the service can translate it. | |
Nearby page text | The quarterly revenue <b>increased</b> after the product launch. | Can include surrounding text from the same page section, not just the exact word you selected. | |
Language setting | lang=en | Tells the service which language direction to use for your translation. | |
Request type | type=web | Marks the translation request as coming from the web flow. |
| Content-Type | application/x-www-form-urlencoded |
| Content-Type | application/json |
The selection context and translation request paths
getSelectionTextFullContent(e) {
if (!e || 0 === e.rangeCount) return "";
const o = 1e3, s = e.getRangeAt(0), u = e.toString().trim();
if (!u) return "";
let h = s.commonAncestorContainer;
h.nodeType === Node.TEXT_NODE && (h = h.parentElement);
let v = this.collectInlineText(h).trim();
if (v.length <= u.length) {
let me = 0;
for (; me < 5 && h.parentElement && (h = h.parentElement, v = this.collectInlineText(h).trim(), !(v.length > u.length)); ) me++;
}
if (v.length <= u.length && (v = this.getTextContent(h, !1, !0) || "", v = v.trim()), !v || 0 === v.trim().length) return u;
const S = v.indexOf(u);
if (-1 === S) return v.length > o ? v.substring(0, o) : v;
const L = "\u3002\uff01\uff1f.!?\uff1b;";
let Y = S;
for (; Y > 0 && !L.includes(v[Y - 1]); ) Y--;
let W = S + u.length;
for (; W < v.length && !L.includes(v[W]); ) W++;
W < v.length && W++;
let K = v.substring(Y, W);
const oe = S - Y, de = K.substring(0, oe) + "<b>" + K.substring(oe, oe + u.length) + "</b>" + K.substring(oe + u.length);
return de.length > o ? de.substring(0, o) : de;
}showCaptureWindow(e, o) {
var s = this;
return (0, I.A)(function*() {
if (e.currentTarget instanceof Document) {
const h = e.currentTarget.location.href;
if (h.startsWith((0, fe.Bw)()) || window.top !== window.self && h.startsWith((0, fe.DP)())) return;
}
const u = s.getCaptureSelectionModel();
if (u) {
const h = u.selection, g = u.selectedText;
if (h.focusNode instanceof HTMLElement && h.focusNode.tagName.toLowerCase().startsWith(nt.h.selectorId)) return;
if (g.length > 0) {
if (yield s.bridgeWorkerService.checkSidePanelOpened()) {
const W = s.getSelectionTextContext(h, g);
return void s.bridgeWorkerService.doSidePanelTransWord(g, W).subscribe();
}
let L, Y = !0;
o && e instanceof MouseEvent && (L = new ol(e.clientX, e.clientY), Y = !(L.y > o.y && L.y - o.y > 20)), s.finalShowExplainContent(g, s.getSelectionTextContext(h, g), new al(h, s.settingModel.model.captureExplainSizeWidth, s.settingModel.model.captureExplainSizeMaxHeight, void 0, L, Y));
} else s.clearAndCancelCaptureWindow();
s.cdr.detectChanges();
}
})();
}
finalShowExplainContent(e, o, s) {
(0, i.naY)() && console.log(`finalShowExplainContent word = ${e}`), (0, fe.P)() ? Aa.A.showExplainContent(e, o, s) : (this.getInitData(), this.selectionMousePos = s, this.selectedWord = e, this.selectedContext = o, (0, fe.IY)() && (this.isDragNavBarVisible = !0));
}
getSelectionTextContext(e, o) {
if (!e && this.doc.getSelection && (e = this.doc.getSelection()), !e) return;
const s = this.domService.getSelectionTextFullContent(e);
if (!s) return;
if (s.includes(o)) return s;
const u = o.replace(/\n/g, ""), h = s.replace(/\n/g, "");
return h.includes(u) ? h : void 0;
}apiExplainContent(t, e, n) {
const r = `${(0,s.uh)()}/api/v2/dicts/explain`, o = new URLSearchParams;
o.append("word", t), e && e.length > 0 && o.append("context", e), o.append("lang", (0, s.ZO)()), o.append("type", "web");
const i = `${r}?${o.toString()}`;
return this.fetchApi(i, {
method: "GET",
headers: {
Authorization: (0, s.jL)(n ? n.userid : "", n && n.token ? n.token.token : ""),
"Content-Type": "application/json"
}
}, void 0, !0);
}
explainWithContextResponse(t, e, n) {
return (0, g.A)(function*() {
return yield fetch(`${(0,s.nu)()}/dicts/ExplainWithContextStream?context=${encodeURIComponent(e)}&word=${encodeURIComponent(t)}&lang=${(0,s.ZO)()}&userid=${n}`, {
method: "POST",
headers: {
EudicUserAgent: (0, s.N1)(),
"Content-Type": "application/json"
},
credentials: "omit",
body: void 0
});
})();
}
translate(J, P, G, H, tt, z, at) {
const U = {
q: J,
from: this.eusoftTranslateMap[H],
to: this.eusoftTranslateMap[tt] || tt,
engine: z
};
P && P.length > 0 && (U.context = P), G && G.length > 0 && (U.summary = G);
let x = "/dicts/minitranslate";
z === K.gpt && at && (x = "/dicts/MiniTranslateStream");
const it = new URLSearchParams(U), et = (0, b.nu)() + x, nt = this.httpWorker.fetchApiWithCookie(et, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: it
}, !1).pipe((0, Q.n)(q => {
let $;
try {
$ = JSON.parse(q);
} catch {
return (0, V.$)(() => new W.A(W.O.needLogin));
}
let T = "";
switch (z) {
case K.eusoft:
for (let O of $.trans_result.data) T += O.dst + "\n";
T = T.trim();
break;
case K.gpt: {
const O = $;
if (O.code !== d.KK.success) throw O.code === d.KK.notPurchase ? new W.A(W.O.needVip) : O.code === d.KK.noBalance ? new W.A(W.O.noBalance) : O.message;
T = O.data.result;
}
}
return (0, E.of)(T);
}));
return C.k.getIsLogin().pipe((0, Q.n)(q => q ? nt : (0, V.$)(() => new W.A(W.O.needLogin))));
}- dict.eudic.net
Receives minitranslate and contextual translation requests for selected text.
- api.frdic.com
Receives dictionary explain requests that include the selected word and context query parameters.