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