blob: 47e12263735ad720c9ccd5597736623fb9ff376b [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 "garnet/bin/ui/view_manager/view_stub.h"
#include <utility>
#include "garnet/bin/ui/view_manager/view_registry.h"
#include "garnet/bin/ui/view_manager/view_state.h"
#include "garnet/bin/ui/view_manager/view_tree_state.h"
#include "lib/fxl/logging.h"
namespace view_manager {
class PendingViewTransferState {
public:
PendingViewTransferState(std::unique_ptr<ViewStub> view_stub,
zx::eventpair transferred_view_token)
: view_stub_(std::move(view_stub)),
transferred_view_token_(std::move(transferred_view_token)) {}
~PendingViewTransferState() {}
// A reference to keep the |ViewStub| alive until |OnViewResolved| is called.
std::unique_ptr<ViewStub> view_stub_;
// The token paired with the ViewHolder we want to transfer ownership to.
zx::eventpair transferred_view_token_;
};
ViewStub::ViewStub(ViewRegistry* registry, ViewLinker::ExportLink view_link,
zx::eventpair host_import_token)
: registry_(registry),
view_link_(std::move(view_link)),
host_import_token_(std::move(host_import_token)),
weak_factory_(this) {
FXL_DCHECK(registry_);
FXL_DCHECK(view_link_.valid());
FXL_DCHECK(host_import_token_);
}
ViewStub::~ViewStub() {
// Ensure that everything was properly released before this object was
// destroyed. The |ViewRegistry| is responsible for maintaining the
// invariant that all |ViewState| objects are owned so by the time we
// get here, the view should have found a new owner or been unregistered.
FXL_DCHECK(is_unavailable());
}
ViewContainerState* ViewStub::container() const {
return parent_ ? static_cast<ViewContainerState*>(parent_) : tree_;
}
void ViewStub::AttachView(ViewState* state) {
FXL_DCHECK(state);
FXL_DCHECK(!state->view_stub());
FXL_DCHECK(is_pending());
state_ = state;
state_->set_view_stub(this);
SetTreeForChildrenOfView(state_, tree_);
}
void ViewStub::SetProperties(
::fuchsia::ui::viewsv1::ViewPropertiesPtr properties,
scenic::Session* session) {
FXL_DCHECK(!is_unavailable());
properties_ = std::move(properties);
// TODO(SCN-1026): Remove this.
if (properties_ && properties_->custom_focus_behavior && host_node_) {
fuchsia::ui::gfx::SetImportFocusCmd import_focus;
import_focus.id = host_node_->id();
import_focus.focusable = properties_->custom_focus_behavior->allow_focus;
fuchsia::ui::gfx::Command cmd;
cmd.set_set_import_focus(std::move(import_focus));
session->Enqueue(std::move(cmd));
}
}
ViewState* ViewStub::ReleaseView() {
if (unavailable_)
return nullptr;
ViewState* state = state_;
if (state) {
FXL_DCHECK(state->view_stub() == this);
state->set_view_stub(nullptr);
state_ = nullptr;
SetTreeForChildrenOfView(state, nullptr);
}
properties_.reset();
unavailable_ = true;
return state;
}
void ViewStub::SetContainer(ViewContainerState* container, uint32_t key) {
FXL_DCHECK(container);
FXL_DCHECK(!tree_ && !parent_);
key_ = key;
parent_ = container->AsViewState();
if (parent_) {
if (parent_->view_stub())
SetTreeRecursively(parent_->view_stub()->tree());
} else {
ViewTreeState* tree = container->AsViewTreeState();
FXL_DCHECK(tree);
SetTreeRecursively(tree);
}
// We cannot call this in the constructor, because this might resolve
// immediately. The resolution callback assumes that |container| has been
// set.
// TODO(SCN-972): Remove the need for this by making callbacks fire async.
view_link_.Initialize(
this,
[this](ViewState* state) {
FXL_VLOG(1) << "ViewStub connected: " << this;
OnViewResolved(state, true);
},
[this] {
FXL_VLOG(1) << "ViewStub disconnected: " << this;
OnViewResolved(nullptr, false);
});
}
void ViewStub::Unlink() {
parent_ = nullptr;
key_ = 0;
SetTreeRecursively(nullptr);
}
void ViewStub::SetTreeRecursively(ViewTreeState* tree) {
if (tree_ == tree)
return;
tree_ = tree;
if (state_)
SetTreeForChildrenOfView(state_, tree);
}
void ViewStub::SetTreeForChildrenOfView(ViewState* view, ViewTreeState* tree) {
for (const auto& pair : view->children()) {
pair.second->SetTreeRecursively(tree);
}
}
void ViewStub::OnViewResolved(ViewState* view_state, bool success) {
if (success && transfer_view_when_resolved()) {
// While we were waiting for linking, the view was transferred to a new
// ViewOwner. Now that we are linked, transfer the ownership
// correctly internally.
FXL_DCHECK(!container()); // Make sure we're removed from the view tree
FXL_DCHECK(pending_view_transfer_->view_stub_ != nullptr);
FXL_DCHECK(pending_view_transfer_->transferred_view_token_);
registry_->TransferView(
view_state, std::move(pending_view_transfer_->transferred_view_token_));
// We don't have any |view_state| resolved to us now, but |ReleaseView| will
// still mark us as unavailable and clear properties
ReleaseView();
// |pending_view_transfer_| holds a reference to ourselves. Don't hold that
// reference anymore, which should release us immediately.
pending_view_transfer_.reset();
} else {
// 1. We got the linking callback as expected (in which case view_state is
// non-null and success is true).
// 2. Or, the ViewOwner was closed before linking (in which case view_state
// is null and success if false).
registry_->OnViewResolved(this, view_state);
}
}
void ViewStub::TransferViewWhenResolved(std::unique_ptr<ViewStub> view_stub,
zx::eventpair transferred_view_token) {
FXL_DCHECK(!container()); // Make sure we've been removed from the view tree
FXL_DCHECK(!pending_view_transfer_);
// When |OnViewResolved| gets called, we'll just transfer ownership
// of the view instead of calling |ViewRegistry.OnViewResolved|.
// Save the necessary state in |pending_view_transfer_|
pending_view_transfer_.reset(new PendingViewTransferState(
std::move(view_stub), std::move(transferred_view_token)));
}
void ViewStub::ReleaseHost() {
host_import_token_.reset();
host_node_.reset();
}
void ViewStub::ImportHostNode(scenic::Session* session) {
FXL_DCHECK(host_import_token_);
FXL_DCHECK(!host_node_);
host_node_.reset(new scenic::ImportNode(session));
host_node_->Bind(std::move(host_import_token_));
// TODO(SCN-1026): Remove this.
if (properties_ && properties_->custom_focus_behavior) {
fuchsia::ui::gfx::SetImportFocusCmd import_focus;
import_focus.id = host_node_->id();
import_focus.focusable = properties_->custom_focus_behavior->allow_focus;
fuchsia::ui::gfx::Command cmd;
cmd.set_set_import_focus(std::move(import_focus));
session->Enqueue(std::move(cmd));
}
}
} // namespace view_manager