blob: f7c21a9a97a1e3f6d9b3a73a852c4e04183871e4 [file] [log] [blame]
// Copyright 2018 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 "webrunner/browser/frame_impl.h"
#include <string>
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host_platform.h"
#include "ui/platform_window/platform_window_init_properties.h"
#include "ui/wm/core/base_focus_rules.h"
#include "url/gurl.h"
#include "webrunner/browser/context_impl.h"
namespace webrunner {
namespace {
// Layout manager that allows only one child window and stretches it to fill the
// parent.
class LayoutManagerImpl : public aura::LayoutManager {
public:
LayoutManagerImpl() = default;
~LayoutManagerImpl() override = default;
// aura::LayoutManager.
void OnWindowResized() override {
// Resize the child to match the size of the parent
if (child_) {
SetChildBoundsDirect(child_,
gfx::Rect(child_->parent()->bounds().size()));
}
}
void OnWindowAddedToLayout(aura::Window* child) override {
DCHECK(!child_);
child_ = child;
SetChildBoundsDirect(child_, gfx::Rect(child_->parent()->bounds().size()));
}
void OnWillRemoveWindowFromLayout(aura::Window* child) override {
DCHECK_EQ(child, child_);
child_ = nullptr;
}
void OnWindowRemovedFromLayout(aura::Window* child) override {}
void OnChildWindowVisibilityChanged(aura::Window* child,
bool visible) override {}
void SetChildBounds(aura::Window* child,
const gfx::Rect& requested_bounds) override {}
private:
aura::Window* child_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(LayoutManagerImpl);
};
chromium::web::NavigationEntry ConvertContentNavigationEntry(
content::NavigationEntry* entry) {
DCHECK(entry);
chromium::web::NavigationEntry converted;
converted.title = base::UTF16ToUTF8(entry->GetTitleForDisplay());
converted.url = entry->GetURL().spec();
converted.is_error =
entry->GetPageType() == content::PageType::PAGE_TYPE_ERROR;
return converted;
}
// Computes the observable differences between |entry_1| and |entry_2|.
// Returns true if they are different, |false| if their observable fields are
// identical.
bool ComputeNavigationEvent(const chromium::web::NavigationEntry& old_entry,
const chromium::web::NavigationEntry& new_entry,
chromium::web::NavigationEvent* computed_event) {
DCHECK(computed_event);
bool is_changed = false;
if (old_entry.title != new_entry.title) {
is_changed = true;
computed_event->title = new_entry.title;
}
if (old_entry.url != new_entry.url) {
is_changed = true;
computed_event->url = new_entry.url;
}
computed_event->is_error = new_entry.is_error;
if (old_entry.is_error != new_entry.is_error)
is_changed = true;
return is_changed;
}
class FrameFocusRules : public wm::BaseFocusRules {
public:
FrameFocusRules() = default;
~FrameFocusRules() override = default;
// wm::BaseFocusRules implementation.
bool SupportsChildActivation(aura::Window*) const override;
private:
DISALLOW_COPY_AND_ASSIGN(FrameFocusRules);
};
bool FrameFocusRules::SupportsChildActivation(aura::Window*) const {
// TODO(crbug.com/878439): Return a result based on window properties such as
// visibility.
return true;
}
} // namespace
FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
ContextImpl* context,
fidl::InterfaceRequest<chromium::web::Frame> frame_request)
: web_contents_(std::move(web_contents)),
focus_controller_(
std::make_unique<wm::FocusController>(new FrameFocusRules)),
context_(context),
binding_(this, std::move(frame_request)) {
web_contents_->SetDelegate(this);
binding_.set_error_handler([this]() { context_->DestroyFrame(this); });
}
FrameImpl::~FrameImpl() {
if (window_tree_host_) {
aura::client::SetFocusClient(root_window(), nullptr);
wm::SetActivationClient(root_window(), nullptr);
web_contents_->ClosePage();
window_tree_host_->Hide();
window_tree_host_->compositor()->SetVisible(false);
// Allows posted focus events to process before the FocusController
// is torn down.
content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
focus_controller_.release());
}
}
zx::unowned_channel FrameImpl::GetBindingChannelForTest() const {
return zx::unowned_channel(binding_.channel());
}
bool FrameImpl::ShouldCreateWebContents(
content::WebContents* web_contents,
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
int32_t route_id,
int32_t main_frame_route_id,
int32_t main_frame_widget_route_id,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) {
DCHECK_EQ(web_contents, web_contents_.get());
// Prevent any child WebContents (popup windows, tabs, etc.) from spawning.
// TODO(crbug.com/888131): Implement support for popup windows.
NOTIMPLEMENTED() << "Ignored popup window request for URL: "
<< target_url.spec();
return false;
}
void FrameImpl::CreateView(
fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) {
ui::PlatformWindowInitProperties properties;
properties.view_owner_request = std::move(view_owner);
window_tree_host_ =
std::make_unique<aura::WindowTreeHostPlatform>(std::move(properties));
window_tree_host_->InitHost();
aura::client::SetFocusClient(root_window(), focus_controller_.get());
wm::SetActivationClient(root_window(), focus_controller_.get());
// Add hooks which automatically set the focus state when input events are
// received.
root_window()->AddPreTargetHandler(focus_controller_.get());
// Track child windows for enforcement of window management policies and
// propagate window manager events to them (e.g. window resizing).
root_window()->SetLayoutManager(new LayoutManagerImpl());
root_window()->AddChild(web_contents_->GetNativeView());
web_contents_->GetNativeView()->Show();
window_tree_host_->Show();
}
void FrameImpl::GetNavigationController(
fidl::InterfaceRequest<chromium::web::NavigationController> controller) {
controller_bindings_.AddBinding(this, std::move(controller));
}
void FrameImpl::LoadUrl(fidl::StringPtr url,
std::unique_ptr<chromium::web::LoadUrlParams> params) {
GURL validated_url(*url);
if (!validated_url.is_valid()) {
DLOG(WARNING) << "Invalid URL: " << *url;
return;
}
content::NavigationController::LoadURLParams params_converted(validated_url);
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);
web_contents_->GetController().LoadURLWithParams(params_converted);
}
void FrameImpl::GoBack() {
if (web_contents_->GetController().CanGoBack())
web_contents_->GetController().GoBack();
}
void FrameImpl::GoForward() {
if (web_contents_->GetController().CanGoForward())
web_contents_->GetController().GoForward();
}
void FrameImpl::Stop() {
web_contents_->Stop();
}
void FrameImpl::Reload(chromium::web::ReloadType type) {
content::ReloadType internal_reload_type;
switch (type) {
case chromium::web::ReloadType::PARTIAL_CACHE:
internal_reload_type = content::ReloadType::NORMAL;
break;
case chromium::web::ReloadType::NO_CACHE:
internal_reload_type = content::ReloadType::BYPASSING_CACHE;
break;
}
web_contents_->GetController().Reload(internal_reload_type, false);
}
void FrameImpl::GetVisibleEntry(GetVisibleEntryCallback callback) {
content::NavigationEntry* entry =
web_contents_->GetController().GetVisibleEntry();
if (!entry) {
callback(nullptr);
return;
}
chromium::web::NavigationEntry output = ConvertContentNavigationEntry(entry);
callback(std::make_unique<chromium::web::NavigationEntry>(std::move(output)));
}
void FrameImpl::SetNavigationEventObserver(
fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer) {
// Reset the event buffer state.
waiting_for_navigation_event_ack_ = false;
cached_navigation_state_ = {};
pending_navigation_event_ = {};
pending_navigation_event_is_dirty_ = false;
if (observer) {
navigation_observer_.Bind(std::move(observer));
navigation_observer_.set_error_handler([this]() {
// Stop observing on Observer connection loss.
SetNavigationEventObserver(nullptr);
});
Observe(web_contents_.get());
} else {
navigation_observer_.Unbind();
Observe(nullptr); // Stop receiving WebContentsObserver events.
}
}
void FrameImpl::DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) {
if (web_contents_->GetMainFrame() != render_frame_host) {
return;
}
chromium::web::NavigationEntry current_navigation_state =
ConvertContentNavigationEntry(
web_contents_->GetController().GetVisibleEntry());
pending_navigation_event_is_dirty_ |=
ComputeNavigationEvent(cached_navigation_state_, current_navigation_state,
&pending_navigation_event_);
cached_navigation_state_ = std::move(current_navigation_state);
if (pending_navigation_event_is_dirty_ &&
!waiting_for_navigation_event_ack_) {
MaybeSendNavigationEvent();
}
}
void FrameImpl::MaybeSendNavigationEvent() {
if (pending_navigation_event_is_dirty_) {
pending_navigation_event_is_dirty_ = false;
waiting_for_navigation_event_ack_ = true;
// Send the event to the observer and, upon acknowledgement, revisit this
// function to send another update.
navigation_observer_->OnNavigationStateChanged(
std::move(pending_navigation_event_),
[this]() { MaybeSendNavigationEvent(); });
return;
} else {
// No more changes to report.
waiting_for_navigation_event_ack_ = false;
}
}
} // namespace webrunner