웹 컨텐츠
와 네이티브
의 통합
주로 앱 내 웹 브라우저가 필요하고, 해당 환경에 대한 커스텀이 필요하지 않다면, SFSafariViewController
를 사용하는 것이 좋다. 기본 브라우저를 처리하는 동안 모든 사항을 알아서 처리한다. SafariViewController
는 사용하기 쉽지만, WKWebView 위에 구축되어 있기 때문에 강력하다.
웹을 더 커스텀해야하는 경우에는 WKWebView
를 직접 구축할 수도 있다.
WKWebView는 별도의 프로세스에서 웹 컨텐츠를 분리하여 웹 플랫폼의 복잡성으로부터 앱의 코드와 데이터를 자동으로 보호한다. 이를 통해 웹 컨텐츠를 빠르게 실행하는 동시에, 악의적인 행위에 대한 보호 기능을 제공한다.
If your app lets users view websites from anywhere on the Internet, use the SFSafariViewController class. If your app customizes, interacts with, or controls the display of web content, use the WKWebView class.
앱에서 사용자가 인터넷의 모든 위치에서 웹 사이트를 볼 수 있도록 허용하는 경우 SFSafariViewController 클래스를 사용한다. 앱에서 웹 컨텐츠 표시를 커스텀, 상호작용, 또는 제어하려는 경우 WKWebView 클래스를 사용한다.
WebView와 UIWebView는 웹 컨텐츠를 내장하기 위한 API였지만, 여러 단점이 있어 deprecated되었으며 WebView의 사용을 0으로 줄이려고 노력하고 있다.
그래서 WKWebView를 사용하라고 말하고 있음!
WKWebView API는 여전히 성장하고 있으며, 매년 성장할 것이다.
오늘 WKWebView가 iOS 14와 macOS Big Sur에 가지고 있는 새로운 기능에 대해 이야기 하려고 한다.
먼저 앱과 웹 컨텐츠를 서로 분리하는데 도움이 되는 새로운 API에 대해 설명한다.
그리고 JavaScript를 통해 웹 컨텐츠와 상호작용 하는 개선된 방식에 대해 설명한다.
앱에서 웹 컨텐츠 렌더링을 조정하는데 도움이 되는 몇가지 API들을 살펴보고,
웹 컨텐츠로부터 더 많은 가치를 얻는 방법에 대해 알아본다.
마지막으로, 개인 정보 보호와 관련된 주제를 다룬다.
WebKittens이라는 웹사이트가 있는데, 아이폰보다 훨씬 오래된 웹사이트이다.
그리고 누군가 UIWebView를 사용하여 WebKittens 앱을 구성했다.
Puppies on Safari 웹사이트 또한 많은 강아지들에 대한 최신 정보를 유지하는 웹사이트이다.
그리고 WebKitten과 마찬가지로 Puppies on Safari에도 노후화된 아이폰 앱이 있다.
그래서 두 앱을 여러번 확인해야하는 것이 번거롭기 때문에, 두 사이트를 단일 피드인 Browser Pets App
앱으로 작성하려고 한다.
이 때 직면한 첫번째 과제는 웹 컨텐츠에서 애플리케이션의 로직을 분리하는 것이다.
// 스크롤 안되고 튕기고 있는 중
이 앱의 첫 "hello world" 모먼트는 WebKittens의 뉴스피드를 보여주는 것이었다.
하지만 피드를 스크롤하면 안되고 튕겨버린다.
WebKitten 웹사이트의 스크롤은 JavaScript에 의해 많이 커스텀되어 있다.
브라우저에 있는 페이지는 괜찮지만, 앱에서는 그렇지 않다.
WKWebView API는 javaScriptEnabled
프로퍼티를 false로 설정하여 JavaScript를 항상 비활성화하는 메서드를 갖고 있다.
범용 웹브라우저를 작성하지 않는 한 원격 소스, 특히 제어할 수 없는 소스에서 JavaScript를 비활성화할 수 있는지 자문해보는 것이 좋다.
WKWebView를 사용하면 뷰 내부 컨텐츠와의 대부분의 상호작용이 JavaScript를 평가하는 것이기 때문에 문제가 될 수 있다.
(평가 - 파싱, 컴파일, 메모리상의 로드 등의 작업)
그래서, 올해 그 설정을 폐지했고, 새로운 것을 추가했다.
WKWebPagePreferences에서 allowContentJavaScript
설정을 사용하면 웹페이지 컨텐츠 자체에서 가져온 JavaScript만 비활성화할 수 있다. 인라인 스크립트, 원격으로 참조되는 JavaScript 파일, JavaScript URL 등등..
하지만 앱의 JavaScript는 계속 작동할 것이다.
최근에 추가된 기능으로 navigation별로 특정 동작을 구성할 수 있다.
여기서는 iPad에서 실행중인 경우에도 모바일 디바이스로 표시할 수 있도록 웹 페이지 기본 설정을 구성할 수 있는 policy delegate의 최신 버전을 사용하고 있다.
그리고 여기서는 기본 뉴스 피드를 보는 동안 ContentJavaScript를 비활성화할 수도 있다.
이 변경 사항을 적용하면 스크롤이 예상대로 작동한다.
// 스크롤 잘되는 중
이제 뉴스 피드를 마음대로 스크롤할 수 있다.
이제 게시물의 댓글을 확인해보려고 한다.
그리고 바로 다음 문제 마주침..
이 페이지의 하단이 댓글로 채워져야 하는데, 공백이다.
그 댓글들은 JavaScript로 채워져있다.이제 Web Inspector를 볼 차례다.
Web Inspector는 WebKit 애플리케이션의 웹 컨텐츠에서 발생하는 현상을 탐색하는 좋은 방법이다.
여기 JavaScript 콘솔에 에러가 있다.
웹페이지는 "comment details" symbol이 function이 될 것으로 예상하지만, 그렇지 않다.
"window.commentDetails is null"
앱에서 symbol을 검색했고, 이 코드를 찾았다.
공교롭게도, 내 앱 코드에도 commentDetails
라는 JavaScript symbol이 웹페이지에 삽입되어 있다.
그리고 내 JavaScript를 평가할 때, 그 function을 관계없는 변수로 덮어쓴다.
이러한 유형의 충돌은 WKWebViews와 함께 작업할 때 항상 발생하는 문제이다. 프로그램이 웹 컨텐츠와 충돌할 수 있다.
또는 웹 컨텐츠가 실수로 앱과 충돌할 수도 있다. 악의적인 웹 컨텐츠는 프로그램의 동작을 변경하거나 프로그램에서 중요한 정보를 도용하여 의도적으로 프로그램과 충돌할 수 있다. 이것들은 우리의 JavaScript와 웹페이지의 JavaScript가 같이 실행되는 한 모두 가능하다.
이 문제를 해결하려면 JavaScript를 앱 JavaScript와 별도로 실행할 수 있는 격리된 장소가 필요하다.
JavaScript 코드의 실행 범위를 정의하고 다른 스크립트 간의 충돌을 방지하는 데 사용하는 개체
그것이 WKContentWorld가 등장하는 이유이다. WKContentWorld는 JavaScript를 실행하기 위한 격리된 샌드박스이다.
만일 JavaScript에 익숙하다면, 동일한 페이지 내용에 대해 별도의 window 객체를 갖는 것과 같다.
현재의 웹페이지 컨텐츠 자체를 나타내는 page world와,
앱의 JavaScript를 나타내는 client world가 있다.
client world에서 실행되는 앱의 JavaScript는 여전히 페이지에 내장된 DOM(문서 객체 모델) API를 호출하거나, DOM 자체를 변경하는 등의 작업을 수행할 수 있지만, page의 JavaScript에 의해 설정된 앱의 상태는 볼 수 없다.
마찬가지로 page의 JavaScript도 우리의 JavaScript를 볼 수 없다.
앱에서 이 문제를 해결하기 위해 모든 evaluateJavaScript
호출에 기본 client world를 사용한다.
그리고 이제 앱의 JavaScript와 page의 JavaScript가 충돌하지 않기 때문에, 댓글을 확인할 수 있다.
이제 내 앱과 웹 큰텐츠를 서로로부터 잘 보호한 것 같다.
Browser Pets을 개발하는 다음 단계는 훨씬 더 많은 JavaScript를 포함한다.
그 JavaScript 코드를 통제하기 위해, 내 앱이 사용하는 JavaScript를 관리하는 몇가지 방법을 사용했다.
WebKittens 웹사이트에 Pups on Safari 웹 컨텐츠를 통합하기 위해 JavaScript를 주입했다. (😻 + 🐶)
Pups on Safari 사이트에는 게시물을 가져오기 위한 JavaScript API가 있다. 이것이 결합된 뉴스 피드를 작동시키기 위해 사용해온 것이다.
JavaScript를 다른 값으로 재사용하고 싶을 때, 파라미터로 함수를 호출하는 것처럼 매번 완전히 새로운 JavaScript String을 생성해야 했다.
또 어색한 점은, 데이터를 전달하는 것인데, 이 문자열들이 0을 나타낼까?
앱 코드의 다른 곳에서는 기본적으로 Integer지만, string components로부터 JavaScript string을 만들도록
그것을 String으로 변환해야 한다.
이 native dictionary의 각 항목에 대해 JavaScript 문자열을 수동으로 추가하는 방법은 더 안좋은 방법이다.
좋은 방법은, 명명된 파라미터가 있는 함수와 동일한 상수 JavaScript 문자열을 재사용할 수 있고, 이러한 파라미터에 대해 걱정하지 않는것이다.
바로 callAsyncJavaScript
가 하는 일들이다. callAsyncJavaScript를 사용하는 것이 코드에 어떤 영향을 주는지 살펴보자.
인수로 string을 구성하지 않고도, 자연스럽게 JavaScript를 작성할 수 있다.
내 JavaScript 문자열에 대해 원하는 모든 인수의 이름을 지정하고, callAsyncJavaScript을 호출하여 그들의 값을 제공할 수 있다.
이 작은 방법이 이렇게 다른 인수의 값으로 쉽게 재사용 될 수 있다.
이 예에서 JavaScript에 명시적인 return 값이 있다는 것을 알 수 있다. 이것은 evaluateJavaScript
와 대비되는 차이점이다.
만약 명시적으로 값을 리턴하지 않으면, completion handler가 결과로 "undefined(정의되지 않음)"을 수신한다.
만약 JavaScript가 promise
를 리턴하면, completion handler가 바로 호출되지 않는다. 대신 promise가 해결되기를 기다렸다가 그 이행의 결과와 함께 호출된다.
💡 promise: 자바스크립트 비동기 처리에 사용되는 객체. 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타낸다.
이 코드는 built-in fetch 메서드를 호출한 후 반환되는 promise를 리턴한다.
즉, 해당 리소스의 바이트가 네트워크를 통해 로드될 때까지 completion handler가 호출되지 않는다.
이 예시가 modern asynchronous JavaScript와 원활하게 작동하는 방법을 보여주고 있다.
WebKittens는 몇 가지 기능을 가지고 있다. 예를 들어 새 comment가 post server-side에 추가되는 경우 페이지에 알림이 표시되고 사용자에게 알림이 간다.
이 native app이 native code에 알릴 수 있도록 새로운 comment 알림을 받기를 원했고, WKScriptMessageHandler
를 사용했다.
이 API는 원래 단방향 통신 채널로 설계되었다. 그래서 스크립트 메시지에 대한 회신을 양방향으로 처리하려면, 추가 코드가 필요하다.
예를 들어, 나중에 회신을 검색할 때 사용할 수 있는 identifier를 사용해서 각 메시지를 추적한다.
메시지 시스템이 더 정교할 수록 메시지 추적 메커니즘이 더 필요하고, 더 많은 코드를 작성하고 디버그해야 하며, 해결할 수 없는 race conditions도 있다.
이것을 해결하기 위한 새로운 변화가 2가지 있는데,
첫번째로는 postMessage
는 의미 있는 리턴 값이 없기 때문에 JavaScript에서 "undefined(정의되지않음)"을 리턴하곤 했다.
이제는 promise
를 리턴한다.
두번째로는 수신하는 각 메시지에 completion handler를 첨부하는 새로운 형식의 WKScriptMessageHandlerWithReply
프로토콜이 있다. completion handler를 호출하면 postMessage의 promise
가 해결된다.
메시지 회신을 기다리는 JavaScript 코드는 이것으로 요약된다.
promise의 해결을 통해 메시지에 대한 회신을 처리한다.
그리고 이제 좀 더 유연한 렌더링을 위해 새로운 API를 적용할 수 있다.
이것은 "responsive design(반응형 디자인)"이라는 문구가 만들어지기도 전의 오래된 웹 컨텐츠이다. 오리지널 WebKittens 앱은 초기 아이폰에서는 괜찮아 보이긴한다. 화면이 작아서 아래 댓글이 좀 안보이긴 하지만..
그리고 현대의 아이폰에서 WebKitten의 댓글을 보면 이런데,
게시물에서 뉴스 피드로 돌아갈 수 있도록 Brower Pet에 native UI가 추가된 것을 볼 수 있다.
웹 컨텐츠 자체는 나쁘지는 않아보이지만.. 약간 2005년의 웹 사이트에 더 가까워 보인다.
공간을 더 잘 활용하고 native UI에 더 잘 맞게 사이트를 변경할 수 있을 것 같다.
화면을 좀 더 채우기 위해 CSS를 활용할 수 있을지 보려고 한다. CSS zoom 프로퍼티는 12년 동안 WebKit과 함께 작동해왔고, 전체 body 요소에 적용되어 작동할 수 있을 것 같다.
전체 뷰포트 너비를 차지하도록 하니 효과적이긴한데, 아직도 약간 투박해 보인다.
내가 JavaScript를 내 client world에서 평가한다고 해도, 그것은 페이지에 보이는 방식으로 CSS를 변경한다는 사실을 바꾸지 않는다.
다행히 WKWebView의 새로운 pageZoom 프로퍼티의 형태로 이에 대한 솔루션이 있다.
이것은 실제로 Safari에서 command +, command -해서 전체페이지 확대/축소를 수행하는 것과 동일한 속성이다.
WKWebView에서 이 속성을 사용함으로써 CSS를 JavaScript로 변경하는 것과 정확히 같은 효과를 얻을 수 있고, 단점도 없다.
많은 코드가 제거되었고, 동작은 더 예측 가능하게 되었다.
그리고 Puppies on Safari 댓글을 보면, header와 footer는 브라우저에서 볼 때 웹사이트에서 표시된다. 앱에서는 필요하지 않다.
제거 옵션을 탐색하기 위해 Web Inspector에서 해당 옵션에 대해 자세히 알아보려고 한다.
그 요소들이 pup-header
와 pup-footer
라는 이름을 갖고 있다는 것을 알게되었다.
어디서 쓰이는지 봤더니, custom media query에서 쓰이고 있다.
no-header-and-footer-device는 CSS의 media type 중 "Screen(컴퓨터 화면)", "Print(프린터)" 등인데, 우리는 앱이니까 아님.
알아보니 아이폰용 Pups app이 있기 전에, 원래 Mac WebView를 사용하는 Mac app도 있었다는 것을 알게 되었다.
Mac의 WebView에는 특정 media type으로 표시하도록 지시할 수 있는 기능이 있다. 실제로 screen일 때 print인것처럼 가장하는데 일반적으로 사용되며, custom type에도 사용할 수 있다. 따라서 앱 내의 context에 따라 동일한 컨텐츠의 프레젠테이션이 다를 수 있다.
그리고 올해 모든 플랫폼에서 이 기능을 WKWebView로 제공할 예정이다.
WKWebView에서 custom media type을 설정하면 이러한 요소(원하지 않는 헤더나 푸터같은)를 쉽게 제거할 수 있으며, 내 앱과 같은 커스텀 앱에 제공될 수 있는 다른 스타일도 쉽게 채택할 수 있다.
이 모든 것이 JavaScript 없이, 그리고 navigation별로 적용되는 것이 아니라 전체 앱에 적용된다.
이제 웹 컨텐츠 자체에 초점을 맞추는 다른 기능을 탐색하려고 한다.
여기 많은 댓글이 달린 인기 게시물이 있다.
Beth가 이 댓글들 중 하나에서 재미있는 농담을 했던게 기억났는데, 그걸 찾으려고 스크롤하는 것은 너무 귀찮다..
"find in page" 기능을 사용하고 싶은데 어떻게 구현해야 할까?
검색 환경에 대한 커스텀이 필요하다면, 사용할 수 있는 JavaScript 검색 유틸리티도 있다.
하지만 WKWebView는 이제 플랫폼의 다른 기능과 같이 동작하는, 사용하기 쉬운 찾기 기능을 갖고 있다.
AppKit 및 UIKit API에서 찾을 수 있는 것처럼, 검색에 대해 설정할 수 있는 몇가지 사항들이 있다.
방향, 대소문자 구문, 래핑 등이 있으며, 설정이 없으면 합리적인 기본값이 사용된다.
결과가 발견되면 결과가 선택되고 그 뷰로 스크롤된다.
또 알아볼 다른 기능은 구여운 강아지와 고양이들의 컨텐츠를 공유
하는 것이다.
몇년 동안 WKWebView는 컨텐츠의 비트맵 스냅샷을 만드는 기능을 지원했다. 많은 상황에서 유용하지만,
실제로 얼마나 많은 컨텐츠가 있는지에 관계없이 화면상의 컨텐츠로 제한한다.
예를 들어, 이 사진의 풀 해상도 버전을 확대하는 기능을 추가하면, 보이는 것만 캡쳐할 수 있다.
이 컨텐츠를 공유하는 또 다른 좋은 방법은 PDF가 있다. 올해 WKWebView는 이것도 지원한다.
PDF를 캡쳐하는 방법이나, 화면에 보이지 않는 컨텐츠까지 모든 전체 화면을 PDF로 내보내고 싶은지를 설정할 때 createPDF
메서드를 사용할 수 있다.
이 코드는 검색 가능한 텍스트 형식의 모든 댓글을 포함하여 아래와 같은 PDF 문서 하나에 게시된 전체 게시물을 공유할 수 있게 해준다.
WebKit을 사용할 때 유용한 다른 유형의 컨텐츠 스냅샷은 WebArchive
이다.
WebArchive는 웹 컨텐츠의 현재 DOM과 렌더링해야 하는 하위 리소스의 실제 스냅샷이다.
WebKit 프레임워크의 첫 공개 이후 지원되는 파일 형식일 뿐 아니라, 모든 플렛폼의 웹 컨텐츠를 위한 native pasteboard 형식이기도 하다.
WKWebView는 첫번째 릴리즈 이후 WebArchive 파일을 로드할 수 있었지만, 저장할 수는 없었다.
지금까지는 그랬지만, 이제는 createWebArchiveData
를 사용하여 나중에 디버깅하고 테스트하기위해 웹컨텐츠의 스냅샷을 만들 수 있다.
비트맵 스냅샷을 만들거나 PDF를 만드는 것처럼, WebArchive 데이터를 요청하고 completion handler로 결과를 가져오는 간단한 방법이다. 나중에 사용할 수 있도록 이 데이터를 디스크에 저장하거나, pasteboard에 넣거나, 다른 WKWebView에 로드할 수 있다.
앞서 언급했듯이, WKWebView는 항상 WebArchive 파일을 로드할 수 있었는데, WebArchive를 로드하는데 사용한 코드를 공유하고 있다.
비트맵 스냅샷, PDF, WebArchive를 사용하여 WKWebView에서 데이터를 가져올 수 있는 방법이 점점 늘어나고 있다.
Browser Pets는 새 앱이기 때문에 당연히 SwiftUI를 사용하고 있고, 그 앱의 mac 버전도 만들 수 있었다.
그런데 이 구여운 고양이와 강아지들의 사진을 걸어놓고 일하려면 print
가 매우 중요하다.
WKWebView print는 한동안 iOS에서 가능했지만, mac에서는 그렇지 않았다.
하지만 macOS Big Sur부터는 가능하다.
마지막으로, 사용자 개인 정보 보호와 WKWebView를 사용하는 개발자들에게 미치는 영향에 대해 얘기하려고 한다.
애플이 전에도 이런 말을 많이 해왔지만, privacy는 기본적인 인권이며, 그 믿음이 애플의 핵심 가치 중 하나라고 소개하고 있다.
이를 염두에 두고 iOS와 app store를 설계했다.
정책적 차원뿐만 아니라 기술적 차원에서도 어떠한 native app도 사용자 동의 없이 개인정보를 이용하지 못하도록 하고 있으며, 매년 이를 개선하고 있다.
하지만 웹은 다르다. 사용자들이 wild web을 검색할 때, 종종 감시되고 있을 수도 있다. 일부 웹 기술은 사용자를 보호하는게 아니라 추적하기 위해 발전된 것처럼 보이기도 한다.
우리는 이것에 대해 WebKit으로 무언가를 할 수 있다고 생각했다.
지능형 추적 방지, 즉 ITP에 대한 작업을 시작했다.
ITP는 다양한 클라이언트 사이드 heuristics와 머신 러닝을 사용하여 트래커들을 식별, 분류, 방해한다.
2017년 사파리에 추가한 이후로, 많은 WKWebView 개발자들이 자신의 앱에서 ITP을 사용하라고 요청해왔다.
그리고 iOS 14와 macOS Big Sur에서는 모든 WKWebView 앱에서 ITP가 기본적으로 활성화되어 있다. (유저가 제어할 수 있다.)
예를 들어 범용 웹 브라우저가 있는 경우, 사용자가 제어하지 않는 웹 사이트와의 호환성을 위해 ITP를 비활성화해야할 수도 있다. 대부분의 사람들은 이 상황에 놓이지는 않지만, Safari에서 본 것처럼 사용자에게 비활성화를 지시할 수 있는 API가 있다.
ITP는 웹에서 사용자 개인 정보를 보호하는 강력한 방법임이 입증되었지만, 여기서 멈추지 않았다.
웹에서 사용자를 보호하기 위한 다른 방법을 생각하는 동안, 웹 컨텐츠가 앱에서 사용되는 3가지 일반적인 방법을 확인했다.
가장 이해하기 쉬운 것은 아마 Safari같은 범용 웹 브라우저일 것이다.
이러한 앱의 주요 목적은 모든 소스의 웹 컨텐츠를 보여주고, 사용자가 웹 검색 작업과 관련하여 중요한 것들을 관리할 수 있도록 돕는 것이다.
일반적인 웹 브라우저와 스펙트럼의 반대쪽 끝에는 하나 이상의 사이트의 경계에 머무르는 앱이 있다.
native UI와 웹 컨텐츠 표시의 균형에 관계없이 웹 컨텐츠 자체는 앱의 핵심 구현의 일부이다.
그리고 인앱 브라우저가 있다. 종종 앱 고유의 도메인에서 시작하여 다양한 소스의 컨텐츠를 집계하며, 일반적으로 사용자가 해당 소스에서 검색을 시작할 수 있도록 한다. 대표적인 예로 Browser Pets와 같은 소셜 미디어 뉴스 피드가 있다.
이러한 마지막 2가지 유형의 앱에 대해 WKWebView에 App-bound domains
라는 새로운 기능이 추가되었다.
아이디어는 간단하다. 앱 구현의 핵심 부분인 도메인을 특정한다.
이를 통해 최소 권한 원칙을 구현하여 보안 앱을 설계할 수 있다.
앱의 핵심이 아닌 웹 컨텐츠와의 깊은 상호 작용은 사용자가 작성하는 코드와 프레임워크 또는 라이브러리에서 가져올 수 있는 다른 코드 모두에서 비활성화된다.
이것은 Browser Pets에 영향을 미친다. 게시물의 댓글에 고양이와 개에 대해 더 배우기 위해 다른 도메인에 대한 링크를 추가할 수 있다.
이런 링크를 허용하고 싶다면, App-bound domains를 WebKittens과 Pups on Safari로 제한하는 한 안전하게 허용할 수 있다.
적용하는 것은 쉬운데, 앱의 info.Plist에 WKAppBoundDomains 항목을 추가하기만 하면 된다. 목록을 작성하고 domain 배열을 가리키면 된다. Browser Pets에서는 이렇게 설정했는데, 다른 도메인을 로드하는 것은 여전히 작동하지만, 다른 도메인과의 깊은 상호작용은 기술적 수준에서 방지된다.
info.Plist에 key를 추가하기만 하면 앱의 모든 도메인과 깊은 상호 작용을 비활성화할 수도 있다. 빈 값의 array로 목록을 작성한다. 웹 내용 자체와의 상호 작용이 필요하지 않은 경우 이 방법이 가장 좋다.
이렇게 2개의 별개 웹 사이트에서 컨텐츠를 표시하는 2개의 별개 앱을 가져와 하나의 뉴스 피드로 통합했다.
그 과정에서 앱의 JavaScript가 웹 컨텐츠 자체로부터 격리되도록 했다. 이때 callAsyncJavaScript와 postMessageWithReplies를 사용하여 JavaScript 로직을 크게 단순화 시켰다.
그리고 렌더링에 영향을 미치는 새로운 API를 사용하여 컨텐츠가 표시되는 방법을 미세하게 조정했다. 간편한 "find in page"와 "share as PDF" 기능을 mac에 추가하기도 했다.
또한 ITP (지능형 추적 방지)를 수용하고, App-bound domains를 채택함으로써 사용자의 개인 정보를 보호했다.