Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(947)

Side by Side Diff: ios/chrome/browser/ui/dialogs/dialog_presenter.mm

Issue 2588713002: Upstream Chrome on iOS source code [4/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
6
7 #include <deque>
8 #include <map>
9
10 #import "base/ios/block_types.h"
11 #import "base/ios/weak_nsobject.h"
12 #include "base/logging.h"
13 #import "base/mac/scoped_nsobject.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "components/strings/grit/components_strings.h"
16 #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
17 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
18 #import "ios/chrome/browser/ui/alert_coordinator/input_alert_coordinator.h"
19 #import "ios/chrome/browser/ui/dialogs/javascript_dialog_blocking_util.h"
20 #import "ios/chrome/browser/ui/dialogs/nsurl_protection_space_util.h"
21 #include "ios/chrome/browser/ui/ui_util.h"
22 #include "ios/chrome/grit/ios_strings.h"
23 #include "ios/web/public/web_state/web_state.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "url/gurl.h"
26
27 // Externed accessibility identifier.
28 NSString* const kJavaScriptDialogTextFieldAccessibiltyIdentifier =
29 @"JavaScriptDialogTextFieldAccessibiltyIdentifier";
30
31 namespace {
32 // The hostname to use for JavaScript alerts when there is no valid hostname in
33 // the URL passed to |+localizedTitleForJavaScriptAlertFromPage:type:|.
34 const char kAboutNullHostname[] = "about:null";
35 } // namespace
36
37 @interface DialogPresenter () {
38 // Backing objects for properties of the same name.
39 base::WeakNSProtocol<id<DialogPresenterDelegate>> _delegate;
40 base::WeakNSObject<UIViewController> _viewController;
41 // Queue of WebStates which correspond to the keys in
42 // |_dialogCoordinatorsForWebStates|.
43 std::deque<web::WebState*> _queuedWebStates;
44 // A map associating queued webStates with their coordinators.
45 std::map<web::WebState*, base::scoped_nsobject<AlertCoordinator>>
46 _dialogCoordinatorsForWebStates;
47 web::WebState* _presentedDialogWebState;
48 base::scoped_nsobject<AlertCoordinator> _presentedDialogCoordinator;
49 base::scoped_nsobject<ActionSheetCoordinator>
50 _blockingConfirmationCoordinator;
51 }
52
53 // The delegate passed on initialization.
54 @property(nonatomic, readonly) id<DialogPresenterDelegate> delegate;
55
56 // The presenting view controller passed on initialization.
57 @property(nonatomic, readonly) UIViewController* viewController;
58
59 // Whether a modal dialog is currently being shown.
60 @property(nonatomic, readonly, getter=isShowingDialog) BOOL showingDialog;
61
62 // The webState for |presentedDialog|.
63 @property(nonatomic) web::WebState* presentedDialogWebState;
64
65 // The dialog that's currently being shown, if any.
66 @property(nonatomic, retain) AlertCoordinator* presentedDialogCoordinator;
67
68 // The JavaScript dialog blocking confirmation action sheet being shown, if any.
69 @property(nonatomic, retain) AlertCoordinator* blockingConfirmationCoordinator;
70
71 // Adds |context| and |coordinator| to the queue. If a dialog is not already
72 // being shown, |coordinator| will be presented. Otherwise, |coordinator| will
73 // be displayed once the previously shown dialog is dismissed.
74 - (void)addDialogCoordinator:(AlertCoordinator*)coordinator
75 forWebState:(web::WebState*)webState;
76
77 // Shows the dialog associated with the next context in |contextQueue|.
78 - (void)showNextDialog;
79
80 // Called when a button in |coordinator| is tapped.
81 - (void)buttonWasTappedForCoordinator:(AlertCoordinator*)coordinator;
82
83 // Adds buttons to |alertCoordinator|. A confirmation button with |label| as
84 // the text will be added for |confirmAction|, and a cancel button will be added
85 // for |cancelAction|.
86 - (void)setUpAlertCoordinator:(AlertCoordinator*)alertCoordinator
87 confirmAction:(ProceduralBlock)confirmAction
88 cancelAction:(ProceduralBlock)cancelAction
89 OKLabel:(NSString*)label;
90
91 // Sets up the JavaScript dialog blocking option for |alertCoordinator|.
92 // Overrides |alertCoordinator|'s |startAction| to call
93 // JavaScriptDialogWasShown(). Depending on the value of
94 // ShouldShowDialogBlockingOption() for |webState|, optionally adds a button to
95 // |alertCoordinator| allowing for the blocking of future dialogs. In addition
96 // to blocking dialogs for the WebState, the added button will call
97 // |alertCoordinator|'s |cancelAction|.
98 - (void)setUpBlockingOptionForCoordinator:(AlertCoordinator*)alertCoordinator
99 webState:(web::WebState*)webState;
100
101 // The block to use for the JavaScript dialog blocking option for |coordinator|.
102 - (ProceduralBlock)blockingActionForCoordinator:(AlertCoordinator*)coordinator;
103
104 @end
105
106 @implementation DialogPresenter
107
108 @synthesize active = _active;
109
110 - (instancetype)initWithDelegate:(id<DialogPresenterDelegate>)delegate
111 presentingViewController:(UIViewController*)viewController {
112 if ((self = [super init])) {
113 DCHECK(delegate);
114 DCHECK(viewController);
115 _delegate.reset(delegate);
116 _viewController.reset(viewController);
117 }
118 return self;
119 }
120
121 #pragma mark - Accessors
122
123 - (void)setActive:(BOOL)active {
124 if (_active != active) {
125 _active = active;
126 [self tryToPresent];
127 }
128 }
129
130 - (id<DialogPresenterDelegate>)delegate {
131 return _delegate;
132 }
133
134 - (UIViewController*)viewController {
135 return _viewController;
136 }
137
138 - (BOOL)isShowingDialog {
139 DCHECK_EQ(self.presentedDialogWebState != nullptr,
140 self.presentedDialogCoordinator != nil);
141 return self.presentedDialogCoordinator != nil;
142 }
143
144 - (web::WebState*)presentedDialogWebState {
145 return _presentedDialogWebState;
146 }
147
148 - (void)setPresentedDialogWebState:(web::WebState*)presentedDialogWebState {
149 _presentedDialogWebState = presentedDialogWebState;
150 }
151
152 - (AlertCoordinator*)presentedDialogCoordinator {
153 return _presentedDialogCoordinator;
154 }
155
156 - (void)setPresentedDialogCoordinator:
157 (AlertCoordinator*)presentedDialogCoordinator {
158 _presentedDialogCoordinator.reset([presentedDialogCoordinator retain]);
159 }
160
161 - (ActionSheetCoordinator*)blockingConfirmationCoordinator {
162 return _blockingConfirmationCoordinator;
163 }
164
165 - (void)setBlockingConfirmationCoordinator:
166 (ActionSheetCoordinator*)blockingConfirmationActionSheetCoordinator {
167 _blockingConfirmationCoordinator.reset(
168 [blockingConfirmationActionSheetCoordinator retain]);
169 }
170
171 #pragma mark - Public
172
173 - (void)runJavaScriptAlertPanelWithMessage:(NSString*)message
174 requestURL:(const GURL&)requestURL
175 webState:(web::WebState*)webState
176 completionHandler:(void (^)(void))completionHandler {
177 NSString* title =
178 [DialogPresenter localizedTitleForJavaScriptAlertFromPage:requestURL];
179 AlertCoordinator* alertCoordinator = [[[AlertCoordinator alloc]
180 initWithBaseViewController:self.viewController
181 title:title
182 message:message] autorelease];
183
184 // Handler.
185 base::WeakNSObject<DialogPresenter> weakSelf(self);
186 base::WeakNSObject<AlertCoordinator> weakCoordinator(alertCoordinator);
187 ProceduralBlock OKHandler = ^{
188 if (completionHandler)
189 completionHandler();
190 [weakSelf buttonWasTappedForCoordinator:weakCoordinator];
191 };
192
193 // Add button.
194 [alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_OK)
195 action:OKHandler
196 style:UIAlertActionStyleDefault];
197
198 // Add cancel handler.
199 alertCoordinator.cancelAction = completionHandler;
200 alertCoordinator.noInteractionAction = completionHandler;
201
202 // Blocking option setup.
203 [self setUpBlockingOptionForCoordinator:alertCoordinator webState:webState];
204
205 [self addDialogCoordinator:alertCoordinator forWebState:webState];
206 }
207
208 - (void)runJavaScriptConfirmPanelWithMessage:(NSString*)message
209 requestURL:(const GURL&)requestURL
210 webState:(web::WebState*)webState
211 completionHandler:
212 (void (^)(BOOL isConfirmed))completionHandler {
213 NSString* title =
214 [DialogPresenter localizedTitleForJavaScriptAlertFromPage:requestURL];
215 AlertCoordinator* alertCoordinator = [[[AlertCoordinator alloc]
216 initWithBaseViewController:self.viewController
217 title:title
218 message:message] autorelease];
219
220 // Actions.
221 ProceduralBlock confirmAction = ^{
222 if (completionHandler)
223 completionHandler(YES);
224 };
225
226 ProceduralBlock cancelAction = ^{
227 if (completionHandler)
228 completionHandler(NO);
229 };
230
231 // Coordinator Setup.
232 NSString* OKLabel = l10n_util::GetNSString(IDS_OK);
233 [self setUpAlertCoordinator:alertCoordinator
234 confirmAction:confirmAction
235 cancelAction:cancelAction
236 OKLabel:OKLabel];
237
238 // Blocking option setup.
239 [self setUpBlockingOptionForCoordinator:alertCoordinator webState:webState];
240
241 [self addDialogCoordinator:alertCoordinator forWebState:webState];
242 }
243
244 - (void)runJavaScriptTextInputPanelWithPrompt:(NSString*)message
245 defaultText:(NSString*)defaultText
246 requestURL:(const GURL&)requestURL
247 webState:(web::WebState*)webState
248 completionHandler:
249 (void (^)(NSString* input))completionHandler {
250 NSString* title =
251 [DialogPresenter localizedTitleForJavaScriptAlertFromPage:requestURL];
252 InputAlertCoordinator* alertCoordinator = [[[InputAlertCoordinator alloc]
253 initWithBaseViewController:self.viewController
254 title:title
255 message:message] autorelease];
256
257 // Actions.
258 base::WeakNSObject<InputAlertCoordinator> weakCoordinator(alertCoordinator);
259 ProceduralBlock confirmAction = ^{
260 if (completionHandler) {
261 NSString* textInput = [weakCoordinator textFields].firstObject.text;
262 completionHandler(textInput ? textInput : @"");
263 }
264 };
265
266 ProceduralBlock cancelAction = ^{
267 if (completionHandler)
268 completionHandler(nil);
269 };
270
271 // Coordinator Setup.
272 NSString* OKLabel = l10n_util::GetNSString(IDS_OK);
273 [self setUpAlertCoordinator:alertCoordinator
274 confirmAction:confirmAction
275 cancelAction:cancelAction
276 OKLabel:OKLabel];
277
278 // Blocking option setup.
279 [self setUpBlockingOptionForCoordinator:alertCoordinator webState:webState];
280
281 // Add text field.
282 [alertCoordinator
283 addTextFieldWithConfigurationHandler:^(UITextField* textField) {
284 textField.text = defaultText;
285 textField.accessibilityIdentifier =
286 kJavaScriptDialogTextFieldAccessibiltyIdentifier;
287 }];
288
289 [self addDialogCoordinator:alertCoordinator forWebState:webState];
290 }
291
292 - (void)runAuthDialogForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
293 proposedCredential:(NSURLCredential*)credential
294 webState:(web::WebState*)webState
295 completionHandler:(void (^)(NSString* user,
296 NSString* password))handler {
297 NSString* title = l10n_util::GetNSStringWithFixup(IDS_LOGIN_DIALOG_TITLE);
298 NSString* message =
299 ios_internal::nsurlprotectionspace_util::MessageForHTTPAuth(
300 protectionSpace);
301
302 InputAlertCoordinator* alertCoordinator = [[[InputAlertCoordinator alloc]
303 initWithBaseViewController:self.viewController
304 title:title
305 message:message] autorelease];
306
307 // Actions.
308 base::WeakNSObject<InputAlertCoordinator> weakCoordinator(alertCoordinator);
309 ProceduralBlock confirmAction = ^{
310 if (handler) {
311 NSString* username = [[weakCoordinator textFields] objectAtIndex:0].text;
312 NSString* password = [[weakCoordinator textFields] objectAtIndex:1].text;
313 handler(username, password);
314 }
315 };
316
317 ProceduralBlock cancelAction = ^{
318 if (handler)
319 handler(nil, nil);
320 };
321
322 // Coordinator Setup.
323 NSString* OKLabel =
324 l10n_util::GetNSStringWithFixup(IDS_LOGIN_DIALOG_OK_BUTTON_LABEL);
325 [self setUpAlertCoordinator:alertCoordinator
326 confirmAction:confirmAction
327 cancelAction:cancelAction
328 OKLabel:OKLabel];
329
330 // Add text fields.
331 NSString* username = credential.user ? credential.user : @"";
332 [alertCoordinator
333 addTextFieldWithConfigurationHandler:^(UITextField* textField) {
334 textField.text = username;
335 textField.placeholder = l10n_util::GetNSString(
336 IDS_IOS_HTTP_LOGIN_DIALOG_USERNAME_PLACEHOLDER);
337 }];
338 [alertCoordinator
339 addTextFieldWithConfigurationHandler:^(UITextField* textField) {
340 textField.placeholder = l10n_util::GetNSString(
341 IDS_IOS_HTTP_LOGIN_DIALOG_PASSWORD_PLACEHOLDER);
342 textField.secureTextEntry = YES;
343 }];
344
345 [self addDialogCoordinator:alertCoordinator forWebState:webState];
346 }
347
348 - (void)cancelDialogForWebState:(web::WebState*)webState {
349 DCHECK_NE(webState, self.presentedDialogWebState);
350 AlertCoordinator* dialogToCancel = _dialogCoordinatorsForWebStates[webState];
351 if (dialogToCancel) {
352 auto it =
353 std::find(_queuedWebStates.begin(), _queuedWebStates.end(), webState);
354 DCHECK(it != _queuedWebStates.end());
355 _queuedWebStates.erase(it);
356 [dialogToCancel executeCancelHandler];
357 [dialogToCancel stop];
358 _dialogCoordinatorsForWebStates.erase(webState);
359 }
360 }
361
362 - (void)cancelAllDialogs {
363 [self.presentedDialogCoordinator executeCancelHandler];
364 [self.presentedDialogCoordinator stop];
365 self.presentedDialogCoordinator = nil;
366 self.presentedDialogWebState = nil;
367 while (!_queuedWebStates.empty()) {
368 [self cancelDialogForWebState:_queuedWebStates.front()];
369 }
370 }
371
372 - (void)tryToPresent {
373 // Don't try to present if a JavaScript dialog blocking confirmation sheet is
374 // displayed.
375 if (self.blockingConfirmationCoordinator)
376 return;
377 // The active TabModel can't be changed while a JavaScript dialog is shown.
378 DCHECK(!self.showingDialog);
379 if (_active && !_queuedWebStates.empty() && !self.delegate.presenting)
380 [self showNextDialog];
381 }
382
383 + (NSString*)localizedTitleForJavaScriptAlertFromPage:(const GURL&)pageURL {
384 NSString* hostname = base::SysUTF8ToNSString(pageURL.host());
385 if (!hostname.length)
386 hostname = base::SysUTF8ToNSString(kAboutNullHostname);
387 return l10n_util::GetNSStringF(IDS_JAVASCRIPT_MESSAGEBOX_TITLE,
388 base::SysNSStringToUTF16(hostname));
389 }
390
391 #pragma mark - Private methods.
392
393 - (void)addDialogCoordinator:(AlertCoordinator*)coordinator
394 forWebState:(web::WebState*)webState {
395 DCHECK(coordinator);
396 DCHECK(webState);
397 DCHECK_NE(webState, self.presentedDialogWebState);
398 DCHECK(!_dialogCoordinatorsForWebStates[webState]);
399 _queuedWebStates.push_back(webState);
400 _dialogCoordinatorsForWebStates[webState] =
401 base::scoped_nsobject<AlertCoordinator>([coordinator retain]);
402
403 if (self.active && !self.showingDialog && !self.delegate.presenting)
404 [self showNextDialog];
405 }
406
407 - (void)showNextDialog {
408 DCHECK(self.active);
409 DCHECK(!self.showingDialog);
410 DCHECK(!_queuedWebStates.empty());
411 // Update properties and remove context and the dialog from queue.
412 self.presentedDialogWebState = _queuedWebStates.front();
413 _queuedWebStates.pop_front();
414 self.presentedDialogCoordinator =
415 _dialogCoordinatorsForWebStates[self.presentedDialogWebState];
416 _dialogCoordinatorsForWebStates.erase(self.presentedDialogWebState);
417 // Notify the delegate and display the dialog.
418 [self.delegate dialogPresenter:self
419 willShowDialogForWebState:self.presentedDialogWebState];
420 [self.presentedDialogCoordinator start];
421 }
422
423 - (void)buttonWasTappedForCoordinator:(AlertCoordinator*)coordinator {
424 if (coordinator != self.presentedDialogCoordinator)
425 return;
426 self.presentedDialogWebState = nil;
427 self.presentedDialogCoordinator = nil;
428 self.blockingConfirmationCoordinator = nil;
429 if (!_queuedWebStates.empty() && !self.delegate.presenting)
430 [self showNextDialog];
431 }
432
433 - (void)setUpAlertCoordinator:(AlertCoordinator*)alertCoordinator
434 confirmAction:(ProceduralBlock)confirmAction
435 cancelAction:(ProceduralBlock)cancelAction
436 OKLabel:(NSString*)label {
437 // Handlers.
438 base::WeakNSObject<DialogPresenter> weakSelf(self);
439 base::WeakNSObject<AlertCoordinator> weakCoordinator(alertCoordinator);
440
441 ProceduralBlock confirmHandler = ^{
442 if (confirmAction)
443 confirmAction();
444 [weakSelf buttonWasTappedForCoordinator:weakCoordinator];
445 };
446
447 ProceduralBlock cancelHandler = ^{
448 if (cancelAction)
449 cancelAction();
450 [weakSelf buttonWasTappedForCoordinator:weakCoordinator];
451 };
452
453 // Add buttons.
454 [alertCoordinator addItemWithTitle:label
455 action:confirmHandler
456 style:UIAlertActionStyleDefault];
457 [alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
458 action:cancelHandler
459 style:UIAlertActionStyleCancel];
460
461 // Add cancel handler.
462 alertCoordinator.cancelAction = cancelAction;
463 alertCoordinator.noInteractionAction = cancelAction;
464 }
465
466 - (void)setUpBlockingOptionForCoordinator:(AlertCoordinator*)alertCoordinator
467 webState:(web::WebState*)webState {
468 DCHECK(alertCoordinator);
469 DCHECK(webState);
470
471 // Set up the start action.
472 base::WeakNSObject<DialogPresenter> weakSelf(self);
473 base::WeakNSObject<AlertCoordinator> weakCoordinator(alertCoordinator);
474 ProceduralBlock originalStartAction = alertCoordinator.startAction;
475 alertCoordinator.startAction = ^{
476 if (originalStartAction)
477 originalStartAction();
478 JavaScriptDialogWasShown(webState);
479 };
480
481 // Early return if a blocking option should not be added.
482 if (!ShouldShowDialogBlockingOption(webState))
483 return;
484
485 ProceduralBlock blockingAction =
486 [self blockingActionForCoordinator:alertCoordinator];
487 NSString* blockingOptionTitle =
488 l10n_util::GetNSString(IDS_IOS_JAVA_SCRIPT_DIALOG_BLOCKING_BUTTON_TEXT);
489 [alertCoordinator addItemWithTitle:blockingOptionTitle
490 action:blockingAction
491 style:UIAlertActionStyleDefault];
492 }
493
494 - (ProceduralBlock)blockingActionForCoordinator:(AlertCoordinator*)coordinator {
495 base::WeakNSObject<DialogPresenter> weakSelf(self);
496 base::WeakNSObject<AlertCoordinator> weakCoordinator(coordinator);
497 base::WeakNSObject<UIViewController> weakBaseViewController(
498 coordinator.baseViewController);
499 ProceduralBlock cancelAction = coordinator.cancelAction;
500 return [[^{
501 // Create the confirmation coordinator. Use an action sheet on iPhone and
502 // an alert on iPhone.
503 NSString* confirmMessage =
504 l10n_util::GetNSString(IDS_JAVASCRIPT_MESSAGEBOX_SUPPRESS_OPTION);
505 AlertCoordinator* confirmationCoordinator =
506 IsIPadIdiom()
507 ? [[[AlertCoordinator alloc]
508 initWithBaseViewController:weakBaseViewController
509 title:nil
510 message:confirmMessage] autorelease]
511 : [[[ActionSheetCoordinator alloc]
512 initWithBaseViewController:weakBaseViewController
513 title:nil
514 message:confirmMessage
515 rect:CGRectZero
516 view:nil] autorelease];
517 // Set up button actions.
518 ProceduralBlock confirmHandler = ^{
519 if (cancelAction)
520 cancelAction();
521 base::scoped_nsobject<DialogPresenter> strongSelf([weakSelf retain]);
522 if (!strongSelf)
523 return;
524 DialogBlockingOptionSelected([strongSelf presentedDialogWebState]);
525 [strongSelf buttonWasTappedForCoordinator:weakCoordinator];
526 };
527 ProceduralBlock cancelHandler = ^{
528 if (cancelAction)
529 cancelAction();
530 [weakSelf buttonWasTappedForCoordinator:weakCoordinator];
531 };
532 NSString* blockingOptionTitle =
533 l10n_util::GetNSString(IDS_IOS_JAVA_SCRIPT_DIALOG_BLOCKING_BUTTON_TEXT);
534 [confirmationCoordinator addItemWithTitle:blockingOptionTitle
535 action:confirmHandler
536 style:UIAlertActionStyleDestructive];
537 [confirmationCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
538 action:cancelHandler
539 style:UIAlertActionStyleCancel];
540 [weakSelf setBlockingConfirmationCoordinator:confirmationCoordinator];
541 [[weakSelf blockingConfirmationCoordinator] start];
542 } copy] autorelease];
543 }
544
545 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/dialogs/dialog_presenter.h ('k') | ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698