[Extensions Bindings] Account for listeners destroying the context

An event listener could destroy the context when it's notified (e.g.,
if it does window.close()). We need to account for this case. Check
after each listener is executed whether the context is still valid.

Add native bindings-specific unittests and a JS/Native bindings
browsertest.

Bug: 653596

Change-Id: I47e789800ca90935457781695d3e8c3de7aae317
Reviewed-on: https://chromium-review.googlesource.com/818528
Reviewed-by: Jeremy Roman <[email protected]>
Reviewed-by: Istiaque Ahmed <[email protected]>
Commit-Queue: Devlin <[email protected]>
Cr-Commit-Position: refs/heads/master@{#524466}
diff --git a/chrome/browser/extensions/extension_bindings_apitest.cc b/chrome/browser/extensions/extension_bindings_apitest.cc
index 218e0e3..0e045d86 100644
--- a/chrome/browser/extensions/extension_bindings_apitest.cc
+++ b/chrome/browser/extensions/extension_bindings_apitest.cc
@@ -14,6 +14,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/test/browser_test_utils.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/process_manager.h"
@@ -370,6 +371,58 @@
   ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
 
+IN_PROC_BROWSER_TEST_P(ExtensionBindingsApiTest,
+                       ExtensionListenersRemoveContext) {
+  const Extension* extension = LoadExtension(
+      test_data_dir_.AppendASCII("bindings/listeners_destroy_context"));
+  ASSERT_TRUE(extension);
+
+  ExtensionTestMessageListener listener("ready", true);
+
+  // Navigate to a web page with an iframe (the iframe is title1.html).
+  GURL main_frame_url = embedded_test_server()->GetURL("a.com", "/iframe.html");
+  ui_test_utils::NavigateToURL(browser(), main_frame_url);
+
+  content::WebContents* tab =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  content::RenderFrameHost* main_frame = tab->GetMainFrame();
+  content::RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
+  content::RenderFrameDeletedObserver subframe_deleted(subframe);
+
+  // Wait for the extension's content script to be ready.
+  ASSERT_TRUE(listener.WaitUntilSatisfied());
+
+  // It's actually critical to the test that these frames are in the same
+  // process, because otherwise a crash in the iframe wouldn't be detectable
+  // (since we rely on JS execution in the main frame to tell if the renderer
+  // crashed - see comment below).
+  content::RenderProcessHost* main_frame_process = main_frame->GetProcess();
+  EXPECT_EQ(main_frame_process, subframe->GetProcess());
+
+  ExtensionTestMessageListener failure_listener("failed", false);
+
+  // Tell the extension to register listeners that will remove the iframe, and
+  // trigger them.
+  listener.Reply("go!");
+
+  // The frame will be deleted.
+  subframe_deleted.WaitUntilDeleted();
+
+  // Unfortunately, we don't have a good way of checking if something crashed
+  // after the frame was removed. WebContents::IsCrashed() seems like it should
+  // work, but is insufficient. Instead, use JS execution as the source of
+  // true.
+  EXPECT_FALSE(tab->IsCrashed());
+  EXPECT_EQ(main_frame_url, main_frame->GetLastCommittedURL());
+  EXPECT_EQ(main_frame_process, main_frame->GetProcess());
+  bool renderer_valid = false;
+  EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+      main_frame, "domAutomationController.send(true);", &renderer_valid));
+  EXPECT_TRUE(renderer_valid);
+  EXPECT_FALSE(failure_listener.was_satisfied());
+}
+
 // Run core bindings API tests with both native and JS-based bindings. This
 // ensures we have some minimum level of coverage while in the experimental
 // phase, when native bindings may be enabled on trunk but not at 100% stable.