Skip to content

Commit f4fa8cf

Browse files
committed
Impl login dialog queue to merge multiple login windows
1 parent e92564d commit f4fa8cf

File tree

2 files changed

+332
-5
lines changed

2 files changed

+332
-5
lines changed

src/browser/shell_login_dialog.cc

Lines changed: 217 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,37 @@
2323
#include "base/bind.h"
2424
#include "base/logging.h"
2525
#include "base/strings/utf_string_conversions.h"
26+
#include "chrome/browser/chrome_notification_types.h"
2627
#include "content/public/browser/browser_thread.h"
28+
#include "content/public/browser/notification_registrar.h"
29+
#include "content/public/browser/notification_service.h"
2730
#include "content/public/browser/resource_dispatcher_host.h"
2831
#include "net/base/auth.h"
2932
#include "net/url_request/url_request.h"
3033
#include "ui/base/text/text_elider.h"
3134

35+
using content::BrowserThread;
36+
using content::ResourceDispatcherHost;
37+
38+
namespace {
39+
// Helper to remove the ref from an net::URLRequest to the ShellLoginDialog.
40+
// Should only be called from the IO thread, since it accesses an
41+
// net::URLRequest.
42+
void ResetShellLoginDialogForRequest(net::URLRequest* request) {
43+
ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
44+
}
45+
46+
} // namespace
47+
3248
namespace content {
3349

50+
ShellLoginDialog::ShellLoginDialogList ShellLoginDialog::dialog_queue_;
51+
3452
ShellLoginDialog::ShellLoginDialog(
3553
net::AuthChallengeInfo* auth_info,
3654
net::URLRequest* request) : auth_info_(auth_info),
37-
request_(request) {
55+
request_(request),
56+
handled_auth_(false) {
3857
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
3958
BrowserThread::PostTask(
4059
BrowserThread::UI, FROM_HERE,
@@ -53,6 +72,21 @@ void ShellLoginDialog::OnRequestCancelled() {
5372
void ShellLoginDialog::UserAcceptedAuth(const string16& username,
5473
const string16& password) {
5574
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
75+
76+
if (TestAndSetAuthHandled())
77+
return;
78+
79+
// Calling NotifyAuthSupplied() directly instead of posting a task
80+
// allows other ShellLoginDialog instances to queue their
81+
// CloseContentsDeferred() before ours. Closing dialogs in the
82+
// opposite order as they were created avoids races where remaining
83+
// dialogs in the same tab may be briefly displayed to the user
84+
// before they are removed.
85+
NotifyAuthSupplied(username, password);
86+
87+
BrowserThread::PostTask(
88+
BrowserThread::UI, FROM_HERE,
89+
base::Bind(&ShellLoginDialog::CloseContentsDeferred, this));
5690
BrowserThread::PostTask(
5791
BrowserThread::IO, FROM_HERE,
5892
base::Bind(&ShellLoginDialog::SendAuthToRequester, this,
@@ -61,13 +95,25 @@ void ShellLoginDialog::UserAcceptedAuth(const string16& username,
6195

6296
void ShellLoginDialog::UserCancelledAuth() {
6397
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
98+
if (TestAndSetAuthHandled())
99+
return;
100+
101+
// Similar to how we deal with notifications above in SetAuth()
102+
if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
103+
NotifyAuthCancelled();
104+
} else {
105+
BrowserThread::PostTask(
106+
BrowserThread::UI, FROM_HERE,
107+
base::Bind(&ShellLoginDialog::NotifyAuthCancelled, this));
108+
}
109+
64110
BrowserThread::PostTask(
65111
BrowserThread::IO, FROM_HERE,
66112
base::Bind(&ShellLoginDialog::SendAuthToRequester, this,
67113
false, string16(), string16()));
68114
BrowserThread::PostTask(
69115
BrowserThread::UI, FROM_HERE,
70-
base::Bind(&ShellLoginDialog::PlatformCleanUp, this));
116+
base::Bind(&ShellLoginDialog::CloseContentsDeferred, this));
71117
}
72118

73119
ShellLoginDialog::~ShellLoginDialog() {
@@ -93,16 +139,38 @@ void ShellLoginDialog::PrepDialog(const string16& host,
93139
explanation += ASCIIToUTF16(".");
94140
}
95141

142+
AddObservers();
143+
dialog_queue_.push_back(this);
96144
PlatformCreateDialog(explanation);
145+
if (dialog_queue_.size() == 1)
146+
PlatformShowDialog();
147+
}
148+
149+
void ShellLoginDialog::HandleQueueOnClose()
150+
{
151+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
152+
ShellLoginDialogList::iterator i(
153+
std::find(dialog_queue_.begin(), dialog_queue_.end(), this));
154+
155+
// Ignore the second invocation.
156+
if (i == dialog_queue_.end())
157+
return;
158+
159+
bool removed_topmost_dialog = i == dialog_queue_.begin();
160+
dialog_queue_.erase(i);
161+
if (!dialog_queue_.empty() && removed_topmost_dialog) {
162+
ShellLoginDialog* next_dlg = dialog_queue_.front();
163+
next_dlg->PlatformShowDialog();
164+
}
97165
}
98166

99167
void ShellLoginDialog::SendAuthToRequester(bool success,
100168
const string16& username,
101169
const string16& password) {
102170
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
103-
if (success)
171+
if (success) {
104172
request_->SetAuth(net::AuthCredentials(username, password));
105-
else
173+
} else
106174
request_->CancelAuth();
107175
ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request_);
108176

@@ -111,4 +179,149 @@ void ShellLoginDialog::SendAuthToRequester(bool success,
111179
base::Bind(&ShellLoginDialog::PlatformCleanUp, this));
112180
}
113181

182+
void ShellLoginDialog::NotifyAuthSupplied(const string16& username,
183+
const string16& password) {
184+
185+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
186+
187+
content::NotificationService* service =
188+
content::NotificationService::current();
189+
190+
AuthSuppliedLoginNotificationDetails details(this, username, password);
191+
192+
service->Notify(chrome::NOTIFICATION_AUTH_SUPPLIED,
193+
content::Source<ShellLoginDialog>(this),
194+
content::Details<AuthSuppliedLoginNotificationDetails>(&details));
195+
}
196+
197+
void ShellLoginDialog::NotifyAuthCancelled() {
198+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
199+
DCHECK(WasAuthHandled());
200+
201+
content::NotificationService* service =
202+
content::NotificationService::current();
203+
204+
AuthSuppliedLoginNotificationDetails details(this, string16(), string16());
205+
206+
service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
207+
content::Source<ShellLoginDialog>(this),
208+
content::Details<LoginNotificationDetails>(&details));
209+
}
210+
211+
212+
void ShellLoginDialog::Observe(int type,
213+
const content::NotificationSource& source,
214+
const content::NotificationDetails& details) {
215+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
216+
DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
217+
type == chrome::NOTIFICATION_AUTH_CANCELLED);
218+
219+
// Break out early if we aren't interested in the notification.
220+
if (WasAuthHandled())
221+
return;
222+
223+
LoginNotificationDetails* login_details =
224+
content::Details<LoginNotificationDetails>(details).ptr();
225+
226+
// WasAuthHandled() should always test positive before we publish
227+
// AUTH_SUPPLIED or AUTH_CANCELLED notifications.
228+
DCHECK(login_details->handler() != this);
229+
230+
// Only handle notification for the identical auth info.
231+
if (!login_details->handler()->auth_info()->Equals(*auth_info()))
232+
return;
233+
234+
// Set or cancel the auth in this handler.
235+
if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
236+
AuthSuppliedLoginNotificationDetails* supplied_details =
237+
content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
238+
UserAcceptedAuth(supplied_details->username(), supplied_details->password());
239+
} else {
240+
DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
241+
UserCancelledAuth();
242+
}
243+
}
244+
245+
// Returns whether authentication had been handled (SetAuth or CancelAuth).
246+
bool ShellLoginDialog::WasAuthHandled() const {
247+
base::AutoLock lock(handled_auth_lock_);
248+
bool was_handled = handled_auth_;
249+
return was_handled;
250+
}
251+
252+
// Marks authentication as handled and returns the previous handled state.
253+
bool ShellLoginDialog::TestAndSetAuthHandled() {
254+
base::AutoLock lock(handled_auth_lock_);
255+
bool was_handled = handled_auth_;
256+
handled_auth_ = true;
257+
return was_handled;
258+
}
259+
260+
// Closes the view_contents from the UI loop.
261+
void ShellLoginDialog::CloseContentsDeferred() {
262+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
263+
264+
HandleQueueOnClose();
265+
PlatformCleanUp();
266+
}
267+
268+
void ShellLoginDialog::AddObservers() {
269+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
270+
271+
// This is probably OK; we need to listen to everything and we break out of
272+
// the Observe() if we aren't handling the same auth_info().
273+
registrar_.reset(new content::NotificationRegistrar);
274+
registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
275+
content::NotificationService::AllBrowserContextsAndSources());
276+
registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
277+
content::NotificationService::AllBrowserContextsAndSources());
278+
}
279+
280+
void ShellLoginDialog::RemoveObservers() {
281+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
282+
283+
registrar_.reset();
284+
}
285+
286+
void ShellLoginDialog::ReleaseSoon() {
287+
if (!TestAndSetAuthHandled()) {
288+
BrowserThread::PostTask(
289+
BrowserThread::IO, FROM_HERE,
290+
base::Bind(&ShellLoginDialog::CancelAuthDeferred, this));
291+
BrowserThread::PostTask(
292+
BrowserThread::UI, FROM_HERE,
293+
base::Bind(&ShellLoginDialog::NotifyAuthCancelled, this));
294+
}
295+
296+
BrowserThread::PostTask(
297+
BrowserThread::UI, FROM_HERE,
298+
base::Bind(&ShellLoginDialog::RemoveObservers, this));
299+
300+
// Delete this object once all InvokeLaters have been called.
301+
BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
302+
}
303+
304+
// Calls SetAuth from the IO loop.
305+
void ShellLoginDialog::SetAuthDeferred(const string16& username,
306+
const string16& password) {
307+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
308+
309+
if (request_) {
310+
request_->SetAuth(net::AuthCredentials(username, password));
311+
ResetShellLoginDialogForRequest(request_);
312+
}
313+
}
314+
315+
// Calls CancelAuth from the IO loop.
316+
void ShellLoginDialog::CancelAuthDeferred() {
317+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
318+
319+
if (request_) {
320+
request_->CancelAuth();
321+
// Verify that CancelAuth doesn't destroy the request via our delegate.
322+
DCHECK(request_ != NULL);
323+
ResetShellLoginDialogForRequest(request_);
324+
}
325+
}
326+
114327
} // namespace content

0 commit comments

Comments
 (0)