From f7d73da301410bade83e11f284669b9b8787a88a Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Mon, 12 Oct 2026 07:25:27 +0900 Subject: [PATCH] =?UTF-8?q?250=20=EA=B3=A0=EC=B9=A8=202:=20Android=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=8D=AE=EA=B0=9C=EA=B0=80=20=EB=9C=A8?= =?UTF-8?q?=EC=9E=90=EB=A7=88=EC=9E=90=20=EA=B1=B7=ED=9E=88=EB=8D=98=20?= =?UTF-8?q?=EA=B2=83=20-=20=EB=8D=AE=EA=B0=9C=EB=8A=94=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EA=B0=80=20=EC=98=AE=EA=B8=B8=20=EB=95=8C?= =?UTF-8?q?=EB=A7=8C=20=EA=B1=B7=EC=9D=8C=20=C2=B7=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=EC=9D=98=20=EC=98=88=EC=99=B8=20=EC=9D=B4=EB=A6=84=20=EB=B9=BC?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a4 의 932 기기 확인 (노트 10+): 작업 기록에 "웹 페이지를 열지 못함 (…): net::ERR_TUNNEL_CONNECTION_FAILED" 는 남는데 덮개가 보이지 않았다 (버그 수정). - 원인: Android 는 본문 오류 (onReceivedError) 뒤에 엔진의 오류 페이지를 또 읽어 진행률이 0 → 100 으로 한 번 더 돈다. "새로 읽기 시작 (진행률이 1 아래로)" 에서 덮개를 걷어, 뜨자마자 걷혔다. Windows (Edge) 는 이 단계가 없어 통과했다. - 덮개는 사용자가 옮길 때만 걷는다: 주소 · 뒤로 · 앞으로 · 새로고침 · [다시 시도] (WebNav 를 감싸 알림 - _NavWatch). 덮개가 웹 뷰를 덮고 있어 그 밖의 길로는 옮겨지지 않는다. - 덮개를 띄우면 작업 기록에 "DNS 안내 덮개를 띄움 (…)" - 기기에서 "띄웠는데 안 보임" 과 "띄우지 않음" 을 가리게. - DNS 실패 글에 예외 이름 ("TimeoutException:") 을 빼고 이유만 (a4 932). - 시험: Android 순서 (본문 오류 → 오류 페이지 진행률 0 → 100) 뒤에도 덮개가 남음. 실제 Edge 통합 시험 다시 통과. Co-Authored-By: Claude Opus 5.5 --- assets/l10n/en.json | 1 + assets/l10n/ja.json | 1 + assets/l10n/zh-Hans.json | 1 + lib/core/dns_resolver.dart | 8 ++++- lib/ui/browser_page.dart | 67 ++++++++++++++++++++++++++++++++++---- test/browser_test.dart | 6 ++++ 6 files changed, 76 insertions(+), 8 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 0d3b3b9..d51919e 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -164,6 +164,7 @@ "DNS 서버가 {0} 의 주소를 찾지 못해 {1}": "The DNS server couldn't find the address of {0}, so {1}", "DNS 설정: {0}": "DNS setting: {0}", "DNS 실패 ({0}): {1}": "DNS failure ({0}): {1}", + "DNS 안내 덮개를 띄움 ({0} · DNS {1})": "Showed DNS notice over the page ({0} · DNS {1})", "DNS: 기기 DNS → 직접 지정 (8.8.8.8 · 8.8.4.4). 앱의 인터넷 연결 (브라우저 · 다운로드 · 업데이트) 은 이 서버로 주소를 찾습니다. 되돌리기: 환경 설정 > 네트워크 · DNS > 시스템 DNS (끄기) 회사 · 학교망처럼 8.8.8.8 이 막힌 곳에서는 닿지 않는다는 안내의 [시스템 DNS 로 바꾸기] 를 누르세요.": "DNS: device DNS → custom (8.8.8.8 · 8.8.4.4). The app's internet connections (browser · downloads · updates) now look up addresses with these servers. To undo: Settings > Network · DNS > System DNS (off) On networks that block 8.8.8.8 (such as company or school networks), press [Switch to system DNS] in the \"can't reach\" notice.", "DOC · DOCX · HWP · XLS · XLSX 를 PDF 로 바꿔 보기 (변환기를 받음)": "View DOC · DOCX · HWP · XLS · XLSX converted to PDF (downloads a converter)", "DoH 서버 ({0}) 가 HTTP {1} 로 답했습니다": "The DoH server ({0}) answered HTTP {1}", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 2b930bb..1636967 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -164,6 +164,7 @@ "DNS 서버가 {0} 의 주소를 찾지 못해 {1}": "DNS サーバーが {0} のアドレスを見つけられないため、{1}", "DNS 설정: {0}": "DNS 設定: {0}", "DNS 실패 ({0}): {1}": "DNS 失敗 ({0}): {1}", + "DNS 안내 덮개를 띄움 ({0} · DNS {1})": "ページに DNS 案内を表示 ({0} · DNS {1})", "DNS: 기기 DNS → 직접 지정 (8.8.8.8 · 8.8.4.4). 앱의 인터넷 연결 (브라우저 · 다운로드 · 업데이트) 은 이 서버로 주소를 찾습니다. 되돌리기: 환경 설정 > 네트워크 · DNS > 시스템 DNS (끄기) 회사 · 학교망처럼 8.8.8.8 이 막힌 곳에서는 닿지 않는다는 안내의 [시스템 DNS 로 바꾸기] 를 누르세요.": "DNS: 端末の DNS → 直接指定 (8.8.8.8 · 8.8.4.4)。アプリのインターネット接続 (ブラウザ · ダウンロード · 更新) はこのサーバーでアドレスを調べます。元に戻す: 設定 > ネットワーク · DNS > システム DNS (オフ) 会社 · 学校のネットワークなど 8.8.8.8 がブロックされている所では、接続できないという案内の [システム DNS に切り替える] を押してください。", "DOC · DOCX · HWP · XLS · XLSX 를 PDF 로 바꿔 보기 (변환기를 받음)": "DOC・DOCX・HWP・XLS・XLSX を PDF に変換して表示 (変換ツールをダウンロード)", "DoH 서버 ({0}) 가 HTTP {1} 로 답했습니다": "DoH サーバー ({0}) が HTTP {1} を返しました", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index bb26336..4bb2528 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -164,6 +164,7 @@ "DNS 서버가 {0} 의 주소를 찾지 못해 {1}": "DNS 服务器找不到 {0} 的地址,因此{1}", "DNS 설정: {0}": "DNS 设置: {0}", "DNS 실패 ({0}): {1}": "DNS 失败 ({0}): {1}", + "DNS 안내 덮개를 띄움 ({0} · DNS {1})": "已在页面上显示 DNS 提示 ({0} · DNS {1})", "DNS: 기기 DNS → 직접 지정 (8.8.8.8 · 8.8.4.4). 앱의 인터넷 연결 (브라우저 · 다운로드 · 업데이트) 은 이 서버로 주소를 찾습니다. 되돌리기: 환경 설정 > 네트워크 · DNS > 시스템 DNS (끄기) 회사 · 학교망처럼 8.8.8.8 이 막힌 곳에서는 닿지 않는다는 안내의 [시스템 DNS 로 바꾸기] 를 누르세요.": "DNS:设备 DNS → 手动指定 (8.8.8.8 · 8.8.4.4)。应用的网络连接 (浏览器 · 下载 · 更新) 将用这些服务器查找地址。恢复:设置 > 网络 · DNS > 系统 DNS (关闭) 在公司 · 学校等屏蔽 8.8.8.8 的网络中,请点击无法连接提示中的 [切换为系统 DNS]。", "DOC · DOCX · HWP · XLS · XLSX 를 PDF 로 바꿔 보기 (변환기를 받음)": "将 DOC · DOCX · HWP · XLS · XLSX 转为 PDF 查看 (需下载转换器)", "DoH 서버 ({0}) 가 HTTP {1} 로 답했습니다": "DoH 服务器 ({0}) 返回 HTTP {1}", diff --git a/lib/core/dns_resolver.dart b/lib/core/dns_resolver.dart index 8c29b7d..9f459a4 100644 --- a/lib/core/dns_resolver.dart +++ b/lib/core/dns_resolver.dart @@ -207,7 +207,13 @@ class DnsResolver { last = e; } } - throw DnsException(trf('DNS 서버 ({0}) 에 닿지 않습니다: {1}', [c.label, last]), unreachable: true, server: c.label); + // 작업 기록 · 화면에 예외 이름 ("TimeoutException:") 없이 (a4 932) + final why = switch (last) { + TimeoutException(:final message) => message ?? tr('답이 없습니다'), + SocketException(:final message) => message, + _ => '$last', + }; + throw DnsException(trf('DNS 서버 ({0}) 에 닿지 않습니다: {1}', [c.label, why]), unreachable: true, server: c.label); } Future _udp(InternetAddress server, String name, int type) async { diff --git a/lib/ui/browser_page.dart b/lib/ui/browser_page.dart index 6a9ef07..717ecd5 100644 --- a/lib/ui/browser_page.dart +++ b/lib/ui/browser_page.dart @@ -63,6 +63,53 @@ abstract class WebNav { Future setJavaScript(bool on); } +/// 250: 사용자가 페이지를 옮길 때 (주소 · 뒤로 · 앞으로 · 새로고침 · 다시 시도) 알린다 - DNS 덮개는 이때만 걷는다. +/// 진행률로 걷으면 Android 는 본문 오류 뒤에 엔진의 오류 페이지를 또 읽어 (진행률 0 → 100) 덮개가 뜨자마자 걷혔다 (932 노트 10+) +class _NavWatch implements WebNav { + final WebNav n; + final void Function() onUserNavigate; + _NavWatch(this.n, this.onUserNavigate); + + @override + Future load(String url) { + onUserNavigate(); + return n.load(url); + } + + @override + Future back() { + onUserNavigate(); + return n.back(); + } + + @override + Future forward() { + onUserNavigate(); + return n.forward(); + } + + @override + Future reload() { + onUserNavigate(); + return n.reload(); + } + + @override + Future stop() => n.stop(); + @override + Future exportCookies() => n.exportCookies(); + @override + Future pauseMedia() => n.pauseMedia(); + @override + Future runScript(String js) => n.runScript(js); + @override + Future clearData() => n.clearData(); + @override + Future evaluate(String js) => n.evaluate(js); + @override + Future setJavaScript(bool on) => n.setJavaScript(on); +} + /// 페이지의 모든 동영상 · 소리를 멈추는 스크립트 const pauseMediaScript = "document.querySelectorAll('video,audio').forEach(function(m){try{m.pause()}catch(e){}})"; @@ -168,6 +215,12 @@ class _BrowserPageState extends State with RouteAware { /// [error]: 엔진이 알려 준 본문 오류. 프록시 터널 실패이고 DNS 서버에 지금 닿지 않는 중이면, 그 이름의 실패 기록이 없어도 /// (Chromium 이 실패한 프록시를 기억해 다시 묻지 않는 때 등) DNS 때문으로 본다. /// 덮개는 여기서 걷지 않는다 (새로 읽기 시작 · [다시 시도] 에서) - Android 는 오류 뒤에 읽기 끝이 또 온다. + /// 250: 사용자가 다른 곳으로 가거나 다시 읽음 - 덮개를 걷는다 (다시 실패하면 또 뜬다) + void _userNavigated() { + _loadStartAt = DateTime.now(); + if (_dnsBlocked != null && mounted) setState(() => _dnsBlocked = null); + } + void _checkDnsBlocked({String? host, String? error}) { final h = host ?? Uri.tryParse(_url)?.host ?? ''; if (h.isEmpty) return; @@ -177,7 +230,10 @@ class _BrowserPageState extends State with RouteAware { if (server == null && f != null && error != null && (error.contains('TUNNEL') || error.contains('PROXY'))) server = f.server; if (server == null) return; final next = (h, server); - if (next != _dnsBlocked) setState(() => _dnsBlocked = next); + if (next == _dnsBlocked) return; + // 기기에서 "덮개를 띄웠는데 안 보임" 과 "띄우지 않음" 을 가릴 수 있게 + widget.c.note(trf('DNS 안내 덮개를 띄움 ({0} · DNS {1})', [h, server])); + setState(() => _dnsBlocked = next); } /// 250: DNS 실패면 웹 뷰 위에 덮는다 ([시스템 DNS 로 바꾸기] [다시 시도]). @@ -253,11 +309,8 @@ class _BrowserPageState extends State with RouteAware { onTitle: (t) => mounted ? setState(() => _title = t) : null, onProgress: (p) { if (!mounted) return; - // 250: 새로 읽기 시작 - 지난 DNS 실패 화면을 걷고 시작 시각을 적어 둔다 - if (p < 1 && _progress >= 1) { - _loadStartAt = DateTime.now(); - _dnsBlocked = null; - } + // 250: 새로 읽기 시작 - 시작 시각을 적어 둔다 (덮개는 여기서 걷지 않는다 - [_userNavigated]) + if (p < 1 && _progress >= 1) _loadStartAt = DateTime.now(); setState(() => _progress = p); if (p >= 1) { _checkDnsBlocked(); @@ -272,7 +325,7 @@ class _BrowserPageState extends State with RouteAware { _canFwd = f; }); }, - attach: (n) => _nav = BrowserPage.debugNav = n, + attach: (n) => _nav = BrowserPage.debugNav = _NavWatch(n, _userNavigated), // 250: 엔진의 본문 오류 (Edge · Android). 읽기 끝 (진행률) 만으로는 Android 에서 덮개가 뜨지 않았다 (931, 노트 10+) onMainFrameError: (u, e) { if (!mounted) return; diff --git a/test/browser_test.dart b/test/browser_test.dart index 627e5ff..50d03ff 100644 --- a/test/browser_test.dart +++ b/test/browser_test.dart @@ -399,6 +399,12 @@ void main() { host.onProgress(1); await tester.pump(); expect(find.text('DNS 서버 (192.0.2.1) 에 닿지 않아 example.org 을(를) 열 수 없습니다.'), findsOneWidget); + // 932 (노트 10+): Android 는 본문 오류 뒤에 엔진의 오류 페이지를 또 읽는다 (진행률 0 → 100) - 덮개가 걷히면 안 된다 + host.onProgress(0.1); + await tester.pump(); + host.onProgress(1); + await tester.pump(); + expect(find.text('DNS 서버 (192.0.2.1) 에 닿지 않아 example.org 을(를) 열 수 없습니다.'), findsOneWidget); // DNS 와 상관없는 오류 (이름 기록도 없음) 는 엔진 화면 그대로 await tester.tap(find.text('다시 시도')); await tester.pump();