blob: b1bd13932d8329d26419f975c6dbcfdf961f9ba4 [file] [log] [blame]
Wez627fd582019-06-25 04:30:381// 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 Gadania9c45682019-07-31 22:05:1412#include "content/public/common/was_activated_option.mojom.h"
Wez627fd582019-06-25 04:30:3813#include "ui/base/page_transition_types.h"
14
15namespace {
16
17void 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
45NavigationControllerImpl::NavigationControllerImpl(
46 content::WebContents* web_contents)
47 : web_contents_(web_contents), weak_factory_(this) {
48 Observe(web_contents_);
49}
50
51NavigationControllerImpl::~NavigationControllerImpl() = default;
52
53void NavigationControllerImpl::AddBinding(
54 fidl::InterfaceRequest<fuchsia::web::NavigationController> controller) {
55 controller_bindings_.AddBinding(this, std::move(controller));
56}
57
58void 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-Riberi4a5c47e2019-07-31 20:40:1365 // Simply unbind if no new listener was set.
66 if (!listener) {
Wez627fd582019-06-25 04:30:3867 navigation_listener_.Unbind();
Fabrice de Gans-Riberi4a5c47e2019-07-31 20:40:1368 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();
Wez627fd582019-06-25 04:30:3885 }
86}
87
88void 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
105void 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
127void 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 Gadania9c45682019-07-31 22:05:14162 params_converted.was_activated = content::mojom::WasActivatedOption::kYes;
Wez627fd582019-06-25 04:30:38163 } else {
Lucas Furukawa Gadania9c45682019-07-31 22:05:14164 params_converted.was_activated = content::mojom::WasActivatedOption::kNo;
Wez627fd582019-06-25 04:30:38165 }
166
167 web_contents_->GetController().LoadURLWithParams(params_converted);
168 result.set_response(fuchsia::web::NavigationController_LoadUrl_Response());
169 callback(std::move(result));
170}
171
172void NavigationControllerImpl::GoBack() {
173 if (web_contents_->GetController().CanGoBack())
174 web_contents_->GetController().GoBack();
175}
176
177void NavigationControllerImpl::GoForward() {
178 if (web_contents_->GetController().CanGoForward())
179 web_contents_->GetController().GoForward();
180}
181
182void NavigationControllerImpl::Stop() {
183 web_contents_->Stop();
184}
185
186void 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
199void 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
214void NavigationControllerImpl::TitleWasSet(content::NavigationEntry* entry) {
215 // The title was changed after the document was loaded.
216 OnNavigationEntryChanged();
217}
218
219void 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
226void 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
234void 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
243void 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}