1 // Copyright 2014 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 "content/test/content_browser_test_utils_internal.h"
12 #include "base/strings/stringprintf.h"
13 #include "content/browser/frame_host/frame_tree_node.h"
14 #include "content/browser/frame_host/navigator.h"
15 #include "content/browser/frame_host/render_frame_proxy_host.h"
16 #include "content/public/test/browser_test_utils.h"
17 #include "content/public/test/content_browser_test_utils.h"
18 #include "content/shell/browser/shell.h"
19 #include "content/test/test_frame_navigation_observer.h"
24 void NavigateFrameToURL(FrameTreeNode
* node
, const GURL
& url
) {
25 TestFrameNavigationObserver
observer(node
);
26 NavigationController::LoadURLParams
params(url
);
27 params
.transition_type
= ui::PAGE_TRANSITION_LINK
;
28 params
.frame_tree_node_id
= node
->frame_tree_node_id();
29 node
->navigator()->GetController()->LoadURLWithParams(params
);
33 FrameTreeVisualizer::FrameTreeVisualizer() {
36 FrameTreeVisualizer::~FrameTreeVisualizer() {
39 std::string
FrameTreeVisualizer::DepictFrameTree(FrameTreeNode
* root
) {
40 // Tracks the sites actually used in this depiction.
41 std::map
<std::string
, SiteInstance
*> legend
;
43 // Traversal 1: Assign names to current frames. This ensures that the first
44 // call to the pretty-printer will result in a naming of the site instances
45 // that feels natural and stable.
46 std::stack
<FrameTreeNode
*> to_explore
;
47 for (to_explore
.push(root
); !to_explore
.empty();) {
48 FrameTreeNode
* node
= to_explore
.top();
50 for (size_t i
= node
->child_count(); i
-- != 0;) {
51 to_explore
.push(node
->child_at(i
));
54 RenderFrameHost
* current
= node
->render_manager()->current_frame_host();
55 legend
[GetName(current
->GetSiteInstance())] = current
->GetSiteInstance();
58 // Traversal 2: Assign names to the pending/speculative frames. For stability
59 // of assigned names it's important to do this before trying to name the
60 // proxies, which have a less well defined order.
61 for (to_explore
.push(root
); !to_explore
.empty();) {
62 FrameTreeNode
* node
= to_explore
.top();
64 for (size_t i
= node
->child_count(); i
-- != 0;) {
65 to_explore
.push(node
->child_at(i
));
68 RenderFrameHost
* pending
= node
->render_manager()->pending_frame_host();
69 RenderFrameHost
* spec
= node
->render_manager()->speculative_frame_host();
71 legend
[GetName(pending
->GetSiteInstance())] = pending
->GetSiteInstance();
73 legend
[GetName(spec
->GetSiteInstance())] = spec
->GetSiteInstance();
76 // Traversal 3: Assign names to the proxies and add them to |legend| too.
77 // Typically, only openers should have their names assigned this way.
78 for (to_explore
.push(root
); !to_explore
.empty();) {
79 FrameTreeNode
* node
= to_explore
.top();
81 for (size_t i
= node
->child_count(); i
-- != 0;) {
82 to_explore
.push(node
->child_at(i
));
85 // Sort the proxies by SiteInstance ID to avoid hash_map ordering.
86 std::map
<int, RenderFrameProxyHost
*> sorted_proxy_hosts
=
87 node
->render_manager()->GetAllProxyHostsForTesting();
88 for (auto& proxy_pair
: sorted_proxy_hosts
) {
89 RenderFrameProxyHost
* proxy
= proxy_pair
.second
;
90 legend
[GetName(proxy
->GetSiteInstance())] = proxy
->GetSiteInstance();
94 // Traversal 4: Now that all names are assigned, make a big loop to pretty-
95 // print the tree. Each iteration produces exactly one line of format.
97 for (to_explore
.push(root
); !to_explore
.empty();) {
98 FrameTreeNode
* node
= to_explore
.top();
100 for (size_t i
= node
->child_count(); i
-- != 0;) {
101 to_explore
.push(node
->child_at(i
));
104 // Draw the feeler line tree graphics by walking up to the root. A feeler
105 // line is needed for each ancestor that is the last child of its parent.
106 // This creates the ASCII art that looks like:
116 // TODO(nick): Make this more elegant.
119 if (node
->parent()->child_at(node
->parent()->child_count() - 1) != node
)
123 for (FrameTreeNode
* up
= node
->parent(); up
!= root
; up
= up
->parent()) {
124 if (up
->parent()->child_at(up
->parent()->child_count() - 1) != up
)
131 // Prefix one extra space of padding for two reasons. First, this helps the
132 // diagram aligns nicely with the legend. Second, this makes it easier to
133 // read the diffs that gtest spits out on EXPECT_EQ failure.
136 // Summarize the FrameTreeNode's state. Always show the site of the current
137 // RenderFrameHost, and show any exceptional state of the node, like a
138 // pending or speculative RenderFrameHost.
139 RenderFrameHost
* current
= node
->render_manager()->current_frame_host();
140 RenderFrameHost
* pending
= node
->render_manager()->pending_frame_host();
141 RenderFrameHost
* spec
= node
->render_manager()->speculative_frame_host();
142 base::StringAppendF(&line
, "Site %s",
143 GetName(current
->GetSiteInstance()).c_str());
145 base::StringAppendF(&line
, " (%s pending)",
146 GetName(pending
->GetSiteInstance()).c_str());
149 base::StringAppendF(&line
, " (%s speculative)",
150 GetName(spec
->GetSiteInstance()).c_str());
153 // Show the SiteInstances of the RenderFrameProxyHosts of this node.
154 std::map
<int, RenderFrameProxyHost
*> sorted_proxy_host_map
=
155 node
->render_manager()->GetAllProxyHostsForTesting();
156 if (!sorted_proxy_host_map
.empty()) {
157 // Show a dashed line of variable length before the proxy list. Always at
161 // To make proxy lists align vertically for the first three tree levels,
162 // pad with dashes up to a first tab stop at column 19 (which works out to
163 // text editor column 28 in the typical diagram fed to EXPECT_EQ as a
164 // string literal). Lining the lists up vertically makes differences in
165 // the proxy sets easier to spot visually. We choose not to use the
166 // *actual* tree height here, because that would make the diagram's
167 // appearance less stable as the tree's shape evolves.
168 while (line
.length() < 20) {
171 line
.append(" proxies for");
173 // Sort these alphabetically, to avoid hash_map ordering dependency.
174 std::vector
<std::string
> sorted_proxy_hosts
;
175 for (auto& proxy_pair
: sorted_proxy_host_map
) {
176 sorted_proxy_hosts
.push_back(
177 GetName(proxy_pair
.second
->GetSiteInstance()));
179 std::sort(sorted_proxy_hosts
.begin(), sorted_proxy_hosts
.end());
180 for (std::string
& proxy_name
: sorted_proxy_hosts
) {
181 base::StringAppendF(&line
, " %s", proxy_name
.c_str());
189 // Finally, show a legend with details of the site instances.
190 const char* prefix
= "Where ";
191 for (auto& legend_entry
: legend
) {
192 SiteInstanceImpl
* site_instance
=
193 static_cast<SiteInstanceImpl
*>(legend_entry
.second
);
194 base::StringAppendF(&result
, "\n%s%s = %s", prefix
,
195 legend_entry
.first
.c_str(),
196 site_instance
->GetSiteURL().spec().c_str());
197 // Highlight some exceptionable conditions.
198 if (site_instance
->active_frame_count() == 0)
199 result
.append(" (active_frame_count == 0)");
200 if (!site_instance
->GetProcess()->HasConnection())
201 result
.append(" (no process)");
207 std::string
FrameTreeVisualizer::GetName(SiteInstance
* site_instance
) {
208 // Indices into the vector correspond to letters of the alphabet.
210 std::find(seen_site_instance_ids_
.begin(), seen_site_instance_ids_
.end(),
211 site_instance
->GetId()) -
212 seen_site_instance_ids_
.begin();
213 if (index
== seen_site_instance_ids_
.size())
214 seen_site_instance_ids_
.push_back(site_instance
->GetId());
216 // Whosoever writes a test using >=26 site instances shall be a lucky ducky.
218 return base::StringPrintf("%c", 'A' + static_cast<char>(index
));
220 return base::StringPrintf("Z%d", static_cast<int>(index
- 25));
223 Shell
* OpenPopup(const ToRenderFrameHost
& opener
,
225 const std::string
& name
) {
226 ShellAddedObserver new_shell_observer
;
227 bool did_create_popup
= false;
228 bool did_execute_script
= ExecuteScriptAndExtractBool(
230 "window.domAutomationController.send("
231 " !!window.open('" + url
.spec() + "', '" + name
+ "'));",
233 if (!did_execute_script
|| !did_create_popup
)
236 Shell
* new_shell
= new_shell_observer
.GetShell();
237 WaitForLoadStop(new_shell
->web_contents());
241 } // namespace content