123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- // Copyright 2019 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "fuchsia_web/webengine/browser/navigation_controller_impl.h"
- #include <fuchsia/mem/cpp/fidl.h>
- #include <lib/fpromise/result.h>
- #include "base/bits.h"
- #include "base/fuchsia/fuchsia_logging.h"
- #include "base/memory/page_size.h"
- #include "base/strings/strcat.h"
- #include "base/strings/string_piece.h"
- #include "base/strings/utf_string_conversions.h"
- #include "components/favicon/content/content_favicon_driver.h"
- #include "content/public/browser/favicon_status.h"
- #include "content/public/browser/navigation_entry.h"
- #include "content/public/browser/navigation_handle.h"
- #include "content/public/browser/web_contents.h"
- #include "fuchsia_web/common/string_util.h"
- #include "net/base/net_errors.h"
- #include "net/http/http_util.h"
- #include "third_party/blink/public/mojom/navigation/was_activated_option.mojom.h"
- #include "third_party/skia/include/core/SkBitmap.h"
- #include "ui/base/page_transition_types.h"
- #include "ui/gfx/image/image.h"
- namespace {
- // Converts a gfx::Image to a fuchsia::web::Favicon.
- fuchsia::web::Favicon GfxImageToFidlFavicon(gfx::Image gfx_image) {
- fuchsia::web::Favicon favicon;
- if (gfx_image.IsEmpty())
- return favicon;
- int height = gfx_image.AsBitmap().pixmap().height();
- int width = gfx_image.AsBitmap().pixmap().width();
- size_t stride = width * SkColorTypeBytesPerPixel(kRGBA_8888_SkColorType);
- // Create VMO.
- fuchsia::mem::Buffer buffer;
- buffer.size = stride * height;
- zx_status_t status = zx::vmo::create(buffer.size, 0, &buffer.vmo);
- ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
- // Map the VMO.
- uintptr_t addr;
- size_t mapped_size = base::bits::AlignUp(buffer.size, base::GetPageSize());
- zx_vm_option_t options = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
- status = zx::vmar::root_self()->map(options, /*vmar_offset=*/0, buffer.vmo,
- /*vmo_offset=*/0, mapped_size, &addr);
- ZX_CHECK(status == ZX_OK, status) << "zx_vmar_map";
- // Copy the data to the mapped VMO.
- gfx_image.AsBitmap().readPixels(
- SkImageInfo::Make(width, height, kRGBA_8888_SkColorType,
- kPremul_SkAlphaType),
- reinterpret_cast<void*>(addr), stride, 0, 0);
- // Unmap the VMO.
- status = zx::vmar::root_self()->unmap(addr, mapped_size);
- ZX_DCHECK(status == ZX_OK, status) << "zx_vmar_unmap";
- favicon.set_data(std::move(buffer));
- favicon.set_height(height);
- favicon.set_width(width);
- return favicon;
- }
- } // namespace
- namespace {
- // For each field that differs between |old_entry| and |new_entry|, the field
- // is set to its new value in |difference|. |new_entry| is assumed to have been
- // fully-populated with fields.
- void DiffNavigationEntries(const fuchsia::web::NavigationState& old_entry,
- const fuchsia::web::NavigationState& new_entry,
- fuchsia::web::NavigationState* difference) {
- DCHECK(difference);
- // |new_entry| should not be empty when the difference is between states
- // pre- and post-navigation. It is possible for non-navigation events (e.g.
- // Renderer-process teardown) to trigger notifications, in which case both
- // states may be empty (i.e. both come from the "initial" NavigationEntry).
- if (new_entry.IsEmpty() && old_entry.IsEmpty()) {
- return;
- }
- DCHECK(new_entry.has_title());
- if (!old_entry.has_title() || (new_entry.title() != old_entry.title())) {
- difference->set_title(new_entry.title());
- }
- DCHECK(new_entry.has_url());
- if (!old_entry.has_url() || (new_entry.url() != old_entry.url())) {
- difference->set_url(new_entry.url());
- }
- DCHECK(new_entry.has_page_type());
- if (!old_entry.has_page_type() ||
- (new_entry.page_type() != old_entry.page_type())) {
- difference->set_page_type(new_entry.page_type());
- }
- DCHECK(new_entry.has_can_go_back());
- if (!old_entry.has_can_go_back() ||
- old_entry.can_go_back() != new_entry.can_go_back()) {
- difference->set_can_go_back(new_entry.can_go_back());
- }
- DCHECK(new_entry.has_can_go_forward());
- if (!old_entry.has_can_go_forward() ||
- old_entry.can_go_forward() != new_entry.can_go_forward()) {
- difference->set_can_go_forward(new_entry.can_go_forward());
- }
- DCHECK(new_entry.has_is_main_document_loaded());
- if (!old_entry.has_is_main_document_loaded() ||
- old_entry.is_main_document_loaded() !=
- new_entry.is_main_document_loaded()) {
- difference->set_is_main_document_loaded(
- new_entry.is_main_document_loaded());
- }
- }
- } // namespace
- NavigationControllerImpl::NavigationControllerImpl(
- content::WebContents* web_contents)
- : web_contents_(web_contents), weak_factory_(this) {
- Observe(web_contents_);
- }
- NavigationControllerImpl::~NavigationControllerImpl() = default;
- void NavigationControllerImpl::AddBinding(
- fidl::InterfaceRequest<fuchsia::web::NavigationController> controller) {
- controller_bindings_.AddBinding(this, std::move(controller));
- }
- void NavigationControllerImpl::SetEventListener(
- fidl::InterfaceHandle<fuchsia::web::NavigationEventListener> listener,
- fuchsia::web::NavigationEventListenerFlags flags) {
- // Reset the event buffer state.
- waiting_for_navigation_event_ack_ = false;
- previous_navigation_state_ = {};
- pending_navigation_event_ = {};
- // Simply unbind if no new listener was set.
- if (!listener) {
- navigation_listener_.Unbind();
- return;
- }
- send_favicon_ =
- (flags & fuchsia::web::NavigationEventListenerFlags::FAVICON) ==
- fuchsia::web::NavigationEventListenerFlags::FAVICON;
- favicon::ContentFaviconDriver* favicon_driver =
- favicon::ContentFaviconDriver::FromWebContents(web_contents_);
- if (send_favicon_) {
- if (!favicon_driver) {
- favicon::ContentFaviconDriver::CreateForWebContents(
- web_contents_,
- /*favicon_service=*/nullptr);
- favicon_driver =
- favicon::ContentFaviconDriver::FromWebContents(web_contents_);
- }
- favicon_driver->AddObserver(this);
- } else {
- if (favicon_driver)
- favicon_driver->RemoveObserver(this);
- }
- navigation_listener_.Bind(std::move(listener));
- navigation_listener_.set_error_handler(
- [this](zx_status_t status) { SetEventListener(nullptr, {}); });
- // Send the current navigation state to the listener immediately.
- waiting_for_navigation_event_ack_ = true;
- navigation_listener_->OnNavigationStateChanged(
- GetVisibleNavigationState(), [this]() {
- waiting_for_navigation_event_ack_ = false;
- MaybeSendNavigationEvent();
- });
- }
- fuchsia::web::NavigationState
- NavigationControllerImpl::GetVisibleNavigationState() const {
- content::NavigationEntry* const entry =
- web_contents_->GetController().GetVisibleEntry();
- if (!entry || entry->IsInitialEntry())
- return fuchsia::web::NavigationState();
- fuchsia::web::NavigationState state;
- // Populate some fields directly from the NavigationEntry, if possible.
- state.set_title(base::UTF16ToUTF8(entry->GetTitleForDisplay()));
- state.set_url(entry->GetURL().spec());
- if (web_contents_->IsCrashed()) {
- // TODO(https:://crbug.com/1092506): Add an explicit crashed indicator to
- // NavigationState, separate from PageType::ERROR.
- state.set_page_type(fuchsia::web::PageType::ERROR);
- } else if (uncommitted_load_error_) {
- // If there was a loading error which prevented the navigation entry from
- // being committed, then report PageType::ERROR.
- state.set_page_type(fuchsia::web::PageType::ERROR);
- } else {
- switch (entry->GetPageType()) {
- case content::PageType::PAGE_TYPE_NORMAL:
- state.set_page_type(fuchsia::web::PageType::NORMAL);
- break;
- case content::PageType::PAGE_TYPE_ERROR:
- state.set_page_type(fuchsia::web::PageType::ERROR);
- break;
- }
- }
- state.set_is_main_document_loaded(is_main_document_loaded_);
- state.set_can_go_back(web_contents_->GetController().CanGoBack());
- state.set_can_go_forward(web_contents_->GetController().CanGoForward());
- return state;
- }
- void NavigationControllerImpl::OnNavigationEntryChanged() {
- fuchsia::web::NavigationState new_state = GetVisibleNavigationState();
- DiffNavigationEntries(previous_navigation_state_, new_state,
- &pending_navigation_event_);
- previous_navigation_state_ = std::move(new_state);
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::BindOnce(&NavigationControllerImpl::MaybeSendNavigationEvent,
- weak_factory_.GetWeakPtr()));
- }
- void NavigationControllerImpl::MaybeSendNavigationEvent() {
- if (!navigation_listener_)
- return;
- if (pending_navigation_event_.IsEmpty() ||
- waiting_for_navigation_event_ack_) {
- return;
- }
- waiting_for_navigation_event_ack_ = true;
- // Send the event to the observer and, upon acknowledgement, revisit this
- // function to send another update.
- navigation_listener_->OnNavigationStateChanged(
- std::move(pending_navigation_event_), [this]() {
- waiting_for_navigation_event_ack_ = false;
- MaybeSendNavigationEvent();
- });
- pending_navigation_event_ = {};
- }
- void NavigationControllerImpl::LoadUrl(std::string url,
- fuchsia::web::LoadUrlParams params,
- LoadUrlCallback callback) {
- GURL validated_url(url);
- if (!validated_url.is_valid()) {
- callback(
- fpromise::error(fuchsia::web::NavigationControllerError::INVALID_URL));
- return;
- }
- content::NavigationController::LoadURLParams params_converted(validated_url);
- if (params.has_headers()) {
- std::vector<std::string> extra_headers;
- extra_headers.reserve(params.headers().size());
- for (const auto& header : params.headers()) {
- base::StringPiece header_name = BytesAsString(header.name);
- base::StringPiece header_value = BytesAsString(header.value);
- if (!net::HttpUtil::IsValidHeaderName(header_name) ||
- !net::HttpUtil::IsValidHeaderValue(header_value)) {
- callback(fpromise::error(
- fuchsia::web::NavigationControllerError::INVALID_HEADER));
- return;
- }
- extra_headers.emplace_back(
- base::StrCat({header_name, ": ", header_value}));
- }
- params_converted.extra_headers = base::JoinString(extra_headers, "\n");
- }
- if (validated_url.scheme() == url::kDataScheme)
- params_converted.load_type = content::NavigationController::LOAD_TYPE_DATA;
- params_converted.transition_type = ui::PageTransitionFromInt(
- ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
- if (params.has_was_user_activated() && params.was_user_activated()) {
- params_converted.was_activated = blink::mojom::WasActivatedOption::kYes;
- } else {
- params_converted.was_activated = blink::mojom::WasActivatedOption::kNo;
- }
- web_contents_->GetController().LoadURLWithParams(params_converted);
- callback(fpromise::ok());
- }
- void NavigationControllerImpl::GoBack() {
- if (web_contents_->GetController().CanGoBack())
- web_contents_->GetController().GoBack();
- }
- void NavigationControllerImpl::GoForward() {
- if (web_contents_->GetController().CanGoForward())
- web_contents_->GetController().GoForward();
- }
- void NavigationControllerImpl::Stop() {
- web_contents_->Stop();
- }
- void NavigationControllerImpl::Reload(fuchsia::web::ReloadType type) {
- content::ReloadType internal_reload_type;
- switch (type) {
- case fuchsia::web::ReloadType::PARTIAL_CACHE:
- internal_reload_type = content::ReloadType::NORMAL;
- break;
- case fuchsia::web::ReloadType::NO_CACHE:
- internal_reload_type = content::ReloadType::BYPASSING_CACHE;
- break;
- }
- web_contents_->GetController().Reload(internal_reload_type, false);
- }
- void NavigationControllerImpl::GetVisibleEntry(
- fuchsia::web::NavigationController::GetVisibleEntryCallback callback) {
- callback(GetVisibleNavigationState());
- }
- void NavigationControllerImpl::TitleWasSet(content::NavigationEntry* entry) {
- // The title was changed after the document was loaded.
- OnNavigationEntryChanged();
- }
- void NavigationControllerImpl::PrimaryMainDocumentElementAvailable() {
- // The main document is loaded, but not necessarily all the subresources. Some
- // fields like "title" will change here.
- OnNavigationEntryChanged();
- }
- void NavigationControllerImpl::DidFinishLoad(
- content::RenderFrameHost* render_frame_host,
- const GURL& validated_url) {
- // The current document and its statically-declared subresources are loaded.
- // Don't process load completion on the current document if the WebContents
- // is already in the process of navigating to a different page.
- if (active_navigation_)
- return;
- // Only allow the primary main frame to transition this state.
- if (!render_frame_host->IsInPrimaryMainFrame())
- return;
- is_main_document_loaded_ = true;
- OnNavigationEntryChanged();
- }
- void NavigationControllerImpl::PrimaryMainFrameRenderProcessGone(
- base::TerminationStatus status) {
- // If the current RenderProcess terminates then trigger a NavigationState
- // change to let the caller know that something is wrong.
- LOG(WARNING) << "RenderProcess gone, TerminationStatus=" << status;
- OnNavigationEntryChanged();
- }
- void NavigationControllerImpl::DidStartNavigation(
- content::NavigationHandle* navigation_handle) {
- if (!navigation_handle->IsInPrimaryMainFrame() ||
- navigation_handle->IsSameDocument()) {
- return;
- }
- // If favicons are enabled then reset favicon in the pending navigation.
- if (send_favicon_)
- pending_navigation_event_.set_favicon({});
- uncommitted_load_error_ = false;
- active_navigation_ = navigation_handle;
- is_main_document_loaded_ = false;
- OnNavigationEntryChanged();
- }
- void NavigationControllerImpl::DidFinishNavigation(
- content::NavigationHandle* navigation_handle) {
- if (navigation_handle != active_navigation_)
- return;
- active_navigation_ = nullptr;
- uncommitted_load_error_ = !navigation_handle->HasCommitted() &&
- navigation_handle->GetNetErrorCode() != net::OK;
- OnNavigationEntryChanged();
- }
- void NavigationControllerImpl::OnFaviconUpdated(
- favicon::FaviconDriver* favicon_driver,
- NotificationIconType notification_icon_type,
- const GURL& icon_url,
- bool icon_url_changed,
- const gfx::Image& image) {
- // Currently FaviconDriverImpl loads only 16 DIP images, except on Android and
- // iOS.
- DCHECK_EQ(notification_icon_type, FaviconDriverObserver::NON_TOUCH_16_DIP);
- pending_navigation_event_.set_favicon(GfxImageToFidlFavicon(image));
- OnNavigationEntryChanged();
- }
- void DiffNavigationEntriesForTest( // IN-TEST
- const fuchsia::web::NavigationState& old_entry,
- const fuchsia::web::NavigationState& new_entry,
- fuchsia::web::NavigationState* difference) {
- DiffNavigationEntries(old_entry, new_entry, difference);
- }
|