summaryrefslogtreecommitdiffstats
path: root/webkit/tools/test_shell/test_shell.cc
blob: 564979c176501f67c5bd8ed15e2ce2620626e496 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
// Copyright (c) 2009 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.

#undef LOG

#include "webkit/tools/test_shell/test_shell.h"

#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/debug_on_start.h"
#include "base/file_path.h"
#include "base/file_util.h"
#if defined(OS_MACOSX)
#include "base/mac_util.h"
#endif
#include "base/md5.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/stats_table.h"
#include "base/utf_string_conversions.h"
#include "build/build_config.h"
#include "gfx/codec/png_codec.h"
#include "gfx/size.h"
#include "googleurl/src/url_util.h"
#include "grit/webkit_strings.h"
#include "net/base/mime_util.h"
#include "net/base/net_util.h"
#include "net/url_request/url_request_file_job.h"
#include "net/url_request/url_request_filter.h"
#include "skia/ext/bitmap_platform_device.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/WebKit/WebKit/chromium/public/WebAccessibilityObject.h"
#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/WebKit/chromium/public/WebKit.h"
#include "third_party/WebKit/WebKit/chromium/public/WebScriptController.h"
#include "third_party/WebKit/WebKit/chromium/public/WebRect.h"
#include "third_party/WebKit/WebKit/chromium/public/WebSize.h"
#include "third_party/WebKit/WebKit/chromium/public/WebString.h"
#include "third_party/WebKit/WebKit/chromium/public/WebURL.h"
#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h"
#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h"
#include "third_party/WebKit/WebKit/chromium/public/WebView.h"
#include "webkit/glue/glue_serialize.h"
#include "webkit/glue/webkit_glue.h"
#include "webkit/glue/webpreferences.h"
#include "webkit/tools/test_shell/accessibility_controller.h"
#include "webkit/tools/test_shell/notification_presenter.h"
#include "webkit/tools/test_shell/simple_resource_loader_bridge.h"
#include "webkit/tools/test_shell/test_navigation_controller.h"
#include "webkit/tools/test_shell/test_shell_devtools_agent.h"
#include "webkit/tools/test_shell/test_shell_devtools_client.h"
#include "webkit/tools/test_shell/test_shell_switches.h"
#include "webkit/tools/test_shell/test_webview_delegate.h"

using WebKit::WebCanvas;
using WebKit::WebFrame;
using WebKit::WebNavigationPolicy;
using WebKit::WebRect;
using WebKit::WebScriptController;
using WebKit::WebSize;
using WebKit::WebURLRequest;
using WebKit::WebView;

namespace {

// Default timeout in ms for file page loads when in layout test mode.
const int kDefaultFileTestTimeoutMillisecs = 10 * 1000;

// Content area size for newly created windows.
const int kTestWindowWidth = 800;
const int kTestWindowHeight = 600;

// The W3C SVG layout tests use a different size than the other layout
// tests.
const int kSVGTestWindowWidth = 480;
const int kSVGTestWindowHeight = 360;

// URLRequestTestShellFileJob is used to serve the inspector
class URLRequestTestShellFileJob : public URLRequestFileJob {
 public:
  static URLRequestJob* InspectorFactory(URLRequest* request,
                                         const std::string& scheme) {
    FilePath path;
    PathService::Get(base::DIR_EXE, &path);
    path = path.AppendASCII("resources");
    path = path.AppendASCII("inspector");
    path = path.AppendASCII(request->url().path().substr(1));
    return new URLRequestTestShellFileJob(request, path);
  }

 private:
  URLRequestTestShellFileJob(URLRequest* request, const FilePath& path)
      : URLRequestFileJob(request, path) {
  }
  virtual ~URLRequestTestShellFileJob() { }

  DISALLOW_COPY_AND_ASSIGN(URLRequestTestShellFileJob);
};

}  // namespace

// Initialize static member variable
WindowList* TestShell::window_list_;
WebPreferences* TestShell::web_prefs_ = NULL;
bool TestShell::developer_extras_enabled_ = false;
bool TestShell::inspector_test_mode_ = false;
bool TestShell::layout_test_mode_ = false;
bool TestShell::allow_external_pages_ = false;
int TestShell::file_test_timeout_ms_ = kDefaultFileTestTimeoutMillisecs;
bool TestShell::test_is_preparing_ = false;
bool TestShell::test_is_pending_ = false;

TestShell::TestShell()
    : m_mainWnd(NULL),
      m_editWnd(NULL),
      m_webViewHost(NULL),
      m_popupHost(NULL),
      m_focusedWidgetHost(NULL),
#if defined(OS_WIN)
      default_edit_wnd_proc_(0),
#endif
      test_params_(NULL),
      is_modal_(false),
      is_loading_(false),
      allow_images_(true),
      allow_plugins_(true),
      allow_scripts_(true),
      dump_stats_table_on_exit_(false) {
    accessibility_controller_.reset(new AccessibilityController(this));
    delegate_.reset(new TestWebViewDelegate(this));
    popup_delegate_.reset(new TestWebViewDelegate(this));
    layout_test_controller_.reset(new LayoutTestController(this));
    event_sending_controller_.reset(new EventSendingController(this));
    plain_text_controller_.reset(new PlainTextController(this));
    text_input_controller_.reset(new TextInputController(this));
    navigation_controller_.reset(new TestNavigationController(this));
    notification_presenter_.reset(new TestNotificationPresenter(this));

    URLRequestFilter* filter = URLRequestFilter::GetInstance();
    filter->AddHostnameHandler("test-shell-resource", "inspector",
                               &URLRequestTestShellFileJob::InspectorFactory);
    url_util::AddStandardScheme("test-shell-resource");
}

TestShell::~TestShell() {
  delegate_->RevokeDragDrop();

  // DevTools frontend page is supposed to be navigated only once and
  // loading another URL in that Page is an error.
  if (!dev_tools_client_.get()) {
    // Navigate to an empty page to fire all the destruction logic for the
    // current page.
    LoadURL(GURL("about:blank"));
  }

  // Call GC twice to clean up garbage.
  CallJSGC();
  CallJSGC();

  // Destroy the WebView before the TestWebViewDelegate.
  m_webViewHost.reset();

  CloseDevTools();

  PlatformCleanUp();

  StatsTable *table = StatsTable::current();
  if (dump_stats_table_on_exit_) {
    // Dump the stats table.
    printf("<stats>\n");
    if (table != NULL) {
      int counter_max = table->GetMaxCounters();
      for (int index = 0; index < counter_max; index++) {
        std::string name(table->GetRowName(index));
        if (name.length() > 0) {
          int value = table->GetRowValue(index);
          printf("%s:\t%d\n", name.c_str(), value);
        }
      }
    }
    printf("</stats>\n");
  }
}

void TestShell::UpdateNavigationControls() {
  int current_index = navigation_controller()->GetCurrentEntryIndex();
  int max_index = navigation_controller()->GetEntryCount() - 1;

  EnableUIControl(BACK_BUTTON, current_index > 0);
  EnableUIControl(FORWARD_BUTTON, current_index < max_index);
  EnableUIControl(STOP_BUTTON, is_loading_);
}

bool TestShell::CreateNewWindow(const GURL& starting_url,
                                TestShell** result) {
  TestShell* shell = new TestShell();
  bool rv = shell->Initialize(starting_url);
  if (rv) {
    if (result)
      *result = shell;
    TestShell::windowList()->push_back(shell->m_mainWnd);
  }
  return rv;
}

void TestShell::ShutdownTestShell() {
  PlatformShutdown();
  SimpleResourceLoaderBridge::Shutdown();
  delete window_list_;
  delete TestShell::web_prefs_;
}

// All fatal log messages (e.g. DCHECK failures) imply unit test failures
static void UnitTestAssertHandler(const std::string& str) {
  FAIL() << str;
}

// static
void TestShell::Dump(TestShell* shell) {
  const TestParams* params = NULL;
  if ((shell == NULL) || ((params = shell->test_params()) == NULL))
    return;

  WebScriptController::flushConsoleMessages();

  // Dump the requested representation.
  WebFrame* frame = shell->webView()->mainFrame();
  if (frame) {
    bool should_dump_as_text =
        shell->layout_test_controller_->ShouldDumpAsText();
    bool dumped_anything = false;
    if (params->dump_tree) {
      dumped_anything = true;
      // Text output: the test page can request different types of output
      // which we handle here.
      if (!should_dump_as_text) {
        // Plain text pages should be dumped as text
        const string16& mime_type =
            frame->dataSource()->response().mimeType();
        should_dump_as_text = EqualsASCII(mime_type, "text/plain");
      }
      if (should_dump_as_text) {
        bool recursive = shell->layout_test_controller_->
            ShouldDumpChildFramesAsText();
        std::string data_utf8 = WideToUTF8(
            webkit_glue::DumpFramesAsText(frame, recursive));
        if (fwrite(data_utf8.c_str(), 1, data_utf8.size(), stdout) !=
            data_utf8.size()) {
          LOG(FATAL) << "Short write to stdout, disk full?";
        }
      } else {
        printf("%s", WideToUTF8(
            webkit_glue::DumpRenderer(frame)).c_str());

        bool recursive = shell->layout_test_controller_->
            ShouldDumpChildFrameScrollPositions();
        printf("%s", WideToUTF8(
            webkit_glue::DumpFrameScrollPosition(frame, recursive)).c_str());
      }

      if (shell->layout_test_controller_->ShouldDumpBackForwardList()) {
        std::wstring bfDump;
        DumpAllBackForwardLists(&bfDump);
        printf("%s", WideToUTF8(bfDump).c_str());
      }
    }

    if (params->dump_pixels && !should_dump_as_text) {
      // Image output: we write the image data to the file given on the
      // command line (for the dump pixels argument), and the MD5 sum to
      // stdout.
      dumped_anything = true;
      WebViewHost* view_host = shell->webViewHost();
      view_host->webview()->layout();
      if (shell->layout_test_controller()->test_repaint()) {
        WebSize view_size = view_host->webview()->size();
        int width = view_size.width;
        int height = view_size.height;
        if (shell->layout_test_controller()->sweep_horizontally()) {
          for (gfx::Rect column(0, 0, 1, height); column.x() < width;
              column.Offset(1, 0)) {
            view_host->PaintRect(column);
          }
        } else {
          for (gfx::Rect line(0, 0, width, 1); line.y() < height;
              line.Offset(0, 1)) {
            view_host->PaintRect(line);
          }
        }
      } else {
        view_host->Paint();
      }

      // See if we need to draw the selection bounds rect. Selection bounds
      // rect is the rect enclosing the (possibly transformed) selection.
      // The rect should be drawn after everything is laid out and painted.
      if (shell->layout_test_controller_->ShouldDumpSelectionRect()) {
        // If there is a selection rect - draw a red 1px border enclosing rect
        WebRect wr = frame->selectionBoundsRect();
        if (!wr.isEmpty()) {
          // Render a red rectangle bounding selection rect
          SkPaint paint;
          paint.setColor(0xFFFF0000);  // Fully opaque red
          paint.setStyle(SkPaint::kStroke_Style);
          paint.setFlags(SkPaint::kAntiAlias_Flag);
          paint.setStrokeWidth(1.0f);
          SkIRect rect;  // Bounding rect
          rect.set(wr.x, wr.y, wr.x + wr.width, wr.y + wr.height);
          view_host->canvas()->drawIRect(rect, paint);
        }
      }

      std::string md5sum = DumpImage(view_host->canvas(),
          params->pixel_file_name, params->pixel_hash);
      printf("#MD5:%s\n", md5sum.c_str());
    }
    if (dumped_anything)
      printf("#EOF\n");
    fflush(stdout);
  }
}

// static
std::string TestShell::DumpImage(skia::PlatformCanvas* canvas,
    const std::wstring& file_name, const std::string& pixel_hash) {
  skia::BitmapPlatformDevice& device =
      static_cast<skia::BitmapPlatformDevice&>(canvas->getTopPlatformDevice());
  const SkBitmap& src_bmp = device.accessBitmap(false);

  // Encode image.
  std::vector<unsigned char> png;
  SkAutoLockPixels src_bmp_lock(src_bmp);
  gfx::PNGCodec::ColorFormat color_format = gfx::PNGCodec::FORMAT_BGRA;

  // Fix the alpha. The expected PNGs on Mac have an alpha channel, so we want
  // to keep it. On Windows, the alpha channel is wrong since text/form control
  // drawing may have erased it in a few places. So on Windows we force it to
  // opaque and also don't write the alpha channel for the reference. Linux
  // doesn't have the wrong alpha like Windows, but we ignore it anyway.
#if defined(OS_WIN)
  bool discard_transparency = true;
  device.makeOpaque(0, 0, src_bmp.width(), src_bmp.height());
#elif defined(OS_MACOSX)
  bool discard_transparency = false;
#elif defined(OS_POSIX)
  bool discard_transparency = true;
#endif

  // Compute MD5 sum.  We should have done this before calling
  // device.makeOpaque on Windows.  Because we do it after the call, there are
  // some images that are the pixel identical on windows and other platforms
  // but have different MD5 sums.  At this point, rebaselining all the windows
  // tests is too much of a pain, so we just check in different baselines.
  MD5Context ctx;
  MD5Init(&ctx);
  MD5Update(&ctx, src_bmp.getPixels(), src_bmp.getSize());

  MD5Digest digest;
  MD5Final(&digest, &ctx);
  std::string md5hash = MD5DigestToBase16(digest);

  // Only encode and dump the png if the hashes don't match. Encoding the image
  // is really expensive.
  if (md5hash.compare(pixel_hash) != 0) {
    gfx::PNGCodec::Encode(
        reinterpret_cast<const unsigned char*>(src_bmp.getPixels()),
        color_format, src_bmp.width(), src_bmp.height(),
        static_cast<int>(src_bmp.rowBytes()), discard_transparency, &png);

    // Write to disk.
    file_util::WriteFile(file_name, reinterpret_cast<const char *>(&png[0]),
                         png.size());
  }

  return md5hash;
}

// static
void TestShell::InitLogging(bool suppress_error_dialogs,
                            bool layout_test_mode,
                            bool enable_gp_fault_error_box) {
    if (suppress_error_dialogs)
        logging::SetLogAssertHandler(UnitTestAssertHandler);

#if defined(OS_WIN)
    if (!IsDebuggerPresent()) {
        UINT new_flags = SEM_FAILCRITICALERRORS |
                         SEM_NOOPENFILEERRORBOX;
        if (!enable_gp_fault_error_box)
            new_flags |= SEM_NOGPFAULTERRORBOX;

        // Preserve existing error mode, as discussed at
        // http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx
        UINT existing_flags = SetErrorMode(new_flags);
        SetErrorMode(existing_flags | new_flags);
    }
#endif

    // Only log to a file if we're running layout tests. This prevents debugging
    // output from disrupting whether or not we pass.
    logging::LoggingDestination destination =
        logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG;
    if (layout_test_mode)
      destination = logging::LOG_ONLY_TO_FILE;

    // We might have multiple test_shell processes going at once
    FilePath log_filename;
    PathService::Get(base::DIR_EXE, &log_filename);
    log_filename = log_filename.AppendASCII("test_shell.log");
    logging::InitLogging(log_filename.value().c_str(),
                         destination,
                         logging::LOCK_LOG_FILE,
                         logging::DELETE_OLD_LOG_FILE);

    // we want process and thread IDs because we may have multiple processes
    logging::SetLogItems(true, true, false, true);

    // Turn on logging of notImplemented()s inside WebKit, but only if we're
    // not running layout tests (because otherwise they'd corrupt the test
    // output).
    if (!layout_test_mode)
      webkit_glue::EnableWebCoreNotImplementedLogging();
}

// static
void TestShell::CleanupLogging() {
    logging::CloseLogFile();
}

// static
void TestShell::SetAllowScriptsToCloseWindows() {
  if (web_prefs_)
    web_prefs_->allow_scripts_to_close_windows = true;
}

// static
void TestShell::ResetWebPreferences() {
    DCHECK(web_prefs_);

    // Match the settings used by Mac DumpRenderTree, with the exception of
    // fonts.
    if (web_prefs_) {
        *web_prefs_ = WebPreferences();

#if defined(OS_MACOSX)
        web_prefs_->serif_font_family = L"Times";
        web_prefs_->cursive_font_family = L"Apple Chancery";
        web_prefs_->fantasy_font_family = L"Papyrus";
#else
        // NOTE: case matters here, this must be 'times new roman', else
        // some layout tests fail.
        web_prefs_->serif_font_family = L"times new roman";

        // These two fonts are picked from the intersection of
        // Win XP font list and Vista font list :
        //   http://www.microsoft.com/typography/fonts/winxp.htm
        //   http://blogs.msdn.com/michkap/archive/2006/04/04/567881.aspx
        // Some of them are installed only with CJK and complex script
        // support enabled on Windows XP and are out of consideration here.
        // (although we enabled both on our buildbots.)
        // They (especially Impact for fantasy) are not typical cursive
        // and fantasy fonts, but it should not matter for layout tests
        // as long as they're available.
        web_prefs_->cursive_font_family = L"Comic Sans MS";
        web_prefs_->fantasy_font_family = L"Impact";
#endif
        web_prefs_->standard_font_family = web_prefs_->serif_font_family;
        web_prefs_->fixed_font_family = L"Courier";
        web_prefs_->sans_serif_font_family = L"Helvetica";

        web_prefs_->default_encoding = "ISO-8859-1";
        web_prefs_->default_font_size = 16;
        web_prefs_->default_fixed_font_size = 13;
        web_prefs_->minimum_font_size = 1;
        web_prefs_->minimum_logical_font_size = 9;
        web_prefs_->javascript_can_open_windows_automatically = true;
        web_prefs_->dom_paste_enabled = true;
        web_prefs_->developer_extras_enabled = !layout_test_mode_ ||
            developer_extras_enabled_;
        web_prefs_->site_specific_quirks_enabled = true;
        web_prefs_->shrinks_standalone_images_to_fit = false;
        web_prefs_->uses_universal_detector = false;
        web_prefs_->text_areas_are_resizable = false;
        web_prefs_->java_enabled = false;
        web_prefs_->allow_scripts_to_close_windows = false;
        web_prefs_->javascript_can_access_clipboard = true;
        web_prefs_->xss_auditor_enabled = false;
        // It's off by default for Chrome, but we don't want to
        // lose the coverage of dynamic font tests in webkit test.
        web_prefs_->remote_fonts_enabled = true;
        web_prefs_->local_storage_enabled = true;
        web_prefs_->application_cache_enabled = true;
        web_prefs_->databases_enabled = true;
        web_prefs_->allow_file_access_from_file_urls = true;
        // LayoutTests were written with Safari Mac in mind which does not allow
        // tabbing to links by default.
        web_prefs_->tabs_to_links = false;

        // Allow those layout tests running as local files, i.e. under
        // LayoutTests/http/tests/local, to access http server.
        if (layout_test_mode_)
          web_prefs_->allow_universal_access_from_file_urls = true;
    }
}

// static
bool TestShell::RemoveWindowFromList(gfx::NativeWindow window) {
  WindowList::iterator entry =
      std::find(TestShell::windowList()->begin(),
                TestShell::windowList()->end(),
                window);
  if (entry != TestShell::windowList()->end()) {
    TestShell::windowList()->erase(entry);
    return true;
  }

  return false;
}

void TestShell::TestTimedOut() {
  puts("#TEST_TIMED_OUT\n");
  TestFinished();
}

void TestShell::Show(WebNavigationPolicy policy) {
  delegate_->show(policy);
}

void TestShell::BindJSObjectsToWindow(WebFrame* frame) {
  // Only bind the test classes if we're running tests.
  if (layout_test_mode_) {
    accessibility_controller_->BindToJavascript(
        frame, L"accessibilityController");
    layout_test_controller_->BindToJavascript(frame, L"layoutTestController");
    event_sending_controller_->BindToJavascript(frame, L"eventSender");
    plain_text_controller_->BindToJavascript(frame, L"plainText");
    text_input_controller_->BindToJavascript(frame, L"textInputController");
  }
}

void TestShell::DumpBackForwardEntry(int index, std::wstring* result) {
  int current_index = navigation_controller_->GetLastCommittedEntryIndex();

  std::string content_state =
      navigation_controller_->GetEntryAtIndex(index)->GetContentState();
  if (content_state.empty()) {
    content_state = webkit_glue::CreateHistoryStateForURL(
        navigation_controller_->GetEntryAtIndex(index)->GetURL());
  }

  result->append(
      webkit_glue::DumpHistoryState(content_state, 8, index == current_index));
}

void TestShell::DumpBackForwardList(std::wstring* result) {
  result->append(L"\n============== Back Forward List ==============\n");

  for (int i = 0; i < navigation_controller_->GetEntryCount(); ++i)
    DumpBackForwardEntry(i, result);

  result->append(L"===============================================\n");
}

void TestShell::CallJSGC() {
  webView()->mainFrame()->collectGarbage();
}

WebView* TestShell::CreateWebView() {
  // If we're running layout tests, only open a new window if the test has
  // called layoutTestController.setCanOpenWindows()
  if (layout_test_mode_ && !layout_test_controller_->CanOpenWindows())
    return NULL;

  TestShell* new_win;
  if (!CreateNewWindow(GURL(), &new_win))
    return NULL;

  return new_win->webView();
}

void TestShell::InitializeDevToolsAgent(WebView* webView) {
  DCHECK(!dev_tools_agent_.get());
  dev_tools_agent_.reset(new TestShellDevToolsAgent(webView));
}

void TestShell::ShowDevTools() {
  if (!devtools_shell_) {
    FilePath dir_exe;
    PathService::Get(base::DIR_EXE, &dir_exe);
    FilePath devtools_path =
        dir_exe.AppendASCII("resources/inspector/devtools.html");
    TestShell* devtools_shell;
    TestShell::CreateNewWindow(GURL(devtools_path.value()),
                               &devtools_shell);
    devtools_shell_ = devtools_shell->AsWeakPtr();
    devtools_shell_->CreateDevToolsClient(dev_tools_agent_.get());
  }
  DCHECK(devtools_shell_);
  devtools_shell_->Show(WebKit::WebNavigationPolicyNewWindow);
}

void TestShell::CloseDevTools() {
  if (devtools_shell_)
    devtools_shell_->DestroyWindow(devtools_shell_->mainWnd());
}

void TestShell::CreateDevToolsClient(TestShellDevToolsAgent *agent) {
  dev_tools_client_.reset(new TestShellDevToolsClient(agent,
                                                      webView()));
}

bool TestShell::IsSVGTestURL(const GURL& url) {
  return url.is_valid() && url.spec().find("W3C-SVG-1.1") != std::string::npos;
}

void TestShell::SizeToSVG() {
  SizeTo(kSVGTestWindowWidth, kSVGTestWindowHeight);
}

void TestShell::SizeToDefault() {
  SizeTo(kTestWindowWidth, kTestWindowHeight);
}

void TestShell::ResetTestController() {
  accessibility_controller_->Reset();
  layout_test_controller_->Reset();
  event_sending_controller_->Reset();
  notification_presenter_->Reset();
  delegate_->Reset();
}

void TestShell::LoadFile(const FilePath& file) {
  LoadURLForFrame(net::FilePathToFileURL(file), std::wstring());
}

void TestShell::LoadURL(const GURL& url) {
  // Used as a sentinal for run_webkit_tests.py to know when to start reading
  // test output for this test and so we know we're not getting out of sync.
  if (layout_test_mode_ && test_params())
    printf("#URL:%s\n", test_params()->test_url.c_str());

  LoadURLForFrame(url, std::wstring());
}

bool TestShell::Navigate(const TestNavigationEntry& entry, bool reload) {
  // Get the right target frame for the entry.
  WebFrame* frame = webView()->mainFrame();
  if (!entry.GetTargetFrame().empty()) {
      frame = webView()->findFrameByName(
          WideToUTF16Hack(entry.GetTargetFrame()));
  }
  // TODO(mpcomplete): should we clear the target frame, or should
  // back/forward navigations maintain the target frame?

  // A navigation resulting from loading a javascript URL should not be
  // treated as a browser initiated event.  Instead, we want it to look as if
  // the page initiated any load resulting from JS execution.
  if (!entry.GetURL().SchemeIs("javascript")) {
    delegate_->set_pending_extra_data(
        new TestShellExtraData(entry.GetPageID()));
  }

  // If we are reloading, then WebKit will use the state of the current page.
  // Otherwise, we give it the state to navigate to.
  if (reload) {
    frame->reload(false);
  } else if (!entry.GetContentState().empty()) {
    DCHECK_NE(entry.GetPageID(), -1);
    frame->loadHistoryItem(
        webkit_glue::HistoryItemFromString(entry.GetContentState()));
  } else {
    DCHECK_EQ(entry.GetPageID(), -1);
    frame->loadRequest(WebURLRequest(entry.GetURL()));
  }

  // In case LoadRequest failed before DidCreateDataSource was called.
  delegate_->set_pending_extra_data(NULL);

  // Restore focus to the main frame prior to loading new request.
  // This makes sure that we don't have a focused iframe. Otherwise, that
  // iframe would keep focus when the SetFocus called immediately after
  // LoadRequest, thus making some tests fail (see http://b/issue?id=845337
  // for more details).
  webView()->setFocusedFrame(frame);
  SetFocus(webViewHost(), true);

  return true;
}

void TestShell::GoBackOrForward(int offset) {
    navigation_controller_->GoToOffset(offset);
}

void TestShell::DumpDocumentText() {
  std::wstring file_path;
  if (!PromptForSaveFile(L"Dump document text", &file_path))
      return;

  const std::string data =
      WideToUTF8(webkit_glue::DumpDocumentText(webView()->mainFrame()));
  file_util::WriteFile(file_path, data.c_str(), data.length());
}

void TestShell::DumpRenderTree() {
  std::wstring file_path;
  if (!PromptForSaveFile(L"Dump render tree", &file_path))
    return;

  const std::string data =
      WideToUTF8(webkit_glue::DumpRenderer(webView()->mainFrame()));
  file_util::WriteFile(file_path, data.c_str(), data.length());
}

std::wstring TestShell::GetDocumentText() {
  return webkit_glue::DumpDocumentText(webView()->mainFrame());
}

void TestShell::Reload() {
    navigation_controller_->Reload();
}

void TestShell::SetFocus(WebWidgetHost* host, bool enable) {
  if (!layout_test_mode_) {
    InteractiveSetFocus(host, enable);
  } else {
    // Simulate the effects of InteractiveSetFocus(), which includes calling
    // both setFocus() and setIsActive().
    if (enable) {
      if (m_focusedWidgetHost != host) {
        if (m_focusedWidgetHost)
          m_focusedWidgetHost->webwidget()->setFocus(false);
        webView()->setIsActive(enable);
        host->webwidget()->setFocus(enable);
        m_focusedWidgetHost = host;
      }
    } else {
      if (m_focusedWidgetHost == host) {
        host->webwidget()->setFocus(enable);
        webView()->setIsActive(enable);
        m_focusedWidgetHost = NULL;
      }
    }
  }
}

//-----------------------------------------------------------------------------

namespace webkit_glue {

void PrecacheUrl(const char16* url, int url_length) {}

void AppendToLog(const char* file, int line, const char* msg) {
  logging::LogMessage(file, line).stream() << msg;
}

bool GetApplicationDirectory(FilePath* path) {
  return PathService::Get(base::DIR_EXE, path);
}

bool GetExeDirectory(FilePath* path) {
  return GetApplicationDirectory(path);
}

bool IsPluginRunningInRendererProcess() {
  return true;
}

bool GetPluginFinderURL(std::string* plugin_finder_url) {
  return false;
}

bool IsDefaultPluginEnabled() {
#if defined(OS_WIN)
  FilePath exe_path;

  if (PathService::Get(base::FILE_EXE, &exe_path)) {
    std::wstring exe_name = file_util::GetFilenameFromPath(
        exe_path.ToWStringHack());
    if (StartsWith(exe_name, L"test_shell_tests", false))
      return true;
  }
#endif  // OS_WIN
  return false;
}

bool IsProtocolSupportedForMedia(const GURL& url) {
  if (url.SchemeIsFile() ||
      url.SchemeIs("http") ||
      url.SchemeIs("https") ||
      url.SchemeIs("data"))
    return true;
  return false;
}

std::wstring GetWebKitLocale() {
  return L"en-US";
}

void CloseCurrentConnections() {
  // Used in benchmarking,  Ignored for test_shell.
}

void SetCacheMode(bool enabled) {
  // Used in benchmarking,  Ignored for test_shell.
}

std::string GetProductVersion() {
  return std::string("Chrome/0.0.0.0");
}

}  // namespace webkit_glue