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.
4
5 #include "content/test/content_browser_test_utils_internal.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <map>
11 #include <memory>
12 #include <set>
13 #include <utility>
14 #include <vector>
15
16 #include "base/bind.h"
17 #include "base/containers/stack.h"
18 #include "base/json/json_reader.h"
19 #include "base/stl_util.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/task/thread_pool.h"
22 #include "base/test/test_timeouts.h"
23 #include "base/threading/thread_task_runner_handle.h"
24 #include "content/browser/renderer_host/delegated_frame_host.h"
25 #include "content/browser/renderer_host/frame_tree_node.h"
26 #include "content/browser/renderer_host/navigator.h"
27 #include "content/browser/renderer_host/render_frame_host_delegate.h"
28 #include "content/browser/renderer_host/render_frame_proxy_host.h"
29 #include "content/browser/web_contents/web_contents_impl.h"
30 #include "content/public/browser/browser_task_traits.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "content/public/browser/file_select_listener.h"
33 #include "content/public/browser/navigation_handle.h"
34 #include "content/public/browser/render_frame_host.h"
35 #include "content/public/browser/web_contents.h"
36 #include "content/public/common/use_zoom_for_dsf_policy.h"
37 #include "content/public/test/browser_test_utils.h"
38 #include "content/public/test/content_browser_test_utils.h"
39 #include "content/public/test/test_frame_navigation_observer.h"
40 #include "content/public/test/test_navigation_observer.h"
41 #include "content/shell/browser/shell.h"
42 #include "content/shell/browser/shell_javascript_dialog_manager.h"
43 #include "third_party/blink/public/common/frame/frame_visual_properties.h"
44
45 namespace content {
46
NavigateFrameToURL(FrameTreeNode * node,const GURL & url)47 bool NavigateFrameToURL(FrameTreeNode* node, const GURL& url) {
48 TestFrameNavigationObserver observer(node);
49 NavigationController::LoadURLParams params(url);
50 params.transition_type = ui::PAGE_TRANSITION_LINK;
51 params.frame_tree_node_id = node->frame_tree_node_id();
52 node->navigator().GetController()->LoadURLWithParams(params);
53 observer.Wait();
54
55 if (!observer.last_navigation_succeeded()) {
56 DLOG(WARNING) << "Navigation did not succeed: " << url;
57 return false;
58 }
59 if (url != node->current_url()) {
60 DLOG(WARNING) << "Expected URL " << url << " but observed "
61 << node->current_url();
62 return false;
63 }
64 return true;
65 }
66
SetShouldProceedOnBeforeUnload(Shell * shell,bool proceed,bool success)67 void SetShouldProceedOnBeforeUnload(Shell* shell, bool proceed, bool success) {
68 ShellJavaScriptDialogManager* manager =
69 static_cast<ShellJavaScriptDialogManager*>(
70 shell->GetJavaScriptDialogManager(shell->web_contents()));
71 manager->set_should_proceed_on_beforeunload(proceed, success);
72 }
73
ConvertToRenderFrameHost(FrameTreeNode * frame_tree_node)74 RenderFrameHost* ConvertToRenderFrameHost(FrameTreeNode* frame_tree_node) {
75 return frame_tree_node->current_frame_host();
76 }
77
NavigateToURLInSameBrowsingInstance(Shell * window,const GURL & url)78 bool NavigateToURLInSameBrowsingInstance(Shell* window, const GURL& url) {
79 TestNavigationObserver observer(window->web_contents());
80 // Using a PAGE_TRANSITION_LINK transition with a browser-initiated
81 // navigation forces it to stay in the current BrowsingInstance, as normally
82 // that transition is used by renderer-initiated navigations.
83 window->LoadURLForFrame(url, std::string(),
84 ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK));
85 observer.Wait();
86
87 if (!IsLastCommittedEntryOfPageType(window->web_contents(),
88 PAGE_TYPE_NORMAL)) {
89 NavigationEntry* last_entry =
90 window->web_contents()->GetController().GetLastCommittedEntry();
91 DLOG(WARNING) << "last_entry->GetPageType() = "
92 << (last_entry ? last_entry->GetPageType() : -1);
93 return false;
94 }
95
96 if (window->web_contents()->GetLastCommittedURL() != url) {
97 DLOG(WARNING) << "window->web_contents()->GetLastCommittedURL() = "
98 << window->web_contents()->GetLastCommittedURL()
99 << "; url = " << url;
100 return false;
101 }
102
103 return true;
104 }
105
FrameTreeVisualizer()106 FrameTreeVisualizer::FrameTreeVisualizer() {
107 }
108
~FrameTreeVisualizer()109 FrameTreeVisualizer::~FrameTreeVisualizer() {
110 }
111
DepictFrameTree(FrameTreeNode * root)112 std::string FrameTreeVisualizer::DepictFrameTree(FrameTreeNode* root) {
113 // Tracks the sites actually used in this depiction.
114 std::map<std::string, SiteInstance*> legend;
115
116 // Traversal 1: Assign names to current frames. This ensures that the first
117 // call to the pretty-printer will result in a naming of the site instances
118 // that feels natural and stable.
119 base::stack<FrameTreeNode*> to_explore;
120 for (to_explore.push(root); !to_explore.empty();) {
121 FrameTreeNode* node = to_explore.top();
122 to_explore.pop();
123 for (size_t i = node->child_count(); i-- != 0;) {
124 to_explore.push(node->child_at(i));
125 }
126
127 RenderFrameHost* current = node->render_manager()->current_frame_host();
128 legend[GetName(current->GetSiteInstance())] = current->GetSiteInstance();
129 }
130
131 // Traversal 2: Assign names to the pending/speculative frames. For stability
132 // of assigned names it's important to do this before trying to name the
133 // proxies, which have a less well defined order.
134 for (to_explore.push(root); !to_explore.empty();) {
135 FrameTreeNode* node = to_explore.top();
136 to_explore.pop();
137 for (size_t i = node->child_count(); i-- != 0;) {
138 to_explore.push(node->child_at(i));
139 }
140
141 RenderFrameHost* spec = node->render_manager()->speculative_frame_host();
142 if (spec)
143 legend[GetName(spec->GetSiteInstance())] = spec->GetSiteInstance();
144 }
145
146 // Traversal 3: Assign names to the proxies and add them to |legend| too.
147 // Typically, only openers should have their names assigned this way.
148 for (to_explore.push(root); !to_explore.empty();) {
149 FrameTreeNode* node = to_explore.top();
150 to_explore.pop();
151 for (size_t i = node->child_count(); i-- != 0;) {
152 to_explore.push(node->child_at(i));
153 }
154
155 // Sort the proxies by SiteInstance ID to avoid unordered_map ordering.
156 std::vector<SiteInstance*> site_instances;
157 for (const auto& proxy_pair :
158 node->render_manager()->GetAllProxyHostsForTesting()) {
159 site_instances.push_back(proxy_pair.second->GetSiteInstance());
160 }
161 std::sort(site_instances.begin(), site_instances.end(),
162 [](SiteInstance* lhs, SiteInstance* rhs) {
163 return lhs->GetId() < rhs->GetId();
164 });
165
166 for (SiteInstance* site_instance : site_instances)
167 legend[GetName(site_instance)] = site_instance;
168 }
169
170 // Traversal 4: Now that all names are assigned, make a big loop to pretty-
171 // print the tree. Each iteration produces exactly one line of format.
172 std::string result;
173 for (to_explore.push(root); !to_explore.empty();) {
174 FrameTreeNode* node = to_explore.top();
175 to_explore.pop();
176 for (size_t i = node->child_count(); i-- != 0;) {
177 to_explore.push(node->child_at(i));
178 }
179
180 // Draw the feeler line tree graphics by walking up to the root. A feeler
181 // line is needed for each ancestor that is the last child of its parent.
182 // This creates the ASCII art that looks like:
183 // Foo
184 // |--Foo
185 // |--Foo
186 // | |--Foo
187 // | +--Foo
188 // | +--Foo
189 // +--Foo
190 // +--Foo
191 //
192 // TODO(nick): Make this more elegant.
193 std::string line;
194 if (node != root) {
195 if (node->parent()->child_at(node->parent()->child_count() - 1) != node)
196 line = " |--";
197 else
198 line = " +--";
199 for (FrameTreeNode* up = node->parent()->frame_tree_node(); up != root;
200 up = FrameTreeNode::From(up->parent())) {
201 if (up->parent()->child_at(up->parent()->child_count() - 1) != up)
202 line = " | " + line;
203 else
204 line = " " + line;
205 }
206 }
207
208 // Prefix one extra space of padding for two reasons. First, this helps the
209 // diagram aligns nicely with the legend. Second, this makes it easier to
210 // read the diffs that gtest spits out on EXPECT_EQ failure.
211 line = " " + line;
212
213 // Summarize the FrameTreeNode's state. Always show the site of the current
214 // RenderFrameHost, and show any exceptional state of the node, like a
215 // pending or speculative RenderFrameHost.
216 RenderFrameHost* current = node->render_manager()->current_frame_host();
217 RenderFrameHost* spec = node->render_manager()->speculative_frame_host();
218 base::StringAppendF(&line, "Site %s",
219 GetName(current->GetSiteInstance()).c_str());
220 if (spec) {
221 base::StringAppendF(&line, " (%s speculative)",
222 GetName(spec->GetSiteInstance()).c_str());
223 }
224
225 // Show the SiteInstances of the RenderFrameProxyHosts of this node.
226 const auto& proxy_host_map =
227 node->render_manager()->GetAllProxyHostsForTesting();
228 if (!proxy_host_map.empty()) {
229 // Show a dashed line of variable length before the proxy list. Always at
230 // least two dashes.
231 line.append(" --");
232
233 // To make proxy lists align vertically for the first three tree levels,
234 // pad with dashes up to a first tab stop at column 19 (which works out to
235 // text editor column 28 in the typical diagram fed to EXPECT_EQ as a
236 // string literal). Lining the lists up vertically makes differences in
237 // the proxy sets easier to spot visually. We choose not to use the
238 // *actual* tree height here, because that would make the diagram's
239 // appearance less stable as the tree's shape evolves.
240 while (line.length() < 20) {
241 line.append("-");
242 }
243 line.append(" proxies for");
244
245 // Sort these alphabetically, to avoid hash_map ordering dependency.
246 std::vector<std::string> sorted_proxy_hosts;
247 for (const auto& proxy_pair : proxy_host_map) {
248 sorted_proxy_hosts.push_back(
249 GetName(proxy_pair.second->GetSiteInstance()));
250 }
251 std::sort(sorted_proxy_hosts.begin(), sorted_proxy_hosts.end());
252 for (std::string& proxy_name : sorted_proxy_hosts) {
253 base::StringAppendF(&line, " %s", proxy_name.c_str());
254 }
255 }
256 if (node != root)
257 result.append("\n");
258 result.append(line);
259 }
260
261 // Finally, show a legend with details of the site instances.
262 const char* prefix = "Where ";
263 for (auto& legend_entry : legend) {
264 SiteInstanceImpl* site_instance =
265 static_cast<SiteInstanceImpl*>(legend_entry.second);
266 std::string description = site_instance->GetSiteURL().spec();
267 base::StringAppendF(&result, "\n%s%s = %s", prefix,
268 legend_entry.first.c_str(), description.c_str());
269 // Highlight some exceptionable conditions.
270 if (site_instance->active_frame_count() == 0)
271 result.append(" (active_frame_count == 0)");
272 if (!site_instance->GetProcess()->IsInitializedAndNotDead())
273 result.append(" (no process)");
274 prefix = " ";
275 }
276 return result;
277 }
278
GetName(SiteInstance * site_instance)279 std::string FrameTreeVisualizer::GetName(SiteInstance* site_instance) {
280 // Indices into the vector correspond to letters of the alphabet.
281 size_t index =
282 std::find(seen_site_instance_ids_.begin(), seen_site_instance_ids_.end(),
283 site_instance->GetId()) -
284 seen_site_instance_ids_.begin();
285 if (index == seen_site_instance_ids_.size())
286 seen_site_instance_ids_.push_back(site_instance->GetId());
287
288 // Whosoever writes a test using >=26 site instances shall be a lucky ducky.
289 if (index < 25)
290 return base::StringPrintf("%c", 'A' + static_cast<char>(index));
291 else
292 return base::StringPrintf("Z%d", static_cast<int>(index - 25));
293 }
294
OpenPopup(const ToRenderFrameHost & opener,const GURL & url,const std::string & name)295 Shell* OpenPopup(const ToRenderFrameHost& opener,
296 const GURL& url,
297 const std::string& name) {
298 return OpenPopup(opener, url, name, "", true);
299 }
300
OpenPopup(const ToRenderFrameHost & opener,const GURL & url,const std::string & name,const std::string & features,bool expect_return_from_window_open)301 Shell* OpenPopup(const ToRenderFrameHost& opener,
302 const GURL& url,
303 const std::string& name,
304 const std::string& features,
305 bool expect_return_from_window_open) {
306 TestNavigationObserver observer(url);
307 observer.StartWatchingNewWebContents();
308
309 ShellAddedObserver new_shell_observer;
310 bool did_create_popup = false;
311 std::string popup_script =
312 "window.domAutomationController.send("
313 " !!window.open('" +
314 url.spec() + "', '" + name + "', '" + features + "'));";
315 bool did_execute_script =
316 ExecuteScriptAndExtractBool(opener, popup_script, &did_create_popup);
317
318 // Don't check the value of |did_create_popup| since there are valid reasons
319 // for it to be false, e.g. |features| specifies 'noopener', or 'noreferrer'
320 // or others.
321 if (!did_execute_script ||
322 !(did_create_popup || !expect_return_from_window_open)) {
323 return nullptr;
324 }
325
326 observer.Wait();
327
328 Shell* new_shell = new_shell_observer.GetShell();
329 EXPECT_EQ(url,
330 new_shell->web_contents()->GetMainFrame()->GetLastCommittedURL());
331 return new_shell_observer.GetShell();
332 }
333
FileChooserDelegate(const base::FilePath & file,base::OnceClosure callback)334 FileChooserDelegate::FileChooserDelegate(const base::FilePath& file,
335 base::OnceClosure callback)
336 : file_(file), callback_(std::move(callback)) {}
337
338 FileChooserDelegate::~FileChooserDelegate() = default;
339
RunFileChooser(RenderFrameHost * render_frame_host,scoped_refptr<content::FileSelectListener> listener,const blink::mojom::FileChooserParams & params)340 void FileChooserDelegate::RunFileChooser(
341 RenderFrameHost* render_frame_host,
342 scoped_refptr<content::FileSelectListener> listener,
343 const blink::mojom::FileChooserParams& params) {
344 // Send the selected file to the renderer process.
345 auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile(
346 blink::mojom::NativeFileInfo::New(file_, base::string16()));
347 std::vector<blink::mojom::FileChooserFileInfoPtr> files;
348 files.push_back(std::move(file_info));
349 listener->FileSelected(std::move(files), base::FilePath(),
350 blink::mojom::FileChooserParams::Mode::kOpen);
351
352 params_ = params.Clone();
353 std::move(callback_).Run();
354 }
355
FrameTestNavigationManager(int filtering_frame_tree_node_id,WebContents * web_contents,const GURL & url)356 FrameTestNavigationManager::FrameTestNavigationManager(
357 int filtering_frame_tree_node_id,
358 WebContents* web_contents,
359 const GURL& url)
360 : TestNavigationManager(web_contents, url),
361 filtering_frame_tree_node_id_(filtering_frame_tree_node_id) {}
362
ShouldMonitorNavigation(NavigationHandle * handle)363 bool FrameTestNavigationManager::ShouldMonitorNavigation(
364 NavigationHandle* handle) {
365 return TestNavigationManager::ShouldMonitorNavigation(handle) &&
366 handle->GetFrameTreeNodeId() == filtering_frame_tree_node_id_;
367 }
368
UrlCommitObserver(FrameTreeNode * frame_tree_node,const GURL & url)369 UrlCommitObserver::UrlCommitObserver(FrameTreeNode* frame_tree_node,
370 const GURL& url)
371 : content::WebContentsObserver(frame_tree_node->current_frame_host()
372 ->delegate()
373 ->GetAsWebContents()),
374 frame_tree_node_id_(frame_tree_node->frame_tree_node_id()),
375 url_(url) {
376 }
377
~UrlCommitObserver()378 UrlCommitObserver::~UrlCommitObserver() {}
379
Wait()380 void UrlCommitObserver::Wait() {
381 run_loop_.Run();
382 }
383
DidFinishNavigation(NavigationHandle * navigation_handle)384 void UrlCommitObserver::DidFinishNavigation(
385 NavigationHandle* navigation_handle) {
386 if (navigation_handle->HasCommitted() &&
387 !navigation_handle->IsErrorPage() &&
388 navigation_handle->GetURL() == url_ &&
389 navigation_handle->GetFrameTreeNodeId() == frame_tree_node_id_) {
390 run_loop_.Quit();
391 }
392 }
393
RenderProcessHostBadIpcMessageWaiter(RenderProcessHost * render_process_host)394 RenderProcessHostBadIpcMessageWaiter::RenderProcessHostBadIpcMessageWaiter(
395 RenderProcessHost* render_process_host)
396 : internal_waiter_(render_process_host,
397 "Stability.BadMessageTerminated.Content") {}
398
399 base::Optional<bad_message::BadMessageReason>
Wait()400 RenderProcessHostBadIpcMessageWaiter::Wait() {
401 base::Optional<int> internal_result = internal_waiter_.Wait();
402 if (!internal_result.has_value())
403 return base::nullopt;
404 return static_cast<bad_message::BadMessageReason>(internal_result.value());
405 }
406
ShowPopupWidgetWaiter(WebContents * web_contents,RenderFrameHostImpl * frame_host)407 ShowPopupWidgetWaiter::ShowPopupWidgetWaiter(WebContents* web_contents,
408 RenderFrameHostImpl* frame_host)
409 : WebContentsObserver(web_contents), frame_host_(frame_host) {
410 frame_host_->SetCreateNewPopupCallbackForTesting(base::BindRepeating(
411 &ShowPopupWidgetWaiter::DidCreatePopupWidget, base::Unretained(this)));
412 }
413
~ShowPopupWidgetWaiter()414 ShowPopupWidgetWaiter::~ShowPopupWidgetWaiter() {
415 if (auto* rwhi = RenderWidgetHostImpl::FromID(process_id_, routing_id_)) {
416 rwhi->popup_widget_host_receiver_for_testing().SwapImplForTesting(rwhi);
417 }
418 if (frame_host_)
419 frame_host_->SetCreateNewPopupCallbackForTesting(base::NullCallback());
420 }
421
Wait()422 void ShowPopupWidgetWaiter::Wait() {
423 run_loop_.Run();
424 }
425
Stop()426 void ShowPopupWidgetWaiter::Stop() {
427 Observe(nullptr);
428 frame_host_->SetCreateNewPopupCallbackForTesting(base::NullCallback());
429 frame_host_ = nullptr;
430 }
431
GetForwardingInterface()432 blink::mojom::PopupWidgetHost* ShowPopupWidgetWaiter::GetForwardingInterface() {
433 DCHECK_NE(MSG_ROUTING_NONE, routing_id_);
434 return RenderWidgetHostImpl::FromID(process_id_, routing_id_);
435 }
436
ShowPopup(const gfx::Rect & initial_rect,ShowPopupCallback callback)437 void ShowPopupWidgetWaiter::ShowPopup(const gfx::Rect& initial_rect,
438 ShowPopupCallback callback) {
439 GetForwardingInterface()->ShowPopup(initial_rect, std::move(callback));
440 initial_rect_ = initial_rect;
441 run_loop_.Quit();
442 }
443
DidCreatePopupWidget(RenderWidgetHostImpl * render_widget_host)444 void ShowPopupWidgetWaiter::DidCreatePopupWidget(
445 RenderWidgetHostImpl* render_widget_host) {
446 process_id_ = render_widget_host->GetProcess()->GetID();
447 routing_id_ = render_widget_host->GetRoutingID();
448 render_widget_host->popup_widget_host_receiver_for_testing()
449 .SwapImplForTesting(this);
450 }
451
452 #if defined(OS_MAC) || defined(OS_ANDROID)
ShowPopupMenu(RenderFrameHost * render_frame_host,mojo::PendingRemote<blink::mojom::PopupMenuClient> * popup_client,const gfx::Rect & bounds,int32_t item_height,double font_size,int32_t selected_item,std::vector<blink::mojom::MenuItemPtr> * menu_items,bool right_aligned,bool allow_multiple_selection)453 bool ShowPopupWidgetWaiter::ShowPopupMenu(
454 RenderFrameHost* render_frame_host,
455 mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
456 const gfx::Rect& bounds,
457 int32_t item_height,
458 double font_size,
459 int32_t selected_item,
460 std::vector<blink::mojom::MenuItemPtr>* menu_items,
461 bool right_aligned,
462 bool allow_multiple_selection) {
463 initial_rect_ = bounds;
464 run_loop_.Quit();
465 return true;
466 }
467 #endif
468
DropMessageFilter(uint32_t message_class,uint32_t drop_message_id)469 DropMessageFilter::DropMessageFilter(uint32_t message_class,
470 uint32_t drop_message_id)
471 : BrowserMessageFilter(message_class), drop_message_id_(drop_message_id) {}
472
473 DropMessageFilter::~DropMessageFilter() = default;
474
OnMessageReceived(const IPC::Message & message)475 bool DropMessageFilter::OnMessageReceived(const IPC::Message& message) {
476 return message.type() == drop_message_id_;
477 }
478
ObserveMessageFilter(uint32_t message_class,uint32_t watch_message_id)479 ObserveMessageFilter::ObserveMessageFilter(uint32_t message_class,
480 uint32_t watch_message_id)
481 : BrowserMessageFilter(message_class),
482 watch_message_id_(watch_message_id) {}
483
484 ObserveMessageFilter::~ObserveMessageFilter() = default;
485
Wait()486 void ObserveMessageFilter::Wait() {
487 base::RunLoop loop;
488 quit_closure_ = loop.QuitClosure();
489 loop.Run();
490 }
491
OnMessageReceived(const IPC::Message & message)492 bool ObserveMessageFilter::OnMessageReceived(const IPC::Message& message) {
493 if (message.type() == watch_message_id_) {
494 // Exit the Wait() method if it's being used, but in a fresh stack once the
495 // message is actually handled.
496 if (quit_closure_ && !received_) {
497 base::ThreadPool::PostTask(
498 FROM_HERE, base::BindOnce(&ObserveMessageFilter::QuitWait, this));
499 }
500 received_ = true;
501 }
502 return false;
503 }
504
QuitWait()505 void ObserveMessageFilter::QuitWait() {
506 std::move(quit_closure_).Run();
507 }
508
UnresponsiveRendererObserver(WebContents * web_contents)509 UnresponsiveRendererObserver::UnresponsiveRendererObserver(
510 WebContents* web_contents)
511 : WebContentsObserver(web_contents) {}
512
513 UnresponsiveRendererObserver::~UnresponsiveRendererObserver() = default;
514
Wait(base::TimeDelta timeout)515 RenderProcessHost* UnresponsiveRendererObserver::Wait(base::TimeDelta timeout) {
516 if (!captured_render_process_host_) {
517 base::OneShotTimer timer;
518 timer.Start(FROM_HERE, timeout, run_loop_.QuitClosure());
519 run_loop_.Run();
520 timer.Stop();
521 }
522 return captured_render_process_host_;
523 }
524
OnRendererUnresponsive(RenderProcessHost * render_process_host)525 void UnresponsiveRendererObserver::OnRendererUnresponsive(
526 RenderProcessHost* render_process_host) {
527 captured_render_process_host_ = render_process_host;
528 run_loop_.Quit();
529 }
530
BeforeUnloadBlockingDelegate(WebContentsImpl * web_contents)531 BeforeUnloadBlockingDelegate::BeforeUnloadBlockingDelegate(
532 WebContentsImpl* web_contents)
533 : web_contents_(web_contents) {
534 web_contents_->SetDelegate(this);
535 }
536
~BeforeUnloadBlockingDelegate()537 BeforeUnloadBlockingDelegate::~BeforeUnloadBlockingDelegate() {
538 if (!callback_.is_null())
539 std::move(callback_).Run(true, base::string16());
540
541 web_contents_->SetDelegate(nullptr);
542 web_contents_->SetJavaScriptDialogManagerForTesting(nullptr);
543 }
544
Wait()545 void BeforeUnloadBlockingDelegate::Wait() {
546 run_loop_->Run();
547 run_loop_ = std::make_unique<base::RunLoop>();
548 }
549
550 JavaScriptDialogManager*
GetJavaScriptDialogManager(WebContents * source)551 BeforeUnloadBlockingDelegate::GetJavaScriptDialogManager(WebContents* source) {
552 return this;
553 }
554
RunJavaScriptDialog(WebContents * web_contents,RenderFrameHost * render_frame_host,JavaScriptDialogType dialog_type,const base::string16 & message_text,const base::string16 & default_prompt_text,DialogClosedCallback callback,bool * did_suppress_message)555 void BeforeUnloadBlockingDelegate::RunJavaScriptDialog(
556 WebContents* web_contents,
557 RenderFrameHost* render_frame_host,
558 JavaScriptDialogType dialog_type,
559 const base::string16& message_text,
560 const base::string16& default_prompt_text,
561 DialogClosedCallback callback,
562 bool* did_suppress_message) {
563 NOTREACHED();
564 }
565
RunBeforeUnloadDialog(WebContents * web_contents,RenderFrameHost * render_frame_host,bool is_reload,DialogClosedCallback callback)566 void BeforeUnloadBlockingDelegate::RunBeforeUnloadDialog(
567 WebContents* web_contents,
568 RenderFrameHost* render_frame_host,
569 bool is_reload,
570 DialogClosedCallback callback) {
571 callback_ = std::move(callback);
572 run_loop_->Quit();
573 }
574
HandleJavaScriptDialog(WebContents * web_contents,bool accept,const base::string16 * prompt_override)575 bool BeforeUnloadBlockingDelegate::HandleJavaScriptDialog(
576 WebContents* web_contents,
577 bool accept,
578 const base::string16* prompt_override) {
579 NOTREACHED();
580 return true;
581 }
582
583 namespace {
584 static constexpr int kEnableLogMessageId = 0;
585 static constexpr char kEnableLogMessage[] = R"({"id":0,"method":"Log.enable"})";
586 static constexpr int kDisableLogMessageId = 1;
587 static constexpr char kDisableLogMessage[] =
588 R"({"id":1,"method":"Log.disable"})";
589 } // namespace
590
DevToolsInspectorLogWatcher(WebContents * web_contents)591 DevToolsInspectorLogWatcher::DevToolsInspectorLogWatcher(
592 WebContents* web_contents) {
593 host_ = DevToolsAgentHost::GetOrCreateFor(web_contents);
594 host_->AttachClient(this);
595
596 host_->DispatchProtocolMessage(
597 this, base::as_bytes(
598 base::make_span(kEnableLogMessage, strlen(kEnableLogMessage))));
599
600 run_loop_enable_log_.Run();
601 }
602
~DevToolsInspectorLogWatcher()603 DevToolsInspectorLogWatcher::~DevToolsInspectorLogWatcher() {
604 host_->DetachClient(this);
605 }
606
DispatchProtocolMessage(DevToolsAgentHost * host,base::span<const uint8_t> message)607 void DevToolsInspectorLogWatcher::DispatchProtocolMessage(
608 DevToolsAgentHost* host,
609 base::span<const uint8_t> message) {
610 base::StringPiece message_str(reinterpret_cast<const char*>(message.data()),
611 message.size());
612 auto parsed_message = base::JSONReader::Read(message_str);
613 base::Optional<int> command_id = parsed_message->FindIntPath("id");
614 if (command_id.has_value()) {
615 switch (command_id.value()) {
616 case kEnableLogMessageId:
617 run_loop_enable_log_.Quit();
618 break;
619 case kDisableLogMessageId:
620 run_loop_disable_log_.Quit();
621 break;
622 default:
623 NOTREACHED();
624 }
625 return;
626 }
627
628 std::string* notification = parsed_message->FindStringPath("method");
629 if (notification && *notification == "Log.entryAdded") {
630 std::string* text = parsed_message->FindStringPath("params.entry.text");
631 DCHECK(text);
632 last_message_ = *text;
633 }
634 }
635
AgentHostClosed(DevToolsAgentHost * host)636 void DevToolsInspectorLogWatcher::AgentHostClosed(DevToolsAgentHost* host) {}
637
FlushAndStopWatching()638 void DevToolsInspectorLogWatcher::FlushAndStopWatching() {
639 host_->DispatchProtocolMessage(
640 this, base::as_bytes(base::make_span(kDisableLogMessage,
641 strlen(kDisableLogMessage))));
642 run_loop_disable_log_.Run();
643 }
644
645 } // namespace content
646