| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/ui/cocoa/ssl_client_certificate_selector_cocoa.h" | 5 #import "chrome/browser/ui/cocoa/ssl_client_certificate_selector_cocoa.h" |
| 6 | 6 |
| 7 #import <SecurityInterface/SFChooseIdentityPanel.h> | 7 #import <SecurityInterface/SFChooseIdentityPanel.h> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #import "base/mac/mac_util.h" | 10 #import "base/mac/mac_util.h" |
| 11 #include "base/macros.h" | 11 #include "base/macros.h" |
| 12 #include "base/memory/ptr_util.h" | 12 #include "base/memory/ptr_util.h" |
| 13 #include "chrome/browser/ssl/ssl_client_certificate_selector.h" | 13 #include "chrome/browser/ssl/ssl_client_certificate_selector.h" |
| 14 #include "chrome/browser/ssl/ssl_client_certificate_selector_test.h" | 14 #include "chrome/browser/ssl/ssl_client_certificate_selector_test.h" |
| 15 #include "chrome/browser/ui/browser.h" | 15 #include "chrome/browser/ui/browser.h" |
| 16 #include "chrome/browser/ui/browser_commands.h" | 16 #include "chrome/browser/ui/browser_commands.h" |
| 17 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 17 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 18 #include "components/web_modal/web_contents_modal_dialog_manager.h" | 18 #include "components/web_modal/web_contents_modal_dialog_manager.h" |
| 19 #include "content/public/browser/client_certificate_delegate.h" | 19 #include "content/public/browser/client_certificate_delegate.h" |
| 20 #include "content/public/browser/web_contents.h" | 20 #include "content/public/browser/web_contents.h" |
| 21 #include "content/public/test/test_utils.h" | 21 #include "content/public/test/test_utils.h" |
| 22 #include "net/cert/x509_certificate.h" | 22 #include "net/cert/x509_certificate.h" |
| 23 #include "net/ssl/client_cert_identity_mac.h" |
| 24 #include "net/ssl/ssl_private_key_test_util.h" |
| 23 #include "net/test/cert_test_util.h" | 25 #include "net/test/cert_test_util.h" |
| 26 #include "net/test/keychain_test_util_mac.h" |
| 24 #include "net/test/test_data_directory.h" | 27 #include "net/test/test_data_directory.h" |
| 25 #import "testing/gtest_mac.h" | 28 #import "testing/gtest_mac.h" |
| 26 #include "ui/base/cocoa/window_size_constants.h" | 29 #include "ui/base/cocoa/window_size_constants.h" |
| 27 | 30 |
| 28 using web_modal::WebContentsModalDialogManager; | 31 using web_modal::WebContentsModalDialogManager; |
| 29 | 32 |
| 33 @interface SFChooseIdentityPanel (SystemPrivate) |
| 34 // A system-private interface that dismisses a panel whose sheet was started by |
| 35 // -beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:identities:mess
age: |
| 36 // as though the user clicked the button identified by returnCode. Verified |
| 37 // present in 10.5 through 10.8. |
| 38 - (void)_dismissWithCode:(NSInteger)code; |
| 39 @end |
| 40 |
| 30 namespace { | 41 namespace { |
| 31 | 42 |
| 43 struct TestClientCertificateDelegateResults { |
| 44 bool destroyed = false; |
| 45 bool continue_with_certificate_called = false; |
| 46 scoped_refptr<net::X509Certificate> cert; |
| 47 scoped_refptr<net::SSLPrivateKey> key; |
| 48 }; |
| 49 |
| 32 class TestClientCertificateDelegate | 50 class TestClientCertificateDelegate |
| 33 : public content::ClientCertificateDelegate { | 51 : public content::ClientCertificateDelegate { |
| 34 public: | 52 public: |
| 35 // Creates a ClientCertificateDelegate that sets |*destroyed| to true on | 53 // Creates a ClientCertificateDelegate that sets |*destroyed| to true on |
| 36 // destruction. | 54 // destruction. |
| 37 explicit TestClientCertificateDelegate(bool* destroyed) | 55 explicit TestClientCertificateDelegate( |
| 38 : destroyed_(destroyed) {} | 56 TestClientCertificateDelegateResults* results) |
| 57 : results_(results) {} |
| 39 | 58 |
| 40 ~TestClientCertificateDelegate() override { | 59 ~TestClientCertificateDelegate() override { results_->destroyed = true; } |
| 41 if (destroyed_ != nullptr) | |
| 42 *destroyed_ = true; | |
| 43 } | |
| 44 | 60 |
| 45 // content::ClientCertificateDelegate. | 61 // content::ClientCertificateDelegate. |
| 46 void ContinueWithCertificate(net::X509Certificate* cert) override { | 62 void ContinueWithCertificate(scoped_refptr<net::X509Certificate> cert, |
| 47 // TODO(davidben): Add a test which explicitly tests selecting a | 63 scoped_refptr<net::SSLPrivateKey> key) override { |
| 48 // certificate, or selecting no certificate, since closing the dialog | 64 EXPECT_FALSE(results_->continue_with_certificate_called); |
| 49 // (normally by closing the tab) is not the same as explicitly selecting no | 65 results_->cert = cert; |
| 50 // certificate. | 66 results_->key = key; |
| 51 ADD_FAILURE() << "Certificate selected"; | 67 results_->continue_with_certificate_called = true; |
| 68 // TODO(mattm): Add a test of selecting the 2nd certificate (if possible). |
| 52 } | 69 } |
| 53 | 70 |
| 54 private: | 71 private: |
| 55 bool* destroyed_; | 72 TestClientCertificateDelegateResults* results_; |
| 56 | 73 |
| 57 DISALLOW_COPY_AND_ASSIGN(TestClientCertificateDelegate); | 74 DISALLOW_COPY_AND_ASSIGN(TestClientCertificateDelegate); |
| 58 }; | 75 }; |
| 59 | 76 |
| 60 } // namespace | 77 } // namespace |
| 61 | 78 |
| 62 class SSLClientCertificateSelectorCocoaTest | 79 class SSLClientCertificateSelectorCocoaTest |
| 63 : public SSLClientCertificateSelectorTestBase { | 80 : public SSLClientCertificateSelectorTestBase { |
| 64 public: | 81 public: |
| 65 ~SSLClientCertificateSelectorCocoaTest() override; | 82 ~SSLClientCertificateSelectorCocoaTest() override; |
| 66 | 83 |
| 67 // InProcessBrowserTest: | 84 // InProcessBrowserTest: |
| 68 void SetUpInProcessBrowserTestFixture() override; | 85 void SetUpInProcessBrowserTestFixture() override; |
| 69 | 86 |
| 70 net::CertificateList GetTestCertificateList(); | 87 net::ClientCertIdentityList GetTestCertificateList(); |
| 71 | 88 |
| 72 private: | 89 protected: |
| 73 scoped_refptr<net::X509Certificate> mit_davidben_cert_; | 90 scoped_refptr<net::X509Certificate> client_cert1_; |
| 74 scoped_refptr<net::X509Certificate> foaf_me_chromium_test_cert_; | 91 scoped_refptr<net::X509Certificate> client_cert2_; |
| 75 net::CertificateList client_cert_list_; | 92 std::string pkcs8_key1_; |
| 93 std::string pkcs8_key2_; |
| 94 net::ScopedTestKeychain scoped_keychain_; |
| 95 base::ScopedCFTypeRef<SecIdentityRef> sec_identity1_; |
| 96 base::ScopedCFTypeRef<SecIdentityRef> sec_identity2_; |
| 76 }; | 97 }; |
| 77 | 98 |
| 78 SSLClientCertificateSelectorCocoaTest:: | 99 SSLClientCertificateSelectorCocoaTest:: |
| 79 ~SSLClientCertificateSelectorCocoaTest() = default; | 100 ~SSLClientCertificateSelectorCocoaTest() = default; |
| 80 | 101 |
| 81 void SSLClientCertificateSelectorCocoaTest::SetUpInProcessBrowserTestFixture() { | 102 void SSLClientCertificateSelectorCocoaTest::SetUpInProcessBrowserTestFixture() { |
| 82 SSLClientCertificateSelectorTestBase::SetUpInProcessBrowserTestFixture(); | 103 SSLClientCertificateSelectorTestBase::SetUpInProcessBrowserTestFixture(); |
| 83 | 104 |
| 84 base::FilePath certs_dir = net::GetTestCertsDirectory(); | 105 base::FilePath certs_dir = net::GetTestCertsDirectory(); |
| 85 | 106 |
| 86 mit_davidben_cert_ = net::ImportCertFromFile(certs_dir, "mit.davidben.der"); | 107 client_cert1_ = net::ImportCertFromFile(certs_dir, "client_1.pem"); |
| 87 ASSERT_TRUE(mit_davidben_cert_.get()); | 108 ASSERT_TRUE(client_cert1_); |
| 109 client_cert2_ = net::ImportCertFromFile(certs_dir, "client_2.pem"); |
| 110 ASSERT_TRUE(client_cert2_); |
| 88 | 111 |
| 89 foaf_me_chromium_test_cert_ = | 112 ASSERT_TRUE(base::ReadFileToString(certs_dir.AppendASCII("client_1.pk8"), |
| 90 net::ImportCertFromFile(certs_dir, "foaf.me.chromium-test-cert.der"); | 113 &pkcs8_key1_)); |
| 91 ASSERT_TRUE(foaf_me_chromium_test_cert_.get()); | 114 ASSERT_TRUE(base::ReadFileToString(certs_dir.AppendASCII("client_2.pk8"), |
| 115 &pkcs8_key2_)); |
| 92 | 116 |
| 93 client_cert_list_.push_back(mit_davidben_cert_); | 117 ASSERT_TRUE(scoped_keychain_.Initialize()); |
| 94 client_cert_list_.push_back(foaf_me_chromium_test_cert_); | 118 |
| 119 sec_identity1_ = net::ImportCertAndKeyToKeychain( |
| 120 client_cert1_.get(), pkcs8_key1_, scoped_keychain_.keychain()); |
| 121 ASSERT_TRUE(sec_identity1_); |
| 122 sec_identity2_ = net::ImportCertAndKeyToKeychain( |
| 123 client_cert2_.get(), pkcs8_key2_, scoped_keychain_.keychain()); |
| 124 ASSERT_TRUE(sec_identity2_); |
| 95 } | 125 } |
| 96 | 126 |
| 97 net::CertificateList | 127 net::ClientCertIdentityList |
| 98 SSLClientCertificateSelectorCocoaTest::GetTestCertificateList() { | 128 SSLClientCertificateSelectorCocoaTest::GetTestCertificateList() { |
| 99 return client_cert_list_; | 129 net::ClientCertIdentityList client_cert_list; |
| 130 client_cert_list.push_back(base::MakeUnique<net::ClientCertIdentityMac>( |
| 131 client_cert1_, base::ScopedCFTypeRef<SecIdentityRef>(sec_identity1_))); |
| 132 client_cert_list.push_back(base::MakeUnique<net::ClientCertIdentityMac>( |
| 133 client_cert2_, base::ScopedCFTypeRef<SecIdentityRef>(sec_identity2_))); |
| 134 return client_cert_list; |
| 100 } | 135 } |
| 101 | 136 |
| 102 // Flaky on 10.7; crbug.com/313243 | 137 // XXX crbug.com/313243 http://crbug.com/222296 |
| 103 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorCocoaTest, DISABLED_Basic) { | 138 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorCocoaTest, Basic) { |
| 104 // TODO(kbr): re-enable: http://crbug.com/222296 | |
| 105 return; | |
| 106 | |
| 107 content::WebContents* web_contents = | 139 content::WebContents* web_contents = |
| 108 browser()->tab_strip_model()->GetActiveWebContents(); | 140 browser()->tab_strip_model()->GetActiveWebContents(); |
| 109 WebContentsModalDialogManager* web_contents_modal_dialog_manager = | 141 WebContentsModalDialogManager* web_contents_modal_dialog_manager = |
| 110 WebContentsModalDialogManager::FromWebContents(web_contents); | 142 WebContentsModalDialogManager::FromWebContents(web_contents); |
| 111 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); | 143 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 112 | 144 |
| 113 bool destroyed = false; | 145 TestClientCertificateDelegateResults results; |
| 114 SSLClientCertificateSelectorCocoa* selector = [ | 146 SSLClientCertificateSelectorCocoa* selector = [ |
| 115 [SSLClientCertificateSelectorCocoa alloc] | 147 [SSLClientCertificateSelectorCocoa alloc] |
| 116 initWithBrowserContext:web_contents->GetBrowserContext() | 148 initWithBrowserContext:web_contents->GetBrowserContext() |
| 117 certRequestInfo:auth_requestor_->cert_request_info_.get() | 149 certRequestInfo:auth_requestor_->cert_request_info_.get() |
| 118 delegate:base::WrapUnique(new TestClientCertificateDelegate( | 150 delegate:base::WrapUnique( |
| 119 &destroyed))]; | 151 new TestClientCertificateDelegate(&results))]; |
| 120 [selector displayForWebContents:web_contents | 152 [selector displayForWebContents:web_contents |
| 121 clientCerts:GetTestCertificateList()]; | 153 clientCerts:GetTestCertificateList()]; |
| 122 content::RunAllPendingInMessageLoop(); | 154 content::RunAllPendingInMessageLoop(); |
| 123 EXPECT_TRUE([selector panel]); | 155 EXPECT_TRUE([selector panel]); |
| 124 EXPECT_TRUE(web_contents_modal_dialog_manager->IsDialogActive()); | 156 EXPECT_TRUE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 125 | 157 |
| 126 WebContentsModalDialogManager::TestApi test_api( | 158 WebContentsModalDialogManager::TestApi test_api( |
| 127 web_contents_modal_dialog_manager); | 159 web_contents_modal_dialog_manager); |
| 128 test_api.CloseAllDialogs(); | 160 test_api.CloseAllDialogs(); |
| 129 content::RunAllPendingInMessageLoop(); | 161 content::RunAllPendingInMessageLoop(); |
| 130 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); | 162 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 131 | 163 |
| 132 EXPECT_TRUE(destroyed); | 164 EXPECT_TRUE(results.destroyed); |
| 165 EXPECT_FALSE(results.continue_with_certificate_called); |
| 166 } |
| 167 |
| 168 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorCocoaTest, Cancel) { |
| 169 content::WebContents* web_contents = |
| 170 browser()->tab_strip_model()->GetActiveWebContents(); |
| 171 WebContentsModalDialogManager* web_contents_modal_dialog_manager = |
| 172 WebContentsModalDialogManager::FromWebContents(web_contents); |
| 173 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 174 |
| 175 TestClientCertificateDelegateResults results; |
| 176 SSLClientCertificateSelectorCocoa* selector = [ |
| 177 [SSLClientCertificateSelectorCocoa alloc] |
| 178 initWithBrowserContext:web_contents->GetBrowserContext() |
| 179 certRequestInfo:auth_requestor_->cert_request_info_.get() |
| 180 delegate:base::WrapUnique( |
| 181 new TestClientCertificateDelegate(&results))]; |
| 182 [selector displayForWebContents:web_contents |
| 183 clientCerts:GetTestCertificateList()]; |
| 184 content::RunAllPendingInMessageLoop(); |
| 185 EXPECT_TRUE([selector panel]); |
| 186 EXPECT_TRUE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 187 |
| 188 // Cancel the selector. Dunno if there is a better way to do this. |
| 189 [[selector panel] _dismissWithCode:NSFileHandlingPanelCancelButton]; |
| 190 content::RunAllPendingInMessageLoop(); |
| 191 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 192 |
| 193 // ContinueWithCertificate(nullptr, nullptr) should have been called. |
| 194 EXPECT_TRUE(results.destroyed); |
| 195 EXPECT_TRUE(results.continue_with_certificate_called); |
| 196 EXPECT_FALSE(results.cert); |
| 197 EXPECT_FALSE(results.key); |
| 198 } |
| 199 |
| 200 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorCocoaTest, Accept) { |
| 201 content::WebContents* web_contents = |
| 202 browser()->tab_strip_model()->GetActiveWebContents(); |
| 203 WebContentsModalDialogManager* web_contents_modal_dialog_manager = |
| 204 WebContentsModalDialogManager::FromWebContents(web_contents); |
| 205 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 206 |
| 207 TestClientCertificateDelegateResults results; |
| 208 SSLClientCertificateSelectorCocoa* selector = [ |
| 209 [SSLClientCertificateSelectorCocoa alloc] |
| 210 initWithBrowserContext:web_contents->GetBrowserContext() |
| 211 certRequestInfo:auth_requestor_->cert_request_info_.get() |
| 212 delegate:base::WrapUnique( |
| 213 new TestClientCertificateDelegate(&results))]; |
| 214 [selector displayForWebContents:web_contents |
| 215 clientCerts:GetTestCertificateList()]; |
| 216 content::RunAllPendingInMessageLoop(); |
| 217 EXPECT_TRUE([selector panel]); |
| 218 EXPECT_TRUE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 219 |
| 220 // Accept the selection. Dunno if there is a better way to do this. |
| 221 [[selector panel] _dismissWithCode:NSFileHandlingPanelOKButton]; |
| 222 content::RunAllPendingInMessageLoop(); |
| 223 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); |
| 224 |
| 225 // The first cert in the list should have been selected. |
| 226 EXPECT_TRUE(results.destroyed); |
| 227 EXPECT_TRUE(results.continue_with_certificate_called); |
| 228 EXPECT_EQ(client_cert1_, results.cert); |
| 229 ASSERT_TRUE(results.key); |
| 230 TestSSLPrivateKeyMatches(results.key.get(), pkcs8_key1_); |
| 133 } | 231 } |
| 134 | 232 |
| 135 // Test that switching to another tab correctly hides the sheet. | 233 // Test that switching to another tab correctly hides the sheet. |
| 136 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorCocoaTest, HideShow) { | 234 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorCocoaTest, HideShow) { |
| 137 content::WebContents* web_contents = | 235 content::WebContents* web_contents = |
| 138 browser()->tab_strip_model()->GetActiveWebContents(); | 236 browser()->tab_strip_model()->GetActiveWebContents(); |
| 237 TestClientCertificateDelegateResults results; |
| 139 SSLClientCertificateSelectorCocoa* selector = [ | 238 SSLClientCertificateSelectorCocoa* selector = [ |
| 140 [SSLClientCertificateSelectorCocoa alloc] | 239 [SSLClientCertificateSelectorCocoa alloc] |
| 141 initWithBrowserContext:web_contents->GetBrowserContext() | 240 initWithBrowserContext:web_contents->GetBrowserContext() |
| 142 certRequestInfo:auth_requestor_->cert_request_info_.get() | 241 certRequestInfo:auth_requestor_->cert_request_info_.get() |
| 143 delegate:base::WrapUnique( | 242 delegate:base::WrapUnique( |
| 144 new TestClientCertificateDelegate(nullptr))]; | 243 new TestClientCertificateDelegate(&results))]; |
| 145 [selector displayForWebContents:web_contents | 244 [selector displayForWebContents:web_contents |
| 146 clientCerts:GetTestCertificateList()]; | 245 clientCerts:GetTestCertificateList()]; |
| 147 content::RunAllPendingInMessageLoop(); | 246 content::RunAllPendingInMessageLoop(); |
| 148 | 247 |
| 149 NSWindow* sheetWindow = [[selector overlayWindow] attachedSheet]; | 248 NSWindow* sheetWindow = [[selector overlayWindow] attachedSheet]; |
| 150 EXPECT_EQ(1.0, [sheetWindow alphaValue]); | 249 EXPECT_EQ(1.0, [sheetWindow alphaValue]); |
| 151 | 250 |
| 152 // Switch to another tab and verify that the sheet is hidden. Interaction with | 251 // Switch to another tab and verify that the sheet is hidden. Interaction with |
| 153 // the tab underneath should not be blocked. | 252 // the tab underneath should not be blocked. |
| 154 AddBlankTabAndShow(browser()); | 253 AddBlankTabAndShow(browser()); |
| 155 EXPECT_EQ(0.0, [sheetWindow alphaValue]); | 254 EXPECT_EQ(0.0, [sheetWindow alphaValue]); |
| 156 EXPECT_TRUE([[selector overlayWindow] ignoresMouseEvents]); | 255 EXPECT_TRUE([[selector overlayWindow] ignoresMouseEvents]); |
| 157 | 256 |
| 158 // Switch back and verify that the sheet is shown. Interaction with the tab | 257 // Switch back and verify that the sheet is shown. Interaction with the tab |
| 159 // underneath should be blocked while the sheet is showing. | 258 // underneath should be blocked while the sheet is showing. |
| 160 chrome::SelectNumberedTab(browser(), 0); | 259 chrome::SelectNumberedTab(browser(), 0); |
| 161 EXPECT_EQ(1.0, [sheetWindow alphaValue]); | 260 EXPECT_EQ(1.0, [sheetWindow alphaValue]); |
| 162 EXPECT_FALSE([[selector overlayWindow] ignoresMouseEvents]); | 261 EXPECT_FALSE([[selector overlayWindow] ignoresMouseEvents]); |
| 262 |
| 263 EXPECT_FALSE(results.destroyed); |
| 264 EXPECT_FALSE(results.continue_with_certificate_called); |
| 163 } | 265 } |
| 164 | 266 |
| 165 @interface DeallocTrackingSSLClientCertificateSelectorCocoa | 267 @interface DeallocTrackingSSLClientCertificateSelectorCocoa |
| 166 : SSLClientCertificateSelectorCocoa | 268 : SSLClientCertificateSelectorCocoa |
| 167 @property(nonatomic) BOOL* wasDeallocated; | 269 @property(nonatomic) BOOL* wasDeallocated; |
| 168 @end | 270 @end |
| 169 | 271 |
| 170 @implementation DeallocTrackingSSLClientCertificateSelectorCocoa | 272 @implementation DeallocTrackingSSLClientCertificateSelectorCocoa |
| 171 @synthesize wasDeallocated = wasDeallocated_; | 273 @synthesize wasDeallocated = wasDeallocated_; |
| 172 | 274 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 209 userInfo:@{ | 311 userInfo:@{ |
| 210 @"NSScrollerStyle" : @(NSScrollerStyleLegacy) | 312 @"NSScrollerStyle" : @(NSScrollerStyleLegacy) |
| 211 }]; | 313 }]; |
| 212 [[NSNotificationCenter defaultCenter] | 314 [[NSNotificationCenter defaultCenter] |
| 213 postNotificationName:NSPreferredScrollerStyleDidChangeNotification | 315 postNotificationName:NSPreferredScrollerStyleDidChangeNotification |
| 214 object:nil | 316 object:nil |
| 215 userInfo:@{ | 317 userInfo:@{ |
| 216 @"NSScrollerStyle" : @(NSScrollerStyleOverlay) | 318 @"NSScrollerStyle" : @(NSScrollerStyleOverlay) |
| 217 }]; | 319 }]; |
| 218 } | 320 } |
| OLD | NEW |