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
3 // found in the LICENSE file.
4 
5 #include "components/web_modal/web_contents_modal_dialog_manager.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/check.h"
11 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
12 #include "content/public/browser/navigation_handle.h"
13 #include "content/public/browser/web_contents.h"
14 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
15 
16 using content::WebContents;
17 
18 namespace web_modal {
19 
~WebContentsModalDialogManager()20 WebContentsModalDialogManager::~WebContentsModalDialogManager() {
21   DCHECK(child_dialogs_.empty());
22 }
23 
SetDelegate(WebContentsModalDialogManagerDelegate * d)24 void WebContentsModalDialogManager::SetDelegate(
25     WebContentsModalDialogManagerDelegate* d) {
26   delegate_ = d;
27 
28   for (const auto& dialog : child_dialogs_) {
29     // Delegate can be null on Views/Win32 during tab drag.
30     dialog.manager->HostChanged(d ? d->GetWebContentsModalDialogHost()
31                                   : nullptr);
32   }
33 }
34 
35 // TODO(gbillock): Maybe "ShowBubbleWithManager"?
ShowDialogWithManager(gfx::NativeWindow dialog,std::unique_ptr<SingleWebContentsDialogManager> manager)36 void WebContentsModalDialogManager::ShowDialogWithManager(
37     gfx::NativeWindow dialog,
38     std::unique_ptr<SingleWebContentsDialogManager> manager) {
39   if (delegate_)
40     manager->HostChanged(delegate_->GetWebContentsModalDialogHost());
41   child_dialogs_.emplace_back(dialog, std::move(manager));
42 
43   if (child_dialogs_.size() == 1) {
44     BlockWebContentsInteraction(true);
45     if (delegate_ && delegate_->IsWebContentsVisible(web_contents()))
46       child_dialogs_.back().manager->Show();
47   }
48 }
49 
IsDialogActive() const50 bool WebContentsModalDialogManager::IsDialogActive() const {
51   return !child_dialogs_.empty();
52 }
53 
FocusTopmostDialog() const54 void WebContentsModalDialogManager::FocusTopmostDialog() const {
55   DCHECK(!child_dialogs_.empty());
56   child_dialogs_.front().manager->Focus();
57 }
58 
GetWebContents() const59 content::WebContents* WebContentsModalDialogManager::GetWebContents() const {
60   return web_contents();
61 }
62 
WillClose(gfx::NativeWindow dialog)63 void WebContentsModalDialogManager::WillClose(gfx::NativeWindow dialog) {
64   auto dlg = std::find_if(child_dialogs_.begin(), child_dialogs_.end(),
65                           [dialog](const DialogState& child_dialog) {
66                             return child_dialog.dialog == dialog;
67                           });
68 
69   // The Views tab contents modal dialog calls WillClose twice.  Ignore the
70   // second invocation.
71   if (dlg == child_dialogs_.end())
72     return;
73 
74   bool removed_topmost_dialog = dlg == child_dialogs_.begin();
75   child_dialogs_.erase(dlg);
76   if (!closing_all_dialogs_ &&
77       (!child_dialogs_.empty() && removed_topmost_dialog) &&
78       (delegate_ && delegate_->IsWebContentsVisible(web_contents()))) {
79     child_dialogs_.front().manager->Show();
80   }
81 
82   BlockWebContentsInteraction(!child_dialogs_.empty());
83 }
84 
WebContentsModalDialogManager(content::WebContents * web_contents)85 WebContentsModalDialogManager::WebContentsModalDialogManager(
86     content::WebContents* web_contents)
87     : content::WebContentsObserver(web_contents),
88       delegate_(nullptr),
89       web_contents_is_hidden_(web_contents->GetVisibility() ==
90                               content::Visibility::HIDDEN),
91       closing_all_dialogs_(false) {}
92 
DialogState(gfx::NativeWindow dialog,std::unique_ptr<SingleWebContentsDialogManager> mgr)93 WebContentsModalDialogManager::DialogState::DialogState(
94     gfx::NativeWindow dialog,
95     std::unique_ptr<SingleWebContentsDialogManager> mgr)
96     : dialog(dialog), manager(std::move(mgr)) {}
97 
98 WebContentsModalDialogManager::DialogState::DialogState(DialogState&& state) =
99     default;
100 
101 WebContentsModalDialogManager::DialogState::~DialogState() = default;
102 
103 // TODO(gbillock): Move this to Views impl within Show()? It would
104 // call WebContents* contents = native_delegate_->GetWebContents(); and
105 // then set the block state. Advantage: could restrict some of the
106 // WCMDM delegate methods, then, and pass them behind the scenes.
BlockWebContentsInteraction(bool blocked)107 void WebContentsModalDialogManager::BlockWebContentsInteraction(bool blocked) {
108   WebContents* contents = web_contents();
109   if (!contents) {
110     // The WebContents has already disconnected.
111     return;
112   }
113 
114   contents->SetIgnoreInputEvents(blocked);
115   if (delegate_)
116     delegate_->SetWebContentsBlocked(contents, blocked);
117 }
118 
CloseAllDialogs()119 void WebContentsModalDialogManager::CloseAllDialogs() {
120   closing_all_dialogs_ = true;
121 
122   // Clear out any dialogs since we are leaving this page entirely.
123   while (!child_dialogs_.empty()) {
124     child_dialogs_.front().manager->Close();
125   }
126 
127   closing_all_dialogs_ = false;
128 }
129 
DidFinishNavigation(content::NavigationHandle * navigation_handle)130 void WebContentsModalDialogManager::DidFinishNavigation(
131     content::NavigationHandle* navigation_handle) {
132   if (!navigation_handle->IsInMainFrame() || !navigation_handle->HasCommitted())
133     return;
134 
135   // Close constrained windows if necessary.
136   if (!net::registry_controlled_domains::SameDomainOrHost(
137           navigation_handle->GetPreviousURL(), navigation_handle->GetURL(),
138           net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES))
139     CloseAllDialogs();
140 }
141 
DidGetIgnoredUIEvent()142 void WebContentsModalDialogManager::DidGetIgnoredUIEvent() {
143   if (!child_dialogs_.empty()) {
144     child_dialogs_.front().manager->Focus();
145   }
146 }
147 
OnVisibilityChanged(content::Visibility visibility)148 void WebContentsModalDialogManager::OnVisibilityChanged(
149     content::Visibility visibility) {
150   const bool web_contents_was_hidden = web_contents_is_hidden_;
151   web_contents_is_hidden_ = visibility == content::Visibility::HIDDEN;
152 
153   // Avoid reshowing on transitions between VISIBLE and OCCLUDED.
154   if (child_dialogs_.empty() ||
155       web_contents_is_hidden_ == web_contents_was_hidden) {
156     return;
157   }
158 
159   if (web_contents_is_hidden_)
160     child_dialogs_.front().manager->Hide();
161   else
162     child_dialogs_.front().manager->Show();
163 }
164 
WebContentsDestroyed()165 void WebContentsModalDialogManager::WebContentsDestroyed() {
166   // First cleanly close all child dialogs.
167   // TODO(mpcomplete): handle case if MaybeCloseChildWindows() already asked
168   // some of these to close.  CloseAllDialogs is async, so it might get called
169   // twice before it runs.
170   CloseAllDialogs();
171 }
172 
173 WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsModalDialogManager)
174 
175 }  // namespace web_modal
176