webContents: defer url load when there is a pending navigation entry
authordeepak1556 <hop2deep@gmail.com>
Sun, 19 Feb 2017 20:43:17 +0000 (02:13 +0530)
committerdeepak1556 <hop2deep@gmail.com>
Sun, 5 Mar 2017 18:40:46 +0000 (00:10 +0530)
atom/browser/api/atom_api_web_contents.cc
atom/browser/api/atom_api_web_contents.h
spec/api-browser-window-spec.js

index 89e7a7d..94de2e6 100644 (file)
@@ -46,6 +46,7 @@
 #include "chrome/browser/printing/print_preview_message_handler.h"
 #include "chrome/browser/printing/print_view_manager_basic.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/view_messages.h"
@@ -54,6 +55,9 @@
 #include "content/public/browser/navigation_details.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
 #include "content/public/browser/plugin_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -242,6 +246,22 @@ void OnCapturePageDone(base::Callback<void(const gfx::Image&)> callback,
   callback.Run(gfx::Image::CreateFrom1xBitmap(bitmap));
 }
 
+// Set the background color of RenderWidgetHostView.
+void SetBackgroundColor(content::WebContents* web_contents) {
+  const auto view = web_contents->GetRenderWidgetHostView();
+  if (view) {
+    WebContentsPreferences* web_preferences =
+        WebContentsPreferences::FromWebContents(web_contents);
+    std::string color_name;
+    if (web_preferences->web_preferences()->GetString(options::kBackgroundColor,
+                                                      &color_name)) {
+      view->SetBackgroundColor(ParseHexColor(color_name));
+    } else {
+      view->SetBackgroundColor(SK_ColorTRANSPARENT);
+    }
+  }
+}
+
 }  // namespace
 
 WebContents::WebContents(v8::Isolate* isolate,
@@ -338,7 +358,7 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate,
                                             content::WebContents *web_contents,
                                             mate::Handle<api::Session> session,
                                             const mate::Dictionary& options) {
-  Observe(web_contents);
+  content::WebContentsObserver::Observe(web_contents);
   InitWithWebContents(web_contents, session->browser_context());
 
   managed_web_contents()->GetView()->SetDelegate(this);
@@ -374,6 +394,11 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate,
       SetOwnerWindow(owner_window);
   }
 
+  const content::NavigationController* controller =
+      &web_contents->GetController();
+  registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
+                 content::Source<content::NavigationController>(controller));
+
   Init(isolate);
   AttachAsUserData(web_contents);
 }
@@ -733,6 +758,30 @@ void WebContents::DidGetRedirectForResourceRequest(
        details.headers.get());
 }
 
+void WebContents::DidStartNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame() || navigation_handle->IsSamePage())
+    return;
+
+  if (deferred_load_url_.id) {
+    auto web_contents = navigation_handle->GetWebContents();
+    auto& controller = web_contents->GetController();
+    int id = controller.GetPendingEntry()->GetUniqueID();
+    if (id == deferred_load_url_.id) {
+      if (!deferred_load_url_.params.url.is_empty()) {
+        auto params = deferred_load_url_.params;
+        deferred_load_url_.id = 0;
+        deferred_load_url_.params =
+            content::NavigationController::LoadURLParams(GURL());
+        controller.LoadURLWithParams(params);
+        SetBackgroundColor(web_contents);
+      } else {
+        deferred_load_url_.id = 0;
+      }
+    }
+  }
+}
+
 void WebContents::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
   bool is_main_frame = navigation_handle->IsInMainFrame();
@@ -777,6 +826,33 @@ void WebContents::DidUpdateFaviconURL(
   Emit("page-favicon-updated", unique_urls);
 }
 
+void WebContents::Observe(int type,
+                          const content::NotificationSource& source,
+                          const content::NotificationDetails& details) {
+  switch (type) {
+    case content::NOTIFICATION_NAV_ENTRY_PENDING: {
+      content::NavigationEntry* entry =
+          content::Details<content::NavigationEntry>(details).ptr();
+      content::NavigationEntryImpl* entry_impl =
+          static_cast<content::NavigationEntryImpl*>(entry);
+      // In NavigatorImpl::DidStartMainFrameNavigation when there is no
+      // browser side pending entry available it creates a new one based
+      // on existing pending entry, hence we track the unique id here
+      // instead in WebContents::LoadURL with controller.GetPendingEntry()
+      // TODO(deepak1556): Remove once we have
+      // https://codereview.chromium.org/2661743002.
+      if (entry_impl->is_renderer_initiated() &&
+          entry_impl->frame_tree_node_id() == -1) {
+        deferred_load_url_.id = entry->GetUniqueID();
+      }
+      break;
+    }
+    default:
+      NOTREACHED();
+      break;
+  }
+}
+
 void WebContents::DevToolsReloadPage() {
   Emit("devtools-reload-page");
 }
@@ -921,23 +997,16 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) {
   params.transition_type = ui::PAGE_TRANSITION_TYPED;
   params.should_clear_history_list = true;
   params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE;
-  web_contents()->GetController().LoadURLWithParams(params);
 
-  // Set the background color of RenderWidgetHostView.
+  if (deferred_load_url_.id) {
+    deferred_load_url_.params = params;
+    return;
+  }
+
+  web_contents()->GetController().LoadURLWithParams(params);
   // We have to call it right after LoadURL because the RenderViewHost is only
   // created after loading a page.
-  const auto view = web_contents()->GetRenderWidgetHostView();
-  if (view) {
-    WebContentsPreferences* web_preferences =
-        WebContentsPreferences::FromWebContents(web_contents());
-    std::string color_name;
-    if (web_preferences->web_preferences()->GetString(options::kBackgroundColor,
-                                                      &color_name)) {
-      view->SetBackgroundColor(ParseHexColor(color_name));
-    } else {
-      view->SetBackgroundColor(SK_ColorTRANSPARENT);
-    }
-  }
+  SetBackgroundColor(web_contents());
 }
 
 void WebContents::DownloadURL(const GURL& url) {
index a37fb8a..41edcc2 100644 (file)
@@ -13,6 +13,8 @@
 #include "atom/browser/api/trackable_object.h"
 #include "atom/browser/common_web_contents_delegate.h"
 #include "content/common/cursors/webcursor.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/favicon_url.h"
 #include "native_mate/handle.h"
@@ -46,7 +48,8 @@ namespace api {
 
 class WebContents : public mate::TrackableObject<WebContents>,
                     public CommonWebContentsDelegate,
-                    public content::WebContentsObserver {
+                    public content::WebContentsObserver,
+                    public content::NotificationObserver {
  public:
   enum Type {
     BACKGROUND_PAGE,  // A DevTools extension background page.
@@ -308,6 +311,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
       const content::ResourceRequestDetails& details) override;
   void DidGetRedirectForResourceRequest(
       const content::ResourceRedirectDetails& details) override;
+  void DidStartNavigation(
+      content::NavigationHandle* navigation_handle) override;
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
   bool OnMessageReceived(const IPC::Message& message) override;
@@ -325,6 +330,11 @@ class WebContents : public mate::TrackableObject<WebContents>,
                            const MediaPlayerId& id) override;
   void DidChangeThemeColor(SkColor theme_color) override;
 
+  // content::NotificationObserver:
+  void Observe(int type,
+               const content::NotificationSource& source,
+               const content::NotificationDetails& details) override;
+
   // brightray::InspectableWebContentsDelegate:
   void DevToolsReloadPage() override;
 
@@ -334,6 +344,13 @@ class WebContents : public mate::TrackableObject<WebContents>,
   void DevToolsClosed() override;
 
  private:
+  struct LoadURLParams {
+    LoadURLParams() : params(GURL()), id(0) {}
+
+    content::NavigationController::LoadURLParams params;
+    int id;
+  };
+
   AtomBrowserContext* GetBrowserContext() const;
 
   uint32_t GetNextRequestId() {
@@ -384,6 +401,11 @@ class WebContents : public mate::TrackableObject<WebContents>,
   // Whether to enable devtools.
   bool enable_devtools_;
 
+  // Container to hold url parms for deferred load when
+  // there is a pending navigation entry.
+  LoadURLParams deferred_load_url_;
+  content::NotificationRegistrar registrar_;
+
   DISALLOW_COPY_AND_ASSIGN(WebContents);
 };
 
index 354befd..9631de8 100644 (file)
@@ -229,6 +229,24 @@ describe('BrowserWindow module', function () {
       w.loadURL(`data:image/png;base64,${data}`)
     })
 
+    it('should not crash when there is a pending navigation entry', function (done) {
+      const source = `
+        <script>
+          setInterval(function () {
+            window.location = 'http://host'
+          }, 1000)
+        </script>
+      `
+      w.webContents.on('did-fail-load', function (event, code, desc, url, isMainFrame) {
+        assert.equal(url, 'http://host/')
+        w.webContents.loadURL('about:blank')
+      })
+      w.webContents.on('did-navigate', function (event, url) {
+        if (url === 'about:blank') done()
+      })
+      w.loadURL(`data:text/html,${source}`)
+    })
+
     describe('POST navigations', function () {
       afterEach(() => {
         w.webContents.session.webRequest.onBeforeSendHeaders(null)