1 // Copyright 2015 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.
5 #include "components/web_view/frame.h"
9 #include "base/auto_reset.h"
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/stl_util.h"
13 #include "components/mus/public/cpp/view.h"
14 #include "components/mus/public/cpp/view_property.h"
15 #include "components/web_view/frame_tree.h"
16 #include "components/web_view/frame_tree_delegate.h"
17 #include "components/web_view/frame_user_data.h"
18 #include "components/web_view/frame_utils.h"
19 #include "mojo/application/public/interfaces/shell.mojom.h"
23 DECLARE_VIEW_PROPERTY_TYPE(web_view::Frame
*);
27 // Used to find the Frame associated with a View.
28 DEFINE_LOCAL_VIEW_PROPERTY_KEY(Frame
*, kFrame
, nullptr);
32 const uint32_t kNoParentId
= 0u;
33 const mus::ConnectionSpecificId kInvalidConnectionId
= 0u;
35 FrameDataPtr
FrameToFrameData(const Frame
* frame
) {
36 FrameDataPtr
frame_data(FrameData::New());
37 frame_data
->frame_id
= frame
->id();
38 frame_data
->parent_id
= frame
->parent() ? frame
->parent()->id() : kNoParentId
;
39 frame_data
->client_properties
=
40 mojo::Map
<mojo::String
, mojo::Array
<uint8_t>>::From(
41 frame
->client_properties());
42 return frame_data
.Pass();
47 struct Frame::FrameTreeServerBinding
{
48 scoped_ptr
<FrameUserData
> user_data
;
49 scoped_ptr
<mojo::Binding
<FrameTreeServer
>> frame_tree_server_binding
;
52 Frame::Frame(FrameTree
* tree
,
56 ViewOwnership view_ownership
,
57 FrameTreeClient
* frame_tree_client
,
58 scoped_ptr
<FrameUserData
> user_data
,
59 const ClientPropertyMap
& client_properties
)
62 embedded_connection_id_(kInvalidConnectionId
),
66 view_ownership_(view_ownership
),
67 user_data_(user_data
.Pass()),
68 frame_tree_client_(frame_tree_client
),
71 client_properties_(client_properties
),
72 embed_weak_ptr_factory_(this),
73 navigate_weak_ptr_factory_(this) {
80 view_
->RemoveObserver(this);
81 while (!children_
.empty())
84 parent_
->Remove(this);
86 view_
->ClearLocalProperty(kFrame
);
87 if (view_ownership_
== ViewOwnership::OWNS_VIEW
)
90 tree_
->delegate_
->DidDestroyFrame(this);
93 void Frame::Init(Frame
* parent
,
94 mojo::ViewTreeClientPtr view_tree_client
,
95 mojo::InterfaceRequest
<FrameTreeServer
> server_request
) {
97 // Set the FrameTreeClient to null so that we don't notify the client of the
98 // add before OnConnect().
99 base::AutoReset
<FrameTreeClient
*> frame_tree_client_resetter(
100 &frame_tree_client_
, nullptr);
105 const ClientType client_type
= server_request
.is_pending()
106 ? ClientType::NEW_CHILD_FRAME
107 : ClientType::EXISTING_FRAME_NEW_APP
;
108 InitClient(client_type
, nullptr, view_tree_client
.Pass(),
109 server_request
.Pass());
111 tree_
->delegate_
->DidCreateFrame(this);
115 Frame
* Frame::FindFirstFrameAncestor(View
* view
) {
116 while (view
&& !view
->GetLocalProperty(kFrame
))
117 view
= view
->parent();
118 return view
? view
->GetLocalProperty(kFrame
) : nullptr;
121 const Frame
* Frame::FindFrame(uint32_t id
) const {
125 for (const Frame
* child
: children_
) {
126 const Frame
* match
= child
->FindFrame(id
);
133 bool Frame::HasAncestor(const Frame
* frame
) const {
134 const Frame
* current
= this;
135 while (current
&& current
!= frame
)
136 current
= current
->parent_
;
137 return current
== frame
;
140 bool Frame::IsLoading() const {
143 for (const Frame
* child
: children_
) {
144 if (child
->IsLoading())
150 double Frame::GatherProgress(int* frame_count
) const {
152 double progress
= progress_
;
153 for (const Frame
* child
: children_
)
154 progress
+= child
->GatherProgress(frame_count
);
158 void Frame::InitClient(
159 ClientType client_type
,
160 scoped_ptr
<FrameTreeServerBinding
> frame_tree_server_binding
,
161 mojo::ViewTreeClientPtr view_tree_client
,
162 mojo::InterfaceRequest
<FrameTreeServer
> server_request
) {
163 if (client_type
== ClientType::EXISTING_FRAME_NEW_APP
&&
164 view_tree_client
.get()) {
165 embedded_connection_id_
= kInvalidConnectionId
;
166 embed_weak_ptr_factory_
.InvalidateWeakPtrs();
168 view_tree_client
.Pass(), mojo::ViewTree::ACCESS_POLICY_DEFAULT
,
169 base::Bind(&Frame::OnEmbedAck
, embed_weak_ptr_factory_
.GetWeakPtr()));
172 if (client_type
== ClientType::NEW_CHILD_FRAME
) {
173 // Don't install an error handler. We allow for the target to only
174 // implement ViewTreeClient.
175 // This frame (and client) was created by an existing FrameTreeClient. There
176 // is no need to send it OnConnect().
177 frame_tree_server_binding_
.reset(
178 new mojo::Binding
<FrameTreeServer
>(this, server_request
.Pass()));
179 frame_tree_client_
->OnConnect(
180 nullptr, tree_
->change_id(), id_
, VIEW_CONNECT_TYPE_USE_NEW
,
181 mojo::Array
<FrameDataPtr
>(),
182 base::Bind(&OnConnectAck
, base::Passed(&frame_tree_server_binding
)));
184 std::vector
<const Frame
*> frames
;
185 tree_
->root()->BuildFrameTree(&frames
);
187 mojo::Array
<FrameDataPtr
> array(frames
.size());
188 for (size_t i
= 0; i
< frames
.size(); ++i
)
189 array
[i
] = FrameToFrameData(frames
[i
]).Pass();
191 FrameTreeServerPtr frame_tree_server_ptr
;
192 // Don't install an error handler. We allow for the target to only
193 // implement ViewTreeClient.
194 frame_tree_server_binding_
.reset(new mojo::Binding
<FrameTreeServer
>(
195 this, GetProxy(&frame_tree_server_ptr
).Pass()));
196 frame_tree_client_
->OnConnect(
197 frame_tree_server_ptr
.Pass(), tree_
->change_id(), id_
,
198 client_type
== ClientType::EXISTING_FRAME_SAME_APP
199 ? VIEW_CONNECT_TYPE_USE_EXISTING
200 : VIEW_CONNECT_TYPE_USE_NEW
,
202 base::Bind(&OnConnectAck
, base::Passed(&frame_tree_server_binding
)));
203 tree_
->delegate_
->DidStartNavigation(this);
208 void Frame::OnConnectAck(
209 scoped_ptr
<FrameTreeServerBinding
> frame_tree_server_binding
) {}
211 void Frame::ChangeClient(FrameTreeClient
* frame_tree_client
,
212 scoped_ptr
<FrameUserData
> user_data
,
213 mojo::ViewTreeClientPtr view_tree_client
,
215 while (!children_
.empty())
218 ClientType client_type
= view_tree_client
.get() == nullptr
219 ? ClientType::EXISTING_FRAME_SAME_APP
220 : ClientType::EXISTING_FRAME_NEW_APP
;
221 scoped_ptr
<FrameTreeServerBinding
> frame_tree_server_binding
;
223 if (client_type
== ClientType::EXISTING_FRAME_SAME_APP
) {
224 // See comment in InitClient() for details.
225 frame_tree_server_binding
.reset(new FrameTreeServerBinding
);
226 frame_tree_server_binding
->user_data
= user_data_
.Pass();
227 frame_tree_server_binding
->frame_tree_server_binding
=
228 frame_tree_server_binding_
.Pass();
234 user_data_
= user_data
.Pass();
235 frame_tree_client_
= frame_tree_client
;
236 frame_tree_server_binding_
.reset();
239 InitClient(client_type
, frame_tree_server_binding
.Pass(),
240 view_tree_client
.Pass(), nullptr);
243 void Frame::OnEmbedAck(bool success
, mus::ConnectionSpecificId connection_id
) {
245 embedded_connection_id_
= connection_id
;
248 void Frame::SetView(mus::View
* view
) {
250 DCHECK_EQ(id_
, view
->id());
252 view_
->SetLocalProperty(kFrame
, this);
253 view_
->AddObserver(this);
254 if (pending_navigate_
.get())
255 StartNavigate(pending_navigate_
.Pass());
258 void Frame::BuildFrameTree(std::vector
<const Frame
*>* frames
) const {
259 frames
->push_back(this);
260 for (const Frame
* frame
: children_
)
261 frame
->BuildFrameTree(frames
);
264 void Frame::Add(Frame
* node
) {
265 DCHECK(!node
->parent_
);
267 node
->parent_
= this;
268 children_
.push_back(node
);
270 tree_
->root()->NotifyAdded(this, node
, tree_
->AdvanceChangeID());
273 void Frame::Remove(Frame
* node
) {
274 DCHECK_EQ(node
->parent_
, this);
275 auto iter
= std::find(children_
.begin(), children_
.end(), node
);
276 DCHECK(iter
!= children_
.end());
277 node
->parent_
= nullptr;
278 children_
.erase(iter
);
280 tree_
->root()->NotifyRemoved(this, node
, tree_
->AdvanceChangeID());
283 void Frame::StartNavigate(mojo::URLRequestPtr request
) {
284 pending_navigate_
.reset();
286 // We need a View to navigate. When we get the View we'll complete the
289 pending_navigate_
= request
.Pass();
293 // Drop any pending navigation requests.
294 navigate_weak_ptr_factory_
.InvalidateWeakPtrs();
296 tree_
->delegate_
->CanNavigateFrame(
297 this, request
.Pass(),
298 base::Bind(&Frame::OnCanNavigateFrame
,
299 navigate_weak_ptr_factory_
.GetWeakPtr()));
302 void Frame::OnCanNavigateFrame(uint32_t app_id
,
303 FrameTreeClient
* frame_tree_client
,
304 scoped_ptr
<FrameUserData
> user_data
,
305 mojo::ViewTreeClientPtr view_tree_client
) {
306 if (AreAppIdsEqual(app_id
, app_id_
)) {
307 // The app currently rendering the frame will continue rendering it. In this
308 // case we do not use the ViewTreeClient (because the app has a View already
309 // and ends up reusing it).
310 DCHECK(!view_tree_client
.get());
312 frame_tree_client_
->OnWillNavigate();
313 DCHECK(view_tree_client
.get());
315 ChangeClient(frame_tree_client
, user_data
.Pass(), view_tree_client
.Pass(),
319 void Frame::NotifyAdded(const Frame
* source
,
320 const Frame
* added_node
,
321 uint32_t change_id
) {
322 // |frame_tree_client_| may be null during initial frame creation and
324 if (frame_tree_client_
)
325 frame_tree_client_
->OnFrameAdded(change_id
, FrameToFrameData(added_node
));
327 for (Frame
* child
: children_
)
328 child
->NotifyAdded(source
, added_node
, change_id
);
331 void Frame::NotifyRemoved(const Frame
* source
,
332 const Frame
* removed_node
,
333 uint32_t change_id
) {
334 frame_tree_client_
->OnFrameRemoved(change_id
, removed_node
->id());
336 for (Frame
* child
: children_
)
337 child
->NotifyRemoved(source
, removed_node
, change_id
);
340 void Frame::NotifyClientPropertyChanged(const Frame
* source
,
341 const mojo::String
& name
,
342 const mojo::Array
<uint8_t>& value
) {
344 frame_tree_client_
->OnFrameClientPropertyChanged(source
->id(), name
,
347 for (Frame
* child
: children_
)
348 child
->NotifyClientPropertyChanged(source
, name
, value
);
351 void Frame::NotifyFrameLoadingStateChanged(const Frame
* frame
, bool loading
) {
352 frame_tree_client_
->OnFrameLoadingStateChanged(frame
->id(), loading
);
355 void Frame::NotifyDispatchFrameLoadEvent(const Frame
* frame
) {
356 frame_tree_client_
->OnDispatchFrameLoadEvent(frame
->id());
359 void Frame::OnTreeChanged(const TreeChangeParams
& params
) {
360 if (params
.new_parent
&& this == tree_
->root()) {
361 Frame
* child_frame
= FindFrame(params
.target
->id());
362 if (child_frame
&& !child_frame
->view_
)
363 child_frame
->SetView(params
.target
);
367 void Frame::OnViewDestroying(mus::View
* view
) {
369 parent_
->Remove(this);
371 // Reset |view_ownership_| so we don't attempt to delete |view_| in the
373 view_ownership_
= ViewOwnership::DOESNT_OWN_VIEW
;
375 if (tree_
->root() == this) {
376 view_
->RemoveObserver(this);
384 void Frame::OnViewEmbeddedAppDisconnected(mus::View
* view
) {
385 // See FrameTreeDelegate::OnViewEmbeddedAppDisconnected() for details of when
388 // Currently we have no way to distinguish between the cases that lead to this
389 // being called, so we assume we can continue on. Continuing on is important
390 // for html as it's entirely possible for a page to create a frame, navigate
391 // to a bogus url and expect the frame to still exist.
392 tree_
->delegate_
->OnViewEmbeddedInFrameDisconnected(this);
395 void Frame::PostMessageEventToFrame(uint32_t target_frame_id
,
396 HTMLMessageEventPtr event
) {
397 // NOTE: |target_frame_id| is allowed to be from another connection.
398 Frame
* target
= tree_
->root()->FindFrame(target_frame_id
);
399 if (!target
|| target
== this ||
400 !tree_
->delegate_
->CanPostMessageEventToFrame(this, target
, event
.get()))
403 target
->frame_tree_client_
->OnPostMessageEvent(id_
, target_frame_id
,
407 void Frame::LoadingStateChanged(bool loading
, double progress
) {
408 bool loading_state_changed
= loading_
!= loading
;
410 progress_
= progress
;
411 tree_
->LoadingStateChanged();
413 if (loading_state_changed
&& parent_
&&
414 !AreAppIdsEqual(app_id_
, parent_
->app_id_
)) {
415 // We need to notify the parent if it is in a different app, so that it can
416 // keep track of this frame's loading state. If the parent is in the same
417 // app, we assume that the loading state is propagated directly within the
418 // app itself and no notification is needed from our side.
419 parent_
->NotifyFrameLoadingStateChanged(this, loading_
);
423 void Frame::TitleChanged(const mojo::String
& title
) {
424 // Only care about title changes on the root frame.
426 tree_
->TitleChanged(title
);
429 void Frame::DidCommitProvisionalLoad() {
430 tree_
->DidCommitProvisionalLoad(this);
433 void Frame::SetClientProperty(const mojo::String
& name
,
434 mojo::Array
<uint8_t> value
) {
435 auto iter
= client_properties_
.find(name
);
436 const bool already_in_map
= (iter
!= client_properties_
.end());
437 if (value
.is_null()) {
440 client_properties_
.erase(iter
);
442 std::vector
<uint8_t> as_vector(value
.To
<std::vector
<uint8_t>>());
443 if (already_in_map
&& iter
->second
== as_vector
)
445 client_properties_
[name
] = as_vector
;
447 tree_
->ClientPropertyChanged(this, name
, value
);
450 void Frame::OnCreatedFrame(
451 mojo::InterfaceRequest
<FrameTreeServer
> server_request
,
452 FrameTreeClientPtr client
,
454 mojo::Map
<mojo::String
, mojo::Array
<uint8_t>> client_properties
) {
455 if ((frame_id
>> 16) != embedded_connection_id_
) {
456 // TODO(sky): kill connection here?
457 // TODO(sky): there is a race in that there is no guarantee we received the
458 // connection id before the frame tries to create a new frame. Ideally we
459 // could pause the frame until we get the connection id, but bindings don't
460 // offer such an API.
461 DVLOG(1) << "OnCreatedFrame supplied invalid frame id, expecting"
462 << embedded_connection_id_
;
466 if (FindFrame(frame_id
)) {
467 // TODO(sky): kill connection here?
468 DVLOG(1) << "OnCreatedFrame supplied id of existing frame.";
472 Frame
* child_frame
= tree_
->CreateChildFrame(
473 this, server_request
.Pass(), client
.Pass(), frame_id
, app_id_
,
474 client_properties
.To
<ClientPropertyMap
>());
475 child_frame
->embedded_connection_id_
= embedded_connection_id_
;
478 void Frame::RequestNavigate(NavigationTargetType target_type
,
479 uint32_t target_frame_id
,
480 mojo::URLRequestPtr request
) {
481 if (target_type
== NAVIGATION_TARGET_TYPE_EXISTING_FRAME
) {
482 // |target_frame| is allowed to come from another connection.
483 Frame
* target_frame
= tree_
->root()->FindFrame(target_frame_id
);
485 DVLOG(1) << "RequestNavigate EXISTING_FRAME with no matching frame";
488 if (target_frame
!= tree_
->root()) {
489 target_frame
->StartNavigate(request
.Pass());
492 // Else case if |target_frame| == root. Treat at top level request.
494 tree_
->delegate_
->NavigateTopLevel(this, request
.Pass());
497 void Frame::DidNavigateLocally(const mojo::String
& url
) {
501 void Frame::DispatchLoadEventToParent() {
502 if (parent_
&& !AreAppIdsEqual(app_id_
, parent_
->app_id_
)) {
503 // Send notification to fire a load event in the parent, if the parent is in
504 // a different app. If the parent is in the same app, we assume that the app
505 // itself handles firing load event directly and no notification is needed
507 parent_
->NotifyDispatchFrameLoadEvent(this);
511 } // namespace web_view