OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 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 #include "chrome/browser/login_prompt.h" | 5 #include "chrome/browser/login_prompt.h" |
6 #import "chrome/browser/login_prompt_mac.h" | 6 #import "chrome/browser/login_prompt_mac.h" |
7 | 7 |
8 #include "app/l10n_util.h" | 8 #include "app/l10n_util.h" |
9 #include "base/mac_util.h" | 9 #include "base/mac_util.h" |
10 #include "base/message_loop.h" | |
11 #include "base/sys_string_conversions.h" | 10 #include "base/sys_string_conversions.h" |
| 11 #include "chrome/browser/chrome_thread.h" |
12 #include "chrome/browser/cocoa/constrained_window_mac.h" | 12 #include "chrome/browser/cocoa/constrained_window_mac.h" |
13 #include "chrome/browser/login_model.h" | 13 #include "chrome/browser/login_model.h" |
14 #include "chrome/browser/password_manager/password_manager.h" | 14 #include "chrome/browser/password_manager/password_manager.h" |
15 #include "chrome/browser/renderer_host/resource_dispatcher_host.h" | 15 #include "chrome/browser/renderer_host/resource_dispatcher_host.h" |
16 #include "chrome/browser/tab_contents/navigation_controller.h" | 16 #include "chrome/browser/tab_contents/navigation_controller.h" |
17 #include "chrome/browser/tab_contents/tab_contents.h" | 17 #include "chrome/browser/tab_contents/tab_contents.h" |
18 #include "chrome/browser/tab_contents/tab_util.h" | 18 #include "chrome/browser/tab_contents/tab_util.h" |
19 #include "chrome/common/notification_service.h" | 19 #include "chrome/common/notification_service.h" |
20 #include "grit/generated_resources.h" | 20 #include "grit/generated_resources.h" |
21 #include "net/url_request/url_request.h" | 21 #include "net/url_request/url_request.h" |
22 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | 22 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" |
23 | 23 |
24 using webkit_glue::PasswordForm; | 24 using webkit_glue::PasswordForm; |
25 | 25 |
26 // ---------------------------------------------------------------------------- | 26 // ---------------------------------------------------------------------------- |
27 // LoginHandlerMac | 27 // LoginHandlerMac |
28 | 28 |
29 // This class simply forwards the authentication from the LoginView (on | 29 // This class simply forwards the authentication from the LoginView (on |
30 // the UI thread) to the URLRequest (on the I/O thread). | 30 // the UI thread) to the URLRequest (on the I/O thread). |
31 // This class uses ref counting to ensure that it lives until all InvokeLaters | 31 // This class uses ref counting to ensure that it lives until all InvokeLaters |
32 // have been called. | 32 // have been called. |
33 class LoginHandlerMac : public LoginHandler, | 33 class LoginHandlerMac : public LoginHandler, |
34 public base::RefCountedThreadSafe<LoginHandlerMac>, | 34 public base::RefCountedThreadSafe<LoginHandlerMac>, |
35 public ConstrainedWindowMacDelegateCustomSheet, | 35 public ConstrainedWindowMacDelegateCustomSheet, |
36 public LoginModelObserver { | 36 public LoginModelObserver { |
37 public: | 37 public: |
38 LoginHandlerMac(URLRequest* request, MessageLoop* ui_loop) | 38 LoginHandlerMac(URLRequest* request) |
39 : handled_auth_(false), | 39 : handled_auth_(false), |
40 dialog_(NULL), | 40 dialog_(NULL), |
41 ui_loop_(ui_loop), | |
42 request_(request), | 41 request_(request), |
43 request_loop_(MessageLoop::current()), | |
44 password_manager_(NULL), | 42 password_manager_(NULL), |
45 sheet_controller_(nil), | 43 sheet_controller_(nil), |
46 login_model_(NULL) { | 44 login_model_(NULL) { |
47 // This constructor is called on the I/O thread, so we cannot load the nib | 45 // This constructor is called on the I/O thread, so we cannot load the nib |
48 // here. BuildViewForPasswordManager() will be invoked on the UI thread | 46 // here. BuildViewForPasswordManager() will be invoked on the UI thread |
49 // later, so wait with loading the nib until then. | 47 // later, so wait with loading the nib until then. |
50 DCHECK(request_) << "LoginHandlerMac constructed with NULL request"; | 48 DCHECK(request_) << "LoginHandlerMac constructed with NULL request"; |
51 | 49 |
52 AddRef(); // matched by ReleaseLater. | 50 AddRef(); // matched by ReleaseLater. |
53 if (!ResourceDispatcherHost::RenderViewForRequest(request_, | 51 if (!ResourceDispatcherHost::RenderViewForRequest(request_, |
(...skipping 19 matching lines...) Expand all Loading... |
73 // LoginModelObserver implementation. | 71 // LoginModelObserver implementation. |
74 virtual void OnAutofillDataAvailable(const std::wstring& username, | 72 virtual void OnAutofillDataAvailable(const std::wstring& username, |
75 const std::wstring& password) { | 73 const std::wstring& password) { |
76 [sheet_controller_ autofillLogin:base::SysWideToNSString(username) | 74 [sheet_controller_ autofillLogin:base::SysWideToNSString(username) |
77 password:base::SysWideToNSString(password)]; | 75 password:base::SysWideToNSString(password)]; |
78 } | 76 } |
79 | 77 |
80 // LoginHandler: | 78 // LoginHandler: |
81 virtual void BuildViewForPasswordManager(PasswordManager* manager, | 79 virtual void BuildViewForPasswordManager(PasswordManager* manager, |
82 std::wstring explanation) { | 80 std::wstring explanation) { |
83 DCHECK(MessageLoop::current() == ui_loop_); | 81 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
84 | 82 |
85 // Load nib here instead of in constructor. | 83 // Load nib here instead of in constructor. |
86 sheet_controller_ = [[[LoginHandlerSheet alloc] | 84 sheet_controller_ = [[[LoginHandlerSheet alloc] |
87 initWithLoginHandler:this] autorelease]; | 85 initWithLoginHandler:this] autorelease]; |
88 init([sheet_controller_ window], sheet_controller_, | 86 init([sheet_controller_ window], sheet_controller_, |
89 @selector(sheetDidEnd:returnCode:contextInfo:)); | 87 @selector(sheetDidEnd:returnCode:contextInfo:)); |
90 | 88 |
91 SetModel(manager); | 89 SetModel(manager); |
92 | 90 |
93 [sheet_controller_ setExplanation:base::SysWideToNSString(explanation)]; | 91 [sheet_controller_ setExplanation:base::SysWideToNSString(explanation)]; |
(...skipping 10 matching lines...) Expand all Loading... |
104 | 102 |
105 virtual void SetPasswordForm(const webkit_glue::PasswordForm& form) { | 103 virtual void SetPasswordForm(const webkit_glue::PasswordForm& form) { |
106 password_form_ = form; | 104 password_form_ = form; |
107 } | 105 } |
108 | 106 |
109 virtual void SetPasswordManager(PasswordManager* password_manager) { | 107 virtual void SetPasswordManager(PasswordManager* password_manager) { |
110 password_manager_ = password_manager; | 108 password_manager_ = password_manager; |
111 } | 109 } |
112 | 110 |
113 virtual TabContents* GetTabContentsForLogin() { | 111 virtual TabContents* GetTabContentsForLogin() { |
114 DCHECK(MessageLoop::current() == ui_loop_); | 112 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
115 | 113 |
116 return tab_util::GetTabContentsByID(render_process_host_id_, | 114 return tab_util::GetTabContentsByID(render_process_host_id_, |
117 tab_contents_id_); | 115 tab_contents_id_); |
118 } | 116 } |
119 | 117 |
120 virtual void SetAuth(const std::wstring& username, | 118 virtual void SetAuth(const std::wstring& username, |
121 const std::wstring& password) { | 119 const std::wstring& password) { |
122 if (WasAuthHandled(true)) | 120 if (WasAuthHandled(true)) |
123 return; | 121 return; |
124 | 122 |
125 // Tell the password manager the credentials were submitted / accepted. | 123 // Tell the password manager the credentials were submitted / accepted. |
126 if (password_manager_) { | 124 if (password_manager_) { |
127 password_form_.username_value = WideToUTF16Hack(username); | 125 password_form_.username_value = WideToUTF16Hack(username); |
128 password_form_.password_value = WideToUTF16Hack(password); | 126 password_form_.password_value = WideToUTF16Hack(password); |
129 password_manager_->ProvisionallySavePassword(password_form_); | 127 password_manager_->ProvisionallySavePassword(password_form_); |
130 } | 128 } |
131 | 129 |
132 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( | 130 ChromeThread::PostTask( |
133 this, &LoginHandlerMac::CloseContentsDeferred)); | 131 ChromeThread::UI, FROM_HERE, |
134 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( | 132 NewRunnableMethod(this, &LoginHandlerMac::CloseContentsDeferred)); |
135 this, &LoginHandlerMac::SendNotifications)); | 133 ChromeThread::PostTask( |
136 request_loop_->PostTask(FROM_HERE, NewRunnableMethod( | 134 ChromeThread::UI, FROM_HERE, |
137 this, &LoginHandlerMac::SetAuthDeferred, username, password)); | 135 NewRunnableMethod(this, &LoginHandlerMac::SendNotifications)); |
| 136 ChromeThread::PostTask( |
| 137 ChromeThread::IO, FROM_HERE, |
| 138 NewRunnableMethod( |
| 139 this, &LoginHandlerMac::SetAuthDeferred, username, password)); |
138 } | 140 } |
139 | 141 |
140 virtual void CancelAuth() { | 142 virtual void CancelAuth() { |
141 if (WasAuthHandled(true)) | 143 if (WasAuthHandled(true)) |
142 return; | 144 return; |
143 | 145 |
144 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( | 146 ChromeThread::PostTask( |
145 this, &LoginHandlerMac::CloseContentsDeferred)); | 147 ChromeThread::UI, FROM_HERE, |
146 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( | 148 NewRunnableMethod(this, &LoginHandlerMac::CloseContentsDeferred)); |
147 this, &LoginHandlerMac::SendNotifications)); | 149 ChromeThread::PostTask( |
148 request_loop_->PostTask(FROM_HERE, NewRunnableMethod( | 150 ChromeThread::UI, FROM_HERE, |
149 this, &LoginHandlerMac::CancelAuthDeferred)); | 151 NewRunnableMethod(this, &LoginHandlerMac::SendNotifications)); |
| 152 ChromeThread::PostTask( |
| 153 ChromeThread::IO, FROM_HERE, |
| 154 NewRunnableMethod(this, &LoginHandlerMac::CancelAuthDeferred)); |
150 } | 155 } |
151 | 156 |
152 virtual void OnRequestCancelled() { | 157 virtual void OnRequestCancelled() { |
153 DCHECK(MessageLoop::current() == request_loop_) << | 158 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)) << |
154 "Why is OnRequestCancelled called from the UI thread?"; | 159 "Why is OnRequestCancelled called from the UI thread?"; |
155 | 160 |
156 // Reference is no longer valid. | 161 // Reference is no longer valid. |
157 request_ = NULL; | 162 request_ = NULL; |
158 | 163 |
159 // Give up on auth if the request was cancelled. | 164 // Give up on auth if the request was cancelled. |
160 CancelAuth(); | 165 CancelAuth(); |
161 } | 166 } |
162 | 167 |
163 // Overridden from ConstrainedWindowMacDelegate: | 168 // Overridden from ConstrainedWindowMacDelegate: |
164 virtual void DeleteDelegate() { | 169 virtual void DeleteDelegate() { |
165 if (!WasAuthHandled(true)) { | 170 if (!WasAuthHandled(true)) { |
166 request_loop_->PostTask(FROM_HERE, NewRunnableMethod( | 171 ChromeThread::PostTask( |
167 this, &LoginHandlerMac::CancelAuthDeferred)); | 172 ChromeThread::IO, FROM_HERE, |
168 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( | 173 NewRunnableMethod(this, &LoginHandlerMac::CancelAuthDeferred)); |
169 this, &LoginHandlerMac::SendNotifications)); | 174 ChromeThread::PostTask( |
| 175 ChromeThread::UI, FROM_HERE, |
| 176 NewRunnableMethod(this, &LoginHandlerMac::SendNotifications)); |
170 } | 177 } |
171 | 178 |
172 // Close sheet if it's still open, as required by | 179 // Close sheet if it's still open, as required by |
173 // ConstrainedWindowMacDelegate. | 180 // ConstrainedWindowMacDelegate. |
174 DCHECK(MessageLoop::current() == ui_loop_); | 181 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
175 if (is_sheet_open()) | 182 if (is_sheet_open()) |
176 [NSApp endSheet:sheet()]; | 183 [NSApp endSheet:sheet()]; |
177 | 184 |
178 SetModel(NULL); | 185 SetModel(NULL); |
179 | 186 |
180 // Delete this object once all InvokeLaters have been called. | 187 // Delete this object once all InvokeLaters have been called. |
181 request_loop_->ReleaseSoon(FROM_HERE, this); | 188 ChromeThread::ReleaseSoon(ChromeThread::IO, FROM_HERE, this); |
182 } | 189 } |
183 | 190 |
184 void OnLoginPressed(const std::wstring& username, | 191 void OnLoginPressed(const std::wstring& username, |
185 const std::wstring& password) { | 192 const std::wstring& password) { |
186 DCHECK(MessageLoop::current() == ui_loop_); | 193 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
187 SetAuth(username, password); | 194 SetAuth(username, password); |
188 } | 195 } |
189 | 196 |
190 void OnCancelPressed() { | 197 void OnCancelPressed() { |
191 DCHECK(MessageLoop::current() == ui_loop_); | 198 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
192 CancelAuth(); | 199 CancelAuth(); |
193 } | 200 } |
194 | 201 |
195 private: | 202 private: |
196 friend class LoginPrompt; | 203 friend class LoginPrompt; |
197 | 204 |
198 // Calls SetAuth from the request_loop. | 205 // Calls SetAuth from the IO loop. |
199 void SetAuthDeferred(const std::wstring& username, | 206 void SetAuthDeferred(const std::wstring& username, |
200 const std::wstring& password) { | 207 const std::wstring& password) { |
201 DCHECK(MessageLoop::current() == request_loop_); | 208 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); |
202 | 209 |
203 if (request_) { | 210 if (request_) { |
204 request_->SetAuth(username, password); | 211 request_->SetAuth(username, password); |
205 ResetLoginHandlerForRequest(request_); | 212 ResetLoginHandlerForRequest(request_); |
206 } | 213 } |
207 } | 214 } |
208 | 215 |
209 // Calls CancelAuth from the request_loop. | 216 // Calls CancelAuth from the IO loop. |
210 void CancelAuthDeferred() { | 217 void CancelAuthDeferred() { |
211 DCHECK(MessageLoop::current() == request_loop_); | 218 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); |
212 | 219 |
213 if (request_) { | 220 if (request_) { |
214 request_->CancelAuth(); | 221 request_->CancelAuth(); |
215 // Verify that CancelAuth does destroy the request via our delegate. | 222 // Verify that CancelAuth does destroy the request via our delegate. |
216 DCHECK(request_ != NULL); | 223 DCHECK(request_ != NULL); |
217 ResetLoginHandlerForRequest(request_); | 224 ResetLoginHandlerForRequest(request_); |
218 } | 225 } |
219 } | 226 } |
220 | 227 |
221 // Closes the view_contents from the UI loop. | 228 // Closes the view_contents from the UI loop. |
222 void CloseContentsDeferred() { | 229 void CloseContentsDeferred() { |
223 DCHECK(MessageLoop::current() == ui_loop_); | 230 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
224 | 231 |
225 // The hosting ConstrainedWindow may have been freed. | 232 // The hosting ConstrainedWindow may have been freed. |
226 if (dialog_) | 233 if (dialog_) |
227 dialog_->CloseConstrainedWindow(); | 234 dialog_->CloseConstrainedWindow(); |
228 } | 235 } |
229 | 236 |
230 // Returns whether authentication had been handled (SetAuth or CancelAuth). | 237 // Returns whether authentication had been handled (SetAuth or CancelAuth). |
231 // If |set_handled| is true, it will mark authentication as handled. | 238 // If |set_handled| is true, it will mark authentication as handled. |
232 bool WasAuthHandled(bool set_handled) { | 239 bool WasAuthHandled(bool set_handled) { |
233 AutoLock lock(handled_auth_lock_); | 240 AutoLock lock(handled_auth_lock_); |
234 bool was_handled = handled_auth_; | 241 bool was_handled = handled_auth_; |
235 if (set_handled) | 242 if (set_handled) |
236 handled_auth_ = true; | 243 handled_auth_ = true; |
237 return was_handled; | 244 return was_handled; |
238 } | 245 } |
239 | 246 |
240 // Notify observers that authentication is needed or received. The automation | 247 // Notify observers that authentication is needed or received. The automation |
241 // proxy uses this for testing. | 248 // proxy uses this for testing. |
242 void SendNotifications() { | 249 void SendNotifications() { |
243 DCHECK(MessageLoop::current() == ui_loop_); | 250 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
244 | 251 |
245 NotificationService* service = NotificationService::current(); | 252 NotificationService* service = NotificationService::current(); |
246 TabContents* requesting_contents = GetTabContentsForLogin(); | 253 TabContents* requesting_contents = GetTabContentsForLogin(); |
247 if (!requesting_contents) | 254 if (!requesting_contents) |
248 return; | 255 return; |
249 | 256 |
250 NavigationController* controller = &requesting_contents->controller(); | 257 NavigationController* controller = &requesting_contents->controller(); |
251 | 258 |
252 if (!WasAuthHandled(false)) { | 259 if (!WasAuthHandled(false)) { |
253 LoginNotificationDetails details(this); | 260 LoginNotificationDetails details(this); |
254 service->Notify(NotificationType::AUTH_NEEDED, | 261 service->Notify(NotificationType::AUTH_NEEDED, |
255 Source<NavigationController>(controller), | 262 Source<NavigationController>(controller), |
256 Details<LoginNotificationDetails>(&details)); | 263 Details<LoginNotificationDetails>(&details)); |
257 } else { | 264 } else { |
258 service->Notify(NotificationType::AUTH_SUPPLIED, | 265 service->Notify(NotificationType::AUTH_SUPPLIED, |
259 Source<NavigationController>(controller), | 266 Source<NavigationController>(controller), |
260 NotificationService::NoDetails()); | 267 NotificationService::NoDetails()); |
261 } | 268 } |
262 } | 269 } |
263 | 270 |
264 // True if we've handled auth (SetAuth or CancelAuth has been called). | 271 // True if we've handled auth (SetAuth or CancelAuth has been called). |
265 bool handled_auth_; | 272 bool handled_auth_; |
266 Lock handled_auth_lock_; | 273 Lock handled_auth_lock_; |
267 | 274 |
268 // The ConstrainedWindow that is hosting our LoginView. | 275 // The ConstrainedWindow that is hosting our LoginView. |
269 // This should only be accessed on the ui_loop_. | 276 // This should only be accessed on the UI loop. |
270 ConstrainedWindow* dialog_; | 277 ConstrainedWindow* dialog_; |
271 | 278 |
272 // The MessageLoop of the thread that the ChromeViewContents lives in. | |
273 MessageLoop* ui_loop_; | |
274 | |
275 // The request that wants login data. | 279 // The request that wants login data. |
276 // This should only be accessed on the request_loop_. | 280 // This should only be accessed on the IO loop. |
277 URLRequest* request_; | 281 URLRequest* request_; |
278 | 282 |
279 // The MessageLoop of the thread that the URLRequest lives in. | |
280 MessageLoop* request_loop_; | |
281 | |
282 // The PasswordForm sent to the PasswordManager. This is so we can refer to it | 283 // The PasswordForm sent to the PasswordManager. This is so we can refer to it |
283 // when later notifying the password manager if the credentials were accepted | 284 // when later notifying the password manager if the credentials were accepted |
284 // or rejected. | 285 // or rejected. |
285 // This should only be accessed on the ui_loop_. | 286 // This should only be accessed on the UI loop. |
286 PasswordForm password_form_; | 287 PasswordForm password_form_; |
287 | 288 |
288 // Points to the password manager owned by the TabContents requesting auth. | 289 // Points to the password manager owned by the TabContents requesting auth. |
289 // Can be null if the TabContents is not a TabContents. | 290 // Can be null if the TabContents is not a TabContents. |
290 // This should only be accessed on the ui_loop_. | 291 // This should only be accessed on the UI loop. |
291 PasswordManager* password_manager_; | 292 PasswordManager* password_manager_; |
292 | 293 |
293 // Cached from the URLRequest, in case it goes NULL on us. | 294 // Cached from the URLRequest, in case it goes NULL on us. |
294 int render_process_host_id_; | 295 int render_process_host_id_; |
295 int tab_contents_id_; | 296 int tab_contents_id_; |
296 | 297 |
297 // The Cocoa controller of the GUI. | 298 // The Cocoa controller of the GUI. |
298 LoginHandlerSheet* sheet_controller_; | 299 LoginHandlerSheet* sheet_controller_; |
299 | 300 |
300 // If not null, points to a model we need to notify of our own destruction | 301 // If not null, points to a model we need to notify of our own destruction |
301 // so it doesn't try and access this when its too late. | 302 // so it doesn't try and access this when its too late. |
302 LoginModel* login_model_; | 303 LoginModel* login_model_; |
303 | 304 |
304 DISALLOW_COPY_AND_ASSIGN(LoginHandlerMac); | 305 DISALLOW_COPY_AND_ASSIGN(LoginHandlerMac); |
305 }; | 306 }; |
306 | 307 |
307 // static | 308 // static |
308 LoginHandler* LoginHandler::Create(URLRequest* request, MessageLoop* ui_loop) { | 309 LoginHandler* LoginHandler::Create(URLRequest* request) { |
309 return new LoginHandlerMac(request, ui_loop); | 310 return new LoginHandlerMac(request); |
310 } | 311 } |
311 | 312 |
312 // ---------------------------------------------------------------------------- | 313 // ---------------------------------------------------------------------------- |
313 // LoginHandlerSheet | 314 // LoginHandlerSheet |
314 | 315 |
315 @implementation LoginHandlerSheet | 316 @implementation LoginHandlerSheet |
316 | 317 |
317 - (id)initWithLoginHandler:(LoginHandlerMac*)handler { | 318 - (id)initWithLoginHandler:(LoginHandlerMac*)handler { |
318 NSString* nibPath = | 319 NSString* nibPath = |
319 [mac_util::MainAppBundle() pathForResource:@"HttpAuthLoginSheet" | 320 [mac_util::MainAppBundle() pathForResource:@"HttpAuthLoginSheet" |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
362 sizeToFitFixedWidthTextField:explanationField_]; | 363 sizeToFitFixedWidthTextField:explanationField_]; |
363 | 364 |
364 // Resize the window (no shifting needed due to window layout). | 365 // Resize the window (no shifting needed due to window layout). |
365 NSSize windowDelta = NSMakeSize(0, explanationShift); | 366 NSSize windowDelta = NSMakeSize(0, explanationShift); |
366 [GTMUILocalizerAndLayoutTweaker | 367 [GTMUILocalizerAndLayoutTweaker |
367 resizeWindowWithoutAutoResizingSubViews:[self window] | 368 resizeWindowWithoutAutoResizingSubViews:[self window] |
368 delta:windowDelta]; | 369 delta:windowDelta]; |
369 } | 370 } |
370 | 371 |
371 @end | 372 @end |
OLD | NEW |