// Copyright 2015 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 "mandoline/tab/frame.h" #include #include "base/auto_reset.h" #include "base/bind.h" #include "base/callback.h" #include "base/stl_util.h" #include "components/view_manager/public/cpp/view.h" #include "components/view_manager/public/cpp/view_property.h" #include "mandoline/tab/frame_tree.h" #include "mandoline/tab/frame_tree_delegate.h" #include "mandoline/tab/frame_user_data.h" using mojo::View; DECLARE_VIEW_PROPERTY_TYPE(mandoline::Frame*); namespace mandoline { // Used to find the Frame associated with a View. DEFINE_LOCAL_VIEW_PROPERTY_KEY(Frame*, kFrame, nullptr); namespace { const uint32_t kNoParentId = 0u; FrameDataPtr FrameToFrameData(const Frame* frame) { FrameDataPtr frame_data(FrameData::New()); frame_data->frame_id = frame->id(); frame_data->parent_id = frame->parent() ? frame->parent()->id() : kNoParentId; frame_data->client_properties = mojo::Map>::From( frame->client_properties()); return frame_data.Pass(); } } // namespace Frame::Frame(FrameTree* tree, View* view, uint32_t id, ViewOwnership view_ownership, FrameTreeClient* frame_tree_client, scoped_ptr user_data, const ClientPropertyMap& client_properties) : tree_(tree), view_(nullptr), id_(id), parent_(nullptr), view_ownership_(view_ownership), user_data_(user_data.Pass()), frame_tree_client_(frame_tree_client), loading_(false), progress_(0.f), client_properties_(client_properties), frame_tree_server_binding_(this), weak_factory_(this) { if (view) SetView(view); } Frame::~Frame() { if (view_) view_->RemoveObserver(this); while (!children_.empty()) delete children_[0]; if (parent_) parent_->Remove(this); if (view_) { view_->ClearLocalProperty(kFrame); if (view_ownership_ == ViewOwnership::OWNS_VIEW) view_->Destroy(); } } void Frame::Init(Frame* parent) { { // Set the FrameTreeClient to null so that we don't notify the client of the // add before OnConnect(). base::AutoReset frame_tree_client_resetter( &frame_tree_client_, nullptr); if (parent) parent->Add(this); } InitClient(); } // static Frame* Frame::FindFirstFrameAncestor(View* view) { while (view && !view->GetLocalProperty(kFrame)) view = view->parent(); return view ? view->GetLocalProperty(kFrame) : nullptr; } const Frame* Frame::FindFrame(uint32_t id) const { if (id == id_) return this; for (const Frame* child : children_) { const Frame* match = child->FindFrame(id); if (match) return match; } return nullptr; } bool Frame::HasAncestor(const Frame* frame) const { const Frame* current = this; while (current && current != frame) current = current->parent_; return current == frame; } bool Frame::IsLoading() const { if (loading_) return true; for (const Frame* child : children_) { if (child->IsLoading()) return true; } return false; } double Frame::GatherProgress(int* frame_count) const { ++(*frame_count); double progress = progress_; for (const Frame* child : children_) progress += child->GatherProgress(frame_count); return progress_; } void Frame::InitClient() { std::vector frames; tree_->root()->BuildFrameTree(&frames); mojo::Array array(frames.size()); for (size_t i = 0; i < frames.size(); ++i) array[i] = FrameToFrameData(frames[i]).Pass(); // TODO(sky): error handling. FrameTreeServerPtr frame_tree_server_ptr; frame_tree_server_binding_.Bind(GetProxy(&frame_tree_server_ptr).Pass()); if (frame_tree_client_) { frame_tree_client_->OnConnect(frame_tree_server_ptr.Pass(), tree_->change_id(), array.Pass()); tree_->delegate_->DidStartNavigation(this); } } void Frame::OnWillNavigateAck(FrameTreeClient* frame_tree_client, scoped_ptr user_data, mojo::ViewManagerClientPtr view_manager_client) { while (!children_.empty()) delete children_[0]; user_data_ = user_data.Pass(); frame_tree_client_ = frame_tree_client; frame_tree_server_binding_.Close(); loading_ = false; progress_ = 0.f; view_->Embed(view_manager_client.Pass()); InitClient(); } void Frame::SetView(mojo::View* view) { DCHECK(!view_); DCHECK_EQ(id_, view->id()); view_ = view; view_->SetLocalProperty(kFrame, this); view_->AddObserver(this); if (pending_navigate_.get()) StartNavigate(pending_navigate_.Pass()); } Frame* Frame::GetAncestorWithFrameTreeClient() { Frame* frame = this; while (frame && !frame->frame_tree_client_) frame = frame->parent_; return frame; } void Frame::BuildFrameTree(std::vector* frames) const { frames->push_back(this); for (const Frame* frame : children_) frame->BuildFrameTree(frames); } void Frame::Add(Frame* node) { DCHECK(!node->parent_); node->parent_ = this; children_.push_back(node); tree_->root()->NotifyAdded(this, node, tree_->AdvanceChangeID()); } void Frame::Remove(Frame* node) { DCHECK_EQ(node->parent_, this); auto iter = std::find(children_.begin(), children_.end(), node); DCHECK(iter != children_.end()); node->parent_ = nullptr; children_.erase(iter); tree_->root()->NotifyRemoved(this, node, tree_->AdvanceChangeID()); } void Frame::StartNavigate(mojo::URLRequestPtr request) { pending_navigate_.reset(); // We need a View to navigate. When we get the View we'll complete the // navigation. // TODO(sky): consider state and what is not legal while waiting. if (!view_) { pending_navigate_ = request.Pass(); return; } FrameTreeClient* frame_tree_client = nullptr; scoped_ptr user_data; mojo::ViewManagerClientPtr view_manager_client; if (!tree_->delegate_->CanNavigateFrame(this, request.Pass(), &frame_tree_client, &user_data, &view_manager_client)) { return; } // TODO(sky): consider what state this should correspond to. Should we // disallow certain operations until we get the ack? Frame* ancestor_with_frame_tree_client = GetAncestorWithFrameTreeClient(); DCHECK(ancestor_with_frame_tree_client); ancestor_with_frame_tree_client->frame_tree_client_->OnWillNavigate( id_, base::Bind(&Frame::OnWillNavigateAck, weak_factory_.GetWeakPtr(), frame_tree_client, base::Passed(&user_data), base::Passed(&view_manager_client))); } void Frame::LoadingStartedImpl() { DCHECK(!loading_); loading_ = true; progress_ = 0.f; tree_->LoadingStateChanged(); } void Frame::LoadingStoppedImpl() { DCHECK(loading_); loading_ = false; tree_->LoadingStateChanged(); } void Frame::ProgressChangedImpl(double progress) { DCHECK(loading_); progress_ = progress; tree_->ProgressChanged(); } void Frame::SetClientPropertyImpl(const mojo::String& name, mojo::Array value) { auto iter = client_properties_.find(name); const bool already_in_map = (iter != client_properties_.end()); if (value.is_null()) { if (!already_in_map) return; client_properties_.erase(iter); } else { std::vector as_vector(value.To>()); if (already_in_map && iter->second == as_vector) return; client_properties_[name] = as_vector; } tree_->ClientPropertyChanged(this, name, value); } Frame* Frame::FindTargetFrame(uint32_t frame_id) { if (frame_id == id_) return this; // Common case. // TODO(sky): I need a way to sanity check frame_id here, but the connection // id isn't known to us. Frame* frame = FindFrame(frame_id); if (frame->frame_tree_client_) { // The frame has it's own client/server pair. It should make requests using // the server it has rather than an ancestor. DVLOG(1) << "ignore request for a frame that has its own client."; return nullptr; } return frame; } void Frame::NotifyAdded(const Frame* source, const Frame* added_node, uint32_t change_id) { if (frame_tree_client_) frame_tree_client_->OnFrameAdded(change_id, FrameToFrameData(added_node)); for (Frame* child : children_) child->NotifyAdded(source, added_node, change_id); } void Frame::NotifyRemoved(const Frame* source, const Frame* removed_node, uint32_t change_id) { if (frame_tree_client_) frame_tree_client_->OnFrameRemoved(change_id, removed_node->id()); for (Frame* child : children_) child->NotifyRemoved(source, removed_node, change_id); } void Frame::NotifyClientPropertyChanged(const Frame* source, const mojo::String& name, const mojo::Array& value) { if (this != source && frame_tree_client_) frame_tree_client_->OnFrameClientPropertyChanged(source->id(), name, value.Clone()); for (Frame* child : children_) child->NotifyClientPropertyChanged(source, name, value); } void Frame::OnTreeChanged(const TreeChangeParams& params) { if (params.new_parent && this == tree_->root()) { Frame* child_frame = FindFrame(params.target->id()); if (child_frame && !child_frame->view_) child_frame->SetView(params.target); } } void Frame::OnViewDestroying(mojo::View* view) { if (parent_) parent_->Remove(this); // Reset |view_ownership_| so we don't attempt to delete |view_| in the // destructor. view_ownership_ = ViewOwnership::DOESNT_OWN_VIEW; // TODO(sky): Change browser to create a child for each FrameTree. if (tree_->root() == this) { view_->RemoveObserver(this); view_ = nullptr; return; } delete this; } void Frame::PostMessageEventToFrame(uint32_t source_frame_id, uint32_t target_frame_id, HTMLMessageEventPtr event) { Frame* source = tree_->root()->FindFrame(source_frame_id); Frame* target = tree_->root()->FindFrame(target_frame_id); if (!target || !source || source == target || !tree_->delegate_ || !tree_->delegate_->CanPostMessageEventToFrame(source, target, event.get())) return; DCHECK(target->GetAncestorWithFrameTreeClient()); target->GetAncestorWithFrameTreeClient() ->frame_tree_client_->OnPostMessageEvent(source_frame_id, target_frame_id, event.Pass()); } void Frame::LoadingStarted(uint32_t frame_id) { Frame* target_frame = FindTargetFrame(frame_id); if (target_frame) target_frame->LoadingStartedImpl(); } void Frame::LoadingStopped(uint32_t frame_id) { Frame* target_frame = FindTargetFrame(frame_id); if (target_frame) target_frame->LoadingStoppedImpl(); } void Frame::ProgressChanged(uint32_t frame_id, double progress) { Frame* target_frame = FindTargetFrame(frame_id); if (target_frame) target_frame->ProgressChangedImpl(progress); } void Frame::SetClientProperty(uint32_t frame_id, const mojo::String& name, mojo::Array value) { Frame* target_frame = FindTargetFrame(frame_id); if (target_frame) target_frame->SetClientPropertyImpl(name, value.Pass()); } void Frame::OnCreatedFrame( uint32_t parent_id, uint32_t frame_id, mojo::Map> client_properties) { // TODO(sky): I need a way to verify the id. Unfortunately the code here // doesn't know the connection id of the embedder, so it's not possible to // do it. if (FindFrame(frame_id)) { // TODO(sky): kill connection here? DVLOG(1) << "OnCreatedLocalFrame supplied id of existing frame."; return; } Frame* parent_frame = FindFrame(parent_id); if (!parent_frame) { DVLOG(1) << "OnCreatedLocalFrame supplied invalid parent_id."; return; } if (parent_frame != this && parent_frame->frame_tree_client_) { DVLOG(1) << "OnCreatedLocalFrame supplied parent from another connection."; return; } tree_->CreateSharedFrame(parent_frame, frame_id, client_properties.To()); } void Frame::RequestNavigate(mandoline::NavigationTargetType target_type, uint32_t target_frame_id, mojo::URLRequestPtr request) { if (target_type == NAVIGATION_TARGET_TYPE_EXISTING_FRAME) { Frame* target_frame = tree_->root()->FindFrame(target_frame_id); if (!target_frame) { DVLOG(1) << "RequestNavigate EXIT_FRAME with no matching frame"; return; } if (target_frame != tree_->root()) { target_frame->StartNavigate(request.Pass()); return; } // Else case if |target_frame| == root. Treat at top level request. } tree_->delegate_->NavigateTopLevel(this, request.Pass()); } void Frame::DidNavigateLocally(uint32_t frame_id, const mojo::String& url) { NOTIMPLEMENTED(); } } // namespace mandoline