summaryrefslogtreecommitdiffstats
path: root/content/browser/frame_host
diff options
context:
space:
mode:
authornasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-30 21:06:40 +0000
committernasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-30 21:06:40 +0000
commitd4a8ca488bb83c9a77c2e8b9a8c8fb7408b6faf6 (patch)
treeda0f92e87f8655795cb2f2861e2bc964920671f4 /content/browser/frame_host
parent9004e635f04bbba6012ecf7e525eccc65ef9b33c (diff)
downloadchromium_src-d4a8ca488bb83c9a77c2e8b9a8c8fb7408b6faf6.zip
chromium_src-d4a8ca488bb83c9a77c2e8b9a8c8fb7408b6faf6.tar.gz
chromium_src-d4a8ca488bb83c9a77c2e8b9a8c8fb7408b6faf6.tar.bz2
Move navigation and frame tree classes to a new frame_host/ directory.
This CL moves all frame tree classes, Navigation* and its dependencies from web_contents/ to a separate frame_host/ directory. BUG=304341 Review URL: https://codereview.chromium.org/49823002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@231921 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/frame_host')
-rw-r--r--content/browser/frame_host/DEPS22
-rw-r--r--content/browser/frame_host/debug_urls.cc82
-rw-r--r--content/browser/frame_host/debug_urls.h20
-rw-r--r--content/browser/frame_host/frame_tree.cc138
-rw-r--r--content/browser/frame_host/frame_tree.h103
-rw-r--r--content/browser/frame_host/frame_tree_node.cc57
-rw-r--r--content/browser/frame_host/frame_tree_node.h116
-rw-r--r--content/browser/frame_host/frame_tree_unittest.cc173
-rw-r--r--content/browser/frame_host/interstitial_page_impl.cc843
-rw-r--r--content/browser/frame_host/interstitial_page_impl.h267
-rw-r--r--content/browser/frame_host/navigation_controller_delegate.h79
-rw-r--r--content/browser/frame_host/navigation_controller_impl.cc1700
-rw-r--r--content/browser/frame_host/navigation_controller_impl.h410
-rw-r--r--content/browser/frame_host/navigation_controller_impl_unittest.cc3844
-rw-r--r--content/browser/frame_host/navigation_entry_impl.cc342
-rw-r--r--content/browser/frame_host/navigation_entry_impl.h336
-rw-r--r--content/browser/frame_host/navigation_entry_impl_unittest.cc241
-rw-r--r--content/browser/frame_host/render_frame_host_impl.cc103
-rw-r--r--content/browser/frame_host/render_frame_host_impl.h77
-rw-r--r--content/browser/frame_host/render_frame_message_filter.cc71
-rw-r--r--content/browser/frame_host/render_frame_message_filter.h44
-rw-r--r--content/browser/frame_host/render_view_host_manager.cc1119
-rw-r--r--content/browser/frame_host/render_view_host_manager.h397
-rw-r--r--content/browser/frame_host/render_view_host_manager_unittest.cc1312
-rw-r--r--content/browser/frame_host/web_contents_screenshot_manager.cc273
-rw-r--r--content/browser/frame_host/web_contents_screenshot_manager.h91
26 files changed, 12260 insertions, 0 deletions
diff --git a/content/browser/frame_host/DEPS b/content/browser/frame_host/DEPS
new file mode 100644
index 0000000..85fb015
--- /dev/null
+++ b/content/browser/frame_host/DEPS
@@ -0,0 +1,22 @@
+include_rules = [
+ # The frame_host files should only call upwards in the layering via the
+ # delegate interfaces.
+ "-content/browser/web_contents",
+ "-content/public/browser/web_contents.h",
+ "-content/public/browser/web_contents_delegate.h",
+ "-content/public/browser/web_contents_view.h",
+]
+
+specific_include_rules = {
+ ".*_(unit|browser)test\.cc": [
+ "+content/browser/web_contents",
+ "+content/public/browser/web_contents_delegate.h",
+ ],
+ ".*interstitial_page_impl\.cc": [
+ # TODO(nasko): This should be removed once we remove
+ # WebContentsObserver as the method of telling interstitial pages to
+ # clean themselves up.
+ "+content/browser/web_contents",
+ "+content/public/browser/web_contents_delegate.h",
+ ],
+}
diff --git a/content/browser/frame_host/debug_urls.cc b/content/browser/frame_host/debug_urls.cc
new file mode 100644
index 0000000..7cb11e8
--- /dev/null
+++ b/content/browser/frame_host/debug_urls.cc
@@ -0,0 +1,82 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/debug_urls.h"
+
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/gpu/gpu_process_host_ui_shim.h"
+#include "content/browser/ppapi_plugin_process_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+void HandlePpapiFlashDebugURL(const GURL& url) {
+#if defined(ENABLE_PLUGINS)
+ bool crash = url == GURL(kChromeUIPpapiFlashCrashURL);
+
+ std::vector<PpapiPluginProcessHost*> hosts;
+ PpapiPluginProcessHost::FindByName(UTF8ToUTF16(kFlashPluginName), &hosts);
+ for (std::vector<PpapiPluginProcessHost*>::iterator iter = hosts.begin();
+ iter != hosts.end(); ++iter) {
+ if (crash)
+ (*iter)->Send(new PpapiMsg_Crash());
+ else
+ (*iter)->Send(new PpapiMsg_Hang());
+ }
+#endif
+}
+
+} // namespace
+
+bool HandleDebugURL(const GURL& url, PageTransition transition) {
+ // Ensure that the user explicitly navigated to this URL.
+ if (!(transition & PAGE_TRANSITION_FROM_ADDRESS_BAR))
+ return false;
+
+ if (url.host() == kChromeUIBrowserCrashHost) {
+ // Induce an intentional crash in the browser process.
+ CHECK(false);
+ return true;
+ }
+
+ if (url == GURL(kChromeUIGpuCleanURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateRemoveAllContext();
+ return true;
+ }
+
+ if (url == GURL(kChromeUIGpuCrashURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateCrash();
+ return true;
+ }
+
+ if (url == GURL(kChromeUIGpuHangURL)) {
+ GpuProcessHostUIShim* shim = GpuProcessHostUIShim::GetOneInstance();
+ if (shim)
+ shim->SimulateHang();
+ return true;
+ }
+
+ if (url == GURL(kChromeUIPpapiFlashCrashURL) ||
+ url == GURL(kChromeUIPpapiFlashHangURL)) {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&HandlePpapiFlashDebugURL, url));
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/debug_urls.h b/content/browser/frame_host/debug_urls.h
new file mode 100644
index 0000000..1985253
--- /dev/null
+++ b/content/browser/frame_host/debug_urls.h
@@ -0,0 +1,20 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_DEBUG_URLS_H_
+#define CONTENT_BROWSER_FRAME_HOST_DEBUG_URLS_H_
+
+#include "content/public/common/page_transition_types.h"
+
+class GURL;
+
+namespace content {
+
+// Checks if the given url is a url used for debugging purposes, and if so
+// handles it and returns true.
+bool HandleDebugURL(const GURL& url, PageTransition transition);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_DEBUG_URLS_H_
diff --git a/content/browser/frame_host/frame_tree.cc b/content/browser/frame_host/frame_tree.cc
new file mode 100644
index 0000000..0cb9d5e
--- /dev/null
+++ b/content/browser/frame_host/frame_tree.cc
@@ -0,0 +1,138 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/frame_tree.h"
+
+#include <queue>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "content/browser/frame_host/frame_tree_node.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+
+namespace content {
+
+namespace {
+// Used with FrameTree::ForEach() to search for the FrameTreeNode
+// corresponding to |frame_id|.
+bool FrameTreeNodeForId(int64 frame_id, FrameTreeNode** out_node,
+ FrameTreeNode* node) {
+ if (node->frame_id() == frame_id) {
+ *out_node = node;
+ // Terminate iteration once the node has been found.
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+FrameTree::FrameTree()
+ : root_(new FrameTreeNode(FrameTreeNode::kInvalidFrameId, std::string(),
+ scoped_ptr<RenderFrameHostImpl>())) {
+}
+
+FrameTree::~FrameTree() {
+}
+
+FrameTreeNode* FrameTree::FindByID(int64 frame_id) {
+ FrameTreeNode* node = NULL;
+ ForEach(base::Bind(&FrameTreeNodeForId, frame_id, &node));
+ return node;
+}
+
+void FrameTree::ForEach(
+ const base::Callback<bool(FrameTreeNode*)>& on_node) const {
+ std::queue<FrameTreeNode*> queue;
+ queue.push(root_.get());
+
+ while (!queue.empty()) {
+ FrameTreeNode* node = queue.front();
+ queue.pop();
+ if (!on_node.Run(node))
+ break;
+
+ for (size_t i = 0; i < node->child_count(); ++i)
+ queue.push(node->child_at(i));
+ }
+}
+
+bool FrameTree::IsFirstNavigationAfterSwap() const {
+ return root_->frame_id() == FrameTreeNode::kInvalidFrameId;
+}
+
+void FrameTree::OnFirstNavigationAfterSwap(int main_frame_id) {
+ root_->set_frame_id(main_frame_id);
+}
+
+void FrameTree::AddFrame(int render_frame_host_id, int64 parent_frame_id,
+ int64 frame_id, const std::string& frame_name) {
+ FrameTreeNode* parent = FindByID(parent_frame_id);
+ // TODO(ajwong): Should the renderer be killed here? Would there be a race on
+ // shutdown that might make this case possible?
+ if (!parent)
+ return;
+
+ parent->AddChild(CreateNode(frame_id, frame_name, render_frame_host_id,
+ parent->render_frame_host()->GetProcess()));
+}
+
+void FrameTree::RemoveFrame(int64 parent_frame_id, int64 frame_id) {
+ // If switches::kSitePerProcess is not specified, then the FrameTree only
+ // contains a node for the root element. However, even in this case
+ // frame detachments need to be broadcast outwards.
+ //
+ // TODO(ajwong): Move this below the |parent| check after the FrameTree is
+ // guaranteed to be correctly populated even without the
+ // switches::kSitePerProcess flag.
+ FrameTreeNode* parent = FindByID(parent_frame_id);
+ if (!on_frame_removed_.is_null()) {
+ on_frame_removed_.Run(
+ root_->render_frame_host()->render_view_host(), frame_id);
+ }
+
+ // TODO(ajwong): Should the renderer be killed here? Would there be a race on
+ // shutdown that might make this case possible?
+ if (!parent)
+ return;
+
+ parent->RemoveChild(frame_id);
+}
+
+void FrameTree::SetFrameUrl(int64 frame_id, const GURL& url) {
+ FrameTreeNode* node = FindByID(frame_id);
+ // TODO(ajwong): Should the renderer be killed here? Would there be a race on
+ // shutdown that might make this case possible?
+ if (!node)
+ return;
+
+ if (node)
+ node->set_current_url(url);
+}
+
+void FrameTree::SwapMainFrame(RenderFrameHostImpl* render_frame_host) {
+ return root_->ResetForMainFrame(render_frame_host);
+}
+
+RenderFrameHostImpl* FrameTree::GetMainFrame() const {
+ return root_->render_frame_host();
+}
+
+void FrameTree::SetFrameRemoveListener(
+ const base::Callback<void(RenderViewHostImpl*, int64)>& on_frame_removed) {
+ on_frame_removed_ = on_frame_removed;
+}
+
+scoped_ptr<FrameTreeNode> FrameTree::CreateNode(
+ int64 frame_id, const std::string& frame_name, int render_frame_host_id,
+ RenderProcessHost* render_process_host) {
+ scoped_ptr<RenderFrameHostImpl> render_frame_host(
+ new RenderFrameHostImpl(root_->render_frame_host()->render_view_host(),
+ this, render_frame_host_id, false));
+
+ return make_scoped_ptr(new FrameTreeNode(frame_id, frame_name,
+ render_frame_host.Pass()));
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/frame_tree.h b/content/browser/frame_host/frame_tree.h
new file mode 100644
index 0000000..f9bf809
--- /dev/null
+++ b/content/browser/frame_host/frame_tree.h
@@ -0,0 +1,103 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_FRAME_TREE_H_
+#define CONTENT_BROWSER_FRAME_HOST_FRAME_TREE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/frame_host/frame_tree_node.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class FrameTreeNode;
+class RenderProcessHost;
+class RenderViewHostImpl;
+
+// Represents the frame tree for a page. With the exception of the main frame,
+// all FrameTreeNodes will be created/deleted in response to frame attach and
+// detach events in the DOM.
+//
+// The main frame's FrameTreeNode is special in that it is reused. This allows
+// it to serve as an anchor for state that needs to persist across top-level
+// page navigations.
+//
+// TODO(ajwong): Move NavigationController ownership to the main frame
+// FrameTreeNode. Possibly expose access to it from here.
+//
+// TODO(ajwong): Currently this class only contains FrameTreeNodes for
+// subframes if the --site-per-process flag is enabled.
+//
+// This object is only used on the UI thread.
+class CONTENT_EXPORT FrameTree {
+ public:
+ FrameTree();
+ ~FrameTree();
+
+ // Returns the FrameTreeNode with the given |frame_id|.
+ FrameTreeNode* FindByID(int64 frame_id);
+
+ // Executes |on_node| on each node in the frame tree. If |on_node| returns
+ // false, terminates the iteration immediately. Returning false is useful
+ // if |on_node| is just doing a search over the tree.
+ void ForEach(const base::Callback<bool(FrameTreeNode*)>& on_node) const;
+
+ // After the FrameTree is created, or after SwapMainFrame() has been called,
+ // the root node does not yet have a frame id. This is allocated by the
+ // renderer and is published to the browser process on the first navigation
+ // after a swap. These two functions are used to set the root node's frame
+ // id.
+ //
+ // TODO(ajwong): Remove these once RenderFrameHost's routing id replaces
+ // frame_id.
+ bool IsFirstNavigationAfterSwap() const;
+ void OnFirstNavigationAfterSwap(int main_frame_id);
+
+ // Frame tree manipulation routines.
+ void AddFrame(int render_frame_host_id, int64 parent_frame_id,
+ int64 frame_id, const std::string& frame_name);
+ void RemoveFrame(int64 parent_frame_id, int64 frame_id);
+ void SetFrameUrl(int64 frame_id, const GURL& url);
+
+ // Resets the FrameTree and changes RenderFrameHost for the main frame.
+ // This destroys most of the frame tree but retains the root node so that
+ // navigation state may be kept on it between process swaps. Used to
+ // support bookkeeping for top-level navigations.
+ //
+ // If |main_frame| is NULL, reset tree to initially constructed state.
+ //
+ // TODO(ajwong): This function should not be given a |main_frame|. This is
+ // required currently because the RenderViewHost owns its main frame. When
+ // that relation is fixed, the FrameTree should be responsible for
+ // created/destroying the main frame on the swap.
+ void SwapMainFrame(RenderFrameHostImpl* main_frame);
+
+ // Convenience accessor for the main frame's RenderFrameHostImpl.
+ RenderFrameHostImpl* GetMainFrame() const;
+
+ // Allows a client to listen for frame removal.
+ void SetFrameRemoveListener(
+ const base::Callback<void(RenderViewHostImpl*, int64)>& on_frame_removed);
+
+ FrameTreeNode* GetRootForTesting() { return root_.get(); }
+
+ private:
+ scoped_ptr<FrameTreeNode> CreateNode(int64 frame_id,
+ const std::string& frame_name,
+ int render_frame_host_id,
+ RenderProcessHost* render_process_host);
+
+ scoped_ptr<FrameTreeNode> root_;
+
+ base::Callback<void(RenderViewHostImpl*, int64)> on_frame_removed_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameTree);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_FRAME_TREE_H_
diff --git a/content/browser/frame_host/frame_tree_node.cc b/content/browser/frame_host/frame_tree_node.cc
new file mode 100644
index 0000000..9bed9f6
--- /dev/null
+++ b/content/browser/frame_host/frame_tree_node.cc
@@ -0,0 +1,57 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/frame_tree_node.h"
+
+#include <queue>
+
+#include "base/stl_util.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+
+namespace content {
+
+const int64 FrameTreeNode::kInvalidFrameId = -1;
+
+FrameTreeNode::FrameTreeNode(int64 frame_id, const std::string& name,
+ scoped_ptr<RenderFrameHostImpl> render_frame_host)
+ : frame_id_(frame_id),
+ frame_name_(name),
+ owns_render_frame_host_(true),
+ render_frame_host_(render_frame_host.release()) {
+}
+
+FrameTreeNode::~FrameTreeNode() {
+ if (owns_render_frame_host_)
+ delete render_frame_host_;
+}
+
+void FrameTreeNode::AddChild(scoped_ptr<FrameTreeNode> child) {
+ children_.push_back(child.release());
+}
+
+void FrameTreeNode::RemoveChild(int64 child_id) {
+ std::vector<FrameTreeNode*>::iterator iter;
+
+ for (iter = children_.begin(); iter != children_.end(); ++iter) {
+ if ((*iter)->frame_id() == child_id)
+ break;
+ }
+
+ if (iter != children_.end())
+ children_.erase(iter);
+}
+
+void FrameTreeNode::ResetForMainFrame(
+ RenderFrameHostImpl* new_render_frame_host) {
+ DCHECK_EQ(0UL, children_.size());
+
+ owns_render_frame_host_ = false;
+ frame_id_ = kInvalidFrameId;
+ current_url_ = GURL();
+ children_.clear();
+
+ render_frame_host_ = new_render_frame_host;
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/frame_tree_node.h b/content/browser/frame_host/frame_tree_node.h
new file mode 100644
index 0000000..63030a6
--- /dev/null
+++ b/content/browser/frame_host/frame_tree_node.h
@@ -0,0 +1,116 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_FRAME_TREE_NODE_H_
+#define CONTENT_BROWSER_FRAME_HOST_FRAME_TREE_NODE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class RenderFrameHostImpl;
+
+// When a page contains iframes, its renderer process maintains a tree structure
+// of those frames. We are mirroring this tree in the browser process. This
+// class represents a node in this tree and is a wrapper for all objects that
+// are frame-specific (as opposed to page-specific).
+class CONTENT_EXPORT FrameTreeNode {
+ public:
+ static const int64 kInvalidFrameId;
+
+ FrameTreeNode(int64 frame_id, const std::string& name,
+ scoped_ptr<RenderFrameHostImpl> render_frame_host);
+ ~FrameTreeNode();
+
+ void AddChild(scoped_ptr<FrameTreeNode> child);
+ void RemoveChild(int64 child_id);
+
+ // Transitional API allowing the RenderFrameHost of a FrameTreeNode
+ // representing the main frame to be provided by someone else. After
+ // this is called, the FrameTreeNode no longer owns its RenderFrameHost.
+ //
+ // This should only be used for the main frame (aka root) in a frame tree.
+ //
+ // TODO(ajwong): Remove this method once the main frame RenderFrameHostImpl is
+ // no longer owned by the RenderViewHostImpl.
+ void ResetForMainFrame(RenderFrameHostImpl* new_render_frame_host);
+
+ void set_frame_id(int64 frame_id) {
+ DCHECK_EQ(frame_id_, kInvalidFrameId);
+ frame_id_ = frame_id;
+ }
+
+ int64 frame_id() const {
+ return frame_id_;
+ }
+
+ const std::string& frame_name() const {
+ return frame_name_;
+ }
+
+ size_t child_count() const {
+ return children_.size();
+ }
+
+ FrameTreeNode* child_at(size_t index) const {
+ return children_[index];
+ }
+
+ const GURL& current_url() const {
+ return current_url_;
+ }
+
+ void set_current_url(const GURL& url) {
+ current_url_ = url;
+ }
+
+ RenderFrameHostImpl* render_frame_host() const {
+ return render_frame_host_;
+ }
+
+ private:
+ // The unique identifier for the frame in the page.
+ int64 frame_id_;
+
+ // The assigned name of the frame. This name can be empty, unlike the unique
+ // name generated internally in the DOM tree.
+ std::string frame_name_;
+
+ // The immediate children of this specific frame.
+ ScopedVector<FrameTreeNode> children_;
+
+ // When ResetForMainFrame() is called, this is set to false and the
+ // |render_frame_host_| below is not deleted on destruction.
+ //
+ // For the mainframe, the FrameTree does not own the |render_frame_host_|.
+ // This is a transitional wart because RenderViewHostManager does not yet
+ // have the bookkeeping logic to handle creating a pending RenderFrameHost
+ // along with a pending RenderViewHost. Thus, for the main frame, the
+ // RenderViewHost currently retains ownership and the FrameTreeNode should
+ // not delete it on destruction.
+ bool owns_render_frame_host_;
+
+ // The active RenderFrameHost for this frame. The FrameTreeNode does not
+ // always own this pointer. See comments above |owns_render_frame_host_|.
+ // TODO(ajwong): Replace with RenderFrameHostManager.
+ RenderFrameHostImpl* render_frame_host_;
+
+ // Track the current frame's last committed URL, so we can estimate the
+ // process impact of out-of-process iframes.
+ // TODO(creis): Remove this when we can store subframe URLs in the
+ // NavigationController.
+ GURL current_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameTreeNode);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_FRAME_TREE_NODE_H_
diff --git a/content/browser/frame_host/frame_tree_unittest.cc b/content/browser/frame_host/frame_tree_unittest.cc
new file mode 100644
index 0000000..20295dc
--- /dev/null
+++ b/content/browser/frame_host/frame_tree_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/frame_tree.h"
+
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+class FrameTreeTest : public RenderViewHostTestHarness {
+ protected:
+ // Prints a FrameTree, for easy assertions of the tree hierarchy.
+ std::string GetTreeState(FrameTree* frame_tree) {
+ std::string result;
+ AppendTreeNodeState(frame_tree->GetRootForTesting(), &result);
+ return result;
+ }
+
+ private:
+ void AppendTreeNodeState(FrameTreeNode* node, std::string* result) {
+ result->append(base::Int64ToString(node->frame_id()));
+ if (!node->frame_name().empty()) {
+ result->append(" '");
+ result->append(node->frame_name());
+ result->append("'");
+ }
+ result->append(": [");
+ const char* separator = "";
+ for (size_t i = 0; i < node->child_count(); i++) {
+ result->append(separator);
+ AppendTreeNodeState(node->child_at(i), result);
+ separator = ", ";
+ }
+ result->append("]");
+ }
+};
+
+// The root node never changes during navigation even though its
+// RenderFrameHost does.
+// - Swapping main frame doesn't change root node.
+// - Swapping back to NULL doesn't crash (easier tear-down for interstitials).
+// - Main frame does not own RenderFrameHost.
+TEST_F(FrameTreeTest, RootNode) {
+ FrameTree frame_tree;
+
+ // Initial state has empty node.
+ FrameTreeNode* root = frame_tree.GetRootForTesting();
+ ASSERT_TRUE(root);
+ EXPECT_FALSE(frame_tree.GetMainFrame());
+
+ // Swap in main frame.
+ RenderFrameHostImpl* dummy = reinterpret_cast<RenderFrameHostImpl*>(0x1);
+ frame_tree.SwapMainFrame(dummy);
+ EXPECT_EQ(root, frame_tree.GetRootForTesting());
+ EXPECT_EQ(dummy, frame_tree.GetMainFrame());
+
+ // Move back to NULL.
+ frame_tree.SwapMainFrame(NULL);
+ EXPECT_EQ(root, frame_tree.GetRootForTesting());
+ EXPECT_FALSE(frame_tree.GetMainFrame());
+
+ // Move back to an invalid pointer, let the FrameTree go out of scope. Test
+ // should not crash because the main frame isn't owned.
+ frame_tree.SwapMainFrame(dummy);
+}
+
+// Test that swapping the main frame resets the renderer-assigned frame id.
+// - On creation, frame id is unassigned.
+// - After a swap, frame id is unassigned.
+TEST_F(FrameTreeTest, FirstNavigationAfterSwap) {
+ FrameTree frame_tree;
+
+ EXPECT_TRUE(frame_tree.IsFirstNavigationAfterSwap());
+ EXPECT_EQ(FrameTreeNode::kInvalidFrameId,
+ frame_tree.GetRootForTesting()->frame_id());
+ frame_tree.OnFirstNavigationAfterSwap(1);
+ EXPECT_FALSE(frame_tree.IsFirstNavigationAfterSwap());
+ EXPECT_EQ(1, frame_tree.GetRootForTesting()->frame_id());
+
+ frame_tree.SwapMainFrame(NULL);
+ EXPECT_TRUE(frame_tree.IsFirstNavigationAfterSwap());
+ EXPECT_EQ(FrameTreeNode::kInvalidFrameId,
+ frame_tree.GetRootForTesting()->frame_id());
+}
+
+// Exercise tree manipulation routines.
+// - Add a series of nodes and verify tree structure.
+// - Remove a series of nodes and verify tree structure.
+TEST_F(FrameTreeTest, Shape) {
+ FrameTree frame_tree;
+ std::string no_children_node("no children node");
+ std::string deep_subtree("node with deep subtree");
+
+ // Ensure the top-level node of the FrameTree is initialized by simulating a
+ // main frame swap here.
+ RenderFrameHostImpl render_frame_host(static_cast<RenderViewHostImpl*>(rvh()),
+ &frame_tree,
+ process()->GetNextRoutingID(), false);
+ frame_tree.SwapMainFrame(&render_frame_host);
+ frame_tree.OnFirstNavigationAfterSwap(5);
+
+ ASSERT_EQ("5: []", GetTreeState(&frame_tree));
+
+ // Simulate attaching a series of frames to build the frame tree.
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 5, 14, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 5, 15, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 5, 16, std::string());
+
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 14, 244, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 15, 255, no_children_node);
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 14, 245, std::string());
+
+ ASSERT_EQ("5: [14: [244: [], 245: []], "
+ "15: [255 'no children node': []], "
+ "16: []]",
+ GetTreeState(&frame_tree));
+
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 16, 264, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 16, 265, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 16, 266, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 16, 267, deep_subtree);
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 16, 268, std::string());
+
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 267, 365, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 365, 455, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 455, 555, std::string());
+ frame_tree.AddFrame(process()->GetNextRoutingID(), 555, 655, std::string());
+
+ // Now that's it's fully built, verify the tree structure is as expected.
+ ASSERT_EQ("5: [14: [244: [], 245: []], "
+ "15: [255 'no children node': []], "
+ "16: [264: [], 265: [], 266: [], "
+ "267 'node with deep subtree': "
+ "[365: [455: [555: [655: []]]]], 268: []]]",
+ GetTreeState(&frame_tree));
+
+ // Test removing of nodes.
+ frame_tree.RemoveFrame(555, 655);
+ ASSERT_EQ("5: [14: [244: [], 245: []], "
+ "15: [255 'no children node': []], "
+ "16: [264: [], 265: [], 266: [], "
+ "267 'node with deep subtree': "
+ "[365: [455: [555: []]]], 268: []]]",
+ GetTreeState(&frame_tree));
+
+ frame_tree.RemoveFrame(16, 265);
+ ASSERT_EQ("5: [14: [244: [], 245: []], "
+ "15: [255 'no children node': []], "
+ "16: [264: [], 266: [], "
+ "267 'node with deep subtree': "
+ "[365: [455: [555: []]]], 268: []]]",
+ GetTreeState(&frame_tree));
+
+ frame_tree.RemoveFrame(5, 15);
+ ASSERT_EQ("5: [14: [244: [], 245: []], "
+ "16: [264: [], 266: [], "
+ "267 'node with deep subtree': "
+ "[365: [455: [555: []]]], 268: []]]",
+ GetTreeState(&frame_tree));
+}
+
+} // namespace
+} // namespace content
diff --git a/content/browser/frame_host/interstitial_page_impl.cc b/content/browser/frame_host/interstitial_page_impl.cc
new file mode 100644
index 0000000..4bcee69
--- /dev/null
+++ b/content/browser/frame_host/interstitial_page_impl.cc
@@ -0,0 +1,843 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/interstitial_page_impl.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/browser/frame_host/navigation_controller_impl.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_view_host_delegate_view.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/port/browser/web_contents_view_port.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/dom_operation_notification_details.h"
+#include "content/public/browser/interstitial_page_delegate.h"
+#include "content/public/browser/invalidate_type.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/page_transition_types.h"
+#include "net/base/escape.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using WebKit::WebDragOperation;
+using WebKit::WebDragOperationsMask;
+
+namespace content {
+namespace {
+
+void ResourceRequestHelper(ResourceDispatcherHostImpl* rdh,
+ int process_id,
+ int render_view_host_id,
+ ResourceRequestAction action) {
+ switch (action) {
+ case BLOCK:
+ rdh->BlockRequestsForRoute(process_id, render_view_host_id);
+ break;
+ case RESUME:
+ rdh->ResumeBlockedRequestsForRoute(process_id, render_view_host_id);
+ break;
+ case CANCEL:
+ rdh->CancelBlockedRequestsForRoute(process_id, render_view_host_id);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+} // namespace
+
+class InterstitialPageImpl::InterstitialPageRVHDelegateView
+ : public RenderViewHostDelegateView {
+ public:
+ explicit InterstitialPageRVHDelegateView(InterstitialPageImpl* page);
+
+ // RenderViewHostDelegateView implementation:
+ virtual void ShowPopupMenu(const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) OVERRIDE;
+ virtual void StartDragging(const DropData& drop_data,
+ WebDragOperationsMask operations_allowed,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) OVERRIDE;
+ virtual void UpdateDragCursor(WebDragOperation operation) OVERRIDE;
+ virtual void GotFocus() OVERRIDE;
+ virtual void TakeFocus(bool reverse) OVERRIDE;
+ virtual void OnFindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+
+ private:
+ InterstitialPageImpl* interstitial_page_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPageRVHDelegateView);
+};
+
+
+// We keep a map of the various blocking pages shown as the UI tests need to
+// be able to retrieve them.
+typedef std::map<WebContents*, InterstitialPageImpl*> InterstitialPageMap;
+static InterstitialPageMap* g_web_contents_to_interstitial_page;
+
+// Initializes g_web_contents_to_interstitial_page in a thread-safe manner.
+// Should be called before accessing g_web_contents_to_interstitial_page.
+static void InitInterstitialPageMap() {
+ if (!g_web_contents_to_interstitial_page)
+ g_web_contents_to_interstitial_page = new InterstitialPageMap;
+}
+
+InterstitialPage* InterstitialPage::Create(WebContents* web_contents,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialPageDelegate* delegate) {
+ return new InterstitialPageImpl(
+ web_contents,
+ static_cast<RenderWidgetHostDelegate*>(
+ static_cast<WebContentsImpl*>(web_contents)),
+ new_navigation, url, delegate);
+}
+
+InterstitialPage* InterstitialPage::GetInterstitialPage(
+ WebContents* web_contents) {
+ InitInterstitialPageMap();
+ InterstitialPageMap::const_iterator iter =
+ g_web_contents_to_interstitial_page->find(web_contents);
+ if (iter == g_web_contents_to_interstitial_page->end())
+ return NULL;
+
+ return iter->second;
+}
+
+InterstitialPageImpl::InterstitialPageImpl(
+ WebContents* web_contents,
+ RenderWidgetHostDelegate* render_widget_host_delegate,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialPageDelegate* delegate)
+ : WebContentsObserver(web_contents),
+ web_contents_(web_contents),
+ controller_(static_cast<NavigationControllerImpl*>(
+ &web_contents->GetController())),
+ render_widget_host_delegate_(render_widget_host_delegate),
+ url_(url),
+ new_navigation_(new_navigation),
+ should_discard_pending_nav_entry_(new_navigation),
+ reload_on_dont_proceed_(false),
+ enabled_(true),
+ action_taken_(NO_ACTION),
+ render_view_host_(NULL),
+ original_child_id_(web_contents->GetRenderProcessHost()->GetID()),
+ original_rvh_id_(web_contents->GetRenderViewHost()->GetRoutingID()),
+ should_revert_web_contents_title_(false),
+ web_contents_was_loading_(false),
+ resource_dispatcher_host_notified_(false),
+ rvh_delegate_view_(new InterstitialPageRVHDelegateView(this)),
+ create_view_(true),
+ delegate_(delegate),
+ weak_ptr_factory_(this) {
+ InitInterstitialPageMap();
+ // It would be inconsistent to create an interstitial with no new navigation
+ // (which is the case when the interstitial was triggered by a sub-resource on
+ // a page) when we have a pending entry (in the process of loading a new top
+ // frame).
+ DCHECK(new_navigation || !web_contents->GetController().GetPendingEntry());
+}
+
+InterstitialPageImpl::~InterstitialPageImpl() {
+}
+
+void InterstitialPageImpl::Show() {
+ if (!enabled())
+ return;
+
+ // If an interstitial is already showing or about to be shown, close it before
+ // showing the new one.
+ // Be careful not to take an action on the old interstitial more than once.
+ InterstitialPageMap::const_iterator iter =
+ g_web_contents_to_interstitial_page->find(web_contents_);
+ if (iter != g_web_contents_to_interstitial_page->end()) {
+ InterstitialPageImpl* interstitial = iter->second;
+ if (interstitial->action_taken_ != NO_ACTION) {
+ interstitial->Hide();
+ } else {
+ // If we are currently showing an interstitial page for which we created
+ // a transient entry and a new interstitial is shown as the result of a
+ // new browser initiated navigation, then that transient entry has already
+ // been discarded and a new pending navigation entry created.
+ // So we should not discard that new pending navigation entry.
+ // See http://crbug.com/9791
+ if (new_navigation_ && interstitial->new_navigation_)
+ interstitial->should_discard_pending_nav_entry_= false;
+ interstitial->DontProceed();
+ }
+ }
+
+ // Block the resource requests for the render view host while it is hidden.
+ TakeActionOnResourceDispatcher(BLOCK);
+ // We need to be notified when the RenderViewHost is destroyed so we can
+ // cancel the blocked requests. We cannot do that on
+ // NOTIFY_WEB_CONTENTS_DESTROYED as at that point the RenderViewHost has
+ // already been destroyed.
+ notification_registrar_.Add(
+ this, NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(controller_->delegate()->GetRenderViewHost()));
+
+ // Update the g_web_contents_to_interstitial_page map.
+ iter = g_web_contents_to_interstitial_page->find(web_contents_);
+ DCHECK(iter == g_web_contents_to_interstitial_page->end());
+ (*g_web_contents_to_interstitial_page)[web_contents_] = this;
+
+ if (new_navigation_) {
+ NavigationEntryImpl* entry = new NavigationEntryImpl;
+ entry->SetURL(url_);
+ entry->SetVirtualURL(url_);
+ entry->set_page_type(PAGE_TYPE_INTERSTITIAL);
+
+ // Give delegates a chance to set some states on the navigation entry.
+ delegate_->OverrideEntry(entry);
+
+ controller_->SetTransientEntry(entry);
+ }
+
+ DCHECK(!render_view_host_);
+ render_view_host_ = static_cast<RenderViewHostImpl*>(CreateRenderViewHost());
+ render_view_host_->AttachToFrameTree();
+ CreateWebContentsView();
+
+ std::string data_url = "data:text/html;charset=utf-8," +
+ net::EscapePath(delegate_->GetHTMLContents());
+ render_view_host_->NavigateToURL(GURL(data_url));
+
+ notification_registrar_.Add(this, NOTIFICATION_NAV_ENTRY_PENDING,
+ Source<NavigationController>(controller_));
+ notification_registrar_.Add(
+ this, NOTIFICATION_DOM_OPERATION_RESPONSE,
+ Source<RenderViewHost>(render_view_host_));
+}
+
+void InterstitialPageImpl::Hide() {
+ // We may have already been hidden, and are just waiting to be deleted.
+ // We can't check for enabled() here, because some callers have already
+ // called Disable.
+ if (!render_view_host_)
+ return;
+
+ Disable();
+
+ RenderWidgetHostView* old_view =
+ controller_->delegate()->GetRenderViewHost()->GetView();
+ if (controller_->delegate()->GetInterstitialPage() == this &&
+ old_view &&
+ !old_view->IsShowing() &&
+ !controller_->delegate()->IsHidden()) {
+ // Show the original RVH since we're going away. Note it might not exist if
+ // the renderer crashed while the interstitial was showing.
+ // Note that it is important that we don't call Show() if the view is
+ // already showing. That would result in bad things (unparented HWND on
+ // Windows for example) happening.
+ old_view->Show();
+ }
+
+ // If the focus was on the interstitial, let's keep it to the page.
+ // (Note that in unit-tests the RVH may not have a view).
+ if (render_view_host_->GetView() &&
+ render_view_host_->GetView()->HasFocus() &&
+ controller_->delegate()->GetRenderViewHost()->GetView()) {
+ RenderWidgetHostViewPort::FromRWHV(
+ controller_->delegate()->GetRenderViewHost()->GetView())->Focus();
+ }
+
+ // Shutdown the RVH asynchronously, as we may have been called from a RVH
+ // delegate method, and we can't delete the RVH out from under itself.
+ base::MessageLoop::current()->PostNonNestableTask(
+ FROM_HERE,
+ base::Bind(&InterstitialPageImpl::Shutdown,
+ weak_ptr_factory_.GetWeakPtr(),
+ render_view_host_));
+ render_view_host_ = NULL;
+ frame_tree_.SwapMainFrame(NULL);
+ controller_->delegate()->DetachInterstitialPage();
+ // Let's revert to the original title if necessary.
+ NavigationEntry* entry = controller_->GetVisibleEntry();
+ if (!new_navigation_ && should_revert_web_contents_title_) {
+ entry->SetTitle(original_web_contents_title_);
+ controller_->delegate()->NotifyNavigationStateChanged(
+ INVALIDATE_TYPE_TITLE);
+ }
+
+ InterstitialPageMap::iterator iter =
+ g_web_contents_to_interstitial_page->find(web_contents_);
+ DCHECK(iter != g_web_contents_to_interstitial_page->end());
+ if (iter != g_web_contents_to_interstitial_page->end())
+ g_web_contents_to_interstitial_page->erase(iter);
+
+ // Clear the WebContents pointer, because it may now be deleted.
+ // This signifies that we are in the process of shutting down.
+ web_contents_ = NULL;
+}
+
+void InterstitialPageImpl::Observe(
+ int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_NAV_ENTRY_PENDING:
+ // We are navigating away from the interstitial (the user has typed a URL
+ // in the location bar or clicked a bookmark). Make sure clicking on the
+ // interstitial will have no effect. Also cancel any blocked requests
+ // on the ResourceDispatcherHost. Note that when we get this notification
+ // the RenderViewHost has not yet navigated so we'll unblock the
+ // RenderViewHost before the resource request for the new page we are
+ // navigating arrives in the ResourceDispatcherHost. This ensures that
+ // request won't be blocked if the same RenderViewHost was used for the
+ // new navigation.
+ Disable();
+ TakeActionOnResourceDispatcher(CANCEL);
+ break;
+ case NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED:
+ if (action_taken_ == NO_ACTION) {
+ // The RenderViewHost is being destroyed (as part of the tab being
+ // closed); make sure we clear the blocked requests.
+ RenderViewHost* rvh = static_cast<RenderViewHost*>(
+ static_cast<RenderViewHostImpl*>(
+ RenderWidgetHostImpl::From(
+ Source<RenderWidgetHost>(source).ptr())));
+ DCHECK(rvh->GetProcess()->GetID() == original_child_id_ &&
+ rvh->GetRoutingID() == original_rvh_id_);
+ TakeActionOnResourceDispatcher(CANCEL);
+ }
+ break;
+ case NOTIFICATION_DOM_OPERATION_RESPONSE:
+ if (enabled()) {
+ Details<DomOperationNotificationDetails> dom_op_details(
+ details);
+ delegate_->CommandReceived(dom_op_details->json);
+ }
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void InterstitialPageImpl::NavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) {
+ OnNavigatingAwayOrTabClosing();
+}
+
+void InterstitialPageImpl::WebContentsDestroyed(WebContents* web_contents) {
+ OnNavigatingAwayOrTabClosing();
+}
+
+RenderViewHostDelegateView* InterstitialPageImpl::GetDelegateView() {
+ return rvh_delegate_view_.get();
+}
+
+const GURL& InterstitialPageImpl::GetURL() const {
+ return url_;
+}
+
+void InterstitialPageImpl::RenderViewTerminated(
+ RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code) {
+ // Our renderer died. This should not happen in normal cases.
+ // If we haven't already started shutdown, just dismiss the interstitial.
+ // We cannot check for enabled() here, because we may have called Disable
+ // without calling Hide.
+ if (render_view_host_)
+ DontProceed();
+}
+
+void InterstitialPageImpl::DidNavigate(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // A fast user could have navigated away from the page that triggered the
+ // interstitial while the interstitial was loading, that would have disabled
+ // us. In that case we can dismiss ourselves.
+ if (!enabled()) {
+ DontProceed();
+ return;
+ }
+ if (PageTransitionCoreTypeIs(params.transition,
+ PAGE_TRANSITION_AUTO_SUBFRAME)) {
+ // No need to handle navigate message from iframe in the interstitial page.
+ return;
+ }
+
+ // The RenderViewHost has loaded its contents, we can show it now.
+ if (!controller_->delegate()->IsHidden())
+ render_view_host_->GetView()->Show();
+ controller_->delegate()->AttachInterstitialPage(this);
+
+ RenderWidgetHostView* rwh_view =
+ controller_->delegate()->GetRenderViewHost()->GetView();
+
+ // The RenderViewHost may already have crashed before we even get here.
+ if (rwh_view) {
+ // If the page has focus, focus the interstitial.
+ if (rwh_view->HasFocus())
+ Focus();
+
+ // Hide the original RVH since we're showing the interstitial instead.
+ rwh_view->Hide();
+ }
+
+ // Notify the tab we are not loading so the throbber is stopped. It also
+ // causes a WebContentsObserver::DidStopLoading callback that the
+ // AutomationProvider (used by the UI tests) expects to consider a navigation
+ // as complete. Without this, navigating in a UI test to a URL that triggers
+ // an interstitial would hang.
+ web_contents_was_loading_ = controller_->delegate()->IsLoading();
+ controller_->delegate()->SetIsLoading(
+ controller_->delegate()->GetRenderViewHost(), false, NULL);
+}
+
+void InterstitialPageImpl::UpdateTitle(
+ RenderViewHost* render_view_host,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) {
+ if (!enabled())
+ return;
+
+ DCHECK(render_view_host == render_view_host_);
+ NavigationEntry* entry = controller_->GetVisibleEntry();
+ if (!entry) {
+ // Crash reports from the field indicate this can be NULL.
+ // This is unexpected as InterstitialPages constructed with the
+ // new_navigation flag set to true create a transient navigation entry
+ // (that is returned as the active entry). And the only case so far of
+ // interstitial created with that flag set to false is with the
+ // SafeBrowsingBlockingPage, when the resource triggering the interstitial
+ // is a sub-resource, meaning the main page has already been loaded and a
+ // navigation entry should have been created.
+ NOTREACHED();
+ return;
+ }
+
+ // If this interstitial is shown on an existing navigation entry, we'll need
+ // to remember its title so we can revert to it when hidden.
+ if (!new_navigation_ && !should_revert_web_contents_title_) {
+ original_web_contents_title_ = entry->GetTitle();
+ should_revert_web_contents_title_ = true;
+ }
+ // TODO(evan): make use of title_direction.
+ // http://code.google.com/p/chromium/issues/detail?id=27094
+ entry->SetTitle(title);
+ controller_->delegate()->NotifyNavigationStateChanged(INVALIDATE_TYPE_TITLE);
+}
+
+RendererPreferences InterstitialPageImpl::GetRendererPrefs(
+ BrowserContext* browser_context) const {
+ delegate_->OverrideRendererPrefs(&renderer_preferences_);
+ return renderer_preferences_;
+}
+
+WebPreferences InterstitialPageImpl::GetWebkitPrefs() {
+ if (!enabled())
+ return WebPreferences();
+
+ return render_view_host_->GetWebkitPrefs(url_);
+}
+
+void InterstitialPageImpl::RenderWidgetDeleted(
+ RenderWidgetHostImpl* render_widget_host) {
+ delete this;
+}
+
+bool InterstitialPageImpl::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ if (!enabled())
+ return false;
+ return render_widget_host_delegate_->PreHandleKeyboardEvent(
+ event, is_keyboard_shortcut);
+}
+
+void InterstitialPageImpl::HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ if (enabled())
+ render_widget_host_delegate_->HandleKeyboardEvent(event);
+}
+
+#if defined(OS_WIN) && defined(USE_AURA)
+gfx::NativeViewAccessible
+InterstitialPageImpl::GetParentNativeViewAccessible() {
+ return render_widget_host_delegate_->GetParentNativeViewAccessible();
+}
+#endif
+
+WebContents* InterstitialPageImpl::web_contents() const {
+ return web_contents_;
+}
+
+RenderViewHost* InterstitialPageImpl::CreateRenderViewHost() {
+ if (!enabled())
+ return NULL;
+
+ // Interstitial pages don't want to share the session storage so we mint a
+ // new one.
+ BrowserContext* browser_context = web_contents()->GetBrowserContext();
+ scoped_refptr<SiteInstance> site_instance =
+ SiteInstance::Create(browser_context);
+ DOMStorageContextWrapper* dom_storage_context =
+ static_cast<DOMStorageContextWrapper*>(
+ BrowserContext::GetStoragePartition(
+ browser_context, site_instance.get())->GetDOMStorageContext());
+ session_storage_namespace_ =
+ new SessionStorageNamespaceImpl(dom_storage_context);
+
+ return RenderViewHostFactory::Create(site_instance.get(),
+ this,
+ this,
+ MSG_ROUTING_NONE,
+ MSG_ROUTING_NONE,
+ false,
+ false);
+}
+
+WebContentsView* InterstitialPageImpl::CreateWebContentsView() {
+ if (!enabled() || !create_view_)
+ return NULL;
+ WebContentsView* web_contents_view = web_contents()->GetView();
+ WebContentsViewPort* web_contents_view_port =
+ static_cast<WebContentsViewPort*>(web_contents_view);
+ RenderWidgetHostView* view =
+ web_contents_view_port->CreateViewForWidget(render_view_host_);
+ render_view_host_->SetView(view);
+ render_view_host_->AllowBindings(BINDINGS_POLICY_DOM_AUTOMATION);
+
+ int32 max_page_id = web_contents()->
+ GetMaxPageIDForSiteInstance(render_view_host_->GetSiteInstance());
+ render_view_host_->CreateRenderView(string16(),
+ MSG_ROUTING_NONE,
+ max_page_id);
+ controller_->delegate()->RenderViewForInterstitialPageCreated(
+ render_view_host_);
+ view->SetSize(web_contents_view->GetContainerSize());
+ // Don't show the interstitial until we have navigated to it.
+ view->Hide();
+ return web_contents_view;
+}
+
+void InterstitialPageImpl::Proceed() {
+ // Don't repeat this if we are already shutting down. We cannot check for
+ // enabled() here, because we may have called Disable without calling Hide.
+ if (!render_view_host_)
+ return;
+
+ if (action_taken_ != NO_ACTION) {
+ NOTREACHED();
+ return;
+ }
+ Disable();
+ action_taken_ = PROCEED_ACTION;
+
+ // Resumes the throbber, if applicable.
+ if (web_contents_was_loading_)
+ controller_->delegate()->SetIsLoading(
+ controller_->delegate()->GetRenderViewHost(), true, NULL);
+
+ // If this is a new navigation, the old page is going away, so we cancel any
+ // blocked requests for it. If it is not a new navigation, then it means the
+ // interstitial was shown as a result of a resource loading in the page.
+ // Since the user wants to proceed, we'll let any blocked request go through.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(CANCEL);
+ else
+ TakeActionOnResourceDispatcher(RESUME);
+
+ // No need to hide if we are a new navigation, we'll get hidden when the
+ // navigation is committed.
+ if (!new_navigation_) {
+ Hide();
+ delegate_->OnProceed();
+ return;
+ }
+
+ delegate_->OnProceed();
+}
+
+void InterstitialPageImpl::DontProceed() {
+ // Don't repeat this if we are already shutting down. We cannot check for
+ // enabled() here, because we may have called Disable without calling Hide.
+ if (!render_view_host_)
+ return;
+ DCHECK(action_taken_ != DONT_PROCEED_ACTION);
+
+ Disable();
+ action_taken_ = DONT_PROCEED_ACTION;
+
+ // If this is a new navigation, we are returning to the original page, so we
+ // resume blocked requests for it. If it is not a new navigation, then it
+ // means the interstitial was shown as a result of a resource loading in the
+ // page and we won't return to the original page, so we cancel blocked
+ // requests in that case.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(RESUME);
+ else
+ TakeActionOnResourceDispatcher(CANCEL);
+
+ if (should_discard_pending_nav_entry_) {
+ // Since no navigation happens we have to discard the transient entry
+ // explicitely. Note that by calling DiscardNonCommittedEntries() we also
+ // discard the pending entry, which is what we want, since the navigation is
+ // cancelled.
+ controller_->DiscardNonCommittedEntries();
+ }
+
+ if (reload_on_dont_proceed_)
+ controller_->Reload(true);
+
+ Hide();
+ delegate_->OnDontProceed();
+}
+
+void InterstitialPageImpl::CancelForNavigation() {
+ // The user is trying to navigate away. We should unblock the renderer and
+ // disable the interstitial, but keep it visible until the navigation
+ // completes.
+ Disable();
+ // If this interstitial was shown for a new navigation, allow any navigations
+ // on the original page to resume (e.g., subresource requests, XHRs, etc).
+ // Otherwise, cancel the pending, possibly dangerous navigations.
+ if (new_navigation_)
+ TakeActionOnResourceDispatcher(RESUME);
+ else
+ TakeActionOnResourceDispatcher(CANCEL);
+}
+
+void InterstitialPageImpl::SetSize(const gfx::Size& size) {
+ if (!enabled())
+ return;
+#if !defined(OS_MACOSX)
+ // When a tab is closed, we might be resized after our view was NULLed
+ // (typically if there was an info-bar).
+ if (render_view_host_->GetView())
+ render_view_host_->GetView()->SetSize(size);
+#else
+ // TODO(port): Does Mac need to SetSize?
+ NOTIMPLEMENTED();
+#endif
+}
+
+void InterstitialPageImpl::Focus() {
+ // Focus the native window.
+ if (!enabled())
+ return;
+ RenderWidgetHostViewPort::FromRWHV(render_view_host_->GetView())->Focus();
+}
+
+void InterstitialPageImpl::FocusThroughTabTraversal(bool reverse) {
+ if (!enabled())
+ return;
+ render_view_host_->SetInitialFocus(reverse);
+}
+
+RenderWidgetHostView* InterstitialPageImpl::GetView() {
+ return render_view_host_->GetView();
+}
+
+RenderViewHost* InterstitialPageImpl::GetRenderViewHostForTesting() const {
+ return render_view_host_;
+}
+
+#if defined(OS_ANDROID)
+RenderViewHost* InterstitialPageImpl::GetRenderViewHost() const {
+ return render_view_host_;
+}
+#endif
+
+InterstitialPageDelegate* InterstitialPageImpl::GetDelegateForTesting() {
+ return delegate_.get();
+}
+
+void InterstitialPageImpl::DontCreateViewForTesting() {
+ create_view_ = false;
+}
+
+gfx::Rect InterstitialPageImpl::GetRootWindowResizerRect() const {
+ return gfx::Rect();
+}
+
+void InterstitialPageImpl::CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace) {
+ NOTREACHED() << "InterstitialPage does not support showing popups yet.";
+}
+
+void InterstitialPageImpl::CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) {
+ NOTREACHED() << "InterstitialPage does not support showing drop-downs yet.";
+}
+
+void InterstitialPageImpl::CreateNewFullscreenWidget(int route_id) {
+ NOTREACHED()
+ << "InterstitialPage does not support showing full screen popups.";
+}
+
+void InterstitialPageImpl::ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ NOTREACHED() << "InterstitialPage does not support showing popups yet.";
+}
+
+void InterstitialPageImpl::ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {
+ NOTREACHED() << "InterstitialPage does not support showing drop-downs yet.";
+}
+
+void InterstitialPageImpl::ShowCreatedFullscreenWidget(int route_id) {
+ NOTREACHED()
+ << "InterstitialPage does not support showing full screen popups.";
+}
+
+SessionStorageNamespace* InterstitialPageImpl::GetSessionStorageNamespace(
+ SiteInstance* instance) {
+ return session_storage_namespace_.get();
+}
+
+FrameTree* InterstitialPageImpl::GetFrameTree() {
+ return &frame_tree_;
+}
+
+void InterstitialPageImpl::Disable() {
+ enabled_ = false;
+}
+
+void InterstitialPageImpl::Shutdown(RenderViewHostImpl* render_view_host) {
+ render_view_host->Shutdown();
+ // We are deleted now.
+}
+
+void InterstitialPageImpl::OnNavigatingAwayOrTabClosing() {
+ if (action_taken_ == NO_ACTION) {
+ // We are navigating away from the interstitial or closing a tab with an
+ // interstitial. Default to DontProceed(). We don't just call Hide as
+ // subclasses will almost certainly override DontProceed to do some work
+ // (ex: close pending connections).
+ DontProceed();
+ } else {
+ // User decided to proceed and either the navigation was committed or
+ // the tab was closed before that.
+ Hide();
+ }
+}
+
+void InterstitialPageImpl::TakeActionOnResourceDispatcher(
+ ResourceRequestAction action) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)) <<
+ "TakeActionOnResourceDispatcher should be called on the main thread.";
+
+ if (action == CANCEL || action == RESUME) {
+ if (resource_dispatcher_host_notified_)
+ return;
+ resource_dispatcher_host_notified_ = true;
+ }
+
+ // The tab might not have a render_view_host if it was closed (in which case,
+ // we have taken care of the blocked requests when processing
+ // NOTIFY_RENDER_WIDGET_HOST_DESTROYED.
+ // Also we need to test there is a ResourceDispatcherHostImpl, as when unit-
+ // tests we don't have one.
+ RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(original_child_id_,
+ original_rvh_id_);
+ if (!rvh || !ResourceDispatcherHostImpl::Get())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ResourceRequestHelper,
+ ResourceDispatcherHostImpl::Get(),
+ original_child_id_,
+ original_rvh_id_,
+ action));
+}
+
+InterstitialPageImpl::InterstitialPageRVHDelegateView::
+ InterstitialPageRVHDelegateView(InterstitialPageImpl* page)
+ : interstitial_page_(page) {
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::ShowPopupMenu(
+ const gfx::Rect& bounds,
+ int item_height,
+ double item_font_size,
+ int selected_item,
+ const std::vector<MenuItem>& items,
+ bool right_aligned,
+ bool allow_multiple_selection) {
+ NOTREACHED() << "InterstitialPage does not support showing popup menus.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::StartDragging(
+ const DropData& drop_data,
+ WebDragOperationsMask allowed_operations,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& image_offset,
+ const DragEventSourceInfo& event_info) {
+ NOTREACHED() << "InterstitialPage does not support dragging yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::UpdateDragCursor(
+ WebDragOperation) {
+ NOTREACHED() << "InterstitialPage does not support dragging yet.";
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::GotFocus() {
+ WebContents* web_contents = interstitial_page_->web_contents();
+ if (web_contents && web_contents->GetDelegate())
+ web_contents->GetDelegate()->WebContentsFocused(web_contents);
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::TakeFocus(
+ bool reverse) {
+ if (!interstitial_page_->web_contents())
+ return;
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(interstitial_page_->web_contents());
+ if (!web_contents->GetDelegateView())
+ return;
+
+ web_contents->GetDelegateView()->TakeFocus(reverse);
+}
+
+void InterstitialPageImpl::InterstitialPageRVHDelegateView::OnFindReply(
+ int request_id, int number_of_matches, const gfx::Rect& selection_rect,
+ int active_match_ordinal, bool final_update) {
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/interstitial_page_impl.h b/content/browser/frame_host/interstitial_page_impl.h
new file mode 100644
index 0000000..66c0ca9
--- /dev/null
+++ b/content/browser/frame_host/interstitial_page_impl.h
@@ -0,0 +1,267 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_INTERSTITIAL_PAGE_IMPL_H_
+#define CONTENT_BROWSER_FRAME_HOST_INTERSTITIAL_PAGE_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/frame_host/frame_tree.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/public/browser/interstitial_page.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/renderer_preferences.h"
+#include "url/gurl.h"
+
+namespace content {
+class NavigationEntry;
+class NavigationControllerImpl;
+class RenderViewHostImpl;
+class RenderWidgetHostView;
+class WebContentsView;
+class WebContentsImpl;
+
+enum ResourceRequestAction {
+ BLOCK,
+ RESUME,
+ CANCEL
+};
+
+class CONTENT_EXPORT InterstitialPageImpl
+ : public NON_EXPORTED_BASE(InterstitialPage),
+ public NotificationObserver,
+ public WebContentsObserver,
+ public RenderViewHostDelegate,
+ public RenderWidgetHostDelegate {
+ public:
+ // The different state of actions the user can take in an interstitial.
+ enum ActionState {
+ NO_ACTION, // No action has been taken yet.
+ PROCEED_ACTION, // "Proceed" was selected.
+ DONT_PROCEED_ACTION // "Don't proceed" was selected.
+ };
+
+ InterstitialPageImpl(WebContents* web_contents,
+ RenderWidgetHostDelegate* render_widget_host_delegate,
+ bool new_navigation,
+ const GURL& url,
+ InterstitialPageDelegate* delegate);
+ virtual ~InterstitialPageImpl();
+
+ // InterstitialPage implementation:
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual void DontProceed() OVERRIDE;
+ virtual void Proceed() OVERRIDE;
+ virtual RenderViewHost* GetRenderViewHostForTesting() const OVERRIDE;
+ virtual InterstitialPageDelegate* GetDelegateForTesting() OVERRIDE;
+ virtual void DontCreateViewForTesting() OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+
+ // Allows the user to navigate away by disabling the interstitial, canceling
+ // the pending request, and unblocking the hidden renderer. The interstitial
+ // will stay visible until the navigation completes.
+ void CancelForNavigation();
+
+ // Focus the first (last if reverse is true) element in the interstitial page.
+ // Called when tab traversing.
+ void FocusThroughTabTraversal(bool reverse);
+
+ RenderWidgetHostView* GetView();
+
+ // See description above field.
+ void set_reload_on_dont_proceed(bool value) {
+ reload_on_dont_proceed_ = value;
+ }
+ bool reload_on_dont_proceed() const { return reload_on_dont_proceed_; }
+
+#if defined(OS_ANDROID)
+ // Android shares a single platform window for all tabs, so we need to expose
+ // the RenderViewHost to properly route gestures to the interstitial.
+ RenderViewHost* GetRenderViewHost() const;
+#endif
+
+ protected:
+ // NotificationObserver method:
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // WebContentsObserver implementation:
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
+ virtual void NavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) OVERRIDE;
+
+ // RenderViewHostDelegate implementation:
+ virtual RenderViewHostDelegateView* GetDelegateView() OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual void RenderViewTerminated(RenderViewHost* render_view_host,
+ base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void DidNavigate(
+ RenderViewHost* render_view_host,
+ const ViewHostMsg_FrameNavigate_Params& params) OVERRIDE;
+ virtual void UpdateTitle(RenderViewHost* render_view_host,
+ int32 page_id,
+ const string16& title,
+ base::i18n::TextDirection title_direction) OVERRIDE;
+ virtual RendererPreferences GetRendererPrefs(
+ BrowserContext* browser_context) const OVERRIDE;
+ virtual WebPreferences GetWebkitPrefs() OVERRIDE;
+ virtual gfx::Rect GetRootWindowResizerRect() const OVERRIDE;
+ virtual void CreateNewWindow(
+ int route_id,
+ int main_frame_route_id,
+ const ViewHostMsg_CreateWindow_Params& params,
+ SessionStorageNamespace* session_storage_namespace) OVERRIDE;
+ virtual void CreateNewWidget(int route_id,
+ WebKit::WebPopupType popup_type) OVERRIDE;
+ virtual void CreateNewFullscreenWidget(int route_id) OVERRIDE;
+ virtual void ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) OVERRIDE;
+ virtual void ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) OVERRIDE;
+ virtual void ShowCreatedFullscreenWidget(int route_id) OVERRIDE;
+
+ virtual SessionStorageNamespace* GetSessionStorageNamespace(
+ SiteInstance* instance) OVERRIDE;
+
+ virtual FrameTree* GetFrameTree() OVERRIDE;
+
+ // RenderWidgetHostDelegate implementation:
+ virtual void RenderWidgetDeleted(
+ RenderWidgetHostImpl* render_widget_host) OVERRIDE;
+ virtual bool PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) OVERRIDE;
+ virtual void HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) OVERRIDE;
+#if defined(OS_WIN) && defined(USE_AURA)
+ virtual gfx::NativeViewAccessible GetParentNativeViewAccessible() OVERRIDE;
+#endif
+
+ bool enabled() const { return enabled_; }
+ WebContents* web_contents() const;
+ const GURL& url() const { return url_; }
+
+ // Creates the RenderViewHost containing the interstitial content.
+ // Overriden in unit tests.
+ virtual RenderViewHost* CreateRenderViewHost();
+
+ // Creates the WebContentsView that shows the interstitial RVH.
+ // Overriden in unit tests.
+ virtual WebContentsView* CreateWebContentsView();
+
+ // Notification magic.
+ NotificationRegistrar notification_registrar_;
+
+ private:
+ class InterstitialPageRVHDelegateView;
+
+ // Disable the interstitial:
+ // - if it is not yet showing, then it won't be shown.
+ // - any command sent by the RenderViewHost will be ignored.
+ void Disable();
+
+ // Shutdown the RVH. We will be deleted by the time this method returns.
+ void Shutdown(RenderViewHostImpl* render_view_host);
+
+ void OnNavigatingAwayOrTabClosing();
+
+ // Executes the passed action on the ResourceDispatcher (on the IO thread).
+ // Used to block/resume/cancel requests for the RenderViewHost hidden by this
+ // interstitial.
+ void TakeActionOnResourceDispatcher(ResourceRequestAction action);
+
+ // The contents in which we are displayed. This is valid until Hide is
+ // called, at which point it will be set to NULL because the WebContents
+ // itself may be deleted.
+ WebContents* web_contents_;
+
+ // The NavigationController for the content this page is being displayed over.
+ NavigationControllerImpl* controller_;
+
+ // Delegate for dispatching keyboard events and accessing the native view.
+ RenderWidgetHostDelegate* render_widget_host_delegate_;
+
+ // The URL that is shown when the interstitial is showing.
+ GURL url_;
+
+ // Whether this interstitial is shown as a result of a new navigation (in
+ // which case a transient navigation entry is created).
+ bool new_navigation_;
+
+ // Whether we should discard the pending navigation entry when not proceeding.
+ // This is to deal with cases where |new_navigation_| is true but a new
+ // pending entry was created since this interstitial was shown and we should
+ // not discard it.
+ bool should_discard_pending_nav_entry_;
+
+ // If true and the user chooses not to proceed the target NavigationController
+ // is reloaded. This is used when two NavigationControllers are merged
+ // (CopyStateFromAndPrune).
+ // The default is false.
+ bool reload_on_dont_proceed_;
+
+ // Whether this interstitial is enabled. See Disable() for more info.
+ bool enabled_;
+
+ // Whether the Proceed or DontProceed methods have been called yet.
+ ActionState action_taken_;
+
+ // The RenderViewHost displaying the interstitial contents. This is valid
+ // until Hide is called, at which point it will be set to NULL, signifying
+ // that shutdown has started.
+ RenderViewHostImpl* render_view_host_;
+
+ // The frame tree structure of the current page.
+ FrameTree frame_tree_;
+
+ // The IDs for the Render[View|Process]Host hidden by this interstitial.
+ int original_child_id_;
+ int original_rvh_id_;
+
+ // Whether or not we should change the title of the contents when hidden (to
+ // revert it to its original value).
+ bool should_revert_web_contents_title_;
+
+ // Whether or not the contents was loading resources when the interstitial was
+ // shown. We restore this state if the user proceeds from the interstitial.
+ bool web_contents_was_loading_;
+
+ // Whether the ResourceDispatcherHost has been notified to cancel/resume the
+ // resource requests blocked for the RenderViewHost.
+ bool resource_dispatcher_host_notified_;
+
+ // The original title of the contents that should be reverted to when the
+ // interstitial is hidden.
+ string16 original_web_contents_title_;
+
+ // Our RenderViewHostViewDelegate, necessary for accelerators to work.
+ scoped_ptr<InterstitialPageRVHDelegateView> rvh_delegate_view_;
+
+ // Settings passed to the renderer.
+ mutable RendererPreferences renderer_preferences_;
+
+ bool create_view_;
+
+ scoped_ptr<InterstitialPageDelegate> delegate_;
+
+ base::WeakPtrFactory<InterstitialPageImpl> weak_ptr_factory_;
+
+ scoped_refptr<SessionStorageNamespace> session_storage_namespace_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterstitialPageImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_INTERSTITIAL_PAGE_IMPL_H_
diff --git a/content/browser/frame_host/navigation_controller_delegate.h b/content/browser/frame_host/navigation_controller_delegate.h
new file mode 100644
index 0000000..cb3204c
--- /dev/null
+++ b/content/browser/frame_host/navigation_controller_delegate.h
@@ -0,0 +1,79 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_NAVIGATION_CONTROLLER_DELEGATE_H_
+#define CONTENT_BROWSER_FRAME_HOST_NAVIGATION_CONTROLLER_DELEGATE_H_
+
+#include <string>
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_details.h"
+
+namespace content {
+
+struct LoadCommittedDetails;
+struct LoadNotificationDetails;
+struct NativeWebKeyboardEvent;
+class InterstitialPage;
+class InterstitialPageImpl;
+class RenderViewHost;
+class SiteInstance;
+class WebContents;
+class WebContentsDelegate;
+
+// Interface for objects embedding a NavigationController to provide the
+// functionality NavigationController needs.
+// TODO(nasko): This interface should exist for short amount of time, while
+// we transition navigation code from WebContents to Navigator.
+class NavigationControllerDelegate {
+ public:
+ virtual ~NavigationControllerDelegate() {}
+
+ // Duplicates of WebContents methods.
+ virtual RenderViewHost* GetRenderViewHost() const = 0;
+ virtual InterstitialPage* GetInterstitialPage() const = 0;
+ virtual const std::string& GetContentsMimeType() const = 0;
+ virtual void NotifyNavigationStateChanged(unsigned changed_flags) = 0;
+ virtual void Stop() = 0;
+ virtual SiteInstance* GetSiteInstance() const = 0;
+ virtual SiteInstance* GetPendingSiteInstance() const = 0;
+ virtual int32 GetMaxPageID() = 0;
+ virtual int32 GetMaxPageIDForSiteInstance(SiteInstance* site_instance) = 0;
+ virtual bool IsLoading() const = 0;
+
+ // Methods from WebContentsImpl that NavigationControllerImpl needs to
+ // call.
+ virtual void NotifyBeforeFormRepostWarningShow() = 0;
+ virtual void NotifyNavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) = 0;
+ virtual bool NavigateToPendingEntry(
+ NavigationController::ReloadType reload_type) = 0;
+ virtual void SetHistoryLengthAndPrune(
+ const SiteInstance* site_instance,
+ int merge_history_length,
+ int32 minimum_page_id) = 0;
+ virtual void CopyMaxPageIDsFrom(WebContents* web_contents) = 0;
+ virtual void UpdateMaxPageID(int32 page_id) = 0;
+ virtual void UpdateMaxPageIDForSiteInstance(SiteInstance* site_instance,
+ int32 page_id) = 0;
+ virtual void ActivateAndShowRepostFormWarningDialog() = 0;
+
+ // This method is needed, since we are no longer guaranteed that the
+ // embedder for NavigationController will be a WebContents object.
+ virtual WebContents* GetWebContents() = 0;
+
+ // Methods needed by InterstitialPageImpl.
+ virtual bool IsHidden() = 0;
+ virtual void RenderViewForInterstitialPageCreated(
+ RenderViewHost* render_view_host) = 0;
+ virtual void AttachInterstitialPage(
+ InterstitialPageImpl* interstitial_page) = 0;
+ virtual void DetachInterstitialPage() = 0;
+ virtual void SetIsLoading(RenderViewHost* render_view_host,
+ bool is_loading,
+ LoadNotificationDetails* details) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_NAVIGATION_CONTROLLER_DELEGATE_H_
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
new file mode 100644
index 0000000..04c229a
--- /dev/null
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -0,0 +1,1700 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/navigation_controller_impl.h"
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h" // Temporary
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/browser_url_handler_impl.h"
+#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
+#include "content/browser/dom_storage/session_storage_namespace_impl.h"
+#include "content/browser/frame_host/debug_urls.h"
+#include "content/browser/frame_host/interstitial_page_impl.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
+#include "content/browser/frame_host/web_contents_screenshot_manager.h"
+#include "content/browser/renderer_host/render_view_host_impl.h" // Temporary
+#include "content/browser/site_instance_impl.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/invalidate_type.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/escape.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_util.h"
+#include "skia/ext/platform_canvas.h"
+
+namespace content {
+namespace {
+
+const int kInvalidateAll = 0xFFFFFFFF;
+
+// Invoked when entries have been pruned, or removed. For example, if the
+// current entries are [google, digg, yahoo], with the current entry google,
+// and the user types in cnet, then digg and yahoo are pruned.
+void NotifyPrunedEntries(NavigationControllerImpl* nav_controller,
+ bool from_front,
+ int count) {
+ PrunedDetails details;
+ details.from_front = from_front;
+ details.count = count;
+ NotificationService::current()->Notify(
+ NOTIFICATION_NAV_LIST_PRUNED,
+ Source<NavigationController>(nav_controller),
+ Details<PrunedDetails>(&details));
+}
+
+// Ensure the given NavigationEntry has a valid state, so that WebKit does not
+// get confused if we navigate back to it.
+//
+// An empty state is treated as a new navigation by WebKit, which would mean
+// losing the navigation entries and generating a new navigation entry after
+// this one. We don't want that. To avoid this we create a valid state which
+// WebKit will not treat as a new navigation.
+void SetPageStateIfEmpty(NavigationEntryImpl* entry) {
+ if (!entry->GetPageState().IsValid())
+ entry->SetPageState(PageState::CreateFromURL(entry->GetURL()));
+}
+
+NavigationEntryImpl::RestoreType ControllerRestoreTypeToEntryType(
+ NavigationController::RestoreType type) {
+ switch (type) {
+ case NavigationController::RESTORE_CURRENT_SESSION:
+ return NavigationEntryImpl::RESTORE_CURRENT_SESSION;
+ case NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY:
+ return NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY;
+ case NavigationController::RESTORE_LAST_SESSION_CRASHED:
+ return NavigationEntryImpl::RESTORE_LAST_SESSION_CRASHED;
+ }
+ NOTREACHED();
+ return NavigationEntryImpl::RESTORE_CURRENT_SESSION;
+}
+
+// Configure all the NavigationEntries in entries for restore. This resets
+// the transition type to reload and makes sure the content state isn't empty.
+void ConfigureEntriesForRestore(
+ std::vector<linked_ptr<NavigationEntryImpl> >* entries,
+ NavigationController::RestoreType type) {
+ for (size_t i = 0; i < entries->size(); ++i) {
+ // Use a transition type of reload so that we don't incorrectly increase
+ // the typed count.
+ (*entries)[i]->SetTransitionType(PAGE_TRANSITION_RELOAD);
+ (*entries)[i]->set_restore_type(ControllerRestoreTypeToEntryType(type));
+ // NOTE(darin): This code is only needed for backwards compat.
+ SetPageStateIfEmpty((*entries)[i].get());
+ }
+}
+
+// See NavigationController::IsURLInPageNavigation for how this works and why.
+bool AreURLsInPageNavigation(const GURL& existing_url,
+ const GURL& new_url,
+ bool renderer_says_in_page,
+ NavigationType navigation_type) {
+ if (existing_url == new_url)
+ return renderer_says_in_page;
+
+ if (!new_url.has_ref()) {
+ // When going back from the ref URL to the non ref one the navigation type
+ // is IN_PAGE.
+ return navigation_type == NAVIGATION_TYPE_IN_PAGE;
+ }
+
+ url_canon::Replacements<char> replacements;
+ replacements.ClearRef();
+ return existing_url.ReplaceComponents(replacements) ==
+ new_url.ReplaceComponents(replacements);
+}
+
+// Determines whether or not we should be carrying over a user agent override
+// between two NavigationEntries.
+bool ShouldKeepOverride(const NavigationEntry* last_entry) {
+ return last_entry && last_entry->GetIsOverridingUserAgent();
+}
+
+} // namespace
+
+// NavigationControllerImpl ----------------------------------------------------
+
+const size_t kMaxEntryCountForTestingNotSet = -1;
+
+// static
+size_t NavigationControllerImpl::max_entry_count_for_testing_ =
+ kMaxEntryCountForTestingNotSet;
+
+// Should Reload check for post data? The default is true, but is set to false
+// when testing.
+static bool g_check_for_repost = true;
+
+// static
+NavigationEntry* NavigationController::CreateNavigationEntry(
+ const GURL& url,
+ const Referrer& referrer,
+ PageTransition transition,
+ bool is_renderer_initiated,
+ const std::string& extra_headers,
+ BrowserContext* browser_context) {
+ // Allow the browser URL handler to rewrite the URL. This will, for example,
+ // remove "view-source:" from the beginning of the URL to get the URL that
+ // will actually be loaded. This real URL won't be shown to the user, just
+ // used internally.
+ GURL loaded_url(url);
+ bool reverse_on_redirect = false;
+ BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
+ &loaded_url, browser_context, &reverse_on_redirect);
+
+ NavigationEntryImpl* entry = new NavigationEntryImpl(
+ NULL, // The site instance for tabs is sent on navigation
+ // (WebContents::GetSiteInstance).
+ -1,
+ loaded_url,
+ referrer,
+ string16(),
+ transition,
+ is_renderer_initiated);
+ entry->SetVirtualURL(url);
+ entry->set_user_typed_url(url);
+ entry->set_update_virtual_url_with_url(reverse_on_redirect);
+ entry->set_extra_headers(extra_headers);
+ return entry;
+}
+
+// static
+void NavigationController::DisablePromptOnRepost() {
+ g_check_for_repost = false;
+}
+
+base::Time NavigationControllerImpl::TimeSmoother::GetSmoothedTime(
+ base::Time t) {
+ // If |t| is between the water marks, we're in a run of duplicates
+ // or just getting out of it, so increase the high-water mark to get
+ // a time that probably hasn't been used before and return it.
+ if (low_water_mark_ <= t && t <= high_water_mark_) {
+ high_water_mark_ += base::TimeDelta::FromMicroseconds(1);
+ return high_water_mark_;
+ }
+
+ // Otherwise, we're clear of the last duplicate run, so reset the
+ // water marks.
+ low_water_mark_ = high_water_mark_ = t;
+ return t;
+}
+
+NavigationControllerImpl::NavigationControllerImpl(
+ NavigationControllerDelegate* delegate,
+ BrowserContext* browser_context)
+ : browser_context_(browser_context),
+ pending_entry_(NULL),
+ last_committed_entry_index_(-1),
+ pending_entry_index_(-1),
+ transient_entry_index_(-1),
+ delegate_(delegate),
+ max_restored_page_id_(-1),
+ ssl_manager_(this),
+ needs_reload_(false),
+ is_initial_navigation_(true),
+ pending_reload_(NO_RELOAD),
+ get_timestamp_callback_(base::Bind(&base::Time::Now)),
+ screenshot_manager_(new WebContentsScreenshotManager(this)) {
+ DCHECK(browser_context_);
+}
+
+NavigationControllerImpl::~NavigationControllerImpl() {
+ DiscardNonCommittedEntriesInternal();
+}
+
+WebContents* NavigationControllerImpl::GetWebContents() const {
+ return delegate_->GetWebContents();
+}
+
+BrowserContext* NavigationControllerImpl::GetBrowserContext() const {
+ return browser_context_;
+}
+
+void NavigationControllerImpl::SetBrowserContext(
+ BrowserContext* browser_context) {
+ browser_context_ = browser_context;
+}
+
+void NavigationControllerImpl::Restore(
+ int selected_navigation,
+ RestoreType type,
+ std::vector<NavigationEntry*>* entries) {
+ // Verify that this controller is unused and that the input is valid.
+ DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
+ DCHECK(selected_navigation >= 0 &&
+ selected_navigation < static_cast<int>(entries->size()));
+
+ needs_reload_ = true;
+ for (size_t i = 0; i < entries->size(); ++i) {
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry((*entries)[i]);
+ entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
+ }
+ entries->clear();
+
+ // And finish the restore.
+ FinishRestore(selected_navigation, type);
+}
+
+void NavigationControllerImpl::Reload(bool check_for_repost) {
+ ReloadInternal(check_for_repost, RELOAD);
+}
+void NavigationControllerImpl::ReloadIgnoringCache(bool check_for_repost) {
+ ReloadInternal(check_for_repost, RELOAD_IGNORING_CACHE);
+}
+void NavigationControllerImpl::ReloadOriginalRequestURL(bool check_for_repost) {
+ ReloadInternal(check_for_repost, RELOAD_ORIGINAL_REQUEST_URL);
+}
+
+void NavigationControllerImpl::ReloadInternal(bool check_for_repost,
+ ReloadType reload_type) {
+ if (transient_entry_index_ != -1) {
+ // If an interstitial is showing, treat a reload as a navigation to the
+ // transient entry's URL.
+ NavigationEntryImpl* transient_entry =
+ NavigationEntryImpl::FromNavigationEntry(GetTransientEntry());
+ if (!transient_entry)
+ return;
+ LoadURL(transient_entry->GetURL(),
+ Referrer(),
+ PAGE_TRANSITION_RELOAD,
+ transient_entry->extra_headers());
+ return;
+ }
+
+ NavigationEntryImpl* entry = NULL;
+ int current_index = -1;
+
+ // If we are reloading the initial navigation, just use the current
+ // pending entry. Otherwise look up the current entry.
+ if (IsInitialNavigation() && pending_entry_) {
+ entry = pending_entry_;
+ // The pending entry might be in entries_ (e.g., after a Clone), so we
+ // should also update the current_index.
+ current_index = pending_entry_index_;
+ } else {
+ DiscardNonCommittedEntriesInternal();
+ current_index = GetCurrentEntryIndex();
+ if (current_index != -1) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ GetEntryAtIndex(current_index));
+ }
+ }
+
+ // If we are no where, then we can't reload. TODO(darin): We should add a
+ // CanReload method.
+ if (!entry)
+ return;
+
+ if (reload_type == NavigationControllerImpl::RELOAD_ORIGINAL_REQUEST_URL &&
+ entry->GetOriginalRequestURL().is_valid() && !entry->GetHasPostData()) {
+ // We may have been redirected when navigating to the current URL.
+ // Use the URL the user originally intended to visit, if it's valid and if a
+ // POST wasn't involved; the latter case avoids issues with sending data to
+ // the wrong page.
+ entry->SetURL(entry->GetOriginalRequestURL());
+ }
+
+ if (g_check_for_repost && check_for_repost &&
+ entry->GetHasPostData()) {
+ // The user is asking to reload a page with POST data. Prompt to make sure
+ // they really want to do this. If they do, the dialog will call us back
+ // with check_for_repost = false.
+ delegate_->NotifyBeforeFormRepostWarningShow();
+
+ pending_reload_ = reload_type;
+ delegate_->ActivateAndShowRepostFormWarningDialog();
+ } else {
+ if (!IsInitialNavigation())
+ DiscardNonCommittedEntriesInternal();
+
+ // If we are reloading an entry that no longer belongs to the current
+ // site instance (for example, refreshing a page for just installed app),
+ // the reload must happen in a new process.
+ // The new entry must have a new page_id and site instance, so it behaves
+ // as new navigation (which happens to clear forward history).
+ // Tabs that are discarded due to low memory conditions may not have a site
+ // instance, and should not be treated as a cross-site reload.
+ SiteInstanceImpl* site_instance = entry->site_instance();
+ if (site_instance &&
+ site_instance->HasWrongProcessForURL(entry->GetURL())) {
+ // Create a navigation entry that resembles the current one, but do not
+ // copy page id, site instance, content state, or timestamp.
+ NavigationEntryImpl* nav_entry = NavigationEntryImpl::FromNavigationEntry(
+ CreateNavigationEntry(
+ entry->GetURL(), entry->GetReferrer(), entry->GetTransitionType(),
+ false, entry->extra_headers(), browser_context_));
+
+ // Mark the reload type as NO_RELOAD, so navigation will not be considered
+ // a reload in the renderer.
+ reload_type = NavigationController::NO_RELOAD;
+
+ nav_entry->set_should_replace_entry(true);
+ pending_entry_ = nav_entry;
+ } else {
+ pending_entry_ = entry;
+ pending_entry_index_ = current_index;
+
+ // The title of the page being reloaded might have been removed in the
+ // meanwhile, so we need to revert to the default title upon reload and
+ // invalidate the previously cached title (SetTitle will do both).
+ // See Chromium issue 96041.
+ pending_entry_->SetTitle(string16());
+
+ pending_entry_->SetTransitionType(PAGE_TRANSITION_RELOAD);
+ }
+
+ NavigateToPendingEntry(reload_type);
+ }
+}
+
+void NavigationControllerImpl::CancelPendingReload() {
+ DCHECK(pending_reload_ != NO_RELOAD);
+ pending_reload_ = NO_RELOAD;
+}
+
+void NavigationControllerImpl::ContinuePendingReload() {
+ if (pending_reload_ == NO_RELOAD) {
+ NOTREACHED();
+ } else {
+ ReloadInternal(false, pending_reload_);
+ pending_reload_ = NO_RELOAD;
+ }
+}
+
+bool NavigationControllerImpl::IsInitialNavigation() const {
+ return is_initial_navigation_;
+}
+
+NavigationEntryImpl* NavigationControllerImpl::GetEntryWithPageID(
+ SiteInstance* instance, int32 page_id) const {
+ int index = GetEntryIndexWithPageID(instance, page_id);
+ return (index != -1) ? entries_[index].get() : NULL;
+}
+
+void NavigationControllerImpl::LoadEntry(NavigationEntryImpl* entry) {
+ // When navigating to a new page, we don't know for sure if we will actually
+ // end up leaving the current page. The new page load could for example
+ // result in a download or a 'no content' response (e.g., a mailto: URL).
+ SetPendingEntry(entry);
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::SetPendingEntry(NavigationEntryImpl* entry) {
+ DiscardNonCommittedEntriesInternal();
+ pending_entry_ = entry;
+ NotificationService::current()->Notify(
+ NOTIFICATION_NAV_ENTRY_PENDING,
+ Source<NavigationController>(this),
+ Details<NavigationEntry>(entry));
+}
+
+NavigationEntry* NavigationControllerImpl::GetActiveEntry() const {
+ if (transient_entry_index_ != -1)
+ return entries_[transient_entry_index_].get();
+ if (pending_entry_)
+ return pending_entry_;
+ return GetLastCommittedEntry();
+}
+
+NavigationEntry* NavigationControllerImpl::GetVisibleEntry() const {
+ if (transient_entry_index_ != -1)
+ return entries_[transient_entry_index_].get();
+ // The pending entry is safe to return for new (non-history), browser-
+ // initiated navigations. Most renderer-initiated navigations should not
+ // show the pending entry, to prevent URL spoof attacks.
+ //
+ // We make an exception for renderer-initiated navigations in new tabs, as
+ // long as no other page has tried to access the initial empty document in
+ // the new tab. If another page modifies this blank page, a URL spoof is
+ // possible, so we must stop showing the pending entry.
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ delegate_->GetRenderViewHost());
+ bool safe_to_show_pending =
+ pending_entry_ &&
+ // Require a new navigation.
+ pending_entry_->GetPageID() == -1 &&
+ // Require either browser-initiated or an unmodified new tab.
+ (!pending_entry_->is_renderer_initiated() ||
+ (IsInitialNavigation() &&
+ !GetLastCommittedEntry() &&
+ !rvh->has_accessed_initial_document()));
+
+ // Also allow showing the pending entry for history navigations in a new tab,
+ // such as Ctrl+Back. In this case, no existing page is visible and no one
+ // can script the new tab before it commits.
+ if (!safe_to_show_pending &&
+ pending_entry_ &&
+ pending_entry_->GetPageID() != -1 &&
+ IsInitialNavigation() &&
+ !pending_entry_->is_renderer_initiated())
+ safe_to_show_pending = true;
+
+ if (safe_to_show_pending)
+ return pending_entry_;
+ return GetLastCommittedEntry();
+}
+
+int NavigationControllerImpl::GetCurrentEntryIndex() const {
+ if (transient_entry_index_ != -1)
+ return transient_entry_index_;
+ if (pending_entry_index_ != -1)
+ return pending_entry_index_;
+ return last_committed_entry_index_;
+}
+
+NavigationEntry* NavigationControllerImpl::GetLastCommittedEntry() const {
+ if (last_committed_entry_index_ == -1)
+ return NULL;
+ return entries_[last_committed_entry_index_].get();
+}
+
+bool NavigationControllerImpl::CanViewSource() const {
+ const std::string& mime_type = delegate_->GetContentsMimeType();
+ bool is_viewable_mime_type = net::IsSupportedNonImageMimeType(mime_type) &&
+ !net::IsSupportedMediaMimeType(mime_type);
+ NavigationEntry* visible_entry = GetVisibleEntry();
+ return visible_entry && !visible_entry->IsViewSourceMode() &&
+ is_viewable_mime_type && !delegate_->GetInterstitialPage();
+}
+
+int NavigationControllerImpl::GetLastCommittedEntryIndex() const {
+ return last_committed_entry_index_;
+}
+
+int NavigationControllerImpl::GetEntryCount() const {
+ DCHECK(entries_.size() <= max_entry_count());
+ return static_cast<int>(entries_.size());
+}
+
+NavigationEntry* NavigationControllerImpl::GetEntryAtIndex(
+ int index) const {
+ return entries_.at(index).get();
+}
+
+NavigationEntry* NavigationControllerImpl::GetEntryAtOffset(
+ int offset) const {
+ int index = GetIndexForOffset(offset);
+ if (index < 0 || index >= GetEntryCount())
+ return NULL;
+
+ return entries_[index].get();
+}
+
+int NavigationControllerImpl::GetIndexForOffset(int offset) const {
+ return GetCurrentEntryIndex() + offset;
+}
+
+void NavigationControllerImpl::TakeScreenshot() {
+ screenshot_manager_->TakeScreenshot();
+}
+
+void NavigationControllerImpl::SetScreenshotManager(
+ WebContentsScreenshotManager* manager) {
+ screenshot_manager_.reset(manager ? manager :
+ new WebContentsScreenshotManager(this));
+}
+
+bool NavigationControllerImpl::CanGoBack() const {
+ return entries_.size() > 1 && GetCurrentEntryIndex() > 0;
+}
+
+bool NavigationControllerImpl::CanGoForward() const {
+ int index = GetCurrentEntryIndex();
+ return index >= 0 && index < (static_cast<int>(entries_.size()) - 1);
+}
+
+bool NavigationControllerImpl::CanGoToOffset(int offset) const {
+ int index = GetIndexForOffset(offset);
+ return index >= 0 && index < GetEntryCount();
+}
+
+void NavigationControllerImpl::GoBack() {
+ if (!CanGoBack()) {
+ NOTREACHED();
+ return;
+ }
+
+ // Base the navigation on where we are now...
+ int current_index = GetCurrentEntryIndex();
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = current_index - 1;
+ entries_[pending_entry_index_]->SetTransitionType(
+ PageTransitionFromInt(
+ entries_[pending_entry_index_]->GetTransitionType() |
+ PAGE_TRANSITION_FORWARD_BACK));
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::GoForward() {
+ if (!CanGoForward()) {
+ NOTREACHED();
+ return;
+ }
+
+ bool transient = (transient_entry_index_ != -1);
+
+ // Base the navigation on where we are now...
+ int current_index = GetCurrentEntryIndex();
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = current_index;
+ // If there was a transient entry, we removed it making the current index
+ // the next page.
+ if (!transient)
+ pending_entry_index_++;
+
+ entries_[pending_entry_index_]->SetTransitionType(
+ PageTransitionFromInt(
+ entries_[pending_entry_index_]->GetTransitionType() |
+ PAGE_TRANSITION_FORWARD_BACK));
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::GoToIndex(int index) {
+ if (index < 0 || index >= static_cast<int>(entries_.size())) {
+ NOTREACHED();
+ return;
+ }
+
+ if (transient_entry_index_ != -1) {
+ if (index == transient_entry_index_) {
+ // Nothing to do when navigating to the transient.
+ return;
+ }
+ if (index > transient_entry_index_) {
+ // Removing the transient is goint to shift all entries by 1.
+ index--;
+ }
+ }
+
+ DiscardNonCommittedEntries();
+
+ pending_entry_index_ = index;
+ entries_[pending_entry_index_]->SetTransitionType(
+ PageTransitionFromInt(
+ entries_[pending_entry_index_]->GetTransitionType() |
+ PAGE_TRANSITION_FORWARD_BACK));
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::GoToOffset(int offset) {
+ if (!CanGoToOffset(offset))
+ return;
+
+ GoToIndex(GetIndexForOffset(offset));
+}
+
+bool NavigationControllerImpl::RemoveEntryAtIndex(int index) {
+ if (index == last_committed_entry_index_ ||
+ index == pending_entry_index_)
+ return false;
+
+ RemoveEntryAtIndexInternal(index);
+ return true;
+}
+
+void NavigationControllerImpl::UpdateVirtualURLToURL(
+ NavigationEntryImpl* entry, const GURL& new_url) {
+ GURL new_virtual_url(new_url);
+ if (BrowserURLHandlerImpl::GetInstance()->ReverseURLRewrite(
+ &new_virtual_url, entry->GetVirtualURL(), browser_context_)) {
+ entry->SetVirtualURL(new_virtual_url);
+ }
+}
+
+void NavigationControllerImpl::LoadURL(
+ const GURL& url,
+ const Referrer& referrer,
+ PageTransition transition,
+ const std::string& extra_headers) {
+ LoadURLParams params(url);
+ params.referrer = referrer;
+ params.transition_type = transition;
+ params.extra_headers = extra_headers;
+ LoadURLWithParams(params);
+}
+
+void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
+ TRACE_EVENT0("browser", "NavigationControllerImpl::LoadURLWithParams");
+ if (HandleDebugURL(params.url, params.transition_type))
+ return;
+
+ // Checks based on params.load_type.
+ switch (params.load_type) {
+ case LOAD_TYPE_DEFAULT:
+ break;
+ case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
+ if (!params.url.SchemeIs(kHttpScheme) &&
+ !params.url.SchemeIs(kHttpsScheme)) {
+ NOTREACHED() << "Http post load must use http(s) scheme.";
+ return;
+ }
+ break;
+ case LOAD_TYPE_DATA:
+ if (!params.url.SchemeIs(chrome::kDataScheme)) {
+ NOTREACHED() << "Data load must use data scheme.";
+ return;
+ }
+ break;
+ default:
+ NOTREACHED();
+ break;
+ };
+
+ // The user initiated a load, we don't need to reload anymore.
+ needs_reload_ = false;
+
+ bool override = false;
+ switch (params.override_user_agent) {
+ case UA_OVERRIDE_INHERIT:
+ override = ShouldKeepOverride(GetLastCommittedEntry());
+ break;
+ case UA_OVERRIDE_TRUE:
+ override = true;
+ break;
+ case UA_OVERRIDE_FALSE:
+ override = false;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ CreateNavigationEntry(
+ params.url,
+ params.referrer,
+ params.transition_type,
+ params.is_renderer_initiated,
+ params.extra_headers,
+ browser_context_));
+ if (params.redirect_chain.size() > 0)
+ entry->set_redirect_chain(params.redirect_chain);
+ if (params.should_replace_current_entry)
+ entry->set_should_replace_entry(true);
+ entry->set_should_clear_history_list(params.should_clear_history_list);
+ entry->SetIsOverridingUserAgent(override);
+ entry->set_transferred_global_request_id(
+ params.transferred_global_request_id);
+ entry->SetFrameToNavigate(params.frame_name);
+
+ switch (params.load_type) {
+ case LOAD_TYPE_DEFAULT:
+ break;
+ case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
+ entry->SetHasPostData(true);
+ entry->SetBrowserInitiatedPostData(
+ params.browser_initiated_post_data.get());
+ break;
+ case LOAD_TYPE_DATA:
+ entry->SetBaseURLForDataURL(params.base_url_for_data_url);
+ entry->SetVirtualURL(params.virtual_url_for_data_url);
+ entry->SetCanLoadLocalResources(params.can_load_local_resources);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ };
+
+ LoadEntry(entry);
+}
+
+bool NavigationControllerImpl::RendererDidNavigate(
+ const ViewHostMsg_FrameNavigate_Params& params,
+ LoadCommittedDetails* details) {
+ is_initial_navigation_ = false;
+
+ // Save the previous state before we clobber it.
+ if (GetLastCommittedEntry()) {
+ details->previous_url = GetLastCommittedEntry()->GetURL();
+ details->previous_entry_index = GetLastCommittedEntryIndex();
+ } else {
+ details->previous_url = GURL();
+ details->previous_entry_index = -1;
+ }
+
+ // If we have a pending entry at this point, it should have a SiteInstance.
+ // Restored entries start out with a null SiteInstance, but we should have
+ // assigned one in NavigateToPendingEntry.
+ DCHECK(pending_entry_index_ == -1 || pending_entry_->site_instance());
+
+ // If we are doing a cross-site reload, we need to replace the existing
+ // navigation entry, not add another entry to the history. This has the side
+ // effect of removing forward browsing history, if such existed.
+ // Or if we are doing a cross-site redirect navigation,
+ // we will do a similar thing.
+ details->did_replace_entry =
+ pending_entry_ && pending_entry_->should_replace_entry();
+
+ // Do navigation-type specific actions. These will make and commit an entry.
+ details->type = ClassifyNavigation(params);
+
+ // is_in_page must be computed before the entry gets committed.
+ details->is_in_page = IsURLInPageNavigation(
+ params.url, params.was_within_same_page, details->type);
+
+ switch (details->type) {
+ case NAVIGATION_TYPE_NEW_PAGE:
+ RendererDidNavigateToNewPage(params, details->did_replace_entry);
+ break;
+ case NAVIGATION_TYPE_EXISTING_PAGE:
+ RendererDidNavigateToExistingPage(params);
+ break;
+ case NAVIGATION_TYPE_SAME_PAGE:
+ RendererDidNavigateToSamePage(params);
+ break;
+ case NAVIGATION_TYPE_IN_PAGE:
+ RendererDidNavigateInPage(params, &details->did_replace_entry);
+ break;
+ case NAVIGATION_TYPE_NEW_SUBFRAME:
+ RendererDidNavigateNewSubframe(params);
+ break;
+ case NAVIGATION_TYPE_AUTO_SUBFRAME:
+ if (!RendererDidNavigateAutoSubframe(params))
+ return false;
+ break;
+ case NAVIGATION_TYPE_NAV_IGNORE:
+ // If a pending navigation was in progress, this canceled it. We should
+ // discard it and make sure it is removed from the URL bar. After that,
+ // there is nothing we can do with this navigation, so we just return to
+ // the caller that nothing has happened.
+ if (pending_entry_) {
+ DiscardNonCommittedEntries();
+ delegate_->NotifyNavigationStateChanged(INVALIDATE_TYPE_URL);
+ }
+ return false;
+ default:
+ NOTREACHED();
+ }
+
+ // At this point, we know that the navigation has just completed, so
+ // record the time.
+ //
+ // TODO(akalin): Use "sane time" as described in
+ // http://www.chromium.org/developers/design-documents/sane-time .
+ base::Time timestamp =
+ time_smoother_.GetSmoothedTime(get_timestamp_callback_.Run());
+ DVLOG(1) << "Navigation finished at (smoothed) timestamp "
+ << timestamp.ToInternalValue();
+
+ // We should not have a pending entry anymore. Clear it again in case any
+ // error cases above forgot to do so.
+ DiscardNonCommittedEntriesInternal();
+
+ // All committed entries should have nonempty content state so WebKit doesn't
+ // get confused when we go back to them (see the function for details).
+ DCHECK(params.page_state.IsValid());
+ NavigationEntryImpl* active_entry =
+ NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry());
+ active_entry->SetTimestamp(timestamp);
+ active_entry->SetHttpStatusCode(params.http_status_code);
+ active_entry->SetPageState(params.page_state);
+
+ // Once it is committed, we no longer need to track several pieces of state on
+ // the entry.
+ active_entry->ResetForCommit();
+
+ // The active entry's SiteInstance should match our SiteInstance.
+ CHECK(active_entry->site_instance() == delegate_->GetSiteInstance());
+
+ // Remember the bindings the renderer process has at this point, so that
+ // we do not grant this entry additional bindings if we come back to it.
+ active_entry->SetBindings(
+ delegate_->GetRenderViewHost()->GetEnabledBindings());
+
+ // Now prep the rest of the details for the notification and broadcast.
+ details->entry = active_entry;
+ details->is_main_frame =
+ PageTransitionIsMainFrame(params.transition);
+ details->serialized_security_info = params.security_info;
+ details->http_status_code = params.http_status_code;
+ NotifyNavigationEntryCommitted(details);
+
+ return true;
+}
+
+NavigationType NavigationControllerImpl::ClassifyNavigation(
+ const ViewHostMsg_FrameNavigate_Params& params) const {
+ if (params.page_id == -1) {
+ // The renderer generates the page IDs, and so if it gives us the invalid
+ // page ID (-1) we know it didn't actually navigate. This happens in a few
+ // cases:
+ //
+ // - If a page makes a popup navigated to about blank, and then writes
+ // stuff like a subframe navigated to a real page. We'll get the commit
+ // for the subframe, but there won't be any commit for the outer page.
+ //
+ // - We were also getting these for failed loads (for example, bug 21849).
+ // The guess is that we get a "load commit" for the alternate error page,
+ // but that doesn't affect the page ID, so we get the "old" one, which
+ // could be invalid. This can also happen for a cross-site transition
+ // that causes us to swap processes. Then the error page load will be in
+ // a new process with no page IDs ever assigned (and hence a -1 value),
+ // yet the navigation controller still might have previous pages in its
+ // list.
+ //
+ // In these cases, there's nothing we can do with them, so ignore.
+ return NAVIGATION_TYPE_NAV_IGNORE;
+ }
+
+ if (params.page_id > delegate_->GetMaxPageID()) {
+ // Greater page IDs than we've ever seen before are new pages. We may or may
+ // not have a pending entry for the page, and this may or may not be the
+ // main frame.
+ if (PageTransitionIsMainFrame(params.transition))
+ return NAVIGATION_TYPE_NEW_PAGE;
+
+ // When this is a new subframe navigation, we should have a committed page
+ // for which it's a suframe in. This may not be the case when an iframe is
+ // navigated on a popup navigated to about:blank (the iframe would be
+ // written into the popup by script on the main page). For these cases,
+ // there isn't any navigation stuff we can do, so just ignore it.
+ if (!GetLastCommittedEntry())
+ return NAVIGATION_TYPE_NAV_IGNORE;
+
+ // Valid subframe navigation.
+ return NAVIGATION_TYPE_NEW_SUBFRAME;
+ }
+
+ // We only clear the session history when navigating to a new page.
+ DCHECK(!params.history_list_was_cleared);
+
+ // Now we know that the notification is for an existing page. Find that entry.
+ int existing_entry_index = GetEntryIndexWithPageID(
+ delegate_->GetSiteInstance(),
+ params.page_id);
+ if (existing_entry_index == -1) {
+ // The page was not found. It could have been pruned because of the limit on
+ // back/forward entries (not likely since we'll usually tell it to navigate
+ // to such entries). It could also mean that the renderer is smoking crack.
+ NOTREACHED();
+
+ // Because the unknown entry has committed, we risk showing the wrong URL in
+ // release builds. Instead, we'll kill the renderer process to be safe.
+ LOG(ERROR) << "terminating renderer for bad navigation: " << params.url;
+ RecordAction(UserMetricsAction("BadMessageTerminate_NC"));
+
+ // Temporary code so we can get more information. Format:
+ // http://url/foo.html#page1#max3#frame1#ids:2_Nx,1_1x,3_2
+ std::string temp = params.url.spec();
+ temp.append("#page");
+ temp.append(base::IntToString(params.page_id));
+ temp.append("#max");
+ temp.append(base::IntToString(delegate_->GetMaxPageID()));
+ temp.append("#frame");
+ temp.append(base::IntToString(params.frame_id));
+ temp.append("#ids");
+ for (int i = 0; i < static_cast<int>(entries_.size()); ++i) {
+ // Append entry metadata (e.g., 3_7x):
+ // 3: page_id
+ // 7: SiteInstance ID, or N for null
+ // x: appended if not from the current SiteInstance
+ temp.append(base::IntToString(entries_[i]->GetPageID()));
+ temp.append("_");
+ if (entries_[i]->site_instance())
+ temp.append(base::IntToString(entries_[i]->site_instance()->GetId()));
+ else
+ temp.append("N");
+ if (entries_[i]->site_instance() != delegate_->GetSiteInstance())
+ temp.append("x");
+ temp.append(",");
+ }
+ GURL url(temp);
+ static_cast<RenderViewHostImpl*>(
+ delegate_->GetRenderViewHost())->Send(
+ new ViewMsg_TempCrashWithData(url));
+ return NAVIGATION_TYPE_NAV_IGNORE;
+ }
+ NavigationEntryImpl* existing_entry = entries_[existing_entry_index].get();
+
+ if (!PageTransitionIsMainFrame(params.transition)) {
+ // All manual subframes would get new IDs and were handled above, so we
+ // know this is auto. Since the current page was found in the navigation
+ // entry list, we're guaranteed to have a last committed entry.
+ DCHECK(GetLastCommittedEntry());
+ return NAVIGATION_TYPE_AUTO_SUBFRAME;
+ }
+
+ // Anything below here we know is a main frame navigation.
+ if (pending_entry_ &&
+ !pending_entry_->is_renderer_initiated() &&
+ existing_entry != pending_entry_ &&
+ pending_entry_->GetPageID() == -1 &&
+ existing_entry == GetLastCommittedEntry()) {
+ // In this case, we have a pending entry for a URL but WebCore didn't do a
+ // new navigation. This happens when you press enter in the URL bar to
+ // reload. We will create a pending entry, but WebKit will convert it to
+ // a reload since it's the same page and not create a new entry for it
+ // (the user doesn't want to have a new back/forward entry when they do
+ // this). If this matches the last committed entry, we want to just ignore
+ // the pending entry and go back to where we were (the "existing entry").
+ return NAVIGATION_TYPE_SAME_PAGE;
+ }
+
+ // Any toplevel navigations with the same base (minus the reference fragment)
+ // are in-page navigations. We weeded out subframe navigations above. Most of
+ // the time this doesn't matter since WebKit doesn't tell us about subframe
+ // navigations that don't actually navigate, but it can happen when there is
+ // an encoding override (it always sends a navigation request).
+ if (AreURLsInPageNavigation(existing_entry->GetURL(), params.url,
+ params.was_within_same_page,
+ NAVIGATION_TYPE_UNKNOWN)) {
+ return NAVIGATION_TYPE_IN_PAGE;
+ }
+
+ // Since we weeded out "new" navigations above, we know this is an existing
+ // (back/forward) navigation.
+ return NAVIGATION_TYPE_EXISTING_PAGE;
+}
+
+bool NavigationControllerImpl::IsRedirect(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // For main frame transition, we judge by params.transition.
+ // Otherwise, by params.redirects.
+ if (PageTransitionIsMainFrame(params.transition)) {
+ return PageTransitionIsRedirect(params.transition);
+ }
+ return params.redirects.size() > 1;
+}
+
+void NavigationControllerImpl::RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool replace_entry) {
+ NavigationEntryImpl* new_entry;
+ bool update_virtual_url;
+ // Only make a copy of the pending entry if it is appropriate for the new page
+ // that was just loaded. We verify this at a coarse grain by checking that
+ // the SiteInstance hasn't been assigned to something else.
+ if (pending_entry_ &&
+ (!pending_entry_->site_instance() ||
+ pending_entry_->site_instance() == delegate_->GetSiteInstance())) {
+ new_entry = new NavigationEntryImpl(*pending_entry_);
+
+ // Don't use the page type from the pending entry. Some interstitial page
+ // may have set the type to interstitial. Once we commit, however, the page
+ // type must always be normal.
+ new_entry->set_page_type(PAGE_TYPE_NORMAL);
+ update_virtual_url = new_entry->update_virtual_url_with_url();
+ } else {
+ new_entry = new NavigationEntryImpl;
+
+ // Find out whether the new entry needs to update its virtual URL on URL
+ // change and set up the entry accordingly. This is needed to correctly
+ // update the virtual URL when replaceState is called after a pushState.
+ GURL url = params.url;
+ bool needs_update = false;
+ BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
+ &url, browser_context_, &needs_update);
+ new_entry->set_update_virtual_url_with_url(needs_update);
+
+ // When navigating to a new page, give the browser URL handler a chance to
+ // update the virtual URL based on the new URL. For example, this is needed
+ // to show chrome://bookmarks/#1 when the bookmarks webui extension changes
+ // the URL.
+ update_virtual_url = needs_update;
+ }
+
+ new_entry->SetURL(params.url);
+ if (update_virtual_url)
+ UpdateVirtualURLToURL(new_entry, params.url);
+ new_entry->SetReferrer(params.referrer);
+ new_entry->SetPageID(params.page_id);
+ new_entry->SetTransitionType(params.transition);
+ new_entry->set_site_instance(
+ static_cast<SiteInstanceImpl*>(delegate_->GetSiteInstance()));
+ new_entry->SetHasPostData(params.is_post);
+ new_entry->SetPostID(params.post_id);
+ new_entry->SetOriginalRequestURL(params.original_request_url);
+ new_entry->SetIsOverridingUserAgent(params.is_overriding_user_agent);
+
+ DCHECK(!params.history_list_was_cleared || !replace_entry);
+ // The browser requested to clear the session history when it initiated the
+ // navigation. Now we know that the renderer has updated its state accordingly
+ // and it is safe to also clear the browser side history.
+ if (params.history_list_was_cleared) {
+ DiscardNonCommittedEntriesInternal();
+ entries_.clear();
+ last_committed_entry_index_ = -1;
+ }
+
+ InsertOrReplaceEntry(new_entry, replace_entry);
+}
+
+void NavigationControllerImpl::RendererDidNavigateToExistingPage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // We should only get here for main frame navigations.
+ DCHECK(PageTransitionIsMainFrame(params.transition));
+
+ // This is a back/forward navigation. The existing page for the ID is
+ // guaranteed to exist by ClassifyNavigation, and we just need to update it
+ // with new information from the renderer.
+ int entry_index = GetEntryIndexWithPageID(delegate_->GetSiteInstance(),
+ params.page_id);
+ DCHECK(entry_index >= 0 &&
+ entry_index < static_cast<int>(entries_.size()));
+ NavigationEntryImpl* entry = entries_[entry_index].get();
+
+ // The URL may have changed due to redirects.
+ entry->SetURL(params.url);
+ if (entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(entry, params.url);
+
+ // The redirected to page should not inherit the favicon from the previous
+ // page.
+ if (PageTransitionIsRedirect(params.transition))
+ entry->GetFavicon() = FaviconStatus();
+
+ // The site instance will normally be the same except during session restore,
+ // when no site instance will be assigned.
+ DCHECK(entry->site_instance() == NULL ||
+ entry->site_instance() == delegate_->GetSiteInstance());
+ entry->set_site_instance(
+ static_cast<SiteInstanceImpl*>(delegate_->GetSiteInstance()));
+
+ entry->SetHasPostData(params.is_post);
+ entry->SetPostID(params.post_id);
+
+ // The entry we found in the list might be pending if the user hit
+ // back/forward/reload. This load should commit it (since it's already in the
+ // list, we can just discard the pending pointer). We should also discard the
+ // pending entry if it corresponds to a different navigation, since that one
+ // is now likely canceled. If it is not canceled, we will treat it as a new
+ // navigation when it arrives, which is also ok.
+ //
+ // Note that we need to use the "internal" version since we don't want to
+ // actually change any other state, just kill the pointer.
+ DiscardNonCommittedEntriesInternal();
+
+ // If a transient entry was removed, the indices might have changed, so we
+ // have to query the entry index again.
+ last_committed_entry_index_ =
+ GetEntryIndexWithPageID(delegate_->GetSiteInstance(), params.page_id);
+}
+
+void NavigationControllerImpl::RendererDidNavigateToSamePage(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // This mode implies we have a pending entry that's the same as an existing
+ // entry for this page ID. This entry is guaranteed to exist by
+ // ClassifyNavigation. All we need to do is update the existing entry.
+ NavigationEntryImpl* existing_entry = GetEntryWithPageID(
+ delegate_->GetSiteInstance(), params.page_id);
+
+ // We assign the entry's unique ID to be that of the new one. Since this is
+ // always the result of a user action, we want to dismiss infobars, etc. like
+ // a regular user-initiated navigation.
+ existing_entry->set_unique_id(pending_entry_->GetUniqueID());
+
+ // The URL may have changed due to redirects.
+ if (existing_entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(existing_entry, params.url);
+ existing_entry->SetURL(params.url);
+
+ DiscardNonCommittedEntries();
+}
+
+void NavigationControllerImpl::RendererDidNavigateInPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) {
+ DCHECK(PageTransitionIsMainFrame(params.transition)) <<
+ "WebKit should only tell us about in-page navs for the main frame.";
+ // We're guaranteed to have an entry for this one.
+ NavigationEntryImpl* existing_entry = GetEntryWithPageID(
+ delegate_->GetSiteInstance(), params.page_id);
+
+ // Reference fragment navigation. We're guaranteed to have the last_committed
+ // entry and it will be the same page as the new navigation (minus the
+ // reference fragments, of course). We'll update the URL of the existing
+ // entry without pruning the forward history.
+ existing_entry->SetURL(params.url);
+ if (existing_entry->update_virtual_url_with_url())
+ UpdateVirtualURLToURL(existing_entry, params.url);
+
+ // This replaces the existing entry since the page ID didn't change.
+ *did_replace_entry = true;
+
+ DiscardNonCommittedEntriesInternal();
+
+ // If a transient entry was removed, the indices might have changed, so we
+ // have to query the entry index again.
+ last_committed_entry_index_ =
+ GetEntryIndexWithPageID(delegate_->GetSiteInstance(), params.page_id);
+}
+
+void NavigationControllerImpl::RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ if (PageTransitionCoreTypeIs(params.transition,
+ PAGE_TRANSITION_AUTO_SUBFRAME)) {
+ // This is not user-initiated. Ignore.
+ DiscardNonCommittedEntriesInternal();
+ return;
+ }
+
+ // Manual subframe navigations just get the current entry cloned so the user
+ // can go back or forward to it. The actual subframe information will be
+ // stored in the page state for each of those entries. This happens out of
+ // band with the actual navigations.
+ DCHECK(GetLastCommittedEntry()) << "ClassifyNavigation should guarantee "
+ << "that a last committed entry exists.";
+ NavigationEntryImpl* new_entry = new NavigationEntryImpl(
+ *NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry()));
+ new_entry->SetPageID(params.page_id);
+ InsertOrReplaceEntry(new_entry, false);
+}
+
+bool NavigationControllerImpl::RendererDidNavigateAutoSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params) {
+ // We're guaranteed to have a previously committed entry, and we now need to
+ // handle navigation inside of a subframe in it without creating a new entry.
+ DCHECK(GetLastCommittedEntry());
+
+ // Handle the case where we're navigating back/forward to a previous subframe
+ // navigation entry. This is case "2." in NAV_AUTO_SUBFRAME comment in the
+ // header file. In case "1." this will be a NOP.
+ int entry_index = GetEntryIndexWithPageID(
+ delegate_->GetSiteInstance(),
+ params.page_id);
+ if (entry_index < 0 ||
+ entry_index >= static_cast<int>(entries_.size())) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Update the current navigation entry in case we're going back/forward.
+ if (entry_index != last_committed_entry_index_) {
+ last_committed_entry_index_ = entry_index;
+ DiscardNonCommittedEntriesInternal();
+ return true;
+ }
+
+ // We do not need to discard the pending entry in this case, since we will
+ // not generate commit notifications for this auto-subframe navigation.
+ return false;
+}
+
+int NavigationControllerImpl::GetIndexOfEntry(
+ const NavigationEntryImpl* entry) const {
+ const NavigationEntries::const_iterator i(std::find(
+ entries_.begin(),
+ entries_.end(),
+ entry));
+ return (i == entries_.end()) ? -1 : static_cast<int>(i - entries_.begin());
+}
+
+bool NavigationControllerImpl::IsURLInPageNavigation(
+ const GURL& url,
+ bool renderer_says_in_page,
+ NavigationType navigation_type) const {
+ NavigationEntry* last_committed = GetLastCommittedEntry();
+ return last_committed && AreURLsInPageNavigation(
+ last_committed->GetURL(), url, renderer_says_in_page, navigation_type);
+}
+
+void NavigationControllerImpl::CopyStateFrom(
+ const NavigationController& temp) {
+ const NavigationControllerImpl& source =
+ static_cast<const NavigationControllerImpl&>(temp);
+ // Verify that we look new.
+ DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
+
+ if (source.GetEntryCount() == 0)
+ return; // Nothing new to do.
+
+ needs_reload_ = true;
+ InsertEntriesFrom(source, source.GetEntryCount());
+
+ for (SessionStorageNamespaceMap::const_iterator it =
+ source.session_storage_namespace_map_.begin();
+ it != source.session_storage_namespace_map_.end();
+ ++it) {
+ SessionStorageNamespaceImpl* source_namespace =
+ static_cast<SessionStorageNamespaceImpl*>(it->second.get());
+ session_storage_namespace_map_[it->first] = source_namespace->Clone();
+ }
+
+ FinishRestore(source.last_committed_entry_index_, RESTORE_CURRENT_SESSION);
+
+ // Copy the max page id map from the old tab to the new tab. This ensures
+ // that new and existing navigations in the tab's current SiteInstances
+ // are identified properly.
+ delegate_->CopyMaxPageIDsFrom(source.delegate()->GetWebContents());
+}
+
+void NavigationControllerImpl::CopyStateFromAndPrune(
+ NavigationController* temp) {
+ // It is up to callers to check the invariants before calling this.
+ CHECK(CanPruneAllButVisible());
+
+ NavigationControllerImpl* source =
+ static_cast<NavigationControllerImpl*>(temp);
+ // The SiteInstance and page_id of the last committed entry needs to be
+ // remembered at this point, in case there is only one committed entry
+ // and it is pruned. We use a scoped_refptr to ensure the SiteInstance
+ // can't be freed during this time period.
+ NavigationEntryImpl* last_committed =
+ NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry());
+ scoped_refptr<SiteInstance> site_instance(
+ last_committed->site_instance());
+ int32 minimum_page_id = last_committed->GetPageID();
+ int32 max_page_id =
+ delegate_->GetMaxPageIDForSiteInstance(site_instance.get());
+
+ // Remove all the entries leaving the active entry.
+ PruneAllButVisibleInternal();
+
+ // We now have one entry, possibly with a new pending entry. Ensure that
+ // adding the entries from source won't put us over the limit.
+ DCHECK_EQ(1, GetEntryCount());
+ source->PruneOldestEntryIfFull();
+
+ // Insert the entries from source. Don't use source->GetCurrentEntryIndex as
+ // we don't want to copy over the transient entry. Ignore any pending entry,
+ // since it has not committed in source.
+ int max_source_index = source->last_committed_entry_index_;
+ if (max_source_index == -1)
+ max_source_index = source->GetEntryCount();
+ else
+ max_source_index++;
+ InsertEntriesFrom(*source, max_source_index);
+
+ // Adjust indices such that the last entry and pending are at the end now.
+ last_committed_entry_index_ = GetEntryCount() - 1;
+
+ delegate_->SetHistoryLengthAndPrune(site_instance.get(),
+ max_source_index,
+ minimum_page_id);
+
+ // Copy the max page id map from the old tab to the new tab. This ensures
+ // that new and existing navigations in the tab's current SiteInstances
+ // are identified properly.
+ delegate_->CopyMaxPageIDsFrom(source->delegate()->GetWebContents());
+
+ // If there is a last committed entry, be sure to include it in the new
+ // max page ID map.
+ if (max_page_id > -1) {
+ delegate_->UpdateMaxPageIDForSiteInstance(site_instance.get(),
+ max_page_id);
+ }
+}
+
+bool NavigationControllerImpl::CanPruneAllButVisible() {
+ // If there is no last committed entry, we cannot prune. Even if there is a
+ // pending entry, it may not commit, leaving this WebContents blank, despite
+ // possibly giving it new entries via CopyStateFromAndPrune.
+ if (last_committed_entry_index_ == -1)
+ return false;
+
+ // We cannot prune if there is a pending entry at an existing entry index.
+ // It may not commit, so we have to keep the last committed entry, and thus
+ // there is no sensible place to keep the pending entry. It is ok to have
+ // a new pending entry, which can optionally commit as a new navigation.
+ if (pending_entry_index_ != -1)
+ return false;
+
+ // We should not prune if we are currently showing a transient entry.
+ if (transient_entry_index_ != -1)
+ return false;
+
+ return true;
+}
+
+void NavigationControllerImpl::PruneAllButVisible() {
+ PruneAllButVisibleInternal();
+
+ // We should still have a last committed entry.
+ DCHECK_NE(-1, last_committed_entry_index_);
+
+ // We pass 0 instead of GetEntryCount() for the history_length parameter of
+ // SetHistoryLengthAndPrune, because it will create history_length additional
+ // history entries.
+ // TODO(jochen): This API is confusing and we should clean it up.
+ // http://crbug.com/178491
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(GetVisibleEntry());
+ delegate_->SetHistoryLengthAndPrune(
+ entry->site_instance(), 0, entry->GetPageID());
+}
+
+void NavigationControllerImpl::PruneAllButVisibleInternal() {
+ // It is up to callers to check the invariants before calling this.
+ CHECK(CanPruneAllButVisible());
+
+ // Erase all entries but the last committed entry. There may still be a
+ // new pending entry after this.
+ entries_.erase(entries_.begin(),
+ entries_.begin() + last_committed_entry_index_);
+ entries_.erase(entries_.begin() + 1, entries_.end());
+ last_committed_entry_index_ = 0;
+}
+
+void NavigationControllerImpl::ClearAllScreenshots() {
+ screenshot_manager_->ClearAllScreenshots();
+}
+
+void NavigationControllerImpl::SetSessionStorageNamespace(
+ const std::string& partition_id,
+ SessionStorageNamespace* session_storage_namespace) {
+ if (!session_storage_namespace)
+ return;
+
+ // We can't overwrite an existing SessionStorage without violating spec.
+ // Attempts to do so may give a tab access to another tab's session storage
+ // so die hard on an error.
+ bool successful_insert = session_storage_namespace_map_.insert(
+ make_pair(partition_id,
+ static_cast<SessionStorageNamespaceImpl*>(
+ session_storage_namespace)))
+ .second;
+ CHECK(successful_insert) << "Cannot replace existing SessionStorageNamespace";
+}
+
+void NavigationControllerImpl::SetMaxRestoredPageID(int32 max_id) {
+ max_restored_page_id_ = max_id;
+}
+
+int32 NavigationControllerImpl::GetMaxRestoredPageID() const {
+ return max_restored_page_id_;
+}
+
+SessionStorageNamespace*
+NavigationControllerImpl::GetSessionStorageNamespace(SiteInstance* instance) {
+ std::string partition_id;
+ if (instance) {
+ // TODO(ajwong): When GetDefaultSessionStorageNamespace() goes away, remove
+ // this if statement so |instance| must not be NULL.
+ partition_id =
+ GetContentClient()->browser()->GetStoragePartitionIdForSite(
+ browser_context_, instance->GetSiteURL());
+ }
+
+ SessionStorageNamespaceMap::const_iterator it =
+ session_storage_namespace_map_.find(partition_id);
+ if (it != session_storage_namespace_map_.end())
+ return it->second.get();
+
+ // Create one if no one has accessed session storage for this partition yet.
+ //
+ // TODO(ajwong): Should this use the |partition_id| directly rather than
+ // re-lookup via |instance|? http://crbug.com/142685
+ StoragePartition* partition =
+ BrowserContext::GetStoragePartition(browser_context_, instance);
+ SessionStorageNamespaceImpl* session_storage_namespace =
+ new SessionStorageNamespaceImpl(
+ static_cast<DOMStorageContextWrapper*>(
+ partition->GetDOMStorageContext()));
+ session_storage_namespace_map_[partition_id] = session_storage_namespace;
+
+ return session_storage_namespace;
+}
+
+SessionStorageNamespace*
+NavigationControllerImpl::GetDefaultSessionStorageNamespace() {
+ // TODO(ajwong): Remove if statement in GetSessionStorageNamespace().
+ return GetSessionStorageNamespace(NULL);
+}
+
+const SessionStorageNamespaceMap&
+NavigationControllerImpl::GetSessionStorageNamespaceMap() const {
+ return session_storage_namespace_map_;
+}
+
+bool NavigationControllerImpl::NeedsReload() const {
+ return needs_reload_;
+}
+
+void NavigationControllerImpl::SetNeedsReload() {
+ needs_reload_ = true;
+}
+
+void NavigationControllerImpl::RemoveEntryAtIndexInternal(int index) {
+ DCHECK(index < GetEntryCount());
+ DCHECK(index != last_committed_entry_index_);
+
+ DiscardNonCommittedEntries();
+
+ entries_.erase(entries_.begin() + index);
+ if (last_committed_entry_index_ > index)
+ last_committed_entry_index_--;
+}
+
+void NavigationControllerImpl::DiscardNonCommittedEntries() {
+ bool transient = transient_entry_index_ != -1;
+ DiscardNonCommittedEntriesInternal();
+
+ // If there was a transient entry, invalidate everything so the new active
+ // entry state is shown.
+ if (transient) {
+ delegate_->NotifyNavigationStateChanged(kInvalidateAll);
+ }
+}
+
+NavigationEntry* NavigationControllerImpl::GetPendingEntry() const {
+ return pending_entry_;
+}
+
+int NavigationControllerImpl::GetPendingEntryIndex() const {
+ return pending_entry_index_;
+}
+
+void NavigationControllerImpl::InsertOrReplaceEntry(NavigationEntryImpl* entry,
+ bool replace) {
+ DCHECK(entry->GetTransitionType() != PAGE_TRANSITION_AUTO_SUBFRAME);
+
+ // Copy the pending entry's unique ID to the committed entry.
+ // I don't know if pending_entry_index_ can be other than -1 here.
+ const NavigationEntryImpl* const pending_entry =
+ (pending_entry_index_ == -1) ?
+ pending_entry_ : entries_[pending_entry_index_].get();
+ if (pending_entry)
+ entry->set_unique_id(pending_entry->GetUniqueID());
+
+ DiscardNonCommittedEntriesInternal();
+
+ int current_size = static_cast<int>(entries_.size());
+
+ if (current_size > 0) {
+ // Prune any entries which are in front of the current entry.
+ // Also prune the current entry if we are to replace the current entry.
+ // last_committed_entry_index_ must be updated here since calls to
+ // NotifyPrunedEntries() below may re-enter and we must make sure
+ // last_committed_entry_index_ is not left in an invalid state.
+ if (replace)
+ --last_committed_entry_index_;
+
+ int num_pruned = 0;
+ while (last_committed_entry_index_ < (current_size - 1)) {
+ num_pruned++;
+ entries_.pop_back();
+ current_size--;
+ }
+ if (num_pruned > 0) // Only notify if we did prune something.
+ NotifyPrunedEntries(this, false, num_pruned);
+ }
+
+ PruneOldestEntryIfFull();
+
+ entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
+ last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1;
+
+ // This is a new page ID, so we need everybody to know about it.
+ delegate_->UpdateMaxPageID(entry->GetPageID());
+}
+
+void NavigationControllerImpl::PruneOldestEntryIfFull() {
+ if (entries_.size() >= max_entry_count()) {
+ DCHECK_EQ(max_entry_count(), entries_.size());
+ DCHECK_GT(last_committed_entry_index_, 0);
+ RemoveEntryAtIndex(0);
+ NotifyPrunedEntries(this, true, 1);
+ }
+}
+
+void NavigationControllerImpl::NavigateToPendingEntry(ReloadType reload_type) {
+ needs_reload_ = false;
+
+ // If we were navigating to a slow-to-commit page, and the user performs
+ // a session history navigation to the last committed page, RenderViewHost
+ // will force the throbber to start, but WebKit will essentially ignore the
+ // navigation, and won't send a message to stop the throbber. To prevent this
+ // from happening, we drop the navigation here and stop the slow-to-commit
+ // page from loading (which would normally happen during the navigation).
+ if (pending_entry_index_ != -1 &&
+ pending_entry_index_ == last_committed_entry_index_ &&
+ (entries_[pending_entry_index_]->restore_type() ==
+ NavigationEntryImpl::RESTORE_NONE) &&
+ (entries_[pending_entry_index_]->GetTransitionType() &
+ PAGE_TRANSITION_FORWARD_BACK)) {
+ delegate_->Stop();
+
+ // If an interstitial page is showing, we want to close it to get back
+ // to what was showing before.
+ if (delegate_->GetInterstitialPage())
+ delegate_->GetInterstitialPage()->DontProceed();
+
+ DiscardNonCommittedEntries();
+ return;
+ }
+
+ // If an interstitial page is showing, the previous renderer is blocked and
+ // cannot make new requests. Unblock (and disable) it to allow this
+ // navigation to succeed. The interstitial will stay visible until the
+ // resulting DidNavigate.
+ if (delegate_->GetInterstitialPage()) {
+ static_cast<InterstitialPageImpl*>(delegate_->GetInterstitialPage())->
+ CancelForNavigation();
+ }
+
+ // For session history navigations only the pending_entry_index_ is set.
+ if (!pending_entry_) {
+ DCHECK_NE(pending_entry_index_, -1);
+ pending_entry_ = entries_[pending_entry_index_].get();
+ }
+
+ if (!delegate_->NavigateToPendingEntry(reload_type))
+ DiscardNonCommittedEntries();
+
+ // If the entry is being restored and doesn't have a SiteInstance yet, fill
+ // it in now that we know. This allows us to find the entry when it commits.
+ // This works for browser-initiated navigations. We handle renderer-initiated
+ // navigations to restored entries in WebContentsImpl::OnGoToEntryAtOffset.
+ if (pending_entry_ && !pending_entry_->site_instance() &&
+ pending_entry_->restore_type() != NavigationEntryImpl::RESTORE_NONE) {
+ pending_entry_->set_site_instance(static_cast<SiteInstanceImpl*>(
+ delegate_->GetPendingSiteInstance()));
+ pending_entry_->set_restore_type(NavigationEntryImpl::RESTORE_NONE);
+ }
+}
+
+void NavigationControllerImpl::NotifyNavigationEntryCommitted(
+ LoadCommittedDetails* details) {
+ details->entry = GetLastCommittedEntry();
+
+ // We need to notify the ssl_manager_ before the web_contents_ so the
+ // location bar will have up-to-date information about the security style
+ // when it wants to draw. See http://crbug.com/11157
+ ssl_manager_.DidCommitProvisionalLoad(*details);
+
+ delegate_->NotifyNavigationStateChanged(kInvalidateAll);
+ delegate_->NotifyNavigationEntryCommitted(*details);
+
+ // TODO(avi): Remove. http://crbug.com/170921
+ NotificationDetails notification_details =
+ Details<LoadCommittedDetails>(details);
+ NotificationService::current()->Notify(
+ NOTIFICATION_NAV_ENTRY_COMMITTED,
+ Source<NavigationController>(this),
+ notification_details);
+}
+
+// static
+size_t NavigationControllerImpl::max_entry_count() {
+ if (max_entry_count_for_testing_ != kMaxEntryCountForTestingNotSet)
+ return max_entry_count_for_testing_;
+ return kMaxSessionHistoryEntries;
+}
+
+void NavigationControllerImpl::SetActive(bool is_active) {
+ if (is_active && needs_reload_)
+ LoadIfNecessary();
+}
+
+void NavigationControllerImpl::LoadIfNecessary() {
+ if (!needs_reload_)
+ return;
+
+ // Calling Reload() results in ignoring state, and not loading.
+ // Explicitly use NavigateToPendingEntry so that the renderer uses the
+ // cached state.
+ pending_entry_index_ = last_committed_entry_index_;
+ NavigateToPendingEntry(NO_RELOAD);
+}
+
+void NavigationControllerImpl::NotifyEntryChanged(const NavigationEntry* entry,
+ int index) {
+ EntryChangedDetails det;
+ det.changed_entry = entry;
+ det.index = index;
+ NotificationService::current()->Notify(
+ NOTIFICATION_NAV_ENTRY_CHANGED,
+ Source<NavigationController>(this),
+ Details<EntryChangedDetails>(&det));
+}
+
+void NavigationControllerImpl::FinishRestore(int selected_index,
+ RestoreType type) {
+ DCHECK(selected_index >= 0 && selected_index < GetEntryCount());
+ ConfigureEntriesForRestore(&entries_, type);
+
+ SetMaxRestoredPageID(static_cast<int32>(GetEntryCount()));
+
+ last_committed_entry_index_ = selected_index;
+}
+
+void NavigationControllerImpl::DiscardNonCommittedEntriesInternal() {
+ DiscardPendingEntry();
+ DiscardTransientEntry();
+}
+
+void NavigationControllerImpl::DiscardPendingEntry() {
+ if (pending_entry_index_ == -1)
+ delete pending_entry_;
+ pending_entry_ = NULL;
+ pending_entry_index_ = -1;
+}
+
+void NavigationControllerImpl::DiscardTransientEntry() {
+ if (transient_entry_index_ == -1)
+ return;
+ entries_.erase(entries_.begin() + transient_entry_index_);
+ if (last_committed_entry_index_ > transient_entry_index_)
+ last_committed_entry_index_--;
+ transient_entry_index_ = -1;
+}
+
+int NavigationControllerImpl::GetEntryIndexWithPageID(
+ SiteInstance* instance, int32 page_id) const {
+ for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
+ if ((entries_[i]->site_instance() == instance) &&
+ (entries_[i]->GetPageID() == page_id))
+ return i;
+ }
+ return -1;
+}
+
+NavigationEntry* NavigationControllerImpl::GetTransientEntry() const {
+ if (transient_entry_index_ == -1)
+ return NULL;
+ return entries_[transient_entry_index_].get();
+}
+
+void NavigationControllerImpl::SetTransientEntry(NavigationEntry* entry) {
+ // Discard any current transient entry, we can only have one at a time.
+ int index = 0;
+ if (last_committed_entry_index_ != -1)
+ index = last_committed_entry_index_ + 1;
+ DiscardTransientEntry();
+ entries_.insert(
+ entries_.begin() + index, linked_ptr<NavigationEntryImpl>(
+ NavigationEntryImpl::FromNavigationEntry(entry)));
+ transient_entry_index_ = index;
+ delegate_->NotifyNavigationStateChanged(kInvalidateAll);
+}
+
+void NavigationControllerImpl::InsertEntriesFrom(
+ const NavigationControllerImpl& source,
+ int max_index) {
+ DCHECK_LE(max_index, source.GetEntryCount());
+ size_t insert_index = 0;
+ for (int i = 0; i < max_index; i++) {
+ // When cloning a tab, copy all entries except interstitial pages
+ if (source.entries_[i].get()->GetPageType() !=
+ PAGE_TYPE_INTERSTITIAL) {
+ entries_.insert(entries_.begin() + insert_index++,
+ linked_ptr<NavigationEntryImpl>(
+ new NavigationEntryImpl(*source.entries_[i])));
+ }
+ }
+}
+
+void NavigationControllerImpl::SetGetTimestampCallbackForTest(
+ const base::Callback<base::Time()>& get_timestamp_callback) {
+ get_timestamp_callback_ = get_timestamp_callback;
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/navigation_controller_impl.h b/content/browser/frame_host/navigation_controller_impl.h
new file mode 100644
index 0000000..9177914
--- /dev/null
+++ b/content/browser/frame_host/navigation_controller_impl.h
@@ -0,0 +1,410 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_NAVIGATION_CONTROLLER_IMPL_H_
+#define CONTENT_BROWSER_FRAME_HOST_NAVIGATION_CONTROLLER_IMPL_H_
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/linked_ptr.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "content/browser/frame_host/navigation_controller_delegate.h"
+#include "content/browser/ssl/ssl_manager.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_type.h"
+
+struct ViewHostMsg_FrameNavigate_Params;
+
+namespace content {
+class NavigationEntryImpl;
+class RenderViewHost;
+class WebContentsScreenshotManager;
+class SiteInstance;
+struct LoadCommittedDetails;
+
+class CONTENT_EXPORT NavigationControllerImpl
+ : public NON_EXPORTED_BASE(NavigationController) {
+ public:
+ NavigationControllerImpl(
+ NavigationControllerDelegate* delegate,
+ BrowserContext* browser_context);
+ virtual ~NavigationControllerImpl();
+
+ // NavigationController implementation:
+ virtual WebContents* GetWebContents() const OVERRIDE;
+ virtual BrowserContext* GetBrowserContext() const OVERRIDE;
+ virtual void SetBrowserContext(
+ BrowserContext* browser_context) OVERRIDE;
+ virtual void Restore(
+ int selected_navigation,
+ RestoreType type,
+ std::vector<NavigationEntry*>* entries) OVERRIDE;
+ virtual NavigationEntry* GetActiveEntry() const OVERRIDE;
+ virtual NavigationEntry* GetVisibleEntry() const OVERRIDE;
+ virtual int GetCurrentEntryIndex() const OVERRIDE;
+ virtual NavigationEntry* GetLastCommittedEntry() const OVERRIDE;
+ virtual int GetLastCommittedEntryIndex() const OVERRIDE;
+ virtual bool CanViewSource() const OVERRIDE;
+ virtual int GetEntryCount() const OVERRIDE;
+ virtual NavigationEntry* GetEntryAtIndex(int index) const OVERRIDE;
+ virtual NavigationEntry* GetEntryAtOffset(int offset) const OVERRIDE;
+ virtual void DiscardNonCommittedEntries() OVERRIDE;
+ virtual NavigationEntry* GetPendingEntry() const OVERRIDE;
+ virtual int GetPendingEntryIndex() const OVERRIDE;
+ virtual NavigationEntry* GetTransientEntry() const OVERRIDE;
+ virtual void SetTransientEntry(NavigationEntry* entry) OVERRIDE;
+ virtual void LoadURL(const GURL& url,
+ const Referrer& referrer,
+ PageTransition type,
+ const std::string& extra_headers) OVERRIDE;
+ virtual void LoadURLWithParams(const LoadURLParams& params) OVERRIDE;
+ virtual void LoadIfNecessary() OVERRIDE;
+ virtual bool CanGoBack() const OVERRIDE;
+ virtual bool CanGoForward() const OVERRIDE;
+ virtual bool CanGoToOffset(int offset) const OVERRIDE;
+ virtual void GoBack() OVERRIDE;
+ virtual void GoForward() OVERRIDE;
+ virtual void GoToIndex(int index) OVERRIDE;
+ virtual void GoToOffset(int offset) OVERRIDE;
+ virtual bool RemoveEntryAtIndex(int index) OVERRIDE;
+ virtual const SessionStorageNamespaceMap&
+ GetSessionStorageNamespaceMap() const OVERRIDE;
+ virtual SessionStorageNamespace*
+ GetDefaultSessionStorageNamespace() OVERRIDE;
+ virtual void SetMaxRestoredPageID(int32 max_id) OVERRIDE;
+ virtual int32 GetMaxRestoredPageID() const OVERRIDE;
+ virtual bool NeedsReload() const OVERRIDE;
+ virtual void SetNeedsReload() OVERRIDE;
+ virtual void CancelPendingReload() OVERRIDE;
+ virtual void ContinuePendingReload() OVERRIDE;
+ virtual bool IsInitialNavigation() const OVERRIDE;
+ virtual void Reload(bool check_for_repost) OVERRIDE;
+ virtual void ReloadIgnoringCache(bool check_for_repost) OVERRIDE;
+ virtual void ReloadOriginalRequestURL(bool check_for_repost) OVERRIDE;
+ virtual void NotifyEntryChanged(const NavigationEntry* entry,
+ int index) OVERRIDE;
+ virtual void CopyStateFrom(
+ const NavigationController& source) OVERRIDE;
+ virtual void CopyStateFromAndPrune(
+ NavigationController* source) OVERRIDE;
+ virtual bool CanPruneAllButVisible() OVERRIDE;
+ virtual void PruneAllButVisible() OVERRIDE;
+ virtual void ClearAllScreenshots() OVERRIDE;
+
+ // The session storage namespace that all child RenderViews belonging to
+ // |instance| should use.
+ SessionStorageNamespace* GetSessionStorageNamespace(
+ SiteInstance* instance);
+
+ // Returns the index of the specified entry, or -1 if entry is not contained
+ // in this NavigationController.
+ int GetIndexOfEntry(const NavigationEntryImpl* entry) const;
+
+ // Return the index of the entry with the corresponding instance and page_id,
+ // or -1 if not found.
+ int GetEntryIndexWithPageID(SiteInstance* instance,
+ int32 page_id) const;
+
+ // Return the entry with the corresponding instance and page_id, or NULL if
+ // not found.
+ NavigationEntryImpl* GetEntryWithPageID(
+ SiteInstance* instance,
+ int32 page_id) const;
+
+ NavigationControllerDelegate* delegate() const {
+ return delegate_;
+ }
+
+ // For use by WebContentsImpl ------------------------------------------------
+
+ // Allow renderer-initiated navigations to create a pending entry when the
+ // provisional load starts.
+ void SetPendingEntry(content::NavigationEntryImpl* entry);
+
+ // Handles updating the navigation state after the renderer has navigated.
+ // This is used by the WebContentsImpl.
+ //
+ // If a new entry is created, it will return true and will have filled the
+ // given details structure and broadcast the NOTIFY_NAV_ENTRY_COMMITTED
+ // notification. The caller can then use the details without worrying about
+ // listening for the notification.
+ //
+ // In the case that nothing has changed, the details structure is undefined
+ // and it will return false.
+ bool RendererDidNavigate(const ViewHostMsg_FrameNavigate_Params& params,
+ LoadCommittedDetails* details);
+
+ // Notifies us that we just became active. This is used by the WebContentsImpl
+ // so that we know to load URLs that were pending as "lazy" loads.
+ void SetActive(bool is_active);
+
+ // Returns true if the given URL would be an in-page navigation (i.e. only
+ // the reference fragment is different) from the "last committed entry". We do
+ // not compare it against the "active entry" since the active entry can be
+ // pending and in page navigations only happen on committed pages. If there
+ // is no last committed entry, then nothing will be in-page.
+ //
+ // Special note: if the URLs are the same, it does NOT automatically count as
+ // an in-page navigation. Neither does an input URL that has no ref, even if
+ // the rest is the same. This may seem weird, but when we're considering
+ // whether a navigation happened without loading anything, the same URL could
+ // be a reload, while only a different ref would be in-page (pages can't clear
+ // refs without reload, only change to "#" which we don't count as empty).
+ bool IsURLInPageNavigation(const GURL& url) const {
+ return IsURLInPageNavigation(url, false, NAVIGATION_TYPE_UNKNOWN);
+ }
+
+ // The situation is made murkier by history.replaceState(), which could
+ // provide the same URL as part of an in-page navigation, not a reload. So
+ // we need this form which lets the (untrustworthy) renderer resolve the
+ // ambiguity, but only when the URLs are equal. This should be safe since the
+ // origin isn't changing.
+ bool IsURLInPageNavigation(
+ const GURL& url,
+ bool renderer_says_in_page,
+ NavigationType navigation_type) const;
+
+ // Sets the SessionStorageNamespace for the given |partition_id|. This is
+ // used during initialization of a new NavigationController to allow
+ // pre-population of the SessionStorageNamespace objects. Session restore,
+ // prerendering, and the implementaion of window.open() are the primary users
+ // of this API.
+ //
+ // Calling this function when a SessionStorageNamespace has already been
+ // associated with a |partition_id| will CHECK() fail.
+ void SetSessionStorageNamespace(
+ const std::string& partition_id,
+ SessionStorageNamespace* session_storage_namespace);
+
+ // Random data ---------------------------------------------------------------
+
+ SSLManager* ssl_manager() { return &ssl_manager_; }
+
+ // Maximum number of entries before we start removing entries from the front.
+ static void set_max_entry_count_for_testing(size_t max_entry_count) {
+ max_entry_count_for_testing_ = max_entry_count;
+ }
+ static size_t max_entry_count();
+
+ void SetGetTimestampCallbackForTest(
+ const base::Callback<base::Time()>& get_timestamp_callback);
+
+ // Takes a screenshot of the page at the current state.
+ void TakeScreenshot();
+
+ // Sets the screenshot manager for this NavigationControllerImpl. The
+ // controller takes ownership of the screenshot manager and destroys it when
+ // a new screenshot-manager is set, or when the controller is destroyed.
+ // Setting a NULL manager recreates the default screenshot manager and uses
+ // that.
+ void SetScreenshotManager(WebContentsScreenshotManager* manager);
+
+ // Discards only the pending entry.
+ void DiscardPendingEntry();
+
+ private:
+ friend class RestoreHelper;
+
+ FRIEND_TEST_ALL_PREFIXES(NavigationControllerTest,
+ PurgeScreenshot);
+ FRIEND_TEST_ALL_PREFIXES(TimeSmoother, Basic);
+ FRIEND_TEST_ALL_PREFIXES(TimeSmoother, SingleDuplicate);
+ FRIEND_TEST_ALL_PREFIXES(TimeSmoother, ManyDuplicates);
+ FRIEND_TEST_ALL_PREFIXES(TimeSmoother, ClockBackwardsJump);
+
+ // Helper class to smooth out runs of duplicate timestamps while still
+ // allowing time to jump backwards.
+ class CONTENT_EXPORT TimeSmoother {
+ public:
+ // Returns |t| with possibly some time added on.
+ base::Time GetSmoothedTime(base::Time t);
+
+ private:
+ // |low_water_mark_| is the first time in a sequence of adjusted
+ // times and |high_water_mark_| is the last.
+ base::Time low_water_mark_;
+ base::Time high_water_mark_;
+ };
+
+ // Classifies the given renderer navigation (see the NavigationType enum).
+ NavigationType ClassifyNavigation(
+ const ViewHostMsg_FrameNavigate_Params& params) const;
+
+ // Causes the controller to load the specified entry. The function assumes
+ // ownership of the pointer since it is put in the navigation list.
+ // NOTE: Do not pass an entry that the controller already owns!
+ void LoadEntry(NavigationEntryImpl* entry);
+
+ // Handlers for the different types of navigation types. They will actually
+ // handle the navigations corresponding to the different NavClasses above.
+ // They will NOT broadcast the commit notification, that should be handled by
+ // the caller.
+ //
+ // RendererDidNavigateAutoSubframe is special, it may not actually change
+ // anything if some random subframe is loaded. It will return true if anything
+ // changed, or false if not.
+ //
+ // The functions taking |did_replace_entry| will fill into the given variable
+ // whether the last entry has been replaced or not.
+ // See LoadCommittedDetails.did_replace_entry.
+ void RendererDidNavigateToNewPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool replace_entry);
+ void RendererDidNavigateToExistingPage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateToSamePage(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ void RendererDidNavigateInPage(
+ const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry);
+ void RendererDidNavigateNewSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params);
+ bool RendererDidNavigateAutoSubframe(
+ const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Helper function for code shared between Reload() and ReloadIgnoringCache().
+ void ReloadInternal(bool check_for_repost, ReloadType reload_type);
+
+ // Actually issues the navigation held in pending_entry.
+ void NavigateToPendingEntry(ReloadType reload_type);
+
+ // Allows the derived class to issue notifications that a load has been
+ // committed. This will fill in the active entry to the details structure.
+ void NotifyNavigationEntryCommitted(LoadCommittedDetails* details);
+
+ // Updates the virtual URL of an entry to match a new URL, for cases where
+ // the real renderer URL is derived from the virtual URL, like view-source:
+ void UpdateVirtualURLToURL(NavigationEntryImpl* entry,
+ const GURL& new_url);
+
+ // Invoked after session/tab restore or cloning a tab. Resets the transition
+ // type of the entries, updates the max page id and creates the active
+ // contents.
+ void FinishRestore(int selected_index, RestoreType type);
+
+ // Inserts a new entry or replaces the current entry with a new one, removing
+ // all entries after it. The new entry will become the active one.
+ void InsertOrReplaceEntry(NavigationEntryImpl* entry, bool replace);
+
+ // Removes the entry at |index|, as long as it is not the current entry.
+ void RemoveEntryAtIndexInternal(int index);
+
+ // Discards both the pending and transient entries.
+ void DiscardNonCommittedEntriesInternal();
+
+ // Discards only the transient entry.
+ void DiscardTransientEntry();
+
+ // If we have the maximum number of entries, remove the oldest one in
+ // preparation to add another.
+ void PruneOldestEntryIfFull();
+
+ // Removes all entries except the last committed entry. If there is a new
+ // pending navigation it is preserved. In contrast to PruneAllButVisible()
+ // this does not update the session history of the RenderView. Callers
+ // must ensure that |CanPruneAllButVisible| returns true before calling this.
+ void PruneAllButVisibleInternal();
+
+ // Returns true if the navigation is redirect.
+ bool IsRedirect(const ViewHostMsg_FrameNavigate_Params& params);
+
+ // Returns true if the navigation is likley to be automatic rather than
+ // user-initiated.
+ bool IsLikelyAutoNavigation(base::TimeTicks now);
+
+ // Inserts up to |max_index| entries from |source| into this. This does NOT
+ // adjust any of the members that reference entries_
+ // (last_committed_entry_index_, pending_entry_index_ or
+ // transient_entry_index_).
+ void InsertEntriesFrom(const NavigationControllerImpl& source, int max_index);
+
+ // Returns the navigation index that differs from the current entry by the
+ // specified |offset|. The index returned is not guaranteed to be valid.
+ int GetIndexForOffset(int offset) const;
+
+ // ---------------------------------------------------------------------------
+
+ // The user browser context associated with this controller.
+ BrowserContext* browser_context_;
+
+ // List of NavigationEntry for this tab
+ typedef std::vector<linked_ptr<NavigationEntryImpl> > NavigationEntries;
+ NavigationEntries entries_;
+
+ // An entry we haven't gotten a response for yet. This will be discarded
+ // when we navigate again. It's used only so we know what the currently
+ // displayed tab is.
+ //
+ // This may refer to an item in the entries_ list if the pending_entry_index_
+ // == -1, or it may be its own entry that should be deleted. Be careful with
+ // the memory management.
+ NavigationEntryImpl* pending_entry_;
+
+ // currently visible entry
+ int last_committed_entry_index_;
+
+ // index of pending entry if it is in entries_, or -1 if pending_entry_ is a
+ // new entry (created by LoadURL).
+ int pending_entry_index_;
+
+ // The index for the entry that is shown until a navigation occurs. This is
+ // used for interstitial pages. -1 if there are no such entry.
+ // Note that this entry really appears in the list of entries, but only
+ // temporarily (until the next navigation). Any index pointing to an entry
+ // after the transient entry will become invalid if you navigate forward.
+ int transient_entry_index_;
+
+ // The delegate associated with the controller. Possibly NULL during
+ // setup.
+ NavigationControllerDelegate* delegate_;
+
+ // The max restored page ID in this controller, if it was restored. We must
+ // store this so that WebContentsImpl can tell any renderer in charge of one
+ // of the restored entries to update its max page ID.
+ int32 max_restored_page_id_;
+
+ // Manages the SSL security UI.
+ SSLManager ssl_manager_;
+
+ // Whether we need to be reloaded when made active.
+ bool needs_reload_;
+
+ // Whether this is the initial navigation.
+ // Becomes false when initial navigation commits.
+ bool is_initial_navigation_;
+
+ // Used to find the appropriate SessionStorageNamespace for the storage
+ // partition of a NavigationEntry.
+ //
+ // A NavigationController may contain NavigationEntries that correspond to
+ // different StoragePartitions. Even though they are part of the same
+ // NavigationController, only entries in the same StoragePartition may
+ // share session storage state with one another.
+ SessionStorageNamespaceMap session_storage_namespace_map_;
+
+ // The maximum number of entries that a navigation controller can store.
+ static size_t max_entry_count_for_testing_;
+
+ // If a repost is pending, its type (RELOAD or RELOAD_IGNORING_CACHE),
+ // NO_RELOAD otherwise.
+ ReloadType pending_reload_;
+
+ // Used to get timestamps for newly-created navigation entries.
+ base::Callback<base::Time()> get_timestamp_callback_;
+
+ // Used to smooth out timestamps from |get_timestamp_callback_|.
+ // Without this, whenever there is a run of redirects or
+ // code-generated navigations, those navigations may occur within
+ // the timer resolution, leading to things sometimes showing up in
+ // the wrong order in the history view.
+ TimeSmoother time_smoother_;
+
+ scoped_ptr<WebContentsScreenshotManager> screenshot_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(NavigationControllerImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_NAVIGATION_CONTROLLER_IMPL_H_
diff --git a/content/browser/frame_host/navigation_controller_impl_unittest.cc b/content/browser/frame_host/navigation_controller_impl_unittest.cc
new file mode 100644
index 0000000..9056f85
--- /dev/null
+++ b/content/browser/frame_host/navigation_controller_impl_unittest.cc
@@ -0,0 +1,3844 @@
+// Copyright 2013 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.
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/frame_host/navigation_controller_impl.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
+#include "content/browser/frame_host/web_contents_screenshot_manager.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/page_state.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_notification_tracker.h"
+#include "content/public/test/test_utils.h"
+#include "content/test/test_web_contents.h"
+#include "net/base/net_util.h"
+#include "skia/ext/platform_canvas.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Time;
+
+namespace {
+
+// Creates an image with a 1x1 SkBitmap of the specified |color|.
+gfx::Image CreateImage(SkColor color) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
+ bitmap.allocPixels();
+ bitmap.eraseColor(color);
+ return gfx::Image::CreateFrom1xBitmap(bitmap);
+}
+
+// Returns true if images |a| and |b| have the same pixel data.
+bool DoImagesMatch(const gfx::Image& a, const gfx::Image& b) {
+ // Assume that if the 1x bitmaps match, the images match.
+ SkBitmap a_bitmap = a.AsBitmap();
+ SkBitmap b_bitmap = b.AsBitmap();
+
+ if (a_bitmap.width() != b_bitmap.width() ||
+ a_bitmap.height() != b_bitmap.height()) {
+ return false;
+ }
+ SkAutoLockPixels a_bitmap_lock(a_bitmap);
+ SkAutoLockPixels b_bitmap_lock(b_bitmap);
+ return memcmp(a_bitmap.getPixels(),
+ b_bitmap.getPixels(),
+ a_bitmap.getSize()) == 0;
+}
+
+class MockScreenshotManager : public content::WebContentsScreenshotManager {
+ public:
+ explicit MockScreenshotManager(content::NavigationControllerImpl* owner)
+ : content::WebContentsScreenshotManager(owner),
+ encoding_screenshot_in_progress_(false) {
+ }
+
+ virtual ~MockScreenshotManager() {
+ }
+
+ void TakeScreenshotFor(content::NavigationEntryImpl* entry) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
+ bitmap.allocPixels();
+ bitmap.eraseRGB(0, 0, 0);
+ encoding_screenshot_in_progress_ = true;
+ OnScreenshotTaken(entry->GetUniqueID(), true, bitmap);
+ WaitUntilScreenshotIsReady();
+ }
+
+ int GetScreenshotCount() {
+ return content::WebContentsScreenshotManager::GetScreenshotCount();
+ }
+
+ void WaitUntilScreenshotIsReady() {
+ if (!encoding_screenshot_in_progress_)
+ return;
+ message_loop_runner_ = new content::MessageLoopRunner;
+ message_loop_runner_->Run();
+ }
+
+ private:
+ // Overridden from content::WebContentsScreenshotManager:
+ virtual void TakeScreenshotImpl(
+ content::RenderViewHost* host,
+ content::NavigationEntryImpl* entry) OVERRIDE {
+ }
+
+ virtual void OnScreenshotSet(content::NavigationEntryImpl* entry) OVERRIDE {
+ encoding_screenshot_in_progress_ = false;
+ WebContentsScreenshotManager::OnScreenshotSet(entry);
+ if (message_loop_runner_.get())
+ message_loop_runner_->Quit();
+ }
+
+ scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+ bool encoding_screenshot_in_progress_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockScreenshotManager);
+};
+
+} // namespace
+
+namespace content {
+
+// TimeSmoother tests ----------------------------------------------------------
+
+// With no duplicates, GetSmoothedTime should be the identity
+// function.
+TEST(TimeSmoother, Basic) {
+ NavigationControllerImpl::TimeSmoother smoother;
+ for (int64 i = 1; i < 1000; ++i) {
+ base::Time t = base::Time::FromInternalValue(i);
+ EXPECT_EQ(t, smoother.GetSmoothedTime(t));
+ }
+}
+
+// With a single duplicate and timestamps thereafter increasing by one
+// microsecond, the smoothed time should always be one behind.
+TEST(TimeSmoother, SingleDuplicate) {
+ NavigationControllerImpl::TimeSmoother smoother;
+ base::Time t = base::Time::FromInternalValue(1);
+ EXPECT_EQ(t, smoother.GetSmoothedTime(t));
+ for (int64 i = 1; i < 1000; ++i) {
+ base::Time expected_t = base::Time::FromInternalValue(i + 1);
+ t = base::Time::FromInternalValue(i);
+ EXPECT_EQ(expected_t, smoother.GetSmoothedTime(t));
+ }
+}
+
+// With k duplicates and timestamps thereafter increasing by one
+// microsecond, the smoothed time should always be k behind.
+TEST(TimeSmoother, ManyDuplicates) {
+ const int64 kNumDuplicates = 100;
+ NavigationControllerImpl::TimeSmoother smoother;
+ base::Time t = base::Time::FromInternalValue(1);
+ for (int64 i = 0; i < kNumDuplicates; ++i) {
+ base::Time expected_t = base::Time::FromInternalValue(i + 1);
+ EXPECT_EQ(expected_t, smoother.GetSmoothedTime(t));
+ }
+ for (int64 i = 1; i < 1000; ++i) {
+ base::Time expected_t =
+ base::Time::FromInternalValue(i + kNumDuplicates);
+ t = base::Time::FromInternalValue(i);
+ EXPECT_EQ(expected_t, smoother.GetSmoothedTime(t));
+ }
+}
+
+// If the clock jumps far back enough after a run of duplicates, it
+// should immediately jump to that value.
+TEST(TimeSmoother, ClockBackwardsJump) {
+ const int64 kNumDuplicates = 100;
+ NavigationControllerImpl::TimeSmoother smoother;
+ base::Time t = base::Time::FromInternalValue(1000);
+ for (int64 i = 0; i < kNumDuplicates; ++i) {
+ base::Time expected_t = base::Time::FromInternalValue(i + 1000);
+ EXPECT_EQ(expected_t, smoother.GetSmoothedTime(t));
+ }
+ t = base::Time::FromInternalValue(500);
+ EXPECT_EQ(t, smoother.GetSmoothedTime(t));
+}
+
+// NavigationControllerTest ----------------------------------------------------
+
+class NavigationControllerTest
+ : public RenderViewHostImplTestHarness,
+ public WebContentsObserver {
+ public:
+ NavigationControllerTest() : navigation_entry_committed_counter_(0) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ RenderViewHostImplTestHarness::SetUp();
+ WebContents* web_contents = RenderViewHostImplTestHarness::web_contents();
+ ASSERT_TRUE(web_contents); // The WebContents should be created by now.
+ WebContentsObserver::Observe(web_contents);
+ }
+
+ // WebContentsObserver:
+ virtual void NavigateToPendingEntry(
+ const GURL& url,
+ NavigationController::ReloadType reload_type) OVERRIDE {
+ navigated_url_ = url;
+ }
+
+ virtual void NavigationEntryCommitted(
+ const LoadCommittedDetails& load_details) OVERRIDE {
+ navigation_entry_committed_counter_++;
+ }
+
+ const GURL& navigated_url() const {
+ return navigated_url_;
+ }
+
+ NavigationControllerImpl& controller_impl() {
+ return static_cast<NavigationControllerImpl&>(controller());
+ }
+
+ protected:
+ GURL navigated_url_;
+ size_t navigation_entry_committed_counter_;
+};
+
+void RegisterForAllNavNotifications(TestNotificationTracker* tracker,
+ NavigationController* controller) {
+ tracker->ListenFor(NOTIFICATION_NAV_LIST_PRUNED,
+ Source<NavigationController>(controller));
+ tracker->ListenFor(NOTIFICATION_NAV_ENTRY_CHANGED,
+ Source<NavigationController>(controller));
+}
+
+SiteInstance* GetSiteInstanceFromEntry(NavigationEntry* entry) {
+ return NavigationEntryImpl::FromNavigationEntry(entry)->site_instance();
+}
+
+class TestWebContentsDelegate : public WebContentsDelegate {
+ public:
+ explicit TestWebContentsDelegate() :
+ navigation_state_change_count_(0) {}
+
+ int navigation_state_change_count() {
+ return navigation_state_change_count_;
+ }
+
+ // Keep track of whether the tab has notified us of a navigation state change.
+ virtual void NavigationStateChanged(const WebContents* source,
+ unsigned changed_flags) OVERRIDE {
+ navigation_state_change_count_++;
+ }
+
+ private:
+ // The number of times NavigationStateChanged has been called.
+ int navigation_state_change_count_;
+};
+
+// -----------------------------------------------------------------------------
+
+TEST_F(NavigationControllerTest, Defaults) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.GetVisibleEntry());
+ EXPECT_FALSE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), -1);
+ EXPECT_EQ(controller.GetEntryCount(), 0);
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+TEST_F(NavigationControllerTest, GoToOffset) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const int kNumUrls = 5;
+ std::vector<GURL> urls(kNumUrls);
+ for (int i = 0; i < kNumUrls; ++i) {
+ urls[i] = GURL(base::StringPrintf("http://www.a.com/%d", i));
+ }
+
+ test_rvh()->SendNavigate(0, urls[0]);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(urls[0], controller.GetVisibleEntry()->GetVirtualURL());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_FALSE(controller.CanGoToOffset(1));
+
+ for (int i = 1; i <= 4; ++i) {
+ test_rvh()->SendNavigate(i, urls[i]);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(urls[i], controller.GetVisibleEntry()->GetVirtualURL());
+ EXPECT_TRUE(controller.CanGoToOffset(-i));
+ EXPECT_FALSE(controller.CanGoToOffset(-(i + 1)));
+ EXPECT_FALSE(controller.CanGoToOffset(1));
+ }
+
+ // We have loaded 5 pages, and are currently at the last-loaded page.
+ int url_index = 4;
+
+ enum Tests {
+ GO_TO_MIDDLE_PAGE = -2,
+ GO_FORWARDS = 1,
+ GO_BACKWARDS = -1,
+ GO_TO_BEGINNING = -2,
+ GO_TO_END = 4,
+ NUM_TESTS = 5,
+ };
+
+ const int test_offsets[NUM_TESTS] = {
+ GO_TO_MIDDLE_PAGE,
+ GO_FORWARDS,
+ GO_BACKWARDS,
+ GO_TO_BEGINNING,
+ GO_TO_END
+ };
+
+ for (int test = 0; test < NUM_TESTS; ++test) {
+ int offset = test_offsets[test];
+ controller.GoToOffset(offset);
+ url_index += offset;
+ // Check that the GoToOffset will land on the expected page.
+ EXPECT_EQ(urls[url_index], controller.GetPendingEntry()->GetVirtualURL());
+ test_rvh()->SendNavigate(url_index, urls[url_index]);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ // Check that we can go to any valid offset into the history.
+ for (size_t j = 0; j < urls.size(); ++j)
+ EXPECT_TRUE(controller.CanGoToOffset(j - url_index));
+ // Check that we can't go beyond the beginning or end of the history.
+ EXPECT_FALSE(controller.CanGoToOffset(-(url_index + 1)));
+ EXPECT_FALSE(controller.CanGoToOffset(urls.size() - url_index));
+ }
+}
+
+TEST_F(NavigationControllerTest, LoadURL) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // Creating a pending notification should not have issued any of the
+ // notifications we're listening for.
+ EXPECT_EQ(0U, notifications.size());
+
+ // The load should now be pending.
+ EXPECT_EQ(controller.GetEntryCount(), 0);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), -1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetLastCommittedEntry());
+ ASSERT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntry(), controller.GetVisibleEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), -1);
+
+ // Neither the timestamp nor the status code should have been set yet.
+ EXPECT_TRUE(controller.GetPendingEntry()->GetTimestamp().is_null());
+ EXPECT_EQ(0, controller.GetPendingEntry()->GetHttpStatusCode());
+
+ // We should have gotten no notifications from the preceeding checks.
+ EXPECT_EQ(0U, notifications.size());
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The load should now be committed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ ASSERT_TRUE(controller.GetVisibleEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 0);
+ EXPECT_EQ(0, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+
+ // The timestamp should have been set.
+ EXPECT_FALSE(controller.GetVisibleEntry()->GetTimestamp().is_null());
+
+ // Load another...
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // The load should now be pending.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ ASSERT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntry(), controller.GetVisibleEntry());
+ // TODO(darin): maybe this should really be true?
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 0);
+
+ EXPECT_TRUE(controller.GetPendingEntry()->GetTimestamp().is_null());
+
+ // Simulate the beforeunload ack for the cross-site transition, and then the
+ // commit.
+ test_rvh()->SendShouldCloseACK(true);
+ static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost())->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The load should now be committed.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ ASSERT_TRUE(controller.GetVisibleEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 1);
+
+ EXPECT_FALSE(controller.GetVisibleEntry()->GetTimestamp().is_null());
+}
+
+namespace {
+
+base::Time GetFixedTime(base::Time time) {
+ return time;
+}
+
+} // namespace
+
+TEST_F(NavigationControllerTest, LoadURLSameTime) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set the clock to always return a timestamp of 1.
+ controller.SetGetTimestampCallbackForTest(
+ base::Bind(&GetFixedTime, base::Time::FromInternalValue(1)));
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Load another...
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // Simulate the beforeunload ack for the cross-site transition, and then the
+ // commit.
+ test_rvh()->SendShouldCloseACK(true);
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The two loads should now be committed.
+ ASSERT_EQ(controller.GetEntryCount(), 2);
+
+ // Timestamps should be distinct despite the clock returning the
+ // same value.
+ EXPECT_EQ(1u,
+ controller.GetEntryAtIndex(0)->GetTimestamp().ToInternalValue());
+ EXPECT_EQ(2u,
+ controller.GetEntryAtIndex(1)->GetTimestamp().ToInternalValue());
+}
+
+void CheckNavigationEntryMatchLoadParams(
+ NavigationController::LoadURLParams& load_params,
+ NavigationEntryImpl* entry) {
+ EXPECT_EQ(load_params.url, entry->GetURL());
+ EXPECT_EQ(load_params.referrer.url, entry->GetReferrer().url);
+ EXPECT_EQ(load_params.referrer.policy, entry->GetReferrer().policy);
+ EXPECT_EQ(load_params.transition_type, entry->GetTransitionType());
+ EXPECT_EQ(load_params.extra_headers, entry->extra_headers());
+
+ EXPECT_EQ(load_params.is_renderer_initiated, entry->is_renderer_initiated());
+ EXPECT_EQ(load_params.base_url_for_data_url, entry->GetBaseURLForDataURL());
+ if (!load_params.virtual_url_for_data_url.is_empty()) {
+ EXPECT_EQ(load_params.virtual_url_for_data_url, entry->GetVirtualURL());
+ }
+ if (NavigationController::UA_OVERRIDE_INHERIT !=
+ load_params.override_user_agent) {
+ bool should_override = (NavigationController::UA_OVERRIDE_TRUE ==
+ load_params.override_user_agent);
+ EXPECT_EQ(should_override, entry->GetIsOverridingUserAgent());
+ }
+ EXPECT_EQ(load_params.browser_initiated_post_data,
+ entry->GetBrowserInitiatedPostData());
+ EXPECT_EQ(load_params.transferred_global_request_id,
+ entry->transferred_global_request_id());
+}
+
+TEST_F(NavigationControllerTest, LoadURLWithParams) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ NavigationController::LoadURLParams load_params(GURL("http://foo"));
+ load_params.referrer =
+ Referrer(GURL("http://referrer"), WebKit::WebReferrerPolicyDefault);
+ load_params.transition_type = PAGE_TRANSITION_GENERATED;
+ load_params.extra_headers = "content-type: text/plain";
+ load_params.load_type = NavigationController::LOAD_TYPE_DEFAULT;
+ load_params.is_renderer_initiated = true;
+ load_params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE;
+ load_params.transferred_global_request_id = GlobalRequestID(2, 3);
+
+ controller.LoadURLWithParams(load_params);
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry());
+
+ // The timestamp should not have been set yet.
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->GetTimestamp().is_null());
+
+ CheckNavigationEntryMatchLoadParams(load_params, entry);
+}
+
+TEST_F(NavigationControllerTest, LoadURLWithExtraParams_Data) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ NavigationController::LoadURLParams load_params(
+ GURL("data:text/html,dataurl"));
+ load_params.load_type = NavigationController::LOAD_TYPE_DATA;
+ load_params.base_url_for_data_url = GURL("http://foo");
+ load_params.virtual_url_for_data_url = GURL(kAboutBlankURL);
+ load_params.override_user_agent = NavigationController::UA_OVERRIDE_FALSE;
+
+ controller.LoadURLWithParams(load_params);
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry());
+
+ CheckNavigationEntryMatchLoadParams(load_params, entry);
+}
+
+TEST_F(NavigationControllerTest, LoadURLWithExtraParams_HttpPost) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ NavigationController::LoadURLParams load_params(GURL("https://posturl"));
+ load_params.transition_type = PAGE_TRANSITION_TYPED;
+ load_params.load_type =
+ NavigationController::LOAD_TYPE_BROWSER_INITIATED_HTTP_POST;
+ load_params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE;
+
+
+ const unsigned char* raw_data =
+ reinterpret_cast<const unsigned char*>("d\n\0a2");
+ const int length = 5;
+ std::vector<unsigned char> post_data_vector(raw_data, raw_data+length);
+ scoped_refptr<base::RefCountedBytes> data =
+ base::RefCountedBytes::TakeVector(&post_data_vector);
+ load_params.browser_initiated_post_data = data.get();
+
+ controller.LoadURLWithParams(load_params);
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry());
+
+ CheckNavigationEntryMatchLoadParams(load_params, entry);
+}
+
+// Tests what happens when the same page is loaded again. Should not create a
+// new session history entry. This is what happens when you press enter in the
+// URL bar to reload: a pending entry is created and then it is discarded when
+// the load commits (because WebCore didn't actually make a new entry).
+TEST_F(NavigationControllerTest, LoadURL_SamePage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ ASSERT_TRUE(controller.GetVisibleEntry());
+ const base::Time timestamp = controller.GetVisibleEntry()->GetTimestamp();
+ EXPECT_FALSE(timestamp.is_null());
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // We should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ ASSERT_TRUE(controller.GetVisibleEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ // The timestamp should have been updated.
+ //
+ // TODO(akalin): Change this EXPECT_GE (and other similar ones) to
+ // EXPECT_GT once we guarantee that timestamps are unique.
+ EXPECT_GE(controller.GetVisibleEntry()->GetTimestamp(), timestamp);
+}
+
+// Tests loading a URL but discarding it before the load commits.
+TEST_F(NavigationControllerTest, LoadURL_Discarded) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ ASSERT_TRUE(controller.GetVisibleEntry());
+ const base::Time timestamp = controller.GetVisibleEntry()->GetTimestamp();
+ EXPECT_FALSE(timestamp.is_null());
+
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ controller.DiscardNonCommittedEntries();
+ EXPECT_EQ(0U, notifications.size());
+
+ // Should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ ASSERT_TRUE(controller.GetVisibleEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ // Timestamp should not have changed.
+ EXPECT_EQ(timestamp, controller.GetVisibleEntry()->GetTimestamp());
+}
+
+// Tests navigations that come in unrequested. This happens when the user
+// navigates from the web page, and here we test that there is no pending entry.
+TEST_F(NavigationControllerTest, LoadURL_NoPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make an existing committed entry.
+ const GURL kExistingURL1("http://eh");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Do a new navigation without making a pending one.
+ const GURL kNewURL("http://see");
+ test_rvh()->SendNavigate(99, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetVisibleEntry()->GetURL());
+}
+
+// Tests navigating to a new URL when there is a new pending navigation that is
+// not the one that just loaded. This will happen if the user types in a URL to
+// somewhere slow, and then navigates the current page before the typed URL
+// commits.
+TEST_F(NavigationControllerTest, LoadURL_NewPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make an existing committed entry.
+ const GURL kExistingURL1("http://eh");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Make a pending entry to somewhere new.
+ const GURL kExistingURL2("http://bee");
+ controller.LoadURL(
+ kExistingURL2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+
+ // After the beforeunload but before it commits, do a new navigation.
+ test_rvh()->SendShouldCloseACK(true);
+ const GURL kNewURL("http://see");
+ static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost())->SendNavigate(3, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetVisibleEntry()->GetURL());
+}
+
+// Tests navigating to a new URL when there is a pending back/forward
+// navigation. This will happen if the user hits back, but before that commits,
+// they navigate somewhere new.
+TEST_F(NavigationControllerTest, LoadURL_ExistingPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make some history.
+ const GURL kExistingURL1("http://foo/eh");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL kExistingURL2("http://foo/bee");
+ controller.LoadURL(
+ kExistingURL2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, kExistingURL2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now make a pending back/forward navigation. The zeroth entry should be
+ // pending.
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+
+ // Before that commits, do a new navigation.
+ const GURL kNewURL("http://foo/see");
+ LoadCommittedDetails details;
+ test_rvh()->SendNavigate(3, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetVisibleEntry()->GetURL());
+}
+
+// Tests navigating to a new URL when there is a pending back/forward
+// navigation to a cross-process, privileged URL. This will happen if the user
+// hits back, but before that commits, they navigate somewhere new.
+TEST_F(NavigationControllerTest, LoadURL_PrivilegedPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make some history, starting with a privileged URL.
+ const GURL kExistingURL1("http://privileged");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // Pretend it has bindings so we can tell if we incorrectly copy it.
+ test_rvh()->AllowBindings(2);
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Navigate cross-process to a second URL.
+ const GURL kExistingURL2("http://foo/eh");
+ controller.LoadURL(
+ kExistingURL2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendShouldCloseACK(true);
+ TestRenderViewHost* foo_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost());
+ foo_rvh->SendNavigate(1, kExistingURL2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now make a pending back/forward navigation to a privileged entry.
+ // The zeroth entry should be pending.
+ controller.GoBack();
+ foo_rvh->SendShouldCloseACK(true);
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(2, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry())->bindings());
+
+ // Before that commits, do a new navigation.
+ const GURL kNewURL("http://foo/bee");
+ LoadCommittedDetails details;
+ foo_rvh->SendNavigate(3, kNewURL);
+
+ // There should no longer be any pending entry, and the third navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kNewURL, controller.GetVisibleEntry()->GetURL());
+ EXPECT_EQ(0, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+}
+
+// Tests navigating to an existing URL when there is a pending new navigation.
+// This will happen if the user enters a URL, but before that commits, the
+// current page fires history.back().
+TEST_F(NavigationControllerTest, LoadURL_BackPreemptsPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make some history.
+ const GURL kExistingURL1("http://foo/eh");
+ controller.LoadURL(
+ kExistingURL1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL kExistingURL2("http://foo/bee");
+ controller.LoadURL(
+ kExistingURL2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, kExistingURL2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now make a pending new navigation.
+ const GURL kNewURL("http://foo/see");
+ controller.LoadURL(
+ kNewURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+
+ // Before that commits, a back navigation from the renderer commits.
+ test_rvh()->SendNavigate(0, kExistingURL1);
+
+ // There should no longer be any pending entry, and the back navigation we
+ // just made should be committed.
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kExistingURL1, controller.GetVisibleEntry()->GetURL());
+}
+
+// Tests an ignored navigation when there is a pending new navigation.
+// This will happen if the user enters a URL, but before that commits, the
+// current blank page reloads. See http://crbug.com/77507.
+TEST_F(NavigationControllerTest, LoadURL_IgnorePreemptsPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Without any navigations, the renderer starts at about:blank.
+ const GURL kExistingURL(kAboutBlankURL);
+
+ // Now make a pending new navigation.
+ const GURL kNewURL("http://eh");
+ controller.LoadURL(
+ kNewURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+
+ // Before that commits, a document.write and location.reload can cause the
+ // renderer to send a FrameNavigate with page_id -1.
+ test_rvh()->SendNavigate(-1, kExistingURL);
+
+ // This should clear the pending entry and notify of a navigation state
+ // change, so that we do not keep displaying kNewURL.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(2, delegate->navigation_state_change_count());
+
+ contents()->SetDelegate(NULL);
+}
+
+// Tests that the pending entry state is correct after an abort.
+// We do not want to clear the pending entry, so that the user doesn't
+// lose a typed URL. (See http://crbug.com/9682.)
+TEST_F(NavigationControllerTest, LoadURL_AbortDoesntCancelPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Start with a pending new navigation.
+ const GURL kNewURL("http://eh");
+ controller.LoadURL(
+ kNewURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+
+ // It may abort before committing, if it's a download or due to a stop or
+ // a new navigation from the user.
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params params;
+ params.frame_id = 1;
+ params.is_main_frame = true;
+ params.error_code = net::ERR_ABORTED;
+ params.error_description = string16();
+ params.url = kNewURL;
+ params.showing_repost_interstitial = false;
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ params));
+
+ // This should not clear the pending entry or notify of a navigation state
+ // change, so that we keep displaying kNewURL (until the user clears it).
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(1, delegate->navigation_state_change_count());
+ NavigationEntry* pending_entry = controller.GetPendingEntry();
+
+ // Ensure that a reload keeps the same pending entry.
+ controller.Reload(true);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(pending_entry, controller.GetPendingEntry());
+ EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
+
+ contents()->SetDelegate(NULL);
+}
+
+// Tests that the pending URL is not visible during a renderer-initiated
+// redirect and abort. See http://crbug.com/83031.
+TEST_F(NavigationControllerTest, LoadURL_RedirectAbortDoesntShowPendingURL) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // First make an existing committed entry.
+ const GURL kExistingURL("http://foo/eh");
+ controller.LoadURL(kExistingURL, content::Referrer(),
+ content::PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, kExistingURL);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Set a WebContentsDelegate to listen for state changes.
+ scoped_ptr<TestWebContentsDelegate> delegate(new TestWebContentsDelegate());
+ EXPECT_FALSE(contents()->GetDelegate());
+ contents()->SetDelegate(delegate.get());
+
+ // Now make a pending new navigation, initiated by the renderer.
+ const GURL kNewURL("http://foo/bee");
+ NavigationController::LoadURLParams load_url_params(kNewURL);
+ load_url_params.transition_type = PAGE_TRANSITION_TYPED;
+ load_url_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url_params);
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(0, delegate->navigation_state_change_count());
+
+ // The visible entry should be the last committed URL, not the pending one.
+ EXPECT_EQ(kExistingURL, controller.GetVisibleEntry()->GetURL());
+
+ // Now the navigation redirects.
+ const GURL kRedirectURL("http://foo/see");
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidRedirectProvisionalLoad(0, // routing_id
+ -1, // pending page_id
+ kNewURL, // old url
+ kRedirectURL)); // new url
+
+ // We don't want to change the NavigationEntry's url, in case it cancels.
+ // Prevents regression of http://crbug.com/77786.
+ EXPECT_EQ(kNewURL, controller.GetPendingEntry()->GetURL());
+
+ // It may abort before committing, if it's a download or due to a stop or
+ // a new navigation from the user.
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params params;
+ params.frame_id = 1;
+ params.is_main_frame = true;
+ params.error_code = net::ERR_ABORTED;
+ params.error_description = string16();
+ params.url = kRedirectURL;
+ params.showing_repost_interstitial = false;
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ params));
+
+ // Because the pending entry is renderer initiated and not visible, we
+ // clear it when it fails.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(0, delegate->navigation_state_change_count());
+
+ // The visible entry should be the last committed URL, not the pending one,
+ // so that no spoof is possible.
+ EXPECT_EQ(kExistingURL, controller.GetVisibleEntry()->GetURL());
+
+ contents()->SetDelegate(NULL);
+}
+
+// Ensure that NavigationEntries track which bindings their RenderViewHost had
+// at the time they committed. http://crbug.com/173672.
+TEST_F(NavigationControllerTest, LoadURL_WithBindings) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ // Navigate to a first, unprivileged URL.
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(NavigationEntryImpl::kInvalidBindings,
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry())->bindings());
+
+ // Commit.
+ TestRenderViewHost* orig_rvh = static_cast<TestRenderViewHost*>(test_rvh());
+ orig_rvh->SendNavigate(0, url1);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(0, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+
+ // Manually increase the number of active views in the SiteInstance
+ // that orig_rvh belongs to, to prevent it from being destroyed when
+ // it gets swapped out, so that we can reuse orig_rvh when the
+ // controller goes back.
+ static_cast<SiteInstanceImpl*>(orig_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ // Navigate to a second URL, simulate the beforeunload ack for the cross-site
+ // transition, and set bindings on the pending RenderViewHost to simulate a
+ // privileged url.
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ orig_rvh->SendShouldCloseACK(true);
+ contents()->GetPendingRenderViewHost()->AllowBindings(1);
+ static_cast<TestRenderViewHost*>(
+ contents()->GetPendingRenderViewHost())->SendNavigate(1, url2);
+
+ // The second load should be committed, and bindings should be remembered.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_EQ(1, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+
+ // Going back, the first entry should still appear unprivileged.
+ controller.GoBack();
+ orig_rvh->SendNavigate(0, url1);
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(0, NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->bindings());
+}
+
+TEST_F(NavigationControllerTest, Reload) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ ASSERT_TRUE(controller.GetVisibleEntry());
+ controller.GetVisibleEntry()->SetTitle(ASCIIToUTF16("Title"));
+ controller.Reload(true);
+ EXPECT_EQ(0U, notifications.size());
+
+ const base::Time timestamp = controller.GetVisibleEntry()->GetTimestamp();
+ EXPECT_FALSE(timestamp.is_null());
+
+ // The reload is pending.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ // Make sure the title has been cleared (will be redrawn just after reload).
+ // Avoids a stale cached title when the new page being reloaded has no title.
+ // See http://crbug.com/96041.
+ EXPECT_TRUE(controller.GetVisibleEntry()->GetTitle().empty());
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ // The timestamp should have been updated.
+ ASSERT_TRUE(controller.GetVisibleEntry());
+ EXPECT_GE(controller.GetVisibleEntry()->GetTimestamp(), timestamp);
+}
+
+// Tests what happens when a reload navigation produces a new page.
+TEST_F(NavigationControllerTest, Reload_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.Reload(true);
+ EXPECT_EQ(0U, notifications.size());
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+#if !defined(OS_ANDROID) // http://crbug.com/157428
+TEST_F(NavigationControllerTest, ReloadOriginalRequestURL) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL original_url("http://foo1");
+ const GURL final_url("http://foo2");
+
+ // Load up the original URL, but get redirected.
+ controller.LoadURL(
+ original_url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigateWithOriginalRequestURL(0, final_url, original_url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The NavigationEntry should save both the original URL and the final
+ // redirected URL.
+ EXPECT_EQ(
+ original_url, controller.GetVisibleEntry()->GetOriginalRequestURL());
+ EXPECT_EQ(final_url, controller.GetVisibleEntry()->GetURL());
+
+ // Reload using the original URL.
+ controller.GetVisibleEntry()->SetTitle(ASCIIToUTF16("Title"));
+ controller.ReloadOriginalRequestURL(false);
+ EXPECT_EQ(0U, notifications.size());
+
+ // The reload is pending. The request should point to the original URL.
+ EXPECT_EQ(original_url, navigated_url());
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ // Make sure the title has been cleared (will be redrawn just after reload).
+ // Avoids a stale cached title when the new page being reloaded has no title.
+ // See http://crbug.com/96041.
+ EXPECT_TRUE(controller.GetVisibleEntry()->GetTitle().empty());
+
+ // Send that the navigation has proceeded; say it got redirected again.
+ test_rvh()->SendNavigate(0, final_url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now the reload is committed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+#endif // !defined(OS_ANDROID)
+
+// Test that certain non-persisted NavigationEntryImpl values get reset after
+// commit.
+TEST_F(NavigationControllerTest, ResetEntryValuesAfterCommit) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // Set up some sample values.
+ const unsigned char* raw_data =
+ reinterpret_cast<const unsigned char*>("post\n\n\0data");
+ const int length = 11;
+ std::vector<unsigned char> post_data_vector(raw_data, raw_data+length);
+ scoped_refptr<base::RefCountedBytes> post_data =
+ base::RefCountedBytes::TakeVector(&post_data_vector);
+ GlobalRequestID transfer_id(3, 4);
+ std::vector<GURL> redirects;
+ redirects.push_back(GURL("http://foo2"));
+
+ // Set non-persisted values on the pending entry.
+ NavigationEntryImpl* pending_entry =
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry());
+ pending_entry->SetBrowserInitiatedPostData(post_data.get());
+ pending_entry->set_is_renderer_initiated(true);
+ pending_entry->set_transferred_global_request_id(transfer_id);
+ pending_entry->set_should_replace_entry(true);
+ pending_entry->set_redirect_chain(redirects);
+ pending_entry->set_should_clear_history_list(true);
+ EXPECT_EQ(post_data.get(), pending_entry->GetBrowserInitiatedPostData());
+ EXPECT_TRUE(pending_entry->is_renderer_initiated());
+ EXPECT_EQ(transfer_id, pending_entry->transferred_global_request_id());
+ EXPECT_TRUE(pending_entry->should_replace_entry());
+ EXPECT_EQ(1U, pending_entry->redirect_chain().size());
+ EXPECT_TRUE(pending_entry->should_clear_history_list());
+
+ test_rvh()->SendNavigate(0, url1);
+
+ // Certain values that are only used for pending entries get reset after
+ // commit.
+ NavigationEntryImpl* committed_entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry());
+ EXPECT_FALSE(committed_entry->GetBrowserInitiatedPostData());
+ EXPECT_FALSE(committed_entry->is_renderer_initiated());
+ EXPECT_EQ(GlobalRequestID(-1, -1),
+ committed_entry->transferred_global_request_id());
+ EXPECT_FALSE(committed_entry->should_replace_entry());
+ EXPECT_EQ(0U, committed_entry->redirect_chain().size());
+ EXPECT_FALSE(committed_entry->should_clear_history_list());
+}
+
+// Tests what happens when we navigate back successfully
+TEST_F(NavigationControllerTest, Back) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL url2("http://foo2");
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+
+ // We should now have a pending navigation to go back.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoToOffset(-1));
+ EXPECT_TRUE(controller.CanGoForward());
+ EXPECT_TRUE(controller.CanGoToOffset(1));
+ EXPECT_FALSE(controller.CanGoToOffset(2)); // Cannot go foward 2 steps.
+
+ // Timestamp for entry 1 should be on or after that of entry 0.
+ EXPECT_FALSE(controller.GetEntryAtIndex(0)->GetTimestamp().is_null());
+ EXPECT_GE(controller.GetEntryAtIndex(1)->GetTimestamp(),
+ controller.GetEntryAtIndex(0)->GetTimestamp());
+
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The back navigation completed successfully.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoToOffset(-1));
+ EXPECT_TRUE(controller.CanGoForward());
+ EXPECT_TRUE(controller.CanGoToOffset(1));
+ EXPECT_FALSE(controller.CanGoToOffset(2)); // Cannot go foward 2 steps.
+
+ // Timestamp for entry 0 should be on or after that of entry 1
+ // (since we went back to it).
+ EXPECT_GE(controller.GetEntryAtIndex(0)->GetTimestamp(),
+ controller.GetEntryAtIndex(1)->GetTimestamp());
+}
+
+// Tests what happens when a back navigation produces a new page.
+TEST_F(NavigationControllerTest, Back_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoBack();
+ EXPECT_EQ(0U, notifications.size());
+
+ // We should now have a pending navigation to go back.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoForward());
+
+ test_rvh()->SendNavigate(2, url3);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The back navigation resulted in a completely new navigation.
+ // TODO(darin): perhaps this behavior will be confusing to users?
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 2);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Receives a back message when there is a new pending navigation entry.
+TEST_F(NavigationControllerTest, Back_NewPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL kUrl1("http://foo1");
+ const GURL kUrl2("http://foo2");
+ const GURL kUrl3("http://foo3");
+
+ // First navigate two places so we have some back history.
+ test_rvh()->SendNavigate(0, kUrl1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // controller.LoadURL(kUrl2, PAGE_TRANSITION_TYPED);
+ test_rvh()->SendNavigate(1, kUrl2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Now start a new pending navigation and go back before it commits.
+ controller.LoadURL(kUrl3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(kUrl3, controller.GetPendingEntry()->GetURL());
+ controller.GoBack();
+
+ // The pending navigation should now be the "back" item and the new one
+ // should be gone.
+ EXPECT_EQ(0, controller.GetPendingEntryIndex());
+ EXPECT_EQ(kUrl1, controller.GetPendingEntry()->GetURL());
+}
+
+// Receives a back message when there is a different renavigation already
+// pending.
+TEST_F(NavigationControllerTest, Back_OtherBackPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL kUrl1("http://foo/1");
+ const GURL kUrl2("http://foo/2");
+ const GURL kUrl3("http://foo/3");
+
+ // First navigate three places so we have some back history.
+ test_rvh()->SendNavigate(0, kUrl1);
+ test_rvh()->SendNavigate(1, kUrl2);
+ test_rvh()->SendNavigate(2, kUrl3);
+
+ // With nothing pending, say we get a navigation to the second entry.
+ test_rvh()->SendNavigate(1, kUrl2);
+
+ // We know all the entries have the same site instance, so we can just grab
+ // a random one for looking up other entries.
+ SiteInstance* site_instance =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->site_instance();
+
+ // That second URL should be the last committed and it should have gotten the
+ // new title.
+ EXPECT_EQ(kUrl2, controller.GetEntryWithPageID(site_instance, 1)->GetURL());
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+
+ // Now go forward to the last item again and say it was committed.
+ controller.GoForward();
+ test_rvh()->SendNavigate(2, kUrl3);
+
+ // Now start going back one to the second page. It will be pending.
+ controller.GoBack();
+ EXPECT_EQ(1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+
+ // Not synthesize a totally new back event to the first page. This will not
+ // match the pending one.
+ test_rvh()->SendNavigate(0, kUrl1);
+
+ // The committed navigation should clear the pending entry.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+
+ // But the navigated entry should be the last committed.
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(kUrl1, controller.GetLastCommittedEntry()->GetURL());
+}
+
+// Tests what happens when we navigate forward successfully.
+TEST_F(NavigationControllerTest, Forward) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoForward();
+
+ // We should now have a pending navigation to go forward.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoToOffset(-1));
+ EXPECT_FALSE(controller.CanGoToOffset(-2)); // Cannot go back 2 steps.
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_FALSE(controller.CanGoToOffset(1));
+
+ // Timestamp for entry 0 should be on or after that of entry 1
+ // (since we went back to it).
+ EXPECT_FALSE(controller.GetEntryAtIndex(0)->GetTimestamp().is_null());
+ EXPECT_GE(controller.GetEntryAtIndex(0)->GetTimestamp(),
+ controller.GetEntryAtIndex(1)->GetTimestamp());
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // The forward navigation completed successfully.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_TRUE(controller.CanGoToOffset(-1));
+ EXPECT_FALSE(controller.CanGoToOffset(-2)); // Cannot go back 2 steps.
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_FALSE(controller.CanGoToOffset(1));
+
+ // Timestamp for entry 1 should be on or after that of entry 0
+ // (since we went forward to it).
+ EXPECT_GE(controller.GetEntryAtIndex(1)->GetTimestamp(),
+ controller.GetEntryAtIndex(0)->GetTimestamp());
+}
+
+// Tests what happens when a forward navigation produces a new page.
+TEST_F(NavigationControllerTest, Forward_GeneratesNewPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoBack();
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ controller.GoForward();
+ EXPECT_EQ(0U, notifications.size());
+
+ // Should now have a pending navigation to go forward.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), 1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+
+ test_rvh()->SendNavigate(2, url3);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(notifications.Check1AndReset(NOTIFICATION_NAV_LIST_PRUNED));
+
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Two consequent navigation for the same URL entered in should be considered
+// as SAME_PAGE navigation even when we are redirected to some other page.
+TEST_F(NavigationControllerTest, Redirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Second request
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ EXPECT_TRUE(details.type == NAVIGATION_TYPE_SAME_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Similar to Redirect above, but the first URL is requested by POST,
+// the second URL is requested by GET. NavigationEntry::has_post_data_
+// must be cleared. http://crbug.com/21245
+TEST_F(NavigationControllerTest, PostThenRedirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request as POST
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ controller.GetVisibleEntry()->SetHasPostData(true);
+
+ EXPECT_EQ(0U, notifications.size());
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Second request
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ EXPECT_TRUE(details.type == NAVIGATION_TYPE_SAME_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+ EXPECT_FALSE(controller.GetVisibleEntry()->GetHasPostData());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// A redirect right off the bat should be a NEW_PAGE.
+TEST_F(NavigationControllerTest, ImmediateRedirect) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2"); // Redirection target
+
+ // First request
+ controller.LoadURL(url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_SERVER_REDIRECT;
+ params.redirects.push_back(GURL("http://foo1"));
+ params.redirects.push_back(GURL("http://foo2"));
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ LoadCommittedDetails details;
+
+ EXPECT_EQ(0U, notifications.size());
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ EXPECT_TRUE(details.type == NAVIGATION_TYPE_NEW_PAGE);
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+// Tests navigation via link click within a subframe. A new navigation entry
+// should be created.
+TEST_F(NavigationControllerTest, NewSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_MANUAL_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(url1, details.previous_url);
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_FALSE(details.is_main_frame);
+
+ // The new entry should be appended.
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // New entry should refer to the new page, but the old URL (entries only
+ // reflect the toplevel URL).
+ EXPECT_EQ(url1, details.entry->GetURL());
+ EXPECT_EQ(params.page_id, details.entry->GetPageID());
+}
+
+// Some pages create a popup, then write an iframe into it. This causes a
+// subframe navigation without having any committed entry. Such navigations
+// just get thrown on the ground, but we shouldn't crash.
+TEST_F(NavigationControllerTest, SubframeOnEmptyPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Navigation controller currently has no entries.
+ const GURL url("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url;
+ params.transition = PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+
+ LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(0U, notifications.size());
+}
+
+// Auto subframes are ones the page loads automatically like ads. They should
+// not create new navigation entries.
+TEST_F(NavigationControllerTest, AutoSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ // Navigating should do nothing.
+ LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(0U, notifications.size());
+
+ // There should still be only one entry.
+ EXPECT_EQ(1, controller.GetEntryCount());
+}
+
+// Tests navigation and then going back to a subframe navigation.
+TEST_F(NavigationControllerTest, BackSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // First manual subframe navigation.
+ const GURL url2("http://foo2");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_MANUAL_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ // This should generate a new entry.
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // Second manual subframe navigation should also make a new entry.
+ const GURL url3("http://foo3");
+ params.page_id = 2;
+ params.url = url3;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(2, controller.GetCurrentEntryIndex());
+
+ // Go back one.
+ controller.GoBack();
+ params.url = url2;
+ params.page_id = 1;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+
+ // Go back one more.
+ controller.GoBack();
+ params.url = url1;
+ params.page_id = 0;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+}
+
+TEST_F(NavigationControllerTest, LinkClick) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ test_rvh()->SendNavigate(1, url2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Should not have produced a new session history entry.
+ EXPECT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+}
+
+TEST_F(NavigationControllerTest, InPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Ensure main page navigation to same url respects the was_within_same_page
+ // hint provided in the params.
+ ViewHostMsg_FrameNavigate_Params self_params;
+ self_params.page_id = 0;
+ self_params.url = url1;
+ self_params.transition = PAGE_TRANSITION_LINK;
+ self_params.should_update_history = false;
+ self_params.gesture = NavigationGestureUser;
+ self_params.is_post = false;
+ self_params.page_state = PageState::CreateFromURL(url1);
+ self_params.was_within_same_page = true;
+
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(self_params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(1, controller.GetEntryCount());
+
+ // Fragment navigation to a new page_id.
+ const GURL url2("http://foo#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+ params.was_within_same_page = true;
+
+ // This should generate a new entry.
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_FALSE(details.did_replace_entry);
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ // Go back one.
+ ViewHostMsg_FrameNavigate_Params back_params(params);
+ controller.GoBack();
+ back_params.url = url1;
+ back_params.page_id = 0;
+ EXPECT_TRUE(controller.RendererDidNavigate(back_params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(back_params.url, controller.GetVisibleEntry()->GetURL());
+
+ // Go forward
+ ViewHostMsg_FrameNavigate_Params forward_params(params);
+ controller.GoForward();
+ forward_params.url = url2;
+ forward_params.page_id = 1;
+ EXPECT_TRUE(controller.RendererDidNavigate(forward_params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(forward_params.url,
+ controller.GetVisibleEntry()->GetURL());
+
+ // Now go back and forward again. This is to work around a bug where we would
+ // compare the incoming URL with the last committed entry rather than the
+ // one identified by an existing page ID. This would result in the second URL
+ // losing the reference fragment when you navigate away from it and then back.
+ controller.GoBack();
+ EXPECT_TRUE(controller.RendererDidNavigate(back_params, &details));
+ controller.GoForward();
+ EXPECT_TRUE(controller.RendererDidNavigate(forward_params, &details));
+ EXPECT_EQ(forward_params.url,
+ controller.GetVisibleEntry()->GetURL());
+
+ // Finally, navigate to an unrelated URL to make sure in_page is not sticky.
+ const GURL url3("http://bar");
+ params.page_id = 2;
+ params.url = url3;
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(2, controller.GetCurrentEntryIndex());
+}
+
+TEST_F(NavigationControllerTest, InPage_Replace) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Main page.
+ const GURL url1("http://foo");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // First navigation.
+ const GURL url2("http://foo#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0; // Same page_id
+ params.url = url2;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url2);
+
+ // This should NOT generate a new entry, nor prune the list.
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(1, controller.GetEntryCount());
+}
+
+// Tests for http://crbug.com/40395
+// Simulates this:
+// <script>
+// window.location.replace("#a");
+// window.location='http://foo3/';
+// </script>
+TEST_F(NavigationControllerTest, ClientRedirectAfterInPageNavigation) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Load an initial page.
+ {
+ const GURL url("http://foo/");
+ test_rvh()->SendNavigate(0, url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ }
+
+ // Navigate to a new page.
+ {
+ const GURL url("http://foo2/");
+ test_rvh()->SendNavigate(1, url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ }
+
+ // Navigate within the page.
+ {
+ const GURL url("http://foo2/#a");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1; // Same page_id
+ params.url = url;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.redirects.push_back(url);
+ params.should_update_history = true;
+ params.gesture = NavigationGestureUnknown;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+
+ // This should NOT generate a new entry, nor prune the list.
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_TRUE(details.is_in_page);
+ EXPECT_TRUE(details.did_replace_entry);
+ EXPECT_EQ(2, controller.GetEntryCount());
+ }
+
+ // Perform a client redirect to a new page.
+ {
+ const GURL url("http://foo3/");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 2; // New page_id
+ params.url = url;
+ params.transition = PAGE_TRANSITION_CLIENT_REDIRECT;
+ params.redirects.push_back(GURL("http://foo2/#a"));
+ params.redirects.push_back(url);
+ params.should_update_history = true;
+ params.gesture = NavigationGestureUnknown;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+
+ // This SHOULD generate a new entry.
+ LoadCommittedDetails details;
+ EXPECT_TRUE(controller.RendererDidNavigate(params, &details));
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_FALSE(details.is_in_page);
+ EXPECT_EQ(3, controller.GetEntryCount());
+ }
+
+ // Verify that BACK brings us back to http://foo2/.
+ {
+ const GURL url("http://foo2/");
+ controller.GoBack();
+ test_rvh()->SendNavigate(1, url);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_EQ(url, controller.GetVisibleEntry()->GetURL());
+ }
+}
+
+// NotificationObserver implementation used in verifying we've received the
+// NOTIFICATION_NAV_LIST_PRUNED method.
+class PrunedListener : public NotificationObserver {
+ public:
+ explicit PrunedListener(NavigationControllerImpl* controller)
+ : notification_count_(0) {
+ registrar_.Add(this, NOTIFICATION_NAV_LIST_PRUNED,
+ Source<NavigationController>(controller));
+ }
+
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE {
+ if (type == NOTIFICATION_NAV_LIST_PRUNED) {
+ notification_count_++;
+ details_ = *(Details<PrunedDetails>(details).ptr());
+ }
+ }
+
+ // Number of times NAV_LIST_PRUNED has been observed.
+ int notification_count_;
+
+ // Details from the last NAV_LIST_PRUNED.
+ PrunedDetails details_;
+
+ private:
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrunedListener);
+};
+
+// Tests that we limit the number of navigation entries created correctly.
+TEST_F(NavigationControllerTest, EnforceMaxNavigationCount) {
+ NavigationControllerImpl& controller = controller_impl();
+ size_t original_count = NavigationControllerImpl::max_entry_count();
+ const int kMaxEntryCount = 5;
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(kMaxEntryCount);
+
+ int url_index;
+ // Load up to the max count, all entries should be there.
+ for (url_index = 0; url_index < kMaxEntryCount; url_index++) {
+ GURL url(base::StringPrintf("http://www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ }
+
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+
+ // Created a PrunedListener to observe prune notifications.
+ PrunedListener listener(&controller);
+
+ // Navigate some more.
+ GURL url(base::StringPrintf("http://www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ url_index++;
+
+ // We should have got a pruned navigation.
+ EXPECT_EQ(1, listener.notification_count_);
+ EXPECT_TRUE(listener.details_.from_front);
+ EXPECT_EQ(1, listener.details_.count);
+
+ // We expect http://www.a.com/0 to be gone.
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(),
+ GURL("http:////www.a.com/1"));
+
+ // More navigations.
+ for (int i = 0; i < 3; i++) {
+ url = GURL(base::StringPrintf("http:////www.a.com/%d", url_index));
+ controller.LoadURL(
+ url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(url_index, url);
+ url_index++;
+ }
+ EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(),
+ GURL("http:////www.a.com/4"));
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(original_count);
+}
+
+// Tests that we can do a restore and navigate to the restored entries and
+// everything is updated properly. This can be tricky since there is no
+// SiteInstance for the entries created initially.
+TEST_F(NavigationControllerTest, RestoreNavigate) {
+ // Create a NavigationController with a restored set of tabs.
+ GURL url("http://foo");
+ std::vector<NavigationEntry*> entries;
+ NavigationEntry* entry = NavigationControllerImpl::CreateNavigationEntry(
+ url, Referrer(), PAGE_TRANSITION_RELOAD, false, std::string(),
+ browser_context());
+ entry->SetPageID(0);
+ entry->SetTitle(ASCIIToUTF16("Title"));
+ entry->SetPageState(PageState::CreateFromEncodedData("state"));
+ const base::Time timestamp = base::Time::Now();
+ entry->SetTimestamp(timestamp);
+ entries.push_back(entry);
+ scoped_ptr<WebContentsImpl> our_contents(static_cast<WebContentsImpl*>(
+ WebContents::Create(WebContents::CreateParams(browser_context()))));
+ NavigationControllerImpl& our_controller = our_contents->GetController();
+ our_controller.Restore(
+ 0,
+ NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ &entries);
+ ASSERT_EQ(0u, entries.size());
+
+ // Before navigating to the restored entry, it should have a restore_type
+ // and no SiteInstance.
+ ASSERT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_FALSE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // After navigating, we should have one entry, and it should be "pending".
+ // It should now have a SiteInstance and no restore_type.
+ our_controller.GoToIndex(0);
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(our_controller.GetEntryAtIndex(0),
+ our_controller.GetPendingEntry());
+ EXPECT_EQ(0, our_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry
+ (our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_TRUE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // Timestamp should remain the same before the navigation finishes.
+ EXPECT_EQ(timestamp, our_controller.GetEntryAtIndex(0)->GetTimestamp());
+
+ // Say we navigated to that entry.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+ LoadCommittedDetails details;
+ our_controller.RendererDidNavigate(params, &details);
+
+ // There should be no longer any pending entry and one committed one. This
+ // means that we were able to locate the entry, assign its site instance, and
+ // commit it properly.
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(0, our_controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(our_controller.GetPendingEntry());
+ EXPECT_EQ(url,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetLastCommittedEntry())->site_instance()->
+ GetSiteURL());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+
+ // Timestamp should have been updated.
+ EXPECT_GE(our_controller.GetEntryAtIndex(0)->GetTimestamp(), timestamp);
+}
+
+// Tests that we can still navigate to a restored entry after a different
+// navigation fails and clears the pending entry. http://crbug.com/90085
+TEST_F(NavigationControllerTest, RestoreNavigateAfterFailure) {
+ // Create a NavigationController with a restored set of tabs.
+ GURL url("http://foo");
+ std::vector<NavigationEntry*> entries;
+ NavigationEntry* entry = NavigationControllerImpl::CreateNavigationEntry(
+ url, Referrer(), PAGE_TRANSITION_RELOAD, false, std::string(),
+ browser_context());
+ entry->SetPageID(0);
+ entry->SetTitle(ASCIIToUTF16("Title"));
+ entry->SetPageState(PageState::CreateFromEncodedData("state"));
+ entries.push_back(entry);
+ scoped_ptr<WebContentsImpl> our_contents(static_cast<WebContentsImpl*>(
+ WebContents::Create(WebContents::CreateParams(browser_context()))));
+ NavigationControllerImpl& our_controller = our_contents->GetController();
+ our_controller.Restore(
+ 0, NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY, &entries);
+ ASSERT_EQ(0u, entries.size());
+
+ // Before navigating to the restored entry, it should have a restore_type
+ // and no SiteInstance.
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_FALSE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // After navigating, we should have one entry, and it should be "pending".
+ // It should now have a SiteInstance and no restore_type.
+ our_controller.GoToIndex(0);
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(our_controller.GetEntryAtIndex(0),
+ our_controller.GetPendingEntry());
+ EXPECT_EQ(0, our_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+ EXPECT_TRUE(NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->site_instance());
+
+ // This pending navigation may have caused a different navigation to fail,
+ // which causes the pending entry to be cleared.
+ TestRenderViewHost* rvh =
+ static_cast<TestRenderViewHost*>(our_contents->GetRenderViewHost());
+ ViewHostMsg_DidFailProvisionalLoadWithError_Params fail_load_params;
+ fail_load_params.frame_id = 1;
+ fail_load_params.is_main_frame = true;
+ fail_load_params.error_code = net::ERR_ABORTED;
+ fail_load_params.error_description = string16();
+ fail_load_params.url = url;
+ fail_load_params.showing_repost_interstitial = false;
+ rvh->OnMessageReceived(
+ ViewHostMsg_DidFailProvisionalLoadWithError(0, // routing_id
+ fail_load_params));
+
+ // Now the pending restored entry commits.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = url;
+ params.transition = PAGE_TRANSITION_LINK;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureUser;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url);
+ LoadCommittedDetails details;
+ our_controller.RendererDidNavigate(params, &details);
+
+ // There should be no pending entry and one committed one.
+ EXPECT_EQ(1, our_controller.GetEntryCount());
+ EXPECT_EQ(0, our_controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(our_controller.GetPendingEntry());
+ EXPECT_EQ(url,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetLastCommittedEntry())->site_instance()->
+ GetSiteURL());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE,
+ NavigationEntryImpl::FromNavigationEntry(
+ our_controller.GetEntryAtIndex(0))->restore_type());
+}
+
+// Make sure that the page type and stuff is correct after an interstitial.
+TEST_F(NavigationControllerTest, Interstitial) {
+ NavigationControllerImpl& controller = controller_impl();
+ // First navigate somewhere normal.
+ const GURL url1("http://foo");
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+
+ // Now navigate somewhere with an interstitial.
+ const GURL url2("http://bar");
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ set_page_type(PAGE_TYPE_INTERSTITIAL);
+
+ // At this point the interstitial will be displayed and the load will still
+ // be pending. If the user continues, the load will commit.
+ test_rvh()->SendNavigate(1, url2);
+
+ // The page should be a normal page again.
+ EXPECT_EQ(url2, controller.GetLastCommittedEntry()->GetURL());
+ EXPECT_EQ(PAGE_TYPE_NORMAL,
+ controller.GetLastCommittedEntry()->GetPageType());
+}
+
+TEST_F(NavigationControllerTest, RemoveEntry) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url4("http://foo/4");
+ const GURL url5("http://foo/5");
+ const GURL pending_url("http://foo/pending");
+ const GURL default_url("http://foo/default");
+
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url1);
+ controller.LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url2);
+ controller.LoadURL(
+ url3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(2, url3);
+ controller.LoadURL(
+ url4, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(3, url4);
+ controller.LoadURL(
+ url5, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(4, url5);
+
+ // Try to remove the last entry. Will fail because it is the current entry.
+ EXPECT_FALSE(controller.RemoveEntryAtIndex(controller.GetEntryCount() - 1));
+ EXPECT_EQ(5, controller.GetEntryCount());
+ EXPECT_EQ(4, controller.GetLastCommittedEntryIndex());
+
+ // Go back, but don't commit yet. Check that we can't delete the current
+ // and pending entries.
+ controller.GoBack();
+ EXPECT_FALSE(controller.RemoveEntryAtIndex(controller.GetEntryCount() - 1));
+ EXPECT_FALSE(controller.RemoveEntryAtIndex(controller.GetEntryCount() - 2));
+
+ // Now commit and delete the last entry.
+ test_rvh()->SendNavigate(3, url4);
+ EXPECT_TRUE(controller.RemoveEntryAtIndex(controller.GetEntryCount() - 1));
+ EXPECT_EQ(4, controller.GetEntryCount());
+ EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+
+ // Remove an entry which is not the last committed one.
+ EXPECT_TRUE(controller.RemoveEntryAtIndex(0));
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+
+ // Remove the 2 remaining entries.
+ controller.RemoveEntryAtIndex(1);
+ controller.RemoveEntryAtIndex(0);
+
+ // This should leave us with only the last committed entry.
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+}
+
+// Tests the transient entry, making sure it goes away with all navigations.
+TEST_F(NavigationControllerTest, TransientEntry) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url3_ref("http://foo/3#bar");
+ const GURL url4("http://foo/4");
+ const GURL transient_url("http://foo/transient");
+
+ controller.LoadURL(
+ url0, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url0);
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(1, url1);
+
+ notifications.Reset();
+
+ // Adding a transient with no pending entry.
+ NavigationEntryImpl* transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+
+ // We should not have received any notifications.
+ EXPECT_EQ(0U, notifications.size());
+
+ // Check our state.
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 1);
+ EXPECT_EQ(controller.GetPendingEntryIndex(), -1);
+ EXPECT_TRUE(controller.GetLastCommittedEntry());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(contents()->GetMaxPageID(), 1);
+
+ // Navigate.
+ controller.LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(2, url2);
+
+ // We should have navigated, transient entry should be gone.
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 3);
+
+ // Add a transient again, then navigate with no pending entry this time.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(3, url3);
+ // Transient entry should be gone.
+ EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 4);
+
+ // Initiate a navigation, add a transient then commit navigation.
+ controller.LoadURL(
+ url4, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(4, url4);
+ EXPECT_EQ(url4, controller.GetVisibleEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+
+ // Add a transient and go back. This should simply remove the transient.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ EXPECT_TRUE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ controller.GoBack();
+ // Transient entry should be gone.
+ EXPECT_EQ(url4, controller.GetVisibleEntry()->GetURL());
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+ test_rvh()->SendNavigate(3, url3);
+
+ // Add a transient and go to an entry before the current one.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ controller.GoToIndex(1);
+ // The navigation should have been initiated, transient entry should be gone.
+ EXPECT_FALSE(controller.GetTransientEntry());
+ EXPECT_EQ(url1, controller.GetPendingEntry()->GetURL());
+ // Visible entry does not update for history navigations until commit.
+ EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+
+ // Add a transient and go to an entry after the current one.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ controller.GoToIndex(3);
+ // The navigation should have been initiated, transient entry should be gone.
+ // Because of the transient entry that is removed, going to index 3 makes us
+ // land on url2 (which is visible after the commit).
+ EXPECT_EQ(url2, controller.GetPendingEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(2, url2);
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+
+ // Add a transient and go forward.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ EXPECT_TRUE(controller.CanGoForward());
+ controller.GoForward();
+ // We should have navigated, transient entry should be gone.
+ EXPECT_FALSE(controller.GetTransientEntry());
+ EXPECT_EQ(url3, controller.GetPendingEntry()->GetURL());
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(3, url3);
+ EXPECT_EQ(url3, controller.GetVisibleEntry()->GetURL());
+
+ // Add a transient and do an in-page navigation, replacing the current entry.
+ transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(3, url3_ref);
+ // Transient entry should be gone.
+ EXPECT_FALSE(controller.GetTransientEntry());
+ EXPECT_EQ(url3_ref, controller.GetVisibleEntry()->GetURL());
+
+ // Ensure the URLs are correct.
+ EXPECT_EQ(controller.GetEntryCount(), 5);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0);
+ EXPECT_EQ(controller.GetEntryAtIndex(1)->GetURL(), url1);
+ EXPECT_EQ(controller.GetEntryAtIndex(2)->GetURL(), url2);
+ EXPECT_EQ(controller.GetEntryAtIndex(3)->GetURL(), url3_ref);
+ EXPECT_EQ(controller.GetEntryAtIndex(4)->GetURL(), url4);
+}
+
+// Test that Reload initiates a new navigation to a transient entry's URL.
+TEST_F(NavigationControllerTest, ReloadTransient) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+ const GURL transient_url("http://foo/transient");
+
+ // Load |url0|, and start a pending navigation to |url1|.
+ controller.LoadURL(
+ url0, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ test_rvh()->SendNavigate(0, url0);
+ controller.LoadURL(
+ url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // A transient entry is added, interrupting the navigation.
+ NavigationEntryImpl* transient_entry = new NavigationEntryImpl;
+ transient_entry->SetURL(transient_url);
+ controller.SetTransientEntry(transient_entry);
+ EXPECT_TRUE(controller.GetTransientEntry());
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+
+ // The page is reloaded, which should remove the pending entry for |url1| and
+ // the transient entry for |transient_url|, and start a navigation to
+ // |transient_url|.
+ controller.Reload(true);
+ EXPECT_FALSE(controller.GetTransientEntry());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(transient_url, controller.GetVisibleEntry()->GetURL());
+ ASSERT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0);
+
+ // Load of |transient_url| completes.
+ test_rvh()->SendNavigate(1, transient_url);
+ ASSERT_EQ(controller.GetEntryCount(), 2);
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url0);
+ EXPECT_EQ(controller.GetEntryAtIndex(1)->GetURL(), transient_url);
+}
+
+// Ensure that renderer initiated pending entries get replaced, so that we
+// don't show a stale virtual URL when a navigation commits.
+// See http://crbug.com/266922.
+TEST_F(NavigationControllerTest, RendererInitiatedPendingEntries) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ const GURL url1("nonexistent:12121");
+ const GURL url1_fixed("http://nonexistent:12121/");
+ const GURL url2("http://foo");
+
+ // We create pending entries for renderer-initiated navigations so that we
+ // can show them in new tabs when it is safe.
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, url1);
+
+ // Simulate what happens if a BrowserURLHandler rewrites the URL, causing
+ // the virtual URL to differ from the URL.
+ controller.GetPendingEntry()->SetURL(url1_fixed);
+ controller.GetPendingEntry()->SetVirtualURL(url1);
+
+ EXPECT_EQ(url1_fixed, controller.GetPendingEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetPendingEntry()->GetVirtualURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+
+ // If the user clicks another link, we should replace the pending entry.
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, url2);
+ EXPECT_EQ(url2, controller.GetPendingEntry()->GetURL());
+ EXPECT_EQ(url2, controller.GetPendingEntry()->GetVirtualURL());
+
+ // Once it commits, the URL and virtual URL should reflect the actual page.
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(url2, controller.GetLastCommittedEntry()->GetURL());
+ EXPECT_EQ(url2, controller.GetLastCommittedEntry()->GetVirtualURL());
+
+ // We should not replace the pending entry for an error URL.
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, url1);
+ EXPECT_EQ(url1, controller.GetPendingEntry()->GetURL());
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, GURL(kUnreachableWebDataURL));
+ EXPECT_EQ(url1, controller.GetPendingEntry()->GetURL());
+
+ // We should remember if the pending entry will replace the current one.
+ // http://crbug.com/308444.
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, url1);
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ set_should_replace_entry(true);
+ contents()->DidStartProvisionalLoadForFrame(
+ test_rvh(), 1, -1, true, url2);
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ should_replace_entry());
+ test_rvh()->SendNavigate(0, url2);
+ EXPECT_EQ(url2, controller.GetLastCommittedEntry()->GetURL());
+}
+
+// Tests that the URLs for renderer-initiated navigations are not displayed to
+// the user until the navigation commits, to prevent URL spoof attacks.
+// See http://crbug.com/99016.
+TEST_F(NavigationControllerTest, DontShowRendererURLUntilCommit) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+
+ // For typed navigations (browser-initiated), both pending and visible entries
+ // should update before commit.
+ controller.LoadURL(url0, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_EQ(url0, controller.GetPendingEntry()->GetURL());
+ EXPECT_EQ(url0, controller.GetVisibleEntry()->GetURL());
+ test_rvh()->SendNavigate(0, url0);
+
+ // For link clicks (renderer-initiated navigations), the pending entry should
+ // update before commit but the visible should not.
+ NavigationController::LoadURLParams load_url_params(url1);
+ load_url_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url_params);
+ EXPECT_EQ(url0, controller.GetVisibleEntry()->GetURL());
+ EXPECT_EQ(url1, controller.GetPendingEntry()->GetURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+
+ // After commit, both visible should be updated, there should be no pending
+ // entry, and we should no longer treat the entry as renderer-initiated.
+ test_rvh()->SendNavigate(1, url1);
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_FALSE(
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetLastCommittedEntry())->is_renderer_initiated());
+
+ notifications.Reset();
+}
+
+// Tests that the URLs for renderer-initiated navigations in new tabs are
+// displayed to the user before commit, as long as the initial about:blank
+// page has not been modified. If so, we must revert to showing about:blank.
+// See http://crbug.com/9682.
+TEST_F(NavigationControllerTest, ShowRendererURLInNewTabUntilModified) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url("http://foo");
+
+ // For renderer-initiated navigations in new tabs (with no committed entries),
+ // we show the pending entry's URL as long as the about:blank page is not
+ // modified.
+ NavigationController::LoadURLParams load_url_params(url);
+ load_url_params.transition_type = PAGE_TRANSITION_LINK;
+ load_url_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url_params);
+ EXPECT_EQ(url, controller.GetVisibleEntry()->GetURL());
+ EXPECT_EQ(url, controller.GetPendingEntry()->GetURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+ EXPECT_TRUE(controller.IsInitialNavigation());
+ EXPECT_FALSE(test_rvh()->has_accessed_initial_document());
+
+ // There should be no title yet.
+ EXPECT_TRUE(contents()->GetTitle().empty());
+
+ // If something else modifies the contents of the about:blank page, then
+ // we must revert to showing about:blank to avoid a URL spoof.
+ test_rvh()->OnMessageReceived(
+ ViewHostMsg_DidAccessInitialDocument(0));
+ EXPECT_TRUE(test_rvh()->has_accessed_initial_document());
+ EXPECT_FALSE(controller.GetVisibleEntry());
+ EXPECT_EQ(url, controller.GetPendingEntry()->GetURL());
+
+ notifications.Reset();
+}
+
+TEST_F(NavigationControllerTest, DontShowRendererURLInNewTabAfterCommit) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ const GURL url1("http://foo/eh");
+ const GURL url2("http://foo/bee");
+
+ // For renderer-initiated navigations in new tabs (with no committed entries),
+ // we show the pending entry's URL as long as the about:blank page is not
+ // modified.
+ NavigationController::LoadURLParams load_url_params(url1);
+ load_url_params.transition_type = PAGE_TRANSITION_LINK;
+ load_url_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url_params);
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+ EXPECT_TRUE(
+ NavigationEntryImpl::FromNavigationEntry(controller.GetPendingEntry())->
+ is_renderer_initiated());
+ EXPECT_TRUE(controller.IsInitialNavigation());
+ EXPECT_FALSE(test_rvh()->has_accessed_initial_document());
+
+ // Simulate a commit and then starting a new pending navigation.
+ test_rvh()->SendNavigate(0, url1);
+ NavigationController::LoadURLParams load_url2_params(url2);
+ load_url2_params.transition_type = PAGE_TRANSITION_LINK;
+ load_url2_params.is_renderer_initiated = true;
+ controller.LoadURLWithParams(load_url2_params);
+
+ // We should not consider this an initial navigation, and thus should
+ // not show the pending URL.
+ EXPECT_FALSE(test_rvh()->has_accessed_initial_document());
+ EXPECT_FALSE(controller.IsInitialNavigation());
+ EXPECT_TRUE(controller.GetVisibleEntry());
+ EXPECT_EQ(url1, controller.GetVisibleEntry()->GetURL());
+
+ notifications.Reset();
+}
+
+// Tests that IsInPageNavigation returns appropriate results. Prevents
+// regression for bug 1126349.
+TEST_F(NavigationControllerTest, IsInPageNavigation) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Navigate to URL with no refs.
+ const GURL url("http://www.google.com/home.html");
+ test_rvh()->SendNavigate(0, url);
+
+ // Reloading the page is not an in-page navigation.
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url));
+ const GURL other_url("http://www.google.com/add.html");
+ EXPECT_FALSE(controller.IsURLInPageNavigation(other_url));
+ const GURL url_with_ref("http://www.google.com/home.html#my_ref");
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url_with_ref));
+
+ // Navigate to URL with refs.
+ test_rvh()->SendNavigate(1, url_with_ref);
+
+ // Reloading the page is not an in-page navigation.
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url_with_ref));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(other_url));
+ const GURL other_url_with_ref("http://www.google.com/home.html#my_other_ref");
+ EXPECT_TRUE(controller.IsURLInPageNavigation(other_url_with_ref));
+
+ // Going to the same url again will be considered in-page
+ // if the renderer says it is even if the navigation type isn't IN_PAGE.
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url_with_ref, true,
+ NAVIGATION_TYPE_UNKNOWN));
+
+ // Going back to the non ref url will be considered in-page if the navigation
+ // type is IN_PAGE.
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url, true,
+ NAVIGATION_TYPE_IN_PAGE));
+}
+
+// Some pages can have subframes with the same base URL (minus the reference) as
+// the main page. Even though this is hard, it can happen, and we don't want
+// these subframe navigations to affect the toplevel document. They should
+// instead be ignored. http://crbug.com/5585
+TEST_F(NavigationControllerTest, SameSubframe) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Navigate the main frame.
+ const GURL url("http://www.google.com/");
+ test_rvh()->SendNavigate(0, url);
+
+ // We should be at the first navigation entry.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+
+ // Navigate a subframe that would normally count as in-page.
+ const GURL subframe("http://www.google.com/#");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 0;
+ params.url = subframe;
+ params.transition = PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(subframe);
+ LoadCommittedDetails details;
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+
+ // Nothing should have changed.
+ EXPECT_EQ(controller.GetEntryCount(), 1);
+ EXPECT_EQ(controller.GetLastCommittedEntryIndex(), 0);
+}
+
+// Make sure that on cloning a WebContentsImpl and going back needs_reload is
+// false.
+TEST_F(NavigationControllerTest, CloneAndGoBack) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const string16 title(ASCIIToUTF16("Title"));
+
+ NavigateAndCommit(url1);
+ controller.GetVisibleEntry()->SetTitle(title);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<WebContents> clone(controller.GetWebContents()->Clone());
+
+ ASSERT_EQ(2, clone->GetController().GetEntryCount());
+ EXPECT_TRUE(clone->GetController().NeedsReload());
+ clone->GetController().GoBack();
+ // Navigating back should have triggered needs_reload_ to go false.
+ EXPECT_FALSE(clone->GetController().NeedsReload());
+
+ // Ensure that the pending URL and its title are visible.
+ EXPECT_EQ(url1, clone->GetController().GetVisibleEntry()->GetURL());
+ EXPECT_EQ(title, clone->GetTitle());
+}
+
+// Make sure that reloading a cloned tab doesn't change its pending entry index.
+// See http://crbug.com/234491.
+TEST_F(NavigationControllerTest, CloneAndReload) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const string16 title(ASCIIToUTF16("Title"));
+
+ NavigateAndCommit(url1);
+ controller.GetVisibleEntry()->SetTitle(title);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<WebContents> clone(controller.GetWebContents()->Clone());
+ clone->GetController().LoadIfNecessary();
+
+ ASSERT_EQ(2, clone->GetController().GetEntryCount());
+ EXPECT_EQ(1, clone->GetController().GetPendingEntryIndex());
+
+ clone->GetController().Reload(true);
+ EXPECT_EQ(1, clone->GetController().GetPendingEntryIndex());
+}
+
+// Make sure that cloning a WebContentsImpl doesn't copy interstitials.
+TEST_F(NavigationControllerTest, CloneOmitsInterstitials) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // Add an interstitial entry. Should be deleted with controller.
+ NavigationEntryImpl* interstitial_entry = new NavigationEntryImpl();
+ interstitial_entry->set_page_type(PAGE_TYPE_INTERSTITIAL);
+ controller.SetTransientEntry(interstitial_entry);
+
+ scoped_ptr<WebContents> clone(controller.GetWebContents()->Clone());
+
+ ASSERT_EQ(2, clone->GetController().GetEntryCount());
+}
+
+// Test requesting and triggering a lazy reload.
+TEST_F(NavigationControllerTest, LazyReload) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url("http://foo");
+ NavigateAndCommit(url);
+ ASSERT_FALSE(controller.NeedsReload());
+
+ // Request a reload to happen when the controller becomes active (e.g. after
+ // the renderer gets killed in background on Android).
+ controller.SetNeedsReload();
+ ASSERT_TRUE(controller.NeedsReload());
+
+ // Set the controller as active, triggering the requested reload.
+ controller.SetActive(true);
+ ASSERT_FALSE(controller.NeedsReload());
+}
+
+// Tests a subframe navigation while a toplevel navigation is pending.
+// http://crbug.com/43967
+TEST_F(NavigationControllerTest, SubframeWhilePending) {
+ NavigationControllerImpl& controller = controller_impl();
+ // Load the first page.
+ const GURL url1("http://foo/");
+ NavigateAndCommit(url1);
+
+ // Now start a pending load to a totally different page, but don't commit it.
+ const GURL url2("http://bar/");
+ controller.LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+
+ // Send a subframe update from the first page, as if one had just
+ // automatically loaded. Auto subframes don't increment the page ID.
+ const GURL url1_sub("http://foo/subframe");
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = controller.GetLastCommittedEntry()->GetPageID();
+ params.url = url1_sub;
+ params.transition = PAGE_TRANSITION_AUTO_SUBFRAME;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(url1_sub);
+ LoadCommittedDetails details;
+
+ // This should return false meaning that nothing was actually updated.
+ EXPECT_FALSE(controller.RendererDidNavigate(params, &details));
+
+ // The notification should have updated the last committed one, and not
+ // the pending load.
+ EXPECT_EQ(url1, controller.GetLastCommittedEntry()->GetURL());
+
+ // The active entry should be unchanged by the subframe load.
+ EXPECT_EQ(url2, controller.GetVisibleEntry()->GetURL());
+}
+
+// Test CopyStateFrom with 2 urls, the first selected and nothing in the target.
+TEST_F(NavigationControllerTest, CopyStateFrom) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_controller.CopyStateFrom(controller);
+
+ // other_controller should now contain 2 urls.
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ // We should be looking at the first one.
+ ASSERT_EQ(0, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ // This is a different site than url1, so the IDs start again at 0.
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(1)->GetPageID());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+
+ // Ensure the SessionStorageNamespaceMaps are the same size and have
+ // the same partitons loaded.
+ //
+ // TODO(ajwong): We should load a url from a different partition earlier
+ // to make sure this map has more than one entry.
+ const SessionStorageNamespaceMap& session_storage_namespace_map =
+ controller.GetSessionStorageNamespaceMap();
+ const SessionStorageNamespaceMap& other_session_storage_namespace_map =
+ other_controller.GetSessionStorageNamespaceMap();
+ EXPECT_EQ(session_storage_namespace_map.size(),
+ other_session_storage_namespace_map.size());
+ for (SessionStorageNamespaceMap::const_iterator it =
+ session_storage_namespace_map.begin();
+ it != session_storage_namespace_map.end();
+ ++it) {
+ SessionStorageNamespaceMap::const_iterator other =
+ other_session_storage_namespace_map.find(it->first);
+ EXPECT_TRUE(other != other_session_storage_namespace_map.end());
+ }
+}
+
+// Tests CopyStateFromAndPrune with 2 urls in source, 1 in dest.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // First two entries should have the same SiteInstance.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(0));
+ SiteInstance* instance2 =
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(1));
+ EXPECT_EQ(instance1, instance2);
+ EXPECT_EQ(0, controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(1, controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(1, contents()->GetMaxPageIDForSiteInstance(instance1));
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain the 3 urls: url1, url2 and url3.
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(1, other_controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(2)->GetPageID());
+
+ // A new SiteInstance should be used for the new tab.
+ SiteInstance* instance3 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_NE(instance3, instance1);
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ EXPECT_EQ(1, other_contents->GetMaxPageIDForSiteInstance(instance1));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance3));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the first selected and 1 entry in
+// the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune2) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 1,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain: url1, url3
+
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ ASSERT_EQ(1, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(1)->GetPageID());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(1));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the last selected and 2 entries in
+// the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPrune3) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->NavigateAndCommit(url4);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(1)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain: url1, url2, url4
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url4, other_controller.GetEntryAtIndex(2)->GetURL());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, 2 entries in the target, with
+// not the last entry selected in the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneNotLast) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->NavigateAndCommit(url4);
+ other_controller.GoBack();
+ other_contents->CommitPendingNavigation();
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain: url1, url2, url3
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->GetURL());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, the first selected and 1 entry plus
+// a pending entry in the target.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneTargetPending) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_controller.LoadURL(
+ url4, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 1,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain url1, url3, and a pending entry
+ // for url4.
+
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ EXPECT_EQ(1, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
+
+ // And there should be a pending entry for url4.
+ ASSERT_TRUE(other_controller.GetPendingEntry());
+ EXPECT_EQ(url4, other_controller.GetPendingEntry()->GetURL());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 1 url in the source, 1 entry and a pending
+// client redirect entry (with the same page ID) in the target. This used to
+// crash because the last committed entry would be pruned but max_page_id
+// remembered the page ID (http://crbug.com/234809).
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneTargetPending2) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2a("http://foo2/a");
+ const GURL url2b("http://foo2/b");
+
+ NavigateAndCommit(url1);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url2a);
+ // Simulate a client redirect, which has the same page ID as entry 2a.
+ other_controller.LoadURL(
+ url2b, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ other_controller.GetPendingEntry()->SetPageID(
+ other_controller.GetLastCommittedEntry()->GetPageID());
+
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 1,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain url1, url2a, and a pending entry
+ // for url2b.
+
+ ASSERT_EQ(2, other_controller.GetEntryCount());
+ EXPECT_EQ(1, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2a, other_controller.GetEntryAtIndex(1)->GetURL());
+
+ // And there should be a pending entry for url4.
+ ASSERT_TRUE(other_controller.GetPendingEntry());
+ EXPECT_EQ(url2b, other_controller.GetPendingEntry()->GetURL());
+
+ // Let the pending entry commit.
+ other_contents->CommitPendingNavigation();
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(1));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Test CopyStateFromAndPrune with 2 urls, a back navigation pending in the
+// source, and 1 entry in the target. The back pending entry should be ignored.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneSourcePending) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ controller.GoBack();
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url3);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // other_controller should now contain: url1, url2, url3
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->GetURL());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(2)->GetPageID());
+
+ // The max page ID map should be copied over and updated with the max page ID
+ // from the current tab.
+ SiteInstance* instance1 =
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(2));
+ EXPECT_EQ(0, other_contents->GetMaxPageIDForSiteInstance(instance1));
+}
+
+// Tests CopyStateFromAndPrune with 3 urls in source, 1 in dest,
+// when the max entry count is 3. We should prune one entry.
+TEST_F(NavigationControllerTest, CopyStateFromAndPruneMaxEntries) {
+ NavigationControllerImpl& controller = controller_impl();
+ size_t original_count = NavigationControllerImpl::max_entry_count();
+ const int kMaxEntryCount = 3;
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(kMaxEntryCount);
+
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+ const GURL url4("http://foo/4");
+
+ // Create a PrunedListener to observe prune notifications.
+ PrunedListener listener(&controller);
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+
+ scoped_ptr<TestWebContents> other_contents(
+ static_cast<TestWebContents*>(CreateTestWebContents()));
+ NavigationControllerImpl& other_controller = other_contents->GetController();
+ other_contents->NavigateAndCommit(url4);
+ other_contents->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(other_controller.GetEntryAtIndex(0)), 2,
+ other_controller.GetEntryAtIndex(0)->GetPageID());
+ other_controller.CopyStateFromAndPrune(&controller);
+
+ // We should have received a pruned notification.
+ EXPECT_EQ(1, listener.notification_count_);
+ EXPECT_TRUE(listener.details_.from_front);
+ EXPECT_EQ(1, listener.details_.count);
+
+ // other_controller should now contain only 3 urls: url2, url3 and url4.
+
+ ASSERT_EQ(3, other_controller.GetEntryCount());
+
+ ASSERT_EQ(2, other_controller.GetCurrentEntryIndex());
+
+ EXPECT_EQ(url2, other_controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
+ EXPECT_EQ(url4, other_controller.GetEntryAtIndex(2)->GetURL());
+ EXPECT_EQ(1, other_controller.GetEntryAtIndex(0)->GetPageID());
+ EXPECT_EQ(2, other_controller.GetEntryAtIndex(1)->GetPageID());
+ EXPECT_EQ(0, other_controller.GetEntryAtIndex(2)->GetPageID());
+
+ NavigationControllerImpl::set_max_entry_count_for_testing(original_count);
+}
+
+// Tests that navigations initiated from the page (with the history object)
+// work as expected without navigation entries.
+TEST_F(NavigationControllerTest, HistoryNavigate) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ // Simulate the page calling history.back(), it should not create a pending
+ // entry.
+ contents()->OnGoToEntryAtOffset(-1);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ // The actual cross-navigation is suspended until the current RVH tells us
+ // it unloaded, simulate that.
+ contents()->ProceedWithCrossSiteNavigation();
+ // Also make sure we told the page to navigate.
+ const IPC::Message* message =
+ process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ ASSERT_TRUE(message != NULL);
+ Tuple1<ViewMsg_Navigate_Params> nav_params;
+ ViewMsg_Navigate::Read(message, &nav_params);
+ EXPECT_EQ(url1, nav_params.a.url);
+ process()->sink().ClearMessages();
+
+ // Now test history.forward()
+ contents()->OnGoToEntryAtOffset(1);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ // The actual cross-navigation is suspended until the current RVH tells us
+ // it unloaded, simulate that.
+ contents()->ProceedWithCrossSiteNavigation();
+ message = process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ ASSERT_TRUE(message != NULL);
+ ViewMsg_Navigate::Read(message, &nav_params);
+ EXPECT_EQ(url3, nav_params.a.url);
+ process()->sink().ClearMessages();
+
+ // Make sure an extravagant history.go() doesn't break.
+ contents()->OnGoToEntryAtOffset(120); // Out of bounds.
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ message = process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID);
+ EXPECT_TRUE(message == NULL);
+}
+
+// Test call to PruneAllButVisible for the only entry.
+TEST_F(NavigationControllerTest, PruneAllButVisibleForSingle) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo1");
+ NavigateAndCommit(url1);
+
+ contents()->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(0)), 0,
+ controller.GetEntryAtIndex(0)->GetPageID());
+
+ controller.PruneAllButVisible();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url1);
+}
+
+// Test call to PruneAllButVisible for first entry.
+TEST_F(NavigationControllerTest, PruneAllButVisibleForFirst) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ contents()->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(0)), 0,
+ controller.GetEntryAtIndex(0)->GetPageID());
+
+ controller.PruneAllButVisible();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url1);
+}
+
+// Test call to PruneAllButVisible for intermediate entry.
+TEST_F(NavigationControllerTest, PruneAllButVisibleForIntermediate) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ contents()->ExpectSetHistoryLengthAndPrune(
+ GetSiteInstanceFromEntry(controller.GetEntryAtIndex(1)), 0,
+ controller.GetEntryAtIndex(1)->GetPageID());
+
+ controller.PruneAllButVisible();
+
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_EQ(controller.GetEntryAtIndex(0)->GetURL(), url2);
+}
+
+// Test call to PruneAllButVisible for a pending entry that is not yet in the
+// list of entries.
+TEST_F(NavigationControllerTest, PruneAllButVisibleForPendingNotInList) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url1("http://foo/1");
+ const GURL url2("http://foo/2");
+ const GURL url3("http://foo/3");
+
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+
+ // Create a pending entry that is not in the entry list.
+ controller.LoadURL(
+ url3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(2, controller.GetEntryCount());
+
+ contents()->ExpectSetHistoryLengthAndPrune(
+ NULL, 0, controller.GetPendingEntry()->GetPageID());
+ controller.PruneAllButVisible();
+
+ // We should only have the last committed and pending entries at this point,
+ // and the pending entry should still not be in the entry list.
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_TRUE(controller.GetPendingEntry());
+ EXPECT_EQ(1, controller.GetEntryCount());
+
+ // Try to commit the pending entry.
+ test_rvh()->SendNavigate(2, url3);
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.GetPendingEntry());
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
+}
+
+// Test to ensure that when we do a history navigation back to the current
+// committed page (e.g., going forward to a slow-loading page, then pressing
+// the back button), we just stop the navigation to prevent the throbber from
+// running continuously. Otherwise, the RenderViewHost forces the throbber to
+// start, but WebKit essentially ignores the navigation and never sends a
+// message to stop the throbber.
+TEST_F(NavigationControllerTest, StopOnHistoryNavigationToCurrentPage) {
+ NavigationControllerImpl& controller = controller_impl();
+ const GURL url0("http://foo/0");
+ const GURL url1("http://foo/1");
+
+ NavigateAndCommit(url0);
+ NavigateAndCommit(url1);
+
+ // Go back to the original page, then forward to the slow page, then back
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+
+ controller.GoForward();
+ EXPECT_EQ(1, controller.GetPendingEntryIndex());
+
+ controller.GoBack();
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+}
+
+TEST_F(NavigationControllerTest, IsInitialNavigation) {
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ // Initial state.
+ EXPECT_TRUE(controller.IsInitialNavigation());
+
+ // After commit, it stays false.
+ const GURL url1("http://foo1");
+ test_rvh()->SendNavigate(0, url1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ EXPECT_FALSE(controller.IsInitialNavigation());
+
+ // After starting a new navigation, it stays false.
+ const GURL url2("http://foo2");
+ controller.LoadURL(
+ url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+}
+
+// Check that the favicon is not reused across a client redirect.
+// (crbug.com/28515)
+TEST_F(NavigationControllerTest, ClearFaviconOnRedirect) {
+ const GURL kPageWithFavicon("http://withfavicon.html");
+ const GURL kPageWithoutFavicon("http://withoutfavicon.html");
+ const GURL kIconURL("http://withfavicon.ico");
+ const gfx::Image kDefaultFavicon = FaviconStatus().image;
+
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ test_rvh()->SendNavigate(0, kPageWithFavicon);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ NavigationEntry* entry = controller.GetLastCommittedEntry();
+ EXPECT_TRUE(entry);
+ EXPECT_EQ(kPageWithFavicon, entry->GetURL());
+
+ // Simulate Chromium having set the favicon for |kPageWithFavicon|.
+ content::FaviconStatus& favicon_status = entry->GetFavicon();
+ favicon_status.image = CreateImage(SK_ColorWHITE);
+ favicon_status.url = kIconURL;
+ favicon_status.valid = true;
+ EXPECT_FALSE(DoImagesMatch(kDefaultFavicon, entry->GetFavicon().image));
+
+ test_rvh()->SendNavigateWithTransition(
+ 0, // same page ID.
+ kPageWithoutFavicon,
+ PAGE_TRANSITION_CLIENT_REDIRECT);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ entry = controller.GetLastCommittedEntry();
+ EXPECT_TRUE(entry);
+ EXPECT_EQ(kPageWithoutFavicon, entry->GetURL());
+
+ EXPECT_TRUE(DoImagesMatch(kDefaultFavicon, entry->GetFavicon().image));
+}
+
+// Check that the favicon is not cleared for NavigationEntries which were
+// previously navigated to.
+TEST_F(NavigationControllerTest, BackNavigationDoesNotClearFavicon) {
+ const GURL kUrl1("http://www.a.com/1");
+ const GURL kUrl2("http://www.a.com/2");
+ const GURL kIconURL("http://www.a.com/1/favicon.ico");
+
+ NavigationControllerImpl& controller = controller_impl();
+ TestNotificationTracker notifications;
+ RegisterForAllNavNotifications(&notifications, &controller);
+
+ test_rvh()->SendNavigate(0, kUrl1);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Simulate Chromium having set the favicon for |kUrl1|.
+ gfx::Image favicon_image = CreateImage(SK_ColorWHITE);
+ content::NavigationEntry* entry = controller.GetLastCommittedEntry();
+ EXPECT_TRUE(entry);
+ content::FaviconStatus& favicon_status = entry->GetFavicon();
+ favicon_status.image = favicon_image;
+ favicon_status.url = kIconURL;
+ favicon_status.valid = true;
+
+ // Navigate to another page and go back to the original page.
+ test_rvh()->SendNavigate(1, kUrl2);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+ test_rvh()->SendNavigateWithTransition(
+ 0,
+ kUrl1,
+ PAGE_TRANSITION_FORWARD_BACK);
+ EXPECT_EQ(1U, navigation_entry_committed_counter_);
+ navigation_entry_committed_counter_ = 0;
+
+ // Verify that the favicon for the page at |kUrl1| was not cleared.
+ entry = controller.GetEntryAtIndex(0);
+ EXPECT_TRUE(entry);
+ EXPECT_EQ(kUrl1, entry->GetURL());
+ EXPECT_TRUE(DoImagesMatch(favicon_image, entry->GetFavicon().image));
+}
+
+// The test crashes on android: http://crbug.com/170449
+#if defined(OS_ANDROID)
+#define MAYBE_PurgeScreenshot DISABLED_PurgeScreenshot
+#else
+#define MAYBE_PurgeScreenshot PurgeScreenshot
+#endif
+// Tests that screenshot are purged correctly.
+TEST_F(NavigationControllerTest, MAYBE_PurgeScreenshot) {
+ NavigationControllerImpl& controller = controller_impl();
+
+ NavigationEntryImpl* entry;
+
+ // Navigate enough times to make sure that some screenshots are purged.
+ for (int i = 0; i < 12; ++i) {
+ const GURL url(base::StringPrintf("http://foo%d/", i));
+ NavigateAndCommit(url);
+ EXPECT_EQ(i, controller.GetCurrentEntryIndex());
+ }
+
+ MockScreenshotManager* screenshot_manager =
+ new MockScreenshotManager(&controller);
+ controller.SetScreenshotManager(screenshot_manager);
+ for (int i = 0; i < controller.GetEntryCount(); ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ screenshot_manager->TakeScreenshotFor(entry);
+ EXPECT_TRUE(entry->screenshot().get());
+ }
+
+ NavigateAndCommit(GURL("https://foo/"));
+ EXPECT_EQ(13, controller.GetEntryCount());
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(11));
+ screenshot_manager->TakeScreenshotFor(entry);
+
+ for (int i = 0; i < 2; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_FALSE(entry->screenshot().get()) << "Screenshot " << i
+ << " not purged";
+ }
+
+ for (int i = 2; i < controller.GetEntryCount() - 1; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_TRUE(entry->screenshot().get()) << "Screenshot not found for " << i;
+ }
+
+ // Navigate to index 5 and then try to assign screenshot to all entries.
+ controller.GoToIndex(5);
+ contents()->CommitPendingNavigation();
+ EXPECT_EQ(5, controller.GetCurrentEntryIndex());
+ for (int i = 0; i < controller.GetEntryCount() - 1; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ screenshot_manager->TakeScreenshotFor(entry);
+ }
+
+ for (int i = 10; i <= 12; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_FALSE(entry->screenshot().get()) << "Screenshot " << i
+ << " not purged";
+ screenshot_manager->TakeScreenshotFor(entry);
+ }
+
+ // Navigate to index 7 and assign screenshot to all entries.
+ controller.GoToIndex(7);
+ contents()->CommitPendingNavigation();
+ EXPECT_EQ(7, controller.GetCurrentEntryIndex());
+ for (int i = 0; i < controller.GetEntryCount() - 1; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ screenshot_manager->TakeScreenshotFor(entry);
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_FALSE(entry->screenshot().get()) << "Screenshot " << i
+ << " not purged";
+ }
+
+ // Clear all screenshots.
+ EXPECT_EQ(13, controller.GetEntryCount());
+ EXPECT_EQ(10, screenshot_manager->GetScreenshotCount());
+ controller.ClearAllScreenshots();
+ EXPECT_EQ(0, screenshot_manager->GetScreenshotCount());
+ for (int i = 0; i < controller.GetEntryCount(); ++i) {
+ entry = NavigationEntryImpl::FromNavigationEntry(
+ controller.GetEntryAtIndex(i));
+ EXPECT_FALSE(entry->screenshot().get()) << "Screenshot " << i
+ << " not cleared";
+ }
+}
+
+// Test that the navigation controller clears its session history when a
+// navigation commits with the clear history list flag set.
+TEST_F(NavigationControllerTest, ClearHistoryList) {
+ const GURL url1("http://foo1");
+ const GURL url2("http://foo2");
+ const GURL url3("http://foo3");
+ const GURL url4("http://foo4");
+
+ NavigationControllerImpl& controller = controller_impl();
+
+ // Create a session history with three entries, second entry is active.
+ NavigateAndCommit(url1);
+ NavigateAndCommit(url2);
+ NavigateAndCommit(url3);
+ controller.GoBack();
+ contents()->CommitPendingNavigation();
+ EXPECT_EQ(3, controller.GetEntryCount());
+ EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+
+ // Create a new pending navigation, and indicate that the session history
+ // should be cleared.
+ NavigationController::LoadURLParams params(url4);
+ params.should_clear_history_list = true;
+ controller.LoadURLWithParams(params);
+
+ // Verify that the pending entry correctly indicates that the session history
+ // should be cleared.
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(
+ controller.GetPendingEntry());
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->should_clear_history_list());
+
+ // Assume that the RV correctly cleared its history and commit the navigation.
+ static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost())->
+ set_simulate_history_list_was_cleared(true);
+ contents()->CommitPendingNavigation();
+
+ // Verify that the NavigationController's session history was correctly
+ // cleared.
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(0, controller.GetCurrentEntryIndex());
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(-1, controller.GetPendingEntryIndex());
+ EXPECT_FALSE(controller.CanGoBack());
+ EXPECT_FALSE(controller.CanGoForward());
+ EXPECT_EQ(url4, controller.GetVisibleEntry()->GetURL());
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/navigation_entry_impl.cc b/content/browser/frame_host/navigation_entry_impl.cc
new file mode 100644
index 0000000..58dbaf4
--- /dev/null
+++ b/content/browser/frame_host/navigation_entry_impl.cc
@@ -0,0 +1,342 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/navigation_entry_impl.h"
+
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/net_util.h"
+#include "ui/gfx/text_elider.h"
+
+// Use this to get a new unique ID for a NavigationEntry during construction.
+// The returned ID is guaranteed to be nonzero (which is the "no ID" indicator).
+static int GetUniqueIDInConstructor() {
+ static int unique_id_counter = 0;
+ return ++unique_id_counter;
+}
+
+namespace content {
+
+int NavigationEntryImpl::kInvalidBindings = -1;
+
+NavigationEntry* NavigationEntry::Create() {
+ return new NavigationEntryImpl();
+}
+
+NavigationEntry* NavigationEntry::Create(const NavigationEntry& copy) {
+ return new NavigationEntryImpl(static_cast<const NavigationEntryImpl&>(copy));
+}
+
+NavigationEntryImpl* NavigationEntryImpl::FromNavigationEntry(
+ NavigationEntry* entry) {
+ return static_cast<NavigationEntryImpl*>(entry);
+}
+
+NavigationEntryImpl::NavigationEntryImpl()
+ : unique_id_(GetUniqueIDInConstructor()),
+ site_instance_(NULL),
+ bindings_(kInvalidBindings),
+ page_type_(PAGE_TYPE_NORMAL),
+ update_virtual_url_with_url_(false),
+ page_id_(-1),
+ transition_type_(PAGE_TRANSITION_LINK),
+ has_post_data_(false),
+ post_id_(-1),
+ restore_type_(RESTORE_NONE),
+ is_overriding_user_agent_(false),
+ http_status_code_(0),
+ is_renderer_initiated_(false),
+ should_replace_entry_(false),
+ should_clear_history_list_(false),
+ can_load_local_resources_(false) {
+}
+
+NavigationEntryImpl::NavigationEntryImpl(SiteInstanceImpl* instance,
+ int page_id,
+ const GURL& url,
+ const Referrer& referrer,
+ const string16& title,
+ PageTransition transition_type,
+ bool is_renderer_initiated)
+ : unique_id_(GetUniqueIDInConstructor()),
+ site_instance_(instance),
+ bindings_(kInvalidBindings),
+ page_type_(PAGE_TYPE_NORMAL),
+ url_(url),
+ referrer_(referrer),
+ update_virtual_url_with_url_(false),
+ title_(title),
+ page_id_(page_id),
+ transition_type_(transition_type),
+ has_post_data_(false),
+ post_id_(-1),
+ restore_type_(RESTORE_NONE),
+ is_overriding_user_agent_(false),
+ http_status_code_(0),
+ is_renderer_initiated_(is_renderer_initiated),
+ should_replace_entry_(false),
+ should_clear_history_list_(false),
+ can_load_local_resources_(false) {
+}
+
+NavigationEntryImpl::~NavigationEntryImpl() {
+}
+
+int NavigationEntryImpl::GetUniqueID() const {
+ return unique_id_;
+}
+
+PageType NavigationEntryImpl::GetPageType() const {
+ return page_type_;
+}
+
+void NavigationEntryImpl::SetURL(const GURL& url) {
+ url_ = url;
+ cached_display_title_.clear();
+}
+
+const GURL& NavigationEntryImpl::GetURL() const {
+ return url_;
+}
+
+void NavigationEntryImpl::SetBaseURLForDataURL(const GURL& url) {
+ base_url_for_data_url_ = url;
+}
+
+const GURL& NavigationEntryImpl::GetBaseURLForDataURL() const {
+ return base_url_for_data_url_;
+}
+
+void NavigationEntryImpl::SetReferrer(const Referrer& referrer) {
+ referrer_ = referrer;
+}
+
+const Referrer& NavigationEntryImpl::GetReferrer() const {
+ return referrer_;
+}
+
+void NavigationEntryImpl::SetVirtualURL(const GURL& url) {
+ virtual_url_ = (url == url_) ? GURL() : url;
+ cached_display_title_.clear();
+}
+
+const GURL& NavigationEntryImpl::GetVirtualURL() const {
+ return virtual_url_.is_empty() ? url_ : virtual_url_;
+}
+
+void NavigationEntryImpl::SetTitle(const string16& title) {
+ title_ = title;
+ cached_display_title_.clear();
+}
+
+const string16& NavigationEntryImpl::GetTitle() const {
+ return title_;
+}
+
+void NavigationEntryImpl::SetPageState(const PageState& state) {
+ page_state_ = state;
+}
+
+const PageState& NavigationEntryImpl::GetPageState() const {
+ return page_state_;
+}
+
+void NavigationEntryImpl::SetPageID(int page_id) {
+ page_id_ = page_id;
+}
+
+int32 NavigationEntryImpl::GetPageID() const {
+ return page_id_;
+}
+
+void NavigationEntryImpl::set_site_instance(SiteInstanceImpl* site_instance) {
+ site_instance_ = site_instance;
+}
+
+void NavigationEntryImpl::SetBindings(int bindings) {
+ // Ensure this is set to a valid value, and that it stays the same once set.
+ CHECK_NE(bindings, kInvalidBindings);
+ CHECK(bindings_ == kInvalidBindings || bindings_ == bindings);
+ bindings_ = bindings;
+}
+
+const string16& NavigationEntryImpl::GetTitleForDisplay(
+ const std::string& languages) const {
+ // Most pages have real titles. Don't even bother caching anything if this is
+ // the case.
+ if (!title_.empty())
+ return title_;
+
+ // More complicated cases will use the URLs as the title. This result we will
+ // cache since it's more complicated to compute.
+ if (!cached_display_title_.empty())
+ return cached_display_title_;
+
+ // Use the virtual URL first if any, and fall back on using the real URL.
+ string16 title;
+ if (!virtual_url_.is_empty()) {
+ title = net::FormatUrl(virtual_url_, languages);
+ } else if (!url_.is_empty()) {
+ title = net::FormatUrl(url_, languages);
+ }
+
+ // For file:// URLs use the filename as the title, not the full path.
+ if (url_.SchemeIsFile()) {
+ string16::size_type slashpos = title.rfind('/');
+ if (slashpos != string16::npos)
+ title = title.substr(slashpos + 1);
+ }
+
+ gfx::ElideString(title, kMaxTitleChars, &cached_display_title_);
+ return cached_display_title_;
+}
+
+bool NavigationEntryImpl::IsViewSourceMode() const {
+ return virtual_url_.SchemeIs(kViewSourceScheme);
+}
+
+void NavigationEntryImpl::SetTransitionType(
+ PageTransition transition_type) {
+ transition_type_ = transition_type;
+}
+
+PageTransition NavigationEntryImpl::GetTransitionType() const {
+ return transition_type_;
+}
+
+const GURL& NavigationEntryImpl::GetUserTypedURL() const {
+ return user_typed_url_;
+}
+
+void NavigationEntryImpl::SetHasPostData(bool has_post_data) {
+ has_post_data_ = has_post_data;
+}
+
+bool NavigationEntryImpl::GetHasPostData() const {
+ return has_post_data_;
+}
+
+void NavigationEntryImpl::SetPostID(int64 post_id) {
+ post_id_ = post_id;
+}
+
+int64 NavigationEntryImpl::GetPostID() const {
+ return post_id_;
+}
+
+void NavigationEntryImpl::SetBrowserInitiatedPostData(
+ const base::RefCountedMemory* data) {
+ browser_initiated_post_data_ = data;
+}
+
+const base::RefCountedMemory*
+NavigationEntryImpl::GetBrowserInitiatedPostData() const {
+ return browser_initiated_post_data_.get();
+}
+
+
+const FaviconStatus& NavigationEntryImpl::GetFavicon() const {
+ return favicon_;
+}
+
+FaviconStatus& NavigationEntryImpl::GetFavicon() {
+ return favicon_;
+}
+
+const SSLStatus& NavigationEntryImpl::GetSSL() const {
+ return ssl_;
+}
+
+SSLStatus& NavigationEntryImpl::GetSSL() {
+ return ssl_;
+}
+
+void NavigationEntryImpl::SetOriginalRequestURL(const GURL& original_url) {
+ original_request_url_ = original_url;
+}
+
+const GURL& NavigationEntryImpl::GetOriginalRequestURL() const {
+ return original_request_url_;
+}
+
+void NavigationEntryImpl::SetIsOverridingUserAgent(bool override) {
+ is_overriding_user_agent_ = override;
+}
+
+bool NavigationEntryImpl::GetIsOverridingUserAgent() const {
+ return is_overriding_user_agent_;
+}
+
+void NavigationEntryImpl::SetTimestamp(base::Time timestamp) {
+ timestamp_ = timestamp;
+}
+
+base::Time NavigationEntryImpl::GetTimestamp() const {
+ return timestamp_;
+}
+
+void NavigationEntryImpl::SetHttpStatusCode(int http_status_code) {
+ http_status_code_ = http_status_code;
+}
+
+int NavigationEntryImpl::GetHttpStatusCode() const {
+ return http_status_code_;
+}
+
+void NavigationEntryImpl::SetCanLoadLocalResources(bool allow) {
+ can_load_local_resources_ = allow;
+}
+
+bool NavigationEntryImpl::GetCanLoadLocalResources() const {
+ return can_load_local_resources_;
+}
+
+void NavigationEntryImpl::SetFrameToNavigate(const std::string& frame_name) {
+ frame_to_navigate_ = frame_name;
+}
+
+const std::string& NavigationEntryImpl::GetFrameToNavigate() const {
+ return frame_to_navigate_;
+}
+
+void NavigationEntryImpl::SetExtraData(const std::string& key,
+ const string16& data) {
+ extra_data_[key] = data;
+}
+
+bool NavigationEntryImpl::GetExtraData(const std::string& key,
+ string16* data) const {
+ std::map<std::string, string16>::const_iterator iter = extra_data_.find(key);
+ if (iter == extra_data_.end())
+ return false;
+ *data = iter->second;
+ return true;
+}
+
+void NavigationEntryImpl::ClearExtraData(const std::string& key) {
+ extra_data_.erase(key);
+}
+
+void NavigationEntryImpl::ResetForCommit() {
+ // Any state that only matters when a navigation entry is pending should be
+ // cleared here.
+ SetBrowserInitiatedPostData(NULL);
+ set_is_renderer_initiated(false);
+ set_transferred_global_request_id(GlobalRequestID());
+ set_should_replace_entry(false);
+ redirect_chain_.clear();
+ set_should_clear_history_list(false);
+}
+
+void NavigationEntryImpl::SetScreenshotPNGData(
+ scoped_refptr<base::RefCountedBytes> png_data) {
+ screenshot_ = png_data;
+ if (screenshot_.get())
+ UMA_HISTOGRAM_MEMORY_KB("Overscroll.ScreenshotSize", screenshot_->size());
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/navigation_entry_impl.h b/content/browser/frame_host/navigation_entry_impl.h
new file mode 100644
index 0000000..1149ae6
--- /dev/null
+++ b/content/browser/frame_host/navigation_entry_impl.h
@@ -0,0 +1,336 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_NAVIGATION_ENTRY_IMPL_H_
+#define CONTENT_BROWSER_FRAME_HOST_NAVIGATION_ENTRY_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/public/browser/favicon_status.h"
+#include "content/public/browser/global_request_id.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/common/page_state.h"
+#include "content/public/common/ssl_status.h"
+
+namespace content {
+
+class CONTENT_EXPORT NavigationEntryImpl
+ : public NON_EXPORTED_BASE(NavigationEntry) {
+ public:
+ static NavigationEntryImpl* FromNavigationEntry(NavigationEntry* entry);
+
+ // The value of bindings() before it is set during commit.
+ static int kInvalidBindings;
+
+ NavigationEntryImpl();
+ NavigationEntryImpl(SiteInstanceImpl* instance,
+ int page_id,
+ const GURL& url,
+ const Referrer& referrer,
+ const string16& title,
+ PageTransition transition_type,
+ bool is_renderer_initiated);
+ virtual ~NavigationEntryImpl();
+
+ // NavigationEntry implementation:
+ virtual int GetUniqueID() const OVERRIDE;
+ virtual PageType GetPageType() const OVERRIDE;
+ virtual void SetURL(const GURL& url) OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual void SetBaseURLForDataURL(const GURL& url) OVERRIDE;
+ virtual const GURL& GetBaseURLForDataURL() const OVERRIDE;
+ virtual void SetReferrer(const Referrer& referrer) OVERRIDE;
+ virtual const Referrer& GetReferrer() const OVERRIDE;
+ virtual void SetVirtualURL(const GURL& url) OVERRIDE;
+ virtual const GURL& GetVirtualURL() const OVERRIDE;
+ virtual void SetTitle(const string16& title) OVERRIDE;
+ virtual const string16& GetTitle() const OVERRIDE;
+ virtual void SetPageState(const PageState& state) OVERRIDE;
+ virtual const PageState& GetPageState() const OVERRIDE;
+ virtual void SetPageID(int page_id) OVERRIDE;
+ virtual int32 GetPageID() const OVERRIDE;
+ virtual const string16& GetTitleForDisplay(
+ const std::string& languages) const OVERRIDE;
+ virtual bool IsViewSourceMode() const OVERRIDE;
+ virtual void SetTransitionType(PageTransition transition_type) OVERRIDE;
+ virtual PageTransition GetTransitionType() const OVERRIDE;
+ virtual const GURL& GetUserTypedURL() const OVERRIDE;
+ virtual void SetHasPostData(bool has_post_data) OVERRIDE;
+ virtual bool GetHasPostData() const OVERRIDE;
+ virtual void SetPostID(int64 post_id) OVERRIDE;
+ virtual int64 GetPostID() const OVERRIDE;
+ virtual void SetBrowserInitiatedPostData(
+ const base::RefCountedMemory* data) OVERRIDE;
+ virtual const base::RefCountedMemory*
+ GetBrowserInitiatedPostData() const OVERRIDE;
+ virtual const FaviconStatus& GetFavicon() const OVERRIDE;
+ virtual FaviconStatus& GetFavicon() OVERRIDE;
+ virtual const SSLStatus& GetSSL() const OVERRIDE;
+ virtual SSLStatus& GetSSL() OVERRIDE;
+ virtual void SetOriginalRequestURL(const GURL& original_url) OVERRIDE;
+ virtual const GURL& GetOriginalRequestURL() const OVERRIDE;
+ virtual void SetIsOverridingUserAgent(bool override) OVERRIDE;
+ virtual bool GetIsOverridingUserAgent() const OVERRIDE;
+ virtual void SetTimestamp(base::Time timestamp) OVERRIDE;
+ virtual base::Time GetTimestamp() const OVERRIDE;
+ virtual void SetCanLoadLocalResources(bool allow) OVERRIDE;
+ virtual bool GetCanLoadLocalResources() const OVERRIDE;
+ virtual void SetFrameToNavigate(const std::string& frame_name) OVERRIDE;
+ virtual const std::string& GetFrameToNavigate() const OVERRIDE;
+ virtual void SetExtraData(const std::string& key,
+ const string16& data) OVERRIDE;
+ virtual bool GetExtraData(const std::string& key,
+ string16* data) const OVERRIDE;
+ virtual void ClearExtraData(const std::string& key) OVERRIDE;
+ virtual void SetHttpStatusCode(int http_status_code) OVERRIDE;
+ virtual int GetHttpStatusCode() const OVERRIDE;
+
+ // Once a navigation entry is committed, we should no longer track several
+ // pieces of non-persisted state, as documented on the members below.
+ void ResetForCommit();
+
+ void set_unique_id(int unique_id) {
+ unique_id_ = unique_id;
+ }
+
+ // The SiteInstance tells us how to share sub-processes. This is a reference
+ // counted pointer to a shared site instance.
+ //
+ // Note that the SiteInstance should usually not be changed after it is set,
+ // but this may happen if the NavigationEntry was cloned and needs to use a
+ // different SiteInstance.
+ void set_site_instance(SiteInstanceImpl* site_instance);
+ SiteInstanceImpl* site_instance() const {
+ return site_instance_.get();
+ }
+
+ // Remember the set of bindings granted to this NavigationEntry at the time
+ // of commit, to ensure that we do not grant it additional bindings if we
+ // navigate back to it in the future. This can only be changed once.
+ void SetBindings(int bindings);
+ int bindings() const {
+ return bindings_;
+ }
+
+ void set_page_type(PageType page_type) {
+ page_type_ = page_type;
+ }
+
+ bool has_virtual_url() const {
+ return !virtual_url_.is_empty();
+ }
+
+ bool update_virtual_url_with_url() const {
+ return update_virtual_url_with_url_;
+ }
+ void set_update_virtual_url_with_url(bool update) {
+ update_virtual_url_with_url_ = update;
+ }
+
+ // Extra headers (separated by \n) to send during the request.
+ void set_extra_headers(const std::string& extra_headers) {
+ extra_headers_ = extra_headers;
+ }
+ const std::string& extra_headers() const {
+ return extra_headers_;
+ }
+
+ // Whether this (pending) navigation is renderer-initiated. Resets to false
+ // for all types of navigations after commit.
+ void set_is_renderer_initiated(bool is_renderer_initiated) {
+ is_renderer_initiated_ = is_renderer_initiated;
+ }
+ bool is_renderer_initiated() const {
+ return is_renderer_initiated_;
+ }
+
+ void set_user_typed_url(const GURL& user_typed_url) {
+ user_typed_url_ = user_typed_url;
+ }
+
+ // Enumerations of the possible restore types.
+ enum RestoreType {
+ // Restore from the previous session.
+ RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ RESTORE_LAST_SESSION_CRASHED,
+
+ // The entry has been restored from the current session. This is used when
+ // the user issues 'reopen closed tab'.
+ RESTORE_CURRENT_SESSION,
+
+ // The entry was not restored.
+ RESTORE_NONE
+ };
+
+ // The RestoreType for this entry. This is set if the entry was retored. This
+ // is set to RESTORE_NONE once the entry is loaded.
+ void set_restore_type(RestoreType type) {
+ restore_type_ = type;
+ }
+ RestoreType restore_type() const {
+ return restore_type_;
+ }
+
+ void set_transferred_global_request_id(
+ const GlobalRequestID& transferred_global_request_id) {
+ transferred_global_request_id_ = transferred_global_request_id;
+ }
+
+ GlobalRequestID transferred_global_request_id() const {
+ return transferred_global_request_id_;
+ }
+
+ // Whether this (pending) navigation needs to replace current entry.
+ // Resets to false after commit.
+ bool should_replace_entry() const {
+ return should_replace_entry_;
+ }
+
+ void set_should_replace_entry(bool should_replace_entry) {
+ should_replace_entry_ = should_replace_entry;
+ }
+
+ // Any redirects present in a pending entry when it is transferred from one
+ // process to another. Not valid after commit.
+ const std::vector<GURL>& redirect_chain() const {
+ return redirect_chain_;
+ }
+
+ void set_redirect_chain(const std::vector<GURL>& redirect_chain) {
+ redirect_chain_ = redirect_chain;
+ }
+
+ void SetScreenshotPNGData(scoped_refptr<base::RefCountedBytes> png_data);
+ const scoped_refptr<base::RefCountedBytes> screenshot() const {
+ return screenshot_;
+ }
+
+ // Whether this (pending) navigation should clear the session history. Resets
+ // to false after commit.
+ bool should_clear_history_list() const {
+ return should_clear_history_list_;
+ }
+ void set_should_clear_history_list(bool should_clear_history_list) {
+ should_clear_history_list_ = should_clear_history_list;
+ }
+
+ private:
+ // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ // Session/Tab restore save portions of this class so that it can be recreated
+ // later. If you add a new field that needs to be persisted you'll have to
+ // update SessionService/TabRestoreService and Android WebView
+ // state_serializer.cc appropriately.
+ // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+
+ // See the accessors above for descriptions.
+ int unique_id_;
+ scoped_refptr<SiteInstanceImpl> site_instance_;
+ // TODO(creis): Persist bindings_. http://crbug.com/173672.
+ int bindings_;
+ PageType page_type_;
+ GURL url_;
+ Referrer referrer_;
+ GURL virtual_url_;
+ bool update_virtual_url_with_url_;
+ string16 title_;
+ FaviconStatus favicon_;
+ PageState page_state_;
+ int32 page_id_;
+ SSLStatus ssl_;
+ PageTransition transition_type_;
+ GURL user_typed_url_;
+ bool has_post_data_;
+ int64 post_id_;
+ RestoreType restore_type_;
+ GURL original_request_url_;
+ bool is_overriding_user_agent_;
+ base::Time timestamp_;
+ int http_status_code_;
+
+ // This member is not persisted with session restore because it is transient.
+ // If the post request succeeds, this field is cleared since the same
+ // information is stored in |content_state_| above. It is also only shallow
+ // copied with compiler provided copy constructor.
+ // Cleared in |ResetForCommit|.
+ scoped_refptr<const base::RefCountedMemory> browser_initiated_post_data_;
+
+ // This is also a transient member (i.e. is not persisted with session
+ // restore). The screenshot of a page is taken when navigating away from the
+ // page. This screenshot is displayed during an overscroll-navigation
+ // gesture. |screenshot_| will be NULL when the screenshot is not available
+ // (e.g. after a session restore, or if taking the screenshot of a page
+ // failed). The UI is responsible for dealing with missing screenshots
+ // appropriately (e.g. display a placeholder image instead).
+ scoped_refptr<base::RefCountedBytes> screenshot_;
+
+ // This member is not persisted with session restore.
+ std::string extra_headers_;
+
+ // Used for specifying base URL for pages loaded via data URLs. Only used and
+ // persisted by Android WebView.
+ GURL base_url_for_data_url_;
+
+ // Whether the entry, while loading, was created for a renderer-initiated
+ // navigation. This dictates whether the URL should be displayed before the
+ // navigation commits. It is cleared in |ResetForCommit| and not persisted.
+ bool is_renderer_initiated_;
+
+ // This is a cached version of the result of GetTitleForDisplay. It prevents
+ // us from having to do URL formatting on the URL every time the title is
+ // displayed. When the URL, virtual URL, or title is set, this should be
+ // cleared to force a refresh.
+ mutable string16 cached_display_title_;
+
+ // In case a navigation is transferred to a new RVH but the request has
+ // been generated in the renderer already, this identifies the old request so
+ // that it can be resumed. The old request is stored until the
+ // ResourceDispatcher receives the navigation from the renderer which
+ // carries this |transferred_global_request_id_| annotation. Once the request
+ // is transferred to the new process, this is cleared and the request
+ // continues as normal.
+ // Cleared in |ResetForCommit|.
+ GlobalRequestID transferred_global_request_id_;
+
+ // This is set to true when this entry is being reloaded and due to changes in
+ // the state of the URL, it has to be reloaded in a different site instance.
+ // In such case, we must treat it as an existing navigation in the new site
+ // instance, instead of a new navigation. This value should not be persisted
+ // and is cleared in |ResetForCommit|.
+ //
+ // We also use this flag for cross-process redirect navigations, so that the
+ // browser will replace the current navigation entry (which is the page
+ // doing the redirect).
+ bool should_replace_entry_;
+
+ // This is used when transferring a pending entry from one process to another.
+ // It is cleared in |ResetForCommit| and should not be persisted.
+ std::vector<GURL> redirect_chain_;
+
+ // This is set to true when this entry's navigation should clear the session
+ // history both on the renderer and browser side. The browser side history
+ // won't be cleared until the renderer has committed this navigation. This
+ // entry is not persisted by the session restore system, as it is always
+ // cleared in |ResetForCommit|.
+ bool should_clear_history_list_;
+
+ // Set when this entry should be able to access local file:// resources. This
+ // value is not needed after the entry commits and is not persisted.
+ bool can_load_local_resources_;
+
+ // If not empty, the name of the frame to navigate. This field is not
+ // persisted, because it is currently only used in tests.
+ std::string frame_to_navigate_;
+
+ // Used to store extra data to support browser features. This member is not
+ // persisted, unless specific data is taken out/put back in at save/restore
+ // time (see TabNavigation for an example of this).
+ std::map<std::string, string16> extra_data_;
+
+ // Copy and assignment is explicitly allowed for this class.
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_NAVIGATION_ENTRY_IMPL_H_
diff --git a/content/browser/frame_host/navigation_entry_impl_unittest.cc b/content/browser/frame_host/navigation_entry_impl_unittest.cc
new file mode 100644
index 0000000..6486fde
--- /dev/null
+++ b/content/browser/frame_host/navigation_entry_impl_unittest.cc
@@ -0,0 +1,241 @@
+// Copyright 2013 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.
+
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/public/common/ssl_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class NavigationEntryTest : public testing::Test {
+ public:
+ NavigationEntryTest() : instance_(NULL) {
+ }
+
+ virtual void SetUp() {
+ entry1_.reset(new NavigationEntryImpl);
+
+#if !defined(OS_IOS)
+ instance_ = static_cast<SiteInstanceImpl*>(SiteInstance::Create(NULL));
+#endif
+ entry2_.reset(new NavigationEntryImpl(
+ instance_, 3,
+ GURL("test:url"),
+ Referrer(GURL("from"), WebKit::WebReferrerPolicyDefault),
+ ASCIIToUTF16("title"),
+ PAGE_TRANSITION_TYPED,
+ false));
+ }
+
+ virtual void TearDown() {
+ }
+
+ protected:
+ scoped_ptr<NavigationEntryImpl> entry1_;
+ scoped_ptr<NavigationEntryImpl> entry2_;
+ // SiteInstances are deleted when their NavigationEntries are gone.
+ SiteInstanceImpl* instance_;
+};
+
+// Test unique ID accessors
+TEST_F(NavigationEntryTest, NavigationEntryUniqueIDs) {
+ // Two entries should have different IDs by default
+ EXPECT_NE(entry1_->GetUniqueID(), entry2_->GetUniqueID());
+
+ // Can set an entry to have the same ID as another
+ entry2_->set_unique_id(entry1_->GetUniqueID());
+ EXPECT_EQ(entry1_->GetUniqueID(), entry2_->GetUniqueID());
+}
+
+// Test URL accessors
+TEST_F(NavigationEntryTest, NavigationEntryURLs) {
+ // Start with no virtual_url (even if a url is set)
+ EXPECT_FALSE(entry1_->has_virtual_url());
+ EXPECT_FALSE(entry2_->has_virtual_url());
+
+ EXPECT_EQ(GURL(), entry1_->GetURL());
+ EXPECT_EQ(GURL(), entry1_->GetVirtualURL());
+ EXPECT_TRUE(entry1_->GetTitleForDisplay(std::string()).empty());
+
+ // Setting URL affects virtual_url and GetTitleForDisplay
+ entry1_->SetURL(GURL("http://www.google.com"));
+ EXPECT_EQ(GURL("http://www.google.com"), entry1_->GetURL());
+ EXPECT_EQ(GURL("http://www.google.com"), entry1_->GetVirtualURL());
+ EXPECT_EQ(ASCIIToUTF16("www.google.com"),
+ entry1_->GetTitleForDisplay(std::string()));
+
+ // file:/// URLs should only show the filename.
+ entry1_->SetURL(GURL("file:///foo/bar baz.txt"));
+ EXPECT_EQ(ASCIIToUTF16("bar baz.txt"),
+ entry1_->GetTitleForDisplay(std::string()));
+
+ // Title affects GetTitleForDisplay
+ entry1_->SetTitle(ASCIIToUTF16("Google"));
+ EXPECT_EQ(ASCIIToUTF16("Google"), entry1_->GetTitleForDisplay(std::string()));
+
+ // Setting virtual_url doesn't affect URL
+ entry2_->SetVirtualURL(GURL("display:url"));
+ EXPECT_TRUE(entry2_->has_virtual_url());
+ EXPECT_EQ(GURL("test:url"), entry2_->GetURL());
+ EXPECT_EQ(GURL("display:url"), entry2_->GetVirtualURL());
+
+ // Having a title set in constructor overrides virtual URL
+ EXPECT_EQ(ASCIIToUTF16("title"), entry2_->GetTitleForDisplay(std::string()));
+
+ // User typed URL is independent of the others
+ EXPECT_EQ(GURL(), entry1_->GetUserTypedURL());
+ EXPECT_EQ(GURL(), entry2_->GetUserTypedURL());
+ entry2_->set_user_typed_url(GURL("typedurl"));
+ EXPECT_EQ(GURL("typedurl"), entry2_->GetUserTypedURL());
+}
+
+// Test Favicon inner class construction.
+TEST_F(NavigationEntryTest, NavigationEntryFavicons) {
+ EXPECT_EQ(GURL(), entry1_->GetFavicon().url);
+ EXPECT_FALSE(entry1_->GetFavicon().valid);
+}
+
+// Test SSLStatus inner class
+TEST_F(NavigationEntryTest, NavigationEntrySSLStatus) {
+ // Default (unknown)
+ EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry1_->GetSSL().security_style);
+ EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry2_->GetSSL().security_style);
+ EXPECT_EQ(0, entry1_->GetSSL().cert_id);
+ EXPECT_EQ(0U, entry1_->GetSSL().cert_status);
+ EXPECT_EQ(-1, entry1_->GetSSL().security_bits);
+ int content_status = entry1_->GetSSL().content_status;
+ EXPECT_FALSE(!!(content_status & SSLStatus::DISPLAYED_INSECURE_CONTENT));
+ EXPECT_FALSE(!!(content_status & SSLStatus::RAN_INSECURE_CONTENT));
+}
+
+// Test other basic accessors
+TEST_F(NavigationEntryTest, NavigationEntryAccessors) {
+ // SiteInstance
+ EXPECT_TRUE(entry1_->site_instance() == NULL);
+ EXPECT_EQ(instance_, entry2_->site_instance());
+ entry1_->set_site_instance(instance_);
+ EXPECT_EQ(instance_, entry1_->site_instance());
+
+ // Page type
+ EXPECT_EQ(PAGE_TYPE_NORMAL, entry1_->GetPageType());
+ EXPECT_EQ(PAGE_TYPE_NORMAL, entry2_->GetPageType());
+ entry2_->set_page_type(PAGE_TYPE_INTERSTITIAL);
+ EXPECT_EQ(PAGE_TYPE_INTERSTITIAL, entry2_->GetPageType());
+
+ // Referrer
+ EXPECT_EQ(GURL(), entry1_->GetReferrer().url);
+ EXPECT_EQ(GURL("from"), entry2_->GetReferrer().url);
+ entry2_->SetReferrer(
+ Referrer(GURL("from2"), WebKit::WebReferrerPolicyDefault));
+ EXPECT_EQ(GURL("from2"), entry2_->GetReferrer().url);
+
+ // Title
+ EXPECT_EQ(string16(), entry1_->GetTitle());
+ EXPECT_EQ(ASCIIToUTF16("title"), entry2_->GetTitle());
+ entry2_->SetTitle(ASCIIToUTF16("title2"));
+ EXPECT_EQ(ASCIIToUTF16("title2"), entry2_->GetTitle());
+
+ // State
+ EXPECT_FALSE(entry1_->GetPageState().IsValid());
+ EXPECT_FALSE(entry2_->GetPageState().IsValid());
+ entry2_->SetPageState(PageState::CreateFromEncodedData("state"));
+ EXPECT_EQ("state", entry2_->GetPageState().ToEncodedData());
+
+ // Page ID
+ EXPECT_EQ(-1, entry1_->GetPageID());
+ EXPECT_EQ(3, entry2_->GetPageID());
+ entry2_->SetPageID(2);
+ EXPECT_EQ(2, entry2_->GetPageID());
+
+ // Transition type
+ EXPECT_EQ(PAGE_TRANSITION_LINK, entry1_->GetTransitionType());
+ EXPECT_EQ(PAGE_TRANSITION_TYPED, entry2_->GetTransitionType());
+ entry2_->SetTransitionType(PAGE_TRANSITION_RELOAD);
+ EXPECT_EQ(PAGE_TRANSITION_RELOAD, entry2_->GetTransitionType());
+
+ // Is renderer initiated
+ EXPECT_FALSE(entry1_->is_renderer_initiated());
+ EXPECT_FALSE(entry2_->is_renderer_initiated());
+ entry2_->set_is_renderer_initiated(true);
+ EXPECT_TRUE(entry2_->is_renderer_initiated());
+
+ // Post Data
+ EXPECT_FALSE(entry1_->GetHasPostData());
+ EXPECT_FALSE(entry2_->GetHasPostData());
+ entry2_->SetHasPostData(true);
+ EXPECT_TRUE(entry2_->GetHasPostData());
+
+ // Restored
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE, entry1_->restore_type());
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_NONE, entry2_->restore_type());
+ entry2_->set_restore_type(
+ NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY);
+ EXPECT_EQ(NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY,
+ entry2_->restore_type());
+
+ // Original URL
+ EXPECT_EQ(GURL(), entry1_->GetOriginalRequestURL());
+ EXPECT_EQ(GURL(), entry2_->GetOriginalRequestURL());
+ entry2_->SetOriginalRequestURL(GURL("original_url"));
+ EXPECT_EQ(GURL("original_url"), entry2_->GetOriginalRequestURL());
+
+ // User agent override
+ EXPECT_FALSE(entry1_->GetIsOverridingUserAgent());
+ EXPECT_FALSE(entry2_->GetIsOverridingUserAgent());
+ entry2_->SetIsOverridingUserAgent(true);
+ EXPECT_TRUE(entry2_->GetIsOverridingUserAgent());
+
+ // Browser initiated post data
+ EXPECT_EQ(NULL, entry1_->GetBrowserInitiatedPostData());
+ EXPECT_EQ(NULL, entry2_->GetBrowserInitiatedPostData());
+ const int length = 11;
+ const unsigned char* raw_data =
+ reinterpret_cast<const unsigned char*>("post\n\n\0data");
+ std::vector<unsigned char> post_data_vector(raw_data, raw_data+length);
+ scoped_refptr<base::RefCountedBytes> post_data =
+ base::RefCountedBytes::TakeVector(&post_data_vector);
+ entry2_->SetBrowserInitiatedPostData(post_data.get());
+ EXPECT_EQ(post_data->front(),
+ entry2_->GetBrowserInitiatedPostData()->front());
+
+ // Frame to navigate.
+ EXPECT_TRUE(entry1_->GetFrameToNavigate().empty());
+ EXPECT_TRUE(entry2_->GetFrameToNavigate().empty());
+}
+
+// Test timestamps.
+TEST_F(NavigationEntryTest, NavigationEntryTimestamps) {
+ EXPECT_EQ(base::Time(), entry1_->GetTimestamp());
+ const base::Time now = base::Time::Now();
+ entry1_->SetTimestamp(now);
+ EXPECT_EQ(now, entry1_->GetTimestamp());
+}
+
+// Test extra data stored in the navigation entry.
+TEST_F(NavigationEntryTest, NavigationEntryExtraData) {
+ string16 test_data = ASCIIToUTF16("my search terms");
+ string16 output;
+ entry1_->SetExtraData("search_terms", test_data);
+
+ EXPECT_FALSE(entry1_->GetExtraData("non_existent_key", &output));
+ EXPECT_EQ(ASCIIToUTF16(""), output);
+ EXPECT_TRUE(entry1_->GetExtraData("search_terms", &output));
+ EXPECT_EQ(test_data, output);
+ // Data is cleared.
+ entry1_->ClearExtraData("search_terms");
+ // Content in |output| is not modified if data is not present at the key.
+ EXPECT_FALSE(entry1_->GetExtraData("search_terms", &output));
+ EXPECT_EQ(test_data, output);
+ // Using an empty string shows that the data is not present in the map.
+ string16 output2;
+ EXPECT_FALSE(entry1_->GetExtraData("search_terms", &output2));
+ EXPECT_EQ(ASCIIToUTF16(""), output2);
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
new file mode 100644
index 0000000..c1fbc30
--- /dev/null
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -0,0 +1,103 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/render_frame_host_impl.h"
+
+#include "base/containers/hash_tables.h"
+#include "base/lazy_instance.h"
+#include "content/browser/frame_host/frame_tree.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/common/frame_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// The (process id, routing id) pair that identifies one RenderFrame.
+typedef std::pair<int32, int32> RenderFrameHostID;
+typedef base::hash_map<RenderFrameHostID, RenderFrameHostImpl*>
+ RoutingIDFrameMap;
+static base::LazyInstance<RoutingIDFrameMap> g_routing_id_frame_map =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+RenderFrameHostImpl* RenderFrameHostImpl::FromID(
+ int process_id, int routing_id) {
+ RoutingIDFrameMap* frames = g_routing_id_frame_map.Pointer();
+ RoutingIDFrameMap::iterator it = frames->find(
+ RenderFrameHostID(process_id, routing_id));
+ return it == frames->end() ? NULL : it->second;
+}
+
+RenderFrameHostImpl::RenderFrameHostImpl(
+ RenderViewHostImpl* render_view_host,
+ FrameTree* frame_tree,
+ int routing_id,
+ bool is_swapped_out)
+ : render_view_host_(render_view_host),
+ frame_tree_(frame_tree),
+ routing_id_(routing_id),
+ is_swapped_out_(is_swapped_out) {
+ GetProcess()->AddRoute(routing_id_, this);
+ g_routing_id_frame_map.Get().insert(std::make_pair(
+ RenderFrameHostID(GetProcess()->GetID(), routing_id_),
+ this));
+}
+
+RenderFrameHostImpl::~RenderFrameHostImpl() {
+ GetProcess()->RemoveRoute(routing_id_);
+ g_routing_id_frame_map.Get().erase(
+ RenderFrameHostID(GetProcess()->GetID(), routing_id_));
+
+}
+
+bool RenderFrameHostImpl::Send(IPC::Message* message) {
+ return GetProcess()->Send(message);
+}
+
+bool RenderFrameHostImpl::OnMessageReceived(const IPC::Message &msg) {
+ bool handled = true;
+ bool msg_is_ok = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(RenderFrameHostImpl, msg, msg_is_ok)
+ IPC_MESSAGE_HANDLER(FrameHostMsg_Detach, OnDetach)
+ IPC_MESSAGE_HANDLER(FrameHostMsg_DidStartProvisionalLoadForFrame,
+ OnDidStartProvisionalLoadForFrame)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+void RenderFrameHostImpl::Init() {
+ GetProcess()->ResumeRequestsForView(routing_id());
+}
+
+RenderProcessHost* RenderFrameHostImpl::GetProcess() const {
+ // TODO(nasko): This should return its own process, once we have working
+ // cross-process navigation for subframes.
+ return render_view_host_->GetProcess();
+}
+
+void RenderFrameHostImpl::OnCreateChildFrame(int new_frame_routing_id,
+ int64 parent_frame_id,
+ int64 frame_id,
+ const std::string& frame_name) {
+ frame_tree_->AddFrame(new_frame_routing_id, parent_frame_id, frame_id,
+ frame_name);
+}
+
+void RenderFrameHostImpl::OnDetach(int64 parent_frame_id, int64 frame_id) {
+ frame_tree_->RemoveFrame(parent_frame_id, frame_id);
+}
+
+void RenderFrameHostImpl::OnDidStartProvisionalLoadForFrame(
+ int64 frame_id,
+ int64 parent_frame_id,
+ bool is_main_frame,
+ const GURL& url) {
+ render_view_host_->OnDidStartProvisionalLoadForFrame(
+ frame_id, parent_frame_id, is_main_frame, url);
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
new file mode 100644
index 0000000..dbe8972
--- /dev/null
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -0,0 +1,77 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_HOST_IMPL_H_
+#define CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_HOST_IMPL_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "content/public/browser/render_frame_host.h"
+
+class GURL;
+
+namespace content {
+
+class FrameTree;
+class RenderProcessHost;
+class RenderViewHostImpl;
+
+class CONTENT_EXPORT RenderFrameHostImpl : public RenderFrameHost {
+ public:
+ static RenderFrameHostImpl* FromID(int process_id, int routing_id);
+
+ // TODO(nasko): Remove dependency on RenderViewHost here. RenderProcessHost
+ // should be the abstraction needed here, but we need RenderViewHost to pass
+ // into WebContentsObserver::FrameDetached for now.
+ RenderFrameHostImpl(RenderViewHostImpl* render_view_host,
+ FrameTree* frame_tree,
+ int routing_id,
+ bool is_swapped_out);
+ virtual ~RenderFrameHostImpl();
+
+ // IPC::Sender
+ virtual bool Send(IPC::Message* msg) OVERRIDE;
+
+ // IPC::Listener
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+
+ void Init();
+ RenderProcessHost* GetProcess() const;
+ int routing_id() const { return routing_id_; }
+ void OnCreateChildFrame(int new_frame_routing_id,
+ int64 parent_frame_id,
+ int64 frame_id,
+ const std::string& frame_name);
+
+ RenderViewHostImpl* render_view_host() {
+ return render_view_host_;
+ }
+
+ private:
+ // IPC Message handlers.
+ void OnDetach(int64 parent_frame_id, int64 frame_id);
+ void OnDidStartProvisionalLoadForFrame(int64 frame_id,
+ int64 parent_frame_id,
+ bool main_frame,
+ const GURL& url);
+
+ bool is_swapped_out() { return is_swapped_out_; }
+
+ // TODO(nasko): This should be removed and replaced by RenderProcessHost.
+ RenderViewHostImpl* render_view_host_; // Not owned.
+
+ // Reference to the whole frame tree that this RenderFrameHost belongs too.
+ // Allows this RenderFrameHost to add and remove nodes in response to
+ // messages from the renderer requesting DOM manipulation.
+ FrameTree* frame_tree_;
+ int routing_id_;
+ bool is_swapped_out_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderFrameHostImpl);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_HOST_IMPL_H_
diff --git a/content/browser/frame_host/render_frame_message_filter.cc b/content/browser/frame_host/render_frame_message_filter.cc
new file mode 100644
index 0000000..c187680
--- /dev/null
+++ b/content/browser/frame_host/render_frame_message_filter.cc
@@ -0,0 +1,71 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/render_frame_message_filter.h"
+
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/renderer_host/render_widget_helper.h"
+#include "content/common/frame_messages.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+namespace {
+
+void CreateChildFrameOnUI(int process_id,
+ int parent_render_frame_id,
+ int64 parent_frame_id,
+ int64 frame_id,
+ const std::string& frame_name,
+ int new_render_frame_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ RenderFrameHostImpl* render_frame_host =
+ RenderFrameHostImpl::FromID(process_id, parent_render_frame_id);
+ // Handles the RenderFrameHost being deleted on the UI thread while
+ // processing a subframe creation message.
+ if (render_frame_host) {
+ render_frame_host->OnCreateChildFrame(new_render_frame_id,
+ parent_frame_id, frame_id,
+ frame_name);
+ }
+}
+
+} // namespace
+
+RenderFrameMessageFilter::RenderFrameMessageFilter(
+ int render_process_id,
+ RenderWidgetHelper* render_widget_helper)
+ : render_process_id_(render_process_id),
+ render_widget_helper_(render_widget_helper) {
+}
+
+RenderFrameMessageFilter::~RenderFrameMessageFilter() {
+}
+
+bool RenderFrameMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(RenderFrameMessageFilter, message, *message_was_ok)
+ IPC_MESSAGE_HANDLER(FrameHostMsg_CreateChildFrame, OnCreateChildFrame)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP_EX()
+
+ return handled;
+}
+
+void RenderFrameMessageFilter::OnCreateChildFrame(
+ int parent_render_frame_id,
+ int64 parent_frame_id,
+ int64 frame_id,
+ const std::string& frame_name,
+ int* new_render_frame_id) {
+ *new_render_frame_id = render_widget_helper_->GetNextRoutingID();
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&CreateChildFrameOnUI, render_process_id_,
+ parent_render_frame_id, parent_frame_id, frame_id, frame_name,
+ *new_render_frame_id));
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/render_frame_message_filter.h b/content/browser/frame_host/render_frame_message_filter.h
new file mode 100644
index 0000000..82a1b94
--- /dev/null
+++ b/content/browser/frame_host/render_frame_message_filter.h
@@ -0,0 +1,44 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_MESSAGE_FILTER_H_
+#define CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_MESSAGE_FILTER_H_
+
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+class RenderWidgetHelper;
+
+// RenderFrameMessageFilter intercepts FrameHost messages on the IO thread
+// that require low-latency processing. The canonical example of this is
+// child-frame creation which is a sync IPC that provides the renderer
+// with the routing id for a newly created RenderFrame.
+//
+// This object is created on the UI thread and used on the IO thread.
+class RenderFrameMessageFilter : public BrowserMessageFilter {
+ public:
+ RenderFrameMessageFilter(int render_process_id,
+ RenderWidgetHelper* render_widget_helper);
+
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+
+ private:
+ virtual ~RenderFrameMessageFilter();
+
+ void OnCreateChildFrame(int parent_render_frame_id,
+ int64 parent_frame_id,
+ int64 frame_id,
+ const std::string& frame_name,
+ int* new_render_frame_id);
+
+ const int render_process_id_;
+
+ // Needed for issuing routing ids and surface ids.
+ scoped_refptr<RenderWidgetHelper> render_widget_helper_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_MESSAGE_FILTER_H_
diff --git a/content/browser/frame_host/render_view_host_manager.cc b/content/browser/frame_host/render_view_host_manager.cc
new file mode 100644
index 0000000..34e2a00
--- /dev/null
+++ b/content/browser/frame_host/render_view_host_manager.cc
@@ -0,0 +1,1119 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/render_view_host_manager.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "content/browser/devtools/render_view_devtools_agent_host.h"
+#include "content/browser/frame_host/interstitial_page_impl.h"
+#include "content/browser/frame_host/navigation_controller_impl.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/browser/renderer_host/render_view_host_factory.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/browser/webui/web_ui_impl.h"
+#include "content/common/view_messages.h"
+#include "content/port/browser/render_widget_host_view_port.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_widget_host_iterator.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+
+namespace content {
+
+RenderViewHostManager::PendingNavigationParams::PendingNavigationParams()
+ : is_transfer(false), frame_id(-1) {
+}
+
+RenderViewHostManager::PendingNavigationParams::PendingNavigationParams(
+ const GlobalRequestID& global_request_id,
+ bool is_transfer,
+ const std::vector<GURL>& transfer_url_chain,
+ Referrer referrer,
+ PageTransition page_transition,
+ int64 frame_id)
+ : global_request_id(global_request_id),
+ is_transfer(is_transfer),
+ transfer_url_chain(transfer_url_chain),
+ referrer(referrer),
+ page_transition(page_transition),
+ frame_id(frame_id) {
+}
+
+RenderViewHostManager::PendingNavigationParams::~PendingNavigationParams() {}
+
+RenderViewHostManager::RenderViewHostManager(
+ RenderViewHostDelegate* render_view_delegate,
+ RenderWidgetHostDelegate* render_widget_delegate,
+ Delegate* delegate)
+ : delegate_(delegate),
+ cross_navigation_pending_(false),
+ render_view_delegate_(render_view_delegate),
+ render_widget_delegate_(render_widget_delegate),
+ render_view_host_(NULL),
+ pending_render_view_host_(NULL),
+ interstitial_page_(NULL) {
+}
+
+RenderViewHostManager::~RenderViewHostManager() {
+ if (pending_render_view_host_)
+ CancelPending();
+
+ // We should always have a main RenderViewHost except in some tests.
+ RenderViewHostImpl* render_view_host = render_view_host_;
+ render_view_host_ = NULL;
+ if (render_view_host)
+ render_view_host->Shutdown();
+
+ // Shut down any swapped out RenderViewHosts.
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ iter->second->Shutdown();
+ }
+}
+
+void RenderViewHostManager::Init(BrowserContext* browser_context,
+ SiteInstance* site_instance,
+ int routing_id,
+ int main_frame_routing_id) {
+ // Create a RenderViewHost, once we have an instance. It is important to
+ // immediately give this SiteInstance to a RenderViewHost so that it is
+ // ref counted.
+ if (!site_instance)
+ site_instance = SiteInstance::Create(browser_context);
+ render_view_host_ = static_cast<RenderViewHostImpl*>(
+ RenderViewHostFactory::Create(
+ site_instance, render_view_delegate_, render_widget_delegate_,
+ routing_id, main_frame_routing_id, false,
+ delegate_->IsHidden()));
+ render_view_host_->AttachToFrameTree();
+
+ // Keep track of renderer processes as they start to shut down or are
+ // crashed/killed.
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSING,
+ NotificationService::AllSources());
+}
+
+RenderViewHostImpl* RenderViewHostManager::current_host() const {
+ return render_view_host_;
+}
+
+RenderViewHostImpl* RenderViewHostManager::pending_render_view_host() const {
+ return pending_render_view_host_;
+}
+
+RenderWidgetHostView* RenderViewHostManager::GetRenderWidgetHostView() const {
+ if (interstitial_page_)
+ return interstitial_page_->GetView();
+ if (!render_view_host_)
+ return NULL;
+ return render_view_host_->GetView();
+}
+
+void RenderViewHostManager::SetPendingWebUI(const NavigationEntryImpl& entry) {
+ pending_web_ui_.reset(
+ delegate_->CreateWebUIForRenderManager(entry.GetURL()));
+ pending_and_current_web_ui_.reset();
+
+ // If we have assigned (zero or more) bindings to this NavigationEntry in the
+ // past, make sure we're not granting it different bindings than it had
+ // before. If so, note it and don't give it any bindings, to avoid a
+ // potential privilege escalation.
+ if (pending_web_ui_.get() &&
+ entry.bindings() != NavigationEntryImpl::kInvalidBindings &&
+ pending_web_ui_->GetBindings() != entry.bindings()) {
+ RecordAction(UserMetricsAction("ProcessSwapBindingsMismatch_RVHM"));
+ pending_web_ui_.reset();
+ }
+}
+
+RenderViewHostImpl* RenderViewHostManager::Navigate(
+ const NavigationEntryImpl& entry) {
+ TRACE_EVENT0("browser", "RenderViewHostManager:Navigate");
+ // Create a pending RenderViewHost. It will give us the one we should use
+ RenderViewHostImpl* dest_render_view_host =
+ static_cast<RenderViewHostImpl*>(UpdateRendererStateForNavigate(entry));
+ if (!dest_render_view_host)
+ return NULL; // We weren't able to create a pending render view host.
+
+ // If the current render_view_host_ isn't live, we should create it so
+ // that we don't show a sad tab while the dest_render_view_host fetches
+ // its first page. (Bug 1145340)
+ if (dest_render_view_host != render_view_host_ &&
+ !render_view_host_->IsRenderViewLive()) {
+ // Note: we don't call InitRenderView here because we are navigating away
+ // soon anyway, and we don't have the NavigationEntry for this host.
+ delegate_->CreateRenderViewForRenderManager(render_view_host_,
+ MSG_ROUTING_NONE);
+ }
+
+ // If the renderer crashed, then try to create a new one to satisfy this
+ // navigation request.
+ if (!dest_render_view_host->IsRenderViewLive()) {
+ // Recreate the opener chain.
+ int opener_route_id = delegate_->CreateOpenerRenderViewsForRenderManager(
+ dest_render_view_host->GetSiteInstance());
+ if (!InitRenderView(dest_render_view_host, opener_route_id))
+ return NULL;
+
+ // Now that we've created a new renderer, be sure to hide it if it isn't
+ // our primary one. Otherwise, we might crash if we try to call Show()
+ // on it later.
+ if (dest_render_view_host != render_view_host_ &&
+ dest_render_view_host->GetView()) {
+ dest_render_view_host->GetView()->Hide();
+ } else {
+ // This is our primary renderer, notify here as we won't be calling
+ // CommitPending (which does the notify).
+ delegate_->NotifySwappedFromRenderManager(NULL, render_view_host_);
+ }
+ }
+
+ return dest_render_view_host;
+}
+
+void RenderViewHostManager::Stop() {
+ render_view_host_->Stop();
+
+ // If we are cross-navigating, we should stop the pending renderers. This
+ // will lead to a DidFailProvisionalLoad, which will properly destroy them.
+ if (cross_navigation_pending_) {
+ pending_render_view_host_->Send(
+ new ViewMsg_Stop(pending_render_view_host_->GetRoutingID()));
+ }
+}
+
+void RenderViewHostManager::SetIsLoading(bool is_loading) {
+ render_view_host_->SetIsLoading(is_loading);
+ if (pending_render_view_host_)
+ pending_render_view_host_->SetIsLoading(is_loading);
+}
+
+bool RenderViewHostManager::ShouldCloseTabOnUnresponsiveRenderer() {
+ if (!cross_navigation_pending_)
+ return true;
+
+ // We should always have a pending RVH when there's a cross-process navigation
+ // in progress. Sanity check this for http://crbug.com/276333.
+ CHECK(pending_render_view_host_);
+
+ // If the tab becomes unresponsive during {before}unload while doing a
+ // cross-site navigation, proceed with the navigation. (This assumes that
+ // the pending RenderViewHost is still responsive.)
+ if (render_view_host_->is_waiting_for_unload_ack()) {
+ // The request has been started and paused while we're waiting for the
+ // unload handler to finish. We'll pretend that it did. The pending
+ // renderer will then be swapped in as part of the usual DidNavigate logic.
+ // (If the unload handler later finishes, this call will be ignored because
+ // the pending_nav_params_ state will already be cleaned up.)
+ current_host()->OnSwappedOut(true);
+ } else if (render_view_host_->is_waiting_for_beforeunload_ack()) {
+ // Haven't gotten around to starting the request, because we're still
+ // waiting for the beforeunload handler to finish. We'll pretend that it
+ // did finish, to let the navigation proceed. Note that there's a danger
+ // that the beforeunload handler will later finish and possibly return
+ // false (meaning the navigation should not proceed), but we'll ignore it
+ // in this case because it took too long.
+ if (pending_render_view_host_->are_navigations_suspended())
+ pending_render_view_host_->SetNavigationsSuspended(
+ false, base::TimeTicks::Now());
+ }
+ return false;
+}
+
+void RenderViewHostManager::SwappedOut(RenderViewHost* render_view_host) {
+ // Make sure this is from our current RVH, and that we have a pending
+ // navigation from OnCrossSiteResponse. (There may be no pending navigation
+ // for data URLs that don't make network requests, for example.) If not,
+ // just return early and ignore.
+ if (render_view_host != render_view_host_ || !pending_nav_params_.get()) {
+ pending_nav_params_.reset();
+ return;
+ }
+
+ // Now that the unload handler has run, we need to either initiate the
+ // pending transfer (if there is one) or resume the paused response (if not).
+ // TODO(creis): The blank swapped out page is visible during this time, but
+ // we can shorten this by delivering the response directly, rather than
+ // forcing an identical request to be made.
+ if (pending_nav_params_->is_transfer) {
+ // Treat the last URL in the chain as the destination and the remainder as
+ // the redirect chain.
+ CHECK(pending_nav_params_->transfer_url_chain.size());
+ GURL transfer_url = pending_nav_params_->transfer_url_chain.back();
+ pending_nav_params_->transfer_url_chain.pop_back();
+
+ // We don't know whether the original request had |user_action| set to true.
+ // However, since we force the navigation to be in the current tab, it
+ // doesn't matter.
+ render_view_host->GetDelegate()->RequestTransferURL(
+ transfer_url,
+ pending_nav_params_->transfer_url_chain,
+ pending_nav_params_->referrer,
+ pending_nav_params_->page_transition,
+ CURRENT_TAB,
+ pending_nav_params_->frame_id,
+ pending_nav_params_->global_request_id,
+ false,
+ true);
+ } else if (pending_render_view_host_) {
+ RenderProcessHostImpl* pending_process =
+ static_cast<RenderProcessHostImpl*>(
+ pending_render_view_host_->GetProcess());
+ pending_process->ResumeDeferredNavigation(
+ pending_nav_params_->global_request_id);
+ }
+ pending_nav_params_.reset();
+}
+
+void RenderViewHostManager::DidNavigateMainFrame(
+ RenderViewHost* render_view_host) {
+ if (!cross_navigation_pending_) {
+ DCHECK(!pending_render_view_host_);
+
+ // We should only hear this from our current renderer.
+ DCHECK(render_view_host == render_view_host_);
+
+ // Even when there is no pending RVH, there may be a pending Web UI.
+ if (pending_web_ui())
+ CommitPending();
+ return;
+ }
+
+ if (render_view_host == pending_render_view_host_) {
+ // The pending cross-site navigation completed, so show the renderer.
+ // If it committed without sending network requests (e.g., data URLs),
+ // then we still need to swap out the old RVH first and run its unload
+ // handler. OK for that to happen in the background.
+ if (pending_render_view_host_->HasPendingCrossSiteRequest())
+ SwapOutOldPage();
+
+ CommitPending();
+ cross_navigation_pending_ = false;
+ } else if (render_view_host == render_view_host_) {
+ // A navigation in the original page has taken place. Cancel the pending
+ // one.
+ CancelPending();
+ cross_navigation_pending_ = false;
+ } else {
+ // No one else should be sending us DidNavigate in this state.
+ DCHECK(false);
+ }
+}
+
+void RenderViewHostManager::DidDisownOpener(RenderViewHost* render_view_host) {
+ // Notify all swapped out hosts, including the pending RVH.
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ DCHECK_NE(iter->second->GetSiteInstance(),
+ current_host()->GetSiteInstance());
+ iter->second->DisownOpener();
+ }
+}
+
+void RenderViewHostManager::RendererAbortedProvisionalLoad(
+ RenderViewHost* render_view_host) {
+ // We used to cancel the pending renderer here for cross-site downloads.
+ // However, it's not safe to do that because the download logic repeatedly
+ // looks for this WebContents based on a render view ID. Instead, we just
+ // leave the pending renderer around until the next navigation event
+ // (Navigate, DidNavigate, etc), which will clean it up properly.
+ // TODO(creis): All of this will go away when we move the cross-site logic
+ // to ResourceDispatcherHost, so that we intercept responses rather than
+ // navigation events. (That's necessary to support onunload anyway.) Once
+ // we've made that change, we won't create a pending renderer until we know
+ // the response is not a download.
+}
+
+void RenderViewHostManager::RendererProcessClosing(
+ RenderProcessHost* render_process_host) {
+ // Remove any swapped out RVHs from this process, so that we don't try to
+ // swap them back in while the process is exiting. Start by finding them,
+ // since there could be more than one.
+ std::list<int> ids_to_remove;
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ if (iter->second->GetProcess() == render_process_host)
+ ids_to_remove.push_back(iter->first);
+ }
+
+ // Now delete them.
+ while (!ids_to_remove.empty()) {
+ swapped_out_hosts_[ids_to_remove.back()]->Shutdown();
+ swapped_out_hosts_.erase(ids_to_remove.back());
+ ids_to_remove.pop_back();
+ }
+}
+
+void RenderViewHostManager::ShouldClosePage(
+ bool for_cross_site_transition,
+ bool proceed,
+ const base::TimeTicks& proceed_time) {
+ if (for_cross_site_transition) {
+ // Ignore if we're not in a cross-site navigation.
+ if (!cross_navigation_pending_)
+ return;
+
+ if (proceed) {
+ // Ok to unload the current page, so proceed with the cross-site
+ // navigation. Note that if navigations are not currently suspended, it
+ // might be because the renderer was deemed unresponsive and this call was
+ // already made by ShouldCloseTabOnUnresponsiveRenderer. In that case, it
+ // is ok to do nothing here.
+ if (pending_render_view_host_ &&
+ pending_render_view_host_->are_navigations_suspended()) {
+ pending_render_view_host_->SetNavigationsSuspended(false, proceed_time);
+ }
+ } else {
+ // Current page says to cancel.
+ CancelPending();
+ cross_navigation_pending_ = false;
+ }
+ } else {
+ // Non-cross site transition means closing the entire tab.
+ bool proceed_to_fire_unload;
+ delegate_->BeforeUnloadFiredFromRenderManager(proceed, proceed_time,
+ &proceed_to_fire_unload);
+
+ if (proceed_to_fire_unload) {
+ // If we're about to close the tab and there's a pending RVH, cancel it.
+ // Otherwise, if the navigation in the pending RVH completes before the
+ // close in the current RVH, we'll lose the tab close.
+ if (pending_render_view_host_) {
+ CancelPending();
+ cross_navigation_pending_ = false;
+ }
+
+ // This is not a cross-site navigation, the tab is being closed.
+ render_view_host_->ClosePage();
+ }
+ }
+}
+
+void RenderViewHostManager::OnCrossSiteResponse(
+ RenderViewHost* pending_render_view_host,
+ const GlobalRequestID& global_request_id,
+ bool is_transfer,
+ const std::vector<GURL>& transfer_url_chain,
+ const Referrer& referrer,
+ PageTransition page_transition,
+ int64 frame_id) {
+ // This should be called either when the pending RVH is ready to commit or
+ // when we realize that the current RVH's request requires a transfer.
+ DCHECK(
+ pending_render_view_host == pending_render_view_host_ ||
+ pending_render_view_host == render_view_host_);
+
+ // TODO(creis): Eventually we will want to check all navigation responses
+ // here, but currently we pass information for a transfer if
+ // ShouldSwapProcessesForRedirect returned true in the network stack.
+ // In that case, we should set up a transfer after the unload handler runs.
+ // If is_transfer is false, we will just run the unload handler and resume.
+ pending_nav_params_.reset(new PendingNavigationParams(
+ global_request_id, is_transfer, transfer_url_chain, referrer,
+ page_transition, frame_id));
+
+ // Run the unload handler of the current page.
+ SwapOutOldPage();
+}
+
+void RenderViewHostManager::SwapOutOldPage() {
+ // Should only see this while we have a pending renderer or transfer.
+ CHECK(cross_navigation_pending_ || pending_nav_params_.get());
+
+ // First close any modal dialogs that would prevent us from swapping out.
+ // TODO(creis): This is not a guarantee. The renderer could immediately
+ // create another dialog in a loop, potentially causing a renderer crash when
+ // we tell it to swap out with a nested message loop and PageGroupLoadDeferrer
+ // on the stack. We should prevent the renderer from showing more dialogs
+ // until the SwapOut. See http://crbug.com/312490.
+ delegate_->CancelModalDialogsForRenderManager();
+
+ // Tell the old renderer it is being swapped out. This will fire the unload
+ // handler (without firing the beforeunload handler a second time). When the
+ // unload handler finishes and the navigation completes, we will send a
+ // message to the ResourceDispatcherHost, allowing the pending RVH's response
+ // to resume.
+ render_view_host_->SwapOut();
+
+ // ResourceDispatcherHost has told us to run the onunload handler, which
+ // means it is not a download or unsafe page, and we are going to perform the
+ // navigation. Thus, we no longer need to remember that the RenderViewHost
+ // is part of a pending cross-site request.
+ if (pending_render_view_host_)
+ pending_render_view_host_->SetHasPendingCrossSiteRequest(false);
+}
+
+void RenderViewHostManager::Observe(
+ int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFICATION_RENDERER_PROCESS_CLOSED:
+ case NOTIFICATION_RENDERER_PROCESS_CLOSING:
+ RendererProcessClosing(
+ Source<RenderProcessHost>(source).ptr());
+ break;
+
+ default:
+ NOTREACHED();
+ }
+}
+
+bool RenderViewHostManager::ShouldTransitionCrossSite() {
+ // False in the single-process mode, as it makes RVHs to accumulate
+ // in swapped_out_hosts_.
+ // True if we are using process-per-site-instance (default) or
+ // process-per-site (kProcessPerSite).
+ return
+ !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab);
+}
+
+bool RenderViewHostManager::ShouldSwapProcessesForNavigation(
+ const NavigationEntry* curr_entry,
+ const NavigationEntryImpl* new_entry) const {
+ DCHECK(new_entry);
+
+ // Check for reasons to swap processes even if we are in a process model that
+ // doesn't usually swap (e.g., process-per-tab).
+
+ // For security, we should transition between processes when one is a Web UI
+ // page and one isn't. If there's no curr_entry, check the current RVH's
+ // site, which might already be committed to a Web UI URL (such as the NTP).
+ const GURL& current_url = (curr_entry) ? curr_entry->GetURL() :
+ render_view_host_->GetSiteInstance()->GetSiteURL();
+ BrowserContext* browser_context =
+ delegate_->GetControllerForRenderManager().GetBrowserContext();
+ if (WebUIControllerFactoryRegistry::GetInstance()->UseWebUIForURL(
+ browser_context, current_url)) {
+ // Force swap if it's not an acceptable URL for Web UI.
+ // Here, data URLs are never allowed.
+ if (!WebUIControllerFactoryRegistry::GetInstance()->IsURLAcceptableForWebUI(
+ browser_context, new_entry->GetURL(), false)) {
+ return true;
+ }
+ } else {
+ // Force swap if it's a Web UI URL.
+ if (WebUIControllerFactoryRegistry::GetInstance()->UseWebUIForURL(
+ browser_context, new_entry->GetURL())) {
+ return true;
+ }
+ }
+
+ if (GetContentClient()->browser()->ShouldSwapProcessesForNavigation(
+ render_view_host_->GetSiteInstance(),
+ curr_entry ? curr_entry->GetURL() : GURL(),
+ new_entry->GetURL())) {
+ return true;
+ }
+
+ if (!curr_entry)
+ return false;
+
+ // We can't switch a RenderView between view source and non-view source mode
+ // without screwing up the session history sometimes (when navigating between
+ // "view-source:http://foo.com/" and "http://foo.com/", WebKit doesn't treat
+ // it as a new navigation). So require a view switch.
+ if (curr_entry->IsViewSourceMode() != new_entry->IsViewSourceMode())
+ return true;
+
+ return false;
+}
+
+bool RenderViewHostManager::ShouldReuseWebUI(
+ const NavigationEntry* curr_entry,
+ const NavigationEntryImpl* new_entry) const {
+ NavigationControllerImpl& controller =
+ delegate_->GetControllerForRenderManager();
+ return curr_entry && web_ui_.get() &&
+ (WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType(
+ controller.GetBrowserContext(), curr_entry->GetURL()) ==
+ WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType(
+ controller.GetBrowserContext(), new_entry->GetURL()));
+}
+
+SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry(
+ const NavigationEntryImpl& entry,
+ SiteInstance* curr_instance) {
+ // NOTE: This is only called when ShouldTransitionCrossSite is true.
+
+ const GURL& dest_url = entry.GetURL();
+ NavigationControllerImpl& controller =
+ delegate_->GetControllerForRenderManager();
+ BrowserContext* browser_context = controller.GetBrowserContext();
+
+ // If the entry has an instance already we should use it.
+ if (entry.site_instance())
+ return entry.site_instance();
+
+ // (UGLY) HEURISTIC, process-per-site only:
+ //
+ // If this navigation is generated, then it probably corresponds to a search
+ // query. Given that search results typically lead to users navigating to
+ // other sites, we don't really want to use the search engine hostname to
+ // determine the site instance for this navigation.
+ //
+ // NOTE: This can be removed once we have a way to transition between
+ // RenderViews in response to a link click.
+ //
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerSite) &&
+ PageTransitionCoreTypeIs(entry.GetTransitionType(),
+ PAGE_TRANSITION_GENERATED)) {
+ return curr_instance;
+ }
+
+ SiteInstanceImpl* curr_site_instance =
+ static_cast<SiteInstanceImpl*>(curr_instance);
+
+ // If we haven't used our SiteInstance (and thus RVH) yet, then we can use it
+ // for this entry. We won't commit the SiteInstance to this site until the
+ // navigation commits (in DidNavigate), unless the navigation entry was
+ // restored or it's a Web UI as described below.
+ if (!curr_site_instance->HasSite()) {
+ // If we've already created a SiteInstance for our destination, we don't
+ // want to use this unused SiteInstance; use the existing one. (We don't
+ // do this check if the curr_instance has a site, because for now, we want
+ // to compare against the current URL and not the SiteInstance's site. In
+ // this case, there is no current URL, so comparing against the site is ok.
+ // See additional comments below.)
+ //
+ // Also, if the URL should use process-per-site mode and there is an
+ // existing process for the site, we should use it. We can call
+ // GetRelatedSiteInstance() for this, which will eagerly set the site and
+ // thus use the correct process.
+ bool use_process_per_site =
+ RenderProcessHost::ShouldUseProcessPerSite(browser_context, dest_url) &&
+ RenderProcessHostImpl::GetProcessHostForSite(browser_context, dest_url);
+ if (curr_site_instance->HasRelatedSiteInstance(dest_url) ||
+ use_process_per_site) {
+ return curr_site_instance->GetRelatedSiteInstance(dest_url);
+ }
+
+ // For extensions, Web UI URLs (such as the new tab page), and apps we do
+ // not want to use the curr_instance if it has no site, since it will have a
+ // RenderProcessHost of PRIV_NORMAL. Create a new SiteInstance for this
+ // URL instead (with the correct process type).
+ if (curr_site_instance->HasWrongProcessForURL(dest_url))
+ return curr_site_instance->GetRelatedSiteInstance(dest_url);
+
+ // View-source URLs must use a new SiteInstance and BrowsingInstance.
+ // TODO(nasko): This is the same condition as later in the function. This
+ // should be taken into account when refactoring this method as part of
+ // http://crbug.com/123007.
+ if (entry.IsViewSourceMode())
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+
+ // If we are navigating from a blank SiteInstance to a WebUI, make sure we
+ // create a new SiteInstance.
+ if (WebUIControllerFactoryRegistry::GetInstance()->UseWebUIForURL(
+ browser_context, dest_url)) {
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+ }
+
+ // Normally the "site" on the SiteInstance is set lazily when the load
+ // actually commits. This is to support better process sharing in case
+ // the site redirects to some other site: we want to use the destination
+ // site in the site instance.
+ //
+ // In the case of session restore, as it loads all the pages immediately
+ // we need to set the site first, otherwise after a restore none of the
+ // pages would share renderers in process-per-site.
+ if (entry.restore_type() != NavigationEntryImpl::RESTORE_NONE)
+ curr_site_instance->SetSite(dest_url);
+
+ return curr_site_instance;
+ }
+
+ // Otherwise, only create a new SiteInstance for cross-site navigation.
+
+ // TODO(creis): Once we intercept links and script-based navigations, we
+ // will be able to enforce that all entries in a SiteInstance actually have
+ // the same site, and it will be safe to compare the URL against the
+ // SiteInstance's site, as follows:
+ // const GURL& current_url = curr_instance->site();
+ // For now, though, we're in a hybrid model where you only switch
+ // SiteInstances if you type in a cross-site URL. This means we have to
+ // compare the entry's URL to the last committed entry's URL.
+ NavigationEntry* curr_entry = controller.GetLastCommittedEntry();
+ if (interstitial_page_) {
+ // The interstitial is currently the last committed entry, but we want to
+ // compare against the last non-interstitial entry.
+ curr_entry = controller.GetEntryAtOffset(-1);
+ }
+ // If there is no last non-interstitial entry (and curr_instance already
+ // has a site), then we must have been opened from another tab. We want
+ // to compare against the URL of the page that opened us, but we can't
+ // get to it directly. The best we can do is check against the site of
+ // the SiteInstance. This will be correct when we intercept links and
+ // script-based navigations, but for now, it could place some pages in a
+ // new process unnecessarily. We should only hit this case if a page tries
+ // to open a new tab to an interstitial-inducing URL, and then navigates
+ // the page to a different same-site URL. (This seems very unlikely in
+ // practice.)
+ const GURL& current_url = (curr_entry) ? curr_entry->GetURL() :
+ curr_instance->GetSiteURL();
+
+ // View-source URLs must use a new SiteInstance and BrowsingInstance.
+ // TODO(creis): Refactor this method so this duplicated code isn't needed.
+ // See http://crbug.com/123007.
+ if (curr_entry &&
+ curr_entry->IsViewSourceMode() != entry.IsViewSourceMode()) {
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+ }
+
+ // Use the current SiteInstance for same site navigations, as long as the
+ // process type is correct. (The URL may have been installed as an app since
+ // the last time we visited it.)
+ if (SiteInstance::IsSameWebSite(browser_context, current_url, dest_url) &&
+ !static_cast<SiteInstanceImpl*>(curr_instance)->HasWrongProcessForURL(
+ dest_url)) {
+ return curr_instance;
+ } else if (ShouldSwapProcessesForNavigation(curr_entry, &entry)) {
+ // When we're swapping, we need to force the site instance AND browsing
+ // instance to be different ones. This addresses special cases where we use
+ // a single BrowsingInstance for all pages of a certain type (e.g., New Tab
+ // Pages), keeping them in the same process. When you navigate away from
+ // that page, we want to explicity ignore that BrowsingInstance and group
+ // this page into the appropriate SiteInstance for its URL.
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+ } else {
+ // Start the new renderer in a new SiteInstance, but in the current
+ // BrowsingInstance. It is important to immediately give this new
+ // SiteInstance to a RenderViewHost (if it is different than our current
+ // SiteInstance), so that it is ref counted. This will happen in
+ // CreateRenderView.
+ return curr_instance->GetRelatedSiteInstance(dest_url);
+ }
+}
+
+int RenderViewHostManager::CreateRenderView(
+ SiteInstance* instance,
+ int opener_route_id,
+ bool swapped_out,
+ bool hidden) {
+ CHECK(instance);
+ DCHECK(!swapped_out || hidden); // Swapped out views should always be hidden.
+
+ // Check if we've already created an RVH for this SiteInstance. If so, try
+ // to re-use the existing one, which has already been initialized. We'll
+ // remove it from the list of swapped out hosts if it commits.
+ RenderViewHostImpl* new_render_view_host = static_cast<RenderViewHostImpl*>(
+ GetSwappedOutRenderViewHost(instance));
+ if (new_render_view_host) {
+ // Prevent the process from exiting while we're trying to use it.
+ if (!swapped_out)
+ new_render_view_host->GetProcess()->AddPendingView();
+ } else {
+ // Create a new RenderViewHost if we don't find an existing one.
+ new_render_view_host = static_cast<RenderViewHostImpl*>(
+ RenderViewHostFactory::Create(instance,
+ render_view_delegate_,
+ render_widget_delegate_,
+ MSG_ROUTING_NONE,
+ MSG_ROUTING_NONE,
+ swapped_out,
+ hidden));
+
+ // If the new RVH is swapped out already, store it. Otherwise prevent the
+ // process from exiting while we're trying to navigate in it.
+ if (swapped_out) {
+ swapped_out_hosts_[instance->GetId()] = new_render_view_host;
+ } else {
+ new_render_view_host->GetProcess()->AddPendingView();
+ }
+
+ bool success = InitRenderView(new_render_view_host, opener_route_id);
+ if (success) {
+ // Don't show the view until we get a DidNavigate from it.
+ new_render_view_host->GetView()->Hide();
+ } else if (!swapped_out) {
+ CancelPending();
+ }
+ }
+
+ // Use this as our new pending RVH if it isn't swapped out.
+ if (!swapped_out)
+ pending_render_view_host_ = new_render_view_host;
+
+ return new_render_view_host->GetRoutingID();
+}
+
+bool RenderViewHostManager::InitRenderView(RenderViewHost* render_view_host,
+ int opener_route_id) {
+ // If the pending navigation is to a WebUI and the RenderView is not in a
+ // guest process, tell the RenderView about any bindings it will need enabled.
+ if (pending_web_ui() && !render_view_host->GetProcess()->IsGuest())
+ render_view_host->AllowBindings(pending_web_ui()->GetBindings());
+
+ return delegate_->CreateRenderViewForRenderManager(render_view_host,
+ opener_route_id);
+}
+
+void RenderViewHostManager::CommitPending() {
+ // First check whether we're going to want to focus the location bar after
+ // this commit. We do this now because the navigation hasn't formally
+ // committed yet, so if we've already cleared |pending_web_ui_| the call chain
+ // this triggers won't be able to figure out what's going on.
+ bool will_focus_location_bar = delegate_->FocusLocationBarByDefault();
+
+ // We currently can't guarantee that the renderer isn't showing a new modal
+ // dialog, even though we canceled them in SwapOutOldPage. (It may have
+ // created another in the meantime.) Make sure we run and reset the callback
+ // now before we delete its RVH below.
+ // TODO(creis): Remove this if we can guarantee that no new dialogs will be
+ // shown after SwapOutOldPage. See http://crbug.com/312490.
+ delegate_->CancelModalDialogsForRenderManager();
+
+ // Next commit the Web UI, if any. Either replace |web_ui_| with
+ // |pending_web_ui_|, or clear |web_ui_| if there is no pending WebUI, or
+ // leave |web_ui_| as is if reusing it.
+ DCHECK(!(pending_web_ui_.get() && pending_and_current_web_ui_.get()));
+ if (pending_web_ui_)
+ web_ui_.reset(pending_web_ui_.release());
+ else if (!pending_and_current_web_ui_.get())
+ web_ui_.reset();
+
+ // It's possible for the pending_render_view_host_ to be NULL when we aren't
+ // crossing process boundaries. If so, we just needed to handle the Web UI
+ // committing above and we're done.
+ if (!pending_render_view_host_) {
+ if (will_focus_location_bar)
+ delegate_->SetFocusToLocationBar(false);
+ return;
+ }
+
+ // Remember if the page was focused so we can focus the new renderer in
+ // that case.
+ bool focus_render_view = !will_focus_location_bar &&
+ render_view_host_->GetView() && render_view_host_->GetView()->HasFocus();
+
+ // Swap in the pending view and make it active. Also ensure the FrameTree
+ // stays in sync.
+ RenderViewHostImpl* old_render_view_host = render_view_host_;
+ render_view_host_ = pending_render_view_host_;
+ pending_render_view_host_ = NULL;
+ render_view_host_->AttachToFrameTree();
+
+ // The process will no longer try to exit, so we can decrement the count.
+ render_view_host_->GetProcess()->RemovePendingView();
+
+ // If the view is gone, then this RenderViewHost died while it was hidden.
+ // We ignored the RenderProcessGone call at the time, so we should send it now
+ // to make sure the sad tab shows up, etc.
+ if (!render_view_host_->GetView())
+ delegate_->RenderProcessGoneFromRenderManager(render_view_host_);
+ else if (!delegate_->IsHidden())
+ render_view_host_->GetView()->Show();
+
+ // Hide the old view now that the new one is visible.
+ if (old_render_view_host->GetView()) {
+ old_render_view_host->GetView()->Hide();
+ old_render_view_host->WasSwappedOut();
+ }
+
+ // Make sure the size is up to date. (Fix for bug 1079768.)
+ delegate_->UpdateRenderViewSizeForRenderManager();
+
+ if (will_focus_location_bar)
+ delegate_->SetFocusToLocationBar(false);
+ else if (focus_render_view && render_view_host_->GetView())
+ RenderWidgetHostViewPort::FromRWHV(render_view_host_->GetView())->Focus();
+
+ // Notify that we've swapped RenderViewHosts. We do this
+ // before shutting down the RVH so that we can clean up
+ // RendererResources related to the RVH first.
+ delegate_->NotifySwappedFromRenderManager(old_render_view_host,
+ render_view_host_);
+
+ // If the pending view was on the swapped out list, we can remove it.
+ swapped_out_hosts_.erase(render_view_host_->GetSiteInstance()->GetId());
+
+ // If there are no active RVHs in this SiteInstance, it means that
+ // this RVH was the last active one in the SiteInstance. Now that we
+ // know that all RVHs are swapped out, we can delete all the RVHs in
+ // this SiteInstance.
+ if (!static_cast<SiteInstanceImpl*>(old_render_view_host->GetSiteInstance())->
+ active_view_count()) {
+ ShutdownRenderViewHostsInSiteInstance(
+ old_render_view_host->GetSiteInstance()->GetId());
+ // This is deleted while cleaning up the SitaInstance's views.
+ old_render_view_host = NULL;
+ } else if (old_render_view_host->IsRenderViewLive()) {
+ // If the old RVH is live, we are swapping it out and should keep track of
+ // it in case we navigate back to it.
+ DCHECK(old_render_view_host->is_swapped_out());
+ // Temp fix for http://crbug.com/90867 until we do a better cleanup to make
+ // sure we don't get different rvh instances for the same site instance
+ // in the same rvhmgr.
+ // TODO(creis): Clean this up.
+ int32 old_site_instance_id =
+ old_render_view_host->GetSiteInstance()->GetId();
+ RenderViewHostMap::iterator iter =
+ swapped_out_hosts_.find(old_site_instance_id);
+ if (iter != swapped_out_hosts_.end() &&
+ iter->second != old_render_view_host) {
+ // Shutdown the RVH that will be replaced in the map to avoid a leak.
+ iter->second->Shutdown();
+ }
+ swapped_out_hosts_[old_site_instance_id] = old_render_view_host;
+ } else {
+ old_render_view_host->Shutdown();
+ old_render_view_host = NULL; // Shutdown() deletes it.
+ }
+}
+
+void RenderViewHostManager::ShutdownRenderViewHostsInSiteInstance(
+ int32 site_instance_id) {
+ // First remove any swapped out RVH for this SiteInstance from our
+ // list.
+ swapped_out_hosts_.erase(site_instance_id);
+
+ scoped_ptr<RenderWidgetHostIterator> widgets(
+ RenderWidgetHostImpl::GetAllRenderWidgetHosts());
+ while (RenderWidgetHost* widget = widgets->GetNextHost()) {
+ if (!widget->IsRenderView())
+ continue;
+ RenderViewHostImpl* rvh =
+ static_cast<RenderViewHostImpl*>(RenderViewHost::From(widget));
+ if (site_instance_id == rvh->GetSiteInstance()->GetId())
+ rvh->Shutdown();
+ }
+}
+
+RenderViewHostImpl* RenderViewHostManager::UpdateRendererStateForNavigate(
+ const NavigationEntryImpl& entry) {
+ // If we are cross-navigating, then we want to get back to normal and navigate
+ // as usual.
+ if (cross_navigation_pending_) {
+ if (pending_render_view_host_)
+ CancelPending();
+ cross_navigation_pending_ = false;
+ }
+
+ // render_view_host_ will not be deleted before the end of this method, so we
+ // don't have to worry about this SiteInstance's ref count dropping to zero.
+ SiteInstance* curr_instance = render_view_host_->GetSiteInstance();
+
+ // Determine if we need a new SiteInstance for this entry.
+ // Again, new_instance won't be deleted before the end of this method, so it
+ // is safe to use a normal pointer here.
+ SiteInstance* new_instance = curr_instance;
+ const NavigationEntry* curr_entry =
+ delegate_->GetLastCommittedNavigationEntryForRenderManager();
+ bool is_guest_scheme = curr_instance->GetSiteURL().SchemeIs(kGuestScheme);
+ bool force_swap = ShouldSwapProcessesForNavigation(curr_entry, &entry);
+ if (!is_guest_scheme && (ShouldTransitionCrossSite() || force_swap))
+ new_instance = GetSiteInstanceForEntry(entry, curr_instance);
+
+ if (!is_guest_scheme && (new_instance != curr_instance || force_swap)) {
+ // New SiteInstance.
+ DCHECK(!cross_navigation_pending_);
+
+ // This will possibly create (set to NULL) a Web UI object for the pending
+ // page. We'll use this later to give the page special access. This must
+ // happen before the new renderer is created below so it will get bindings.
+ // It must also happen after the above conditional call to CancelPending(),
+ // otherwise CancelPending may clear the pending_web_ui_ and the page will
+ // not have its bindings set appropriately.
+ SetPendingWebUI(entry);
+
+ // Ensure that we have created RVHs for the new RVH's opener chain if
+ // we are staying in the same BrowsingInstance. This allows the pending RVH
+ // to send cross-process script calls to its opener(s).
+ int opener_route_id = MSG_ROUTING_NONE;
+ if (new_instance->IsRelatedSiteInstance(curr_instance)) {
+ opener_route_id =
+ delegate_->CreateOpenerRenderViewsForRenderManager(new_instance);
+ }
+
+ // Create a non-swapped-out pending RVH with the given opener and navigate
+ // it.
+ int route_id = CreateRenderView(new_instance, opener_route_id, false,
+ delegate_->IsHidden());
+ if (route_id == MSG_ROUTING_NONE)
+ return NULL;
+
+ // Check if our current RVH is live before we set up a transition.
+ if (!render_view_host_->IsRenderViewLive()) {
+ if (!cross_navigation_pending_) {
+ // The current RVH is not live. There's no reason to sit around with a
+ // sad tab or a newly created RVH while we wait for the pending RVH to
+ // navigate. Just switch to the pending RVH now and go back to non
+ // cross-navigating (Note that we don't care about on{before}unload
+ // handlers if the current RVH isn't live.)
+ CommitPending();
+ return render_view_host_;
+ } else {
+ NOTREACHED();
+ return render_view_host_;
+ }
+ }
+ // Otherwise, it's safe to treat this as a pending cross-site transition.
+
+ // We need to wait until the beforeunload handler has run, unless we are
+ // transferring an existing request (in which case it has already run).
+ // Suspend the new render view (i.e., don't let it send the cross-site
+ // Navigate message) until we hear back from the old renderer's
+ // beforeunload handler. If the handler returns false, we'll have to
+ // cancel the request.
+ DCHECK(!pending_render_view_host_->are_navigations_suspended());
+ bool is_transfer =
+ entry.transferred_global_request_id() != GlobalRequestID();
+ if (is_transfer) {
+ // We don't need to stop the old renderer or run beforeunload/unload
+ // handlers, because those have already been done.
+ DCHECK(pending_nav_params_->global_request_id ==
+ entry.transferred_global_request_id());
+ } else {
+ // Also make sure the old render view stops, in case a load is in
+ // progress. (We don't want to do this for transfers, since it will
+ // interrupt the transfer with an unexpected DidStopLoading.)
+ render_view_host_->Send(
+ new ViewMsg_Stop(render_view_host_->GetRoutingID()));
+
+ pending_render_view_host_->SetNavigationsSuspended(true,
+ base::TimeTicks());
+
+ // Tell the CrossSiteRequestManager that this RVH has a pending cross-site
+ // request, so that ResourceDispatcherHost will know to tell us to run the
+ // old page's unload handler before it sends the response.
+ pending_render_view_host_->SetHasPendingCrossSiteRequest(true);
+ }
+
+ // We now have a pending RVH.
+ DCHECK(!cross_navigation_pending_);
+ cross_navigation_pending_ = true;
+
+ // Unless we are transferring an existing request, we should now
+ // tell the old render view to run its beforeunload handler, since it
+ // doesn't otherwise know that the cross-site request is happening. This
+ // will trigger a call to ShouldClosePage with the reply.
+ if (!is_transfer)
+ render_view_host_->FirePageBeforeUnload(true);
+
+ return pending_render_view_host_;
+ } else {
+ if (ShouldReuseWebUI(curr_entry, &entry)) {
+ pending_web_ui_.reset();
+ pending_and_current_web_ui_ = web_ui_->AsWeakPtr();
+ } else {
+ SetPendingWebUI(entry);
+
+ // Make sure the new RenderViewHost has the right bindings.
+ if (pending_web_ui() && !render_view_host_->GetProcess()->IsGuest())
+ render_view_host_->AllowBindings(pending_web_ui()->GetBindings());
+ }
+
+ if (pending_web_ui() && render_view_host_->IsRenderViewLive())
+ pending_web_ui()->GetController()->RenderViewReused(render_view_host_);
+
+ // The renderer can exit view source mode when any error or cancellation
+ // happen. We must overwrite to recover the mode.
+ if (entry.IsViewSourceMode()) {
+ render_view_host_->Send(
+ new ViewMsg_EnableViewSourceMode(render_view_host_->GetRoutingID()));
+ }
+ }
+
+ // Same SiteInstance can be used. Navigate render_view_host_ if we are not
+ // cross navigating.
+ DCHECK(!cross_navigation_pending_);
+ return render_view_host_;
+}
+
+void RenderViewHostManager::CancelPending() {
+ RenderViewHostImpl* pending_render_view_host = pending_render_view_host_;
+ pending_render_view_host_ = NULL;
+
+ RenderViewDevToolsAgentHost::OnCancelPendingNavigation(
+ pending_render_view_host,
+ render_view_host_);
+
+ // We no longer need to prevent the process from exiting.
+ pending_render_view_host->GetProcess()->RemovePendingView();
+
+ // The pending RVH may already be on the swapped out list if we started to
+ // swap it back in and then canceled. If so, make sure it gets swapped out
+ // again. If it's not on the swapped out list (e.g., aborting a pending
+ // load), then it's safe to shut down.
+ if (IsOnSwappedOutList(pending_render_view_host)) {
+ // Any currently suspended navigations are no longer needed.
+ pending_render_view_host->CancelSuspendedNavigations();
+
+ pending_render_view_host->SwapOut();
+ } else {
+ // We won't be coming back, so shut this one down.
+ pending_render_view_host->Shutdown();
+ }
+
+ pending_web_ui_.reset();
+ pending_and_current_web_ui_.reset();
+}
+
+void RenderViewHostManager::RenderViewDeleted(RenderViewHost* rvh) {
+ // We are doing this in order to work around and to track a crasher
+ // (http://crbug.com/23411) where it seems that pending_render_view_host_ is
+ // deleted (not sure from where) but not NULLed.
+ if (rvh == pending_render_view_host_) {
+ // If you hit this NOTREACHED, please report it in the following bug
+ // http://crbug.com/23411 Make sure to include what you were doing when it
+ // happened (navigating to a new page, closing a tab...) and if you can
+ // reproduce.
+ NOTREACHED();
+ pending_render_view_host_ = NULL;
+ }
+
+ // Make sure deleted RVHs are not kept in the swapped out list while we are
+ // still alive. (If render_view_host_ is null, we're already being deleted.)
+ if (!render_view_host_)
+ return;
+ // We can't look it up by SiteInstance ID, which may no longer be valid.
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ if (iter->second == rvh) {
+ swapped_out_hosts_.erase(iter);
+ break;
+ }
+ }
+}
+
+bool RenderViewHostManager::IsOnSwappedOutList(RenderViewHost* rvh) const {
+ if (!rvh->GetSiteInstance())
+ return false;
+
+ RenderViewHostMap::const_iterator iter = swapped_out_hosts_.find(
+ rvh->GetSiteInstance()->GetId());
+ if (iter == swapped_out_hosts_.end())
+ return false;
+
+ return iter->second == rvh;
+}
+
+RenderViewHostImpl* RenderViewHostManager::GetSwappedOutRenderViewHost(
+ SiteInstance* instance) {
+ RenderViewHostMap::iterator iter = swapped_out_hosts_.find(instance->GetId());
+ if (iter != swapped_out_hosts_.end())
+ return iter->second;
+
+ return NULL;
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/render_view_host_manager.h b/content/browser/frame_host/render_view_host_manager.h
new file mode 100644
index 0000000..377758f
--- /dev/null
+++ b/content/browser/frame_host/render_view_host_manager.h
@@ -0,0 +1,397 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_RENDER_VIEW_HOST_MANAGER_H_
+#define CONTENT_BROWSER_FRAME_HOST_RENDER_VIEW_HOST_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/common/referrer.h"
+
+
+namespace content {
+class BrowserContext;
+class InterstitialPageImpl;
+class NavigationControllerImpl;
+class NavigationEntry;
+class NavigationEntryImpl;
+class RenderViewHost;
+class RenderViewHostImpl;
+class RenderViewHostManagerTest;
+class RenderWidgetHostDelegate;
+class RenderWidgetHostView;
+class TestWebContents;
+class WebUIImpl;
+
+// Manages RenderViewHosts for a WebContentsImpl. Normally there is only one and
+// it is easy to do. But we can also have transitions of processes (and hence
+// RenderViewHosts) that can get complex.
+class CONTENT_EXPORT RenderViewHostManager
+ : public RenderViewHostDelegate::RendererManagement,
+ public NotificationObserver {
+ public:
+ // Functions implemented by our owner that we need.
+ //
+ // TODO(brettw) Clean this up! These are all the functions in WebContentsImpl
+ // that are required to run this class. The design should probably be better
+ // such that these are more clear.
+ //
+ // There is additional complexity that some of the functions we need in
+ // WebContentsImpl are inherited and non-virtual. These are named with
+ // "RenderManager" so that the duplicate implementation of them will be clear.
+ class CONTENT_EXPORT Delegate {
+ public:
+ // Initializes the given renderer if necessary and creates the view ID
+ // corresponding to this view host. If this method is not called and the
+ // process is not shared, then the WebContentsImpl will act as though the
+ // renderer is not running (i.e., it will render "sad tab"). This method is
+ // automatically called from LoadURL.
+ //
+ // If you are attaching to an already-existing RenderView, you should call
+ // InitWithExistingID.
+ virtual bool CreateRenderViewForRenderManager(
+ RenderViewHost* render_view_host, int opener_route_id) = 0;
+ virtual void BeforeUnloadFiredFromRenderManager(
+ bool proceed, const base::TimeTicks& proceed_time,
+ bool* proceed_to_fire_unload) = 0;
+ virtual void RenderProcessGoneFromRenderManager(
+ RenderViewHost* render_view_host) = 0;
+ virtual void UpdateRenderViewSizeForRenderManager() = 0;
+ virtual void CancelModalDialogsForRenderManager() = 0;
+ virtual void NotifySwappedFromRenderManager(
+ RenderViewHost* old_host, RenderViewHost* new_host) = 0;
+ virtual NavigationControllerImpl&
+ GetControllerForRenderManager() = 0;
+
+ // Create swapped out RenderViews in the given SiteInstance for each tab in
+ // the opener chain of this tab, if any. This allows the current tab to
+ // make cross-process script calls to its opener(s). Returns the route ID
+ // of the immediate opener, if one exists (otherwise MSG_ROUTING_NONE).
+ virtual int CreateOpenerRenderViewsForRenderManager(
+ SiteInstance* instance) = 0;
+
+ // Creates a WebUI object for the given URL if one applies. Ownership of the
+ // returned pointer will be passed to the caller. If no WebUI applies,
+ // returns NULL.
+ virtual WebUIImpl* CreateWebUIForRenderManager(const GURL& url) = 0;
+
+ // Returns the navigation entry of the current navigation, or NULL if there
+ // is none.
+ virtual NavigationEntry*
+ GetLastCommittedNavigationEntryForRenderManager() = 0;
+
+ // Returns true if the location bar should be focused by default rather than
+ // the page contents. The view calls this function when the tab is focused
+ // to see what it should do.
+ virtual bool FocusLocationBarByDefault() = 0;
+
+ // Focuses the location bar.
+ virtual void SetFocusToLocationBar(bool select_all) = 0;
+
+ // Creates a view and sets the size for the specified RVH.
+ virtual void CreateViewAndSetSizeForRVH(RenderViewHost* rvh) = 0;
+
+ // Returns true if views created for this delegate should be created in a
+ // hidden state.
+ virtual bool IsHidden() = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // All three delegate pointers must be non-NULL and are not owned by this
+ // class. They must outlive this class. The RenderViewHostDelegate and
+ // RenderWidgetHostDelegate are what will be installed into all
+ // RenderViewHosts that are created.
+ //
+ // You must call Init() before using this class.
+ RenderViewHostManager(
+ RenderViewHostDelegate* render_view_delegate,
+ RenderWidgetHostDelegate* render_widget_delegate,
+ Delegate* delegate);
+ virtual ~RenderViewHostManager();
+
+ // For arguments, see WebContentsImpl constructor.
+ void Init(BrowserContext* browser_context,
+ SiteInstance* site_instance,
+ int routing_id,
+ int main_frame_routing_id);
+
+ // Returns the currently active RenderViewHost.
+ //
+ // This will be non-NULL between Init() and Shutdown(). You may want to NULL
+ // check it in many cases, however. Windows can send us messages during the
+ // destruction process after it has been shut down.
+ RenderViewHostImpl* current_host() const;
+
+ // Returns the view associated with the current RenderViewHost, or NULL if
+ // there is no current one.
+ RenderWidgetHostView* GetRenderWidgetHostView() const;
+
+ // Returns the pending render view host, or NULL if there is no pending one.
+ RenderViewHostImpl* pending_render_view_host() const;
+
+ // Returns the current committed Web UI or NULL if none applies.
+ WebUIImpl* web_ui() const { return web_ui_.get(); }
+
+ // Returns the Web UI for the pending navigation, or NULL of none applies.
+ WebUIImpl* pending_web_ui() const {
+ return pending_web_ui_.get() ? pending_web_ui_.get() :
+ pending_and_current_web_ui_.get();
+ }
+
+ // Sets the pending Web UI for the pending navigation, ensuring that the
+ // bindings are appropriate for the given NavigationEntry.
+ void SetPendingWebUI(const NavigationEntryImpl& entry);
+
+ // Called when we want to instruct the renderer to navigate to the given
+ // navigation entry. It may create a new RenderViewHost or re-use an existing
+ // one. The RenderViewHost to navigate will be returned. Returns NULL if one
+ // could not be created.
+ RenderViewHostImpl* Navigate(const NavigationEntryImpl& entry);
+
+ // Instructs the various live views to stop. Called when the user directed the
+ // page to stop loading.
+ void Stop();
+
+ // Notifies the regular and pending RenderViewHosts that a load is or is not
+ // happening. Even though the message is only for one of them, we don't know
+ // which one so we tell both.
+ void SetIsLoading(bool is_loading);
+
+ // Whether to close the tab or not when there is a hang during an unload
+ // handler. If we are mid-crosssite navigation, then we should proceed
+ // with the navigation instead of closing the tab.
+ bool ShouldCloseTabOnUnresponsiveRenderer();
+
+ // The RenderViewHost has been swapped out, so we should resume the pending
+ // network response and allow the pending RenderViewHost to commit.
+ void SwappedOut(RenderViewHost* render_view_host);
+
+ // Called when a renderer's main frame navigates.
+ void DidNavigateMainFrame(RenderViewHost* render_view_host);
+
+ // Called when a renderer sets its opener to null.
+ void DidDisownOpener(RenderViewHost* render_view_host);
+
+ // Helper method to create a RenderViewHost. If |swapped_out| is true, it
+ // will be initially placed on the swapped out hosts list. Otherwise, it
+ // will be used for a pending cross-site navigation.
+ int CreateRenderView(SiteInstance* instance,
+ int opener_route_id,
+ bool swapped_out,
+ bool hidden);
+
+ // Called when a provisional load on the given renderer is aborted.
+ void RendererAbortedProvisionalLoad(RenderViewHost* render_view_host);
+
+ // Sets the passed passed interstitial as the currently showing interstitial.
+ // |interstitial_page| should be non NULL (use the remove_interstitial_page
+ // method to unset the interstitial) and no interstitial page should be set
+ // when there is already a non NULL interstitial page set.
+ void set_interstitial_page(InterstitialPageImpl* interstitial_page) {
+ DCHECK(!interstitial_page_ && interstitial_page);
+ interstitial_page_ = interstitial_page;
+ }
+
+ // Unsets the currently showing interstitial.
+ void remove_interstitial_page() {
+ DCHECK(interstitial_page_);
+ interstitial_page_ = NULL;
+ }
+
+ // Returns the currently showing interstitial, NULL if no interstitial is
+ // showing.
+ InterstitialPageImpl* interstitial_page() const { return interstitial_page_; }
+
+ // RenderViewHostDelegate::RendererManagement implementation.
+ virtual void ShouldClosePage(
+ bool for_cross_site_transition,
+ bool proceed,
+ const base::TimeTicks& proceed_time) OVERRIDE;
+ virtual void OnCrossSiteResponse(
+ RenderViewHost* pending_render_view_host,
+ const GlobalRequestID& global_request_id,
+ bool is_transfer,
+ const std::vector<GURL>& transfer_url_chain,
+ const Referrer& referrer,
+ PageTransition page_transition,
+ int64 frame_id) OVERRIDE;
+
+ // NotificationObserver implementation.
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ // Called when a RenderViewHost is about to be deleted.
+ void RenderViewDeleted(RenderViewHost* rvh);
+
+ // Returns whether the given RenderViewHost is on the list of swapped out
+ // RenderViewHosts.
+ bool IsOnSwappedOutList(RenderViewHost* rvh) const;
+
+ // Returns the swapped out RenderViewHost for the given SiteInstance, if any.
+ RenderViewHostImpl* GetSwappedOutRenderViewHost(SiteInstance* instance);
+
+ // Runs the unload handler in the current page, when we know that a pending
+ // cross-process navigation is going to commit. We may initiate a transfer
+ // to a new process after this completes or times out.
+ void SwapOutOldPage();
+
+ private:
+ friend class RenderViewHostManagerTest;
+ friend class TestWebContents;
+
+ // Tracks information about a navigation while a cross-process transition is
+ // in progress, in case we need to transfer it to a new RenderViewHost.
+ struct PendingNavigationParams {
+ PendingNavigationParams();
+ PendingNavigationParams(const GlobalRequestID& global_request_id,
+ bool is_transfer,
+ const std::vector<GURL>& transfer_url,
+ Referrer referrer,
+ PageTransition page_transition,
+ int64 frame_id);
+ ~PendingNavigationParams();
+
+ // The child ID and request ID for the pending navigation. Present whether
+ // |is_transfer| is true or false.
+ GlobalRequestID global_request_id;
+
+ // Whether this pending navigation needs to be transferred to another
+ // process than the one it was going to commit in. If so, the
+ // |transfer_url|, |referrer|, and |frame_id| parameters will be set.
+ bool is_transfer;
+
+ // If |is_transfer|, this is the URL chain of the request. The first entry
+ // is the original request URL, and the last entry is the destination URL to
+ // request in the new process.
+ std::vector<GURL> transfer_url_chain;
+
+ // If |is_transfer|, this is the referrer to use for the request in the new
+ // process.
+ Referrer referrer;
+
+ // If |is_transfer|, this is the transition type for the original
+ // navigation.
+ PageTransition page_transition;
+
+ // If |is_transfer|, this is the frame ID to use in RequestTransferURL.
+ int64 frame_id;
+ };
+
+ // Returns whether this tab should transition to a new renderer for
+ // cross-site URLs. Enabled unless we see the --process-per-tab command line
+ // switch. Can be overridden in unit tests.
+ bool ShouldTransitionCrossSite();
+
+ // Returns true if the two navigation entries are incompatible in some way
+ // other than site instances. Cases where this can happen include Web UI
+ // to regular web pages. It will cause us to swap RenderViewHosts (and hence
+ // RenderProcessHosts) even if the site instance would otherwise be the same.
+ // As part of this, we'll also force new SiteInstances and BrowsingInstances.
+ // Either of the entries may be NULL.
+ bool ShouldSwapProcessesForNavigation(
+ const NavigationEntry* curr_entry,
+ const NavigationEntryImpl* new_entry) const;
+
+ bool ShouldReuseWebUI(
+ const NavigationEntry* curr_entry,
+ const NavigationEntryImpl* new_entry) const;
+
+ // Returns an appropriate SiteInstance object for the given NavigationEntry,
+ // possibly reusing the current SiteInstance.
+ // Never called if --process-per-tab is used.
+ SiteInstance* GetSiteInstanceForEntry(
+ const NavigationEntryImpl& entry,
+ SiteInstance* curr_instance);
+
+ // Sets up the necessary state for a new RenderViewHost with the given opener.
+ bool InitRenderView(RenderViewHost* render_view_host, int opener_route_id);
+
+ // Sets the pending RenderViewHost/WebUI to be the active one. Note that this
+ // doesn't require the pending render_view_host_ pointer to be non-NULL, since
+ // there could be Web UI switching as well. Call this for every commit.
+ void CommitPending();
+
+ // Shutdown all RenderViewHosts in a SiteInstance. This is called
+ // to shutdown views when all the views in a SiteInstance are
+ // confirmed to be swapped out.
+ void ShutdownRenderViewHostsInSiteInstance(int32 site_instance_id);
+
+ // Helper method to terminate the pending RenderViewHost.
+ void CancelPending();
+
+ RenderViewHostImpl* UpdateRendererStateForNavigate(
+ const NavigationEntryImpl& entry);
+
+ // Called when a renderer process is starting to close. We should not
+ // schedule new navigations in its swapped out RenderViewHosts after this.
+ void RendererProcessClosing(RenderProcessHost* render_process_host);
+
+ // Our delegate, not owned by us. Guaranteed non-NULL.
+ Delegate* delegate_;
+
+ // Whether a navigation requiring different RenderView's is pending. This is
+ // either cross-site request is (in the new process model), or when required
+ // for the view type (like view source versus not).
+ bool cross_navigation_pending_;
+
+ // Implemented by the owner of this class, these delegates are installed into
+ // all the RenderViewHosts that we create.
+ RenderViewHostDelegate* render_view_delegate_;
+ RenderWidgetHostDelegate* render_widget_delegate_;
+
+ // Our RenderView host and its associated Web UI (if any, will be NULL for
+ // non-DOM-UI pages). This object is responsible for all communication with
+ // a child RenderView instance.
+ RenderViewHostImpl* render_view_host_;
+ scoped_ptr<WebUIImpl> web_ui_;
+
+ // A RenderViewHost used to load a cross-site page. This remains hidden
+ // while a cross-site request is pending until it calls DidNavigate. It may
+ // have an associated Web UI, in which case the Web UI pointer will be non-
+ // NULL.
+ //
+ // The |pending_web_ui_| may be non-NULL even when the
+ // |pending_render_view_host_| is NULL. This will happen when we're
+ // transitioning between two Web UI pages: the RVH won't be swapped, so the
+ // pending pointer will be unused, but there will be a pending Web UI
+ // associated with the navigation.
+ RenderViewHostImpl* pending_render_view_host_;
+
+ // Tracks information about any current pending cross-process navigation.
+ scoped_ptr<PendingNavigationParams> pending_nav_params_;
+
+ // If either of these is non-NULL, the pending navigation is to a chrome:
+ // page. The scoped_ptr is used if pending_web_ui_ != web_ui_, the WeakPtr is
+ // used for when they reference the same object. If either is non-NULL, the
+ // other should be NULL.
+ scoped_ptr<WebUIImpl> pending_web_ui_;
+ base::WeakPtr<WebUIImpl> pending_and_current_web_ui_;
+
+ // A map of site instance ID to swapped out RenderViewHosts. This may include
+ // pending_render_view_host_ for navigations to existing entries.
+ typedef base::hash_map<int32, RenderViewHostImpl*> RenderViewHostMap;
+ RenderViewHostMap swapped_out_hosts_;
+
+ // The intersitial page currently shown if any, not own by this class
+ // (the InterstitialPage is self-owned, it deletes itself when hidden).
+ InterstitialPageImpl* interstitial_page_;
+
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_RENDER_VIEW_HOST_MANAGER_H_
diff --git a/content/browser/frame_host/render_view_host_manager_unittest.cc b/content/browser/frame_host/render_view_host_manager_unittest.cc
new file mode 100644
index 0000000..f55de8a
--- /dev/null
+++ b/content/browser/frame_host/render_view_host_manager_unittest.cc
@@ -0,0 +1,1312 @@
+// Copyright 2013 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.
+
+#include "base/strings/utf_string_conversions.h"
+#include "content/browser/frame_host/navigation_controller_impl.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
+#include "content/browser/frame_host/render_view_host_manager.h"
+#include "content/browser/renderer_host/test_render_view_host.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/common/view_messages.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_widget_host_iterator.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/common/bindings_policy.h"
+#include "content/public/common/javascript_message_type.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/common/url_utils.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_notification_tracker.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_content_client.h"
+#include "content/test/test_web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+class RenderViewHostManagerTestWebUIControllerFactory
+ : public WebUIControllerFactory {
+ public:
+ RenderViewHostManagerTestWebUIControllerFactory()
+ : should_create_webui_(false) {
+ }
+ virtual ~RenderViewHostManagerTestWebUIControllerFactory() {}
+
+ void set_should_create_webui(bool should_create_webui) {
+ should_create_webui_ = should_create_webui;
+ }
+
+ // WebUIFactory implementation.
+ virtual WebUIController* CreateWebUIControllerForURL(
+ WebUI* web_ui, const GURL& url) const OVERRIDE {
+ if (!(should_create_webui_ && HasWebUIScheme(url)))
+ return NULL;
+ return new WebUIController(web_ui);
+ }
+
+ virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return WebUI::kNoWebUI;
+ }
+
+ virtual bool UseWebUIForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return HasWebUIScheme(url);
+ }
+
+ virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context,
+ const GURL& url) const OVERRIDE {
+ return HasWebUIScheme(url);
+ }
+
+ private:
+ bool should_create_webui_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostManagerTestWebUIControllerFactory);
+};
+
+class BeforeUnloadFiredWebContentsDelegate : public WebContentsDelegate {
+ public:
+ BeforeUnloadFiredWebContentsDelegate() {}
+ virtual ~BeforeUnloadFiredWebContentsDelegate() {}
+
+ virtual void BeforeUnloadFired(WebContents* web_contents,
+ bool proceed,
+ bool* proceed_to_fire_unload) OVERRIDE {
+ *proceed_to_fire_unload = proceed;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BeforeUnloadFiredWebContentsDelegate);
+};
+
+} // namespace
+
+class RenderViewHostManagerTest
+ : public RenderViewHostImplTestHarness {
+ public:
+ virtual void SetUp() OVERRIDE {
+ RenderViewHostImplTestHarness::SetUp();
+ WebUIControllerFactory::RegisterFactory(&factory_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ RenderViewHostImplTestHarness::TearDown();
+ WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
+ }
+
+ void set_should_create_webui(bool should_create_webui) {
+ factory_.set_should_create_webui(should_create_webui);
+ }
+
+ void NavigateActiveAndCommit(const GURL& url) {
+ // Note: we navigate the active RenderViewHost because previous navigations
+ // won't have committed yet, so NavigateAndCommit does the wrong thing
+ // for us.
+ controller().LoadURL(url, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ TestRenderViewHost* old_rvh = test_rvh();
+
+ // Simulate the ShouldClose_ACK that is received from the current renderer
+ // for a cross-site navigation.
+ if (old_rvh != active_rvh())
+ old_rvh->SendShouldCloseACK(true);
+
+ // Commit the navigation with a new page ID.
+ int32 max_page_id = contents()->GetMaxPageIDForSiteInstance(
+ active_rvh()->GetSiteInstance());
+
+ // Simulate the SwapOut_ACK that fires if you commit a cross-site
+ // navigation.
+ if (old_rvh != active_rvh())
+ old_rvh->OnSwappedOut(false);
+
+ active_test_rvh()->SendNavigate(max_page_id + 1, url);
+ }
+
+ bool ShouldSwapProcesses(RenderViewHostManager* manager,
+ const NavigationEntryImpl* cur_entry,
+ const NavigationEntryImpl* new_entry) const {
+ return manager->ShouldSwapProcessesForNavigation(cur_entry, new_entry);
+ }
+
+ // Creates a test RenderViewHost that's swapped out.
+ TestRenderViewHost* CreateSwappedOutRenderViewHost() {
+ const GURL kChromeURL("chrome://foo");
+ const GURL kDestUrl("http://www.google.com/");
+
+ // Navigate our first tab to a chrome url and then to the destination.
+ NavigateActiveAndCommit(kChromeURL);
+ TestRenderViewHost* ntp_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->current_host());
+
+ // Navigate to a cross-site URL.
+ contents()->GetController().LoadURL(
+ kDestUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+
+ // Manually increase the number of active views in the
+ // SiteInstance that ntp_rvh belongs to, to prevent it from being
+ // destroyed when it gets swapped out.
+ static_cast<SiteInstanceImpl*>(ntp_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ TestRenderViewHost* dest_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->pending_render_view_host());
+ CHECK(dest_rvh);
+ EXPECT_NE(ntp_rvh, dest_rvh);
+
+ // BeforeUnload finishes.
+ ntp_rvh->SendShouldCloseACK(true);
+
+ // Assume SwapOutACK times out, so the dest_rvh proceeds and commits.
+ dest_rvh->SendNavigate(101, kDestUrl);
+
+ EXPECT_TRUE(ntp_rvh->is_swapped_out());
+ return ntp_rvh;
+ }
+
+ private:
+ RenderViewHostManagerTestWebUIControllerFactory factory_;
+};
+
+// Tests that when you navigate from a chrome:// url to another page, and
+// then do that same thing in another tab, that the two resulting pages have
+// different SiteInstances, BrowsingInstances, and RenderProcessHosts. This is
+// a regression test for bug 9364.
+TEST_F(RenderViewHostManagerTest, NewTabPageProcesses) {
+ set_should_create_webui(true);
+ const GURL kChromeUrl("chrome://foo");
+ const GURL kDestUrl("http://www.google.com/");
+
+ // Navigate our first tab to the chrome url and then to the destination,
+ // ensuring we grant bindings to the chrome URL.
+ NavigateActiveAndCommit(kChromeUrl);
+ EXPECT_TRUE(active_rvh()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+ NavigateActiveAndCommit(kDestUrl);
+
+ // Make a second tab.
+ scoped_ptr<TestWebContents> contents2(
+ TestWebContents::Create(browser_context(), NULL));
+
+ // Load the two URLs in the second tab. Note that the first navigation creates
+ // a RVH that's not pending (since there is no cross-site transition), so
+ // we use the committed one.
+ contents2->GetController().LoadURL(
+ kChromeUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ TestRenderViewHost* ntp_rvh2 = static_cast<TestRenderViewHost*>(
+ contents2->GetRenderManagerForTesting()->current_host());
+ EXPECT_FALSE(contents2->cross_navigation_pending());
+ ntp_rvh2->SendNavigate(100, kChromeUrl);
+
+ // The second one is the opposite, creating a cross-site transition and
+ // requiring a beforeunload ack.
+ contents2->GetController().LoadURL(
+ kDestUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ EXPECT_TRUE(contents2->cross_navigation_pending());
+ TestRenderViewHost* dest_rvh2 = static_cast<TestRenderViewHost*>(
+ contents2->GetRenderManagerForTesting()->pending_render_view_host());
+ ASSERT_TRUE(dest_rvh2);
+
+ ntp_rvh2->SendShouldCloseACK(true);
+ ntp_rvh2->OnSwappedOut(false);
+ dest_rvh2->SendNavigate(101, kDestUrl);
+
+ // The two RVH's should be different in every way.
+ EXPECT_NE(active_rvh()->GetProcess(), dest_rvh2->GetProcess());
+ EXPECT_NE(active_rvh()->GetSiteInstance(), dest_rvh2->GetSiteInstance());
+ EXPECT_FALSE(active_rvh()->GetSiteInstance()->IsRelatedSiteInstance(
+ dest_rvh2->GetSiteInstance()));
+
+ // Navigate both to the new tab page, and verify that they share a
+ // RenderProcessHost (not a SiteInstance).
+ NavigateActiveAndCommit(kChromeUrl);
+
+ contents2->GetController().LoadURL(
+ kChromeUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ dest_rvh2->SendShouldCloseACK(true);
+ dest_rvh2->OnSwappedOut(false);
+ static_cast<TestRenderViewHost*>(contents2->GetRenderManagerForTesting()->
+ pending_render_view_host())->SendNavigate(102, kChromeUrl);
+
+ EXPECT_NE(active_rvh()->GetSiteInstance(),
+ contents2->GetRenderViewHost()->GetSiteInstance());
+ EXPECT_EQ(active_rvh()->GetSiteInstance()->GetProcess(),
+ contents2->GetRenderViewHost()->GetSiteInstance()->GetProcess());
+}
+
+// Ensure that the browser ignores most IPC messages that arrive from a
+// RenderViewHost that has been swapped out. We do not want to take
+// action on requests from a non-active renderer. The main exception is
+// for synchronous messages, which cannot be ignored without leaving the
+// renderer in a stuck state. See http://crbug.com/93427.
+TEST_F(RenderViewHostManagerTest, FilterMessagesWhileSwappedOut) {
+ const GURL kChromeURL("chrome://foo");
+ const GURL kDestUrl("http://www.google.com/");
+
+ // Navigate our first tab to a chrome url and then to the destination.
+ NavigateActiveAndCommit(kChromeURL);
+ TestRenderViewHost* ntp_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->current_host());
+
+ // Send an update title message and make sure it works.
+ const string16 ntp_title = ASCIIToUTF16("NTP Title");
+ WebKit::WebTextDirection direction = WebKit::WebTextDirectionLeftToRight;
+ EXPECT_TRUE(ntp_rvh->OnMessageReceived(
+ ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 0, ntp_title, direction)));
+ EXPECT_EQ(ntp_title, contents()->GetTitle());
+
+ // Navigate to a cross-site URL.
+ contents()->GetController().LoadURL(
+ kDestUrl, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ EXPECT_TRUE(contents()->cross_navigation_pending());
+ TestRenderViewHost* dest_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->pending_render_view_host());
+ ASSERT_TRUE(dest_rvh);
+ EXPECT_NE(ntp_rvh, dest_rvh);
+
+ // Create one more view in the same SiteInstance where dest_rvh2
+ // exists so that it doesn't get deleted on navigation to another
+ // site.
+ static_cast<SiteInstanceImpl*>(ntp_rvh->GetSiteInstance())->
+ increment_active_view_count();
+
+ // BeforeUnload finishes.
+ ntp_rvh->SendShouldCloseACK(true);
+
+ // Assume SwapOutACK times out, so the dest_rvh proceeds and commits.
+ dest_rvh->SendNavigate(101, kDestUrl);
+
+ // The new RVH should be able to update its title.
+ const string16 dest_title = ASCIIToUTF16("Google");
+ EXPECT_TRUE(dest_rvh->OnMessageReceived(
+ ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 101, dest_title,
+ direction)));
+ EXPECT_EQ(dest_title, contents()->GetTitle());
+
+ // The old renderer, being slow, now updates the title. It should be filtered
+ // out and not take effect.
+ EXPECT_TRUE(ntp_rvh->is_swapped_out());
+ EXPECT_TRUE(ntp_rvh->OnMessageReceived(
+ ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 0, ntp_title, direction)));
+ EXPECT_EQ(dest_title, contents()->GetTitle());
+
+ // We cannot filter out synchronous IPC messages, because the renderer would
+ // be left waiting for a reply. We pick RunBeforeUnloadConfirm as an example
+ // that can run easily within a unit test, and that needs to receive a reply
+ // without showing an actual dialog.
+ MockRenderProcessHost* ntp_process_host =
+ static_cast<MockRenderProcessHost*>(ntp_rvh->GetProcess());
+ ntp_process_host->sink().ClearMessages();
+ const string16 msg = ASCIIToUTF16("Message");
+ bool result = false;
+ string16 unused;
+ ViewHostMsg_RunBeforeUnloadConfirm before_unload_msg(
+ rvh()->GetRoutingID(), kChromeURL, msg, false, &result, &unused);
+ // Enable pumping for check in BrowserMessageFilter::CheckCanDispatchOnUI.
+ before_unload_msg.EnableMessagePumping();
+ EXPECT_TRUE(ntp_rvh->OnMessageReceived(before_unload_msg));
+ EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
+
+ // Also test RunJavaScriptMessage.
+ ntp_process_host->sink().ClearMessages();
+ ViewHostMsg_RunJavaScriptMessage js_msg(
+ rvh()->GetRoutingID(), msg, msg, kChromeURL,
+ JAVASCRIPT_MESSAGE_TYPE_CONFIRM, &result, &unused);
+ js_msg.EnableMessagePumping();
+ EXPECT_TRUE(ntp_rvh->OnMessageReceived(js_msg));
+ EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
+}
+
+TEST_F(RenderViewHostManagerTest, WhiteListSwapCompositorFrame) {
+ TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
+ TestRenderWidgetHostView* swapped_out_rwhv =
+ static_cast<TestRenderWidgetHostView*>(swapped_out_rvh->GetView());
+ EXPECT_FALSE(swapped_out_rwhv->did_swap_compositor_frame());
+
+ MockRenderProcessHost* process_host =
+ static_cast<MockRenderProcessHost*>(swapped_out_rvh->GetProcess());
+ process_host->sink().ClearMessages();
+
+ cc::CompositorFrame frame;
+ ViewHostMsg_SwapCompositorFrame msg(rvh()->GetRoutingID(), 0, frame);
+
+ EXPECT_TRUE(swapped_out_rvh->OnMessageReceived(msg));
+ EXPECT_TRUE(swapped_out_rwhv->did_swap_compositor_frame());
+}
+
+TEST_F(RenderViewHostManagerTest, WhiteListDidActivateAcceleratedCompositing) {
+ TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
+
+ MockRenderProcessHost* process_host =
+ static_cast<MockRenderProcessHost*>(swapped_out_rvh->GetProcess());
+ process_host->sink().ClearMessages();
+ ViewHostMsg_DidActivateAcceleratedCompositing msg(
+ rvh()->GetRoutingID(), true);
+ EXPECT_TRUE(swapped_out_rvh->OnMessageReceived(msg));
+ EXPECT_TRUE(swapped_out_rvh->is_accelerated_compositing_active());
+}
+
+// Test if RenderViewHost::GetRenderWidgetHosts() only returns active
+// widgets.
+TEST_F(RenderViewHostManagerTest, GetRenderWidgetHostsReturnsActiveViews) {
+ TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
+ EXPECT_TRUE(swapped_out_rvh->is_swapped_out());
+
+ scoped_ptr<RenderWidgetHostIterator> widgets(
+ RenderWidgetHost::GetRenderWidgetHosts());
+ // We know that there is the only one active widget. Another view is
+ // now swapped out, so the swapped out view is not included in the
+ // list.
+ RenderWidgetHost* widget = widgets->GetNextHost();
+ EXPECT_FALSE(widgets->GetNextHost());
+ RenderViewHost* rvh = RenderViewHost::From(widget);
+ EXPECT_FALSE(static_cast<RenderViewHostImpl*>(rvh)->is_swapped_out());
+}
+
+// Test if RenderViewHost::GetRenderWidgetHosts() returns a subset of
+// RenderViewHostImpl::GetAllRenderWidgetHosts().
+// RenderViewHost::GetRenderWidgetHosts() returns only active widgets, but
+// RenderViewHostImpl::GetAllRenderWidgetHosts() returns everything
+// including swapped out ones.
+TEST_F(RenderViewHostManagerTest,
+ GetRenderWidgetHostsWithinGetAllRenderWidgetHosts) {
+ TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
+ EXPECT_TRUE(swapped_out_rvh->is_swapped_out());
+
+ scoped_ptr<RenderWidgetHostIterator> widgets(
+ RenderWidgetHost::GetRenderWidgetHosts());
+
+ while (RenderWidgetHost* w = widgets->GetNextHost()) {
+ bool found = false;
+ scoped_ptr<RenderWidgetHostIterator> all_widgets(
+ RenderWidgetHostImpl::GetAllRenderWidgetHosts());
+ while (RenderWidgetHost* widget = all_widgets->GetNextHost()) {
+ if (w == widget) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+ }
+}
+
+// Test if SiteInstanceImpl::active_view_count() is correctly updated
+// as views in a SiteInstance get swapped out and in.
+TEST_F(RenderViewHostManagerTest, ActiveViewCountWhileSwappingInandOut) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.chromium.org/");
+
+ // Navigate to an initial URL.
+ contents()->NavigateAndCommit(kUrl1);
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ SiteInstanceImpl* instance1 =
+ static_cast<SiteInstanceImpl*>(rvh1->GetSiteInstance());
+ EXPECT_EQ(instance1->active_view_count(), 1U);
+
+ // Create 2 new tabs and simulate them being the opener chain for the main
+ // tab. They should be in the same SiteInstance.
+ scoped_ptr<TestWebContents> opener1(
+ TestWebContents::Create(browser_context(), instance1));
+ contents()->SetOpener(opener1.get());
+
+ scoped_ptr<TestWebContents> opener2(
+ TestWebContents::Create(browser_context(), instance1));
+ opener1->SetOpener(opener2.get());
+
+ EXPECT_EQ(instance1->active_view_count(), 3U);
+
+ // Navigate to a cross-site URL (different SiteInstance but same
+ // BrowsingInstance).
+ contents()->NavigateAndCommit(kUrl2);
+ TestRenderViewHost* rvh2 = test_rvh();
+ SiteInstanceImpl* instance2 =
+ static_cast<SiteInstanceImpl*>(rvh2->GetSiteInstance());
+
+ // rvh2 is on chromium.org which is different from google.com on
+ // which other tabs are.
+ EXPECT_EQ(instance2->active_view_count(), 1U);
+
+ // There are two active views on google.com now.
+ EXPECT_EQ(instance1->active_view_count(), 2U);
+
+ // Navigate to the original origin (google.com).
+ contents()->NavigateAndCommit(kUrl1);
+
+ EXPECT_EQ(instance1->active_view_count(), 3U);
+}
+
+// This deletes a WebContents when the given RVH is deleted. This is
+// only for testing whether deleting an RVH does not cause any UaF in
+// other parts of the system. For now, this class is only used for the
+// next test cases to detect the bug mentioned at
+// http://crbug.com/259859.
+class RenderViewHostDestroyer : public WebContentsObserver {
+ public:
+ RenderViewHostDestroyer(RenderViewHost* render_view_host,
+ WebContents* web_contents)
+ : WebContentsObserver(WebContents::FromRenderViewHost(render_view_host)),
+ render_view_host_(render_view_host),
+ web_contents_(web_contents) {}
+
+ virtual void RenderViewDeleted(
+ RenderViewHost* render_view_host) OVERRIDE {
+ if (render_view_host == render_view_host_)
+ delete web_contents_;
+ }
+
+ private:
+ RenderViewHost* render_view_host_;
+ WebContents* web_contents_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderViewHostDestroyer);
+};
+
+// Test if ShutdownRenderViewHostsInSiteInstance() does not touch any
+// RenderWidget that has been freed while deleting a RenderViewHost in
+// a previous iteration. This is a regression test for
+// http://crbug.com/259859.
+TEST_F(RenderViewHostManagerTest,
+ DetectUseAfterFreeInShutdownRenderViewHostsInSiteInstance) {
+ const GURL kChromeURL("chrome://newtab");
+ const GURL kUrl1("http://www.google.com");
+ const GURL kUrl2("http://www.chromium.org");
+
+ // Navigate our first tab to a chrome url and then to the destination.
+ NavigateActiveAndCommit(kChromeURL);
+ TestRenderViewHost* ntp_rvh = static_cast<TestRenderViewHost*>(
+ contents()->GetRenderManagerForTesting()->current_host());
+
+ // Create one more tab and navigate to kUrl1. web_contents is not
+ // wrapped as scoped_ptr since it intentionally deleted by destroyer
+ // below as part of this test.
+ TestWebContents* web_contents =
+ TestWebContents::Create(browser_context(), ntp_rvh->GetSiteInstance());
+ web_contents->NavigateAndCommit(kUrl1);
+ RenderViewHostDestroyer destroyer(ntp_rvh, web_contents);
+
+ // This causes the first tab to navigate to kUrl2, which destroys
+ // the ntp_rvh in ShutdownRenderViewHostsInSiteInstance(). When
+ // ntp_rvh is destroyed, it also destroys the RVHs in web_contents
+ // too. This can test whether
+ // SiteInstanceImpl::ShutdownRenderViewHostsInSiteInstance() can
+ // touch any object freed in this way or not while iterating through
+ // all widgets.
+ contents()->NavigateAndCommit(kUrl2);
+}
+
+// When there is an error with the specified page, renderer exits view-source
+// mode. See WebFrameImpl::DidFail(). We check by this test that
+// EnableViewSourceMode message is sent on every navigation regardless
+// RenderView is being newly created or reused.
+TEST_F(RenderViewHostManagerTest, AlwaysSendEnableViewSourceMode) {
+ const GURL kChromeUrl("chrome://foo");
+ const GURL kUrl("view-source:http://foo");
+
+ // We have to navigate to some page at first since without this, the first
+ // navigation will reuse the SiteInstance created by Init(), and the second
+ // one will create a new SiteInstance. Because current_instance and
+ // new_instance will be different, a new RenderViewHost will be created for
+ // the second navigation. We have to avoid this in order to exercise the
+ // target code patch.
+ NavigateActiveAndCommit(kChromeUrl);
+
+ // Navigate.
+ controller().LoadURL(
+ kUrl, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // Simulate response from RenderView for FirePageBeforeUnload.
+ base::TimeTicks now = base::TimeTicks::Now();
+ test_rvh()->OnMessageReceived(ViewHostMsg_ShouldClose_ACK(
+ rvh()->GetRoutingID(), true, now, now));
+ ASSERT_TRUE(pending_rvh()); // New pending RenderViewHost will be created.
+ RenderViewHost* last_rvh = pending_rvh();
+ int32 new_id = contents()->GetMaxPageIDForSiteInstance(
+ active_rvh()->GetSiteInstance()) + 1;
+ pending_test_rvh()->SendNavigate(new_id, kUrl);
+ EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1);
+ ASSERT_TRUE(controller().GetLastCommittedEntry());
+ EXPECT_TRUE(kUrl == controller().GetLastCommittedEntry()->GetURL());
+ EXPECT_FALSE(controller().GetPendingEntry());
+ // Because we're using TestWebContents and TestRenderViewHost in this
+ // unittest, no one calls WebContentsImpl::RenderViewCreated(). So, we see no
+ // EnableViewSourceMode message, here.
+
+ // Clear queued messages before load.
+ process()->sink().ClearMessages();
+ // Navigate, again.
+ controller().LoadURL(
+ kUrl, Referrer(), PAGE_TRANSITION_TYPED, std::string());
+ // The same RenderViewHost should be reused.
+ EXPECT_FALSE(pending_rvh());
+ EXPECT_TRUE(last_rvh == rvh());
+ test_rvh()->SendNavigate(new_id, kUrl); // The same page_id returned.
+ EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1);
+ EXPECT_FALSE(controller().GetPendingEntry());
+ // New message should be sent out to make sure to enter view-source mode.
+ EXPECT_TRUE(process()->sink().GetUniqueMessageMatching(
+ ViewMsg_EnableViewSourceMode::ID));
+}
+
+// Tests the Init function by checking the initial RenderViewHost.
+TEST_F(RenderViewHostManagerTest, Init) {
+ // Using TestBrowserContext.
+ SiteInstanceImpl* instance =
+ static_cast<SiteInstanceImpl*>(SiteInstance::Create(browser_context()));
+ EXPECT_FALSE(instance->HasSite());
+
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ RenderViewHost* host = manager.current_host();
+ ASSERT_TRUE(host);
+ EXPECT_EQ(instance, host->GetSiteInstance());
+ EXPECT_EQ(web_contents.get(), host->GetDelegate());
+ EXPECT_TRUE(manager.GetRenderWidgetHostView());
+ EXPECT_FALSE(manager.pending_render_view_host());
+}
+
+// Tests the Navigate function. We navigate three sites consecutively and check
+// how the pending/committed RenderViewHost are modified.
+TEST_F(RenderViewHostManagerTest, Navigate) {
+ TestNotificationTracker notifications;
+
+ SiteInstance* instance = SiteInstance::Create(browser_context());
+
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ notifications.ListenFor(NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
+ Source<WebContents>(web_contents.get()));
+
+ // Create.
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ RenderViewHost* host;
+
+ // 1) The first navigation. --------------------------
+ const GURL kUrl1("http://www.google.com/");
+ NavigationEntryImpl entry1(
+ NULL /* instance */, -1 /* page_id */, kUrl1, Referrer(),
+ string16() /* title */, PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ host = manager.Navigate(entry1);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ // Commit to SiteInstance should be delayed until RenderView commit.
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1);
+
+ // 2) Navigate to next site. -------------------------
+ const GURL kUrl2("http://www.google.com/foo");
+ NavigationEntryImpl entry2(
+ NULL /* instance */, -1 /* page_id */, kUrl2,
+ Referrer(kUrl1, WebKit::WebReferrerPolicyDefault),
+ string16() /* title */, PAGE_TRANSITION_LINK,
+ true /* is_renderer_init */);
+ host = manager.Navigate(entry2);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+
+ // 3) Cross-site navigate to next site. --------------
+ const GURL kUrl3("http://webkit.org/");
+ NavigationEntryImpl entry3(
+ NULL /* instance */, -1 /* page_id */, kUrl3,
+ Referrer(kUrl2, WebKit::WebReferrerPolicyDefault),
+ string16() /* title */, PAGE_TRANSITION_LINK,
+ false /* is_renderer_init */);
+ host = manager.Navigate(entry3);
+
+ // A new RenderViewHost should be created.
+ EXPECT_TRUE(manager.pending_render_view_host());
+ ASSERT_EQ(host, manager.pending_render_view_host());
+
+ notifications.Reset();
+
+ // Commit.
+ manager.DidNavigateMainFrame(manager.pending_render_view_host());
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ // Check the pending RenderViewHost has been committed.
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // We should observe a notification.
+ EXPECT_TRUE(
+ notifications.Check1AndReset(NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
+}
+
+// Tests the Navigate function. In this unit test we verify that the Navigate
+// function can handle a new navigation event before the previous navigation
+// has been committed. This is also a regression test for
+// http://crbug.com/104600.
+TEST_F(RenderViewHostManagerTest, NavigateWithEarlyReNavigation) {
+ TestNotificationTracker notifications;
+
+ SiteInstance* instance = SiteInstance::Create(browser_context());
+
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ notifications.ListenFor(NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
+ Source<WebContents>(web_contents.get()));
+
+ // Create.
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ // 1) The first navigation. --------------------------
+ const GURL kUrl1("http://www.google.com/");
+ NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHost* host = manager.Navigate(entry1);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // We should observe a notification.
+ EXPECT_TRUE(
+ notifications.Check1AndReset(NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
+ notifications.Reset();
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+
+ // Commit to SiteInstance should be delayed until RenderView commit.
+ EXPECT_TRUE(host == manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1);
+
+ // 2) Cross-site navigate to next site. -------------------------
+ const GURL kUrl2("http://www.example.com");
+ NavigationEntryImpl entry2(
+ NULL /* instance */, -1 /* page_id */, kUrl2, Referrer(),
+ string16() /* title */, PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHostImpl* host2 = static_cast<RenderViewHostImpl*>(
+ manager.Navigate(entry2));
+ int host2_process_id = host2->GetProcess()->GetID();
+
+ // A new RenderViewHost should be created.
+ EXPECT_TRUE(manager.pending_render_view_host());
+ ASSERT_EQ(host2, manager.pending_render_view_host());
+ EXPECT_NE(host2, host);
+
+ // Check that the navigation is still suspended because the old RVH
+ // is not swapped out, yet.
+ EXPECT_TRUE(host2->are_navigations_suspended());
+ MockRenderProcessHost* test_process_host2 =
+ static_cast<MockRenderProcessHost*>(host2->GetProcess());
+ test_process_host2->sink().ClearMessages();
+ host2->NavigateToURL(kUrl2);
+ EXPECT_FALSE(test_process_host2->sink().GetUniqueMessageMatching(
+ ViewMsg_Navigate::ID));
+
+ // Allow closing the current Render View (precondition for swapping out
+ // the RVH): Simulate response from RenderView for ViewMsg_ShouldClose sent by
+ // FirePageBeforeUnload.
+ TestRenderViewHost* test_host = static_cast<TestRenderViewHost*>(host);
+ MockRenderProcessHost* test_process_host =
+ static_cast<MockRenderProcessHost*>(test_host->GetProcess());
+ EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching(
+ ViewMsg_ShouldClose::ID));
+ test_host->SendShouldCloseACK(true);
+
+ // CrossSiteResourceHandler::StartCrossSiteTransition triggers a
+ // call of RenderViewHostManager::SwapOutOldPage before
+ // RenderViewHostManager::DidNavigateMainFrame is called.
+ // The RVH is not swapped out until the commit.
+ manager.SwapOutOldPage();
+ EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching(
+ ViewMsg_SwapOut::ID));
+ test_host->OnSwappedOut(false);
+
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(static_cast<RenderViewHostImpl*>(
+ manager.current_host())->is_swapped_out());
+ EXPECT_EQ(host2, manager.pending_render_view_host());
+ // There should be still no navigation messages being sent.
+ EXPECT_FALSE(test_process_host2->sink().GetUniqueMessageMatching(
+ ViewMsg_Navigate::ID));
+
+ // 3) Cross-site navigate to next site before 2) has committed. --------------
+ const GURL kUrl3("http://webkit.org/");
+ NavigationEntryImpl entry3(NULL /* instance */, -1 /* page_id */, kUrl3,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ test_process_host->sink().ClearMessages();
+ RenderViewHost* host3 = manager.Navigate(entry3);
+
+ // A new RenderViewHost should be created. host2 is now deleted.
+ EXPECT_TRUE(manager.pending_render_view_host());
+ ASSERT_EQ(host3, manager.pending_render_view_host());
+ EXPECT_NE(host3, host);
+ EXPECT_NE(host3->GetProcess()->GetID(), host2_process_id);
+
+ // Navigations in the new RVH should be suspended, which is ok because the
+ // old RVH is not yet swapped out and can respond to a second beforeunload
+ // request.
+ EXPECT_TRUE(static_cast<RenderViewHostImpl*>(
+ host3)->are_navigations_suspended());
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(static_cast<RenderViewHostImpl*>(
+ manager.current_host())->is_swapped_out());
+
+ // Simulate a response to the second beforeunload request.
+ EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching(
+ ViewMsg_ShouldClose::ID));
+ test_host->SendShouldCloseACK(true);
+
+ // CrossSiteResourceHandler::StartCrossSiteTransition triggers a
+ // call of RenderViewHostManager::SwapOutOldPage before
+ // RenderViewHostManager::DidNavigateMainFrame is called.
+ // The RVH is not swapped out until the commit.
+ manager.SwapOutOldPage();
+ EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching(
+ ViewMsg_SwapOut::ID));
+ test_host->OnSwappedOut(false);
+
+ // Commit.
+ manager.DidNavigateMainFrame(host3);
+ EXPECT_TRUE(host3 == manager.current_host());
+ ASSERT_TRUE(host3);
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host3->GetSiteInstance())->
+ HasSite());
+ // Check the pending RenderViewHost has been committed.
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // We should observe a notification.
+ EXPECT_TRUE(
+ notifications.Check1AndReset(NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
+}
+
+// Tests WebUI creation.
+TEST_F(RenderViewHostManagerTest, WebUI) {
+ set_should_create_webui(true);
+ SiteInstance* instance = SiteInstance::Create(browser_context());
+
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+ EXPECT_FALSE(manager.current_host()->IsRenderViewLive());
+
+ const GURL kUrl("chrome://foo");
+ NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHost* host = manager.Navigate(entry);
+
+ // We commit the pending RenderViewHost immediately because the previous
+ // RenderViewHost was not live. We test a case where it is live in
+ // WebUIInNewTab.
+ EXPECT_TRUE(host);
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // It's important that the site instance get set on the Web UI page as soon
+ // as the navigation starts, rather than lazily after it commits, so we don't
+ // try to re-use the SiteInstance/process for non Web UI things that may
+ // get loaded in between.
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ EXPECT_EQ(kUrl, host->GetSiteInstance()->GetSiteURL());
+
+ // The Web UI is committed immediately because the RenderViewHost has not been
+ // used yet. UpdateRendererStateForNavigate() took the short cut path.
+ EXPECT_FALSE(manager.pending_web_ui());
+ EXPECT_TRUE(manager.web_ui());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ EXPECT_TRUE(host->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+}
+
+// Tests that we can open a WebUI link in a new tab from a WebUI page and still
+// grant the correct bindings. http://crbug.com/189101.
+TEST_F(RenderViewHostManagerTest, WebUIInNewTab) {
+ set_should_create_webui(true);
+ SiteInstance* blank_instance = SiteInstance::Create(browser_context());
+
+ // Create a blank tab.
+ scoped_ptr<TestWebContents> web_contents1(
+ TestWebContents::Create(browser_context(), blank_instance));
+ RenderViewHostManager manager1(web_contents1.get(), web_contents1.get(),
+ web_contents1.get());
+ manager1.Init(
+ browser_context(), blank_instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+ // Test the case that new RVH is considered live.
+ manager1.current_host()->CreateRenderView(string16(), -1, -1);
+
+ // Navigate to a WebUI page.
+ const GURL kUrl1("chrome://foo");
+ NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHost* host1 = manager1.Navigate(entry1);
+
+ // We should have a pending navigation to the WebUI RenderViewHost.
+ // It should already have bindings.
+ EXPECT_EQ(host1, manager1.pending_render_view_host());
+ EXPECT_NE(host1, manager1.current_host());
+ EXPECT_TRUE(host1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ // Commit and ensure we still have bindings.
+ manager1.DidNavigateMainFrame(host1);
+ SiteInstance* webui_instance = host1->GetSiteInstance();
+ EXPECT_EQ(host1, manager1.current_host());
+ EXPECT_TRUE(host1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ // Now simulate clicking a link that opens in a new tab.
+ scoped_ptr<TestWebContents> web_contents2(
+ TestWebContents::Create(browser_context(), webui_instance));
+ RenderViewHostManager manager2(web_contents2.get(), web_contents2.get(),
+ web_contents2.get());
+ manager2.Init(
+ browser_context(), webui_instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+ // Make sure the new RVH is considered live. This is usually done in
+ // RenderWidgetHost::Init when opening a new tab from a link.
+ manager2.current_host()->CreateRenderView(string16(), -1, -1);
+
+ const GURL kUrl2("chrome://foo/bar");
+ NavigationEntryImpl entry2(NULL /* instance */, -1 /* page_id */, kUrl2,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_LINK,
+ true /* is_renderer_init */);
+ RenderViewHost* host2 = manager2.Navigate(entry2);
+
+ // No cross-process transition happens because we are already in the right
+ // SiteInstance. We should grant bindings immediately.
+ EXPECT_EQ(host2, manager2.current_host());
+ EXPECT_TRUE(host2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ manager2.DidNavigateMainFrame(host2);
+}
+
+// Tests that we don't end up in an inconsistent state if a page does a back and
+// then reload. http://crbug.com/51680
+TEST_F(RenderViewHostManagerTest, PageDoesBackAndReload) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.evil-site.com/");
+
+ // Navigate to a safe site, then an evil site.
+ // This will switch RenderViewHosts. We cannot assert that the first and
+ // second RVHs are different, though, because the first one may be promptly
+ // deleted.
+ contents()->NavigateAndCommit(kUrl1);
+ contents()->NavigateAndCommit(kUrl2);
+ RenderViewHost* evil_rvh = contents()->GetRenderViewHost();
+
+ // Now let's simulate the evil page calling history.back().
+ contents()->OnGoToEntryAtOffset(-1);
+ // We should have a new pending RVH.
+ // Note that in this case, the navigation has not committed, so evil_rvh will
+ // not be deleted yet.
+ EXPECT_NE(evil_rvh, contents()->GetRenderManagerForTesting()->
+ pending_render_view_host());
+
+ // Before that RVH has committed, the evil page reloads itself.
+ ViewHostMsg_FrameNavigate_Params params;
+ params.page_id = 1;
+ params.url = kUrl2;
+ params.transition = PAGE_TRANSITION_CLIENT_REDIRECT;
+ params.should_update_history = false;
+ params.gesture = NavigationGestureAuto;
+ params.was_within_same_page = false;
+ params.is_post = false;
+ params.page_state = PageState::CreateFromURL(kUrl2);
+ contents()->DidNavigate(evil_rvh, params);
+
+ // That should have cancelled the pending RVH, and the evil RVH should be the
+ // current one.
+ EXPECT_TRUE(contents()->GetRenderManagerForTesting()->
+ pending_render_view_host() == NULL);
+ EXPECT_EQ(evil_rvh, contents()->GetRenderManagerForTesting()->current_host());
+
+ // Also we should not have a pending navigation entry.
+ EXPECT_TRUE(contents()->GetController().GetPendingEntry() == NULL);
+ NavigationEntry* entry = contents()->GetController().GetVisibleEntry();
+ ASSERT_TRUE(entry != NULL);
+ EXPECT_EQ(kUrl2, entry->GetURL());
+}
+
+// Ensure that we can go back and forward even if a SwapOut ACK isn't received.
+// See http://crbug.com/93427.
+TEST_F(RenderViewHostManagerTest, NavigateAfterMissingSwapOutACK) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.chromium.org/");
+
+ // Navigate to two pages.
+ contents()->NavigateAndCommit(kUrl1);
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ // Keep active_view_count nonzero so that no swapped out views in
+ // this SiteInstance get forcefully deleted.
+ static_cast<SiteInstanceImpl*>(rvh1->GetSiteInstance())->
+ increment_active_view_count();
+
+ contents()->NavigateAndCommit(kUrl2);
+ TestRenderViewHost* rvh2 = test_rvh();
+ static_cast<SiteInstanceImpl*>(rvh2->GetSiteInstance())->
+ increment_active_view_count();
+
+ // Now go back, but suppose the SwapOut_ACK isn't received. This shouldn't
+ // happen, but we have seen it when going back quickly across many entries
+ // (http://crbug.com/93427).
+ contents()->GetController().GoBack();
+ EXPECT_TRUE(rvh2->is_waiting_for_beforeunload_ack());
+ contents()->ProceedWithCrossSiteNavigation();
+ EXPECT_FALSE(rvh2->is_waiting_for_beforeunload_ack());
+ rvh2->SwapOut();
+ EXPECT_TRUE(rvh2->is_waiting_for_unload_ack());
+
+ // The back navigation commits. We should proactively clear the
+ // is_waiting_for_unload_ack state to be safe.
+ const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
+ rvh1->SendNavigate(entry1->GetPageID(), entry1->GetURL());
+ EXPECT_TRUE(rvh2->is_swapped_out());
+ EXPECT_FALSE(rvh2->is_waiting_for_unload_ack());
+
+ // We should be able to navigate forward.
+ contents()->GetController().GoForward();
+ contents()->ProceedWithCrossSiteNavigation();
+ const NavigationEntry* entry2 = contents()->GetController().GetPendingEntry();
+ rvh2->SendNavigate(entry2->GetPageID(), entry2->GetURL());
+ EXPECT_EQ(rvh2, rvh());
+ EXPECT_FALSE(rvh2->is_swapped_out());
+ EXPECT_TRUE(rvh1->is_swapped_out());
+}
+
+// Test that we create swapped out RVHs for the opener chain when navigating an
+// opened tab cross-process. This allows us to support certain cross-process
+// JavaScript calls (http://crbug.com/99202).
+TEST_F(RenderViewHostManagerTest, CreateSwappedOutOpenerRVHs) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.chromium.org/");
+ const GURL kChromeUrl("chrome://foo");
+
+ // Navigate to an initial URL.
+ contents()->NavigateAndCommit(kUrl1);
+ RenderViewHostManager* manager = contents()->GetRenderManagerForTesting();
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ // Create 2 new tabs and simulate them being the opener chain for the main
+ // tab. They should be in the same SiteInstance.
+ scoped_ptr<TestWebContents> opener1(
+ TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
+ RenderViewHostManager* opener1_manager =
+ opener1->GetRenderManagerForTesting();
+ contents()->SetOpener(opener1.get());
+
+ scoped_ptr<TestWebContents> opener2(
+ TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
+ RenderViewHostManager* opener2_manager =
+ opener2->GetRenderManagerForTesting();
+ opener1->SetOpener(opener2.get());
+
+ // Navigate to a cross-site URL (different SiteInstance but same
+ // BrowsingInstance).
+ contents()->NavigateAndCommit(kUrl2);
+ TestRenderViewHost* rvh2 = test_rvh();
+ EXPECT_NE(rvh1->GetSiteInstance(), rvh2->GetSiteInstance());
+ EXPECT_TRUE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
+ rvh2->GetSiteInstance()));
+
+ // Ensure rvh1 is placed on swapped out list of the current tab.
+ EXPECT_TRUE(manager->IsOnSwappedOutList(rvh1));
+ EXPECT_EQ(rvh1,
+ manager->GetSwappedOutRenderViewHost(rvh1->GetSiteInstance()));
+
+ // Ensure a swapped out RVH is created in the first opener tab.
+ TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
+ opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
+ EXPECT_TRUE(opener1_manager->IsOnSwappedOutList(opener1_rvh));
+ EXPECT_TRUE(opener1_rvh->is_swapped_out());
+
+ // Ensure a swapped out RVH is created in the second opener tab.
+ TestRenderViewHost* opener2_rvh = static_cast<TestRenderViewHost*>(
+ opener2_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
+ EXPECT_TRUE(opener2_manager->IsOnSwappedOutList(opener2_rvh));
+ EXPECT_TRUE(opener2_rvh->is_swapped_out());
+
+ // Navigate to a cross-BrowsingInstance URL.
+ contents()->NavigateAndCommit(kChromeUrl);
+ TestRenderViewHost* rvh3 = test_rvh();
+ EXPECT_NE(rvh1->GetSiteInstance(), rvh3->GetSiteInstance());
+ EXPECT_FALSE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
+ rvh3->GetSiteInstance()));
+
+ // No scripting is allowed across BrowsingInstances, so we should not create
+ // swapped out RVHs for the opener chain in this case.
+ EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
+ rvh3->GetSiteInstance()));
+ EXPECT_FALSE(opener2_manager->GetSwappedOutRenderViewHost(
+ rvh3->GetSiteInstance()));
+}
+
+// Test that we clean up swapped out RenderViewHosts when a process hosting
+// those associated RenderViews crashes. http://crbug.com/258993
+TEST_F(RenderViewHostManagerTest, CleanUpSwappedOutRVHOnProcessCrash) {
+ const GURL kUrl1("http://www.google.com/");
+
+ // Navigate to an initial URL.
+ contents()->NavigateAndCommit(kUrl1);
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ // Create a new tab as an opener for the main tab.
+ scoped_ptr<TestWebContents> opener1(
+ TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
+ RenderViewHostManager* opener1_manager =
+ opener1->GetRenderManagerForTesting();
+ contents()->SetOpener(opener1.get());
+
+ EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
+ rvh1->GetSiteInstance()));
+ opener1->CreateSwappedOutRenderView(rvh1->GetSiteInstance());
+ EXPECT_TRUE(opener1_manager->GetSwappedOutRenderViewHost(
+ rvh1->GetSiteInstance()));
+
+ // Fake a process crash.
+ RenderProcessHost::RendererClosedDetails details(
+ rvh1->GetProcess()->GetHandle(),
+ base::TERMINATION_STATUS_PROCESS_CRASHED,
+ 0);
+ NotificationService::current()->Notify(
+ NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ Source<RenderProcessHost>(rvh1->GetProcess()),
+ Details<RenderProcessHost::RendererClosedDetails>(&details));
+ rvh1->set_render_view_created(false);
+
+ // Ensure that the swapped out RenderViewHost has been deleted.
+ EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
+ rvh1->GetSiteInstance()));
+
+ // Reload the initial tab. This should recreate the opener.
+ contents()->GetController().Reload(true);
+
+ EXPECT_EQ(opener1_manager->current_host()->GetRoutingID(),
+ test_rvh()->opener_route_id());
+}
+
+// Test that RenderViewHosts created for WebUI navigations are properly
+// granted WebUI bindings even if an unprivileged swapped out RenderViewHost
+// is in the same process (http://crbug.com/79918).
+TEST_F(RenderViewHostManagerTest, EnableWebUIWithSwappedOutOpener) {
+ set_should_create_webui(true);
+ const GURL kSettingsUrl("chrome://chrome/settings");
+ const GURL kPluginUrl("chrome://plugins");
+
+ // Navigate to an initial WebUI URL.
+ contents()->NavigateAndCommit(kSettingsUrl);
+
+ // Ensure the RVH has WebUI bindings.
+ TestRenderViewHost* rvh1 = test_rvh();
+ EXPECT_TRUE(rvh1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+
+ // Create a new tab and simulate it being the opener for the main
+ // tab. It should be in the same SiteInstance.
+ scoped_ptr<TestWebContents> opener1(
+ TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
+ RenderViewHostManager* opener1_manager =
+ opener1->GetRenderManagerForTesting();
+ contents()->SetOpener(opener1.get());
+
+ // Navigate to a different WebUI URL (different SiteInstance, same
+ // BrowsingInstance).
+ contents()->NavigateAndCommit(kPluginUrl);
+ TestRenderViewHost* rvh2 = test_rvh();
+ EXPECT_NE(rvh1->GetSiteInstance(), rvh2->GetSiteInstance());
+ EXPECT_TRUE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
+ rvh2->GetSiteInstance()));
+
+ // Ensure a swapped out RVH is created in the first opener tab.
+ TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
+ opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
+ EXPECT_TRUE(opener1_manager->IsOnSwappedOutList(opener1_rvh));
+ EXPECT_TRUE(opener1_rvh->is_swapped_out());
+
+ // Ensure the new RVH has WebUI bindings.
+ EXPECT_TRUE(rvh2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
+}
+
+// Test that we reuse the same guest SiteInstance if we navigate across sites.
+TEST_F(RenderViewHostManagerTest, NoSwapOnGuestNavigations) {
+ TestNotificationTracker notifications;
+
+ GURL guest_url(std::string(kGuestScheme).append("://abc123"));
+ SiteInstance* instance =
+ SiteInstance::CreateForURL(browser_context(), guest_url);
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+
+ // Create.
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ RenderViewHost* host;
+
+ // 1) The first navigation. --------------------------
+ const GURL kUrl1("http://www.google.com/");
+ NavigationEntryImpl entry1(
+ NULL /* instance */, -1 /* page_id */, kUrl1, Referrer(),
+ string16() /* title */, PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ host = manager.Navigate(entry1);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_TRUE(host == manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+ EXPECT_EQ(manager.current_host()->GetSiteInstance(), instance);
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ // Commit to SiteInstance should be delayed until RenderView commit.
+ EXPECT_EQ(host, manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+
+ // 2) Navigate to a different domain. -------------------------
+ // Guests stay in the same process on navigation.
+ const GURL kUrl2("http://www.chromium.org");
+ NavigationEntryImpl entry2(
+ NULL /* instance */, -1 /* page_id */, kUrl2,
+ Referrer(kUrl1, WebKit::WebReferrerPolicyDefault),
+ string16() /* title */, PAGE_TRANSITION_LINK,
+ true /* is_renderer_init */);
+ host = manager.Navigate(entry2);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+ EXPECT_EQ(host, manager.current_host());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(static_cast<SiteInstanceImpl*>(host->GetSiteInstance()),
+ instance);
+}
+
+// Test that we cancel a pending RVH if we close the tab while it's pending.
+// http://crbug.com/294697.
+TEST_F(RenderViewHostManagerTest, NavigateWithEarlyClose) {
+ TestNotificationTracker notifications;
+
+ SiteInstance* instance = SiteInstance::Create(browser_context());
+
+ BeforeUnloadFiredWebContentsDelegate delegate;
+ scoped_ptr<TestWebContents> web_contents(
+ TestWebContents::Create(browser_context(), instance));
+ web_contents->SetDelegate(&delegate);
+ notifications.ListenFor(NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
+ Source<WebContents>(web_contents.get()));
+
+ // Create.
+ RenderViewHostManager manager(web_contents.get(), web_contents.get(),
+ web_contents.get());
+
+ manager.Init(browser_context(), instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
+
+ // 1) The first navigation. --------------------------
+ const GURL kUrl1("http://www.google.com/");
+ NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1,
+ Referrer(), string16() /* title */,
+ PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHost* host = manager.Navigate(entry1);
+
+ // The RenderViewHost created in Init will be reused.
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(manager.pending_render_view_host());
+
+ // We should observe a notification.
+ EXPECT_TRUE(
+ notifications.Check1AndReset(NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
+ notifications.Reset();
+
+ // Commit.
+ manager.DidNavigateMainFrame(host);
+
+ // Commit to SiteInstance should be delayed until RenderView commit.
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
+ HasSite());
+ static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1);
+
+ // 2) Cross-site navigate to next site. -------------------------
+ const GURL kUrl2("http://www.example.com");
+ NavigationEntryImpl entry2(
+ NULL /* instance */, -1 /* page_id */, kUrl2, Referrer(),
+ string16() /* title */, PAGE_TRANSITION_TYPED,
+ false /* is_renderer_init */);
+ RenderViewHostImpl* host2 = static_cast<RenderViewHostImpl*>(
+ manager.Navigate(entry2));
+
+ // A new RenderViewHost should be created.
+ ASSERT_EQ(host2, manager.pending_render_view_host());
+ EXPECT_NE(host2, host);
+
+ EXPECT_EQ(host, manager.current_host());
+ EXPECT_FALSE(static_cast<RenderViewHostImpl*>(
+ manager.current_host())->is_swapped_out());
+ EXPECT_EQ(host2, manager.pending_render_view_host());
+
+ // 3) Close the tab. -------------------------
+ notifications.ListenFor(NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
+ Source<RenderWidgetHost>(host2));
+ manager.ShouldClosePage(false, true, base::TimeTicks());
+
+ EXPECT_TRUE(
+ notifications.Check1AndReset(NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED));
+ EXPECT_FALSE(manager.pending_render_view_host());
+ EXPECT_EQ(host, manager.current_host());
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/web_contents_screenshot_manager.cc b/content/browser/frame_host/web_contents_screenshot_manager.cc
new file mode 100644
index 0000000..97ac533
--- /dev/null
+++ b/content/browser/frame_host/web_contents_screenshot_manager.cc
@@ -0,0 +1,273 @@
+// Copyright 2013 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.
+
+#include "content/browser/frame_host/web_contents_screenshot_manager.h"
+
+#include "base/command_line.h"
+#include "base/threading/worker_pool.h"
+#include "content/browser/frame_host/navigation_controller_impl.h"
+#include "content/browser/frame_host/navigation_entry_impl.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/common/content_switches.h"
+#include "ui/gfx/codec/png_codec.h"
+
+namespace {
+
+// Minimum delay between taking screenshots.
+const int kMinScreenshotIntervalMS = 1000;
+
+}
+
+namespace content {
+
+// Encodes an SkBitmap to PNG data in a worker thread.
+class ScreenshotData : public base::RefCountedThreadSafe<ScreenshotData> {
+ public:
+ ScreenshotData() {
+ }
+
+ void EncodeScreenshot(const SkBitmap& bitmap, base::Closure callback) {
+ if (!base::WorkerPool::PostTaskAndReply(FROM_HERE,
+ base::Bind(&ScreenshotData::EncodeOnWorker,
+ this,
+ bitmap),
+ callback,
+ true)) {
+ callback.Run();
+ }
+ }
+
+ scoped_refptr<base::RefCountedBytes> data() const { return data_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<ScreenshotData>;
+ virtual ~ScreenshotData() {
+ }
+
+ void EncodeOnWorker(const SkBitmap& bitmap) {
+ std::vector<unsigned char> data;
+ if (gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &data))
+ data_ = new base::RefCountedBytes(data);
+ }
+
+ scoped_refptr<base::RefCountedBytes> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenshotData);
+};
+
+WebContentsScreenshotManager::WebContentsScreenshotManager(
+ NavigationControllerImpl* owner)
+ : owner_(owner),
+ screenshot_factory_(this),
+ min_screenshot_interval_ms_(kMinScreenshotIntervalMS) {
+}
+
+WebContentsScreenshotManager::~WebContentsScreenshotManager() {
+}
+
+void WebContentsScreenshotManager::TakeScreenshot() {
+ static bool overscroll_enabled = CommandLine::ForCurrentProcess()->
+ GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
+ if (!overscroll_enabled)
+ return;
+
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(owner_->GetLastCommittedEntry());
+ if (!entry)
+ return;
+
+ RenderViewHost* render_view_host =
+ owner_->delegate()->GetRenderViewHost();
+ if (!static_cast<RenderViewHostImpl*>
+ (render_view_host)->overscroll_controller()) {
+ return;
+ }
+ content::RenderWidgetHostView* view = render_view_host->GetView();
+ if (!view)
+ return;
+
+ // Make sure screenshots aren't taken too frequently.
+ base::Time now = base::Time::Now();
+ if (now - last_screenshot_time_ <
+ base::TimeDelta::FromMilliseconds(min_screenshot_interval_ms_)) {
+ return;
+ }
+
+ last_screenshot_time_ = now;
+
+ TakeScreenshotImpl(render_view_host, entry);
+}
+
+// Implemented here and not in NavigationEntry because this manager keeps track
+// of the total number of screen shots across all entries.
+void WebContentsScreenshotManager::ClearAllScreenshots() {
+ int count = owner_->GetEntryCount();
+ for (int i = 0; i < count; ++i) {
+ ClearScreenshot(NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(i)));
+ }
+ DCHECK_EQ(GetScreenshotCount(), 0);
+}
+
+void WebContentsScreenshotManager::TakeScreenshotImpl(
+ RenderViewHost* host,
+ NavigationEntryImpl* entry) {
+ DCHECK(host && host->GetView());
+ DCHECK(entry);
+ host->CopyFromBackingStore(gfx::Rect(),
+ host->GetView()->GetViewBounds().size(),
+ base::Bind(&WebContentsScreenshotManager::OnScreenshotTaken,
+ screenshot_factory_.GetWeakPtr(),
+ entry->GetUniqueID()));
+}
+
+void WebContentsScreenshotManager::SetMinScreenshotIntervalMS(int interval_ms) {
+ DCHECK_GE(interval_ms, 0);
+ min_screenshot_interval_ms_ = interval_ms;
+}
+
+void WebContentsScreenshotManager::OnScreenshotTaken(int unique_id,
+ bool success,
+ const SkBitmap& bitmap) {
+ NavigationEntryImpl* entry = NULL;
+ int entry_count = owner_->GetEntryCount();
+ for (int i = 0; i < entry_count; ++i) {
+ NavigationEntry* iter = owner_->GetEntryAtIndex(i);
+ if (iter->GetUniqueID() == unique_id) {
+ entry = NavigationEntryImpl::FromNavigationEntry(iter);
+ break;
+ }
+ }
+
+ if (!entry) {
+ LOG(ERROR) << "Invalid entry with unique id: " << unique_id;
+ return;
+ }
+
+ if (!success || bitmap.empty() || bitmap.isNull()) {
+ if (!ClearScreenshot(entry))
+ OnScreenshotSet(entry);
+ return;
+ }
+
+ scoped_refptr<ScreenshotData> screenshot = new ScreenshotData();
+ screenshot->EncodeScreenshot(
+ bitmap,
+ base::Bind(&WebContentsScreenshotManager::OnScreenshotEncodeComplete,
+ screenshot_factory_.GetWeakPtr(),
+ unique_id,
+ screenshot));
+}
+
+int WebContentsScreenshotManager::GetScreenshotCount() const {
+ int screenshot_count = 0;
+ int entry_count = owner_->GetEntryCount();
+ for (int i = 0; i < entry_count; ++i) {
+ NavigationEntryImpl* entry =
+ NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(i));
+ if (entry->screenshot().get())
+ screenshot_count++;
+ }
+ return screenshot_count;
+}
+
+void WebContentsScreenshotManager::OnScreenshotEncodeComplete(
+ int unique_id,
+ scoped_refptr<ScreenshotData> screenshot) {
+ NavigationEntryImpl* entry = NULL;
+ int entry_count = owner_->GetEntryCount();
+ for (int i = 0; i < entry_count; ++i) {
+ NavigationEntry* iter = owner_->GetEntryAtIndex(i);
+ if (iter->GetUniqueID() == unique_id) {
+ entry = NavigationEntryImpl::FromNavigationEntry(iter);
+ break;
+ }
+ }
+ if (!entry)
+ return;
+ entry->SetScreenshotPNGData(screenshot->data());
+ OnScreenshotSet(entry);
+}
+
+void WebContentsScreenshotManager::OnScreenshotSet(NavigationEntryImpl* entry) {
+ if (entry->screenshot().get())
+ PurgeScreenshotsIfNecessary();
+}
+
+bool WebContentsScreenshotManager::ClearScreenshot(NavigationEntryImpl* entry) {
+ if (!entry->screenshot().get())
+ return false;
+
+ entry->SetScreenshotPNGData(NULL);
+ return true;
+}
+
+void WebContentsScreenshotManager::PurgeScreenshotsIfNecessary() {
+ // Allow only a certain number of entries to keep screenshots.
+ const int kMaxScreenshots = 10;
+ int screenshot_count = GetScreenshotCount();
+ if (screenshot_count < kMaxScreenshots)
+ return;
+
+ const int current = owner_->GetCurrentEntryIndex();
+ const int num_entries = owner_->GetEntryCount();
+ int available_slots = kMaxScreenshots;
+ if (NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(current))
+ ->screenshot().get()) {
+ --available_slots;
+ }
+
+ // Keep screenshots closer to the current navigation entry, and purge the ones
+ // that are farther away from it. So in each step, look at the entries at
+ // each offset on both the back and forward history, and start counting them
+ // to make sure that the correct number of screenshots are kept in memory.
+ // Note that it is possible for some entries to be missing screenshots (e.g.
+ // when taking the screenshot failed for some reason). So there may be a state
+ // where there are a lot of entries in the back history, but none of them has
+ // any screenshot. In such cases, keep the screenshots for |kMaxScreenshots|
+ // entries in the forward history list.
+ int back = current - 1;
+ int forward = current + 1;
+ while (available_slots > 0 && (back >= 0 || forward < num_entries)) {
+ if (back >= 0) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(back));
+ if (entry->screenshot().get())
+ --available_slots;
+ --back;
+ }
+
+ if (available_slots > 0 && forward < num_entries) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(forward));
+ if (entry->screenshot().get())
+ --available_slots;
+ ++forward;
+ }
+ }
+
+ // Purge any screenshot at |back| or lower indices, and |forward| or higher
+ // indices.
+ while (screenshot_count > kMaxScreenshots && back >= 0) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(back));
+ if (ClearScreenshot(entry))
+ --screenshot_count;
+ --back;
+ }
+
+ while (screenshot_count > kMaxScreenshots && forward < num_entries) {
+ NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
+ owner_->GetEntryAtIndex(forward));
+ if (ClearScreenshot(entry))
+ --screenshot_count;
+ ++forward;
+ }
+ CHECK_GE(screenshot_count, 0);
+ CHECK_LE(screenshot_count, kMaxScreenshots);
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/web_contents_screenshot_manager.h b/content/browser/frame_host/web_contents_screenshot_manager.h
new file mode 100644
index 0000000..10339a2
--- /dev/null
+++ b/content/browser/frame_host/web_contents_screenshot_manager.h
@@ -0,0 +1,91 @@
+// Copyright 2013 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_WEB_CONTENTS_SCREENSHOT_MANAGER_H_
+#define CONTENT_BROWSER_FRAME_HOST_WEB_CONTENTS_SCREENSHOT_MANAGER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+class SkBitmap;
+
+namespace content {
+
+class NavigationControllerImpl;
+class NavigationEntryImpl;
+class RenderViewHost;
+class ScreenshotData;
+
+// WebContentsScreenshotManager takes care of taking image-captures for the
+// current navigation entry of a NavigationControllerImpl, and managing these
+// captured images. These image-captures are used for history navigation using
+// overscroll gestures.
+// TODO(nasko): Rename this to better reflect that it is used for
+// navigation entries and not WebContents.
+class CONTENT_EXPORT WebContentsScreenshotManager {
+ public:
+ explicit WebContentsScreenshotManager(NavigationControllerImpl* controller);
+ virtual ~WebContentsScreenshotManager();
+
+ // Takes a screenshot of the last-committed entry of the controller.
+ void TakeScreenshot();
+
+ // Clears screenshots of all navigation entries.
+ void ClearAllScreenshots();
+
+ protected:
+ virtual void TakeScreenshotImpl(RenderViewHost* host,
+ NavigationEntryImpl* entry);
+
+ // Called after a screenshot has been set on an NavigationEntryImpl.
+ // Overridden in tests to get notified of when a screenshot is set.
+ virtual void OnScreenshotSet(NavigationEntryImpl* entry);
+
+ NavigationControllerImpl* owner() { return owner_; }
+
+ void SetMinScreenshotIntervalMS(int interval_ms);
+
+ // The callback invoked when taking the screenshot of the page is complete.
+ // This sets the screenshot on the navigation entry.
+ void OnScreenshotTaken(int unique_id,
+ bool success,
+ const SkBitmap& bitmap);
+
+ // Returns the number of entries with screenshots.
+ int GetScreenshotCount() const;
+
+ private:
+ // This is called when the screenshot data has beene encoded to PNG in a
+ // worker thread.
+ void OnScreenshotEncodeComplete(int unique_id,
+ scoped_refptr<ScreenshotData> data);
+
+ // Removes the screenshot for the entry, returning true if the entry had a
+ // screenshot.
+ bool ClearScreenshot(NavigationEntryImpl* entry);
+
+ // The screenshots in the NavigationEntryImpls can accumulate and consume a
+ // large amount of memory. This function makes sure that the memory
+ // consumption is within a certain limit.
+ void PurgeScreenshotsIfNecessary();
+
+ // The navigation controller that owns this screenshot-manager.
+ NavigationControllerImpl* owner_;
+
+ // Taking a screenshot and encoding them can be async. So use a weakptr for
+ // the callback to make sure that the screenshot/encoding completion callback
+ // does not trigger on a destroyed WebContentsScreenshotManager.
+ base::WeakPtrFactory<WebContentsScreenshotManager> screenshot_factory_;
+
+ base::Time last_screenshot_time_;
+ int min_screenshot_interval_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsScreenshotManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_WEB_CONTENTS_SCREENSHOT_MANAGER_H_