password_manager_browsertest.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1// Copyright 2013 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 <string>
6
7#include "base/command_line.h"
8#include "base/metrics/histogram_samples.h"
9#include "base/metrics/statistics_recorder.h"
10#include "chrome/browser/chrome_notification_types.h"
11#include "chrome/browser/infobars/confirm_infobar_delegate.h"
12#include "chrome/browser/infobars/infobar.h"
13#include "chrome/browser/infobars/infobar_service.h"
14#include "chrome/browser/password_manager/password_store_factory.h"
15#include "chrome/browser/password_manager/test_password_store_service.h"
16#include "chrome/browser/ui/browser.h"
17#include "chrome/browser/ui/tabs/tab_strip_model.h"
18#include "chrome/test/base/in_process_browser_test.h"
19#include "chrome/test/base/test_switches.h"
20#include "chrome/test/base/ui_test_utils.h"
21#include "components/autofill/core/browser/autofill_test_utils.h"
22#include "components/password_manager/core/browser/test_password_store.h"
23#include "content/public/browser/notification_observer.h"
24#include "content/public/browser/notification_registrar.h"
25#include "content/public/browser/notification_service.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/browser_test_utils.h"
30#include "content/public/test/test_utils.h"
31#include "net/test/embedded_test_server/embedded_test_server.h"
32#include "net/url_request/test_url_fetcher_factory.h"
33#include "testing/gmock/include/gmock/gmock.h"
34#include "ui/events/keycodes/keyboard_codes.h"
35
36
37// NavigationObserver ---------------------------------------------------------
38
39namespace {
40
41// Observer that waits for navigation to complete and for the password infobar
42// to be shown.
43class NavigationObserver : public content::NotificationObserver,
44                           public content::WebContentsObserver {
45 public:
46  explicit NavigationObserver(content::WebContents* web_contents)
47      : content::WebContentsObserver(web_contents),
48        message_loop_runner_(new content::MessageLoopRunner),
49        infobar_shown_(false),
50        infobar_removed_(false),
51        should_automatically_accept_infobar_(true),
52        infobar_service_(InfoBarService::FromWebContents(web_contents)) {
53    registrar_.Add(this,
54                   chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED,
55                   content::Source<InfoBarService>(infobar_service_));
56    registrar_.Add(this,
57                   chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
58                   content::Source<InfoBarService>(infobar_service_));
59  }
60
61  virtual ~NavigationObserver() {}
62
63  // Normally Wait() will not return until a main frame navigation occurs.
64  // If a path is set, Wait() will return after this path has been seen,
65  // regardless of the frame that navigated. Useful for multi-frame pages.
66  void SetPathToWaitFor(const std::string& path) {
67    wait_for_path_ = path;
68  }
69
70  // content::NotificationObserver:
71  virtual void Observe(int type,
72                       const content::NotificationSource& source,
73                       const content::NotificationDetails& details) OVERRIDE {
74    switch (type) {
75      case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED:
76        if (should_automatically_accept_infobar_) {
77          infobar_service_->infobar_at(0)
78              ->delegate()
79              ->AsConfirmInfoBarDelegate()
80              ->Accept();
81        }
82        infobar_shown_ = true;
83        return;
84      case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED:
85        infobar_removed_ = true;
86        return;
87      default:
88        NOTREACHED();
89        return;
90    }
91  }
92
93  // content::WebContentsObserver:
94  virtual void DidFinishLoad(
95      int64 frame_id,
96      const GURL& validated_url,
97      bool is_main_frame,
98      content::RenderViewHost* render_view_host) OVERRIDE {
99    if (!wait_for_path_.empty()) {
100      if (validated_url.path() == wait_for_path_)
101        message_loop_runner_->Quit();
102    } else if (is_main_frame) {
103      message_loop_runner_->Quit();
104    }
105  }
106
107  bool infobar_shown() const { return infobar_shown_; }
108  bool infobar_removed() const { return infobar_removed_; }
109
110  void disable_should_automatically_accept_infobar() {
111    should_automatically_accept_infobar_ = false;
112  }
113
114  void Wait() {
115    message_loop_runner_->Run();
116  }
117
118 private:
119  std::string wait_for_path_;
120  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
121  bool infobar_shown_;
122  bool infobar_removed_;
123  // If |should_automatically_accept_infobar_| is true, then whenever the test
124  // sees an infobar added, it will click its accepting button. Default = true.
125  bool should_automatically_accept_infobar_;
126  content::NotificationRegistrar registrar_;
127  InfoBarService* infobar_service_;
128
129  DISALLOW_COPY_AND_ASSIGN(NavigationObserver);
130};
131
132}  // namespace
133
134
135// PasswordManagerBrowserTest -------------------------------------------------
136
137class PasswordManagerBrowserTest : public InProcessBrowserTest {
138 public:
139  PasswordManagerBrowserTest() {}
140  virtual ~PasswordManagerBrowserTest() {}
141
142  // InProcessBrowserTest:
143  virtual void SetUpOnMainThread() OVERRIDE {
144    // Use TestPasswordStore to remove a possible race. Normally the
145    // PasswordStore does its database manipulation on the DB thread, which
146    // creates a possible race during navigation. Specifically the
147    // PasswordManager will ignore any forms in a page if the load from the
148    // PasswordStore has not completed.
149    PasswordStoreFactory::GetInstance()->SetTestingFactory(
150        browser()->profile(), TestPasswordStoreService::Build);
151  }
152
153 protected:
154  content::WebContents* WebContents() {
155    return browser()->tab_strip_model()->GetActiveWebContents();
156  }
157
158  content::RenderViewHost* RenderViewHost() {
159    return WebContents()->GetRenderViewHost();
160  }
161
162  // Wrapper around ui_test_utils::NavigateToURL that waits until
163  // DidFinishLoad() fires. Normally this function returns after
164  // DidStopLoading(), which caused flakiness as the NavigationObserver
165  // would sometimes see the DidFinishLoad event from a previous navigation and
166  // return immediately.
167  void NavigateToFile(const std::string& path) {
168    if (!embedded_test_server()->Started())
169      ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
170
171    NavigationObserver observer(WebContents());
172    GURL url = embedded_test_server()->GetURL(path);
173    ui_test_utils::NavigateToURL(browser(), url);
174    observer.Wait();
175  }
176
177 private:
178  DISALLOW_COPY_AND_ASSIGN(PasswordManagerBrowserTest);
179};
180
181// Actual tests ---------------------------------------------------------------
182IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
183                       PromptForNormalSubmit) {
184  NavigateToFile("/password/password_form.html");
185
186  // Fill a form and submit through a <input type="submit"> button. Nothing
187  // special.
188  NavigationObserver observer(WebContents());
189  std::string fill_and_submit =
190      "document.getElementById('username_field').value = 'temp';"
191      "document.getElementById('password_field').value = 'random';"
192      "document.getElementById('input_submit_button').click()";
193  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
194  observer.Wait();
195  EXPECT_TRUE(observer.infobar_shown());
196}
197
198IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
199                       PromptForSubmitWithInPageNavigation) {
200  NavigateToFile("/password/password_navigate_before_submit.html");
201
202  // Fill a form and submit through a <input type="submit"> button. Nothing
203  // special. The form does an in-page navigation before submitting.
204  NavigationObserver observer(WebContents());
205  std::string fill_and_submit =
206      "document.getElementById('username_field').value = 'temp';"
207      "document.getElementById('password_field').value = 'random';"
208      "document.getElementById('input_submit_button').click()";
209  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
210  observer.Wait();
211  EXPECT_TRUE(observer.infobar_shown());
212}
213
214IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
215                       LoginSuccessWithUnrelatedForm) {
216  // Log in, see a form on the landing page. That form is not related to the
217  // login form (=has a different action), so we should offer saving the
218  // password.
219  NavigateToFile("/password/password_form.html");
220
221  NavigationObserver observer(WebContents());
222  std::string fill_and_submit =
223      "document.getElementById('username_unrelated').value = 'temp';"
224      "document.getElementById('password_unrelated').value = 'random';"
225      "document.getElementById('submit_unrelated').click()";
226  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
227  observer.Wait();
228  EXPECT_TRUE(observer.infobar_shown());
229}
230
231IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, LoginFailed) {
232  // Log in, see a form on the landing page. That form is not related to the
233  // login form (=has a different action), so we should offer saving the
234  // password.
235  NavigateToFile("/password/password_form.html");
236
237  NavigationObserver observer(WebContents());
238  std::string fill_and_submit =
239      "document.getElementById('username_failed').value = 'temp';"
240      "document.getElementById('password_failed').value = 'random';"
241      "document.getElementById('submit_failed').click()";
242  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
243  observer.Wait();
244  EXPECT_FALSE(observer.infobar_shown());
245}
246
247IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, Redirects) {
248  NavigateToFile("/password/password_form.html");
249
250  // Fill a form and submit through a <input type="submit"> button. The form
251  // points to a redirection page.
252  NavigationObserver observer(WebContents());
253  std::string fill_and_submit =
254      "document.getElementById('username_redirect').value = 'temp';"
255      "document.getElementById('password_redirect').value = 'random';"
256      "document.getElementById('submit_redirect').click()";
257  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
258  observer.disable_should_automatically_accept_infobar();
259  observer.Wait();
260  EXPECT_TRUE(observer.infobar_shown());
261
262  // The redirection page now redirects via Javascript. We check that the
263  // infobar stays.
264  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(),
265                                     "window.location.href = 'done.html';"));
266  observer.Wait();
267  EXPECT_FALSE(observer.infobar_removed());
268}
269
270IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
271                       PromptForSubmitUsingJavaScript) {
272  NavigateToFile("/password/password_form.html");
273
274  // Fill a form and submit using <button> that calls submit() on the form.
275  // This should work regardless of the type of element, as long as submit() is
276  // called.
277  NavigationObserver observer(WebContents());
278  std::string fill_and_submit =
279      "document.getElementById('username_field').value = 'temp';"
280      "document.getElementById('password_field').value = 'random';"
281      "document.getElementById('submit_button').click()";
282  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
283  observer.Wait();
284  EXPECT_TRUE(observer.infobar_shown());
285}
286
287// Flaky: crbug.com/301547, observed on win and mac. Probably happens on all
288// platforms.
289IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
290                       DISABLED_PromptForDynamicForm) {
291  NavigateToFile("/password/dynamic_password_form.html");
292
293  // Fill the dynamic password form and submit.
294  NavigationObserver observer(WebContents());
295  std::string fill_and_submit =
296      "document.getElementById('create_form_button').click();"
297      "window.setTimeout(function() {"
298      "  document.dynamic_form.username.value = 'tempro';"
299      "  document.dynamic_form.password.value = 'random';"
300      "  document.dynamic_form.submit();"
301      "}, 0)";
302  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
303  observer.Wait();
304  EXPECT_TRUE(observer.infobar_shown());
305}
306
307IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, NoPromptForNavigation) {
308  NavigateToFile("/password/password_form.html");
309
310  // Don't fill the password form, just navigate away. Shouldn't prompt.
311  NavigationObserver observer(WebContents());
312  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(),
313                                     "window.location.href = 'done.html';"));
314  observer.Wait();
315  EXPECT_FALSE(observer.infobar_shown());
316}
317
318IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
319                       NoPromptForSubFrameNavigation) {
320  NavigateToFile("/password/multi_frames.html");
321
322  // If you are filling out a password form in one frame and a different frame
323  // navigates, this should not trigger the infobar.
324  NavigationObserver observer(WebContents());
325  observer.SetPathToWaitFor("/password/done.html");
326  std::string fill =
327      "var first_frame = document.getElementById('first_frame');"
328      "var frame_doc = first_frame.contentDocument;"
329      "frame_doc.getElementById('username_field').value = 'temp';"
330      "frame_doc.getElementById('password_field').value = 'random';";
331  std::string navigate_frame =
332      "var second_iframe = document.getElementById('second_frame');"
333      "second_iframe.contentWindow.location.href = 'done.html';";
334
335  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill));
336  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), navigate_frame));
337  observer.Wait();
338  EXPECT_FALSE(observer.infobar_shown());
339}
340
341IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
342                       PromptAfterSubmitWithSubFrameNavigation) {
343  NavigateToFile("/password/multi_frames.html");
344
345  // Make sure that we prompt to save password even if a sub-frame navigation
346  // happens first.
347  NavigationObserver observer(WebContents());
348  observer.SetPathToWaitFor("/password/done.html");
349  std::string navigate_frame =
350      "var second_iframe = document.getElementById('second_frame');"
351      "second_iframe.contentWindow.location.href = 'other.html';";
352  std::string fill_and_submit =
353      "var first_frame = document.getElementById('first_frame');"
354      "var frame_doc = first_frame.contentDocument;"
355      "frame_doc.getElementById('username_field').value = 'temp';"
356      "frame_doc.getElementById('password_field').value = 'random';"
357      "frame_doc.getElementById('input_submit_button').click();";
358
359  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), navigate_frame));
360  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
361  observer.Wait();
362  EXPECT_TRUE(observer.infobar_shown());
363}
364
365IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
366                       PromptForXHRSubmit) {
367#if defined(OS_WIN) && defined(USE_ASH)
368  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
369  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
370    return;
371#endif
372  NavigateToFile("/password/password_xhr_submit.html");
373
374  // Verify that we show the save password prompt if a form returns false
375  // in its onsubmit handler but instead logs in/navigates via XHR.
376  // Note that calling 'submit()' on a form with javascript doesn't call
377  // the onsubmit handler, so we click the submit button instead.
378  NavigationObserver observer(WebContents());
379  std::string fill_and_submit =
380      "document.getElementById('username_field').value = 'temp';"
381      "document.getElementById('password_field').value = 'random';"
382      "document.getElementById('submit_button').click()";
383  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
384  observer.Wait();
385  EXPECT_TRUE(observer.infobar_shown());
386}
387
388IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
389                       PromptForXHRWithoutOnSubmit) {
390  NavigateToFile("/password/password_xhr_submit.html");
391
392  // Verify that if XHR navigation occurs and the form is properly filled out,
393  // we try and save the password even though onsubmit hasn't been called.
394  NavigationObserver observer(WebContents());
395  std::string fill_and_navigate =
396      "document.getElementById('username_field').value = 'temp';"
397      "document.getElementById('password_field').value = 'random';"
398      "send_xhr()";
399  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_navigate));
400  observer.Wait();
401  EXPECT_TRUE(observer.infobar_shown());
402}
403
404IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
405                       NoPromptIfLinkClicked) {
406  NavigateToFile("/password/password_form.html");
407
408  // Verify that if the user takes a direct action to leave the page, we don't
409  // prompt to save the password even if the form is already filled out.
410  NavigationObserver observer(WebContents());
411  std::string fill_and_click_link =
412      "document.getElementById('username_field').value = 'temp';"
413      "document.getElementById('password_field').value = 'random';"
414      "document.getElementById('link').click();";
415  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_click_link));
416  observer.Wait();
417  EXPECT_FALSE(observer.infobar_shown());
418}
419
420IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
421                       VerifyPasswordGenerationUpload) {
422  // Prevent Autofill requests from actually going over the wire.
423  net::TestURLFetcherFactory factory;
424  // Disable Autofill requesting access to AddressBook data. This causes
425  // the test to hang on Mac.
426  autofill::test::DisableSystemServices(browser()->profile());
427
428  // Visit a signup form.
429  NavigateToFile("/password/signup_form.html");
430
431  // Enter a password and save it.
432  NavigationObserver first_observer(WebContents());
433  std::string fill_and_submit =
434      "document.getElementById('other_info').value = 'stuff';"
435      "document.getElementById('username_field').value = 'my_username';"
436      "document.getElementById('password_field').value = 'password';"
437      "document.getElementById('input_submit_button').click()";
438  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
439
440  first_observer.Wait();
441  ASSERT_TRUE(first_observer.infobar_shown());
442
443  // Now navigate to a login form that has similar HTML markup.
444  NavigateToFile("/password/password_form.html");
445
446  // Simulate a user click to force an autofill of the form's DOM value, not
447  // just the suggested value.
448  std::string click = "document.getElementById('testform_no_name').click()";
449  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), click));
450
451  // The form should be filled with the previously submitted username.
452  std::string get_username =
453      "window.domAutomationController.send("
454      "document.getElementById('username_field').value);";
455  std::string actual_username;
456  ASSERT_TRUE(content::ExecuteScriptAndExtractString(RenderViewHost(),
457                                                     get_username,
458                                                     &actual_username));
459  ASSERT_EQ("my_username", actual_username);
460
461  // Submit the form and verify that there is no infobar (as the password
462  // has already been saved).
463  NavigationObserver second_observer(WebContents());
464  std::string submit_form =
465      "document.getElementById('input_submit_button').click()";
466  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submit_form));
467  second_observer.Wait();
468  EXPECT_FALSE(second_observer.infobar_shown());
469
470  // Verify that we sent a ping to Autofill saying that the original form
471  // was likely an account creation form since it has more than 2 text input
472  // fields and was used for the first time on a different form.
473  base::HistogramBase* upload_histogram =
474      base::StatisticsRecorder::FindHistogram(
475          "PasswordGeneration.UploadStarted");
476  ASSERT_TRUE(upload_histogram);
477  scoped_ptr<base::HistogramSamples> snapshot =
478      upload_histogram->SnapshotSamples();
479  EXPECT_EQ(0, snapshot->GetCount(0 /* failure */));
480  EXPECT_EQ(1, snapshot->GetCount(1 /* success */));
481}
482
483IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, PromptForSubmitFromIframe) {
484  NavigateToFile("/password/password_submit_from_iframe.html");
485
486  // Submit a form in an iframe, then cause the whole page to navigate without a
487  // user gesture. We expect the save password prompt to be shown here, because
488  // some pages use such iframes for login forms.
489  NavigationObserver observer(WebContents());
490  std::string fill_and_submit =
491      "var iframe = document.getElementById('test_iframe');"
492      "var iframe_doc = iframe.contentDocument;"
493      "iframe_doc.getElementById('username_field').value = 'temp';"
494      "iframe_doc.getElementById('password_field').value = 'random';"
495      "iframe_doc.getElementById('submit_button').click()";
496
497  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
498  observer.Wait();
499  EXPECT_TRUE(observer.infobar_shown());
500}
501
502IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
503                       PromptForInputElementWithoutName) {
504  // Check that the prompt is shown for forms where input elements lack the
505  // "name" attribute but the "id" is present.
506  NavigateToFile("/password/password_form.html");
507
508  NavigationObserver observer(WebContents());
509  std::string fill_and_submit =
510      "document.getElementById('username_field_no_name').value = 'temp';"
511      "document.getElementById('password_field_no_name').value = 'random';"
512      "document.getElementById('input_submit_button_no_name').click()";
513  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
514  observer.Wait();
515  EXPECT_TRUE(observer.infobar_shown());
516}
517
518IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
519                       PromptForInputElementWithoutId) {
520  // Check that the prompt is shown for forms where input elements lack the
521  // "id" attribute but the "name" attribute is present.
522  NavigateToFile("/password/password_form.html");
523
524  NavigationObserver observer(WebContents());
525  std::string fill_and_submit =
526      "document.getElementsByName('username_field_no_id')[0].value = 'temp';"
527      "document.getElementsByName('password_field_no_id')[0].value = 'random';"
528      "document.getElementsByName('input_submit_button_no_id')[0].click()";
529  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
530  observer.Wait();
531  EXPECT_TRUE(observer.infobar_shown());
532}
533
534IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
535                       NoPromptForInputElementWithoutIdAndName) {
536  // Check that no prompt is shown for forms where the input fields lack both
537  // the "id" and the "name" attributes.
538  NavigateToFile("/password/password_form.html");
539
540  NavigationObserver observer(WebContents());
541  std::string fill_and_submit =
542      "var form = document.getElementById('testform_elements_no_id_no_name');"
543      "var username = form.children[0];"
544      "username.value = 'temp';"
545      "var password = form.children[1];"
546      "password.value = 'random';"
547      "form.children[2].click()";  // form.children[2] is the submit button.
548  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
549  observer.Wait();
550  EXPECT_FALSE(observer.infobar_shown());
551}
552
553IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, DeleteFrameBeforeSubmit) {
554  NavigateToFile("/password/multi_frames.html");
555
556  NavigationObserver observer(WebContents());
557  // Make sure we save some password info from an iframe and then destroy it.
558  std::string save_and_remove =
559      "var first_frame = document.getElementById('first_frame');"
560      "var frame_doc = first_frame.contentDocument;"
561      "frame_doc.getElementById('username_field').value = 'temp';"
562      "frame_doc.getElementById('password_field').value = 'random';"
563      "frame_doc.getElementById('input_submit_button').click();"
564      "first_frame.parentNode.removeChild(first_frame);";
565  // Submit from the main frame, but without navigating through the onsubmit
566  // handler.
567  std::string navigate_frame =
568      "document.getElementById('username_field').value = 'temp';"
569      "document.getElementById('password_field').value = 'random';"
570      "document.getElementById('input_submit_button').click();"
571      "window.location.href = 'done.html';";
572
573  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), save_and_remove));
574  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), navigate_frame));
575  observer.Wait();
576  // The only thing we check here is that there is no use-after-free reported.
577}
578