navigation_controller_impl.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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. #include "fuchsia_web/webengine/browser/navigation_controller_impl.h"
  5. #include <fuchsia/mem/cpp/fidl.h>
  6. #include <lib/fpromise/result.h>
  7. #include "base/bits.h"
  8. #include "base/fuchsia/fuchsia_logging.h"
  9. #include "base/memory/page_size.h"
  10. #include "base/strings/strcat.h"
  11. #include "base/strings/string_piece.h"
  12. #include "base/strings/utf_string_conversions.h"
  13. #include "components/favicon/content/content_favicon_driver.h"
  14. #include "content/public/browser/favicon_status.h"
  15. #include "content/public/browser/navigation_entry.h"
  16. #include "content/public/browser/navigation_handle.h"
  17. #include "content/public/browser/web_contents.h"
  18. #include "fuchsia_web/common/string_util.h"
  19. #include "net/base/net_errors.h"
  20. #include "net/http/http_util.h"
  21. #include "third_party/blink/public/mojom/navigation/was_activated_option.mojom.h"
  22. #include "third_party/skia/include/core/SkBitmap.h"
  23. #include "ui/base/page_transition_types.h"
  24. #include "ui/gfx/image/image.h"
  25. namespace {
  26. // Converts a gfx::Image to a fuchsia::web::Favicon.
  27. fuchsia::web::Favicon GfxImageToFidlFavicon(gfx::Image gfx_image) {
  28. fuchsia::web::Favicon favicon;
  29. if (gfx_image.IsEmpty())
  30. return favicon;
  31. int height = gfx_image.AsBitmap().pixmap().height();
  32. int width = gfx_image.AsBitmap().pixmap().width();
  33. size_t stride = width * SkColorTypeBytesPerPixel(kRGBA_8888_SkColorType);
  34. // Create VMO.
  35. fuchsia::mem::Buffer buffer;
  36. buffer.size = stride * height;
  37. zx_status_t status = zx::vmo::create(buffer.size, 0, &buffer.vmo);
  38. ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
  39. // Map the VMO.
  40. uintptr_t addr;
  41. size_t mapped_size = base::bits::AlignUp(buffer.size, base::GetPageSize());
  42. zx_vm_option_t options = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
  43. status = zx::vmar::root_self()->map(options, /*vmar_offset=*/0, buffer.vmo,
  44. /*vmo_offset=*/0, mapped_size, &addr);
  45. ZX_CHECK(status == ZX_OK, status) << "zx_vmar_map";
  46. // Copy the data to the mapped VMO.
  47. gfx_image.AsBitmap().readPixels(
  48. SkImageInfo::Make(width, height, kRGBA_8888_SkColorType,
  49. kPremul_SkAlphaType),
  50. reinterpret_cast<void*>(addr), stride, 0, 0);
  51. // Unmap the VMO.
  52. status = zx::vmar::root_self()->unmap(addr, mapped_size);
  53. ZX_DCHECK(status == ZX_OK, status) << "zx_vmar_unmap";
  54. favicon.set_data(std::move(buffer));
  55. favicon.set_height(height);
  56. favicon.set_width(width);
  57. return favicon;
  58. }
  59. } // namespace
  60. namespace {
  61. // For each field that differs between |old_entry| and |new_entry|, the field
  62. // is set to its new value in |difference|. |new_entry| is assumed to have been
  63. // fully-populated with fields.
  64. void DiffNavigationEntries(const fuchsia::web::NavigationState& old_entry,
  65. const fuchsia::web::NavigationState& new_entry,
  66. fuchsia::web::NavigationState* difference) {
  67. DCHECK(difference);
  68. // |new_entry| should not be empty when the difference is between states
  69. // pre- and post-navigation. It is possible for non-navigation events (e.g.
  70. // Renderer-process teardown) to trigger notifications, in which case both
  71. // states may be empty (i.e. both come from the "initial" NavigationEntry).
  72. if (new_entry.IsEmpty() && old_entry.IsEmpty()) {
  73. return;
  74. }
  75. DCHECK(new_entry.has_title());
  76. if (!old_entry.has_title() || (new_entry.title() != old_entry.title())) {
  77. difference->set_title(new_entry.title());
  78. }
  79. DCHECK(new_entry.has_url());
  80. if (!old_entry.has_url() || (new_entry.url() != old_entry.url())) {
  81. difference->set_url(new_entry.url());
  82. }
  83. DCHECK(new_entry.has_page_type());
  84. if (!old_entry.has_page_type() ||
  85. (new_entry.page_type() != old_entry.page_type())) {
  86. difference->set_page_type(new_entry.page_type());
  87. }
  88. DCHECK(new_entry.has_can_go_back());
  89. if (!old_entry.has_can_go_back() ||
  90. old_entry.can_go_back() != new_entry.can_go_back()) {
  91. difference->set_can_go_back(new_entry.can_go_back());
  92. }
  93. DCHECK(new_entry.has_can_go_forward());
  94. if (!old_entry.has_can_go_forward() ||
  95. old_entry.can_go_forward() != new_entry.can_go_forward()) {
  96. difference->set_can_go_forward(new_entry.can_go_forward());
  97. }
  98. DCHECK(new_entry.has_is_main_document_loaded());
  99. if (!old_entry.has_is_main_document_loaded() ||
  100. old_entry.is_main_document_loaded() !=
  101. new_entry.is_main_document_loaded()) {
  102. difference->set_is_main_document_loaded(
  103. new_entry.is_main_document_loaded());
  104. }
  105. }
  106. } // namespace
  107. NavigationControllerImpl::NavigationControllerImpl(
  108. content::WebContents* web_contents)
  109. : web_contents_(web_contents), weak_factory_(this) {
  110. Observe(web_contents_);
  111. }
  112. NavigationControllerImpl::~NavigationControllerImpl() = default;
  113. void NavigationControllerImpl::AddBinding(
  114. fidl::InterfaceRequest<fuchsia::web::NavigationController> controller) {
  115. controller_bindings_.AddBinding(this, std::move(controller));
  116. }
  117. void NavigationControllerImpl::SetEventListener(
  118. fidl::InterfaceHandle<fuchsia::web::NavigationEventListener> listener,
  119. fuchsia::web::NavigationEventListenerFlags flags) {
  120. // Reset the event buffer state.
  121. waiting_for_navigation_event_ack_ = false;
  122. previous_navigation_state_ = {};
  123. pending_navigation_event_ = {};
  124. // Simply unbind if no new listener was set.
  125. if (!listener) {
  126. navigation_listener_.Unbind();
  127. return;
  128. }
  129. send_favicon_ =
  130. (flags & fuchsia::web::NavigationEventListenerFlags::FAVICON) ==
  131. fuchsia::web::NavigationEventListenerFlags::FAVICON;
  132. favicon::ContentFaviconDriver* favicon_driver =
  133. favicon::ContentFaviconDriver::FromWebContents(web_contents_);
  134. if (send_favicon_) {
  135. if (!favicon_driver) {
  136. favicon::ContentFaviconDriver::CreateForWebContents(
  137. web_contents_,
  138. /*favicon_service=*/nullptr);
  139. favicon_driver =
  140. favicon::ContentFaviconDriver::FromWebContents(web_contents_);
  141. }
  142. favicon_driver->AddObserver(this);
  143. } else {
  144. if (favicon_driver)
  145. favicon_driver->RemoveObserver(this);
  146. }
  147. navigation_listener_.Bind(std::move(listener));
  148. navigation_listener_.set_error_handler(
  149. [this](zx_status_t status) { SetEventListener(nullptr, {}); });
  150. // Send the current navigation state to the listener immediately.
  151. waiting_for_navigation_event_ack_ = true;
  152. navigation_listener_->OnNavigationStateChanged(
  153. GetVisibleNavigationState(), [this]() {
  154. waiting_for_navigation_event_ack_ = false;
  155. MaybeSendNavigationEvent();
  156. });
  157. }
  158. fuchsia::web::NavigationState
  159. NavigationControllerImpl::GetVisibleNavigationState() const {
  160. content::NavigationEntry* const entry =
  161. web_contents_->GetController().GetVisibleEntry();
  162. if (!entry || entry->IsInitialEntry())
  163. return fuchsia::web::NavigationState();
  164. fuchsia::web::NavigationState state;
  165. // Populate some fields directly from the NavigationEntry, if possible.
  166. state.set_title(base::UTF16ToUTF8(entry->GetTitleForDisplay()));
  167. state.set_url(entry->GetURL().spec());
  168. if (web_contents_->IsCrashed()) {
  169. // TODO(https:://crbug.com/1092506): Add an explicit crashed indicator to
  170. // NavigationState, separate from PageType::ERROR.
  171. state.set_page_type(fuchsia::web::PageType::ERROR);
  172. } else if (uncommitted_load_error_) {
  173. // If there was a loading error which prevented the navigation entry from
  174. // being committed, then report PageType::ERROR.
  175. state.set_page_type(fuchsia::web::PageType::ERROR);
  176. } else {
  177. switch (entry->GetPageType()) {
  178. case content::PageType::PAGE_TYPE_NORMAL:
  179. state.set_page_type(fuchsia::web::PageType::NORMAL);
  180. break;
  181. case content::PageType::PAGE_TYPE_ERROR:
  182. state.set_page_type(fuchsia::web::PageType::ERROR);
  183. break;
  184. }
  185. }
  186. state.set_is_main_document_loaded(is_main_document_loaded_);
  187. state.set_can_go_back(web_contents_->GetController().CanGoBack());
  188. state.set_can_go_forward(web_contents_->GetController().CanGoForward());
  189. return state;
  190. }
  191. void NavigationControllerImpl::OnNavigationEntryChanged() {
  192. fuchsia::web::NavigationState new_state = GetVisibleNavigationState();
  193. DiffNavigationEntries(previous_navigation_state_, new_state,
  194. &pending_navigation_event_);
  195. previous_navigation_state_ = std::move(new_state);
  196. base::ThreadTaskRunnerHandle::Get()->PostTask(
  197. FROM_HERE,
  198. base::BindOnce(&NavigationControllerImpl::MaybeSendNavigationEvent,
  199. weak_factory_.GetWeakPtr()));
  200. }
  201. void NavigationControllerImpl::MaybeSendNavigationEvent() {
  202. if (!navigation_listener_)
  203. return;
  204. if (pending_navigation_event_.IsEmpty() ||
  205. waiting_for_navigation_event_ack_) {
  206. return;
  207. }
  208. waiting_for_navigation_event_ack_ = true;
  209. // Send the event to the observer and, upon acknowledgement, revisit this
  210. // function to send another update.
  211. navigation_listener_->OnNavigationStateChanged(
  212. std::move(pending_navigation_event_), [this]() {
  213. waiting_for_navigation_event_ack_ = false;
  214. MaybeSendNavigationEvent();
  215. });
  216. pending_navigation_event_ = {};
  217. }
  218. void NavigationControllerImpl::LoadUrl(std::string url,
  219. fuchsia::web::LoadUrlParams params,
  220. LoadUrlCallback callback) {
  221. GURL validated_url(url);
  222. if (!validated_url.is_valid()) {
  223. callback(
  224. fpromise::error(fuchsia::web::NavigationControllerError::INVALID_URL));
  225. return;
  226. }
  227. content::NavigationController::LoadURLParams params_converted(validated_url);
  228. if (params.has_headers()) {
  229. std::vector<std::string> extra_headers;
  230. extra_headers.reserve(params.headers().size());
  231. for (const auto& header : params.headers()) {
  232. base::StringPiece header_name = BytesAsString(header.name);
  233. base::StringPiece header_value = BytesAsString(header.value);
  234. if (!net::HttpUtil::IsValidHeaderName(header_name) ||
  235. !net::HttpUtil::IsValidHeaderValue(header_value)) {
  236. callback(fpromise::error(
  237. fuchsia::web::NavigationControllerError::INVALID_HEADER));
  238. return;
  239. }
  240. extra_headers.emplace_back(
  241. base::StrCat({header_name, ": ", header_value}));
  242. }
  243. params_converted.extra_headers = base::JoinString(extra_headers, "\n");
  244. }
  245. if (validated_url.scheme() == url::kDataScheme)
  246. params_converted.load_type = content::NavigationController::LOAD_TYPE_DATA;
  247. params_converted.transition_type = ui::PageTransitionFromInt(
  248. ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
  249. if (params.has_was_user_activated() && params.was_user_activated()) {
  250. params_converted.was_activated = blink::mojom::WasActivatedOption::kYes;
  251. } else {
  252. params_converted.was_activated = blink::mojom::WasActivatedOption::kNo;
  253. }
  254. web_contents_->GetController().LoadURLWithParams(params_converted);
  255. callback(fpromise::ok());
  256. }
  257. void NavigationControllerImpl::GoBack() {
  258. if (web_contents_->GetController().CanGoBack())
  259. web_contents_->GetController().GoBack();
  260. }
  261. void NavigationControllerImpl::GoForward() {
  262. if (web_contents_->GetController().CanGoForward())
  263. web_contents_->GetController().GoForward();
  264. }
  265. void NavigationControllerImpl::Stop() {
  266. web_contents_->Stop();
  267. }
  268. void NavigationControllerImpl::Reload(fuchsia::web::ReloadType type) {
  269. content::ReloadType internal_reload_type;
  270. switch (type) {
  271. case fuchsia::web::ReloadType::PARTIAL_CACHE:
  272. internal_reload_type = content::ReloadType::NORMAL;
  273. break;
  274. case fuchsia::web::ReloadType::NO_CACHE:
  275. internal_reload_type = content::ReloadType::BYPASSING_CACHE;
  276. break;
  277. }
  278. web_contents_->GetController().Reload(internal_reload_type, false);
  279. }
  280. void NavigationControllerImpl::GetVisibleEntry(
  281. fuchsia::web::NavigationController::GetVisibleEntryCallback callback) {
  282. callback(GetVisibleNavigationState());
  283. }
  284. void NavigationControllerImpl::TitleWasSet(content::NavigationEntry* entry) {
  285. // The title was changed after the document was loaded.
  286. OnNavigationEntryChanged();
  287. }
  288. void NavigationControllerImpl::PrimaryMainDocumentElementAvailable() {
  289. // The main document is loaded, but not necessarily all the subresources. Some
  290. // fields like "title" will change here.
  291. OnNavigationEntryChanged();
  292. }
  293. void NavigationControllerImpl::DidFinishLoad(
  294. content::RenderFrameHost* render_frame_host,
  295. const GURL& validated_url) {
  296. // The current document and its statically-declared subresources are loaded.
  297. // Don't process load completion on the current document if the WebContents
  298. // is already in the process of navigating to a different page.
  299. if (active_navigation_)
  300. return;
  301. // Only allow the primary main frame to transition this state.
  302. if (!render_frame_host->IsInPrimaryMainFrame())
  303. return;
  304. is_main_document_loaded_ = true;
  305. OnNavigationEntryChanged();
  306. }
  307. void NavigationControllerImpl::PrimaryMainFrameRenderProcessGone(
  308. base::TerminationStatus status) {
  309. // If the current RenderProcess terminates then trigger a NavigationState
  310. // change to let the caller know that something is wrong.
  311. LOG(WARNING) << "RenderProcess gone, TerminationStatus=" << status;
  312. OnNavigationEntryChanged();
  313. }
  314. void NavigationControllerImpl::DidStartNavigation(
  315. content::NavigationHandle* navigation_handle) {
  316. if (!navigation_handle->IsInPrimaryMainFrame() ||
  317. navigation_handle->IsSameDocument()) {
  318. return;
  319. }
  320. // If favicons are enabled then reset favicon in the pending navigation.
  321. if (send_favicon_)
  322. pending_navigation_event_.set_favicon({});
  323. uncommitted_load_error_ = false;
  324. active_navigation_ = navigation_handle;
  325. is_main_document_loaded_ = false;
  326. OnNavigationEntryChanged();
  327. }
  328. void NavigationControllerImpl::DidFinishNavigation(
  329. content::NavigationHandle* navigation_handle) {
  330. if (navigation_handle != active_navigation_)
  331. return;
  332. active_navigation_ = nullptr;
  333. uncommitted_load_error_ = !navigation_handle->HasCommitted() &&
  334. navigation_handle->GetNetErrorCode() != net::OK;
  335. OnNavigationEntryChanged();
  336. }
  337. void NavigationControllerImpl::OnFaviconUpdated(
  338. favicon::FaviconDriver* favicon_driver,
  339. NotificationIconType notification_icon_type,
  340. const GURL& icon_url,
  341. bool icon_url_changed,
  342. const gfx::Image& image) {
  343. // Currently FaviconDriverImpl loads only 16 DIP images, except on Android and
  344. // iOS.
  345. DCHECK_EQ(notification_icon_type, FaviconDriverObserver::NON_TOUCH_16_DIP);
  346. pending_navigation_event_.set_favicon(GfxImageToFidlFavicon(image));
  347. OnNavigationEntryChanged();
  348. }
  349. void DiffNavigationEntriesForTest( // IN-TEST
  350. const fuchsia::web::NavigationState& old_entry,
  351. const fuchsia::web::NavigationState& new_entry,
  352. fuchsia::web::NavigationState* difference) {
  353. DiffNavigationEntries(old_entry, new_entry, difference);
  354. }