Voyager의 시크릿 모드는 브라우저에 덧댄 별도 모드가 아니야. 아이덴티티가 하나 더 있는 거고, 다른 점은 둘뿐이야. 하나, id가 00000000-0000-4000-8000-0000000000F1로 고정돼 있고 어느 IdentityBook에도 안 들어가. 주석 말로는 이 아이덴티티에 관한 건 어디에도 아무것도 안 적힌대. 둘, 저장소가 WKWebsiteDataStore(forIdentifier:)가 아니라 WKWebsiteDataStore.nonPersistent()야. Apple은 이걸 웹사이트 데이터를 메모리에만 두고 디스크엔 안 쓰는 저장소라고 설명하면서, 비공개 브라우징을 만들 때 쓰라고 해.
시크릿 모드도 아이덴티티니까 앞의 두 레슨에서 쌓은 게 공짜로 다 따라와. 시크릿 페이지의 팝업은 시크릿 설정으로 만들어지고, 새 탭으로 연 링크는 시크릿 아이덴티티를 받고, 쿠키를 안팎으로 옮기는 건 아무것도 없어.
시크릿 창
BrowserWindowModel은 만들어질 때 시크릿인지 아닌지가 한 번 정해져. 시크릿 창에선 누가 뭘 요청하든 모든 탭이 시크릿 아이덴티티야. 거기선 openTab이 아이덴티티 인자를 무시해. privateWindowsCannotImportAnAccountOrRestoreHistory 테스트는 기본 아이덴티티를 대놓고 지정해서 시크릿 탭을 여는데, 그래도 받는 건 .privateSession이야.
이런 창은 보자마자 알아볼 수 있어. 주소를 고치는 중에도 툴바에 보라색 시크릿 표시가 남아 있고, 새 시크릿 창은 홈 대신 로컬 안내 화면으로 열려. 그래서 가족 카탈로그 받으러 가족 허브에 연락하지도 않아.
이제 일어나지 않는 쓰기들을 볼 차례야. 하나하나가 부모의 isPrivate 검사거나 자식의 시크릿 id 검사야.
방문 기록 없음. noteVisit이 적기 전에 돌아가.
세션 없음. 창 스냅숏엔 탭이 하나도 없고, 복원하면 빈 탭 하나가 새로 열리고, 닫은 시크릿 탭은 다시 못 열어.
비밀번호 없음. 로그인을 저장하겠냐고 안 묻고, 서버가 로그인을 요구해도 저장된 걸로 대신 답하지 않아.
계정 바로가기도 시크릿 아이덴티티를 가리킬 수 없어(레슨 4).
확장 프로그램 없음. 시크릿 페이지 설정엔 확장 컨트롤러가 안 붙고, 어떤 확장도 시크릿 창을 나열하거나 앞으로 불러올 수 없어.
피파 없음. 내 패널이 안 열리고, 페이지 맥락을 달라는 요청엔 전부 Page sharing is off in Incognito.(시크릿에선 페이지 공유 꺼짐)가 돌아가. 툴바도 Pippa is off in Incognito(시크릿에선 피파 꺼짐)라고 알려 줘.
쿠키가 안 나가. 부모가 던지는 쿠키 질문 하나에 엔진은 시크릿 아이덴티티면 nil로 답하고, 시크릿 쿠키가 바뀌었다고 알리지도 않아(레슨 5).
일상 UX 리뷰가 빈 구석을 채워 줬어. 시크릿 페이지의 파비콘은 메모리에만 캐시되고, 세션이 끝나면 시크릿 다운로드는 목록에서 사라져. 받은 파일과 아빠가 일부러 저장한 북마크는 남고, 이미 진행 중인 다운로드는 끝까지 갈 수도 있어.
시크릿 저장소 하나, 마지막 창에서 버린다
Chrome의 시크릿 창들이 세션 하나를 나누는 것처럼, 시크릿 창은 전부 임시 저장소 하나를 같이 써. 시작 화면도 아빠한테 두 문장으로 말해 줘. 탭들은 평소 계정과 떨어진 임시 세션을 나눠 쓰고, 시크릿 창을 전부 닫아야 새로 시작한다고. closingThePrivateSessionResetsOnlyItsCookiesAndStorage 테스트가 이걸 못 박아. 시크릿 페이지 둘은 같은 저장소 객체를 보고, 한쪽에서 쓴 값이 다른 쪽에 나타나고, 보통 페이지는 둘 다 못 봐.
마지막 시크릿 창이 닫히면 앱 델리게이트가 endPrivateSession()을 불러. 계약은 엔진한테 이렇게 요구해. 다음 시크릿 창이 열리기 전에, 임시 세션 저장소만 동기적으로 버려라. WebKit 자식은 평범한 동기 호출로 저장소 딕셔너리에서 시크릿 항목을 빼. 다음 시크릿 페이지가 저장소를 찾으면 없으니까 완전히 새 비영속 저장소를 받아. 테스트가 확인하는 게 딱 그거야. endPrivateSession() 뒤에 연 시크릿 페이지는 다른 저장소를 갖고 null|을 보는데, 보통 페이지는 여전히 normal|fixture=normal을 읽어. 초기화되는 건 시크릿 세션뿐이야.
선 넘기
탭은 창에서 창으로 끌어 옮길 수 있어. 시크릿 경계를 넘어 상태를 실어 나를 수 있는 움직임은 이런 이동뿐이야. 근데 셸은 애초에 그 경계를 넘게 해 주지 않아. 끌어 놓을 창을 찾을 때 isPrivate가 같은 창만 고르니까, 끌어 온 탭은 같은 종류의 창에만 내려앉아. Move Tab to New Window(탭을 새 창으로 옮기기)도 같은 종류의 창을 열고. 그래도 모델 쪽 attach가 그 선을 한 번 더 지켜. 나중에 누가 새로 부르는 코드를 짜도 실수로 넘지 못하게. 거기선 시크릿 창에 들어오는 탭은 시크릿 아이덴티티를 받고, 보통 창에 들어오는 시크릿 탭은 기본 아이덴티티를 받게 돼 있어. 어느 쪽이든 탭은 주소와 제목은 지키고 복원 상태는 잃어. 그 상태는 다른 저장소 거였으니까. 주석이 한 문장으로 말해. 시크릿 경계를 넘는 이동은 다른 저장소의 방문 기록이나 로그인 상태를 가져올 수 없다.
약속하지 않는 것
리뷰가 분명히 적어 뒀어. 시크릿은 사이트나 네트워크 운영자한테 트래픽을 숨겨 주지 않는다고. 시크릿은 이 Mac이 그 세션에 대해 아무것도 남기지 않는다는 뜻이야. 사이트는 방문을 그대로 보고, 그 사이의 네트워크도 마찬가지야.
/// Removes an identity's cookies and site storage for good. No page of
/// that identity may still be open.
public func removeData(of identity: IdentityID) async throws {
if let watcher = cookieWatchers.removeValue(forKey: identity) { stores[identity]?.httpCookieStore.remove(watcher) }
stores[identity] = nil
guard identity != .privateSession else { return }
try await WKWebsiteDataStore.remove(forIdentifier: identity.raw)
}
public func endPrivateSession() {
stores[.privateSession] = nil
downloads.endPrivateSession()
}
cwkVoyager, Sources/VoyagerCore/BrowserWindowModel.swift: 옮긴 탭은 시크릿 경계 너머로 상태를 못 가져간다 (원문 그대로, 398-413행)·swift
/// Puts a tab taken from another window in at the end.
public func attach(_ saved: SessionTab) {
// Moving a tab preserves its Sidekick key. A cross-privacy move cannot
// import another store's history or sign-in state.
let identity = isPrivate ? IdentityID.privateSession
: (saved.identity == .privateSession ? services.defaultIdentity : saved.identity)
let tab = BrowserTab(
id: TabID(saved.id), identity: identity,
status: PageStatus(url: saved.url, title: saved.title, favicon: saved.favicon),
restorable: identity == saved.identity ? saved.state : nil, pinned: false)
tab.sharesContextWithPippa = saved.sharesContextWithPippa
tab.window = self
tabs.append(tab)
select(tab.id)
sessionChanged()
}
웹 콘텐츠를 보여 주는 앱에 시크릿 모드를 넣어 봐. 모든 시크릿 뷰가 나눠 쓰는 WKWebsiteDataStore.nonPersistent() 하나로 받치고, 마지막 시크릿 뷰가 닫히면 그 저장소 참조를 버려. 그리고 페이지 때문에 앱이 뭔가 적는 곳을 전부 찾아 적어. 방문 기록, 세션 복원, 최근 닫은 목록, 저장된 비밀번호, 썸네일이나 아이콘 캐시, 로그, 페이지를 읽는 어시스턴트나 확장 같은 곳 말이야. 하나마다 검사를 달고, 시크릿 페이지를 열어서 아무것도 안 적혔다는 걸 보이는 테스트를 붙여.
Hint
디스크에 쓰는 곳과 페이지 데이터를 밖으로 보내는 호출을 전부 grep으로 찾고, 하나씩 물어봐. 시크릿 페이지가 여기 닿을 수 있나? 이동도 잊지 마. 탭 끌어 옮기기나 닫은 탭 다시 열기는 상태가 저장소 사이를 건너는 길이야.
Progress
Progress is local-only — sign in to sync across devices.