browser_test_utils.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright (c) 2012 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/public/test/browser_test_utils.h"
6
7#include "base/command_line.h"
8#include "base/json/json_reader.h"
9#include "base/path_service.h"
10#include "base/process/kill.h"
11#include "base/rand_util.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/utf_string_conversions.h"
14#include "base/synchronization/waitable_event.h"
15#include "base/test/test_timeouts.h"
16#include "base/values.h"
17#include "content/browser/renderer_host/render_widget_host_impl.h"
18#include "content/browser/web_contents/web_contents_view.h"
19#include "content/common/input/synthetic_web_input_event_builders.h"
20#include "content/public/browser/browser_context.h"
21#include "content/public/browser/dom_operation_notification_details.h"
22#include "content/public/browser/notification_service.h"
23#include "content/public/browser/notification_types.h"
24#include "content/public/browser/render_frame_host.h"
25#include "content/public/browser/render_process_host.h"
26#include "content/public/browser/render_view_host.h"
27#include "content/public/browser/web_contents.h"
28#include "content/public/browser/web_contents_observer.h"
29#include "content/public/test/test_utils.h"
30#include "grit/webui_resources.h"
31#include "net/base/filename_util.h"
32#include "net/cookies/cookie_store.h"
33#include "net/test/python_utils.h"
34#include "net/url_request/url_request_context.h"
35#include "net/url_request/url_request_context_getter.h"
36#include "testing/gtest/include/gtest/gtest.h"
37#include "ui/base/resource/resource_bundle.h"
38#include "ui/events/gestures/gesture_configuration.h"
39#include "ui/events/keycodes/dom4/keycode_converter.h"
40
41namespace content {
42namespace {
43
44class DOMOperationObserver : public NotificationObserver,
45                             public WebContentsObserver {
46 public:
47  explicit DOMOperationObserver(RenderViewHost* rvh)
48      : WebContentsObserver(WebContents::FromRenderViewHost(rvh)),
49        did_respond_(false) {
50    registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE,
51                   Source<WebContents>(web_contents()));
52    message_loop_runner_ = new MessageLoopRunner;
53  }
54
55  virtual void Observe(int type,
56                       const NotificationSource& source,
57                       const NotificationDetails& details) OVERRIDE {
58    DCHECK(type == NOTIFICATION_DOM_OPERATION_RESPONSE);
59    Details<DomOperationNotificationDetails> dom_op_details(details);
60    response_ = dom_op_details->json;
61    did_respond_ = true;
62    message_loop_runner_->Quit();
63  }
64
65  // Overridden from WebContentsObserver:
66  virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
67    message_loop_runner_->Quit();
68  }
69
70  bool WaitAndGetResponse(std::string* response) WARN_UNUSED_RESULT {
71    message_loop_runner_->Run();
72    *response = response_;
73    return did_respond_;
74  }
75
76 private:
77  NotificationRegistrar registrar_;
78  std::string response_;
79  bool did_respond_;
80  scoped_refptr<MessageLoopRunner> message_loop_runner_;
81
82  DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver);
83};
84
85// Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute.
86bool ExecuteScriptHelper(
87    RenderFrameHost* render_frame_host,
88    const std::string& original_script,
89    scoped_ptr<base::Value>* result) WARN_UNUSED_RESULT;
90
91// Executes the passed |original_script| in the frame specified by
92// |render_frame_host|.  If |result| is not NULL, stores the value that the
93// evaluation of the script in |result|.  Returns true on success.
94bool ExecuteScriptHelper(RenderFrameHost* render_frame_host,
95                         const std::string& original_script,
96                         scoped_ptr<base::Value>* result) {
97  // TODO(jcampan): we should make the domAutomationController not require an
98  //                automation id.
99  std::string script =
100      "window.domAutomationController.setAutomationId(0);" + original_script;
101  DOMOperationObserver dom_op_observer(render_frame_host->GetRenderViewHost());
102  render_frame_host->ExecuteJavaScript(base::UTF8ToUTF16(script));
103  std::string json;
104  if (!dom_op_observer.WaitAndGetResponse(&json)) {
105    DLOG(ERROR) << "Cannot communicate with DOMOperationObserver.";
106    return false;
107  }
108
109  // Nothing more to do for callers that ignore the returned JS value.
110  if (!result)
111    return true;
112
113  base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
114  result->reset(reader.ReadToValue(json));
115  if (!result->get()) {
116    DLOG(ERROR) << reader.GetErrorMessage();
117    return false;
118  }
119
120  return true;
121}
122
123void BuildSimpleWebKeyEvent(blink::WebInputEvent::Type type,
124                            ui::KeyboardCode key_code,
125                            int native_key_code,
126                            int modifiers,
127                            NativeWebKeyboardEvent* event) {
128  event->nativeKeyCode = native_key_code;
129  event->windowsKeyCode = key_code;
130  event->setKeyIdentifierFromWindowsKeyCode();
131  event->type = type;
132  event->modifiers = modifiers;
133  event->isSystemKey = false;
134  event->timeStampSeconds = base::Time::Now().ToDoubleT();
135  event->skip_in_browser = true;
136
137  if (type == blink::WebInputEvent::Char ||
138      type == blink::WebInputEvent::RawKeyDown) {
139    event->text[0] = key_code;
140    event->unmodifiedText[0] = key_code;
141  }
142}
143
144void InjectRawKeyEvent(WebContents* web_contents,
145                       blink::WebInputEvent::Type type,
146                       ui::KeyboardCode key_code,
147                       int native_key_code,
148                       int modifiers) {
149  NativeWebKeyboardEvent event;
150  BuildSimpleWebKeyEvent(type, key_code, native_key_code, modifiers, &event);
151  web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event);
152}
153
154void GetCookiesCallback(std::string* cookies_out,
155                        base::WaitableEvent* event,
156                        const std::string& cookies) {
157  *cookies_out = cookies;
158  event->Signal();
159}
160
161void GetCookiesOnIOThread(const GURL& url,
162                          net::URLRequestContextGetter* context_getter,
163                          base::WaitableEvent* event,
164                          std::string* cookies) {
165  net::CookieStore* cookie_store =
166      context_getter->GetURLRequestContext()->cookie_store();
167  cookie_store->GetCookiesWithOptionsAsync(
168      url, net::CookieOptions(),
169      base::Bind(&GetCookiesCallback, cookies, event));
170}
171
172void SetCookieCallback(bool* result,
173                       base::WaitableEvent* event,
174                       bool success) {
175  *result = success;
176  event->Signal();
177}
178
179void SetCookieOnIOThread(const GURL& url,
180                         const std::string& value,
181                         net::URLRequestContextGetter* context_getter,
182                         base::WaitableEvent* event,
183                         bool* result) {
184  net::CookieStore* cookie_store =
185      context_getter->GetURLRequestContext()->cookie_store();
186  cookie_store->SetCookieWithOptionsAsync(
187      url, value, net::CookieOptions(),
188      base::Bind(&SetCookieCallback, result, event));
189}
190
191}  // namespace
192
193
194GURL GetFileUrlWithQuery(const base::FilePath& path,
195                         const std::string& query_string) {
196  GURL url = net::FilePathToFileURL(path);
197  if (!query_string.empty()) {
198    GURL::Replacements replacements;
199    replacements.SetQueryStr(query_string);
200    return url.ReplaceComponents(replacements);
201  }
202  return url;
203}
204
205void WaitForLoadStop(WebContents* web_contents) {
206    WindowedNotificationObserver load_stop_observer(
207    NOTIFICATION_LOAD_STOP,
208    Source<NavigationController>(&web_contents->GetController()));
209  // In many cases, the load may have finished before we get here.  Only wait if
210  // the tab still has a pending navigation.
211  if (!web_contents->IsLoading())
212    return;
213  load_stop_observer.Wait();
214}
215
216void CrashTab(WebContents* web_contents) {
217  RenderProcessHost* rph = web_contents->GetRenderProcessHost();
218  RenderProcessHostWatcher watcher(
219      rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
220  base::KillProcess(rph->GetHandle(), 0, false);
221  watcher.Wait();
222}
223
224void SimulateMouseClick(WebContents* web_contents,
225                        int modifiers,
226                        blink::WebMouseEvent::Button button) {
227  int x = web_contents->GetContainerBounds().width() / 2;
228  int y = web_contents->GetContainerBounds().height() / 2;
229  SimulateMouseClickAt(web_contents, modifiers, button, gfx::Point(x, y));
230}
231
232void SimulateMouseClickAt(WebContents* web_contents,
233                          int modifiers,
234                          blink::WebMouseEvent::Button button,
235                          const gfx::Point& point) {
236  blink::WebMouseEvent mouse_event;
237  mouse_event.type = blink::WebInputEvent::MouseDown;
238  mouse_event.button = button;
239  mouse_event.x = point.x();
240  mouse_event.y = point.y();
241  mouse_event.modifiers = modifiers;
242  // Mac needs globalX/globalY for events to plugins.
243  gfx::Rect offset = web_contents->GetContainerBounds();
244  mouse_event.globalX = point.x() + offset.x();
245  mouse_event.globalY = point.y() + offset.y();
246  mouse_event.clickCount = 1;
247  web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
248  mouse_event.type = blink::WebInputEvent::MouseUp;
249  web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
250}
251
252void SimulateMouseEvent(WebContents* web_contents,
253                        blink::WebInputEvent::Type type,
254                        const gfx::Point& point) {
255  blink::WebMouseEvent mouse_event;
256  mouse_event.type = type;
257  mouse_event.x = point.x();
258  mouse_event.y = point.y();
259  web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
260}
261
262void SimulateTapAt(WebContents* web_contents, const gfx::Point& point) {
263  const double kTapDurationSeconds =
264      0.5 * (ui::GestureConfiguration::
265                 min_touch_down_duration_in_seconds_for_click() +
266             ui::GestureConfiguration::
267                 max_touch_down_duration_in_seconds_for_click());
268  SyntheticWebTouchEvent touch;
269  touch.timeStampSeconds = 0;
270  touch.PressPoint(point.x(), point.y());
271  RenderWidgetHostImpl* widget_host =
272      RenderWidgetHostImpl::From(web_contents->GetRenderViewHost());
273  widget_host->ForwardTouchEvent(touch);
274  touch.timeStampSeconds = kTapDurationSeconds;
275  touch.ReleasePoint(0);
276  widget_host->ForwardTouchEvent(touch);
277}
278
279void SimulateKeyPress(WebContents* web_contents,
280                      ui::KeyboardCode key_code,
281                      bool control,
282                      bool shift,
283                      bool alt,
284                      bool command) {
285  SimulateKeyPressWithCode(
286      web_contents, key_code, NULL, control, shift, alt, command);
287}
288
289void SimulateKeyPressWithCode(WebContents* web_contents,
290                              ui::KeyboardCode key_code,
291                              const char* code,
292                              bool control,
293                              bool shift,
294                              bool alt,
295                              bool command) {
296  ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance();
297  int native_key_code = key_converter->CodeToNativeKeycode(code);
298
299  int modifiers = 0;
300
301  // The order of these key down events shouldn't matter for our simulation.
302  // For our simulation we can use either the left keys or the right keys.
303  if (control) {
304    modifiers |= blink::WebInputEvent::ControlKey;
305    InjectRawKeyEvent(
306        web_contents,
307        blink::WebInputEvent::RawKeyDown,
308        ui::VKEY_CONTROL,
309        key_converter->CodeToNativeKeycode("ControlLeft"),
310        modifiers);
311  }
312
313  if (shift) {
314    modifiers |= blink::WebInputEvent::ShiftKey;
315    InjectRawKeyEvent(
316        web_contents,
317        blink::WebInputEvent::RawKeyDown,
318        ui::VKEY_SHIFT,
319        key_converter->CodeToNativeKeycode("ShiftLeft"),
320        modifiers);
321  }
322
323  if (alt) {
324    modifiers |= blink::WebInputEvent::AltKey;
325    InjectRawKeyEvent(
326        web_contents,
327        blink::WebInputEvent::RawKeyDown,
328        ui::VKEY_MENU,
329        key_converter->CodeToNativeKeycode("AltLeft"),
330        modifiers);
331  }
332
333  if (command) {
334    modifiers |= blink::WebInputEvent::MetaKey;
335    InjectRawKeyEvent(
336        web_contents,
337        blink::WebInputEvent::RawKeyDown,
338        ui::VKEY_COMMAND,
339        key_converter->CodeToNativeKeycode("OSLeft"),
340        modifiers);
341  }
342
343  InjectRawKeyEvent(
344      web_contents,
345      blink::WebInputEvent::RawKeyDown,
346      key_code,
347      native_key_code,
348      modifiers);
349
350  InjectRawKeyEvent(
351      web_contents,
352      blink::WebInputEvent::Char,
353      key_code,
354      native_key_code,
355      modifiers);
356
357  InjectRawKeyEvent(
358      web_contents,
359      blink::WebInputEvent::KeyUp,
360      key_code,
361      native_key_code,
362      modifiers);
363
364  // The order of these key releases shouldn't matter for our simulation.
365  if (control) {
366    modifiers &= ~blink::WebInputEvent::ControlKey;
367    InjectRawKeyEvent(
368        web_contents,
369        blink::WebInputEvent::KeyUp,
370        ui::VKEY_CONTROL,
371        key_converter->CodeToNativeKeycode("ControlLeft"),
372        modifiers);
373  }
374
375  if (shift) {
376    modifiers &= ~blink::WebInputEvent::ShiftKey;
377    InjectRawKeyEvent(
378        web_contents,
379        blink::WebInputEvent::KeyUp,
380        ui::VKEY_SHIFT,
381        key_converter->CodeToNativeKeycode("ShiftLeft"),
382        modifiers);
383  }
384
385  if (alt) {
386    modifiers &= ~blink::WebInputEvent::AltKey;
387    InjectRawKeyEvent(
388        web_contents,
389        blink::WebInputEvent::KeyUp,
390        ui::VKEY_MENU,
391        key_converter->CodeToNativeKeycode("AltLeft"),
392        modifiers);
393  }
394
395  if (command) {
396    modifiers &= ~blink::WebInputEvent::MetaKey;
397    InjectRawKeyEvent(
398        web_contents,
399        blink::WebInputEvent::KeyUp,
400        ui::VKEY_COMMAND,
401        key_converter->CodeToNativeKeycode("OSLeft"),
402        modifiers);
403  }
404
405  ASSERT_EQ(modifiers, 0);
406}
407
408namespace internal {
409
410ToRenderFrameHost::ToRenderFrameHost(WebContents* web_contents)
411    : render_frame_host_(web_contents->GetMainFrame()) {
412}
413
414ToRenderFrameHost::ToRenderFrameHost(RenderViewHost* render_view_host)
415    : render_frame_host_(render_view_host->GetMainFrame()) {
416}
417
418ToRenderFrameHost::ToRenderFrameHost(RenderFrameHost* render_frame_host)
419    : render_frame_host_(render_frame_host) {
420}
421
422}  // namespace internal
423
424bool ExecuteScript(const internal::ToRenderFrameHost& adapter,
425                   const std::string& script) {
426  std::string new_script =
427      script + ";window.domAutomationController.send(0);";
428  return ExecuteScriptHelper(adapter.render_frame_host(), new_script, NULL);
429}
430
431bool ExecuteScriptAndExtractInt(const internal::ToRenderFrameHost& adapter,
432                                const std::string& script, int* result) {
433  DCHECK(result);
434  scoped_ptr<base::Value> value;
435  if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) ||
436      !value.get()) {
437    return false;
438  }
439
440  return value->GetAsInteger(result);
441}
442
443bool ExecuteScriptAndExtractBool(const internal::ToRenderFrameHost& adapter,
444                                 const std::string& script, bool* result) {
445  DCHECK(result);
446  scoped_ptr<base::Value> value;
447  if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) ||
448      !value.get()) {
449    return false;
450  }
451
452  return value->GetAsBoolean(result);
453}
454
455bool ExecuteScriptAndExtractString(const internal::ToRenderFrameHost& adapter,
456                                   const std::string& script,
457                                   std::string* result) {
458  DCHECK(result);
459  scoped_ptr<base::Value> value;
460  if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) ||
461      !value.get()) {
462    return false;
463  }
464
465  return value->GetAsString(result);
466}
467
468namespace {
469void AddToSetIfFrameMatchesPredicate(
470    std::set<RenderFrameHost*>* frame_set,
471    const base::Callback<bool(RenderFrameHost*)>& predicate,
472    RenderFrameHost* host) {
473  if (predicate.Run(host))
474    frame_set->insert(host);
475}
476}
477
478RenderFrameHost* FrameMatchingPredicate(
479    WebContents* web_contents,
480    const base::Callback<bool(RenderFrameHost*)>& predicate) {
481  std::set<RenderFrameHost*> frame_set;
482  web_contents->ForEachFrame(
483      base::Bind(&AddToSetIfFrameMatchesPredicate, &frame_set, predicate));
484  DCHECK_EQ(1U, frame_set.size());
485  return *frame_set.begin();
486}
487
488bool FrameMatchesName(const std::string& name, RenderFrameHost* frame) {
489  return frame->GetFrameName() == name;
490}
491
492bool FrameIsChildOfMainFrame(RenderFrameHost* frame) {
493  return frame->GetParent() && !frame->GetParent()->GetParent();
494}
495
496bool FrameHasSourceUrl(const GURL& url, RenderFrameHost* frame) {
497  return frame->GetLastCommittedURL() == url;
498}
499
500bool ExecuteWebUIResourceTest(WebContents* web_contents,
501                              const std::vector<int>& js_resource_ids) {
502  // Inject WebUI test runner script first prior to other scripts required to
503  // run the test as scripts may depend on it being declared.
504  std::vector<int> ids;
505  ids.push_back(IDR_WEBUI_JS_WEBUI_RESOURCE_TEST);
506  ids.insert(ids.end(), js_resource_ids.begin(), js_resource_ids.end());
507
508  std::string script;
509  for (std::vector<int>::iterator iter = ids.begin();
510       iter != ids.end();
511       ++iter) {
512    ResourceBundle::GetSharedInstance().GetRawDataResource(*iter)
513        .AppendToString(&script);
514    script.append("\n");
515  }
516  if (!ExecuteScript(web_contents, script))
517    return false;
518
519  DOMMessageQueue message_queue;
520  if (!ExecuteScript(web_contents, "runTests()"))
521    return false;
522
523  std::string message;
524  do {
525    if (!message_queue.WaitForMessage(&message))
526      return false;
527  } while (message.compare("\"PENDING\"") == 0);
528
529  return message.compare("\"SUCCESS\"") == 0;
530}
531
532std::string GetCookies(BrowserContext* browser_context, const GURL& url) {
533  std::string cookies;
534  base::WaitableEvent event(true, false);
535  net::URLRequestContextGetter* context_getter =
536      browser_context->GetRequestContext();
537
538  BrowserThread::PostTask(
539      BrowserThread::IO, FROM_HERE,
540      base::Bind(&GetCookiesOnIOThread, url,
541                 make_scoped_refptr(context_getter), &event, &cookies));
542  event.Wait();
543  return cookies;
544}
545
546bool SetCookie(BrowserContext* browser_context,
547               const GURL& url,
548               const std::string& value) {
549  bool result = false;
550  base::WaitableEvent event(true, false);
551  net::URLRequestContextGetter* context_getter =
552      browser_context->GetRequestContext();
553
554  BrowserThread::PostTask(
555      BrowserThread::IO, FROM_HERE,
556      base::Bind(&SetCookieOnIOThread, url, value,
557                 make_scoped_refptr(context_getter), &event, &result));
558  event.Wait();
559  return result;
560}
561
562TitleWatcher::TitleWatcher(WebContents* web_contents,
563                           const base::string16& expected_title)
564    : WebContentsObserver(web_contents),
565      message_loop_runner_(new MessageLoopRunner) {
566  EXPECT_TRUE(web_contents != NULL);
567  expected_titles_.push_back(expected_title);
568}
569
570void TitleWatcher::AlsoWaitForTitle(const base::string16& expected_title) {
571  expected_titles_.push_back(expected_title);
572}
573
574TitleWatcher::~TitleWatcher() {
575}
576
577const base::string16& TitleWatcher::WaitAndGetTitle() {
578  TestTitle();
579  message_loop_runner_->Run();
580  return observed_title_;
581}
582
583void TitleWatcher::DidStopLoading(RenderViewHost* render_view_host) {
584  // When navigating through the history, the restored NavigationEntry's title
585  // will be used. If the entry ends up having the same title after we return
586  // to it, as will usually be the case, then WebContentsObserver::TitleSet
587  // will then be suppressed, since the NavigationEntry's title hasn't changed.
588  TestTitle();
589}
590
591void TitleWatcher::TitleWasSet(NavigationEntry* entry, bool explicit_set) {
592  TestTitle();
593}
594
595void TitleWatcher::TestTitle() {
596  std::vector<base::string16>::const_iterator it =
597      std::find(expected_titles_.begin(),
598                expected_titles_.end(),
599                web_contents()->GetTitle());
600  if (it == expected_titles_.end())
601    return;
602
603  observed_title_ = *it;
604  message_loop_runner_->Quit();
605}
606
607WebContentsDestroyedWatcher::WebContentsDestroyedWatcher(
608    WebContents* web_contents)
609    : WebContentsObserver(web_contents),
610      message_loop_runner_(new MessageLoopRunner) {
611  EXPECT_TRUE(web_contents != NULL);
612}
613
614WebContentsDestroyedWatcher::~WebContentsDestroyedWatcher() {
615}
616
617void WebContentsDestroyedWatcher::Wait() {
618  message_loop_runner_->Run();
619}
620
621void WebContentsDestroyedWatcher::WebContentsDestroyed() {
622  message_loop_runner_->Quit();
623}
624
625RenderProcessHostWatcher::RenderProcessHostWatcher(
626    RenderProcessHost* render_process_host, WatchType type)
627    : render_process_host_(render_process_host),
628      type_(type),
629      message_loop_runner_(new MessageLoopRunner) {
630  render_process_host_->AddObserver(this);
631}
632
633RenderProcessHostWatcher::RenderProcessHostWatcher(
634    WebContents* web_contents, WatchType type)
635    : render_process_host_(web_contents->GetRenderProcessHost()),
636      type_(type),
637      message_loop_runner_(new MessageLoopRunner) {
638  render_process_host_->AddObserver(this);
639}
640
641RenderProcessHostWatcher::~RenderProcessHostWatcher() {
642  if (render_process_host_)
643    render_process_host_->RemoveObserver(this);
644}
645
646void RenderProcessHostWatcher::Wait() {
647  message_loop_runner_->Run();
648}
649
650void RenderProcessHostWatcher::RenderProcessExited(
651    RenderProcessHost* host,
652    base::ProcessHandle handle,
653    base::TerminationStatus status,
654    int exit_code) {
655  if (type_ == WATCH_FOR_PROCESS_EXIT)
656    message_loop_runner_->Quit();
657}
658
659void RenderProcessHostWatcher::RenderProcessHostDestroyed(
660    RenderProcessHost* host) {
661  render_process_host_ = NULL;
662  if (type_ == WATCH_FOR_HOST_DESTRUCTION)
663    message_loop_runner_->Quit();
664}
665
666DOMMessageQueue::DOMMessageQueue() {
667  registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE,
668                 NotificationService::AllSources());
669}
670
671DOMMessageQueue::~DOMMessageQueue() {}
672
673void DOMMessageQueue::Observe(int type,
674                              const NotificationSource& source,
675                              const NotificationDetails& details) {
676  Details<DomOperationNotificationDetails> dom_op_details(details);
677  message_queue_.push(dom_op_details->json);
678  if (message_loop_runner_)
679    message_loop_runner_->Quit();
680}
681
682void DOMMessageQueue::ClearQueue() {
683  message_queue_ = std::queue<std::string>();
684}
685
686bool DOMMessageQueue::WaitForMessage(std::string* message) {
687  DCHECK(message);
688  if (message_queue_.empty()) {
689    // This will be quit when a new message comes in.
690    message_loop_runner_ = new MessageLoopRunner;
691    message_loop_runner_->Run();
692  }
693  // The queue should not be empty, unless we were quit because of a timeout.
694  if (message_queue_.empty())
695    return false;
696  *message = message_queue_.front();
697  message_queue_.pop();
698  return true;
699}
700
701}  // namespace content
702