두 번째 계정이 새는 곳
탭 둘을 떼어 놓는 건 일의 절반이야. 페이지는 또 페이지를 열거든. 로그인 창, target="_blank" 링크, 새 탭에서 열리는 결과. 그중 하나라도 다른 저장소에 떨어지면 두 번째 계정이 슬그머니 첫 번째 계정이 되거나, 팝업에서 끝낸 로그인이 그걸 요청한 탭에 끝내 안 닿아. 아키텍처 불변식 7번은 이걸 두 문장으로 다 막아. 탭에서 연 링크, 팝업, 리디렉션은 그 탭의 아이덴티티에 남는다. 아이덴티티 사이로 쿠키를 옮기는 건 아무것도 없다.
Voyager는 새 페이지가 생기는 길마다 다른 방법으로 이 규칙을 지켜.
리디렉션: 정할 게 없어
리디렉션은 서버가 보내든 스크립트가 location을 바꾸든 같은 웹뷰 안의 이동이야. WKWebView는 만들어질 때 데이터 저장소까지 포함한 설정을 받고, 살아 있는 뷰의 저장소는 못 바꿔. 그러니 리디렉션은 아이덴티티를 떠날 수가 없고, Voyager에도 그걸 위한 코드가 없어. 이동에 다른 설정이 필요한 곳은 딱 하나, 확장 프로그램 자체 페이지와 보통 페이지 사이야. 거기선 페이지를 새 뷰로 갈아 끼워(트랙 8).
팝업: WebKit이 설정을 건네줘
페이지가 window.open을 부르거나 target="_blank" 링크를 따라가면, WebKit은 UI 델리게이트한테 새 웹뷰를 달라고 하면서 설정 하나를 같이 넘겨. Apple 문서가 조건을 박아 뒀어. 돌려주는 웹뷰는 넘겨받은 그 설정으로 만들어야 한다고. 그 설정에 여는 쪽의 데이터 저장소가 들어 있고, 로그인 팝업이 자기를 연 페이지에 결과를 알리는 데 필요한 연결도 같이 들어 있어.
Voyager의 자식은 딱 그 설정으로 새 페이지를 만들고, 여는 쪽의 아이덴티티를 이름표로 붙여. 따로 찾아볼 필요가 없는 이유는 주석이 말해. 자식은 만들어지는 방식 자체로 여는 쪽의 아이덴티티에 있다는 거야. 그러고 나서 부모한테 이 페이지를 둘지 물어. 거절하면 자식을 닫고 nil을 돌려줘. 페이지 쪽에선 window.open이 null을 돌려받는 걸로 보여.
그래도 부모가 한 번 더 봐
BrowserTab은 자식을 창에 넘기고, 창의 adopt는 여는 쪽과 아이덴티티가 다른 자식을 거절해. WebKit 자식에선 이 검사가 걸릴 일이 없어. 몇 줄 위에서 여는 쪽 아이덴티티를 그대로 복사했으니까. 그래도 있는 건 부모가 WebKit을 못 보기 때문이야. 규칙이 부모 자기 말로도 있어야 가짜 엔진이 브라우저 없이 시험할 수 있어.
aPageOpenedByAPageJoinsOnlyInTheSameIdentity가 바로 그걸 해. 여는 탭한테 새 아이덴티티를 가진 낯선 페이지를 건네면 창이 거절하고 탭은 여전히 하나야. 여는 쪽 아이덴티티의 자식은 바로 다음 탭이 되고 앞으로 나와. 코드에서 하나 더. 페이지가 크기를 정한 창을 달라고 해도 자식은 늘 여는 탭 바로 뒤의 탭이 돼. 요청엔 창 속성에서 읽은 wantsOwnWindow가 적히지만, 지금 창 모델은 그걸 쓰지 않아.
새 탭에서 여는 링크
Command 클릭, 가운데 버튼 클릭, 컨텍스트 메뉴의 Open Link in New Tab(새 탭에서 링크 열기)은 WebKit 팝업 경로를 아예 안 타. 두 클릭은 자식이 이동을 취소하고 부모한테 wantsNewTabAt을 알리면, 탭이 자기 뒤에 자기 아이덴티티로 탭을 열어 답해. 메뉴 명령은 엔진까지 가지도 않아. 셸이 직접 model.openTab을 부르면서 그 탭의 아이덴티티를 넘기고, 새 탭을 그 탭 뒤에 놓고, 앞으로 꺼내지 않아. Command만 누르면 뒤에 열리고, Shift를 더하면 앞으로 나오고, 가운데 버튼은 뒤에 열어. Chrome이랑 같아. aLinkedTabKeepsItsOpenersIdentity는 "Work" 아이덴티티 탭에서 링크를 열고 Work 탭 둘을 기대해. 탭 복제도 아이덴티티를 그대로 지켜.
창은 클릭이 있어야 열려
엔진이 만드는 설정은 전부 javaScriptCanOpenWindowsAutomatically를 false로 둬. Apple 정의로는 JavaScript가 사용자 동작 없이 창을 열어도 되는지를 정하는 설정이야. 옆 주석은 페이지가 창을 여는 건 아빠가 직접 클릭해서 원했을 때뿐이라고 적어 뒀어.
실제 WebKit 테스트 aPageOpenedByAPageIsInItsIdentityAndCanBeRefused는 먼저 이 설정이 꺼져 있는지 보고, 자기 설정에서만 켜서 클릭 대신으로 써. 400×300 창을 열고 셋을 확인해. 자식 아이덴티티가 여는 쪽 것인지, 자식의 데이터 저장소가 여는 쪽과 바로 같은 객체인지(===), wantsOwnWindow가 참인지. 이어서 자식이 window.close()로 스스로 닫고, 부모가 그걸 들어. 마지막으로 부모가 자식을 거절하게 해 두면 window.open이 null을 돌려줘.
이름 붙인 틈 하나
탭에서 나가는 길이 다 아이덴티티를 들고 가진 않아. 컨텍스트 메뉴의 Open Link in New Window(새 창에서 링크 열기)는 새 창을 여는데, 새 창의 첫 탭은 기본 아이덴티티를 받아. Work 탭의 링크를 이렇게 열면 기본 아이덴티티로 로그인된 채 도착해. 구현 계획서엔 링크를 다른 아이덴티티로 열거나 탭을 다른 아이덴티티로 다시 여는 기능, 그리고 사이트별 기본 아이덴티티가 아직 열린 항목으로 남아 있어. 다른 창으로 끌어다 놓은 탭은 아이덴티티를 지켜. 옮기는 탭이 저장된 아이덴티티를 같이 들고 가거든. 끌어 온 탭이 시크릿 창과 보통 창 사이를 건너는 일은 없어. 그래도 모델이 그 선을 어떻게 지키는지는 레슨 3에서 봐.