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:
parent
27c91c04dd
commit
46c6c321f2
26 changed files with 1058 additions and 30 deletions
78
VideoDownloader.Browser/PermissionGate.cs
Normal file
78
VideoDownloader.Browser/PermissionGate.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
namespace VideoDownloader.Browser;
|
||||
|
||||
public enum PermissionVerdict { Ask, Allow, Deny }
|
||||
|
||||
/// <summary>
|
||||
/// T3-11 사이트별 권한 정책 — 출처×종류 단위 기억. WebView2 PermissionRequested 에서
|
||||
/// Ask 만 프롬프트로 올리고 Allow/Deny 는 즉시 응답. "항상" 선택은 네이티브
|
||||
/// Profile.SetPermissionStateAsync 로도 영속(세션 밖 저장).
|
||||
/// </summary>
|
||||
public sealed class PermissionGate
|
||||
{
|
||||
/// <summary>프롬프트 없이 거절: 센서류(피싱 표면 축소).</summary>
|
||||
private static readonly HashSet<string> AutoDeny = new(StringComparer.OrdinalIgnoreCase) { "OtherSensors" };
|
||||
/// <summary>프롬프트 없이 허용: 연속 다운로드(다운로더 앱 본업).</summary>
|
||||
private static readonly HashSet<string> AutoAllow = new(StringComparer.OrdinalIgnoreCase) { "MultipleAutomaticDownloads" };
|
||||
|
||||
private readonly Dictionary<(string origin, string kind), PermissionVerdict> _saved = new();
|
||||
|
||||
public PermissionVerdict Decide(string kind, string origin)
|
||||
{
|
||||
if (AutoDeny.Contains(kind)) return PermissionVerdict.Deny;
|
||||
if (AutoAllow.Contains(kind)) return PermissionVerdict.Allow;
|
||||
return _saved.GetValueOrDefault((origin, kind), PermissionVerdict.Ask);
|
||||
}
|
||||
|
||||
public void Remember(string kind, string origin, PermissionVerdict verdict)
|
||||
=> _saved[(origin, kind)] = verdict;
|
||||
}
|
||||
|
||||
/// <summary>T3-14: 추적 스크립트 블랙리스트 — 호스트 또는 서브도메인 매칭.</summary>
|
||||
public sealed class TrackerBlocklist
|
||||
{
|
||||
/// <summary>주요 광고/분석 도메인(EasyList 공통 항목 발췌). 생성자 주입으로 교체 가능(테스트).</summary>
|
||||
public static readonly string[] DefaultDomains =
|
||||
{
|
||||
"google-analytics.com", "googletagmanager.com", "doubleclick.net", "googlesyndication.com",
|
||||
"googleadservices.com", "scorecardresearch.com", "hotjar.com", "mixpanel.com", "segment.io",
|
||||
"facebook.net", "connect.facebook.net", "amplitude.com", "branch.io", "appsflyer.com",
|
||||
"criteo.com", "taboola.com", "outbrain.com", "adnxs.com", "pubmatic.com", "rubiconproject.com",
|
||||
};
|
||||
|
||||
private readonly HashSet<string> _domains;
|
||||
|
||||
public TrackerBlocklist() : this(DefaultDomains) { }
|
||||
public TrackerBlocklist(IEnumerable<string> domains) =>
|
||||
_domains = new HashSet<string>(domains, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public bool Matches(Uri url)
|
||||
{
|
||||
var host = url.Host;
|
||||
foreach (var d in _domains)
|
||||
if (host.Equals(d, StringComparison.OrdinalIgnoreCase) ||
|
||||
host.EndsWith("." + d, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>T3-14: 차단 카운터 — 총계/도메인별 집계, 변경 통지(실드 배지 갱신용).</summary>
|
||||
public sealed class TrackerCounter
|
||||
{
|
||||
private int _total;
|
||||
public int Total => Volatile.Read(ref _total);
|
||||
public Dictionary<string, int> ByDomain { get; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
public event Action? Changed;
|
||||
|
||||
public void Record(string url)
|
||||
{
|
||||
var host = new Uri(url).Host;
|
||||
var baseDomain = host.StartsWith("www.", StringComparison.OrdinalIgnoreCase) ? host[4..] : host;
|
||||
Interlocked.Increment(ref _total);
|
||||
lock (ByDomain)
|
||||
{
|
||||
ByDomain[baseDomain] = ByDomain.GetValueOrDefault(baseDomain) + 1;
|
||||
}
|
||||
Changed?.Invoke();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue