T3-11/14/15 + M2 잔여: 권한 정책 UI(PermissionGate/PermissionRequested 다이얼로그+SavesInProfile), 추적 방지(TrackerBlocklist/WebResourceRequested 차단+실드 배지·팝업), 주소창 진행 바+파라미터 제거 복사(UrlCleaner/NavProgressTracker), U5 완료 알림(Mobile.Core 감지기+Plugin.LocalNotification), 재생중 ongoing 미디어 알림, 썸네일/해상도 API(Thumbnailer 사이드카 캐시+/thumb)+라이브러리 그리드, 외부망 안내(ConnectionTroubleshoot). 147 tests GREEN

This commit is contained in:
Yun Chan 2026-08-15 18:11:49 +09:00
parent 27c91c04dd
commit 46c6c321f2
26 changed files with 1058 additions and 30 deletions

View file

@ -214,10 +214,33 @@ public partial class MainWindow : Window
_views.GlanceRequested += (_, link) => Dispatcher.BeginInvoke(new Action(() => OpenGlance(link))); // T2-6
_views.SpeakRequested += (_, text) => Dispatcher.BeginInvoke(new Action(() => SpeakText(text))); // T3-9
_views.ContextMenuBuild += OnContextMenuBuild;
_views.BoostScriptProvider = url => BoostCssBuilder.BuildScript(url, _boostStore.Get(url)); // T2-7
_views.GlanceRequested += (_, link) => Dispatcher.BeginInvoke(new Action(() => OpenGlance(link))); // T2-6
_views.SpeakRequested += (_, text) => Dispatcher.BeginInvoke(new Action(() => SpeakText(text))); // T3-9
// T3-15 주소창 진행 바: 전 탭 네비게이션 집계 → 표시
_navTracker = new NavProgressTracker();
_navTracker.PropertyChanged += (_, e) =>
NavProgress.Visibility = e.PropertyName == nameof(NavProgressTracker.IsNavigating) && _navTracker.IsNavigating
? Visibility.Visible : Visibility.Collapsed;
_views.NavigationStarted += _navTracker.Started;
_views.NavigationCompleted += _navTracker.Completed;
// T3-11 권한 다이얼로그: 예=항상 허용 / 아니요=항상 거절 / 취소=이번만 거절
_views.PermissionPrompt += (cv, e) => Dispatcher.BeginInvoke(new Action(() =>
{
var kind = e.PermissionKind.ToString();
var origin = Services.TabViewService.OriginOf(e.Uri);
var r = System.Windows.MessageBox.Show(this,
$"{origin} 사이트가 {KoreanPermission(kind)} 권한을 요청합니다.\n허용하시겠습니까?\n\n예: 항상 허용 · 아니요: 항상 거절 · 취소: 이번만 거절",
"권한 요청", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
e.State = r == MessageBoxResult.Yes ? Microsoft.Web.WebView2.Core.CoreWebView2PermissionState.Allow
: Microsoft.Web.WebView2.Core.CoreWebView2PermissionState.Deny;
e.SavesInProfile = r != MessageBoxResult.Cancel; // 항상 선택만 프로필 영속
e.Handled = true;
if (r != MessageBoxResult.Cancel)
_views.Permissions.Remember(kind, origin,
r == MessageBoxResult.Yes ? PermissionVerdict.Allow : PermissionVerdict.Deny);
}));
// T3-14 실드 배지 갱신
_views.TrackerBlocked += UpdateShield;
_tabs = new TabManager(() => new TabItem(), _views.AttachView);
_tabs.ActiveChanged += OnActiveTabChanged;
@ -284,6 +307,52 @@ public partial class MainWindow : Window
/// <summary>탭별 WebView2 생명주기·배선 전담 서비스(재구성 ④).</summary>
private Services.TabViewService? _views;
/// <summary>T3-15 주소창 진행 바 상태(전 탭 네비게이션 집계).</summary>
private NavProgressTracker? _navTracker;
/// <summary>T3-15: 파라미터 제거 후 클립보드 복사(주소창 우클릭).</summary>
private void CopyCleanUrl_Click(object sender, RoutedEventArgs e)
{
var clean = UrlCleaner.StripQuery(AddressBox.Text);
if (string.IsNullOrEmpty(clean)) return;
try { Clipboard.SetText(clean); Log($"파라미터 제거 URL 복사: {clean}"); } catch { }
}
/// <summary>T3-14: 실드 배지 갱신.</summary>
private void UpdateShield()
{
if (_views == null) return;
var n = _views.TrackerCounter.Total;
ShieldBadgeText.Text = n > 0 ? n.ToString() : "";
ShieldBadge.Visibility = n > 0 ? Visibility.Visible : Visibility.Collapsed;
ShieldBtn.ToolTip = n > 0 ? $"추적 요청 {n}건 차단됨" : "추적 방지";
}
/// <summary>T3-14: 실드 클릭 → 차단 도메인 목록 팝업.</summary>
private void Shield_Click(object sender, RoutedEventArgs e)
{
if (_views == null) return;
var byDomain = _views.TrackerCounter.ByDomain;
ShieldList.ItemsSource = byDomain.Count == 0
? new[] { "차단된 추적 요청이 없습니다" }
: byDomain.OrderByDescending(kv => kv.Value).Select(kv => $"{kv.Value,3} × {kv.Key}").ToList();
ShieldPopup.IsOpen = true;
}
/// <summary>T3-11: 권한 종류 한글 표기.</summary>
private static string KoreanPermission(string kind) => kind switch
{
"Microphone" => "마이크",
"Camera" => "카메라",
"Geolocation" => "위치",
"Notifications" => "알림",
"ClipboardRead" => "클립보드 읽기",
"Autoplay" => "자동 재생",
"FileReadWrite" => "파일 읽기/쓰기",
"PersistentStorage" => "영구 저장소",
_ => kind,
};
private void OnUrlChanged(TabItem tab, string url)
{
if (tab.PendingHistoryUrl != null) RecordHistory(tab.PendingHistoryUrl, tab.PendingHistoryUrl);