Wez | 627fd58 | 2019-06-25 04:30:38 | [diff] [blame] | 1 | // Copyright 2019 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 "fuchsia/engine/browser/navigation_controller_impl.h" |
| 6 | |
| 7 | #include "base/strings/strcat.h" |
| 8 | #include "base/strings/utf_string_conversions.h" |
| 9 | #include "content/public/browser/navigation_entry.h" |
| 10 | #include "content/public/browser/navigation_handle.h" |
| 11 | #include "content/public/browser/web_contents.h" |
Lucas Furukawa Gadani | a9c4568 | 2019-07-31 22:05:14 | [diff] [blame^] | 12 | #include "content/public/common/was_activated_option.mojom.h" |
Wez | 627fd58 | 2019-06-25 04:30:38 | [diff] [blame] | 13 | #include "ui/base/page_transition_types.h" |
| 14 | |
| 15 | namespace { |
| 16 | |
| 17 | void UpdateNavigationStateFromNavigationEntry( |
| 18 | content::NavigationEntry* entry, |
| 19 | content::WebContents* web_contents, |
| 20 | fuchsia::web::NavigationState* navigation_state) { |
| 21 | DCHECK(entry); |
| 22 | DCHECK(web_contents); |
| 23 | DCHECK(navigation_state); |
| 24 | |
| 25 | navigation_state->set_title(base::UTF16ToUTF8(entry->GetTitleForDisplay())); |
| 26 | navigation_state->set_url(entry->GetURL().spec()); |
| 27 | |
| 28 | switch (entry->GetPageType()) { |
| 29 | case content::PageType::PAGE_TYPE_NORMAL: |
| 30 | case content::PageType::PAGE_TYPE_INTERSTITIAL: |
| 31 | navigation_state->set_page_type(fuchsia::web::PageType::NORMAL); |
| 32 | break; |
| 33 | case content::PageType::PAGE_TYPE_ERROR: |
| 34 | navigation_state->set_page_type(fuchsia::web::PageType::ERROR); |
| 35 | break; |
| 36 | } |
| 37 | |
| 38 | navigation_state->set_can_go_back(web_contents->GetController().CanGoBack()); |
| 39 | navigation_state->set_can_go_forward( |
| 40 | web_contents->GetController().CanGoForward()); |
| 41 | } |
| 42 | |
| 43 | } // namespace |
| 44 | |
| 45 | NavigationControllerImpl::NavigationControllerImpl( |
| 46 | content::WebContents* web_contents) |
| 47 | : web_contents_(web_contents), weak_factory_(this) { |
| 48 | Observe(web_contents_); |
| 49 | } |
| 50 | |
| 51 | NavigationControllerImpl::~NavigationControllerImpl() = default; |
| 52 | |
| 53 | void NavigationControllerImpl::AddBinding( |
| 54 | fidl::InterfaceRequest<fuchsia::web::NavigationController> controller) { |
| 55 | controller_bindings_.AddBinding(this, std::move(controller)); |
| 56 | } |
| 57 | |
| 58 | void NavigationControllerImpl::SetEventListener( |
| 59 | fidl::InterfaceHandle<fuchsia::web::NavigationEventListener> listener) { |
| 60 | // Reset the event buffer state. |
| 61 | waiting_for_navigation_event_ack_ = false; |
| 62 | previous_navigation_state_ = {}; |
| 63 | pending_navigation_event_ = {}; |
| 64 | |
Fabrice de Gans-Riberi | 4a5c47e | 2019-07-31 20:40:13 | [diff] [blame] | 65 | // Simply unbind if no new listener was set. |
| 66 | if (!listener) { |
Wez | 627fd58 | 2019-06-25 04:30:38 | [diff] [blame] | 67 | navigation_listener_.Unbind(); |
Fabrice de Gans-Riberi | 4a5c47e | 2019-07-31 20:40:13 | [diff] [blame] | 68 | return; |
| 69 | } |
| 70 | |
| 71 | navigation_listener_.Bind(std::move(listener)); |
| 72 | navigation_listener_.set_error_handler( |
| 73 | [this](zx_status_t status) { SetEventListener(nullptr); }); |
| 74 | |
| 75 | // Immediately send the current navigation state, even if it is empty. |
| 76 | if (web_contents_->GetController().GetVisibleEntry() == nullptr) { |
| 77 | waiting_for_navigation_event_ack_ = true; |
| 78 | navigation_listener_->OnNavigationStateChanged( |
| 79 | fuchsia::web::NavigationState(), [this]() { |
| 80 | waiting_for_navigation_event_ack_ = false; |
| 81 | MaybeSendNavigationEvent(); |
| 82 | }); |
| 83 | } else { |
| 84 | OnNavigationEntryChanged(); |
Wez | 627fd58 | 2019-06-25 04:30:38 | [diff] [blame] | 85 | } |
| 86 | } |
| 87 | |
| 88 | void NavigationControllerImpl::OnNavigationEntryChanged() { |
| 89 | fuchsia::web::NavigationState new_state; |
| 90 | new_state.set_is_main_document_loaded(is_main_document_loaded_); |
| 91 | UpdateNavigationStateFromNavigationEntry( |
| 92 | web_contents_->GetController().GetVisibleEntry(), web_contents_, |
| 93 | &new_state); |
| 94 | |
| 95 | DiffNavigationEntries(previous_navigation_state_, new_state, |
| 96 | &pending_navigation_event_); |
| 97 | previous_navigation_state_ = std::move(new_state); |
| 98 | |
| 99 | base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 100 | FROM_HERE, |
| 101 | base::BindOnce(&NavigationControllerImpl::MaybeSendNavigationEvent, |
| 102 | weak_factory_.GetWeakPtr())); |
| 103 | } |
| 104 | |
| 105 | void NavigationControllerImpl::MaybeSendNavigationEvent() { |
| 106 | if (!navigation_listener_) |
| 107 | return; |
| 108 | |
| 109 | if (pending_navigation_event_.IsEmpty() || |
| 110 | waiting_for_navigation_event_ack_) { |
| 111 | return; |
| 112 | } |
| 113 | |
| 114 | waiting_for_navigation_event_ack_ = true; |
| 115 | |
| 116 | // Send the event to the observer and, upon acknowledgement, revisit this |
| 117 | // function to send another update. |
| 118 | navigation_listener_->OnNavigationStateChanged( |
| 119 | std::move(pending_navigation_event_), [this]() { |
| 120 | waiting_for_navigation_event_ack_ = false; |
| 121 | MaybeSendNavigationEvent(); |
| 122 | }); |
| 123 | |
| 124 | pending_navigation_event_ = {}; |
| 125 | } |
| 126 | |
| 127 | void NavigationControllerImpl::LoadUrl(std::string url, |
| 128 | fuchsia::web::LoadUrlParams params, |
| 129 | LoadUrlCallback callback) { |
| 130 | fuchsia::web::NavigationController_LoadUrl_Result result; |
| 131 | GURL validated_url(url); |
| 132 | if (!validated_url.is_valid()) { |
| 133 | result.set_err(fuchsia::web::NavigationControllerError::INVALID_URL); |
| 134 | callback(std::move(result)); |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | content::NavigationController::LoadURLParams params_converted(validated_url); |
| 139 | if (params.has_headers()) { |
| 140 | std::vector<std::string> extra_headers; |
| 141 | extra_headers.reserve(params.headers().size()); |
| 142 | for (const auto& header : params.headers()) { |
| 143 | // TODO(crbug.com/964732): Check there is no colon in |header_name|. |
| 144 | base::StringPiece header_name( |
| 145 | reinterpret_cast<const char*>(header.name.data()), |
| 146 | header.name.size()); |
| 147 | base::StringPiece header_value( |
| 148 | reinterpret_cast<const char*>(header.value.data()), |
| 149 | header.value.size()); |
| 150 | extra_headers.emplace_back( |
| 151 | base::StrCat({header_name, ": ", header_value})); |
| 152 | } |
| 153 | params_converted.extra_headers = base::JoinString(extra_headers, "\n"); |
| 154 | } |
| 155 | |
| 156 | if (validated_url.scheme() == url::kDataScheme) |
| 157 | params_converted.load_type = content::NavigationController::LOAD_TYPE_DATA; |
| 158 | |
| 159 | params_converted.transition_type = ui::PageTransitionFromInt( |
| 160 | ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); |
| 161 | if (params.has_was_user_activated() && params.was_user_activated()) { |
Lucas Furukawa Gadani | a9c4568 | 2019-07-31 22:05:14 | [diff] [blame^] | 162 | params_converted.was_activated = content::mojom::WasActivatedOption::kYes; |
Wez | 627fd58 | 2019-06-25 04:30:38 | [diff] [blame] | 163 | } else { |
Lucas Furukawa Gadani | a9c4568 | 2019-07-31 22:05:14 | [diff] [blame^] | 164 | params_converted.was_activated = content::mojom::WasActivatedOption::kNo; |
Wez | 627fd58 | 2019-06-25 04:30:38 | [diff] [blame] | 165 | } |
| 166 | |
| 167 | web_contents_->GetController().LoadURLWithParams(params_converted); |
| 168 | result.set_response(fuchsia::web::NavigationController_LoadUrl_Response()); |
| 169 | callback(std::move(result)); |
| 170 | } |
| 171 | |
| 172 | void NavigationControllerImpl::GoBack() { |
| 173 | if (web_contents_->GetController().CanGoBack()) |
| 174 | web_contents_->GetController().GoBack(); |
| 175 | } |
| 176 | |
| 177 | void NavigationControllerImpl::GoForward() { |
| 178 | if (web_contents_->GetController().CanGoForward()) |
| 179 | web_contents_->GetController().GoForward(); |
| 180 | } |
| 181 | |
| 182 | void NavigationControllerImpl::Stop() { |
| 183 | web_contents_->Stop(); |
| 184 | } |
| 185 | |
| 186 | void NavigationControllerImpl::Reload(fuchsia::web::ReloadType type) { |
| 187 | content::ReloadType internal_reload_type; |
| 188 | switch (type) { |
| 189 | case fuchsia::web::ReloadType::PARTIAL_CACHE: |
| 190 | internal_reload_type = content::ReloadType::NORMAL; |
| 191 | break; |
| 192 | case fuchsia::web::ReloadType::NO_CACHE: |
| 193 | internal_reload_type = content::ReloadType::BYPASSING_CACHE; |
| 194 | break; |
| 195 | } |
| 196 | web_contents_->GetController().Reload(internal_reload_type, false); |
| 197 | } |
| 198 | |
| 199 | void NavigationControllerImpl::GetVisibleEntry( |
| 200 | fuchsia::web::NavigationController::GetVisibleEntryCallback callback) { |
| 201 | content::NavigationEntry* entry = |
| 202 | web_contents_->GetController().GetVisibleEntry(); |
| 203 | if (!entry) { |
| 204 | callback({}); |
| 205 | return; |
| 206 | } |
| 207 | |
| 208 | fuchsia::web::NavigationState state; |
| 209 | state.set_is_main_document_loaded(is_main_document_loaded_); |
| 210 | UpdateNavigationStateFromNavigationEntry(entry, web_contents_, &state); |
| 211 | callback(std::move(state)); |
| 212 | } |
| 213 | |
| 214 | void NavigationControllerImpl::TitleWasSet(content::NavigationEntry* entry) { |
| 215 | // The title was changed after the document was loaded. |
| 216 | OnNavigationEntryChanged(); |
| 217 | } |
| 218 | |
| 219 | void NavigationControllerImpl::DocumentAvailableInMainFrame() { |
| 220 | // The main document is loaded, but not necessarily all the subresources. Some |
| 221 | // fields like "title" will change here. |
| 222 | |
| 223 | OnNavigationEntryChanged(); |
| 224 | } |
| 225 | |
| 226 | void NavigationControllerImpl::DidFinishLoad( |
| 227 | content::RenderFrameHost* render_frame_host, |
| 228 | const GURL& validated_url) { |
| 229 | // The document and its statically-declared subresources are loaded. |
| 230 | is_main_document_loaded_ = true; |
| 231 | OnNavigationEntryChanged(); |
| 232 | } |
| 233 | |
| 234 | void NavigationControllerImpl::DidStartNavigation( |
| 235 | content::NavigationHandle* navigation_handle) { |
| 236 | if (navigation_handle->IsSameDocument()) |
| 237 | return; |
| 238 | |
| 239 | is_main_document_loaded_ = false; |
| 240 | OnNavigationEntryChanged(); |
| 241 | } |
| 242 | |
| 243 | void DiffNavigationEntries(const fuchsia::web::NavigationState& old_entry, |
| 244 | const fuchsia::web::NavigationState& new_entry, |
| 245 | fuchsia::web::NavigationState* difference) { |
| 246 | DCHECK(difference); |
| 247 | |
| 248 | DCHECK(new_entry.has_title()); |
| 249 | if (!old_entry.has_title() || (new_entry.title() != old_entry.title())) { |
| 250 | difference->set_title(new_entry.title()); |
| 251 | } |
| 252 | |
| 253 | DCHECK(new_entry.has_url()); |
| 254 | if (!old_entry.has_url() || (new_entry.url() != old_entry.url())) { |
| 255 | difference->set_url(new_entry.url()); |
| 256 | } |
| 257 | |
| 258 | DCHECK(new_entry.has_page_type()); |
| 259 | if (!old_entry.has_page_type() || |
| 260 | (new_entry.page_type() != old_entry.page_type())) { |
| 261 | difference->set_page_type(new_entry.page_type()); |
| 262 | } |
| 263 | |
| 264 | DCHECK(new_entry.has_can_go_back()); |
| 265 | if (!old_entry.has_can_go_back() || |
| 266 | old_entry.can_go_back() != new_entry.can_go_back()) { |
| 267 | difference->set_can_go_back(new_entry.can_go_back()); |
| 268 | } |
| 269 | |
| 270 | DCHECK(new_entry.has_can_go_forward()); |
| 271 | if (!old_entry.has_can_go_forward() || |
| 272 | old_entry.can_go_forward() != new_entry.can_go_forward()) { |
| 273 | difference->set_can_go_forward(new_entry.can_go_forward()); |
| 274 | } |
| 275 | |
| 276 | DCHECK(new_entry.has_is_main_document_loaded()); |
| 277 | if (!old_entry.has_is_main_document_loaded() || |
| 278 | old_entry.is_main_document_loaded() != |
| 279 | new_entry.is_main_document_loaded()) { |
| 280 | difference->set_is_main_document_loaded( |
| 281 | new_entry.is_main_document_loaded()); |
| 282 | } |
| 283 | } |