Support for Proxies in Frame-based MimeHandlerView

The API for attaching inner and outer WebContentses only supports a
RenderFrameHost which is same-process and origin with its parent. This
is due to the fact that all GuestViews start off with a blank <iframe>
which is later swapped out with a proxy during attaching process.

For MimeHandlerViewGuest it is not guaranteed that the starting frame
in an <embed> or <object> is in 'about:blank'. This CL circumvents the
problem by first navigating a plugin frame (on the browser side) to
'about:blank' and then proceeding with the attaching process.

 Bug: 659750

Change-Id: I57d41a38e957b22c4ba3c29cb11122115373946f
Reviewed-on: https://chromium-review.googlesource.com/c/1240635
Commit-Queue: Ehsan Karamad <ekaramad@chromium.org>
Reviewed-by: Ken Buchanan <kenrb@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: James MacLean <wjmaclean@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599986}
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index f86ca0deb..40a1024 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -456,6 +456,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/extension_util.h"
+#include "extensions/browser/guest_view/extensions_guest_view_message_filter.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
 #include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
 #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
@@ -4027,6 +4028,14 @@
         throttles.push_back(std::move(bookmark_app_throttle));
     }
   }
+  if (base::FeatureList::IsEnabled(
+          features::kMimeHandlerViewInCrossProcessFrame)) {
+    auto plugin_frame_attach_throttle =
+        extensions::ExtensionsGuestViewMessageFilter::MaybeCreateThrottle(
+            handle);
+    if (plugin_frame_attach_throttle)
+      throttles.push_back(std::move(plugin_frame_attach_throttle));
+  }
 #endif
 
 #if defined(OS_CHROMEOS)
diff --git a/chrome/test/data/extensions/api_test/mime_handler_view/test_object_with_frame.html b/chrome/test/data/extensions/api_test/mime_handler_view/test_object_with_frame.html
new file mode 100644
index 0000000..51f7147
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/mime_handler_view/test_object_with_frame.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<title>Plugin starting with TEXT/HTML</title>
+<object data="about:blank" type="text/html"></object>
+<script>
+  var object = document.querySelector("object");
+  window.addEventListener("load", init);
+
+  function init() {
+    let href = window.location.href;
+    const token  = "?test_data=";
+    let query_index = href.indexOf(token);
+    let test_data = null;
+    if (query_index !== -1) {
+      test_data = href.substr(query_index + token.length).split(",");
+    }
+
+    if (test_data) {
+      let test_command =
+          `${test_data[0]}('${test_data.splice(1).join("','")}');`;
+      window.eval(test_command);
+    }
+  }
+
+  // Returns true if |w| is cross-origin with |window|.
+  function is_cross_origin(w) {
+    let result = false;
+    try {
+      w.location.origin;
+    } catch(e) {
+      result = true;
+    }
+    return result;
+  }
+
+  // Navigates <object> to |cross_origin_url|. Then sets the object type to
+  // "text/csv" and triggers a MimeHandlerView creation. The test succeeds if
+  // the MHV extension is loaded.
+  function test_cross_origin_frame(cross_origin_url, csv_url) {
+    object.data = cross_origin_url;
+    object.onload = () => {
+      if (!is_cross_origin(window[0]))
+        return;
+      object.type = "text/csv";
+      object.data = csv_url;
+    };
+  }
+
+  // Navigates the <object> to |cross_origin_url| and after a very short timeout
+  // sets the object type and source to "text/csv". The test succeeds if the
+  // MHV extension is loaded.
+  function test_navigation_race_embedder(cross_origin_url, csv_url) {
+    object.data = cross_origin_url;
+    window.setTimeout(() => {
+      object.type = "text/csv";
+      object.data = csv_url;
+    }, 0);
+  }
+
+  // Navigates the object to some cross-origin content which then navigates it
+  // self to another location passed through the query. After the load event,
+  // the <object> is set to render a MimeHandlerView. This would lead to a
+  // navigation race: the browser will try to navigate the content frame to
+  // 'about:blank' while the cross-process renderer triggers its own navigation.
+  // The test succeeds when the MHV extension is loaded.
+  function test_navigation_race_cross_origin(cross_origin_url,
+                                             other_cross_origin_url,
+                                             csv_url) {
+    object.data = `${cross_origin_url}?next=${other_cross_origin_url}`;
+    object.onload = () => {
+      if (!is_cross_origin(window[0]))
+        return;
+      if (object.data === csv_url)
+        return;
+      object.type = "text/csv";
+      object.data = csv_url;
+    };
+  }
+</script>
diff --git a/chrome/test/data/extensions/api_test/mime_handler_view/test_page.html b/chrome/test/data/extensions/api_test/mime_handler_view/test_page.html
new file mode 100644
index 0000000..366b3dc
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/mime_handler_view/test_page.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Test page used for some MimeHandlerView tests</title>
+<h1> Test Page</h1>
+
+<script>
+  function maybe_navigate(query) {
+    if (!query.startsWith("?next="))
+      return false;
+    window.location.href = query.substr(query_index + token.length);
+    return true;
+  }
+
+  function should_add_beforeunload(query) {
+    if (query !== "?beforeunload")
+      return false;
+    window.addEventListener("beforeunload", (e) => {
+        e.returnValue = "foo";
+        return e;
+    });
+  }
+
+  window.addEventListener("load", () => {
+    let search = window.location.search;
+    if (maybe_navigate(search))
+      return;
+    should_add_beforeunload(search);
+  });
+</script>
diff --git a/extensions/browser/guest_view/extensions_guest_view_message_filter.cc b/extensions/browser/guest_view/extensions_guest_view_message_filter.cc
index 8a3ca1a..7462e98 100644
--- a/extensions/browser/guest_view/extensions_guest_view_message_filter.cc
+++ b/extensions/browser/guest_view/extensions_guest_view_message_filter.cc
@@ -6,16 +6,21 @@
 
 #include "base/guid.h"
 #include "base/macros.h"
+#include "base/no_destructor.h"
 #include "base/task/post_task.h"
+#include "base/time/time.h"
 #include "components/guest_view/browser/guest_view_base.h"
 #include "components/guest_view/browser/guest_view_manager.h"
 #include "components/guest_view/browser/guest_view_manager_delegate.h"
 #include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/stream_info.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/mime_handler_view_mode.h"
 #include "extensions/browser/api/extensions_api_client.h"
 #include "extensions/browser/bad_message.h"
@@ -30,10 +35,14 @@
 #include "extensions/common/manifest_handlers/mime_types_handler.h"
 #include "ipc/ipc_message.h"
 #include "ipc/ipc_message_macros.h"
+#include "url/gurl.h"
 
 using content::BrowserContext;
 using content::BrowserThread;
+using content::NavigationHandle;
+using content::NavigationThrottle;
 using content::RenderFrameHost;
+using content::SiteInstance;
 using content::WebContents;
 using guest_view::GuestViewManager;
 using guest_view::GuestViewManagerDelegate;
@@ -43,11 +52,26 @@
 
 namespace {
 
+// Cancels the given navigation handle unconditionally.
+class CancelAndIgnoreNavigationForPluginFrameThrottle
+    : public NavigationThrottle {
+ public:
+  explicit CancelAndIgnoreNavigationForPluginFrameThrottle(
+      NavigationHandle* handle)
+      : NavigationThrottle(handle) {}
+  ~CancelAndIgnoreNavigationForPluginFrameThrottle() override {}
+
+  const char* GetNameForLogging() override {
+    return "CancelAndIgnoreNavigationForPluginFrameThrottle";
+  }
+  ThrottleCheckResult WillStartRequest() override { return CANCEL_AND_IGNORE; }
+};
+
 // TODO(ekaramad): Remove this once MimeHandlerViewGuest has fully migrated to
 // using cross-process-frames.
 // Returns true if |child_routing_id| corresponds to a frame which is a direct
 // child of |parent_rfh|.
-bool AreRoutingIDsConsistent(content::RenderFrameHost* parent_rfh,
+bool AreRoutingIDsConsistent(RenderFrameHost* parent_rfh,
                              int32_t child_routing_id) {
   const bool uses_cross_process_frame =
       content::MimeHandlerViewMode::UsesCrossProcessFrame();
@@ -61,9 +85,8 @@
     // The |child_routing_id| is the routing ID of either a RenderFrame or a
     // proxy in the |parent_rfh|. Therefore, to get the associated RFH we need
     // to go through the FTN first.
-    int32_t child_ftn_id =
-        content::RenderFrameHost::GetFrameTreeNodeIdForRoutingId(
-            parent_rfh->GetProcess()->GetID(), child_routing_id);
+    int32_t child_ftn_id = RenderFrameHost::GetFrameTreeNodeIdForRoutingId(
+        parent_rfh->GetProcess()->GetID(), child_routing_id);
     // The |child_rfh| is not really used; it is retrieved to verify whether or
     // not what the renderer process says makes any sense.
     auto* child_rfh = content::WebContents::FromRenderFrameHost(parent_rfh)
@@ -74,10 +97,200 @@
   return !should_shutdown_process;
 }
 
+using ProcessIdToFilterMap =
+    base::flat_map<int32_t, ExtensionsGuestViewMessageFilter*>;
+ProcessIdToFilterMap* GetProcessIdToFilterMap() {
+  static base::NoDestructor<ProcessIdToFilterMap> instance;
+  return instance.get();
+}
+
 }  // namespace
+
 const uint32_t ExtensionsGuestViewMessageFilter::kFilteredMessageClasses[] = {
     GuestViewMsgStart, ExtensionsGuestViewMsgStart};
 
+// Helper class which navigates a given FrameTreeNode to "about:blank". This is
+// used for scenarios where the plugin element's content frame has a different
+// SiteInstance from its parent frame, or, the frame's origin is not
+// "about:blank". Since this class triggers a navigation, all the document
+// unload events will be dispatched and handled. During the lifetime of this
+// helper class, all other navigations for the corresponding FrameTreeNode will
+// be throttled and ignored.
+class ExtensionsGuestViewMessageFilter::FrameNavigationHelper
+    : public content::WebContentsObserver {
+ public:
+  FrameNavigationHelper(RenderFrameHost* plugin_rfh,
+                        int32_t guest_instance_id,
+                        int32_t element_instance_id,
+                        base::DictionaryValue* attach_params,
+                        bool is_full_page_plugin,
+                        ExtensionsGuestViewMessageFilter* filter);
+  ~FrameNavigationHelper() override;
+
+  void FrameDeleted(RenderFrameHost* render_frame_host) override;
+  void DidFinishNavigation(NavigationHandle* handle) override;
+  void BeforeUnloadFired(bool proceed,
+                         const base::TimeTicks& proceed_time) override;
+
+  // During attaching, we should ignore any navigation which is not a navigation
+  // to "about:blank" from the parent frame's SiteInstance.
+  bool ShouldCancelAndIgnore(NavigationHandle* handle);
+
+  int32_t guest_instance_id() const { return guest_instance_id_; }
+  const base::DictionaryValue& attach_params() const { return attach_params_; }
+  bool is_full_page_plugin() const { return is_full_page_plugin_; }
+  SiteInstance* parent_site_instance() const {
+    return parent_site_instance_.get();
+  }
+
+ private:
+  void NavigateToAboutBlank();
+
+  int32_t frame_tree_node_id_;
+  const int32_t guest_instance_id_;
+  const int32_t element_instance_id_;
+  base::DictionaryValue attach_params_;
+  const bool is_full_page_plugin_;
+  ExtensionsGuestViewMessageFilter* const filter_;
+  scoped_refptr<SiteInstance> parent_site_instance_;
+
+  DISALLOW_COPY_AND_ASSIGN(FrameNavigationHelper);
+};
+
+ExtensionsGuestViewMessageFilter::FrameNavigationHelper::FrameNavigationHelper(
+    RenderFrameHost* plugin_rfh,
+    int32_t guest_instance_id,
+    int32_t element_instance_id,
+    base::DictionaryValue* attach_params,
+    bool is_full_page_plugin,
+    ExtensionsGuestViewMessageFilter* filter)
+    : content::WebContentsObserver(
+          content::WebContents::FromRenderFrameHost(plugin_rfh)),
+      frame_tree_node_id_(plugin_rfh->GetFrameTreeNodeId()),
+      guest_instance_id_(guest_instance_id),
+      element_instance_id_(element_instance_id),
+      is_full_page_plugin_(is_full_page_plugin),
+      filter_(filter),
+      parent_site_instance_(plugin_rfh->GetParent()->GetSiteInstance()) {
+  DCHECK(filter->GetOrCreateGuestViewManager()->GetGuestByInstanceIDSafely(
+      guest_instance_id, plugin_rfh->GetParent()->GetProcess()->GetID()));
+  attach_params_.Swap(attach_params);
+  NavigateToAboutBlank();
+}
+
+ExtensionsGuestViewMessageFilter::FrameNavigationHelper::
+    ~FrameNavigationHelper() {}
+
+void ExtensionsGuestViewMessageFilter::FrameNavigationHelper::FrameDeleted(
+    RenderFrameHost* render_frame_host) {
+  if (render_frame_host->GetFrameTreeNodeId() != frame_tree_node_id_)
+    return;
+  // It is possible that the plugin frame is deleted before a NavigationHandle
+  // is created; one such case is to immediately delete the plugin element right
+  // after MimeHandlerViewFrameContainer requests to create the
+  // MimeHandlerViewGuest on the browser side.
+  filter_->ResumeAttachOrDestroy(element_instance_id_,
+                                 nullptr /* plugin_rfh */);
+}
+
+void ExtensionsGuestViewMessageFilter::FrameNavigationHelper::
+    DidFinishNavigation(NavigationHandle* handle) {
+  if (handle->GetFrameTreeNodeId() != frame_tree_node_id_)
+    return;
+
+  if (!handle->GetURL().IsAboutBlank()) {
+    // Another navigation has committed (it started before our navigation). The
+    // intended navigation to 'about:blank' should arrive later.
+    return;
+  }
+
+  filter_->ResumeAttachOrDestroy(element_instance_id_,
+                                 handle->GetRenderFrameHost());
+}
+
+void ExtensionsGuestViewMessageFilter::FrameNavigationHelper::BeforeUnloadFired(
+    bool proceed,
+    const base::TimeTicks& proceed_time) {
+  if (proceed)
+    return;
+  // Navigating to "about:blank" would involve unloading the current
+  // document/frame if any, and naturally "beforeunload" is a possibility that
+  // should be addressed. If the user chooses to stay on the old page after a
+  // beforeunload dialog, do not create the plugin frame and clean up the
+  // associated MimeHandlerView* classes.
+  // TODO(ekaramad): This will lead to a change in the behavior of plugin
+  // elements in Chrome for scenarios where the plugin element has a frame and
+  // then transitions into a MimeHandlerView. In the BrowserPlugin-based version
+  // of MimeHandlerView, loading a MimeHandlerView will not clear the existing
+  // frames inside the HTMLPlugInElement. This leads to some bugs including not
+  // firing either "unload" or "beforeunload" (naturally so, given that the
+  // frame is not detached. See https://crbug.com/776510 for more context). We
+  // might need to revisit the logic here if current HTMLPlugInElement bugs with
+  // respect to PluginView and FrameView transitions are fixed. There won't be a
+  // plugin frame as desired and the guest view will eventually die.
+  filter_->ResumeAttachOrDestroy(element_instance_id_,
+                                 nullptr /* plugin_rfh */);
+}
+
+bool ExtensionsGuestViewMessageFilter::FrameNavigationHelper::
+    ShouldCancelAndIgnore(NavigationHandle* handle) {
+  if (handle->GetFrameTreeNodeId() != frame_tree_node_id_)
+    return false;
+
+  if (handle->GetRenderFrameHost()->GetSiteInstance() ==
+          parent_site_instance_ &&
+      handle->GetURL().IsAboutBlank()) {
+    // This is either the navigation which was triggered by this class, or a
+    // freebie. As long as such a navigation successfully commits, we are on the
+    // right track for attaching WebContentses.
+    return false;
+  }
+  return true;
+}
+
+void ExtensionsGuestViewMessageFilter::FrameNavigationHelper::
+    NavigateToAboutBlank() {
+  // Immediately start a navigation to "about:blank".
+  GURL about_blank(url::kAboutBlankURL);
+  content::NavigationController::LoadURLParams params(about_blank);
+  params.frame_tree_node_id = frame_tree_node_id_;
+  // The goal is to have a plugin frame which is same-origin with parent, i.e.,
+  // 'about:blank' and share the same SiteInstance.
+  params.source_site_instance = parent_site_instance_;
+  // The renderer (parent of the plugin frame) tries to load a MimeHandlerView
+  // and therefore this navigation should be treated as renderer initiated.
+  params.is_renderer_initiated = true;
+  web_contents()->GetController().LoadURLWithParams(params);
+}
+
+// static
+std::unique_ptr<NavigationThrottle>
+ExtensionsGuestViewMessageFilter::MaybeCreateThrottle(
+    NavigationHandle* handle) {
+  DCHECK(content::MimeHandlerViewMode::UsesCrossProcessFrame());
+  if (!handle->GetParentFrame()) {
+    // A plugin element cannot be the FrameOwner to a main frame.
+    return nullptr;
+  }
+  int32_t parent_process_id = handle->GetParentFrame()->GetProcess()->GetID();
+  auto& map = *GetProcessIdToFilterMap();
+  if (!map.count(parent_process_id)) {
+    // This happens if the RenderProcessHost has not been initialized yet.
+    return nullptr;
+  }
+
+  for (auto& pair : map[parent_process_id]->frame_navigation_helpers_) {
+    if (!pair.second->ShouldCancelAndIgnore(handle))
+      continue;
+    // Any navigation of the corresponding FrameTreeNode which is not to
+    // "about:blank" or is not initiated by parent SiteInstance should be
+    // ignored.
+    return std::make_unique<CancelAndIgnoreNavigationForPluginFrameThrottle>(
+        handle);
+  }
+  return nullptr;
+}
+
 ExtensionsGuestViewMessageFilter::ExtensionsGuestViewMessageFilter(
     int render_process_id,
     BrowserContext* context)
@@ -85,10 +298,13 @@
                              arraysize(kFilteredMessageClasses),
                              render_process_id,
                              context),
-      content::BrowserAssociatedInterface<mojom::GuestView>(this, this) {}
+      content::BrowserAssociatedInterface<mojom::GuestView>(this, this) {
+  GetProcessIdToFilterMap()->insert_or_assign(render_process_id_, this);
+}
 
 ExtensionsGuestViewMessageFilter::~ExtensionsGuestViewMessageFilter() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  GetProcessIdToFilterMap()->erase(render_process_id_);
 }
 
 void ExtensionsGuestViewMessageFilter::OverrideThreadForMessage(
@@ -237,8 +453,7 @@
 
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(
-          content::RenderFrameHost::FromID(render_process_id_,
-                                           render_frame_id));
+          RenderFrameHost::FromID(render_process_id_, render_frame_id));
   if (!web_contents)
     return;
 
@@ -302,18 +517,46 @@
   auto uses_cross_process_frame =
       content::MimeHandlerViewMode::UsesCrossProcessFrame();
   if (uses_cross_process_frame) {
-    auto* plugin_rfh = content::RenderFrameHost::FromID(
-        embedder_render_process_id, plugin_frame_routing_id);
+    auto* plugin_rfh = RenderFrameHost::FromID(embedder_render_process_id,
+                                               plugin_frame_routing_id);
     if (!plugin_rfh) {
-      // TODO(ekaramad): This happens when the plugin element contains a remote
-      // frame. Introduce this edge case to content/ layer.
+      // The plugin element has a proxy instead.
+      plugin_rfh = RenderFrameHost::FromPlaceholderId(
+          embedder_render_process_id, plugin_frame_routing_id);
+    }
+    if (!plugin_rfh) {
+      // This should only happen if the original plugin frame was cross-process
+      // and a concurrent navigation in its process won the race and ended up
+      // destroying the proxy whose routing ID was sent here by the
+      // MimeHandlerViewFrameContainer. We should ask the embedder to retry
+      // creating the guest.
+      guest_view->GetEmbedderFrame()->Send(
+          new ExtensionsGuestViewMsg_RetryCreatingMimeHandlerViewGuest(
+              element_instance_id));
+      guest_view->Destroy(true);
       return;
     }
+
+    if (plugin_rfh->GetSiteInstance() !=
+            plugin_rfh->GetParent()->GetSiteInstance() ||
+        !plugin_rfh->GetLastCommittedURL().IsAboutBlank()) {
+      // The current API for attaching guests requires the frame in outer
+      // WebContents to be same-origin with parent. Also, to respect before
+      // unload handlers in the current plugin frame's document we should first
+      // navigate the plugin frame to "about:blank".
+      frame_navigation_helpers_[element_instance_id] =
+          std::make_unique<FrameNavigationHelper>(
+              plugin_rfh, guest_view->guest_instance_id(), element_instance_id,
+              &attach_params, is_full_page_plugin, this);
+      return;
+    }
+
     AttachToEmbedderFrame(plugin_frame_routing_id, element_instance_id,
                           guest_instance_id, attach_params,
                           is_full_page_plugin);
     return;
   }
+
   auto* manager = GuestViewManager::FromBrowserContext(browser_context_);
   CHECK(manager);
   manager->AttachGuest(embedder_render_process_id, element_instance_id,
@@ -322,4 +565,26 @@
       element_instance_id));
 }
 
+void ExtensionsGuestViewMessageFilter::ResumeAttachOrDestroy(
+    int32_t element_instance_id,
+    RenderFrameHost* plugin_rfh) {
+  auto helper = std::move(frame_navigation_helpers_[element_instance_id]);
+  frame_navigation_helpers_.erase(element_instance_id);
+  if (plugin_rfh) {
+    DCHECK(plugin_rfh->GetLastCommittedURL().IsAboutBlank());
+    AttachToEmbedderFrame(plugin_rfh->GetRoutingID(), element_instance_id,
+                          helper->guest_instance_id(), helper->attach_params(),
+                          helper->is_full_page_plugin());
+  } else if (auto* guest_view =
+                 MimeHandlerViewGuest::From(
+                     helper->parent_site_instance()->GetProcess()->GetID(),
+                     helper->guest_instance_id())
+                     ->As<MimeHandlerViewGuest>()) {
+    guest_view->GetEmbedderFrame()->Send(
+        new ExtensionsGuestViewMsg_DestroyFrameContainer(element_instance_id));
+    guest_view->Destroy(true);
+  }
+  frame_navigation_helpers_.erase(element_instance_id);
+}
+
 }  // namespace extensions
diff --git a/extensions/browser/guest_view/extensions_guest_view_message_filter.h b/extensions/browser/guest_view/extensions_guest_view_message_filter.h
index 04ec3a6..75faeb8 100644
--- a/extensions/browser/guest_view/extensions_guest_view_message_filter.h
+++ b/extensions/browser/guest_view/extensions_guest_view_message_filter.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <map>
 #include <string>
 
 #include "base/macros.h"
@@ -19,6 +20,9 @@
 
 namespace content {
 class BrowserContext;
+class NavigationHandle;
+class NavigationThrottle;
+class RenderFrameHost;
 class WebContents;
 }
 
@@ -31,6 +35,7 @@
 }
 
 namespace extensions {
+class MimeHandlerViewGuest;
 
 // This class filters out incoming extensions GuestView-specific IPC messages
 // from thw renderer process. It is created on the UI thread. Messages may be
@@ -40,12 +45,20 @@
       public content::BrowserAssociatedInterface<mojom::GuestView>,
       public mojom::GuestView {
  public:
+  // During attaching guest to embedder WebContentses the corresponding plugin
+  // frame might be navigated to "about:blank" first. During this time all
+  // navigations for the same FrameTreeNode must be canceled.
+  static std::unique_ptr<content::NavigationThrottle> MaybeCreateThrottle(
+      content::NavigationHandle* navigation_handle);
+
   ExtensionsGuestViewMessageFilter(int render_process_id,
                                    content::BrowserContext* context);
 
  private:
+  class FrameNavigationHelper;
   friend class content::BrowserThread;
   friend class base::DeleteHelper<ExtensionsGuestViewMessageFilter>;
+  friend class ExtensionsGuestViewMessageFilter::FrameNavigationHelper;
 
   ~ExtensionsGuestViewMessageFilter() override;
 
@@ -104,6 +117,16 @@
       bool is_full_page_plugin,
       content::WebContents* web_contents);
 
+  // Called by a FrameNavigationHelper on UI thread to notify the message filter
+  // whether or not it should proceed with attaching a guest. if |plugin_rfh| is
+  // nullptr, the MimeHandlerViewGuest associated with |element_instance_id|
+  // will be destroyed and deleted.
+  void ResumeAttachOrDestroy(int32_t element_instance_id,
+                             content::RenderFrameHost* plugin_rfh);
+
+  std::map<int32_t, std::unique_ptr<FrameNavigationHelper>>
+      frame_navigation_helpers_;
+
   static const uint32_t kFilteredMessageClasses[];
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionsGuestViewMessageFilter);
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc
index feb8942..b1ca71f 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc
+++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc
@@ -9,6 +9,8 @@
 #include "base/macros.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -16,6 +18,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/app_modal/javascript_app_modal_dialog.h"
+#include "components/app_modal/native_app_modal_dialog.h"
 #include "components/guest_view/browser/test_guest_view_manager.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/content_features.h"
@@ -29,6 +32,7 @@
 #include "extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/test/result_catcher.h"
+#include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "services/network/public/cpp/features.h"
@@ -60,13 +64,14 @@
         test_data_dir_.AppendASCII("mime_handler_view"));
     embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
         &MimeHandlerViewTest::MonitorRequest, base::Unretained(this)));
+    host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(StartEmbeddedTestServer());
   }
 
   // TODO(paulmeyer): This function is implemented over and over by the
   // different GuestView test classes. It really needs to be refactored out to
   // some kind of GuestViewTest base class.
-  TestGuestViewManager* GetGuestViewManager() {
+  TestGuestViewManager* GetGuestViewManager() const {
     TestGuestViewManager* manager = static_cast<TestGuestViewManager*>(
         TestGuestViewManager::FromBrowserContext(browser()->profile()));
     // TestGuestViewManager::WaitForSingleGuestCreated can and will get called
@@ -84,6 +89,12 @@
     return manager;
   }
 
+  MimeHandlerViewGuest* GetLastGuestView() const {
+    return MimeHandlerViewGuest::FromWebContents(
+               GetGuestViewManager()->GetLastGuestCreated())
+        ->As<MimeHandlerViewGuest>();
+  }
+
   const extensions::Extension* LoadTestExtension() {
     const extensions::Extension* extension =
         LoadExtension(test_data_dir_.AppendASCII("mime_handler_view"));
@@ -141,14 +152,19 @@
 
   void SetUpCommandLine(base::CommandLine* cl) override {
     MimeHandlerViewTest::SetUpCommandLine(cl);
-    if (GetParam()) {
+    is_cross_process_mode_ = GetParam();
+    if (is_cross_process_mode_) {
       scoped_feature_list_.InitAndEnableFeature(
           features::kMimeHandlerViewInCrossProcessFrame);
     }
   }
 
+  bool is_cross_process_mode() const { return is_cross_process_mode_; }
+
  private:
+  bool is_cross_process_mode_ = false;
   base::test::ScopedFeatureList scoped_feature_list_;
+
   DISALLOW_COPY_AND_ASSIGN(MimeHandlerViewCrossProcessTest);
 };
 
@@ -165,6 +181,138 @@
   EXPECT_EQ(1U, gv_manager->num_guests_created());
 }
 
+// This test start with an <object> that has a content frame. Then the content
+// frame (plugin frame) is navigated to a cross-origin target page. After the
+// navigation is completed, the <object> is set to render MimeHandlerView by
+// setting its |data| and |type| attributes accordingly.
+IN_PROC_BROWSER_TEST_P(MimeHandlerViewCrossProcessTest,
+                       EmbedWithInitialCrossOriginFrame) {
+  const std::string kTestName = "test_cross_origin_frame";
+  std::string cross_origin_url =
+      embedded_test_server()->GetURL("b.com", "/test_page.html").spec();
+  auto test_url = embedded_test_server()->GetURL(
+      "a.com",
+      base::StringPrintf("/test_object_with_frame.html?test_data=%s,%s,%s",
+                         kTestName.c_str(), cross_origin_url.c_str(),
+                         "testEmbedded.csv"));
+  RunTestWithUrl(test_url);
+}
+
+// This test verifies that navigations on the plugin frame before setting it to
+// load MimeHandlerView do not race with the creation of the guest. The test
+// loads a page with an <object> which is first navigated to some cross-origin
+// domain and then immediately after load, the page triggers a navigation of its
+// own to another cross-origin domain. Meanwhile the embedder sets the <object>
+// to load a MimeHandlerView. The test passes if MHV loads. This is to catch the
+// potential race between the cross-origin renderer initiated navigation and
+// the navigation to "about:blank" started from the browser.
+IN_PROC_BROWSER_TEST_P(MimeHandlerViewCrossProcessTest,
+                       NavigationRaceFromEmbedder) {
+  if (!is_cross_process_mode()) {
+    // Note that this test would pass trivially with BrowserPlugin-based guests
+    // because loading a plugin is quite independent from navigating a plugin.
+    // They do actually coexist at times (for more context see
+    // https://crbug.com/776510).
+    return;
+  }
+
+  const std::string kTestName = "test_navigation_race_embedder";
+  auto cross_origin_url =
+      embedded_test_server()->GetURL("b.com", "/test_page.html").spec();
+  auto test_url = embedded_test_server()->GetURL(
+      "a.com",
+      base::StringPrintf("/test_object_with_frame.html?test_data=%s,%s,%s",
+                         kTestName.c_str(), cross_origin_url.c_str(),
+                         "testEmbedded.csv"));
+  RunTestWithUrl(test_url);
+}
+
+// TODO(ekaramad): Without proper handling of navigation to 'about:blank', this
+// test would be flaky. Use TestNavigationManager class and possibly break the
+// test into more sub-tests for various scenarios (https://crbug.com/659750).
+// This test verifies that (almost) concurrent navigations in a cross-process
+// frame inside an <embed> which is transitioning to a MimeHandlerView will
+// not block creation of MimeHandlerView. The test will load some cross-origin
+// content in <object> which right after loading will navigate it self to some
+// other cross-origin content. On the embedder side, when the first page loads,
+// the <object> loads some text/csv content to create a MimeHandlerViewGuest.
+// The test passes if MHV loads.
+IN_PROC_BROWSER_TEST_P(MimeHandlerViewCrossProcessTest,
+                       NavigationRaceFromCrossProcessRenderer) {
+  if (!is_cross_process_mode()) {
+    // Note that this test would pass trivially with BrowserPlugin-based guests
+    // because loading a plugin is quite independent from navigating a plugin.
+    // They do actually coexist at times (for more context see
+    // https://crbug.com/776510).
+    return;
+  }
+
+  const std::string kTestName = "test_navigation_race_cross_origin";
+  auto cross_origin_url =
+      embedded_test_server()->GetURL("b.com", "/test_page.html").spec();
+  auto other_cross_origin_url =
+      embedded_test_server()->GetURL("c.com", "/test_page.html").spec();
+  auto test_url = embedded_test_server()->GetURL(
+      "a.com",
+      base::StringPrintf("/test_object_with_frame.html?test_data=%s,%s,%s,%s",
+                         kTestName.c_str(), cross_origin_url.c_str(),
+                         other_cross_origin_url.c_str(), "testEmbedded.csv"));
+  RunTestWithUrl(test_url);
+}
+
+// TODO(ekaramad): Somehow canceling a first dialog in a setup similar to the
+// test below pops up another dialog. This is likely due to the navigation to
+// about:blank from both the browser side and the embedder side in the method
+// HTMLPlugInElement::RequestObjectInternal. Find out the issue and add another
+// test here where the dialog is dismissed and the guest not created.
+// (https://crbug.com/659750).
+// This test verifies that transitioning a plugin element from text/html to
+// application/pdf respects 'beforeunload'. The test specifically checks that
+// 'beforeunload' dialog is shown to the user and if the user decides to
+// proceed with the transition, MimeHandlerViewGuest is created.
+IN_PROC_BROWSER_TEST_P(MimeHandlerViewCrossProcessTest,
+                       EmbedWithInitialFrameAcceptBeforeUnloadDialog) {
+  if (!is_cross_process_mode()) {
+    // BrowserPlugin-based MimeHandlerView does not care for 'beforeunload' and
+    // the guest will always be created. This test would time out due to frame
+    // never unloading.
+    return;
+  }
+  // Use the testing subclass of MimeHandlerViewGuest.
+  GetGuestViewManager()->RegisterTestGuestViewType<MimeHandlerViewGuest>(
+      base::BindRepeating(&TestMimeHandlerViewGuest::Create));
+  const extensions::Extension* extension = LoadTestExtension();
+  ASSERT_TRUE(extension);
+  ui_test_utils::NavigateToURL(
+      browser(),
+      embedded_test_server()->GetURL("a.com", "/test_object_with_frame.html"));
+  auto* main_frame =
+      browser()->tab_strip_model()->GetWebContentsAt(0)->GetMainFrame();
+  auto url_with_beforeunload =
+      embedded_test_server()->GetURL("b.com", "/test_page.html?beforeunload");
+  bool result = false;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+      main_frame,
+      base::StringPrintf(
+          "object.data = '%s';"
+          " object.onload = () => window.domAutomationController.send(true);",
+          url_with_beforeunload.spec().c_str()),
+      &result));
+  ASSERT_TRUE(result);
+  // Give user gesture to the frame, set the <object> to text/csv resource and
+  // handle the "beforeunload" dialog.
+  content::PrepContentsForBeforeUnloadTest(
+      browser()->tab_strip_model()->GetWebContentsAt(0));
+  ASSERT_TRUE(content::ExecuteScript(main_frame,
+                                     "object.data = './testEmbedded.csv';"
+                                     "object.type = 'text/csv';"));
+  app_modal::JavaScriptAppModalDialog* alert =
+      ui_test_utils::WaitForAppModalDialog();
+  ASSERT_TRUE(alert->is_before_unload_dialog());
+  alert->native_dialog()->AcceptAppModalDialog();
+
+  EXPECT_TRUE(GetGuestViewManager()->WaitForSingleGuestCreated());
+}
 // The following tests will eventually converted into a parametric version which
 // will run on both BrowserPlugin-based and cross-process-frame-based
 // MimeHandlerView (https://crbug.com/659750).
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc
index b7a9609..b74700bb 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc
+++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc
@@ -102,6 +102,13 @@
       embedder_widget_routing_id_(MSG_ROUTING_NONE) {}
 
 MimeHandlerViewGuest::~MimeHandlerViewGuest() {
+  // Before attaching is complete, the instance ID is not valid.
+  if (element_instance_id() != guest_view::kInstanceIDNone) {
+    if (auto* embedder_frame = GetEmbedderFrame()) {
+      embedder_frame->Send(new ExtensionsGuestViewMsg_DestroyFrameContainer(
+          element_instance_id()));
+    }
+  }
 }
 
 bool MimeHandlerViewGuest::CanUseCrossProcessFrames() {
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h
index ebca89a..cfa759b 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h
+++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h
@@ -94,6 +94,8 @@
   void SetBeforeUnloadController(
       mime_handler::BeforeUnloadControlPtrInfo pending_before_unload_control);
 
+  content::RenderFrameHost* GetEmbedderFrame() const;
+
  protected:
   explicit MimeHandlerViewGuest(content::WebContents* owner_web_contents);
   ~MimeHandlerViewGuest() override;
@@ -162,8 +164,6 @@
   void FuseBeforeUnloadControl(
       mime_handler::BeforeUnloadControlRequest request);
 
-  content::RenderFrameHost* GetEmbedderFrame() const;
-
   std::unique_ptr<MimeHandlerViewGuestDelegate> delegate_;
   std::unique_ptr<StreamContainer> stream_;
 
diff --git a/extensions/common/guest_view/extensions_guest_view_messages.h b/extensions/common/guest_view/extensions_guest_view_messages.h
index 14e2d42..0d34a7e 100644
--- a/extensions/common/guest_view/extensions_guest_view_messages.h
+++ b/extensions/common/guest_view/extensions_guest_view_messages.h
@@ -25,6 +25,16 @@
 IPC_MESSAGE_CONTROL1(ExtensionsGuestViewMsg_MimeHandlerViewGuestOnLoadCompleted,
                      int /* element_instance_id */)
 
+// Notifies the embedder to the destroy the MimeHandlerViewFrameContainer
+// associated with |element_instance_id|.
+IPC_MESSAGE_CONTROL1(ExtensionsGuestViewMsg_DestroyFrameContainer,
+                     int /* element_instance_id */)
+
+// Notifies the embedder that the current guest creation has failed and it
+// should retry creating a MimeHandlerViewGuest.
+IPC_MESSAGE_CONTROL1(ExtensionsGuestViewMsg_RetryCreatingMimeHandlerViewGuest,
+                     int /* element_instance_id */)
+
 // Messages sent from the renderer to the browser.
 
 // Queries whether the RenderView of the provided |routing_id| is allowed to
diff --git a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.cc b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.cc
index 750619a..337a306 100644
--- a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.cc
+++ b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.cc
@@ -176,9 +176,9 @@
     const content::WebPluginInfo& info,
     const std::string& mime_type,
     const GURL& original_url)
-    : plugin_path_(info.path.MaybeAsASCII()),
+    : original_url_(original_url),
+      plugin_path_(info.path.MaybeAsASCII()),
       mime_type_(mime_type),
-      original_url_(original_url),
       embedder_render_frame_routing_id_(embedder_render_frame->GetRoutingID()),
       before_unload_control_binding_(this),
       weak_factory_(this) {
@@ -219,12 +219,22 @@
   base::PickleIterator iter(message);
   bool success = iter.ReadInt(&element_instance_id);
   DCHECK(success);
-  for (const auto& pair : g_mime_handler_view_container_base_map.Get()) {
-    for (auto* container : pair.second) {
-      if (container->GetInstanceId() == element_instance_id)
-        container->OnHandleMessage(message);
+  MimeHandlerViewContainerBase* target_container = nullptr;
+  for (auto& pair : g_mime_handler_view_container_base_map.Get()) {
+    auto it = std::find_if(
+        pair.second.begin(), pair.second.end(),
+        [&element_instance_id](MimeHandlerViewContainerBase* container) {
+          return container->GetInstanceId() == element_instance_id;
+        });
+    if (it != pair.second.end()) {
+      target_container = *it;
+      break;
     }
   }
+
+  if (target_container)
+    target_container->OnHandleMessage(message);
+
   return false;
 }
 
@@ -287,6 +297,11 @@
     IPC_MESSAGE_HANDLER(
         ExtensionsGuestViewMsg_MimeHandlerViewGuestOnLoadCompleted,
         OnMimeHandlerViewGuestOnLoadCompleted)
+    IPC_MESSAGE_HANDLER(
+        ExtensionsGuestViewMsg_RetryCreatingMimeHandlerViewGuest,
+        OnRetryCreatingMimeHandlerViewGuest)
+    IPC_MESSAGE_HANDLER(ExtensionsGuestViewMsg_DestroyFrameContainer,
+                        OnDestroyFrameContainer)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
   return handled;
@@ -299,6 +314,9 @@
 
 void MimeHandlerViewContainerBase::DidFinishLoading() {
   DCHECK(is_embedded_);
+  // Warning: It is possible that |this| gets destroyed after this line (when
+  // the MHVCB is of the frame type and the associated plugin element does not
+  // have a content frame).
   CreateMimeHandlerViewGuestIfNecessary();
 }
 
@@ -366,6 +384,16 @@
   guest_created_ = true;
 }
 
+void MimeHandlerViewContainerBase::OnRetryCreatingMimeHandlerViewGuest(
+    int32_t element_instance_id) {
+  NOTREACHED();
+}
+
+void MimeHandlerViewContainerBase::OnDestroyFrameContainer(
+    int element_instance_id) {
+  NOTREACHED();
+}
+
 void MimeHandlerViewContainerBase::OnMimeHandlerViewGuestOnLoadCompleted(
     int32_t /* element_instance_id */) {
   if (!GetEmbedderRenderFrame())
@@ -419,6 +447,9 @@
     content::mojom::TransferrableURLLoaderPtr transferrable_url_loader) {
   transferrable_url_loader_ = std::move(transferrable_url_loader);
   transferrable_url_loader_->url = GURL(plugin_path_ + base::GenerateGUID());
+  // Warning: It is possible that |this| gets destroyed after this line (when
+  // the MHVCB is of the frame type and the associated plugin element does not
+  // have a content frame).
   CreateMimeHandlerViewGuestIfNecessary();
 }
 
diff --git a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.h b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.h
index 14d2e62..a47a76d 100644
--- a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.h
+++ b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.h
@@ -72,6 +72,8 @@
 
  protected:
   virtual void CreateMimeHandlerViewGuestIfNecessary();
+  virtual void OnRetryCreatingMimeHandlerViewGuest(int32_t element_instance_id);
+  virtual void OnDestroyFrameContainer(int32_t element_instance_id);
   virtual blink::WebRemoteFrame* GetGuestProxyFrame() const = 0;
   virtual int32_t GetInstanceId() const = 0;
   virtual gfx::Size GetElementSize() const = 0;
@@ -92,6 +94,9 @@
   // Whether the plugin is embedded or not.
   bool is_embedded_;
 
+  // The original URL of the plugin.
+  const GURL original_url_;
+
   // Only valid for the cross-process-frame-based implementation. This holds the
   // routing ID of the frame or proxy whose corresponding WebFrame is the
   // ContentFrame() of the plugin element.
@@ -115,9 +120,6 @@
   // The MIME type of the plugin.
   const std::string mime_type_;
 
-  // The original URL of the plugin.
-  const GURL original_url_;
-
   // Used when network service is enabled:
   content::mojom::TransferrableURLLoaderPtr transferrable_url_loader_;
 
diff --git a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.cc b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.cc
index 5de5de1..a550b9b 100644
--- a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.cc
+++ b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.cc
@@ -67,10 +67,18 @@
   if (is_embedded_) {
     SendResourceRequest();
   } else {
-    // For non-embedded MimeHandlerViewGuest the stream has already been
-    // intercepted.
-    // TODO(ekaramad): Update |view_id_| before sending this request.
-    CreateMimeHandlerViewGuestIfNecessary();
+    // TODO(ekaramad): Currently the full page version gets the same treatment
+    // as the embedded version of MimeHandlerViewFrameContainer; they both send
+    // a request for the resource. The full page version however should not as
+    // there is already an intercepted stream for the navigation. Change the
+    // logic here to a) IsEmbedded() return false for full page, b) the current
+    // intercepted stream is used and no new URLRequest is sent for the
+    // resource, and c) ensure creation of MimeHandlerViewFrameContainer does
+    // not lead to its destruction right away or the Create() method above would
+    // incorrectly return |true|. Note that currently calling
+    // CreateMimeHandlerViewGuestIfNecessary() could lead to the destruction of
+    // |this| when |plugin_element| does not have a content frame.
+    NOTREACHED();
   }
 }
 
@@ -82,12 +90,22 @@
         content::RenderFrame::GetRoutingIdForWebFrame(frame);
   }
   if (plugin_frame_routing_id_ == MSG_ROUTING_NONE) {
-    // TODO(ekaramad): Destroy and cleanup.
+    OnDestroyFrameContainer(element_instance_id_);
     return;
   }
   MimeHandlerViewContainerBase::CreateMimeHandlerViewGuestIfNecessary();
 }
 
+void MimeHandlerViewFrameContainer::OnRetryCreatingMimeHandlerViewGuest(
+    int32_t element_instance_id) {
+  CreateMimeHandlerViewGuestIfNecessary();
+}
+
+void MimeHandlerViewFrameContainer::OnDestroyFrameContainer(
+    int32_t element_instance_id) {
+  delete this;
+}
+
 blink::WebRemoteFrame* MimeHandlerViewFrameContainer::GetGuestProxyFrame()
     const {
   return GetContentFrame()->ToWebRemoteFrame();
@@ -104,6 +122,7 @@
 blink::WebFrame* MimeHandlerViewFrameContainer::GetContentFrame() const {
   return blink::WebFrame::FromFrameOwnerElement(plugin_element_);
 }
+
 // mime_handler::BeforeUnloadControl implementation.
 void MimeHandlerViewFrameContainer::SetShowBeforeUnloadDialog(
     bool show_dialog,
diff --git a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.h b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.h
index daeb2ec..fd1ac90 100644
--- a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.h
+++ b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.h
@@ -42,6 +42,8 @@
 
   // MimeHandlerViewContainerBase overrides.
   void CreateMimeHandlerViewGuestIfNecessary() final;
+  void OnRetryCreatingMimeHandlerViewGuest(int32_t element_instance_id) final;
+  void OnDestroyFrameContainer(int32_t element_instance_id) final;
   blink::WebRemoteFrame* GetGuestProxyFrame() const final;
   int32_t GetInstanceId() const final;
   gfx::Size GetElementSize() const final;