diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-18 08:17:44 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-18 08:17:44 +0000 |
commit | 0dd3a0ab424a01e887ac7f69dbbae6aaee6843c0 (patch) | |
tree | e29d015f414f9a33fff40171b743261bd9f3c82f /content | |
parent | 9f60a236323e348ab1a0343d6acdafa99125ac3b (diff) | |
download | chromium_src-0dd3a0ab424a01e887ac7f69dbbae6aaee6843c0.zip chromium_src-0dd3a0ab424a01e887ac7f69dbbae6aaee6843c0.tar.gz chromium_src-0dd3a0ab424a01e887ac7f69dbbae6aaee6843c0.tar.bz2 |
Start moving core pieces of Chrome multi-process code to src\content. I'm starting with tab_contents directory.In future changes the headers that include these files will be updated. Once all the files are moved (i.e. renderer_host, rest of browser, renderer etc), then refactoring can begin so that content\DEPS doesn't have chrome in it.
Review URL: http://codereview.chromium.org/6537015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75369 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
36 files changed, 13390 insertions, 0 deletions
diff --git a/content/DEPS b/content/DEPS new file mode 100644 index 0000000..fb297a3 --- /dev/null +++ b/content/DEPS @@ -0,0 +1,23 @@ +# Do NOT add chrome to the list below. We shouldn't be including files from +# src/chrome in src/content. +include_rules = [ + "+app", + + # TEMPORARY ONLY WHILE WE REDUCE THE DEPENDENCIES. + # When the src\content refactoring is complete, this will be unnecessary (and + # in fact, a layering violation). + "+chrome", + + "+grit", + "+net", + + # Don't allow inclusion of these other libs we shouldn't be calling directly. + "-v8", + "-tools", + + # Allow inclusion of WebKit API files. + "+third_party/WebKit/Source/WebKit/chromium", + + "+ui", + "+webkit", +] diff --git a/content/OWNERS b/content/OWNERS new file mode 100644 index 0000000..f8773be --- /dev/null +++ b/content/OWNERS @@ -0,0 +1,4 @@ +ben@chromium.org +darin@chromium.org +jam@chromium.org +brettw@chromium.org diff --git a/content/browser/tab_contents/background_contents.cc b/content/browser/tab_contents/background_contents.cc new file mode 100644 index 0000000..d8c11e0 --- /dev/null +++ b/content/browser/tab_contents/background_contents.cc @@ -0,0 +1,269 @@ +// Copyright (c) 2011 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/tab_contents/background_contents.h" + +#include "chrome/browser/background_contents_service.h" +#include "chrome/browser/browsing_instance.h" +#include "chrome/browser/desktop_notification_handler.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/renderer_preferences_util.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/url_constants.h" +#include "chrome/common/view_types.h" +#include "chrome/common/render_messages_params.h" +#include "ui/gfx/rect.h" + +//////////////// +// BackgroundContents + +BackgroundContents::BackgroundContents(SiteInstance* site_instance, + int routing_id, + Delegate* delegate) + : delegate_(delegate) { + Profile* profile = site_instance->browsing_instance()->profile(); + + // TODO(rafaelw): Implement correct session storage. + render_view_host_ = new RenderViewHost(site_instance, this, routing_id, NULL); + render_view_host_->AllowScriptToClose(true); + + // Close ourselves when the application is shutting down. + registrar_.Add(this, NotificationType::APP_TERMINATING, + NotificationService::AllSources()); + + // Register for our parent profile to shutdown, so we can shut ourselves down + // as well (should only be called for OTR profiles, as we should receive + // APP_TERMINATING before non-OTR profiles are destroyed). + registrar_.Add(this, NotificationType::PROFILE_DESTROYED, + Source<Profile>(profile)); + desktop_notification_handler_.reset( + new DesktopNotificationHandler(NULL, site_instance->GetProcess())); +} + +// Exposed to allow creating mocks. +BackgroundContents::BackgroundContents() + : delegate_(NULL), + render_view_host_(NULL) { +} + +BackgroundContents::~BackgroundContents() { + if (!render_view_host_) // Will be null for unit tests. + return; + Profile* profile = render_view_host_->process()->profile(); + NotificationService::current()->Notify( + NotificationType::BACKGROUND_CONTENTS_DELETED, + Source<Profile>(profile), + Details<BackgroundContents>(this)); + render_view_host_->Shutdown(); // deletes render_view_host +} + +BackgroundContents* BackgroundContents::GetAsBackgroundContents() { + return this; +} + +RenderViewHostDelegate::View* BackgroundContents::GetViewDelegate() { + return this; +} + +const GURL& BackgroundContents::GetURL() const { + return url_; +} + +ViewType::Type BackgroundContents::GetRenderViewType() const { + return ViewType::BACKGROUND_CONTENTS; +} + +int BackgroundContents::GetBrowserWindowID() const { + return extension_misc::kUnknownWindowId; +} + +void BackgroundContents::DidNavigate( + RenderViewHost* render_view_host, + const ViewHostMsg_FrameNavigate_Params& params) { + // We only care when the outer frame changes. + if (!PageTransition::IsMainFrame(params.transition)) + return; + + // Note: because BackgroundContents are only available to extension apps, + // navigation is limited to urls within the app's extent. This is enforced in + // RenderView::decidePolicyForNaviation. If BackgroundContents become + // available as a part of the web platform, it probably makes sense to have + // some way to scope navigation of a background page to its opener's security + // origin. Note: if the first navigation is to a URL outside the app's + // extent a background page will be opened but will remain at about:blank. + url_ = params.url; + + Profile* profile = render_view_host->process()->profile(); + NotificationService::current()->Notify( + NotificationType::BACKGROUND_CONTENTS_NAVIGATED, + Source<Profile>(profile), + Details<BackgroundContents>(this)); +} + +void BackgroundContents::RunJavaScriptMessage( + const std::wstring& message, + const std::wstring& default_prompt, + const GURL& frame_url, + const int flags, + IPC::Message* reply_msg, + bool* did_suppress_message) { + // TODO(rafaelw): Implement, The JavaScriptModalDialog needs to learn about + // BackgroundContents. + *did_suppress_message = true; +} + +bool BackgroundContents::PreHandleKeyboardEvent( + const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) { + return false; +} + +void BackgroundContents::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + // TODO(rafaelw): Implement pagegroup ref-counting so that non-persistent + // background pages are closed when the last referencing frame is closed. + switch (type.value) { + case NotificationType::PROFILE_DESTROYED: + case NotificationType::APP_TERMINATING: { + delete this; + break; + } + default: + NOTREACHED() << "Unexpected notification sent."; + break; + } +} + +void BackgroundContents::OnMessageBoxClosed(IPC::Message* reply_msg, + bool success, + const std::wstring& prompt) { + render_view_host_->JavaScriptMessageBoxClosed(reply_msg, success, prompt); +} + +gfx::NativeWindow BackgroundContents::GetMessageBoxRootWindow() { + NOTIMPLEMENTED(); + return NULL; +} + +TabContents* BackgroundContents::AsTabContents() { + return NULL; +} + +ExtensionHost* BackgroundContents::AsExtensionHost() { + return NULL; +} + +void BackgroundContents::UpdateInspectorSetting(const std::string& key, + const std::string& value) { + Profile* profile = render_view_host_->process()->profile(); + RenderViewHostDelegateHelper::UpdateInspectorSetting(profile, key, value); +} + +void BackgroundContents::ClearInspectorSettings() { + Profile* profile = render_view_host_->process()->profile(); + RenderViewHostDelegateHelper::ClearInspectorSettings(profile); +} + +void BackgroundContents::Close(RenderViewHost* render_view_host) { + Profile* profile = render_view_host->process()->profile(); + NotificationService::current()->Notify( + NotificationType::BACKGROUND_CONTENTS_CLOSED, + Source<Profile>(profile), + Details<BackgroundContents>(this)); + delete this; +} + +void BackgroundContents::RenderViewGone(RenderViewHost* rvh, + base::TerminationStatus status, + int error_code) { + // Our RenderView went away, so we should go away also, so killing the process + // via the TaskManager doesn't permanently leave a BackgroundContents hanging + // around the system, blocking future instances from being created + // (http://crbug.com/65189). + delete this; +} + +bool BackgroundContents::OnMessageReceived(const IPC::Message& message) { + // Forward desktop notification IPCs if any to the + // DesktopNotificationHandler. + return desktop_notification_handler_->OnMessageReceived(message); +} + +RendererPreferences BackgroundContents::GetRendererPrefs( + Profile* profile) const { + RendererPreferences preferences; + renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile); + return preferences; +} + +WebPreferences BackgroundContents::GetWebkitPrefs() { + // TODO(rafaelw): Consider enabling the webkit_prefs.dom_paste_enabled for + // apps. + Profile* profile = render_view_host_->process()->profile(); + return RenderViewHostDelegateHelper::GetWebkitPrefs(profile, + false); // is_dom_ui +} + +void BackgroundContents::ProcessWebUIMessage( + const ViewHostMsg_DomMessage_Params& params) { + // TODO(rafaelw): It may make sense for extensions to be able to open + // BackgroundContents to chrome-extension://<id> pages. Consider implementing. + render_view_host_->BlockExtensionRequest(params.request_id); +} + +void BackgroundContents::CreateNewWindow( + int route_id, + const ViewHostMsg_CreateWindow_Params& params) { + delegate_view_helper_.CreateNewWindow( + route_id, + render_view_host_->process()->profile(), + render_view_host_->site_instance(), + WebUIFactory::GetWebUIType(render_view_host_->process()->profile(), url_), + this, + params.window_container_type, + params.frame_name); +} + +void BackgroundContents::CreateNewWidget(int route_id, + WebKit::WebPopupType popup_type) { + NOTREACHED(); +} + +void BackgroundContents::CreateNewFullscreenWidget(int route_id) { + NOTREACHED(); +} + +void BackgroundContents::ShowCreatedWindow(int route_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) { + TabContents* contents = delegate_view_helper_.GetCreatedWindow(route_id); + if (contents) + delegate_->AddTabContents(contents, disposition, initial_pos, user_gesture); +} + +void BackgroundContents::ShowCreatedWidget(int route_id, + const gfx::Rect& initial_pos) { + NOTIMPLEMENTED(); +} + +void BackgroundContents::ShowCreatedFullscreenWidget(int route_id) { + NOTIMPLEMENTED(); +} + +// static +BackgroundContents* +BackgroundContents::GetBackgroundContentsByID(int render_process_id, + int render_view_id) { + RenderViewHost* render_view_host = + RenderViewHost::FromID(render_process_id, render_view_id); + if (!render_view_host) + return NULL; + + return render_view_host->delegate()->GetAsBackgroundContents(); +} diff --git a/content/browser/tab_contents/background_contents.h b/content/browser/tab_contents/background_contents.h new file mode 100644 index 0000000..e4455fb --- /dev/null +++ b/content/browser/tab_contents/background_contents.h @@ -0,0 +1,186 @@ +// Copyright (c) 2011 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_TAB_CONTENTS_BACKGROUND_CONTENTS_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_BACKGROUND_CONTENTS_H_ +#pragma once + +#include <string> +#include <vector> + +#include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/browser/tab_contents/render_view_host_delegate_helper.h" +#include "chrome/browser/ui/app_modal_dialogs/js_modal_dialog.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/view_types.h" +#include "chrome/common/window_container_type.h" +#include "webkit/glue/window_open_disposition.h" + +class TabContents; +struct WebPreferences; +class DesktopNotificationHandler; + +namespace gfx { +class Rect; +} + +// This class is a peer of TabContents. It can host a renderer, but does not +// have any visible display. Its navigation is not managed by a +// NavigationController because is has no facility for navigating (other than +// programatically view window.location.href) or RenderViewHostManager because +// it is never allowed to navigate across a SiteInstance boundary. +class BackgroundContents : public RenderViewHostDelegate, + public RenderViewHostDelegate::View, + public NotificationObserver, + public JavaScriptAppModalDialogDelegate { + public: + class Delegate { + public: + // Called by ShowCreatedWindow. Asks the delegate to attach the opened + // TabContents to a suitable container (e.g. browser) or to show it if it's + // a popup window. + virtual void AddTabContents(TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) = 0; + + protected: + virtual ~Delegate() {} + }; + + BackgroundContents(SiteInstance* site_instance, + int routing_id, + Delegate* delegate); + virtual ~BackgroundContents(); + + // Provide access to the RenderViewHost for the + // RenderViewHostDelegateViewHelper + RenderViewHost* render_view_host() { return render_view_host_; } + + // RenderViewHostDelegate implementation. + virtual BackgroundContents* GetAsBackgroundContents(); + virtual RenderViewHostDelegate::View* GetViewDelegate(); + virtual const GURL& GetURL() const; + virtual ViewType::Type GetRenderViewType() const; + virtual int GetBrowserWindowID() const; + virtual void DidNavigate(RenderViewHost* render_view_host, + const ViewHostMsg_FrameNavigate_Params& params); + virtual WebPreferences GetWebkitPrefs(); + virtual void ProcessWebUIMessage(const ViewHostMsg_DomMessage_Params& params); + virtual void RunJavaScriptMessage(const std::wstring& message, + const std::wstring& default_prompt, + const GURL& frame_url, + const int flags, + IPC::Message* reply_msg, + bool* did_suppress_message); + virtual void Close(RenderViewHost* render_view_host); + virtual RendererPreferences GetRendererPrefs(Profile* profile) const; + virtual void RenderViewGone(RenderViewHost* rvh, + base::TerminationStatus status, + int error_code); + virtual bool OnMessageReceived(const IPC::Message& message); + + // RenderViewHostDelegate::View + virtual void CreateNewWindow( + int route_id, + const ViewHostMsg_CreateWindow_Params& params); + virtual void CreateNewWidget(int route_id, WebKit::WebPopupType popup_type); + virtual void CreateNewFullscreenWidget(int route_id); + virtual void ShowCreatedWindow(int route_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture); + virtual void ShowCreatedWidget(int route_id, + const gfx::Rect& initial_pos); + virtual void ShowCreatedFullscreenWidget(int route_id); + virtual void ShowContextMenu(const ContextMenuParams& params) {} + virtual void ShowPopupMenu(const gfx::Rect& bounds, + int item_height, + double item_font_size, + int selected_item, + const std::vector<WebMenuItem>& items, + bool right_aligned) {} + virtual void StartDragging(const WebDropData& drop_data, + WebKit::WebDragOperationsMask allowed_operations, + const SkBitmap& image, + const gfx::Point& image_offset) {} + virtual void UpdateDragCursor(WebKit::WebDragOperation operation) {} + virtual void GotFocus() {} + virtual void TakeFocus(bool reverse) {} + virtual void LostCapture() {} + virtual void Activate() {} + virtual void Deactivate() {} + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut); + virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {} + virtual void HandleMouseMove() {} + virtual void HandleMouseDown() {} + virtual void HandleMouseLeave() {} + virtual void HandleMouseUp() {} + virtual void HandleMouseActivate() {} + virtual void UpdatePreferredSize(const gfx::Size& new_size) {} + + // NotificationObserver + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Overridden from JavaScriptAppModalDialogDelegate: + virtual void OnMessageBoxClosed(IPC::Message* reply_msg, + bool success, + const std::wstring& prompt); + virtual void SetSuppressMessageBoxes(bool suppress_message_boxes) {} + virtual gfx::NativeWindow GetMessageBoxRootWindow(); + virtual TabContents* AsTabContents(); + virtual ExtensionHost* AsExtensionHost(); + + virtual void UpdateInspectorSetting(const std::string& key, + const std::string& value); + virtual void ClearInspectorSettings(); + + // Helper to find the BackgroundContents that originated the given request. + // Can be NULL if the page has been closed or some other error occurs. + // Should only be called from the UI thread, since it accesses + // BackgroundContents. + static BackgroundContents* GetBackgroundContentsByID(int render_process_id, + int render_view_id); + + protected: + // Exposed for testing. + BackgroundContents(); + + private: + // The delegate for this BackgroundContents. + Delegate* delegate_; + + // The host for our HTML content. + RenderViewHost* render_view_host_; + + // Common implementations of some RenderViewHostDelegate::View methods. + RenderViewHostDelegateViewHelper delegate_view_helper_; + + // The URL being hosted. + GURL url_; + + NotificationRegistrar registrar_; + + // Handles desktop notification IPCs. + scoped_ptr<DesktopNotificationHandler> desktop_notification_handler_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundContents); +}; + +// This is the data sent out as the details with BACKGROUND_CONTENTS_OPENED. +struct BackgroundContentsOpenedDetails { + // The BackgroundContents object that has just been opened. + BackgroundContents* contents; + + // The name of the parent frame for these contents. + const string16& frame_name; + + // The ID of the parent application (if any). + const string16& application_id; +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_BACKGROUND_CONTENTS_H_ diff --git a/content/browser/tab_contents/constrained_window.h b/content/browser/tab_contents/constrained_window.h new file mode 100644 index 0000000..7dd0b40 --- /dev/null +++ b/content/browser/tab_contents/constrained_window.h @@ -0,0 +1,62 @@ +// Copyright (c) 2006-2008 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_TAB_CONTENTS_CONSTRAINED_WINDOW_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_CONSTRAINED_WINDOW_H_ +#pragma once + +#include "build/build_config.h" + +// The different platform specific subclasses use different delegates for their +// dialogs. +#if defined(OS_WIN) +namespace views { +class WindowDelegate; +class DialogDelegate; +} +typedef views::WindowDelegate ConstrainedWindowDelegate; +typedef views::DialogDelegate ConstrainedDialogDelegate; +#elif defined(OS_MACOSX) +class ConstrainedWindowMacDelegate; +class ConstrainedWindowMacDelegateSystemSheet; +typedef ConstrainedWindowMacDelegate ConstrainedWindowDelegate; +typedef ConstrainedWindowMacDelegateSystemSheet ConstrainedDialogDelegate; +#elif defined(TOOLKIT_USES_GTK) +class ConstrainedWindowGtkDelegate; +typedef ConstrainedWindowGtkDelegate ConstrainedWindowDelegate; +typedef ConstrainedWindowGtkDelegate ConstrainedDialogDelegate; +#endif + +class TabContents; + +/////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindow +// +// This interface represents a window that is constrained to a TabContents' +// bounds. +// +class ConstrainedWindow { + public: + // Create a Constrained Window that contains a platform specific client + // area. Typical uses include the HTTP Basic Auth prompt. The caller must + // provide a delegate to describe the content area and to respond to events. + static ConstrainedWindow* CreateConstrainedDialog( + TabContents* owner, + ConstrainedWindowDelegate* delegate); + + // Makes the Constrained Window visible. Only one Constrained Window is shown + // at a time per tab. + virtual void ShowConstrainedWindow() = 0; + + // Closes the Constrained Window. + virtual void CloseConstrainedWindow() = 0; + + // Sets focus on the Constrained Window. + virtual void FocusConstrainedWindow() {} + + protected: + virtual ~ConstrainedWindow() {} +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_CONSTRAINED_WINDOW_H_ diff --git a/content/browser/tab_contents/infobar_delegate.cc b/content/browser/tab_contents/infobar_delegate.cc new file mode 100644 index 0000000..77b76c9 --- /dev/null +++ b/content/browser/tab_contents/infobar_delegate.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2011 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/tab_contents/infobar_delegate.h" + +#include "base/logging.h" +#include "build/build_config.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/browser/tab_contents/navigation_controller.h" +#include "content/browser/tab_contents/tab_contents.h" + +// InfoBarDelegate ------------------------------------------------------------ + +InfoBarDelegate::~InfoBarDelegate() { +} + +bool InfoBarDelegate::EqualsDelegate(InfoBarDelegate* delegate) const { + return false; +} + +bool InfoBarDelegate::ShouldExpire( + const NavigationController::LoadCommittedDetails& details) const { + return (contents_unique_id_ != details.entry->unique_id()) || + (PageTransition::StripQualifier(details.entry->transition_type()) == + PageTransition::RELOAD); +} + +void InfoBarDelegate::InfoBarDismissed() { +} + +void InfoBarDelegate::InfoBarClosed() { +} + +SkBitmap* InfoBarDelegate::GetIcon() const { + return NULL; +} + +InfoBarDelegate::Type InfoBarDelegate::GetInfoBarType() const { + return WARNING_TYPE; +} + +ConfirmInfoBarDelegate* InfoBarDelegate::AsConfirmInfoBarDelegate() { + return NULL; +} + +CrashedExtensionInfoBarDelegate* + InfoBarDelegate::AsCrashedExtensionInfoBarDelegate() { + return NULL; +} + +ExtensionInfoBarDelegate* InfoBarDelegate::AsExtensionInfoBarDelegate() { + return NULL; +} + +LinkInfoBarDelegate* InfoBarDelegate::AsLinkInfoBarDelegate() { + return NULL; +} + +PluginInstallerInfoBarDelegate* + InfoBarDelegate::AsPluginInstallerInfoBarDelegate() { + return NULL; +} + +ThemeInstalledInfoBarDelegate* + InfoBarDelegate::AsThemePreviewInfobarDelegate() { + return NULL; +} + +TranslateInfoBarDelegate* InfoBarDelegate::AsTranslateInfoBarDelegate() { + return NULL; +} + +InfoBarDelegate::InfoBarDelegate(TabContents* contents) + : contents_unique_id_(0) { + if (contents) + StoreActiveEntryUniqueID(contents); +} + +void InfoBarDelegate::StoreActiveEntryUniqueID(TabContents* contents) { + NavigationEntry* active_entry = contents->controller().GetActiveEntry(); + contents_unique_id_ = active_entry ? active_entry->unique_id() : 0; +} diff --git a/content/browser/tab_contents/infobar_delegate.h b/content/browser/tab_contents/infobar_delegate.h new file mode 100644 index 0000000..b4c6921 --- /dev/null +++ b/content/browser/tab_contents/infobar_delegate.h @@ -0,0 +1,118 @@ +// Copyright (c) 2011 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_TAB_CONTENTS_INFOBAR_DELEGATE_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_INFOBAR_DELEGATE_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/string16.h" +#include "content/browser/tab_contents/navigation_controller.h" +#include "webkit/glue/window_open_disposition.h" + +class ConfirmInfoBarDelegate; +class CrashedExtensionInfoBarDelegate; +class ExtensionInfoBarDelegate; +class InfoBar; +class LinkInfoBarDelegate; +class PluginInstallerInfoBarDelegate; +class SkBitmap; +class ThemeInstalledInfoBarDelegate; +class TranslateInfoBarDelegate; + +// An interface implemented by objects wishing to control an InfoBar. +// Implementing this interface is not sufficient to use an InfoBar, since it +// does not map to a specific InfoBar type. Instead, you must implement either +// LinkInfoBarDelegate or ConfirmInfoBarDelegate, or override with your own +// delegate for your own InfoBar variety. +// +// --- WARNING --- +// When creating your InfoBarDelegate subclass, it is recommended that you +// design it such that you instantiate a brand new delegate for every call to +// AddInfoBar, rather than re-using/sharing a delegate object. Otherwise, +// you need to consider the fact that more than one InfoBar instance can exist +// and reference the same delegate -- even though it is also true that we only +// ever fully show one infobar (they don't stack). The dual-references occur +// because a second InfoBar can be added while the first one is in the process +// of closing (the animations). This can cause problems because when the first +// one does finally fully close InfoBarDelegate::InfoBarClosed() is called, +// and the delegate is free to clean itself up or reset state, which may have +// fatal consequences for the InfoBar that was in the process of opening (or is +// now fully opened) -- it is referencing a delegate that may not even exist +// anymore. +// As such, it is generally much safer to dedicate a delegate instance to +// AddInfoBar! +class InfoBarDelegate { + public: + // The type of the infobar. It controls its appearance, such as its background + // color. + enum Type { + WARNING_TYPE, + PAGE_ACTION_TYPE, + }; + + virtual ~InfoBarDelegate(); + + // Called to create the InfoBar. Implementation of this method is + // platform-specific. + virtual InfoBar* CreateInfoBar() = 0; + + // Returns true if the supplied |delegate| is equal to this one. Equality is + // left to the implementation to define. This function is called by the + // TabContents when determining whether or not a delegate should be added + // because a matching one already exists. If this function returns true, the + // TabContents will not add the new delegate because it considers one to + // already be present. + virtual bool EqualsDelegate(InfoBarDelegate* delegate) const; + + // Returns true if the InfoBar should be closed automatically after the page + // is navigated. The default behavior is to return true if the page is + // navigated somewhere else or reloaded. + virtual bool ShouldExpire( + const NavigationController::LoadCommittedDetails& details) const; + + // Called when the user clicks on the close button to dismiss the infobar. + virtual void InfoBarDismissed(); + + // Called after the InfoBar is closed. The delegate is free to delete itself + // at this point. + virtual void InfoBarClosed(); + + // Return the icon to be shown for this InfoBar. If the returned bitmap is + // NULL, no icon is shown. + virtual SkBitmap* GetIcon() const; + + // Returns the type of the infobar. The type determines the appearance (such + // as background color) of the infobar. + virtual Type GetInfoBarType() const; + + // Type-checking downcast routines: + virtual ConfirmInfoBarDelegate* AsConfirmInfoBarDelegate(); + virtual CrashedExtensionInfoBarDelegate* AsCrashedExtensionInfoBarDelegate(); + virtual ExtensionInfoBarDelegate* AsExtensionInfoBarDelegate(); + virtual LinkInfoBarDelegate* AsLinkInfoBarDelegate(); + virtual PluginInstallerInfoBarDelegate* AsPluginInstallerInfoBarDelegate(); + virtual ThemeInstalledInfoBarDelegate* AsThemePreviewInfobarDelegate(); + virtual TranslateInfoBarDelegate* AsTranslateInfoBarDelegate(); + + protected: + // Provided to subclasses as a convenience to initialize the state of this + // object. If |contents| is non-NULL, its active entry's unique ID will be + // stored using StoreActiveEntryUniqueID automatically. + explicit InfoBarDelegate(TabContents* contents); + + // Store the unique id for the active entry in the specified TabContents, to + // be used later upon navigation to determine if this InfoBarDelegate should + // be expired from |contents_|. + void StoreActiveEntryUniqueID(TabContents* contents); + + private: + // The unique id of the active NavigationEntry of the TabContents that we were + // opened for. Used to help expire on navigations. + int contents_unique_id_; + + DISALLOW_COPY_AND_ASSIGN(InfoBarDelegate); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_INFOBAR_DELEGATE_H_ diff --git a/content/browser/tab_contents/interstitial_page.cc b/content/browser/tab_contents/interstitial_page.cc new file mode 100644 index 0000000..d13965e --- /dev/null +++ b/content/browser/tab_contents/interstitial_page.cc @@ -0,0 +1,732 @@ +// Copyright (c) 2011 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/tab_contents/interstitial_page.h" + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/threading/thread.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/dom_operation_notification_details.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/render_widget_host_view.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/renderer_preferences_util.h" +#include "chrome/common/bindings_policy.h" +#include "chrome/common/dom_storage_common.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "chrome/common/notification_source.h" +#include "content/browser/tab_contents/navigation_controller.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_view.h" +#include "net/base/escape.h" + +#if defined(TOOLKIT_GTK) +#include "chrome/browser/ui/gtk/gtk_theme_provider.h" +#endif + +using WebKit::WebDragOperation; +using WebKit::WebDragOperationsMask; + +namespace { + +class ResourceRequestTask : public Task { + public: + ResourceRequestTask(int process_id, + int render_view_host_id, + ResourceRequestAction action) + : action_(action), + process_id_(process_id), + render_view_host_id_(render_view_host_id), + resource_dispatcher_host_( + g_browser_process->resource_dispatcher_host()) { + } + + virtual void Run() { + switch (action_) { + case BLOCK: + resource_dispatcher_host_->BlockRequestsForRoute( + process_id_, render_view_host_id_); + break; + case RESUME: + resource_dispatcher_host_->ResumeBlockedRequestsForRoute( + process_id_, render_view_host_id_); + break; + case CANCEL: + resource_dispatcher_host_->CancelBlockedRequestsForRoute( + process_id_, render_view_host_id_); + break; + default: + NOTREACHED(); + } + } + + private: + ResourceRequestAction action_; + int process_id_; + int render_view_host_id_; + ResourceDispatcherHost* resource_dispatcher_host_; + + DISALLOW_COPY_AND_ASSIGN(ResourceRequestTask); +}; + +} // namespace + +class InterstitialPage::InterstitialPageRVHViewDelegate + : public RenderViewHostDelegate::View { + public: + explicit InterstitialPageRVHViewDelegate(InterstitialPage* page); + + // RenderViewHostDelegate::View implementation: + virtual void CreateNewWindow( + int route_id, + const ViewHostMsg_CreateWindow_Params& params); + virtual void CreateNewWidget(int route_id, + WebKit::WebPopupType popup_type); + virtual void CreateNewFullscreenWidget(int route_id); + virtual void ShowCreatedWindow(int route_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture); + virtual void ShowCreatedWidget(int route_id, + const gfx::Rect& initial_pos); + virtual void ShowCreatedFullscreenWidget(int route_id); + virtual void ShowContextMenu(const ContextMenuParams& params); + virtual void ShowPopupMenu(const gfx::Rect& bounds, + int item_height, + double item_font_size, + int selected_item, + const std::vector<WebMenuItem>& items, + bool right_aligned); + virtual void StartDragging(const WebDropData& drop_data, + WebDragOperationsMask operations_allowed, + const SkBitmap& image, + const gfx::Point& image_offset); + virtual void UpdateDragCursor(WebDragOperation operation); + virtual void GotFocus(); + virtual void TakeFocus(bool reverse); + virtual void LostCapture(); + virtual void Activate(); + virtual void Deactivate(); + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut); + virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event); + virtual void HandleMouseMove(); + virtual void HandleMouseDown(); + virtual void HandleMouseLeave(); + virtual void HandleMouseUp(); + virtual void HandleMouseActivate(); + virtual void OnFindReply(int request_id, + int number_of_matches, + const gfx::Rect& selection_rect, + int active_match_ordinal, + bool final_update); + virtual void UpdatePreferredSize(const gfx::Size& pref_size); + + private: + InterstitialPage* interstitial_page_; + + DISALLOW_COPY_AND_ASSIGN(InterstitialPageRVHViewDelegate); +}; + +// static +InterstitialPage::InterstitialPageMap* + InterstitialPage::tab_to_interstitial_page_ = NULL; + +InterstitialPage::InterstitialPage(TabContents* tab, + bool new_navigation, + const GURL& url) + : tab_(tab), + 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_(tab->render_view_host()->process()->id()), + original_rvh_id_(tab->render_view_host()->routing_id()), + should_revert_tab_title_(false), + resource_dispatcher_host_notified_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(rvh_view_delegate_( + new InterstitialPageRVHViewDelegate(this))) { + renderer_preferences_util::UpdateFromSystemSettings( + &renderer_preferences_, tab_->profile()); + + 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 || !tab->controller().pending_entry()); +} + +InterstitialPage::~InterstitialPage() { + InterstitialPageMap::iterator iter = tab_to_interstitial_page_->find(tab_); + DCHECK(iter != tab_to_interstitial_page_->end()); + if (iter != tab_to_interstitial_page_->end()) + tab_to_interstitial_page_->erase(iter); + DCHECK(!render_view_host_); +} + +void InterstitialPage::Show() { + // 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 = + tab_to_interstitial_page_->find(tab_); + if (iter != tab_to_interstitial_page_->end()) { + InterstitialPage* 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_TAB_CONTENTS_DESTROYED as at that point the RenderViewHost has + // already been destroyed. + notification_registrar_.Add( + this, NotificationType::RENDER_WIDGET_HOST_DESTROYED, + Source<RenderWidgetHost>(tab_->render_view_host())); + + // Update the tab_to_interstitial_page_ map. + iter = tab_to_interstitial_page_->find(tab_); + DCHECK(iter == tab_to_interstitial_page_->end()); + (*tab_to_interstitial_page_)[tab_] = this; + + if (new_navigation_) { + NavigationEntry* entry = new NavigationEntry; + entry->set_url(url_); + entry->set_virtual_url(url_); + entry->set_page_type(INTERSTITIAL_PAGE); + + // Give sub-classes a chance to set some states on the navigation entry. + UpdateEntry(entry); + + tab_->controller().AddTransientEntry(entry); + } + + DCHECK(!render_view_host_); + render_view_host_ = CreateRenderViewHost(); + CreateTabContentsView(); + + std::string data_url = "data:text/html;charset=utf-8," + + EscapePath(GetHTMLContents()); + render_view_host_->NavigateToURL(GURL(data_url)); + + notification_registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, + Source<TabContents>(tab_)); + notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, + Source<NavigationController>(&tab_->controller())); + notification_registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING, + Source<NavigationController>(&tab_->controller())); +} + +void InterstitialPage::Hide() { + RenderWidgetHostView* old_view = tab_->render_view_host()->view(); + if (old_view && !old_view->IsShowing()) { + // 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_->view() && render_view_host_->view()->HasFocus() && + tab_->render_view_host()->view()) { + tab_->render_view_host()->view()->Focus(); + } + + render_view_host_->Shutdown(); + render_view_host_ = NULL; + if (tab_->interstitial_page()) + tab_->remove_interstitial_page(); + // Let's revert to the original title if necessary. + NavigationEntry* entry = tab_->controller().GetActiveEntry(); + if (!new_navigation_ && should_revert_tab_title_) { + entry->set_title(WideToUTF16Hack(original_tab_title_)); + tab_->NotifyNavigationStateChanged(TabContents::INVALIDATE_TITLE); + } + delete this; +} + +void InterstitialPage::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::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 NotificationType::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 = Source<RenderViewHost>(source).ptr(); + DCHECK(rvh->process()->id() == original_child_id_ && + rvh->routing_id() == original_rvh_id_); + TakeActionOnResourceDispatcher(CANCEL); + } + break; + case NotificationType::TAB_CONTENTS_DESTROYED: + case NotificationType::NAV_ENTRY_COMMITTED: + 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(); + // WARNING: we are now deleted! + } + break; + default: + NOTREACHED(); + } +} + +RenderViewHostDelegate::View* InterstitialPage::GetViewDelegate() { + return rvh_view_delegate_.get(); +} + +const GURL& InterstitialPage::GetURL() const { + return url_; +} + +void InterstitialPage::RenderViewGone(RenderViewHost* render_view_host, + base::TerminationStatus status, + int error_code) { + // Our renderer died. This should not happen in normal cases. + // Just dismiss the interstitial. + DontProceed(); +} + +void InterstitialPage::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; + } + + // The RenderViewHost has loaded its contents, we can show it now. + render_view_host_->view()->Show(); + tab_->set_interstitial_page(this); + + RenderWidgetHostView* rwh_view = tab_->render_view_host()->view(); + + // 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 NOTIFY_LOAD_STOP notification, 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. + tab_->SetIsLoading(false, NULL); +} + +void InterstitialPage::UpdateTitle(RenderViewHost* render_view_host, + int32 page_id, + const std::wstring& title) { + DCHECK(render_view_host == render_view_host_); + NavigationEntry* entry = tab_->controller().GetActiveEntry(); + 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_tab_title_) { + original_tab_title_ = UTF16ToWideHack(entry->title()); + should_revert_tab_title_ = true; + } + entry->set_title(WideToUTF16Hack(title)); + tab_->NotifyNavigationStateChanged(TabContents::INVALIDATE_TITLE); +} + +void InterstitialPage::DomOperationResponse(const std::string& json_string, + int automation_id) { + if (enabled_) + CommandReceived(json_string); +} + +RendererPreferences InterstitialPage::GetRendererPrefs(Profile* profile) const { + return renderer_preferences_; +} + +RenderViewHost* InterstitialPage::CreateRenderViewHost() { + RenderViewHost* render_view_host = new RenderViewHost( + SiteInstance::CreateSiteInstance(tab()->profile()), + this, MSG_ROUTING_NONE, kInvalidSessionStorageNamespaceId); + return render_view_host; +} + +TabContentsView* InterstitialPage::CreateTabContentsView() { + TabContentsView* tab_contents_view = tab()->view(); + RenderWidgetHostView* view = + tab_contents_view->CreateViewForWidget(render_view_host_); + render_view_host_->set_view(view); + render_view_host_->AllowBindings(BindingsPolicy::DOM_AUTOMATION); + + render_view_host_->CreateRenderView(string16()); + view->SetSize(tab_contents_view->GetContainerSize()); + // Don't show the interstitial until we have navigated to it. + view->Hide(); + return tab_contents_view; +} + +void InterstitialPage::Proceed() { + if (action_taken_ != NO_ACTION) { + NOTREACHED(); + return; + } + Disable(); + action_taken_ = PROCEED_ACTION; + + // Resumes the throbber. + tab_->SetIsLoading(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(); + // WARNING: we are now deleted! + } +} + +std::string InterstitialPage::GetHTMLContents() { + return std::string(); +} + +void InterstitialPage::DontProceed() { + 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. + tab_->controller().DiscardNonCommittedEntries(); + } + + if (reload_on_dont_proceed_) + tab_->controller().Reload(true); + + Hide(); + // WARNING: we are now deleted! +} + +void InterstitialPage::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 InterstitialPage::SetSize(const gfx::Size& size) { +#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_->view()) + render_view_host_->view()->SetSize(size); +#else + // TODO(port): Does Mac need to SetSize? + NOTIMPLEMENTED(); +#endif +} + +void InterstitialPage::Focus() { + // Focus the native window. + render_view_host_->view()->Focus(); +} + +void InterstitialPage::FocusThroughTabTraversal(bool reverse) { + render_view_host_->SetInitialFocus(reverse); +} + +ViewType::Type InterstitialPage::GetRenderViewType() const { + return ViewType::INTERSTITIAL_PAGE; +} + +void InterstitialPage::Disable() { + enabled_ = false; +} + +void InterstitialPage::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 ResourceDispatcherHost, as when unit-tests + // we don't have one. + RenderViewHost* rvh = RenderViewHost::FromID(original_child_id_, + original_rvh_id_); + if (!rvh || !g_browser_process->resource_dispatcher_host()) + return; + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + new ResourceRequestTask(original_child_id_, original_rvh_id_, action)); +} + +// static +void InterstitialPage::InitInterstitialPageMap() { + if (!tab_to_interstitial_page_) + tab_to_interstitial_page_ = new InterstitialPageMap; +} + +// static +InterstitialPage* InterstitialPage::GetInterstitialPage( + TabContents* tab_contents) { + InitInterstitialPageMap(); + InterstitialPageMap::const_iterator iter = + tab_to_interstitial_page_->find(tab_contents); + if (iter == tab_to_interstitial_page_->end()) + return NULL; + + return iter->second; +} + +InterstitialPage::InterstitialPageRVHViewDelegate:: + InterstitialPageRVHViewDelegate(InterstitialPage* page) + : interstitial_page_(page) { +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::CreateNewWindow( + int route_id, + const ViewHostMsg_CreateWindow_Params& params) { + NOTREACHED() << "InterstitialPage does not support showing popups yet."; +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::CreateNewWidget( + int route_id, WebKit::WebPopupType popup_type) { + NOTREACHED() << "InterstitialPage does not support showing drop-downs yet."; +} + +void +InterstitialPage::InterstitialPageRVHViewDelegate::CreateNewFullscreenWidget( + int route_id) { + NOTREACHED() + << "InterstitialPage does not support showing full screen popups."; +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::ShowCreatedWindow( + int route_id, WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, bool user_gesture) { + NOTREACHED() << "InterstitialPage does not support showing popups yet."; +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::ShowCreatedWidget( + int route_id, const gfx::Rect& initial_pos) { + NOTREACHED() << "InterstitialPage does not support showing drop-downs yet."; +} + +void +InterstitialPage::InterstitialPageRVHViewDelegate::ShowCreatedFullscreenWidget( + int route_id) { + NOTREACHED() + << "InterstitialPage does not support showing full screen popups."; +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::ShowContextMenu( + const ContextMenuParams& params) { +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::ShowPopupMenu( + const gfx::Rect& bounds, + int item_height, + double item_font_size, + int selected_item, + const std::vector<WebMenuItem>& items, + bool right_aligned) { +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::StartDragging( + const WebDropData& drop_data, + WebDragOperationsMask allowed_operations, + const SkBitmap& image, + const gfx::Point& image_offset) { + NOTREACHED() << "InterstitialPage does not support dragging yet."; +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::UpdateDragCursor( + WebDragOperation) { + NOTREACHED() << "InterstitialPage does not support dragging yet."; +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::GotFocus() { +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::UpdatePreferredSize( + const gfx::Size& pref_size) { +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::TakeFocus( + bool reverse) { + if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate()) + interstitial_page_->tab()->GetViewDelegate()->TakeFocus(reverse); +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::LostCapture() { +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::Activate() { + if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate()) + interstitial_page_->tab()->GetViewDelegate()->Activate(); +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::Deactivate() { + if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate()) + interstitial_page_->tab()->GetViewDelegate()->Deactivate(); +} + +bool InterstitialPage::InterstitialPageRVHViewDelegate::PreHandleKeyboardEvent( + const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { + if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate()) + return interstitial_page_->tab()->GetViewDelegate()->PreHandleKeyboardEvent( + event, is_keyboard_shortcut); + return false; +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::HandleKeyboardEvent( + const NativeWebKeyboardEvent& event) { + if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate()) + interstitial_page_->tab()->GetViewDelegate()->HandleKeyboardEvent(event); +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseMove() { + if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate()) + interstitial_page_->tab()->GetViewDelegate()->HandleMouseMove(); +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseDown() { + if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate()) + interstitial_page_->tab()->GetViewDelegate()->HandleMouseDown(); +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseLeave() { + if (interstitial_page_->tab() && interstitial_page_->tab()->GetViewDelegate()) + interstitial_page_->tab()->GetViewDelegate()->HandleMouseLeave(); +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseUp() { +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::HandleMouseActivate() { +} + +void InterstitialPage::InterstitialPageRVHViewDelegate::OnFindReply( + int request_id, int number_of_matches, const gfx::Rect& selection_rect, + int active_match_ordinal, bool final_update) { +} + +int InterstitialPage::GetBrowserWindowID() const { + return tab_->GetBrowserWindowID(); +} + +void InterstitialPage::UpdateInspectorSetting(const std::string& key, + const std::string& value) { + RenderViewHostDelegateHelper::UpdateInspectorSetting( + tab_->profile(), key, value); +} + +void InterstitialPage::ClearInspectorSettings() { + RenderViewHostDelegateHelper::ClearInspectorSettings(tab_->profile()); +} diff --git a/content/browser/tab_contents/interstitial_page.h b/content/browser/tab_contents/interstitial_page.h new file mode 100644 index 0000000..b3f9e1c --- /dev/null +++ b/content/browser/tab_contents/interstitial_page.h @@ -0,0 +1,245 @@ +// Copyright (c) 2009 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_TAB_CONTENTS_INTERSTITIAL_PAGE_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_INTERSTITIAL_PAGE_H_ +#pragma once + +#include <map> +#include <string> + +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/renderer_preferences.h" +#include "googleurl/src/gurl.h" +#include "ui/gfx/size.h" + +class NavigationEntry; +class TabContents; +class TabContentsView; + +// This class is a base class for interstitial pages, pages that show some +// informative message asking for user validation before reaching the target +// page. (Navigating to a page served over bad HTTPS or a page containing +// malware are typical cases where an interstitial is required.) +// +// If specified in its constructor, this class creates a navigation entry so +// that when the interstitial shows, the current entry is the target URL. +// +// InterstitialPage instances take care of deleting themselves when closed +// through a navigation, the TabContents closing them or the tab containing them +// being closed. + +enum ResourceRequestAction { + BLOCK, + RESUME, + CANCEL +}; + +class InterstitialPage : public NotificationObserver, + public RenderViewHostDelegate { + 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. + }; + + // Creates an interstitial page to show in |tab|. |new_navigation| should be + // set to true when the interstitial is caused by loading a new page, in which + // case a temporary navigation entry is created with the URL |url| and + // added to the navigation controller (so the interstitial page appears as a + // new navigation entry). |new_navigation| should be false when the + // interstitial was triggered by a loading a sub-resource in a page. + InterstitialPage(TabContents* tab, bool new_navigation, const GURL& url); + virtual ~InterstitialPage(); + + // Shows the interstitial page in the tab. + virtual void Show(); + + // Hides the interstitial page. Warning: this deletes the InterstitialPage. + void Hide(); + + // Retrieves the InterstitialPage if any associated with the specified + // |tab_contents| (used by ui tests). + static InterstitialPage* GetInterstitialPage(TabContents* tab_contents); + + // Sub-classes should return the HTML that should be displayed in the page. + virtual std::string GetHTMLContents(); + + // Reverts to the page showing before the interstitial. + // Sub-classes should call this method when the user has chosen NOT to proceed + // to the target URL. + // Warning: if |new_navigation| was set to true in the constructor, 'this' + // will be deleted when this method returns. + virtual void DontProceed(); + + // Sub-classes should call this method when the user has chosen to proceed to + // the target URL. + // Warning: 'this' has been deleted when this method returns. + virtual void Proceed(); + + // 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(); + + // Sizes the RenderViewHost showing the actual interstitial page contents. + void SetSize(const gfx::Size& size); + + ActionState action_taken() const { return action_taken_; } + + // Sets the focus to the interstitial. + void Focus(); + + // Focus the first (last if reverse is true) element in the interstitial page. + // Called when tab traversing. + void FocusThroughTabTraversal(bool reverse); + + virtual ViewType::Type GetRenderViewType() const; + virtual int GetBrowserWindowID() const; + + // 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_; } + + virtual void UpdateInspectorSetting(const std::string& key, + const std::string& value); + virtual void ClearInspectorSettings(); + + protected: + // NotificationObserver method: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // RenderViewHostDelegate implementation: + virtual View* GetViewDelegate(); + virtual const GURL& GetURL() const; + virtual void RenderViewGone(RenderViewHost* render_view_host, + base::TerminationStatus status, + int error_code); + virtual void DidNavigate(RenderViewHost* render_view_host, + const ViewHostMsg_FrameNavigate_Params& params); + virtual void UpdateTitle(RenderViewHost* render_view_host, + int32 page_id, + const std::wstring& title); + virtual void DomOperationResponse(const std::string& json_string, + int automation_id); + virtual RendererPreferences GetRendererPrefs(Profile* profile) const; + + // Invoked when the page sent a command through DOMAutomation. + virtual void CommandReceived(const std::string& command) {} + + // Invoked with the NavigationEntry that is going to be added to the + // navigation controller. + // Gives an opportunity to sub-classes to set states on the |entry|. + // Note that this is only called if the InterstitialPage was constructed with + // |create_navigation_entry| set to true. + virtual void UpdateEntry(NavigationEntry* entry) {} + + TabContents* tab() const { return tab_; } + const GURL& url() const { return url_; } + RenderViewHost* render_view_host() const { return render_view_host_; } + + // Creates the RenderViewHost containing the interstitial content. + // Overriden in unit tests. + virtual RenderViewHost* CreateRenderViewHost(); + + // Creates the TabContentsView that shows the interstitial RVH. + // Overriden in unit tests. + virtual TabContentsView* CreateTabContentsView(); + + private: + // AutomationProvider needs access to Proceed and DontProceed to simulate + // user actions. + friend class AutomationProvider; + + class InterstitialPageRVHViewDelegate; + + // Initializes tab_to_interstitial_page_ in a thread-safe manner. + // Should be called before accessing tab_to_interstitial_page_. + static void InitInterstitialPageMap(); + + // 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(); + + // 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 tab in which we are displayed. + TabContents* tab_; + + // 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_; + + // Notification magic. + NotificationRegistrar notification_registrar_; + + // The RenderViewHost displaying the interstitial contents. + RenderViewHost* render_view_host_; + + // 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 tab when hidden (to revert + // it to its original value). + bool should_revert_tab_title_; + + // 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 tab that should be reverted to when the + // interstitial is hidden. + std::wstring original_tab_title_; + + // Our RenderViewHostViewDelegate, necessary for accelerators to work. + scoped_ptr<InterstitialPageRVHViewDelegate> rvh_view_delegate_; + + // We keep a map of the various blocking pages shown as the UI tests need to + // be able to retrieve them. + typedef std::map<TabContents*, InterstitialPage*> InterstitialPageMap; + static InterstitialPageMap* tab_to_interstitial_page_; + + // Settings passed to the renderer. + RendererPreferences renderer_preferences_; + + DISALLOW_COPY_AND_ASSIGN(InterstitialPage); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_INTERSTITIAL_PAGE_H_ diff --git a/content/browser/tab_contents/language_state.cc b/content/browser/tab_contents/language_state.cc new file mode 100644 index 0000000..86712d1 --- /dev/null +++ b/content/browser/tab_contents/language_state.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2010 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/tab_contents/language_state.h" + +#include "content/browser/tab_contents/navigation_controller.h" +#include "content/browser/tab_contents/navigation_entry.h" + +LanguageState::LanguageState(NavigationController* nav_controller) + : navigation_controller_(nav_controller), + page_translatable_(false), + translation_pending_(false), + translation_declined_(false), + in_page_navigation_(false) { +} + +LanguageState::~LanguageState() { +} + +void LanguageState::DidNavigate( + const NavigationController::LoadCommittedDetails& details) { + in_page_navigation_ = details.is_in_page; + if (in_page_navigation_ || !details.is_main_frame) + return; // Don't reset our states, the page has not changed. + + bool reload = details.entry->transition_type() == PageTransition::RELOAD || + details.type == NavigationType::SAME_PAGE; + if (reload) { + // We might not get a LanguageDetermined notifications on reloads. Make sure + // to keep the original language and to set current_lang_ so + // IsPageTranslated() returns false. + current_lang_ = original_lang_; + } else { + prev_original_lang_ = original_lang_; + prev_current_lang_ = current_lang_; + original_lang_.clear(); + current_lang_.clear(); + } + + translation_pending_ = false; + translation_declined_ = false; +} + +void LanguageState::LanguageDetermined(const std::string& page_language, + bool page_translatable) { + if (in_page_navigation_ && !original_lang_.empty()) { + // In-page navigation, we don't expect our states to change. + // Note that we'll set the languages if original_lang_ is empty. This might + // happen if the we did not get called on the top-page. + return; + } + page_translatable_ = page_translatable; + original_lang_ = page_language; + current_lang_ = page_language; +} + +std::string LanguageState::AutoTranslateTo() const { + // Only auto-translate if: + // - no translation is pending + // - this page is in the same language as the previous page + // - the previous page had been translated + // - this page is not already translated + // - the new page was navigated through a link. + if (!translation_pending_ && + prev_original_lang_ == original_lang_ && + prev_original_lang_ != prev_current_lang_ && + original_lang_ == current_lang_ && + navigation_controller_->GetActiveEntry() && + navigation_controller_->GetActiveEntry()->transition_type() == + PageTransition::LINK) { + return prev_current_lang_; + } + + return std::string(); +} diff --git a/content/browser/tab_contents/language_state.h b/content/browser/tab_contents/language_state.h new file mode 100644 index 0000000..d0c6204 --- /dev/null +++ b/content/browser/tab_contents/language_state.h @@ -0,0 +1,109 @@ +// Copyright (c) 2010 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_TAB_CONTENTS_LANGUAGE_STATE_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_LANGUAGE_STATE_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "content/browser/tab_contents/navigation_controller.h" + +// This class holds the language state of the current page. +// There is one LanguageState instance per TabContents. +// It is used to determine when navigating to a new page whether it should +// automatically be translated. +// This auto-translate behavior is the expected behavior when: +// - user is on page in language A that they had translated to language B. +// - user clicks a link in that page that takes them to a page also in language +// A. + +class LanguageState { + public: + explicit LanguageState(NavigationController* nav_controller); + ~LanguageState(); + + // Should be called when the page did a new navigation (whether it is a main + // frame or sub-frame navigation). + void DidNavigate(const NavigationController::LoadCommittedDetails& details); + + // Should be called when the language of the page has been determined. + // |page_translatable| when false indicates that the browser should not offer + // to translate the page. + void LanguageDetermined(const std::string& page_language, + bool page_translatable); + + // Returns the language the current page should be translated to, based on the + // previous page languages and the transition. This should be called after + // the language page has been determined. + // Returns an empty string if the page should not be auto-translated. + std::string AutoTranslateTo() const; + + // Returns true if the current page in the associated tab has been translated. + bool IsPageTranslated() const { return original_lang_ != current_lang_; } + + const std::string& original_language() const { return original_lang_; } + + void set_current_language(const std::string& language) { + current_lang_ = language; + } + const std::string& current_language() const { return current_lang_; } + + bool page_translatable() const { return page_translatable_; } + + // Whether the page is currently in the process of being translated. + bool translation_pending() const { return translation_pending_; } + void set_translation_pending(bool value) { translation_pending_ = value; } + + // Whether the user has already declined to translate the page. + bool translation_declined() const { return translation_declined_; } + void set_translation_declined(bool value) { translation_declined_ = value; } + + // Whether the current page was navigated through an in-page (fragment) + // navigation. + bool in_page_navigation() const { return in_page_navigation_; } + + private: + // The languages this page is in. Note that current_lang_ is different from + // original_lang_ when the page has been translated. + // Note that these might be empty if the page language has not been determined + // yet. + std::string original_lang_; + std::string current_lang_; + + // Same as above but for the previous page. + std::string prev_original_lang_; + std::string prev_current_lang_; + + // The navigation controller of the tab we are associated with. + NavigationController* navigation_controller_; + + // Whether it is OK to offer to translate the page. Some pages explictly + // specify that they should not be translated by the browser (this is the case + // for GMail for example, which provides its own translation features). + bool page_translatable_; + + // Whether a translation is currently pending (TabContents waiting for the + // PAGE_TRANSLATED notification). This is needed to avoid sending duplicate + // translate requests to a page. TranslateManager initiates translations + // when it received the LANGUAGE_DETERMINED notification. This is sent by + // the renderer with the page contents, every time the load stops for the + // main frame, so we may get several. + // TODO(jcampan): make the renderer send the language just once per navigation + // then we can get rid of that state. + bool translation_pending_; + + // Whether the user has declined to translate the page (by closing the infobar + // for example). This is necessary as a new infobar could be shown if a new + // load happens in the page after the user closed the infobar. + bool translation_declined_; + + // Whether the current navigation is a fragment navigation (in page). + bool in_page_navigation_; + + DISALLOW_COPY_AND_ASSIGN(LanguageState); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_LANGUAGE_STATE_H_ diff --git a/content/browser/tab_contents/navigation_controller.cc b/content/browser/tab_contents/navigation_controller.cc new file mode 100644 index 0000000..2ac9aba --- /dev/null +++ b/content/browser/tab_contents/navigation_controller.cc @@ -0,0 +1,1203 @@ +// Copyright (c) 2010 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/tab_contents/navigation_controller.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_about_handler.h" +#include "chrome/browser/browser_url_handler.h" +#include "chrome/browser/in_process_webkit/session_storage_namespace.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/sessions/session_types.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/navigation_types.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/url_constants.h" +#include "content/browser/tab_contents/interstitial_page.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_delegate.h" +#include "grit/app_resources.h" +#include "net/base/escape.h" +#include "net/base/net_util.h" +#include "net/base/mime_util.h" +#include "webkit/glue/webkit_glue.h" + +namespace { + +const int kInvalidateAllButShelves = + 0xFFFFFFFF & ~TabContents::INVALIDATE_BOOKMARK_BAR; + +// 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(NavigationController* nav_controller, + bool from_front, + int count) { + NavigationController::PrunedDetails details; + details.from_front = from_front; + details.count = count; + NotificationService::current()->Notify( + NotificationType::NAV_LIST_PRUNED, + Source<NavigationController>(nav_controller), + Details<NavigationController::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 SetContentStateIfEmpty(NavigationEntry* entry) { + if (entry->content_state().empty()) { + entry->set_content_state( + webkit_glue::CreateHistoryStateForURL(entry->url())); + } +} + +// 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<NavigationEntry> >* entries, + bool from_last_session) { + 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]->set_transition_type(PageTransition::RELOAD); + (*entries)[i]->set_restore_type(from_last_session ? + NavigationEntry::RESTORE_LAST_SESSION : + NavigationEntry::RESTORE_CURRENT_SESSION); + // NOTE(darin): This code is only needed for backwards compat. + SetContentStateIfEmpty((*entries)[i].get()); + } +} + +// See NavigationController::IsURLInPageNavigation for how this works and why. +bool AreURLsInPageNavigation(const GURL& existing_url, const GURL& new_url) { + if (existing_url == new_url || !new_url.has_ref()) { + // TODO(jcampan): what about when navigating back from a ref URL to the top + // non ref URL? Nothing is loaded in that case but we return false here. + // The user could also navigate from the ref URL to the non ref URL by + // entering the non ref URL in the location bar or through a bookmark, in + // which case there would be a load. I am not sure if the non-load/load + // scenarios can be differentiated with the TransitionType. + return false; + } + + url_canon::Replacements<char> replacements; + replacements.ClearRef(); + return existing_url.ReplaceComponents(replacements) == + new_url.ReplaceComponents(replacements); +} + +} // namespace + +// NavigationController --------------------------------------------------- + +// static +size_t NavigationController::max_entry_count_ = + chrome::kMaxSessionHistoryEntries; + +// static +bool NavigationController::check_for_repost_ = true; + +NavigationController::NavigationController( + TabContents* contents, + Profile* profile, + SessionStorageNamespace* session_storage_namespace) + : profile_(profile), + pending_entry_(NULL), + last_committed_entry_index_(-1), + pending_entry_index_(-1), + transient_entry_index_(-1), + tab_contents_(contents), + max_restored_page_id_(-1), + ALLOW_THIS_IN_INITIALIZER_LIST(ssl_manager_(this)), + needs_reload_(false), + session_storage_namespace_(session_storage_namespace), + pending_reload_(NO_RELOAD) { + DCHECK(profile_); + if (!session_storage_namespace_) + session_storage_namespace_ = new SessionStorageNamespace(profile_); +} + +NavigationController::~NavigationController() { + DiscardNonCommittedEntriesInternal(); + + NotificationService::current()->Notify( + NotificationType::TAB_CLOSED, + Source<NavigationController>(this), + NotificationService::NoDetails()); +} + +void NavigationController::RestoreFromState( + const std::vector<TabNavigation>& navigations, + int selected_navigation, + bool from_last_session) { + // Verify that this controller is unused and that the input is valid. + DCHECK(entry_count() == 0 && !pending_entry()); + DCHECK(selected_navigation >= 0 && + selected_navigation < static_cast<int>(navigations.size())); + + // Populate entries_ from the supplied TabNavigations. + needs_reload_ = true; + CreateNavigationEntriesFromTabNavigations(navigations, &entries_); + + // And finish the restore. + FinishRestore(selected_navigation, from_last_session); +} + +void NavigationController::Reload(bool check_for_repost) { + ReloadInternal(check_for_repost, RELOAD); +} +void NavigationController::ReloadIgnoringCache(bool check_for_repost) { + ReloadInternal(check_for_repost, RELOAD_IGNORING_CACHE); +} + +void NavigationController::ReloadInternal(bool check_for_repost, + ReloadType reload_type) { + // Reloading a transient entry does nothing. + if (transient_entry_index_ != -1) + return; + + DiscardNonCommittedEntriesInternal(); + int current_index = GetCurrentEntryIndex(); + // If we are no where, then we can't reload. TODO(darin): We should add a + // CanReload method. + if (current_index == -1) { + return; + } + + if (check_for_repost_ && check_for_repost && + GetEntryAtIndex(current_index)->has_post_data()) { + // 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. + NotificationService::current()->Notify( + NotificationType::REPOST_WARNING_SHOWN, + Source<NavigationController>(this), + NotificationService::NoDetails()); + + pending_reload_ = reload_type; + tab_contents_->Activate(); + tab_contents_->delegate()->ShowRepostFormWarningDialog(tab_contents_); + } else { + DiscardNonCommittedEntriesInternal(); + + pending_entry_index_ = current_index; + entries_[pending_entry_index_]->set_transition_type(PageTransition::RELOAD); + NavigateToPendingEntry(reload_type); + } +} + +void NavigationController::CancelPendingReload() { + DCHECK(pending_reload_ != NO_RELOAD); + pending_reload_ = NO_RELOAD; +} + +void NavigationController::ContinuePendingReload() { + if (pending_reload_ == NO_RELOAD) { + NOTREACHED(); + } else { + ReloadInternal(false, pending_reload_); + pending_reload_ = NO_RELOAD; + } +} + +bool NavigationController::IsInitialNavigation() { + return last_document_loaded_.is_null(); +} + +// static +NavigationEntry* NavigationController::CreateNavigationEntry( + const GURL& url, const GURL& referrer, PageTransition::Type transition, + Profile* profile) { + // 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; + BrowserURLHandler::RewriteURLIfNecessary( + &loaded_url, profile, &reverse_on_redirect); + + NavigationEntry* entry = new NavigationEntry( + NULL, // The site instance for tabs is sent on navigation + // (TabContents::GetSiteInstance). + -1, + loaded_url, + referrer, + string16(), + transition); + entry->set_virtual_url(url); + entry->set_user_typed_url(url); + entry->set_update_virtual_url_with_url(reverse_on_redirect); + if (url.SchemeIsFile()) { + // Use the filename as the title, not the full path. + // We need to call FormatUrl() to perform URL de-escaping; + // it's a bit ugly to grab the filename out of the resulting string. + std::string languages = + profile->GetPrefs()->GetString(prefs::kAcceptLanguages); + std::wstring formatted = UTF16ToWideHack(net::FormatUrl(url, languages)); + std::wstring filename = + FilePath::FromWStringHack(formatted).BaseName().ToWStringHack(); + entry->set_title(WideToUTF16Hack(filename)); + } + return entry; +} + +NavigationEntry* NavigationController::GetEntryWithPageID( + SiteInstance* instance, int32 page_id) const { + int index = GetEntryIndexWithPageID(instance, page_id); + return (index != -1) ? entries_[index].get() : NULL; +} + +void NavigationController::LoadEntry(NavigationEntry* entry) { + // Handle non-navigational URLs that popup dialogs and such, these should not + // actually navigate. + if (HandleNonNavigationAboutURL(entry->url())) + return; + + // 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). + DiscardNonCommittedEntriesInternal(); + pending_entry_ = entry; + NotificationService::current()->Notify( + NotificationType::NAV_ENTRY_PENDING, + Source<NavigationController>(this), + NotificationService::NoDetails()); + NavigateToPendingEntry(NO_RELOAD); +} + +NavigationEntry* NavigationController::GetActiveEntry() const { + if (transient_entry_index_ != -1) + return entries_[transient_entry_index_].get(); + if (pending_entry_) + return pending_entry_; + return GetLastCommittedEntry(); +} + +int NavigationController::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* NavigationController::GetLastCommittedEntry() const { + if (last_committed_entry_index_ == -1) + return NULL; + return entries_[last_committed_entry_index_].get(); +} + +bool NavigationController::CanViewSource() const { + bool is_supported_mime_type = net::IsSupportedNonImageMimeType( + tab_contents_->contents_mime_type().c_str()); + NavigationEntry* active_entry = GetActiveEntry(); + return active_entry && !active_entry->IsViewSourceMode() && + is_supported_mime_type; +} + +NavigationEntry* NavigationController::GetEntryAtOffset(int offset) const { + int index = (transient_entry_index_ != -1) ? + transient_entry_index_ + offset : + last_committed_entry_index_ + offset; + if (index < 0 || index >= entry_count()) + return NULL; + + return entries_[index].get(); +} + +bool NavigationController::CanGoBack() const { + return entries_.size() > 1 && GetCurrentEntryIndex() > 0; +} + +bool NavigationController::CanGoForward() const { + int index = GetCurrentEntryIndex(); + return index >= 0 && index < (static_cast<int>(entries_.size()) - 1); +} + +void NavigationController::GoBack() { + if (!CanGoBack()) { + NOTREACHED(); + return; + } + + // If an interstitial page is showing, going back is equivalent to hiding the + // interstitial. + if (tab_contents_->interstitial_page()) { + tab_contents_->interstitial_page()->DontProceed(); + return; + } + + // Base the navigation on where we are now... + int current_index = GetCurrentEntryIndex(); + + DiscardNonCommittedEntries(); + + pending_entry_index_ = current_index - 1; + entries_[pending_entry_index_]->set_transition_type( + entries_[pending_entry_index_]->transition_type() | + PageTransition::FORWARD_BACK); + NavigateToPendingEntry(NO_RELOAD); +} + +void NavigationController::GoForward() { + if (!CanGoForward()) { + NOTREACHED(); + 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 (tab_contents_->interstitial_page()) { + tab_contents_->interstitial_page()->CancelForNavigation(); + } + + 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_]->set_transition_type( + entries_[pending_entry_index_]->transition_type() | + PageTransition::FORWARD_BACK); + NavigateToPendingEntry(NO_RELOAD); +} + +void NavigationController::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--; + } + } + + // If an interstitial page is showing, the previous renderer is blocked and + // cannot make new requests. + if (tab_contents_->interstitial_page()) { + if (index == GetCurrentEntryIndex() - 1) { + // Going back one entry is equivalent to hiding the interstitial. + tab_contents_->interstitial_page()->DontProceed(); + return; + } else { + // Unblock the renderer (and disable the interstitial) to allow this + // navigation to succeed. The interstitial will stay visible until the + // resulting DidNavigate. + tab_contents_->interstitial_page()->CancelForNavigation(); + } + } + + DiscardNonCommittedEntries(); + + pending_entry_index_ = index; + entries_[pending_entry_index_]->set_transition_type( + entries_[pending_entry_index_]->transition_type() | + PageTransition::FORWARD_BACK); + NavigateToPendingEntry(NO_RELOAD); +} + +void NavigationController::GoToOffset(int offset) { + int index = (transient_entry_index_ != -1) ? + transient_entry_index_ + offset : + last_committed_entry_index_ + offset; + if (index < 0 || index >= entry_count()) + return; + + GoToIndex(index); +} + +void NavigationController::RemoveEntryAtIndex(int index, + const GURL& default_url) { + int size = static_cast<int>(entries_.size()); + DCHECK(index < size); + + DiscardNonCommittedEntries(); + + entries_.erase(entries_.begin() + index); + + if (last_committed_entry_index_ == index) { + last_committed_entry_index_--; + // We removed the currently shown entry, so we have to load something else. + if (last_committed_entry_index_ != -1) { + pending_entry_index_ = last_committed_entry_index_; + NavigateToPendingEntry(NO_RELOAD); + } else { + // If there is nothing to show, show a default page. + LoadURL(default_url.is_empty() ? GURL("about:blank") : default_url, + GURL(), PageTransition::START_PAGE); + } + } else if (last_committed_entry_index_ > index) { + last_committed_entry_index_--; + } +} + +void NavigationController::UpdateVirtualURLToURL( + NavigationEntry* entry, const GURL& new_url) { + GURL new_virtual_url(new_url); + if (BrowserURLHandler::ReverseURLRewrite( + &new_virtual_url, entry->virtual_url(), profile_)) { + entry->set_virtual_url(new_virtual_url); + } +} + +void NavigationController::AddTransientEntry(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<NavigationEntry>(entry)); + transient_entry_index_ = index; + tab_contents_->NotifyNavigationStateChanged(kInvalidateAllButShelves); +} + +void NavigationController::LoadURL(const GURL& url, const GURL& referrer, + PageTransition::Type transition) { + // The user initiated a load, we don't need to reload anymore. + needs_reload_ = false; + + NavigationEntry* entry = CreateNavigationEntry(url, referrer, transition, + profile_); + + LoadEntry(entry); +} + +void NavigationController::DocumentLoadedInFrame() { + last_document_loaded_ = base::TimeTicks::Now(); +} + +bool NavigationController::RendererDidNavigate( + const ViewHostMsg_FrameNavigate_Params& params, + int extra_invalidate_flags, + LoadCommittedDetails* details) { + + // Save the previous state before we clobber it. + if (GetLastCommittedEntry()) { + details->previous_url = GetLastCommittedEntry()->url(); + details->previous_entry_index = last_committed_entry_index(); + } else { + details->previous_url = GURL(); + details->previous_entry_index = -1; + } + + // Assign the current site instance to any pending entry, so we can find it + // later by calling GetEntryIndexWithPageID. We only care about this if the + // pending entry is an existing navigation and not a new one (or else we + // wouldn't care about finding it with GetEntryIndexWithPageID). + // + // TODO(brettw) this seems slightly bogus as we don't really know if the + // pending entry is what this navigation is for. There is a similar TODO + // w.r.t. the pending entry in RendererDidNavigateToNewPage. + if (pending_entry_index_ >= 0) { + pending_entry_->set_site_instance(tab_contents_->GetSiteInstance()); + pending_entry_->set_restore_type(NavigationEntry::RESTORE_NONE); + } + + // is_in_page must be computed before the entry gets committed. + details->is_in_page = IsURLInPageNavigation(params.url); + + // Do navigation-type specific actions. These will make and commit an entry. + details->type = ClassifyNavigation(params); + + switch (details->type) { + case NavigationType::NEW_PAGE: + RendererDidNavigateToNewPage(params, &(details->did_replace_entry)); + break; + case NavigationType::EXISTING_PAGE: + RendererDidNavigateToExistingPage(params); + break; + case NavigationType::SAME_PAGE: + RendererDidNavigateToSamePage(params); + break; + case NavigationType::IN_PAGE: + RendererDidNavigateInPage(params, &(details->did_replace_entry)); + break; + case NavigationType::NEW_SUBFRAME: + RendererDidNavigateNewSubframe(params); + break; + case NavigationType::AUTO_SUBFRAME: + if (!RendererDidNavigateAutoSubframe(params)) + return false; + break; + case NavigationType::NAV_IGNORE: + // There is nothing we can do with this navigation, so we just return to + // the caller that nothing has happened. + return false; + default: + NOTREACHED(); + } + + // 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). + SetContentStateIfEmpty(GetActiveEntry()); + + // WebKit doesn't set the "auto" transition on meta refreshes properly (bug + // 1051891) so we manually set it for redirects which we normally treat as + // "non-user-gestures" where we want to update stuff after navigations. + // + // Note that the redirect check also checks for a pending entry to + // differentiate real redirects from browser initiated navigations to a + // redirected entry. This happens when you hit back to go to a page that was + // the destination of a redirect, we don't want to treat it as a redirect + // even though that's what its transition will be. See bug 1117048. + // + // TODO(brettw) write a test for this complicated logic. + details->is_auto = (PageTransition::IsRedirect(params.transition) && + !pending_entry()) || + params.gesture == NavigationGestureAuto; + + // Now prep the rest of the details for the notification and broadcast. + details->entry = GetActiveEntry(); + details->is_main_frame = PageTransition::IsMainFrame(params.transition); + details->serialized_security_info = params.security_info; + details->is_content_filtered = params.is_content_filtered; + details->http_status_code = params.http_status_code; + NotifyNavigationEntryCommitted(details, extra_invalidate_flags); + + return true; +} + +NavigationType::Type NavigationController::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 NavigationType::NAV_IGNORE; + } + + if (params.page_id > tab_contents_->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 (PageTransition::IsMainFrame(params.transition)) + return NavigationType::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 NavigationType::NAV_IGNORE; + + // Valid subframe navigation. + return NavigationType::NEW_SUBFRAME; + } + + // Now we know that the notification is for an existing page. Find that entry. + int existing_entry_index = GetEntryIndexWithPageID( + tab_contents_->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(); + return NavigationType::NAV_IGNORE; + } + NavigationEntry* existing_entry = entries_[existing_entry_index].get(); + + if (!PageTransition::IsMainFrame(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 NavigationType::AUTO_SUBFRAME; + } + + // Anything below here we know is a main frame navigation. + if (pending_entry_ && + existing_entry != pending_entry_ && + pending_entry_->page_id() == -1) { + // 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). In this case, we want to just ignore the pending entry and go + // back to where we were (the "existing entry"). + return NavigationType::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->url(), params.url)) + return NavigationType::IN_PAGE; + + // Since we weeded out "new" navigations above, we know this is an existing + // (back/forward) navigation. + return NavigationType::EXISTING_PAGE; +} + +bool NavigationController::IsRedirect( + const ViewHostMsg_FrameNavigate_Params& params) { + // For main frame transition, we judge by params.transition. + // Otherwise, by params.redirects. + if (PageTransition::IsMainFrame(params.transition)) { + return PageTransition::IsRedirect(params.transition); + } + return params.redirects.size() > 1; +} + +void NavigationController::CreateNavigationEntriesFromTabNavigations( + const std::vector<TabNavigation>& navigations, + std::vector<linked_ptr<NavigationEntry> >* entries) { + // Create a NavigationEntry for each of the navigations. + int page_id = 0; + for (std::vector<TabNavigation>::const_iterator i = + navigations.begin(); i != navigations.end(); ++i, ++page_id) { + linked_ptr<NavigationEntry> entry(i->ToNavigationEntry(page_id, profile_)); + entries->push_back(entry); + } +} + +void NavigationController::RendererDidNavigateToNewPage( + const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) { + NavigationEntry* new_entry; + if (pending_entry_) { + // TODO(brettw) this assumes that the pending entry is appropriate for the + // new page that was just loaded. I don't think this is necessarily the + // case! We should have some more tracking to know for sure. This goes along + // with a similar TODO at the top of RendererDidNavigate where we blindly + // set the site instance on the pending entry. + new_entry = new NavigationEntry(*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(NORMAL_PAGE); + } else { + new_entry = new NavigationEntry; + } + + new_entry->set_url(params.url); + if (new_entry->update_virtual_url_with_url()) + UpdateVirtualURLToURL(new_entry, params.url); + new_entry->set_referrer(params.referrer); + new_entry->set_page_id(params.page_id); + new_entry->set_transition_type(params.transition); + new_entry->set_site_instance(tab_contents_->GetSiteInstance()); + new_entry->set_has_post_data(params.is_post); + + InsertOrReplaceEntry(new_entry, *did_replace_entry); +} + +void NavigationController::RendererDidNavigateToExistingPage( + const ViewHostMsg_FrameNavigate_Params& params) { + // We should only get here for main frame navigations. + DCHECK(PageTransition::IsMainFrame(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(tab_contents_->GetSiteInstance(), + params.page_id); + DCHECK(entry_index >= 0 && + entry_index < static_cast<int>(entries_.size())); + NavigationEntry* entry = entries_[entry_index].get(); + + // The URL may have changed due to redirects. The site instance will normally + // be the same except during session restore, when no site instance will be + // assigned. + entry->set_url(params.url); + if (entry->update_virtual_url_with_url()) + UpdateVirtualURLToURL(entry, params.url); + DCHECK(entry->site_instance() == NULL || + entry->site_instance() == tab_contents_->GetSiteInstance()); + entry->set_site_instance(tab_contents_->GetSiteInstance()); + + entry->set_has_post_data(params.is_post); + + // 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). + // + // Note that we need to use the "internal" version since we don't want to + // actually change any other state, just kill the pointer. + if (entry == pending_entry_) + 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(tab_contents_->GetSiteInstance(), params.page_id); +} + +void NavigationController::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. + NavigationEntry* existing_entry = GetEntryWithPageID( + tab_contents_->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_->unique_id()); + + // The URL may have changed due to redirects. + if (existing_entry->update_virtual_url_with_url()) + UpdateVirtualURLToURL(existing_entry, params.url); + existing_entry->set_url(params.url); + + DiscardNonCommittedEntries(); +} + +void NavigationController::RendererDidNavigateInPage( + const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) { + DCHECK(PageTransition::IsMainFrame(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. + NavigationEntry* existing_entry = GetEntryWithPageID( + tab_contents_->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). + NavigationEntry* new_entry = new NavigationEntry(*existing_entry); + new_entry->set_page_id(params.page_id); + if (new_entry->update_virtual_url_with_url()) + UpdateVirtualURLToURL(new_entry, params.url); + new_entry->set_url(params.url); + + // This replaces the existing entry since the page ID didn't change. + *did_replace_entry = true; + InsertOrReplaceEntry(new_entry, true); +} + +void NavigationController::RendererDidNavigateNewSubframe( + const ViewHostMsg_FrameNavigate_Params& params) { + if (PageTransition::StripQualifier(params.transition) == + PageTransition::AUTO_SUBFRAME) { + // This is not user-initiated. Ignore. + 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."; + NavigationEntry* new_entry = new NavigationEntry(*GetLastCommittedEntry()); + new_entry->set_page_id(params.page_id); + InsertOrReplaceEntry(new_entry, false); +} + +bool NavigationController::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( + tab_contents_->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; + return true; + } + return false; +} + +// TODO(brettw) I think this function is unnecessary. +void NavigationController::CommitPendingEntry() { + DiscardTransientEntry(); + + if (!pending_entry()) + return; // Nothing to do. + + // Need to save the previous URL for the notification. + LoadCommittedDetails details; + if (GetLastCommittedEntry()) { + details.previous_url = GetLastCommittedEntry()->url(); + details.previous_entry_index = last_committed_entry_index(); + } else { + details.previous_entry_index = -1; + } + + if (pending_entry_index_ >= 0) { + // This is a previous navigation (back/forward) that we're just now + // committing. Just mark it as committed. + details.type = NavigationType::EXISTING_PAGE; + int new_entry_index = pending_entry_index_; + DiscardNonCommittedEntriesInternal(); + + // Mark that entry as committed. + last_committed_entry_index_ = new_entry_index; + } else { + // This is a new navigation. It's easiest to just copy the entry and insert + // it new again, since InsertOrReplaceEntry expects to take ownership and + // also discard the pending entry. We also need to synthesize a page ID. We + // can only do this because this function will only be called by our custom + // TabContents types. For TabContents, the IDs are generated by the + // renderer, so we can't do this. + details.type = NavigationType::NEW_PAGE; + pending_entry_->set_page_id(tab_contents_->GetMaxPageID() + 1); + tab_contents_->UpdateMaxPageID(pending_entry_->page_id()); + InsertOrReplaceEntry(new NavigationEntry(*pending_entry_), false); + } + + // Broadcast the notification of the navigation. + details.entry = GetActiveEntry(); + details.is_auto = false; + details.is_in_page = AreURLsInPageNavigation(details.previous_url, + details.entry->url()); + details.is_main_frame = true; + NotifyNavigationEntryCommitted(&details, 0); +} + +int NavigationController::GetIndexOfEntry( + const NavigationEntry* 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 NavigationController::IsURLInPageNavigation(const GURL& url) const { + NavigationEntry* last_committed = GetLastCommittedEntry(); + if (!last_committed) + return false; + return AreURLsInPageNavigation(last_committed->url(), url); +} + +void NavigationController::CopyStateFrom(const NavigationController& source) { + // Verify that we look new. + DCHECK(entry_count() == 0 && !pending_entry()); + + if (source.entry_count() == 0) + return; // Nothing new to do. + + needs_reload_ = true; + InsertEntriesFrom(source, source.entry_count()); + + session_storage_namespace_ = source.session_storage_namespace_->Clone(); + + FinishRestore(source.last_committed_entry_index_, false); +} + +void NavigationController::CopyStateFromAndPrune(NavigationController* source) { + // This code is intended for use when the last entry is the active entry. + DCHECK((transient_entry_index_ != -1 && + transient_entry_index_ == entry_count() - 1) || + (pending_entry_ && (pending_entry_index_ == -1 || + pending_entry_index_ == entry_count() - 1)) || + (!pending_entry_ && last_committed_entry_index_ == entry_count() - 1)); + + // Remove all the entries leaving the active entry. + PruneAllButActive(); + + // Insert the entries from source. Don't use source->GetCurrentEntryIndex as + // we don't want to copy over the transient entry. + int max_source_index = source->pending_entry_index_ != -1 ? + source->pending_entry_index_ : source->last_committed_entry_index_; + if (max_source_index == -1) + max_source_index = source->entry_count(); + 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_ = entry_count() - 1; + if (pending_entry_index_ != -1) + pending_entry_index_ = entry_count() - 1; + if (transient_entry_index_ != -1) { + // There's a transient entry. In this case we want the last committed to + // point to the previous entry. + transient_entry_index_ = entry_count() - 1; + if (last_committed_entry_index_ != -1) + last_committed_entry_index_--; + } +} + +void NavigationController::PruneAllButActive() { + if (transient_entry_index_ != -1) { + // There is a transient entry. Prune up to it. + DCHECK_EQ(entry_count() - 1, transient_entry_index_); + entries_.erase(entries_.begin(), entries_.begin() + transient_entry_index_); + transient_entry_index_ = 0; + last_committed_entry_index_ = -1; + pending_entry_index_ = -1; + } else if (!pending_entry_) { + // There's no pending entry. Leave the last entry (if there is one). + if (!entry_count()) + return; + + DCHECK(last_committed_entry_index_ >= 0); + entries_.erase(entries_.begin(), + entries_.begin() + last_committed_entry_index_); + entries_.erase(entries_.begin() + 1, entries_.end()); + last_committed_entry_index_ = 0; + } else if (pending_entry_index_ != -1) { + entries_.erase(entries_.begin(), entries_.begin() + pending_entry_index_); + entries_.erase(entries_.begin() + 1, entries_.end()); + pending_entry_index_ = 0; + last_committed_entry_index_ = 0; + } else { + // There is a pending_entry, but it's not in entries_. + pending_entry_index_ = -1; + last_committed_entry_index_ = -1; + entries_.clear(); + } + + if (tab_contents_->interstitial_page()) { + // Normally the interstitial page hides itself if the user doesn't proceeed. + // This would result in showing a NavigationEntry we just removed. Set this + // so the interstitial triggers a reload if the user doesn't proceed. + tab_contents_->interstitial_page()->set_reload_on_dont_proceed(true); + } +} + +void NavigationController::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) { + tab_contents_->NotifyNavigationStateChanged(kInvalidateAllButShelves); + } +} + +void NavigationController::InsertOrReplaceEntry(NavigationEntry* entry, + bool replace) { + DCHECK(entry->transition_type() != PageTransition::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 NavigationEntry* const pending_entry = (pending_entry_index_ == -1) ? + pending_entry_ : entries_[pending_entry_index_].get(); + if (pending_entry) + entry->set_unique_id(pending_entry->unique_id()); + + 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. + int prune_up_to = replace ? last_committed_entry_index_ - 1 + : last_committed_entry_index_; + int num_pruned = 0; + while (prune_up_to < (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); + } + + if (entries_.size() >= max_entry_count_) { + RemoveEntryAtIndex(0, GURL()); + NotifyPrunedEntries(this, true, 1); + } + + entries_.push_back(linked_ptr<NavigationEntry>(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. + tab_contents_->UpdateMaxPageID(entry->page_id()); +} + +void NavigationController::SetWindowID(const SessionID& id) { + window_id_ = id; + NotificationService::current()->Notify(NotificationType::TAB_PARENTED, + Source<NavigationController>(this), + NotificationService::NoDetails()); +} + +void NavigationController::NavigateToPendingEntry(ReloadType reload_type) { + needs_reload_ = false; + + // 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 (!tab_contents_->NavigateToPendingEntry(reload_type)) + DiscardNonCommittedEntries(); +} + +void NavigationController::NotifyNavigationEntryCommitted( + LoadCommittedDetails* details, + int extra_invalidate_flags) { + details->entry = GetActiveEntry(); + NotificationDetails notification_details = + Details<LoadCommittedDetails>(details); + + // We need to notify the ssl_manager_ before the tab_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(notification_details); + + // TODO(pkasting): http://b/1113079 Probably these explicit notification paths + // should be removed, and interested parties should just listen for the + // notification below instead. + tab_contents_->NotifyNavigationStateChanged( + kInvalidateAllButShelves | extra_invalidate_flags); + + NotificationService::current()->Notify( + NotificationType::NAV_ENTRY_COMMITTED, + Source<NavigationController>(this), + notification_details); +} + +// static +void NavigationController::DisablePromptOnRepost() { + check_for_repost_ = false; +} + +void NavigationController::SetActive(bool is_active) { + if (is_active && needs_reload_) + LoadIfNecessary(); +} + +void NavigationController::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 NavigationController::NotifyEntryChanged(const NavigationEntry* entry, + int index) { + EntryChangedDetails det; + det.changed_entry = entry; + det.index = index; + NotificationService::current()->Notify(NotificationType::NAV_ENTRY_CHANGED, + Source<NavigationController>(this), + Details<EntryChangedDetails>(&det)); +} + +void NavigationController::FinishRestore(int selected_index, + bool from_last_session) { + DCHECK(selected_index >= 0 && selected_index < entry_count()); + ConfigureEntriesForRestore(&entries_, from_last_session); + + set_max_restored_page_id(static_cast<int32>(entry_count())); + + last_committed_entry_index_ = selected_index; +} + +void NavigationController::DiscardNonCommittedEntriesInternal() { + if (pending_entry_index_ == -1) + delete pending_entry_; + pending_entry_ = NULL; + pending_entry_index_ = -1; + + DiscardTransientEntry(); +} + +void NavigationController::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 NavigationController::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]->page_id() == page_id)) + return i; + } + return -1; +} + +NavigationEntry* NavigationController::GetTransientEntry() const { + if (transient_entry_index_ == -1) + return NULL; + return entries_[transient_entry_index_].get(); +} + +void NavigationController::InsertEntriesFrom( + const NavigationController& source, + int max_index) { + DCHECK_LE(max_index, source.entry_count()); + 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()->page_type() != INTERSTITIAL_PAGE) { + entries_.insert(entries_.begin() + insert_index++, + linked_ptr<NavigationEntry>( + new NavigationEntry(*source.entries_[i]))); + } + } +} diff --git a/content/browser/tab_contents/navigation_controller.h b/content/browser/tab_contents/navigation_controller.h new file mode 100644 index 0000000..f348368 --- /dev/null +++ b/content/browser/tab_contents/navigation_controller.h @@ -0,0 +1,603 @@ +// Copyright (c) 2010 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_TAB_CONTENTS_NAVIGATION_CONTROLLER_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_NAVIGATION_CONTROLLER_H_ +#pragma once + +#include "build/build_config.h" + +#include <string> +#include <vector> + +#include "base/linked_ptr.h" +#include "base/time.h" +#include "googleurl/src/gurl.h" +#include "chrome/browser/sessions/session_id.h" +#include "chrome/browser/ssl/ssl_manager.h" +#include "chrome/common/navigation_types.h" +#include "chrome/common/page_transition_types.h" + +class NavigationEntry; +class Profile; +class SessionStorageNamespace; +class SiteInstance; +class TabContents; +class TabNavigation; +struct ViewHostMsg_FrameNavigate_Params; + +// A NavigationController maintains the back-forward list for a single tab and +// manages all navigation within that list. +// +// The NavigationController also owns all TabContents for the tab. This is to +// make sure that we have at most one TabContents instance per type. +class NavigationController { + public: + // Notification details ------------------------------------------------------ + + // Provides the details for a NOTIFY_NAV_ENTRY_CHANGED notification. + struct EntryChangedDetails { + // The changed navigation entry after it has been updated. + const NavigationEntry* changed_entry; + + // Indicates the current index in the back/forward list of the entry. + int index; + }; + + // Provides the details for a NOTIFY_NAV_ENTRY_COMMITTED notification. + // TODO(brettw) this mostly duplicates ProvisionalLoadDetails, it would be + // nice to unify these somehow. + struct LoadCommittedDetails { + // By default, the entry will be filled according to a new main frame + // navigation. + LoadCommittedDetails() + : entry(NULL), + type(NavigationType::UNKNOWN), + previous_entry_index(-1), + is_auto(false), + did_replace_entry(false), + is_in_page(false), + is_main_frame(true), + is_content_filtered(false), + http_status_code(0) { + } + + // The committed entry. This will be the active entry in the controller. + NavigationEntry* entry; + + // The type of navigation that just occurred. Note that not all types of + // navigations in the enum are valid here, since some of them don't actually + // cause a "commit" and won't generate this notification. + NavigationType::Type type; + + // The index of the previously committed navigation entry. This will be -1 + // if there are no previous entries. + int previous_entry_index; + + // The previous URL that the user was on. This may be empty if none. + GURL previous_url; + + // True when this load was non-user initated. This corresponds to a + // a NavigationGestureAuto call from WebKit (see webview_delegate.h). + // We also count reloads and meta-refreshes as "auto" to account for the + // fact that WebKit doesn't always set the user gesture properly in these + // cases (see bug 1051891). + bool is_auto; + + // True if the committed entry has replaced the exisiting one. + // A non-user initiated redirect causes such replacement. + // This is somewhat similiar to is_auto, but not exactly the same. + bool did_replace_entry; + + // True if the navigation was in-page. This means that the active entry's + // URL and the |previous_url| are the same except for reference fragments. + bool is_in_page; + + // True when the main frame was navigated. False means the navigation was a + // sub-frame. + bool is_main_frame; + + // Whether the content of this frame has been altered/blocked because it was + // unsafe. + bool is_content_filtered; + + // When the committed load is a web page from the renderer, this string + // specifies the security state if the page is secure. + // See ViewHostMsg_FrameNavigate_Params.security_info, where it comes from. + // Use SSLManager::DeserializeSecurityInfo to decode it. + std::string serialized_security_info; + + // Returns whether the user probably felt like they navigated somewhere new. + // We often need this logic for showing or hiding something, and this + // returns true only for main frame loads that the user initiated, that go + // to a new page. + bool is_user_initiated_main_frame_load() const { + return !is_auto && !is_in_page && is_main_frame; + } + + // The HTTP status code for this entry.. + int http_status_code; + }; + + // Details sent for NOTIFY_NAV_LIST_PRUNED. + struct PrunedDetails { + // If true, count items were removed from the front of the list, otherwise + // count items were removed from the back of the list. + bool from_front; + + // Number of items removed. + int count; + }; + + enum ReloadType { + NO_RELOAD, // Normal load. + RELOAD, // Normal (cache-validating) reload. + RELOAD_IGNORING_CACHE // Reload bypassing the cache, aka shift-reload. + }; + + // --------------------------------------------------------------------------- + + NavigationController(TabContents* tab_contents, + Profile* profile, + SessionStorageNamespace* session_storage_namespace); + ~NavigationController(); + + // Returns the profile for this controller. It can never be NULL. + Profile* profile() const { + return profile_; + } + + // Sets the profile for this controller. + void set_profile(Profile* profile) { + profile_ = profile; + } + + // Initializes this NavigationController with the given saved navigations, + // using selected_navigation as the currently loaded entry. Before this call + // the controller should be unused (there should be no current entry). If + // from_last_session is true, navigations are from the previous session, + // otherwise they are from the current session (undo tab close). + // This is used for session restore. + void RestoreFromState(const std::vector<TabNavigation>& navigations, + int selected_navigation, bool from_last_session); + + // Active entry -------------------------------------------------------------- + + // Returns the active entry, which is the transient entry if any, the pending + // entry if a navigation is in progress or the last committed entry otherwise. + // NOTE: This can be NULL!! + // + // If you are trying to get the current state of the NavigationController, + // this is the method you will typically want to call. + NavigationEntry* GetActiveEntry() const; + + // Returns the index from which we would go back/forward or reload. This is + // the last_committed_entry_index_ if pending_entry_index_ is -1. Otherwise, + // it is the pending_entry_index_. + int GetCurrentEntryIndex() const; + + // Returns the last committed entry, which may be null if there are no + // committed entries. + NavigationEntry* GetLastCommittedEntry() const; + + // Returns true if the source for the current entry can be viewed. + bool CanViewSource() const; + + // Returns the index of the last committed entry. + int last_committed_entry_index() const { + return last_committed_entry_index_; + } + + // Navigation list ----------------------------------------------------------- + + // Returns the number of entries in the NavigationController, excluding + // the pending entry if there is one, but including the transient entry if + // any. + int entry_count() const { + return static_cast<int>(entries_.size()); + } + + NavigationEntry* GetEntryAtIndex(int index) const { + return entries_.at(index).get(); + } + + // Returns the entry at the specified offset from current. Returns NULL + // if out of bounds. + NavigationEntry* GetEntryAtOffset(int offset) const; + + // Returns the index of the specified entry, or -1 if entry is not contained + // in this NavigationController. + int GetIndexOfEntry(const NavigationEntry* 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. + NavigationEntry* GetEntryWithPageID(SiteInstance* instance, + int32 page_id) const; + + // Pending entry ------------------------------------------------------------- + + // Commits the current pending entry and issues the NOTIFY_NAV_ENTRY_COMMIT + // notification. No changes are made to the entry during this process, it is + // just moved from pending to committed. This is an alternative to + // RendererDidNavigate for simple TabContents types. + // + // When the pending entry is a new navigation, it will have a page ID of -1. + // The caller should leave this as-is. CommitPendingEntry will generate a + // new page ID for you and update the TabContents with that ID. + void CommitPendingEntry(); + + // Discards the pending and transient entries if any. + void DiscardNonCommittedEntries(); + + // Returns the pending entry corresponding to the navigation that is + // currently in progress, or null if there is none. + NavigationEntry* pending_entry() const { + return pending_entry_; + } + + // Returns the index of the pending entry or -1 if the pending entry + // corresponds to a new navigation (created via LoadURL). + int pending_entry_index() const { + return pending_entry_index_; + } + + // Transient entry ----------------------------------------------------------- + + // Adds an entry that is returned by GetActiveEntry(). The entry is + // transient: any navigation causes it to be removed and discarded. + // The NavigationController becomes the owner of |entry| and deletes it when + // it discards it. This is useful with interstitial page that need to be + // represented as an entry, but should go away when the user navigates away + // from them. + // Note that adding a transient entry does not change the active contents. + void AddTransientEntry(NavigationEntry* entry); + + // Returns the transient entry if any. Note that the returned entry is owned + // by the navigation controller and may be deleted at any time. + NavigationEntry* GetTransientEntry() const; + + // New navigations ----------------------------------------------------------- + + // Loads the specified URL. + void LoadURL(const GURL& url, const GURL& referrer, + PageTransition::Type type); + + // Loads the current page if this NavigationController was restored from + // history and the current page has not loaded yet. + void LoadIfNecessary(); + + // Renavigation -------------------------------------------------------------- + + // Navigation relative to the "current entry" + bool CanGoBack() const; + bool CanGoForward() const; + void GoBack(); + void GoForward(); + + // Navigates to the specified absolute index. + void GoToIndex(int index); + + // Navigates to the specified offset from the "current entry". Does nothing if + // the offset is out of bounds. + void GoToOffset(int offset); + + // Reloads the current entry. If |check_for_repost| is true and the current + // entry has POST data the user is prompted to see if they really want to + // reload the page. In nearly all cases pass in true. + void Reload(bool check_for_repost); + // Like Reload(), but don't use caches (aka "shift-reload"). + void ReloadIgnoringCache(bool check_for_repost); + + // Removing of entries ------------------------------------------------------- + + // Removes the entry at the specified |index|. This call dicards any pending + // and transient entries. |default_url| is the URL that the navigation + // controller navigates to if there are no more entries after the removal. + // If |default_url| is empty, we default to "about:blank". + void RemoveEntryAtIndex(int index, const GURL& default_url); + + // TabContents --------------------------------------------------------------- + + // Returns the tab contents associated with this controller. Non-NULL except + // during set-up of the tab. + TabContents* tab_contents() const { + // This currently returns the active tab contents which should be renamed to + // tab_contents. + return tab_contents_; + } + + // Called when a document has been loaded in a frame. + void DocumentLoadedInFrame(); + + // For use by TabContents ---------------------------------------------------- + + // Handles updating the navigation state after the renderer has navigated. + // This is used by the TabContents. Simpler tab contents types can use + // CommitPendingEntry below. + // + // 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. + // + // |extra_invalidate_flags| are an additional set of flags (InvalidateTypes) + // added to the flags sent to the delegate's NotifyNavigationStateChanged. + bool RendererDidNavigate(const ViewHostMsg_FrameNavigate_Params& params, + int extra_invalidate_flags, + LoadCommittedDetails* details); + + // Notifies us that we just became active. This is used by the TabContents + // so that we know to load URLs that were pending as "lazy" loads. + void SetActive(bool is_active); + + // Broadcasts the NOTIFY_NAV_ENTRY_CHANGED notification for the given entry + // (which must be at the given index). This will keep things in sync like + // the saved session. + void NotifyEntryChanged(const NavigationEntry* entry, int index); + + // 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 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 would 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; + + // Copies the navigation state from the given controller to this one. This + // one should be empty (just created). + void CopyStateFrom(const NavigationController& source); + + // A variant of CopyStateFrom. Removes all entries from this except the last + // entry, inserts all entries from |source| before and including the active + // entry. This method is intended for use when the last entry of |this| is the + // active entry. For example: + // source: A B *C* D + // this: E F *G* (last must be active or pending) + // result: A B *G* + // This ignores the transient index of the source and honors that of 'this'. + void CopyStateFromAndPrune(NavigationController* source); + + // Removes all the entries except the active entry. If there is a new pending + // navigation it is preserved. + void PruneAllButActive(); + + // Random data --------------------------------------------------------------- + + // Returns the identifier used by session restore. + const SessionID& session_id() const { return session_id_; } + + // Identifier of the window we're in. + void SetWindowID(const SessionID& id); + const SessionID& window_id() const { return window_id_; } + + SSLManager* ssl_manager() { return &ssl_manager_; } + + // Returns true if a reload happens when activated (SetActive(true) is + // invoked). This is true for session/tab restore and cloned tabs. + bool needs_reload() const { return needs_reload_; } + + // Sets the max restored page ID this NavigationController has seen, if it + // was restored from a previous session. + void set_max_restored_page_id(int32 max_id) { + max_restored_page_id_ = max_id; + } + + // Returns the largest restored page ID seen in this navigation controller, + // if it was restored from a previous session. (-1 otherwise) + int32 max_restored_page_id() const { return max_restored_page_id_; } + + // The session storage namespace that all child render views should use. + SessionStorageNamespace* session_storage_namespace() const { + return session_storage_namespace_; + } + + // Disables checking for a repost and prompting the user. This is used during + // testing. + static void DisablePromptOnRepost(); + + // Maximum number of entries before we start removing entries from the front. +#ifdef UNIT_TEST + static void set_max_entry_count(size_t max_entry_count) { + max_entry_count_ = max_entry_count; + } +#endif + static size_t max_entry_count() { return max_entry_count_; } + + // Cancels a repost that brought up a warning. + void CancelPendingReload(); + // Continues a repost that brought up a warning. + void ContinuePendingReload(); + + // Returns true if we are navigating to the URL the tab is opened with. + bool IsInitialNavigation(); + + // Creates navigation entry and translates the virtual url to a real one. + // Used when restoring a tab from a TabNavigation object and when navigating + // to a new URL using LoadURL. + static NavigationEntry* CreateNavigationEntry(const GURL& url, + const GURL& referrer, + PageTransition::Type transition, + Profile* profile); + + private: + class RestoreHelper; + friend class RestoreHelper; + friend class TabContents; // For invoking OnReservedPageIDRange. + + // Classifies the given renderer navigation (see the NavigationType enum). + NavigationType::Type 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(NavigationEntry* 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* did_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. + // + // |extra_invalidate_flags| are an additional set of flags (InvalidateTypes) + // added to the flags sent to the delegate's NotifyNavigationStateChanged. + void NotifyNavigationEntryCommitted(LoadCommittedDetails* details, + int extra_invalidate_flags); + + // 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(NavigationEntry* 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. See RestoreFromState for a description of from_last_session. + void FinishRestore(int selected_index, bool from_last_session); + + // 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(NavigationEntry* entry, bool replace); + + // Discards the pending and transient entries. + void DiscardNonCommittedEntriesInternal(); + + // Discards the transient entry. + void DiscardTransientEntry(); + + // 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); + + // Creates a new NavigationEntry for each TabNavigation in navigations, adding + // the NavigationEntry to entries. This is used during session restore. + void CreateNavigationEntriesFromTabNavigations( + const std::vector<TabNavigation>& navigations, + std::vector<linked_ptr<NavigationEntry> >* entries); + + // 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 NavigationController& source, int max_index); + + // --------------------------------------------------------------------------- + + // The user profile associated with this controller + Profile* profile_; + + // List of NavigationEntry for this tab + typedef std::vector<linked_ptr<NavigationEntry> > 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. + NavigationEntry* 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 tab contents associated with the controller. Possibly NULL during + // setup. + TabContents* tab_contents_; + + // The max restored page ID in this controller, if it was restored. We must + // store this so that TabContents 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_; + + // Unique identifier of this controller for session restore. This id is only + // unique within the current session, and is not guaranteed to be unique + // across sessions. + SessionID session_id_; + + // Unique identifier of the window we're in. Used by session restore. + SessionID window_id_; + + // The time ticks at which the last document was loaded. + base::TimeTicks last_document_loaded_; + + // The session storage id that any (indirectly) owned RenderView should use. + scoped_refptr<SessionStorageNamespace> session_storage_namespace_; + + // Should Reload check for post data? The default is true, but is set to false + // when testing. + static bool check_for_repost_; + + // The maximum number of entries that a navigation controller can store. + static size_t max_entry_count_; + + // If a repost is pending, its type (RELOAD or RELOAD_IGNORING_CACHE), + // NO_RELOAD otherwise. + ReloadType pending_reload_; + + DISALLOW_COPY_AND_ASSIGN(NavigationController); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_NAVIGATION_CONTROLLER_H_ diff --git a/content/browser/tab_contents/navigation_controller_unittest.cc b/content/browser/tab_contents/navigation_controller_unittest.cc new file mode 100644 index 0000000..2691305 --- /dev/null +++ b/content/browser/tab_contents/navigation_controller_unittest.cc @@ -0,0 +1,2025 @@ +// Copyright (c) 2010 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/file_util.h" +#include "base/path_service.h" +#include "base/scoped_ptr.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/renderer_host/test/test_render_view_host.h" +#include "chrome/browser/sessions/session_service.h" +#include "chrome/browser/sessions/session_service_test_helper.h" +#include "chrome/browser/sessions/session_types.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_delegate.h" +#include "chrome/browser/tab_contents/test_tab_contents.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/test/test_notification_tracker.h" +#include "chrome/test/testing_profile.h" +#include "net/base/net_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/webkit_glue.h" + +using base::Time; + +// NavigationControllerTest ---------------------------------------------------- + +class NavigationControllerTest : public RenderViewHostTestHarness { + public: + NavigationControllerTest() {} +}; + +// NavigationControllerHistoryTest --------------------------------------------- + +class NavigationControllerHistoryTest : public NavigationControllerTest { + public: + NavigationControllerHistoryTest() + : url0("http://foo1"), + url1("http://foo1"), + url2("http://foo1"), + profile_manager_(NULL) { + } + + virtual ~NavigationControllerHistoryTest() { + // Prevent our base class from deleting the profile since profile's + // lifetime is managed by profile_manager_. + STLDeleteElements(&windows_); + } + + // testing::Test overrides. + virtual void SetUp() { + NavigationControllerTest::SetUp(); + + // Force the session service to be created. + SessionService* service = new SessionService(profile()); + profile()->set_session_service(service); + service->SetWindowType(window_id, Browser::TYPE_NORMAL); + service->SetWindowBounds(window_id, gfx::Rect(0, 1, 2, 3), false); + service->SetTabIndexInWindow(window_id, + controller().session_id(), 0); + controller().SetWindowID(window_id); + + session_helper_.set_service(service); + } + + virtual void TearDown() { + // Release profile's reference to the session service. Otherwise the file + // will still be open and we won't be able to delete the directory below. + profile()->set_session_service(NULL); + session_helper_.set_service(NULL); + + // Make sure we wait for history to shut down before continuing. The task + // we add will cause our message loop to quit once it is destroyed. + HistoryService* history = + profile()->GetHistoryService(Profile::IMPLICIT_ACCESS); + if (history) { + history->SetOnBackendDestroyTask(new MessageLoop::QuitTask); + MessageLoop::current()->Run(); + } + + // Do normal cleanup before deleting the profile directory below. + NavigationControllerTest::TearDown(); + + ASSERT_TRUE(file_util::Delete(test_dir_, true)); + ASSERT_FALSE(file_util::PathExists(test_dir_)); + } + + // Deletes the current profile manager and creates a new one. Indirectly this + // shuts down the history database and reopens it. + void ReopenDatabase() { + session_helper_.set_service(NULL); + profile()->set_session_service(NULL); + + SessionService* service = new SessionService(profile()); + profile()->set_session_service(service); + session_helper_.set_service(service); + } + + void GetLastSession() { + profile()->GetSessionService()->TabClosed(controller().window_id(), + controller().session_id(), + false); + + ReopenDatabase(); + Time close_time; + + session_helper_.ReadWindows(&windows_); + } + + CancelableRequestConsumer consumer; + + // URLs for testing. + const GURL url0; + const GURL url1; + const GURL url2; + + std::vector<SessionWindow*> windows_; + + SessionID window_id; + + SessionServiceTestHelper session_helper_; + + private: + ProfileManager* profile_manager_; + FilePath test_dir_; +}; + +void RegisterForAllNavNotifications(TestNotificationTracker* tracker, + NavigationController* controller) { + tracker->ListenFor(NotificationType::NAV_ENTRY_COMMITTED, + Source<NavigationController>(controller)); + tracker->ListenFor(NotificationType::NAV_LIST_PRUNED, + Source<NavigationController>(controller)); + tracker->ListenFor(NotificationType::NAV_ENTRY_CHANGED, + Source<NavigationController>(controller)); +} + +// ----------------------------------------------------------------------------- + +TEST_F(NavigationControllerTest, Defaults) { + EXPECT_FALSE(controller().pending_entry()); + EXPECT_FALSE(controller().GetLastCommittedEntry()); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_EQ(controller().last_committed_entry_index(), -1); + EXPECT_EQ(controller().entry_count(), 0); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); +} + +TEST_F(NavigationControllerTest, LoadURL) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + // 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().entry_count(), 0); + EXPECT_EQ(controller().last_committed_entry_index(), -1); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_FALSE(controller().GetLastCommittedEntry()); + EXPECT_TRUE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + EXPECT_EQ(contents()->GetMaxPageID(), -1); + + // We should have gotten no notifications from the preceeding checks. + EXPECT_EQ(0U, notifications.size()); + + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // The load should now be committed. + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + EXPECT_EQ(contents()->GetMaxPageID(), 0); + + // Load another... + controller().LoadURL(url2, GURL(), PageTransition::TYPED); + + // The load should now be pending. + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_TRUE(controller().pending_entry()); + // TODO(darin): maybe this should really be true? + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + EXPECT_EQ(contents()->GetMaxPageID(), 0); + + rvh()->SendNavigate(1, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // The load should now be committed. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_TRUE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + EXPECT_EQ(contents()->GetMaxPageID(), 1); +} + +// 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + EXPECT_EQ(0U, notifications.size()); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + EXPECT_EQ(0U, notifications.size()); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // We should not have produced a new session history entry. + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); +} + +// Tests loading a URL but discarding it before the load commits. +TEST_F(NavigationControllerTest, LoadURL_Discarded) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + EXPECT_EQ(0U, notifications.size()); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().LoadURL(url2, GURL(), PageTransition::TYPED); + controller().DiscardNonCommittedEntries(); + EXPECT_EQ(0U, notifications.size()); + + // Should not have produced a new session history entry. + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); +} + +// 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + // First make an existing committed entry. + const GURL kExistingURL1("http://eh"); + controller().LoadURL(kExistingURL1, GURL(), + PageTransition::TYPED); + rvh()->SendNavigate(0, kExistingURL1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Do a new navigation without making a pending one. + const GURL kNewURL("http://see"); + rvh()->SendNavigate(99, kNewURL); + + // There should no longer be any pending entry, and the third navigation we + // just made should be committed. + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(1, controller().last_committed_entry_index()); + EXPECT_EQ(kNewURL, controller().GetActiveEntry()->url()); +} + +// 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + // First make an existing committed entry. + const GURL kExistingURL1("http://eh"); + controller().LoadURL(kExistingURL1, GURL(), + PageTransition::TYPED); + rvh()->SendNavigate(0, kExistingURL1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Make a pending entry to somewhere new. + const GURL kExistingURL2("http://bee"); + controller().LoadURL(kExistingURL2, GURL(), + PageTransition::TYPED); + EXPECT_EQ(0U, notifications.size()); + + // Before that commits, do a new navigation. + const GURL kNewURL("http://see"); + rvh()->SendNavigate(3, kNewURL); + + // There should no longer be any pending entry, and the third navigation we + // just made should be committed. + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(1, controller().last_committed_entry_index()); + EXPECT_EQ(kNewURL, controller().GetActiveEntry()->url()); +} + +// 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + // First make some history. + const GURL kExistingURL1("http://eh"); + controller().LoadURL(kExistingURL1, GURL(), + PageTransition::TYPED); + rvh()->SendNavigate(0, kExistingURL1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + const GURL kExistingURL2("http://bee"); + controller().LoadURL(kExistingURL2, GURL(), + PageTransition::TYPED); + rvh()->SendNavigate(1, kExistingURL2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Now make a pending back/forward navigation. The zeroth entry should be + // pending. + controller().GoBack(); + EXPECT_EQ(0U, notifications.size()); + EXPECT_EQ(0, controller().pending_entry_index()); + EXPECT_EQ(1, controller().last_committed_entry_index()); + + // Before that commits, do a new navigation. + const GURL kNewURL("http://see"); + NavigationController::LoadCommittedDetails details; + rvh()->SendNavigate(3, kNewURL); + + // There should no longer be any pending entry, and the third navigation we + // just made should be committed. + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(2, controller().last_committed_entry_index()); + EXPECT_EQ(kNewURL, controller().GetActiveEntry()->url()); +} + +TEST_F(NavigationControllerTest, Reload) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + EXPECT_EQ(0U, notifications.size()); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().Reload(true); + EXPECT_EQ(0U, notifications.size()); + + // The reload is pending. + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), 0); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_TRUE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Now the reload is committed. + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); +} + +// Tests what happens when a reload navigation produces a new page. +TEST_F(NavigationControllerTest, Reload_GeneratesNewPage) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().Reload(true); + EXPECT_EQ(0U, notifications.size()); + + rvh()->SendNavigate(1, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Now the reload is committed. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_TRUE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); +} + +// Tests what happens when we navigate back successfully +TEST_F(NavigationControllerTest, Back) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + const GURL url2("http://foo2"); + rvh()->SendNavigate(1, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().GoBack(); + EXPECT_EQ(0U, notifications.size()); + + // We should now have a pending navigation to go back. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_EQ(controller().pending_entry_index(), 0); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_TRUE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_TRUE(controller().CanGoForward()); + + rvh()->SendNavigate(0, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // The back navigation completed successfully. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_TRUE(controller().CanGoForward()); +} + +// Tests what happens when a back navigation produces a new page. +TEST_F(NavigationControllerTest, Back_GeneratesNewPage) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().LoadURL(url2, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(1, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().GoBack(); + EXPECT_EQ(0U, notifications.size()); + + // We should now have a pending navigation to go back. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_EQ(controller().pending_entry_index(), 0); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_TRUE(controller().pending_entry()); + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_TRUE(controller().CanGoForward()); + + rvh()->SendNavigate(2, url3); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // The back navigation resulted in a completely new navigation. + // TODO(darin): perhaps this behavior will be confusing to users? + EXPECT_EQ(controller().entry_count(), 3); + EXPECT_EQ(controller().last_committed_entry_index(), 2); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &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. + rvh()->SendNavigate(0, kUrl1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // controller().LoadURL(kUrl2, PageTransition::TYPED); + rvh()->SendNavigate(1, kUrl2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Now start a new pending navigation and go back before it commits. + controller().LoadURL(kUrl3, GURL(), PageTransition::TYPED); + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(kUrl3, controller().pending_entry()->url()); + controller().GoBack(); + + // The pending navigation should now be the "back" item and the new one + // should be gone. + EXPECT_EQ(0, controller().pending_entry_index()); + EXPECT_EQ(kUrl1, controller().pending_entry()->url()); +} + +// Receives a back message when there is a different renavigation already +// pending. +TEST_F(NavigationControllerTest, Back_OtherBackPending) { + 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. + rvh()->SendNavigate(0, kUrl1); + rvh()->SendNavigate(1, kUrl2); + rvh()->SendNavigate(2, kUrl3); + + // With nothing pending, say we get a navigation to the second entry. + 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 = + 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)->url()); + EXPECT_EQ(1, controller().last_committed_entry_index()); + EXPECT_EQ(-1, controller().pending_entry_index()); + + // Now go forward to the last item again and say it was committed. + controller().GoForward(); + rvh()->SendNavigate(2, kUrl3); + + // Now start going back one to the second page. It will be pending. + controller().GoBack(); + EXPECT_EQ(1, controller().pending_entry_index()); + EXPECT_EQ(2, controller().last_committed_entry_index()); + + // Not synthesize a totally new back event to the first page. This will not + // match the pending one. + rvh()->SendNavigate(0, kUrl1); + + // The navigation should not have affected the pending entry. + EXPECT_EQ(1, controller().pending_entry_index()); + + // But the navigated entry should be the last committed. + EXPECT_EQ(0, controller().last_committed_entry_index()); + EXPECT_EQ(kUrl1, controller().GetLastCommittedEntry()->url()); +} + +// Tests what happens when we navigate forward successfully. +TEST_F(NavigationControllerTest, Forward) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + rvh()->SendNavigate(1, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().GoBack(); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().GoForward(); + + // We should now have a pending navigation to go forward. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), 1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_TRUE(controller().pending_entry()); + EXPECT_TRUE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + + rvh()->SendNavigate(1, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // The forward navigation completed successfully. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_TRUE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); +} + +// Tests what happens when a forward navigation produces a new page. +TEST_F(NavigationControllerTest, Forward_GeneratesNewPage) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + rvh()->SendNavigate(1, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().GoBack(); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + controller().GoForward(); + EXPECT_EQ(0U, notifications.size()); + + // Should now have a pending navigation to go forward. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_EQ(controller().pending_entry_index(), 1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_TRUE(controller().pending_entry()); + EXPECT_TRUE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + + rvh()->SendNavigate(2, url3); + EXPECT_TRUE(notifications.Check2AndReset( + NotificationType::NAV_LIST_PRUNED, + NotificationType::NAV_ENTRY_COMMITTED)); + + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); // Redirection target + + // First request + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + + EXPECT_EQ(0U, notifications.size()); + rvh()->SendNavigate(0, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Second request + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + + EXPECT_TRUE(controller().pending_entry()); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_EQ(url1, controller().GetActiveEntry()->url()); + + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 0; + params.url = url2; + params.transition = PageTransition::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; + + NavigationController::LoadCommittedDetails details; + + EXPECT_EQ(0U, notifications.size()); + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + EXPECT_TRUE(details.type == NavigationType::SAME_PAGE); + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_EQ(url2, controller().GetActiveEntry()->url()); + + 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); // Redirection target + + // First request as POST + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + controller().GetActiveEntry()->set_has_post_data(true); + + EXPECT_EQ(0U, notifications.size()); + rvh()->SendNavigate(0, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Second request + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + + EXPECT_TRUE(controller().pending_entry()); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_EQ(url1, controller().GetActiveEntry()->url()); + + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 0; + params.url = url2; + params.transition = PageTransition::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; + + NavigationController::LoadCommittedDetails details; + + EXPECT_EQ(0U, notifications.size()); + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + EXPECT_TRUE(details.type == NavigationType::SAME_PAGE); + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_EQ(url2, controller().GetActiveEntry()->url()); + EXPECT_FALSE(controller().GetActiveEntry()->has_post_data()); + + EXPECT_FALSE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); +} + +// A redirect right off the bat should be a NEW_PAGE. +TEST_F(NavigationControllerTest, ImmediateRedirect) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); // Redirection target + + // First request + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + + EXPECT_TRUE(controller().pending_entry()); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_EQ(url1, controller().GetActiveEntry()->url()); + + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 0; + params.url = url2; + params.transition = PageTransition::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; + + NavigationController::LoadCommittedDetails details; + + EXPECT_EQ(0U, notifications.size()); + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + EXPECT_TRUE(details.type == NavigationType::NEW_PAGE); + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_EQ(url2, controller().GetActiveEntry()->url()); + + 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + const GURL url2("http://foo2"); + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 1; + params.url = url2; + params.transition = PageTransition::MANUAL_SUBFRAME; + params.should_update_history = false; + params.gesture = NavigationGestureUser; + params.is_post = false; + + NavigationController::LoadCommittedDetails details; + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(url1, details.previous_url); + EXPECT_FALSE(details.is_auto); + EXPECT_FALSE(details.is_in_page); + EXPECT_FALSE(details.is_main_frame); + + // The new entry should be appended. + EXPECT_EQ(2, controller().entry_count()); + + // New entry should refer to the new page, but the old URL (entries only + // reflect the toplevel URL). + EXPECT_EQ(url1, details.entry->url()); + EXPECT_EQ(params.page_id, details.entry->page_id()); +} + +// 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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &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 = PageTransition::AUTO_SUBFRAME; + params.should_update_history = false; + params.gesture = NavigationGestureAuto; + params.is_post = false; + + NavigationController::LoadCommittedDetails details; + EXPECT_FALSE(controller().RendererDidNavigate(params, 0, &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) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + const GURL url2("http://foo2"); + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 0; + params.url = url2; + params.transition = PageTransition::AUTO_SUBFRAME; + params.should_update_history = false; + params.gesture = NavigationGestureUser; + params.is_post = false; + + // Navigating should do nothing. + NavigationController::LoadCommittedDetails details; + EXPECT_FALSE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_EQ(0U, notifications.size()); + + // There should still be only one entry. + EXPECT_EQ(1, controller().entry_count()); +} + +// Tests navigation and then going back to a subframe navigation. +TEST_F(NavigationControllerTest, BackSubframe) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + // Main page. + const GURL url1("http://foo1"); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // First manual subframe navigation. + const GURL url2("http://foo2"); + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 1; + params.url = url2; + params.transition = PageTransition::MANUAL_SUBFRAME; + params.should_update_history = false; + params.gesture = NavigationGestureUser; + params.is_post = false; + + // This should generate a new entry. + NavigationController::LoadCommittedDetails details; + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(2, controller().entry_count()); + + // 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, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(3, controller().entry_count()); + EXPECT_EQ(2, controller().GetCurrentEntryIndex()); + + // Go back one. + controller().GoBack(); + params.url = url2; + params.page_id = 1; + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(3, controller().entry_count()); + EXPECT_EQ(1, controller().GetCurrentEntryIndex()); + + // Go back one more. + controller().GoBack(); + params.url = url1; + params.page_id = 0; + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(3, controller().entry_count()); + EXPECT_EQ(0, controller().GetCurrentEntryIndex()); +} + +TEST_F(NavigationControllerTest, LinkClick) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + rvh()->SendNavigate(1, url2); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // Should not have produced a new session history entry. + EXPECT_EQ(controller().entry_count(), 2); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_TRUE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); +} + +TEST_F(NavigationControllerTest, InPage) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + // Main page. + const GURL url1("http://foo"); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // First navigation. + const GURL url2("http://foo#a"); + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 1; + params.url = url2; + params.transition = PageTransition::LINK; + params.should_update_history = false; + params.gesture = NavigationGestureUser; + params.is_post = false; + + // This should generate a new entry. + NavigationController::LoadCommittedDetails details; + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_TRUE(details.is_in_page); + EXPECT_FALSE(details.did_replace_entry); + EXPECT_EQ(2, controller().entry_count()); + + // 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, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + // is_in_page is false in that case but should be true. + // See comment in AreURLsInPageNavigation() in navigation_controller.cc + // EXPECT_TRUE(details.is_in_page); + EXPECT_EQ(2, controller().entry_count()); + EXPECT_EQ(0, controller().GetCurrentEntryIndex()); + EXPECT_EQ(back_params.url, controller().GetActiveEntry()->url()); + + // 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, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_TRUE(details.is_in_page); + EXPECT_EQ(2, controller().entry_count()); + EXPECT_EQ(1, controller().GetCurrentEntryIndex()); + EXPECT_EQ(forward_params.url, + controller().GetActiveEntry()->url()); + + // 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, 0, &details)); + controller().GoForward(); + EXPECT_TRUE(controller().RendererDidNavigate(forward_params, 0, &details)); + EXPECT_EQ(forward_params.url, + controller().GetActiveEntry()->url()); + + // 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; + notifications.Reset(); + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_FALSE(details.is_in_page); +} + +TEST_F(NavigationControllerTest, InPage_Replace) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + // Main page. + const GURL url1("http://foo"); + rvh()->SendNavigate(0, url1); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + + // First navigation. + const GURL url2("http://foo#a"); + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 0; // Same page_id + params.url = url2; + params.transition = PageTransition::LINK; + params.should_update_history = false; + params.gesture = NavigationGestureUser; + params.is_post = false; + + // This should NOT generate a new entry. + NavigationController::LoadCommittedDetails details; + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check2AndReset( + NotificationType::NAV_LIST_PRUNED, + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_TRUE(details.is_in_page); + EXPECT_TRUE(details.did_replace_entry); + EXPECT_EQ(1, controller().entry_count()); +} + +// Tests for http://crbug.com/40395 +// Simulates this: +// <script> +// window.location.replace("#a"); +// window.location='http://foo3/'; +// </script> +TEST_F(NavigationControllerTest, ClientRedirectAfterInPageNavigation) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + // Load an initial page. + { + const GURL url("http://foo/"); + rvh()->SendNavigate(0, url); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + } + + // Navigate to a new page. + { + const GURL url("http://foo2/"); + rvh()->SendNavigate(1, url); + controller().DocumentLoadedInFrame(); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + } + + // 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 = PageTransition::LINK; + params.redirects.push_back(url); + params.should_update_history = true; + params.gesture = NavigationGestureUnknown; + params.is_post = false; + + // This should NOT generate a new entry. + NavigationController::LoadCommittedDetails details; + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check2AndReset( + NotificationType::NAV_LIST_PRUNED, + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_TRUE(details.is_in_page); + EXPECT_TRUE(details.did_replace_entry); + EXPECT_EQ(2, controller().entry_count()); + } + + // 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 = PageTransition::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; + + // This SHOULD generate a new entry. + NavigationController::LoadCommittedDetails details; + EXPECT_TRUE(controller().RendererDidNavigate(params, 0, &details)); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_FALSE(details.is_in_page); + EXPECT_EQ(3, controller().entry_count()); + } + + // Verify that BACK brings us back to http://foo2/. + { + const GURL url("http://foo2/"); + controller().GoBack(); + rvh()->SendNavigate(1, url); + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::NAV_ENTRY_COMMITTED)); + EXPECT_EQ(url, controller().GetActiveEntry()->url()); + } +} + +// NotificationObserver implementation used in verifying we've received the +// NotificationType::NAV_LIST_PRUNED method. +class PrunedListener : public NotificationObserver { + public: + explicit PrunedListener(NavigationController* controller) + : notification_count_(0) { + registrar_.Add(this, NotificationType::NAV_LIST_PRUNED, + Source<NavigationController>(controller)); + } + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::NAV_LIST_PRUNED) { + notification_count_++; + details_ = *(Details<NavigationController::PrunedDetails>(details).ptr()); + } + } + + // Number of times NAV_LIST_PRUNED has been observed. + int notification_count_; + + // Details from the last NAV_LIST_PRUNED. + NavigationController::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) { + size_t original_count = NavigationController::max_entry_count(); + const int kMaxEntryCount = 5; + + NavigationController::set_max_entry_count(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(StringPrintf("http://www.a.com/%d", url_index)); + controller().LoadURL(url, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(url_index, url); + } + + EXPECT_EQ(controller().entry_count(), kMaxEntryCount); + + // Created a PrunedListener to observe prune notifications. + PrunedListener listener(&controller()); + + // Navigate some more. + GURL url(StringPrintf("http://www.a.com/%d", url_index)); + controller().LoadURL(url, GURL(), PageTransition::TYPED); + 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().entry_count(), kMaxEntryCount); + EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), + GURL("http:////www.a.com/1")); + + // More navigations. + for (int i = 0; i < 3; i++) { + url = GURL(StringPrintf("http:////www.a.com/%d", url_index)); + controller().LoadURL(url, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(url_index, url); + url_index++; + } + EXPECT_EQ(controller().entry_count(), kMaxEntryCount); + EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), + GURL("http:////www.a.com/4")); + + NavigationController::set_max_entry_count(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<TabNavigation> navigations; + navigations.push_back(TabNavigation(0, url, GURL(), + ASCIIToUTF16("Title"), "state", + PageTransition::LINK)); + TabContents our_contents(profile(), NULL, MSG_ROUTING_NONE, NULL, NULL); + NavigationController& our_controller = our_contents.controller(); + our_controller.RestoreFromState(navigations, 0, true); + our_controller.GoToIndex(0); + + // We should now have one entry, and it should be "pending". + EXPECT_EQ(1, our_controller.entry_count()); + EXPECT_EQ(our_controller.GetEntryAtIndex(0), + our_controller.pending_entry()); + EXPECT_EQ(0, our_controller.GetEntryAtIndex(0)->page_id()); + EXPECT_EQ(NavigationEntry::RESTORE_LAST_SESSION, + our_controller.GetEntryAtIndex(0)->restore_type()); + + // Say we navigated to that entry. + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 0; + params.url = url; + params.transition = PageTransition::LINK; + params.should_update_history = false; + params.gesture = NavigationGestureUser; + params.is_post = false; + NavigationController::LoadCommittedDetails details; + our_controller.RendererDidNavigate(params, 0, &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.entry_count()); + EXPECT_EQ(0, our_controller.last_committed_entry_index()); + EXPECT_FALSE(our_controller.pending_entry()); + EXPECT_EQ(url, + our_controller.GetLastCommittedEntry()->site_instance()->site()); + EXPECT_EQ(NavigationEntry::RESTORE_NONE, + our_controller.GetEntryAtIndex(0)->restore_type()); +} + +// Make sure that the page type and stuff is correct after an interstitial. +TEST_F(NavigationControllerTest, Interstitial) { + // First navigate somewhere normal. + const GURL url1("http://foo"); + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(0, url1); + + // Now navigate somewhere with an interstitial. + const GURL url2("http://bar"); + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + controller().pending_entry()->set_page_type(INTERSTITIAL_PAGE); + + // At this point the interstitial will be displayed and the load will still + // be pending. If the user continues, the load will commit. + rvh()->SendNavigate(1, url2); + + // The page should be a normal page again. + EXPECT_EQ(url2, controller().GetLastCommittedEntry()->url()); + EXPECT_EQ(NORMAL_PAGE, controller().GetLastCommittedEntry()->page_type()); +} + +TEST_F(NavigationControllerTest, RemoveEntry) { + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + const GURL url4("http://foo4"); + const GURL url5("http://foo5"); + const GURL pending_url("http://pending"); + const GURL default_url("http://default"); + + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(0, url1); + controller().LoadURL(url2, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(1, url2); + controller().LoadURL(url3, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(2, url3); + controller().LoadURL(url4, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(3, url4); + controller().LoadURL(url5, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(4, url5); + + // Remove the last entry. + controller().RemoveEntryAtIndex( + controller().entry_count() - 1, default_url); + EXPECT_EQ(4, controller().entry_count()); + EXPECT_EQ(3, controller().last_committed_entry_index()); + NavigationEntry* pending_entry = controller().pending_entry(); + EXPECT_TRUE(pending_entry && pending_entry->url() == url4); + + // Add a pending entry. + controller().LoadURL(pending_url, GURL(), PageTransition::TYPED); + // Now remove the last entry. + controller().RemoveEntryAtIndex( + controller().entry_count() - 1, default_url); + // The pending entry should have been discarded and the last committed entry + // removed. + EXPECT_EQ(3, controller().entry_count()); + EXPECT_EQ(2, controller().last_committed_entry_index()); + pending_entry = controller().pending_entry(); + EXPECT_TRUE(pending_entry && pending_entry->url() == url3); + + // Remove an entry which is not the last committed one. + controller().RemoveEntryAtIndex(0, default_url); + EXPECT_EQ(2, controller().entry_count()); + EXPECT_EQ(1, controller().last_committed_entry_index()); + // No navigation should have been initiated since we did not remove the + // current entry. + EXPECT_FALSE(controller().pending_entry()); + + // Remove the 2 remaining entries. + controller().RemoveEntryAtIndex(1, default_url); + controller().RemoveEntryAtIndex(0, default_url); + + // This should have created a pending default entry. + EXPECT_EQ(0, controller().entry_count()); + EXPECT_EQ(-1, controller().last_committed_entry_index()); + pending_entry = controller().pending_entry(); + EXPECT_TRUE(pending_entry && pending_entry->url() == default_url); +} + +// Tests the transient entry, making sure it goes away with all navigations. +TEST_F(NavigationControllerTest, TransientEntry) { + TestNotificationTracker notifications; + RegisterForAllNavNotifications(¬ifications, &controller()); + + const GURL url0("http://foo0"); + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + const GURL url4("http://foo4"); + const GURL transient_url("http://transient"); + + controller().LoadURL(url0, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(0, url0); + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(1, url1); + + notifications.Reset(); + + // Adding a transient with no pending entry. + NavigationEntry* transient_entry = new NavigationEntry; + transient_entry->set_url(transient_url); + controller().AddTransientEntry(transient_entry); + + // We should not have received any notifications. + EXPECT_EQ(0U, notifications.size()); + + // Check our state. + EXPECT_EQ(transient_url, controller().GetActiveEntry()->url()); + EXPECT_EQ(controller().entry_count(), 3); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_EQ(controller().pending_entry_index(), -1); + EXPECT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_FALSE(controller().pending_entry()); + EXPECT_TRUE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + EXPECT_EQ(contents()->GetMaxPageID(), 1); + + // Navigate. + controller().LoadURL(url2, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(2, url2); + + // We should have navigated, transient entry should be gone. + EXPECT_EQ(url2, controller().GetActiveEntry()->url()); + EXPECT_EQ(controller().entry_count(), 3); + + // Add a transient again, then navigate with no pending entry this time. + transient_entry = new NavigationEntry; + transient_entry->set_url(transient_url); + controller().AddTransientEntry(transient_entry); + EXPECT_EQ(transient_url, controller().GetActiveEntry()->url()); + rvh()->SendNavigate(3, url3); + // Transient entry should be gone. + EXPECT_EQ(url3, controller().GetActiveEntry()->url()); + EXPECT_EQ(controller().entry_count(), 4); + + // Initiate a navigation, add a transient then commit navigation. + controller().LoadURL(url4, GURL(), PageTransition::TYPED); + transient_entry = new NavigationEntry; + transient_entry->set_url(transient_url); + controller().AddTransientEntry(transient_entry); + EXPECT_EQ(transient_url, controller().GetActiveEntry()->url()); + rvh()->SendNavigate(4, url4); + EXPECT_EQ(url4, controller().GetActiveEntry()->url()); + EXPECT_EQ(controller().entry_count(), 5); + + // Add a transient and go back. This should simply remove the transient. + transient_entry = new NavigationEntry; + transient_entry->set_url(transient_url); + controller().AddTransientEntry(transient_entry); + EXPECT_EQ(transient_url, controller().GetActiveEntry()->url()); + EXPECT_TRUE(controller().CanGoBack()); + EXPECT_FALSE(controller().CanGoForward()); + controller().GoBack(); + // Transient entry should be gone. + EXPECT_EQ(url4, controller().GetActiveEntry()->url()); + EXPECT_EQ(controller().entry_count(), 5); + rvh()->SendNavigate(3, url3); + + // Add a transient and go to an entry before the current one. + transient_entry = new NavigationEntry; + transient_entry->set_url(transient_url); + controller().AddTransientEntry(transient_entry); + EXPECT_EQ(transient_url, controller().GetActiveEntry()->url()); + controller().GoToIndex(1); + // The navigation should have been initiated, transient entry should be gone. + EXPECT_EQ(url1, controller().GetActiveEntry()->url()); + rvh()->SendNavigate(1, url1); + + // Add a transient and go to an entry after the current one. + transient_entry = new NavigationEntry; + transient_entry->set_url(transient_url); + controller().AddTransientEntry(transient_entry); + EXPECT_EQ(transient_url, controller().GetActiveEntry()->url()); + 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. + EXPECT_EQ(url2, controller().GetActiveEntry()->url()); + rvh()->SendNavigate(2, url2); + + // Add a transient and go forward. + transient_entry = new NavigationEntry; + transient_entry->set_url(transient_url); + controller().AddTransientEntry(transient_entry); + EXPECT_EQ(transient_url, controller().GetActiveEntry()->url()); + EXPECT_TRUE(controller().CanGoForward()); + controller().GoForward(); + // We should have navigated, transient entry should be gone. + EXPECT_EQ(url3, controller().GetActiveEntry()->url()); + rvh()->SendNavigate(3, url3); + + // Ensure the URLS are correct. + EXPECT_EQ(controller().entry_count(), 5); + EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), url0); + EXPECT_EQ(controller().GetEntryAtIndex(1)->url(), url1); + EXPECT_EQ(controller().GetEntryAtIndex(2)->url(), url2); + EXPECT_EQ(controller().GetEntryAtIndex(3)->url(), url3); + EXPECT_EQ(controller().GetEntryAtIndex(4)->url(), url4); +} + +// Tests that IsInPageNavigation returns appropriate results. Prevents +// regression for bug 1126349. +TEST_F(NavigationControllerTest, IsInPageNavigation) { + // Navigate to URL with no refs. + const GURL url("http://www.google.com/home.html"); + 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. + 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)); +} + +// 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) { + // Navigate the main frame. + const GURL url("http://www.google.com/"); + rvh()->SendNavigate(0, url); + + // We should be at the first navigation entry. + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 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 = PageTransition::AUTO_SUBFRAME; + params.should_update_history = false; + params.gesture = NavigationGestureAuto; + params.is_post = false; + NavigationController::LoadCommittedDetails details; + EXPECT_FALSE(controller().RendererDidNavigate(params, 0, &details)); + + // Nothing should have changed. + EXPECT_EQ(controller().entry_count(), 1); + EXPECT_EQ(controller().last_committed_entry_index(), 0); +} + +// Test view source redirection is reflected in title bar. +TEST_F(NavigationControllerTest, ViewSourceRedirect) { + const char kUrl[] = "view-source:http://redirect.to/google.com"; + const char kResult[] = "http://google.com"; + const char kExpected[] = "view-source:google.com"; + const GURL url(kUrl); + const GURL result_url(kResult); + + controller().LoadURL(url, GURL(), PageTransition::TYPED); + + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 0; + params.url = result_url; + params.transition = PageTransition::SERVER_REDIRECT; + params.should_update_history = false; + params.gesture = NavigationGestureAuto; + params.is_post = false; + NavigationController::LoadCommittedDetails details; + controller().RendererDidNavigate(params, 0, &details); + + EXPECT_EQ(ASCIIToUTF16(kExpected), contents()->GetTitle()); + EXPECT_TRUE(contents()->ShouldDisplayURL()); +} + +// Make sure that on cloning a tabcontents and going back needs_reload is false. +TEST_F(NavigationControllerTest, CloneAndGoBack) { + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + + NavigateAndCommit(url1); + NavigateAndCommit(url2); + + scoped_ptr<TabContents> clone(controller().tab_contents()->Clone()); + + ASSERT_EQ(2, clone->controller().entry_count()); + EXPECT_TRUE(clone->controller().needs_reload()); + clone->controller().GoBack(); + // Navigating back should have triggered needs_reload_ to go false. + EXPECT_FALSE(clone->controller().needs_reload()); +} + +// Make sure that cloning a tabcontents doesn't copy interstitials. +TEST_F(NavigationControllerTest, CloneOmitsInterstitials) { + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + + NavigateAndCommit(url1); + NavigateAndCommit(url2); + + // Add an interstitial entry. Should be deleted with controller. + NavigationEntry* interstitial_entry = new NavigationEntry(); + interstitial_entry->set_page_type(INTERSTITIAL_PAGE); + controller().AddTransientEntry(interstitial_entry); + + scoped_ptr<TabContents> clone(controller().tab_contents()->Clone()); + + ASSERT_EQ(2, clone->controller().entry_count()); +} + +// Tests a subframe navigation while a toplevel navigation is pending. +// http://crbug.com/43967 +TEST_F(NavigationControllerTest, SubframeWhilePending) { + // 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, GURL(), PageTransition::TYPED); + + // 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()->page_id(); + params.url = url1_sub; + params.transition = PageTransition::AUTO_SUBFRAME; + params.should_update_history = false; + params.gesture = NavigationGestureAuto; + params.is_post = false; + NavigationController::LoadCommittedDetails details; + + // This should return false meaning that nothing was actually updated. + EXPECT_FALSE(controller().RendererDidNavigate(params, 0, &details)); + + // The notification should have updated the last committed one, and not + // the pending load. + EXPECT_EQ(url1, controller().GetLastCommittedEntry()->url()); + + // The active entry should be unchanged by the subframe load. + EXPECT_EQ(url2, controller().GetActiveEntry()->url()); +} + +// Tests CopyStateFromAndPrune with 2 urls in source, 1 in dest. +TEST_F(NavigationControllerTest, CopyStateFromAndPrune) { + SessionID id(controller().session_id()); + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + NavigateAndCommit(url1); + NavigateAndCommit(url2); + + scoped_ptr<TestTabContents> other_contents(CreateTestTabContents()); + NavigationController& other_controller = other_contents->controller(); + SessionID other_id(other_controller.session_id()); + other_contents->NavigateAndCommit(url3); + other_controller.CopyStateFromAndPrune(&controller()); + + // other_controller should now contain the 3 urls: url1, url2 and url3. + + ASSERT_EQ(3, other_controller.entry_count()); + + ASSERT_EQ(2, other_controller.GetCurrentEntryIndex()); + + EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->url()); + EXPECT_EQ(url2, other_controller.GetEntryAtIndex(1)->url()); + EXPECT_EQ(url3, other_controller.GetEntryAtIndex(2)->url()); + + // Make sure session ids didn't change. + EXPECT_EQ(id.id(), controller().session_id().id()); + EXPECT_EQ(other_id.id(), other_controller.session_id().id()); +} + +// Test CopyStateFromAndPrune with 2 urls, the first selected and nothing in +// the target. +TEST_F(NavigationControllerTest, CopyStateFromAndPrune2) { + SessionID id(controller().session_id()); + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + NavigateAndCommit(url1); + NavigateAndCommit(url2); + controller().GoBack(); + + scoped_ptr<TestTabContents> other_contents(CreateTestTabContents()); + NavigationController& other_controller = other_contents->controller(); + SessionID other_id(other_controller.session_id()); + other_controller.CopyStateFromAndPrune(&controller()); + + // other_controller should now contain the 1 url: url1. + + ASSERT_EQ(1, other_controller.entry_count()); + + ASSERT_EQ(0, other_controller.GetCurrentEntryIndex()); + + EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->url()); + + // Make sure session ids didn't change. + EXPECT_EQ(id.id(), controller().session_id().id()); + EXPECT_EQ(other_id.id(), other_controller.session_id().id()); +} + +// Test CopyStateFromAndPrune with 2 urls, the first selected and nothing in +// the target. +TEST_F(NavigationControllerTest, CopyStateFromAndPrune3) { + SessionID id(controller().session_id()); + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + NavigateAndCommit(url1); + NavigateAndCommit(url2); + controller().GoBack(); + + scoped_ptr<TestTabContents> other_contents(CreateTestTabContents()); + NavigationController& other_controller = other_contents->controller(); + SessionID other_id(other_controller.session_id()); + other_controller.LoadURL(url3, GURL(), PageTransition::TYPED); + other_controller.CopyStateFromAndPrune(&controller()); + + // other_controller should now contain 1 entry for url1, and a pending entry + // for url3. + + ASSERT_EQ(1, other_controller.entry_count()); + + EXPECT_EQ(0, other_controller.GetCurrentEntryIndex()); + + EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->url()); + + // And there should be a pending entry for url3. + ASSERT_TRUE(other_controller.pending_entry()); + + EXPECT_EQ(url3, other_controller.pending_entry()->url()); + + // Make sure session ids didn't change. + EXPECT_EQ(id.id(), controller().session_id().id()); + EXPECT_EQ(other_id.id(), other_controller.session_id().id()); +} + +// Tests that navigations initiated from the page (with the history object) +// work as expected without navigation entries. +TEST_F(NavigationControllerTest, HistoryNavigate) { + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + 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().pending_entry_index()); + // 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().pending_entry_index()); + // 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().pending_entry_index()); + message = process()->sink().GetFirstMessageMatching(ViewMsg_Navigate::ID); + EXPECT_TRUE(message == NULL); +} + +// Test call to PruneAllButActive for the only entry. +TEST_F(NavigationControllerTest, PruneAllButActiveForSingle) { + const GURL url1("http://foo1"); + NavigateAndCommit(url1); + controller().PruneAllButActive(); + + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), url1); +} + +// Test call to PruneAllButActive for last entry. +TEST_F(NavigationControllerTest, PruneAllButActiveForLast) { + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + NavigateAndCommit(url1); + NavigateAndCommit(url2); + NavigateAndCommit(url3); + controller().GoBack(); + controller().GoBack(); + contents()->CommitPendingNavigation(); + + controller().PruneAllButActive(); + + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), url1); +} + +// Test call to PruneAllButActive for intermediate entry. +TEST_F(NavigationControllerTest, PruneAllButActiveForIntermediate) { + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + NavigateAndCommit(url1); + NavigateAndCommit(url2); + NavigateAndCommit(url3); + controller().GoBack(); + contents()->CommitPendingNavigation(); + + controller().PruneAllButActive(); + + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(controller().GetEntryAtIndex(0)->url(), url2); +} + +// Test call to PruneAllButActive for intermediate entry. +TEST_F(NavigationControllerTest, PruneAllButActiveForPending) { + const GURL url1("http://foo1"); + const GURL url2("http://foo2"); + const GURL url3("http://foo3"); + + NavigateAndCommit(url1); + NavigateAndCommit(url2); + NavigateAndCommit(url3); + controller().GoBack(); + + controller().PruneAllButActive(); + + EXPECT_EQ(0, controller().pending_entry_index()); +} + +// Test call to PruneAllButActive for transient entry. +TEST_F(NavigationControllerTest, PruneAllButActiveForTransient) { + const GURL url0("http://foo0"); + const GURL url1("http://foo1"); + const GURL transient_url("http://transient"); + + controller().LoadURL(url0, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(0, url0); + controller().LoadURL(url1, GURL(), PageTransition::TYPED); + rvh()->SendNavigate(1, url1); + + // Adding a transient with no pending entry. + NavigationEntry* transient_entry = new NavigationEntry; + transient_entry->set_url(transient_url); + controller().AddTransientEntry(transient_entry); + + controller().PruneAllButActive(); + + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(-1, controller().pending_entry_index()); + EXPECT_EQ(controller().GetTransientEntry()->url(), transient_url); +} + +/* TODO(brettw) These test pass on my local machine but fail on the XP buildbot + (but not Vista) cleaning up the directory after they run. + This should be fixed. + +// A basic test case. Navigates to a single url, and make sure the history +// db matches. +TEST_F(NavigationControllerHistoryTest, Basic) { + controller().LoadURL(url0, GURL(), PageTransition::LINK); + rvh()->SendNavigate(0, url0); + + GetLastSession(); + + session_helper_.AssertSingleWindowWithSingleTab(windows_, 1); + session_helper_.AssertTabEquals(0, 0, 1, *(windows_[0]->tabs[0])); + TabNavigation nav1(0, url0, GURL(), string16(), + webkit_glue::CreateHistoryStateForURL(url0), + PageTransition::LINK); + session_helper_.AssertNavigationEquals(nav1, + windows_[0]->tabs[0]->navigations[0]); +} + +// Navigates to three urls, then goes back and make sure the history database +// is in sync. +TEST_F(NavigationControllerHistoryTest, NavigationThenBack) { + rvh()->SendNavigate(0, url0); + rvh()->SendNavigate(1, url1); + rvh()->SendNavigate(2, url2); + + controller().GoBack(); + rvh()->SendNavigate(1, url1); + + GetLastSession(); + + session_helper_.AssertSingleWindowWithSingleTab(windows_, 3); + session_helper_.AssertTabEquals(0, 1, 3, *(windows_[0]->tabs[0])); + + TabNavigation nav(0, url0, GURL(), string16(), + webkit_glue::CreateHistoryStateForURL(url0), + PageTransition::LINK); + session_helper_.AssertNavigationEquals(nav, + windows_[0]->tabs[0]->navigations[0]); + nav.set_url(url1); + session_helper_.AssertNavigationEquals(nav, + windows_[0]->tabs[0]->navigations[1]); + nav.set_url(url2); + session_helper_.AssertNavigationEquals(nav, + windows_[0]->tabs[0]->navigations[2]); +} + +// Navigates to three urls, then goes back twice, then loads a new url. +TEST_F(NavigationControllerHistoryTest, NavigationPruning) { + rvh()->SendNavigate(0, url0); + rvh()->SendNavigate(1, url1); + rvh()->SendNavigate(2, url2); + + controller().GoBack(); + rvh()->SendNavigate(1, url1); + + controller().GoBack(); + rvh()->SendNavigate(0, url0); + + rvh()->SendNavigate(3, url2); + + // Now have url0, and url2. + + GetLastSession(); + + session_helper_.AssertSingleWindowWithSingleTab(windows_, 2); + session_helper_.AssertTabEquals(0, 1, 2, *(windows_[0]->tabs[0])); + + TabNavigation nav(0, url0, GURL(), string16(), + webkit_glue::CreateHistoryStateForURL(url0), + PageTransition::LINK); + session_helper_.AssertNavigationEquals(nav, + windows_[0]->tabs[0]->navigations[0]); + nav.set_url(url2); + session_helper_.AssertNavigationEquals(nav, + windows_[0]->tabs[0]->navigations[1]); +} +*/ diff --git a/content/browser/tab_contents/navigation_entry.cc b/content/browser/tab_contents/navigation_entry.cc new file mode 100644 index 0000000..147461b --- /dev/null +++ b/content/browser/tab_contents/navigation_entry.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2006-2008 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/tab_contents/navigation_entry.h" + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/url_constants.h" +#include "content/browser/tab_contents/navigation_controller.h" +#include "grit/app_resources.h" +#include "net/base/net_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/text/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 GetUniqueID() { + static int unique_id_counter = 0; + return ++unique_id_counter; +} + +NavigationEntry::SSLStatus::SSLStatus() + : security_style_(SECURITY_STYLE_UNKNOWN), + cert_id_(0), + cert_status_(0), + security_bits_(-1), + connection_status_(0), + content_status_(NORMAL_CONTENT) { +} + +NavigationEntry::FaviconStatus::FaviconStatus() : valid_(false) { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + bitmap_ = *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON); +} + + +NavigationEntry::NavigationEntry() + : unique_id_(GetUniqueID()), + site_instance_(NULL), + page_type_(NORMAL_PAGE), + update_virtual_url_with_url_(false), + page_id_(-1), + transition_type_(PageTransition::LINK), + has_post_data_(false), + restore_type_(RESTORE_NONE) { +} + +NavigationEntry::NavigationEntry(SiteInstance* instance, + int page_id, + const GURL& url, + const GURL& referrer, + const string16& title, + PageTransition::Type transition_type) + : unique_id_(GetUniqueID()), + site_instance_(instance), + page_type_(NORMAL_PAGE), + url_(url), + referrer_(referrer), + update_virtual_url_with_url_(false), + title_(title), + page_id_(page_id), + transition_type_(transition_type), + has_post_data_(false), + restore_type_(RESTORE_NONE) { +} + +NavigationEntry::~NavigationEntry() { +} + +void NavigationEntry::set_site_instance(SiteInstance* site_instance) { + site_instance_ = site_instance; +} + +const string16& NavigationEntry::GetTitleForDisplay( + const std::string& languages) { + // 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; + std::wstring elided_title; + if (!virtual_url_.is_empty()) { + title = net::FormatUrl(virtual_url_, languages); + } else if (!url_.is_empty()) { + title = net::FormatUrl(url_, languages); + } + ui::ElideString(UTF16ToWideHack(title), chrome::kMaxTitleChars, + &elided_title); + cached_display_title_ = WideToUTF16Hack(elided_title); + return cached_display_title_; +} + +bool NavigationEntry::IsViewSourceMode() const { + return virtual_url_.SchemeIs(chrome::kViewSourceScheme); +} diff --git a/content/browser/tab_contents/navigation_entry.h b/content/browser/tab_contents/navigation_entry.h new file mode 100644 index 0000000..84db5ef2 --- /dev/null +++ b/content/browser/tab_contents/navigation_entry.h @@ -0,0 +1,429 @@ +// Copyright (c) 2010 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_TAB_CONTENTS_NAVIGATION_ENTRY_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_NAVIGATION_ENTRY_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "chrome/common/page_type.h" +#include "chrome/common/page_transition_types.h" +#include "chrome/common/security_style.h" +#include "googleurl/src/gurl.h" +#include "third_party/skia/include/core/SkBitmap.h" + +class SiteInstance; + +//////////////////////////////////////////////////////////////////////////////// +// +// NavigationEntry class +// +// A NavigationEntry is a data structure that captures all the information +// required to recreate a browsing state. This includes some opaque binary +// state as provided by the TabContents as well as some clear text title and +// URL which is used for our user interface. +// +//////////////////////////////////////////////////////////////////////////////// +class NavigationEntry { + public: + // SSL ----------------------------------------------------------------------- + + // Collects the SSL information for this NavigationEntry. + class SSLStatus { + public: + // Flags used for the page security content status. + enum ContentStatusFlags { + // HTTP page, or HTTPS page with no insecure content. + NORMAL_CONTENT = 0, + + // HTTPS page containing "displayed" HTTP resources (e.g. images, CSS). + DISPLAYED_INSECURE_CONTENT = 1 << 0, + + // HTTPS page containing "executed" HTTP resources (i.e. script). + // Also currently used for HTTPS page containing broken-HTTPS resources; + // this is wrong and should be fixed (see comments in + // SSLPolicy::OnRequestStarted()). + RAN_INSECURE_CONTENT = 1 << 1, + }; + + SSLStatus(); + + bool Equals(const SSLStatus& status) const { + return security_style_ == status.security_style_ && + cert_id_ == status.cert_id_ && + cert_status_ == status.cert_status_ && + security_bits_ == status.security_bits_ && + content_status_ == status.content_status_; + } + + void set_security_style(SecurityStyle security_style) { + security_style_ = security_style; + } + SecurityStyle security_style() const { + return security_style_; + } + + void set_cert_id(int ssl_cert_id) { + cert_id_ = ssl_cert_id; + } + int cert_id() const { + return cert_id_; + } + + void set_cert_status(int ssl_cert_status) { + cert_status_ = ssl_cert_status; + } + int cert_status() const { + return cert_status_; + } + + void set_security_bits(int security_bits) { + security_bits_ = security_bits; + } + int security_bits() const { + return security_bits_; + } + + void set_displayed_insecure_content() { + content_status_ |= DISPLAYED_INSECURE_CONTENT; + } + bool displayed_insecure_content() const { + return (content_status_ & DISPLAYED_INSECURE_CONTENT) != 0; + } + + void set_ran_insecure_content() { + content_status_ |= RAN_INSECURE_CONTENT; + } + bool ran_insecure_content() const { + return (content_status_ & RAN_INSECURE_CONTENT) != 0; + } + + void set_connection_status(int connection_status) { + connection_status_ = connection_status; + } + int connection_status() const { + return connection_status_; + } + + // Raw accessors for all the content status flags. This contains a + // combination of any of the ContentStatusFlags defined above. It is used + // by some tests for checking and for certain copying. Use the per-status + // functions for normal usage. + void set_content_status(int content_status) { + content_status_ = content_status; + } + int content_status() const { + return content_status_; + } + + private: + // See the accessors above for descriptions. + SecurityStyle security_style_; + int cert_id_; + int cert_status_; + int security_bits_; + int connection_status_; + int content_status_; + + // Copy and assignment is explicitly allowed for this class. + }; + + // Favicon ------------------------------------------------------------------- + + // Collects the favicon related information for a NavigationEntry. + class FaviconStatus { + public: + FaviconStatus(); + + // Indicates whether we've gotten an official favicon for the page, or are + // just using the default favicon. + void set_is_valid(bool is_valid) { + valid_ = is_valid; + } + bool is_valid() const { + return valid_; + } + + // The URL of the favicon which was used to load it off the web. + void set_url(const GURL& favicon_url) { + url_ = favicon_url; + } + const GURL& url() const { + return url_; + } + + // The favicon bitmap for the page. If the favicon has not been explicitly + // set or it empty, it will return the default favicon. Note that this is + // loaded asynchronously, so even if the favicon URL is valid we may return + // the default favicon if we haven't gotten the data yet. + void set_bitmap(const SkBitmap& bitmap) { + bitmap_ = bitmap; + } + const SkBitmap& bitmap() const { + return bitmap_; + } + + private: + // See the accessors above for descriptions. + bool valid_; + GURL url_; + SkBitmap bitmap_; + + // Copy and assignment is explicitly allowed for this class. + }; + + // --------------------------------------------------------------------------- + + NavigationEntry(); + NavigationEntry(SiteInstance* instance, + int page_id, + const GURL& url, + const GURL& referrer, + const string16& title, + PageTransition::Type transition_type); + ~NavigationEntry(); + + // Page-related stuff -------------------------------------------------------- + + // A unique ID is preserved across commits and redirects, which means that + // sometimes a NavigationEntry's unique ID needs to be set (e.g. when + // creating a committed entry to correspond to a to-be-deleted pending entry, + // the pending entry's ID must be copied). + void set_unique_id(int unique_id) { + unique_id_ = unique_id; + } + int unique_id() const { + return unique_id_; + } + + // The SiteInstance tells us how to share sub-processes when the tab type is + // TAB_CONTENTS_WEB. This will be NULL otherwise. 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(SiteInstance* site_instance); + SiteInstance* site_instance() const { + return site_instance_; + } + + // The page type tells us if this entry is for an interstitial or error page. + // See the PageType enum above. + void set_page_type(PageType page_type) { + page_type_ = page_type; + } + PageType page_type() const { + return page_type_; + } + + // The actual URL of the page. For some about pages, this may be a scary + // data: URL or something like that. Use virtual_url() below for showing to + // the user. + void set_url(const GURL& url) { + url_ = url; + cached_display_title_.clear(); + } + const GURL& url() const { + return url_; + } + + // The referring URL. Can be empty. + void set_referrer(const GURL& referrer) { + referrer_ = referrer; + } + const GURL& referrer() const { + return referrer_; + } + + // The virtual URL, when nonempty, will override the actual URL of the page + // when we display it to the user. This allows us to have nice and friendly + // URLs that the user sees for things like about: URLs, but actually feed + // the renderer a data URL that results in the content loading. + // + // virtual_url() will return the URL to display to the user in all cases, so + // if there is no overridden display URL, it will return the actual one. + void set_virtual_url(const GURL& url) { + virtual_url_ = (url == url_) ? GURL() : url; + cached_display_title_.clear(); + } + bool has_virtual_url() const { + return !virtual_url_.is_empty(); + } + const GURL& virtual_url() const { + return virtual_url_.is_empty() ? url_ : virtual_url_; + } + + 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; + } + + // The title as set by the page. This will be empty if there is no title set. + // The caller is responsible for detecting when there is no title and + // displaying the appropriate "Untitled" label if this is being displayed to + // the user. + void set_title(const string16& title) { + title_ = title; + cached_display_title_.clear(); + } + const string16& title() const { + return title_; + } + + // The favicon data and tracking information. See FaviconStatus above. + const FaviconStatus& favicon() const { + return favicon_; + } + FaviconStatus& favicon() { + return favicon_; + } + + // Content state is an opaque blob created by WebKit that represents the + // state of the page. This includes form entries and scroll position for each + // frame. We store it so that we can supply it back to WebKit to restore form + // state properly when the user goes back and forward. + // + // WARNING: This state is saved to the file and used to restore previous + // states. If the format is modified in the future, we should still be able to + // deal with older versions. + void set_content_state(const std::string& state) { + content_state_ = state; + } + const std::string& content_state() const { + return content_state_; + } + + // Describes the current page that the tab represents. For web pages + // (TAB_CONTENTS_WEB) this is the ID that the renderer generated for the page + // and is how we can tell new versus renavigations. + void set_page_id(int page_id) { + page_id_ = page_id; + } + int32 page_id() const { + return page_id_; + } + + // All the SSL flags and state. See SSLStatus above. + const SSLStatus& ssl() const { + return ssl_; + } + SSLStatus& ssl() { + return ssl_; + } + + // Page-related helpers ------------------------------------------------------ + + // Returns the title to be displayed on the tab. This could be the title of + // the page if it is available or the URL. |languages| is the list of + // accpeted languages (e.g., prefs::kAcceptLanguages) or empty if proper + // URL formatting isn't needed (e.g., unit tests). + const string16& GetTitleForDisplay(const std::string& languages); + + // Returns true if the current tab is in view source mode. This will be false + // if there is no navigation. + bool IsViewSourceMode() const; + + // Tracking stuff ------------------------------------------------------------ + + // The transition type indicates what the user did to move to this page from + // the previous page. + void set_transition_type(PageTransition::Type transition_type) { + transition_type_ = transition_type; + } + PageTransition::Type transition_type() const { + return transition_type_; + } + + // The user typed URL was the URL that the user initiated the navigation + // with, regardless of any redirects. This is used to generate keywords, for + // example, based on "what the user thinks the site is called" rather than + // what it's actually called. For example, if the user types "foo.com", that + // may redirect somewhere arbitrary like "bar.com/foo", and we want to use + // the name that the user things of the site as having. + // + // This URL will be is_empty() if the URL was navigated to some other way. + // Callers should fall back on using the regular or display URL in this case. + void set_user_typed_url(const GURL& user_typed_url) { + user_typed_url_ = user_typed_url; + } + const GURL& user_typed_url() const { + return user_typed_url_; + } + + // Post data is form data that was posted to get to this page. The data will + // have to be reposted to reload the page properly. This flag indicates + // whether the page had post data. + // + // The actual post data is stored in the content_state and is extracted by + // WebKit to actually make the request. + void set_has_post_data(bool has_post_data) { + has_post_data_ = has_post_data; + } + bool has_post_data() const { + return has_post_data_; + } + + // Enumerations of the possible restore types. + enum RestoreType { + // The entry has been restored is from the last session. + RESTORE_LAST_SESSION, + + // 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_; + } + + 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 appropriately. + // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + + // See the accessors above for descriptions. + int unique_id_; + scoped_refptr<SiteInstance> site_instance_; + PageType page_type_; + GURL url_; + GURL referrer_; + GURL virtual_url_; + bool update_virtual_url_with_url_; + string16 title_; + FaviconStatus favicon_; + std::string content_state_; + int32 page_id_; + SSLStatus ssl_; + PageTransition::Type transition_type_; + GURL user_typed_url_; + bool has_post_data_; + RestoreType restore_type_; + + // This is a cached version of the result of GetTitleForDisplay. It prevents + // us from having to do URL formatting on the URL evey time the title is + // displayed. When the URL, virtual URL, or title is set, this should be + // cleared to force a refresh. + string16 cached_display_title_; + + // Copy and assignment is explicitly allowed for this class. +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_NAVIGATION_ENTRY_H_ diff --git a/content/browser/tab_contents/navigation_entry_unittest.cc b/content/browser/tab_contents/navigation_entry_unittest.cc new file mode 100644 index 0000000..014817c --- /dev/null +++ b/content/browser/tab_contents/navigation_entry_unittest.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2010 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/string16.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "testing/gtest/include/gtest/gtest.h" + +class NavigationEntryTest : public testing::Test { + public: + NavigationEntryTest() : instance_(NULL) { + } + + virtual void SetUp() { + entry1_.reset(new NavigationEntry); + + instance_ = SiteInstance::CreateSiteInstance(NULL); + entry2_.reset(new NavigationEntry(instance_, 3, + GURL("test:url"), + GURL("from"), + ASCIIToUTF16("title"), + PageTransition::TYPED)); + } + + virtual void TearDown() { + } + + protected: + scoped_ptr<NavigationEntry> entry1_; + scoped_ptr<NavigationEntry> entry2_; + // SiteInstances are deleted when their NavigationEntries are gone. + SiteInstance* instance_; +}; + +// Test unique ID accessors +TEST_F(NavigationEntryTest, NavigationEntryUniqueIDs) { + // Two entries should have different IDs by default + EXPECT_NE(entry1_.get()->unique_id(), entry2_.get()->unique_id()); + + // Can set an entry to have the same ID as another + entry2_.get()->set_unique_id(entry1_.get()->unique_id()); + EXPECT_EQ(entry1_.get()->unique_id(), entry2_.get()->unique_id()); +} + +// Test URL accessors +TEST_F(NavigationEntryTest, NavigationEntryURLs) { + // Start with no virtual_url (even if a url is set) + EXPECT_FALSE(entry1_.get()->has_virtual_url()); + EXPECT_FALSE(entry2_.get()->has_virtual_url()); + + EXPECT_EQ(GURL(), entry1_.get()->url()); + EXPECT_EQ(GURL(), entry1_.get()->virtual_url()); + EXPECT_TRUE(entry1_.get()->GetTitleForDisplay("").empty()); + + // Setting URL affects virtual_url and GetTitleForDisplay + entry1_.get()->set_url(GURL("http://www.google.com")); + EXPECT_EQ(GURL("http://www.google.com"), entry1_.get()->url()); + EXPECT_EQ(GURL("http://www.google.com"), entry1_.get()->virtual_url()); + EXPECT_EQ(ASCIIToUTF16("www.google.com"), + entry1_.get()->GetTitleForDisplay("")); + + // Title affects GetTitleForDisplay + entry1_.get()->set_title(ASCIIToUTF16("Google")); + EXPECT_EQ(ASCIIToUTF16("Google"), entry1_.get()->GetTitleForDisplay("")); + + // Setting virtual_url doesn't affect URL + entry2_.get()->set_virtual_url(GURL("display:url")); + EXPECT_TRUE(entry2_.get()->has_virtual_url()); + EXPECT_EQ(GURL("test:url"), entry2_.get()->url()); + EXPECT_EQ(GURL("display:url"), entry2_.get()->virtual_url()); + + // Having a title set in constructor overrides virtual URL + EXPECT_EQ(ASCIIToUTF16("title"), entry2_.get()->GetTitleForDisplay("")); + + // User typed URL is independent of the others + EXPECT_EQ(GURL(), entry1_.get()->user_typed_url()); + EXPECT_EQ(GURL(), entry2_.get()->user_typed_url()); + entry2_.get()->set_user_typed_url(GURL("typedurl")); + EXPECT_EQ(GURL("typedurl"), entry2_.get()->user_typed_url()); +} + +// Test Favicon inner class +TEST_F(NavigationEntryTest, NavigationEntryFavicons) { + EXPECT_EQ(GURL(), entry1_.get()->favicon().url()); + entry1_.get()->favicon().set_url(GURL("icon")); + EXPECT_EQ(GURL("icon"), entry1_.get()->favicon().url()); + + // Validity not affected by setting URL + EXPECT_FALSE(entry1_.get()->favicon().is_valid()); + entry1_.get()->favicon().set_is_valid(true); + EXPECT_TRUE(entry1_.get()->favicon().is_valid()); +} + +// Test SSLStatus inner class +TEST_F(NavigationEntryTest, NavigationEntrySSLStatus) { + // Default (unknown) + EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry1_.get()->ssl().security_style()); + EXPECT_EQ(SECURITY_STYLE_UNKNOWN, entry2_.get()->ssl().security_style()); + EXPECT_EQ(0, entry1_.get()->ssl().cert_id()); + EXPECT_EQ(0, entry1_.get()->ssl().cert_status()); + EXPECT_EQ(-1, entry1_.get()->ssl().security_bits()); + EXPECT_FALSE(entry1_.get()->ssl().displayed_insecure_content()); + EXPECT_FALSE(entry1_.get()->ssl().ran_insecure_content()); + + // Change from the defaults + entry2_.get()->ssl().set_security_style(SECURITY_STYLE_AUTHENTICATED); + entry2_.get()->ssl().set_cert_id(4); + entry2_.get()->ssl().set_cert_status(1); + entry2_.get()->ssl().set_security_bits(0); + entry2_.get()->ssl().set_displayed_insecure_content(); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, + entry2_.get()->ssl().security_style()); + EXPECT_EQ(4, entry2_.get()->ssl().cert_id()); + EXPECT_EQ(1, entry2_.get()->ssl().cert_status()); + EXPECT_EQ(0, entry2_.get()->ssl().security_bits()); + EXPECT_TRUE(entry2_.get()->ssl().displayed_insecure_content()); + + entry2_.get()->ssl().set_security_style(SECURITY_STYLE_AUTHENTICATION_BROKEN); + entry2_.get()->ssl().set_ran_insecure_content(); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, + entry2_.get()->ssl().security_style()); + EXPECT_TRUE(entry2_.get()->ssl().ran_insecure_content()); +} + +// Test other basic accessors +TEST_F(NavigationEntryTest, NavigationEntryAccessors) { + // SiteInstance + EXPECT_TRUE(entry1_.get()->site_instance() == NULL); + EXPECT_EQ(instance_, entry2_.get()->site_instance()); + entry1_.get()->set_site_instance(instance_); + EXPECT_EQ(instance_, entry1_.get()->site_instance()); + + // Page type + EXPECT_EQ(NORMAL_PAGE, entry1_.get()->page_type()); + EXPECT_EQ(NORMAL_PAGE, entry2_.get()->page_type()); + entry2_.get()->set_page_type(INTERSTITIAL_PAGE); + EXPECT_EQ(INTERSTITIAL_PAGE, entry2_.get()->page_type()); + + // Referrer + EXPECT_EQ(GURL(), entry1_.get()->referrer()); + EXPECT_EQ(GURL("from"), entry2_.get()->referrer()); + entry2_.get()->set_referrer(GURL("from2")); + EXPECT_EQ(GURL("from2"), entry2_.get()->referrer()); + + // Title + EXPECT_EQ(string16(), entry1_.get()->title()); + EXPECT_EQ(ASCIIToUTF16("title"), entry2_.get()->title()); + entry2_.get()->set_title(ASCIIToUTF16("title2")); + EXPECT_EQ(ASCIIToUTF16("title2"), entry2_.get()->title()); + + // State + EXPECT_EQ(std::string(), entry1_.get()->content_state()); + EXPECT_EQ(std::string(), entry2_.get()->content_state()); + entry2_.get()->set_content_state("state"); + EXPECT_EQ("state", entry2_.get()->content_state()); + + // Page ID + EXPECT_EQ(-1, entry1_.get()->page_id()); + EXPECT_EQ(3, entry2_.get()->page_id()); + entry2_.get()->set_page_id(2); + EXPECT_EQ(2, entry2_.get()->page_id()); + + // Transition type + EXPECT_EQ(PageTransition::LINK, entry1_.get()->transition_type()); + EXPECT_EQ(PageTransition::TYPED, entry2_.get()->transition_type()); + entry2_.get()->set_transition_type(PageTransition::RELOAD); + EXPECT_EQ(PageTransition::RELOAD, entry2_.get()->transition_type()); + + // Post Data + EXPECT_FALSE(entry1_.get()->has_post_data()); + EXPECT_FALSE(entry2_.get()->has_post_data()); + entry2_.get()->set_has_post_data(true); + EXPECT_TRUE(entry2_.get()->has_post_data()); + + // Restored + EXPECT_EQ(NavigationEntry::RESTORE_NONE, entry1_->restore_type()); + EXPECT_EQ(NavigationEntry::RESTORE_NONE, entry2_->restore_type()); + entry2_->set_restore_type(NavigationEntry::RESTORE_LAST_SESSION); + EXPECT_EQ(NavigationEntry::RESTORE_LAST_SESSION, entry2_->restore_type()); +} diff --git a/content/browser/tab_contents/page_navigator.h b/content/browser/tab_contents/page_navigator.h new file mode 100644 index 0000000..e411c24 --- /dev/null +++ b/content/browser/tab_contents/page_navigator.h @@ -0,0 +1,30 @@ +// Copyright (c) 2006-2008 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. + +// PageNavigator defines an interface that can be used to express the user's +// intention to navigate to a particular URL. The implementing class should +// perform the navigation. + +#ifndef CONTENT_BROWSER_TAB_CONTENTS_PAGE_NAVIGATOR_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_PAGE_NAVIGATOR_H_ +#pragma once + +#include "chrome/common/page_transition_types.h" +#include "webkit/glue/window_open_disposition.h" + +class GURL; + +class PageNavigator { + public: + // Opens a URL with the given disposition. The transition specifies how this + // navigation should be recorded in the history system (for example, typed). + virtual void OpenURL(const GURL& url, const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition) = 0; + + protected: + virtual ~PageNavigator() {} +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_PAGE_NAVIGATOR_H_ diff --git a/content/browser/tab_contents/provisional_load_details.cc b/content/browser/tab_contents/provisional_load_details.cc new file mode 100644 index 0000000..d85d846 --- /dev/null +++ b/content/browser/tab_contents/provisional_load_details.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2006-2008 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/tab_contents/provisional_load_details.h" + +#include "chrome/browser/ssl/ssl_manager.h" + +ProvisionalLoadDetails::ProvisionalLoadDetails(bool is_main_frame, + bool is_in_page_navigation, + const GURL& url, + const std::string& security_info, + bool is_content_filtered, + bool is_error_page, + int64 frame_id) + : error_code_(net::OK), + transition_type_(PageTransition::LINK), + url_(url), + is_main_frame_(is_main_frame), + is_in_page_navigation_(is_in_page_navigation), + ssl_cert_id_(0), + ssl_cert_status_(0), + ssl_security_bits_(-1), + ssl_connection_status_(0), + is_content_filtered_(is_content_filtered), + is_error_page_(is_error_page), + frame_id_(frame_id) { + SSLManager::DeserializeSecurityInfo(security_info, + &ssl_cert_id_, + &ssl_cert_status_, + &ssl_security_bits_, + &ssl_connection_status_); +} diff --git a/content/browser/tab_contents/provisional_load_details.h b/content/browser/tab_contents/provisional_load_details.h new file mode 100644 index 0000000..54fe887 --- /dev/null +++ b/content/browser/tab_contents/provisional_load_details.h @@ -0,0 +1,82 @@ +// Copyright (c) 2006-2008 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_TAB_CONTENTS_PROVISIONAL_LOAD_DETAILS_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_PROVISIONAL_LOAD_DETAILS_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "chrome/common/page_transition_types.h" +#include "googleurl/src/gurl.h" + +// This class captures some of the information associated to the provisional +// load of a frame. It is provided as Details with the +// NOTIFY_FRAME_PROVISIONAL_LOAD_START, NOTIFY_FRAME_PROVISIONAL_LOAD_COMMITTED +// and NOTIFY_FAIL_PROVISIONAL_LOAD_WITH_ERROR notifications +// (see notification_types.h). + +// TODO(brettw) this mostly duplicates +// NavigationController::LoadCommittedDetails, it would be nice to unify these +// somehow. +class ProvisionalLoadDetails { + public: + ProvisionalLoadDetails(bool main_frame, + bool in_page_navigation, + const GURL& url, + const std::string& security_info, + bool is_filtered, + bool is_error_page, + int64 frame_id); + virtual ~ProvisionalLoadDetails() { } + + void set_error_code(int error_code) { error_code_ = error_code; } + int error_code() const { return error_code_; } + + void set_transition_type(PageTransition::Type transition_type) { + transition_type_ = transition_type; + } + PageTransition::Type transition_type() const { + return transition_type_; + } + + const GURL& url() const { return url_; } + + bool main_frame() const { return is_main_frame_; } + + bool in_page_navigation() const { return is_in_page_navigation_; } + + int ssl_cert_id() const { return ssl_cert_id_; } + + int ssl_cert_status() const { return ssl_cert_status_; } + + int ssl_security_bits() const { return ssl_security_bits_; } + + int ssl_connection_status() const { return ssl_connection_status_; } + + bool is_content_filtered() const { return is_content_filtered_; } + + bool is_error_page() const { return is_error_page_; } + + int64 frame_id() const { return frame_id_; } + + private: + int error_code_; + PageTransition::Type transition_type_; + GURL url_; + bool is_main_frame_; + bool is_in_page_navigation_; + int ssl_cert_id_; + int ssl_cert_status_; + int ssl_security_bits_; + int ssl_connection_status_; + bool is_content_filtered_; + bool is_error_page_; + int64 frame_id_; + + DISALLOW_COPY_AND_ASSIGN(ProvisionalLoadDetails); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_PROVISIONAL_LOAD_DETAILS_H_ diff --git a/content/browser/tab_contents/render_view_host_manager.cc b/content/browser/tab_contents/render_view_host_manager.cc new file mode 100644 index 0000000..556c3b7 --- /dev/null +++ b/content/browser/tab_contents/render_view_host_manager.cc @@ -0,0 +1,729 @@ +// Copyright (c) 2011 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/tab_contents/render_view_host_manager.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/browser/renderer_host/render_view_host_factory.h" +#include "chrome/browser/renderer_host/render_widget_host_view.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/webui/web_ui.h" +#include "chrome/browser/webui/web_ui_factory.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/url_constants.h" +#include "content/browser/tab_contents/navigation_controller.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/browser/tab_contents/tab_contents_view.h" + +namespace base { +class WaitableEvent; +} + +RenderViewHostManager::RenderViewHostManager( + RenderViewHostDelegate* render_view_delegate, + Delegate* delegate) + : delegate_(delegate), + cross_navigation_pending_(false), + render_view_delegate_(render_view_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. + RenderViewHost* render_view_host = render_view_host_; + render_view_host_ = NULL; + render_view_host->Shutdown(); +} + +void RenderViewHostManager::Init(Profile* profile, + SiteInstance* site_instance, + int 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::CreateSiteInstance(profile); + render_view_host_ = RenderViewHostFactory::Create( + site_instance, render_view_delegate_, routing_id, delegate_-> + GetControllerForRenderManager().session_storage_namespace()); + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, + Source<RenderViewHostManager>(this), + Details<RenderViewHost>(render_view_host_)); +} + +RenderWidgetHostView* RenderViewHostManager::GetRenderWidgetHostView() const { + if (!render_view_host_) + return NULL; + return render_view_host_->view(); +} + +RenderViewHost* RenderViewHostManager::Navigate(const NavigationEntry& entry) { + // Create a pending RenderViewHost. It will give us the one we should use + RenderViewHost* dest_render_view_host = 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_); + } + + // If the renderer crashed, then try to create a new one to satisfy this + // navigation request. + if (!dest_render_view_host->IsRenderViewLive()) { + if (!InitRenderView(dest_render_view_host, entry)) + 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->view()) { + dest_render_view_host->view()->Hide(); + } else { + // This is our primary renderer, notify here as we won't be calling + // CommitPending (which does the notify). + RenderViewHostSwitchedDetails details; + details.new_host = render_view_host_; + details.old_host = NULL; + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CHANGED, + Source<NavigationController>( + &delegate_->GetControllerForRenderManager()), + Details<RenderViewHostSwitchedDetails>(&details)); + } + } + + 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_->Stop(); +} + +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; + + // If the tab becomes unresponsive during unload while doing a + // cross-site navigation, proceed with the navigation. (This assumes that + // the pending RenderViewHost is still responsive.) + int pending_request_id = pending_render_view_host_->GetPendingRequestId(); + if (pending_request_id == -1) { + // 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); + } else { + // The request has been started and paused, while we're waiting for the + // unload handler to finish. We'll pretend that it did, by notifying the + // IO thread to let the response continue. 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 state in + // CrossSiteResourceHandler will already be cleaned up.) + ViewMsg_ClosePage_Params params; + params.closing_process_id = + render_view_host_->process()->id(); + params.closing_route_id = render_view_host_->routing_id(); + params.for_cross_site_transition = true; + params.new_render_process_host_id = + pending_render_view_host_->process()->id(); + params.new_request_id = pending_request_id; + current_host()->process()->CrossSiteClosePageACK(params); + } + return false; +} + +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_.get()) + CommitPending(); + return; + } + + if (render_view_host == pending_render_view_host_) { + // The pending cross-site navigation completed, so show the renderer. + 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::SetWebUIPostCommit(WebUI* web_ui) { + DCHECK(!web_ui_.get()); + web_ui_.reset(web_ui); +} + +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 TabContents 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::ShouldClosePage(bool for_cross_site_transition, + bool proceed) { + 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); + } 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_to_fire_unload); + + if (proceed_to_fire_unload) { + // This is not a cross-site navigation, the tab is being closed. + render_view_host_->ClosePage(false, -1, -1); + } + } +} + +void RenderViewHostManager::OnCrossSiteResponse(int new_render_process_host_id, + int new_request_id) { + // Should only see this while we have a pending renderer. + if (!cross_navigation_pending_) + return; + DCHECK(pending_render_view_host_); + + // Tell the old renderer to run its onunload handler. When it finishes, it + // will send a ClosePage_ACK to the ResourceDispatcherHost with the given + // IDs (of the pending RVH's request), allowing the pending RVH's response to + // resume. + render_view_host_->ClosePage(true, + new_render_process_host_id, new_request_id); + + // 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. + pending_render_view_host_->SetHasPendingCrossSiteRequest(false, + new_request_id); +} + +void RenderViewHostManager::OnCrossSiteNavigationCanceled() { + DCHECK(cross_navigation_pending_); + cross_navigation_pending_ = false; + if (pending_render_view_host_) + CancelPending(); +} + +bool RenderViewHostManager::ShouldTransitionCrossSite() { + // True if we are using process-per-site-instance (default) or + // process-per-site (kProcessPerSite). + return !CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab); +} + +bool RenderViewHostManager::ShouldSwapProcessesForNavigation( + const NavigationEntry* cur_entry, + const NavigationEntry* 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 cur_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 = (cur_entry) ? cur_entry->url() : + render_view_host_->site_instance()->site(); + Profile* profile = delegate_->GetControllerForRenderManager().profile(); + if (WebUIFactory::UseWebUIForURL(profile, current_url)) { + // Force swap if it's not an acceptable URL for Web UI. + if (!WebUIFactory::IsURLAcceptableForWebUI(profile, new_entry->url())) + return true; + } else { + // Force swap if it's a Web UI URL. + if (WebUIFactory::UseWebUIForURL(profile, new_entry->url())) + return true; + } + + if (!cur_entry) { + // Always choose a new process when navigating to extension URLs. The + // process grouping logic will combine all of a given extension's pages + // into the same process. + if (new_entry->url().SchemeIs(chrome::kExtensionScheme)) + return true; + + 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 (cur_entry->IsViewSourceMode() != new_entry->IsViewSourceMode()) + return true; + + // Also, we must switch if one is an extension and the other is not the exact + // same extension. + if (cur_entry->url().SchemeIs(chrome::kExtensionScheme) || + new_entry->url().SchemeIs(chrome::kExtensionScheme)) { + if (cur_entry->url().GetOrigin() != new_entry->url().GetOrigin()) + return true; + } + + return false; +} + +SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry( + const NavigationEntry& entry, + SiteInstance* curr_instance) { + // NOTE: This is only called when ShouldTransitionCrossSite is true. + + // 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) && + entry.transition_type() == PageTransition::GENERATED) + return curr_instance; + + const GURL& dest_url = entry.url(); + + // 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_instance->has_site()) { + // 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.) + if (curr_instance->HasRelatedSiteInstance(dest_url)) { + return curr_instance->GetRelatedSiteInstance(dest_url); + } else { + // 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. + // + // For Web UI (this mostly comes up for the new tab page), the + // SiteInstance has special meaning: we never want to reassign the + // process. If you navigate to another site before the Web UI commits, + // we still want to create a new process rather than re-using the + // existing Web UI process. + if (entry.restore_type() != NavigationEntry::RESTORE_NONE || + WebUIFactory::HasWebUIScheme(dest_url)) + curr_instance->SetSite(dest_url); + return curr_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. + NavigationController& controller = delegate_->GetControllerForRenderManager(); + 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->url() : + curr_instance->site(); + Profile* profile = controller.profile(); + + if (SiteInstance::IsSameWebSite(profile, current_url, 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::CreateSiteInstanceForURL(profile, 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 + // CreatePendingRenderView. + return curr_instance->GetRelatedSiteInstance(dest_url); + } +} + +bool RenderViewHostManager::CreatePendingRenderView( + const NavigationEntry& entry, SiteInstance* instance) { + NavigationEntry* curr_entry = + delegate_->GetControllerForRenderManager().GetLastCommittedEntry(); + if (curr_entry) { + DCHECK(!curr_entry->content_state().empty()); + // TODO(creis): Should send a message to the RenderView to let it know + // we're about to switch away, so that it sends an UpdateState message. + } + + pending_render_view_host_ = RenderViewHostFactory::Create( + instance, render_view_delegate_, MSG_ROUTING_NONE, delegate_-> + GetControllerForRenderManager().session_storage_namespace()); + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, + Source<RenderViewHostManager>(this), + Details<RenderViewHost>(pending_render_view_host_)); + + bool success = InitRenderView(pending_render_view_host_, entry); + if (success) { + // Don't show the view until we get a DidNavigate from it. + pending_render_view_host_->view()->Hide(); + } else { + CancelPending(); + } + return success; +} + +bool RenderViewHostManager::InitRenderView(RenderViewHost* render_view_host, + const NavigationEntry& entry) { + // If the pending navigation is to a WebUI, tell the RenderView about any + // bindings it will need enabled. + if (pending_web_ui_.get()) + render_view_host->AllowBindings(pending_web_ui_->bindings()); + + // Tell the RenderView whether it will be used for an extension process. + Profile* profile = delegate_->GetControllerForRenderManager().profile(); + bool is_extension_process = profile->GetExtensionService() && + profile->GetExtensionService()->ExtensionBindingsAllowed(entry.url()); + render_view_host->set_is_extension_process(is_extension_process); + + return delegate_->CreateRenderViewForRenderManager(render_view_host); +} + +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(); + + // Next commit the Web UI, if any. + web_ui_.swap(pending_web_ui_); + if (web_ui_.get() && pending_web_ui_.get() && !pending_render_view_host_) + web_ui_->DidBecomeActiveForReusedRenderView(); + pending_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_->view() && render_view_host_->view()->HasFocus(); + + // Hide the current view and prepare to destroy it. + // TODO(creis): Get the old RenderViewHost to send us an UpdateState message + // before we destroy it. + if (render_view_host_->view()) + render_view_host_->view()->Hide(); + RenderViewHost* old_render_view_host = render_view_host_; + + // Swap in the pending view and make it active. + render_view_host_ = pending_render_view_host_; + pending_render_view_host_ = NULL; + + // If the view is gone, then this RenderViewHost died while it was hidden. + // We ignored the RenderViewGone call at the time, so we should send it now + // to make sure the sad tab shows up, etc. + if (render_view_host_->view()) + render_view_host_->view()->Show(); + else + delegate_->RenderViewGoneFromRenderManager(render_view_host_); + + // 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_->view()) + render_view_host_->view()->Focus(); + + RenderViewHostSwitchedDetails details; + details.new_host = render_view_host_; + details.old_host = old_render_view_host; + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CHANGED, + Source<NavigationController>(&delegate_->GetControllerForRenderManager()), + Details<RenderViewHostSwitchedDetails>(&details)); + + old_render_view_host->Shutdown(); + + // Let the task manager know that we've swapped RenderViewHosts, since it + // might need to update its process groupings. + delegate_->NotifySwappedFromRenderManager(); +} + +RenderViewHost* RenderViewHostManager::UpdateRendererStateForNavigate( + const NavigationEntry& 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; + } + + // 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 it's bindings set appropriately. + pending_web_ui_.reset(delegate_->CreateWebUIForRenderManager(entry.url())); + + // 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_->site_instance(); + + // 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; + bool force_swap = ShouldSwapProcessesForNavigation( + delegate_->GetLastCommittedNavigationEntryForRenderManager(), + &entry); + if (ShouldTransitionCrossSite() || force_swap) + new_instance = GetSiteInstanceForEntry(entry, curr_instance); + + if (new_instance != curr_instance || force_swap) { + // New SiteInstance. + DCHECK(!cross_navigation_pending_); + + // Create a pending RVH and navigate it. + bool success = CreatePendingRenderView(entry, new_instance); + if (!success) + 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. + + // Make sure the old render view stops, in case a load is in progress. + render_view_host_->Stop(); + + // 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 + // onbeforeunload handler. If the handler returns false, we'll have to + // cancel the request. + DCHECK(!pending_render_view_host_->are_navigations_suspended()); + pending_render_view_host_->SetNavigationsSuspended(true); + + // 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 onunload handler before it sends the response. + pending_render_view_host_->SetHasPendingCrossSiteRequest(true, -1); + + // We now have a pending RVH. + DCHECK(!cross_navigation_pending_); + cross_navigation_pending_ = true; + + // Tell the old render view to run its onbeforeunload handler, since it + // doesn't otherwise know that the cross-site request is happening. This + // will trigger a call to ShouldClosePage with the reply. + render_view_host_->FirePageBeforeUnload(true); + + return pending_render_view_host_; + } else { + if (pending_web_ui_.get() && render_view_host_->IsRenderViewLive()) + pending_web_ui_->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_->routing_id())); + } + } + + // 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() { + RenderViewHost* pending_render_view_host = pending_render_view_host_; + pending_render_view_host_ = NULL; + pending_render_view_host->Shutdown(); + + pending_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; + } +} + +void RenderViewHostManager::SwapInRenderViewHost(RenderViewHost* rvh) { + web_ui_.reset(); + + // Hide the current view and prepare to destroy it. + if (render_view_host_->view()) + render_view_host_->view()->Hide(); + RenderViewHost* old_render_view_host = render_view_host_; + + // Swap in the new view and make it active. + render_view_host_ = rvh; + render_view_host_->set_delegate(render_view_delegate_); + delegate_->CreateViewAndSetSizeForRVH(render_view_host_); + render_view_host_->ActivateDeferredPluginHandles(); + // If the view is gone, then this RenderViewHost died while it was hidden. + // We ignored the RenderViewGone call at the time, so we should send it now + // to make sure the sad tab shows up, etc. + if (render_view_host_->view()) { + // TODO(tburkard,cbentzel): Figure out why this hack is needed and/or + // if it can be removed. On Windows, prerendering will not work without + // doing a Hide before the Show. + render_view_host_->view()->Hide(); + render_view_host_->view()->Show(); + } + + delegate_->UpdateRenderViewSizeForRenderManager(); + + RenderViewHostSwitchedDetails details; + details.new_host = render_view_host_; + details.old_host = old_render_view_host; + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CHANGED, + Source<NavigationController>(&delegate_->GetControllerForRenderManager()), + Details<RenderViewHostSwitchedDetails>(&details)); + + // This will cause the old RenderViewHost to delete itself. + old_render_view_host->Shutdown(); + + // Let the task manager know that we've swapped RenderViewHosts, since it + // might need to update its process groupings. + delegate_->NotifySwappedFromRenderManager(); +} diff --git a/content/browser/tab_contents/render_view_host_manager.h b/content/browser/tab_contents/render_view_host_manager.h new file mode 100644 index 0000000..04e4109 --- /dev/null +++ b/content/browser/tab_contents/render_view_host_manager.h @@ -0,0 +1,273 @@ +// Copyright (c) 2011 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_TAB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/common/notification_registrar.h" + +class WebUI; +class InterstitialPage; +class NavigationController; +class NavigationEntry; +class Profile; +class RenderWidgetHostView; +class RenderViewHost; +class SiteInstance; + +// Manages RenderViewHosts for a TabContents. 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 RenderViewHostManager + : public RenderViewHostDelegate::RendererManagement { + public: + // Functions implemented by our owner that we need. + // + // TODO(brettw) Clean this up! These are all the functions in TabContents 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 + // TabContents are inherited and non-virtual. These are named with + // "RenderManager" so that the duplicate implementation of them will be clear. + class Delegate { + public: + // See tab_contents.h's implementation for more. + virtual bool CreateRenderViewForRenderManager( + RenderViewHost* render_view_host) = 0; + virtual void BeforeUnloadFiredFromRenderManager( + bool proceed, bool* proceed_to_fire_unload) = 0; + virtual void DidStartLoadingFromRenderManager( + RenderViewHost* render_view_host) = 0; + virtual void RenderViewGoneFromRenderManager( + RenderViewHost* render_view_host) = 0; + virtual void UpdateRenderViewSizeForRenderManager() = 0; + virtual void NotifySwappedFromRenderManager() = 0; + virtual NavigationController& GetControllerForRenderManager() = 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 WebUI* 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. + 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; + + protected: + virtual ~Delegate() {} + }; + + // Both delegate pointers must be non-NULL and are not owned by this class. + // They must outlive this class. The RenderViewHostDelegate is what will be + // installed into all RenderViewHosts that are created. + // + // You must call Init() before using this class. + RenderViewHostManager(RenderViewHostDelegate* render_view_delegate, + Delegate* delegate); + virtual ~RenderViewHostManager(); + + // For arguments, see TabContents constructor. + void Init(Profile* profile, + SiteInstance* site_instance, + int routing_id); + + // Returns the currently actuive 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. + RenderViewHost* current_host() const { + return render_view_host_; + } + + // 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. + RenderViewHost* pending_render_view_host() const { + return pending_render_view_host_; + } + + // Returns the current committed Web UI or NULL if none applies. + WebUI* web_ui() const { return web_ui_.get(); } + + // Returns the Web UI for the pending navigation, or NULL of none applies. + WebUI* pending_web_ui() const { return pending_web_ui_.get(); } + + // 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. + RenderViewHost* Navigate(const NavigationEntry& 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(); + + // Called when a renderer's main frame navigates. + void DidNavigateMainFrame(RenderViewHost* render_view_host); + + // Set the WebUI after committing a page load. This is useful for navigations + // initiated from a renderer, where we want to give the new renderer WebUI + // privileges from the originating renderer. + void SetWebUIPostCommit(WebUI* web_ui); + + // 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(InterstitialPage* 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. + InterstitialPage* interstitial_page() const { + return interstitial_page_; + } + + // RenderViewHostDelegate::RendererManagement implementation. + virtual void ShouldClosePage(bool for_cross_site_transition, bool proceed); + virtual void OnCrossSiteResponse(int new_render_process_host_id, + int new_request_id); + virtual void OnCrossSiteNavigationCanceled(); + + // Called when a RenderViewHost is about to be deleted. + void RenderViewDeleted(RenderViewHost* rvh); + + // Allows a caller to swap in a provided RenderViewHost to replace the + // current RenderViewHost. The current RVH will be shutdown and ultimately + // deleted. + void SwapInRenderViewHost(RenderViewHost* rvh); + + private: + friend class TestTabContents; + friend class RenderViewHostManagerTest; + + // 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* cur_entry, + const NavigationEntry* 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 NavigationEntry& entry, + SiteInstance* curr_instance); + + // Helper method to create a pending RenderViewHost for a cross-site + // navigation. + bool CreatePendingRenderView(const NavigationEntry& entry, + SiteInstance* instance); + + // Sets up the necessary state for a new RenderViewHost navigating to the + // given entry. + bool InitRenderView(RenderViewHost* render_view_host, + const NavigationEntry& entry); + + // 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(); + + // Helper method to terminate the pending RenderViewHost. + void CancelPending(); + + RenderViewHost* UpdateRendererStateForNavigate(const NavigationEntry& entry); + + // 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, this delegate is installed into all + // the RenderViewHosts that we create. + RenderViewHostDelegate* render_view_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. + RenderViewHost* render_view_host_; + scoped_ptr<WebUI> 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. 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. + RenderViewHost* pending_render_view_host_; + scoped_ptr<WebUI> pending_web_ui_; + + // The intersitial page currently shown if any, not own by this class + // (the InterstitialPage is self-owned, it deletes itself when hidden). + InterstitialPage* interstitial_page_; + + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(RenderViewHostManager); +}; + +// The "details" for a NOTIFY_RENDER_VIEW_HOST_CHANGED notification. The old +// host can be NULL when the first RenderViewHost is set. +struct RenderViewHostSwitchedDetails { + RenderViewHost* old_host; + RenderViewHost* new_host; +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_RENDER_VIEW_HOST_MANAGER_H_ diff --git a/content/browser/tab_contents/render_view_host_manager_unittest.cc b/content/browser/tab_contents/render_view_host_manager_unittest.cc new file mode 100644 index 0000000..a4b82d8 --- /dev/null +++ b/content/browser/tab_contents/render_view_host_manager_unittest.cc @@ -0,0 +1,345 @@ +// Copyright (c) 2011 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 "chrome/browser/browser_thread.h" +#include "chrome/browser/browser_url_handler.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/renderer_host/test/test_render_view_host.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/render_view_host_manager.h" +#include "chrome/browser/tab_contents/test_tab_contents.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/page_transition_types.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/test_notification_tracker.h" +#include "chrome/test/testing_profile.h" +#include "ipc/ipc_message.h" +#include "testing/gtest/include/gtest/gtest.h" + +class RenderViewHostManagerTest : public RenderViewHostTestHarness { + public: + 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, GURL(), PageTransition::LINK); + active_rvh()->SendNavigate( + static_cast<MockRenderProcessHost*>(active_rvh()->process())-> + max_page_id() + 1, + url); + } + + bool ShouldSwapProcesses(RenderViewHostManager* manager, + const NavigationEntry* cur_entry, + const NavigationEntry* new_entry) const { + return manager->ShouldSwapProcessesForNavigation(cur_entry, new_entry); + } +}; + +// Tests that when you navigate from the New TabPage 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) { + BrowserThread ui_thread(BrowserThread::UI, MessageLoop::current()); + GURL ntp(chrome::kChromeUINewTabURL); + GURL dest("http://www.google.com/"); + + // Navigate our first tab to the new tab page and then to the destination. + NavigateActiveAndCommit(ntp); + NavigateActiveAndCommit(dest); + + // Make a second tab. + TestTabContents contents2(profile_.get(), 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, but the second one is the opposite. + contents2.controller().LoadURL(ntp, GURL(), PageTransition::LINK); + static_cast<TestRenderViewHost*>(contents2.render_manager()-> + current_host())->SendNavigate(100, ntp); + contents2.controller().LoadURL(dest, GURL(), PageTransition::LINK); + static_cast<TestRenderViewHost*>(contents2.render_manager()-> + pending_render_view_host())->SendNavigate(101, dest); + + // The two RVH's should be different in every way. + EXPECT_NE(active_rvh()->process(), contents2.render_view_host()->process()); + EXPECT_NE(active_rvh()->site_instance(), + contents2.render_view_host()->site_instance()); + EXPECT_NE(active_rvh()->site_instance()->browsing_instance(), + contents2.render_view_host()->site_instance()->browsing_instance()); + + // Navigate both to the new tab page, and verify that they share a + // SiteInstance. + NavigateActiveAndCommit(ntp); + + contents2.controller().LoadURL(ntp, GURL(), PageTransition::LINK); + static_cast<TestRenderViewHost*>(contents2.render_manager()-> + pending_render_view_host())->SendNavigate(102, ntp); + + EXPECT_EQ(active_rvh()->site_instance(), + contents2.render_view_host()->site_instance()); +} + +// 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) { + BrowserThread ui_thread(BrowserThread::UI, MessageLoop::current()); + const GURL kNtpUrl(chrome::kChromeUINewTabURL); + 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(kNtpUrl); + + // Navigate. + controller().LoadURL(kUrl, GURL() /* referer */, PageTransition::TYPED); + // Simulate response from RenderView for FirePageBeforeUnload. + rvh()->TestOnMessageReceived( + ViewHostMsg_ShouldClose_ACK(rvh()->routing_id(), true)); + ASSERT_TRUE(pending_rvh()); // New pending RenderViewHost will be created. + RenderViewHost* last_rvh = pending_rvh(); + int new_id = static_cast<MockRenderProcessHost*>(pending_rvh()->process())-> + max_page_id() + 1; + pending_rvh()->SendNavigate(new_id, kUrl); + EXPECT_EQ(controller().last_committed_entry_index(), 1); + ASSERT_TRUE(controller().GetLastCommittedEntry()); + EXPECT_TRUE(kUrl == controller().GetLastCommittedEntry()->url()); + EXPECT_FALSE(controller().pending_entry()); + // Because we're using TestTabContents and TestRenderViewHost in this + // unittest, no one calls TabContents::RenderViewCreated(). So, we see no + // EnableViewSourceMode message, here. + + // Clear queued messages before load. + process()->sink().ClearMessages(); + // Navigate, again. + controller().LoadURL(kUrl, GURL() /* referer */, PageTransition::TYPED); + // The same RenderViewHost should be reused. + EXPECT_FALSE(pending_rvh()); + EXPECT_TRUE(last_rvh == rvh()); + rvh()->SendNavigate(new_id, kUrl); // The same page_id returned. + EXPECT_EQ(controller().last_committed_entry_index(), 1); + EXPECT_FALSE(controller().pending_entry()); + // 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 TestingProfile. + SiteInstance* instance = SiteInstance::CreateSiteInstance(profile_.get()); + EXPECT_FALSE(instance->has_site()); + + TestTabContents tab_contents(profile_.get(), instance); + RenderViewHostManager manager(&tab_contents, &tab_contents); + + manager.Init(profile_.get(), instance, MSG_ROUTING_NONE); + + RenderViewHost* host = manager.current_host(); + ASSERT_TRUE(host); + EXPECT_TRUE(instance == host->site_instance()); + EXPECT_TRUE(&tab_contents == host->delegate()); + 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::CreateSiteInstance(profile_.get()); + + TestTabContents tab_contents(profile_.get(), instance); + notifications.ListenFor(NotificationType::RENDER_VIEW_HOST_CHANGED, + Source<NavigationController>(&tab_contents.controller())); + + // Create. + RenderViewHostManager manager(&tab_contents, &tab_contents); + + manager.Init(profile_.get(), instance, MSG_ROUTING_NONE); + + RenderViewHost* host; + + // 1) The first navigation. -------------------------- + GURL url1("http://www.google.com/"); + NavigationEntry entry1(NULL /* instance */, -1 /* page_id */, url1, + GURL() /* referrer */, string16() /* title */, + PageTransition::TYPED); + 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(host->site_instance()->has_site()); + host->site_instance()->SetSite(url1); + + // 2) Navigate to next site. ------------------------- + GURL url2("http://www.google.com/foo"); + NavigationEntry entry2(NULL /* instance */, -1 /* page_id */, url2, + url1 /* referrer */, string16() /* title */, + PageTransition::LINK); + 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(host->site_instance()->has_site()); + + // 3) Cross-site navigate to next site. -------------- + GURL url3("http://webkit.org/"); + NavigationEntry entry3(NULL /* instance */, -1 /* page_id */, url3, + url2 /* referrer */, string16() /* title */, + PageTransition::LINK); + host = manager.Navigate(entry3); + + // A new RenderViewHost should be created. + EXPECT_TRUE(manager.pending_render_view_host()); + EXPECT_TRUE(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(host->site_instance()->has_site()); + // Check the pending RenderViewHost has been committed. + EXPECT_FALSE(manager.pending_render_view_host()); + + // We should observe a notification. + EXPECT_TRUE(notifications.Check1AndReset( + NotificationType::RENDER_VIEW_HOST_CHANGED)); +} + +// Tests WebUI creation. +TEST_F(RenderViewHostManagerTest, WebUI) { + BrowserThread ui_thread(BrowserThread::UI, MessageLoop::current()); + SiteInstance* instance = SiteInstance::CreateSiteInstance(profile_.get()); + + TestTabContents tab_contents(profile_.get(), instance); + RenderViewHostManager manager(&tab_contents, &tab_contents); + + manager.Init(profile_.get(), instance, MSG_ROUTING_NONE); + + GURL url(chrome::kChromeUINewTabURL); + NavigationEntry entry(NULL /* instance */, -1 /* page_id */, url, + GURL() /* referrer */, string16() /* title */, + PageTransition::TYPED); + RenderViewHost* host = manager.Navigate(entry); + + EXPECT_TRUE(host); + EXPECT_TRUE(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 DOM-UI things that may + // get loaded in between. + EXPECT_TRUE(host->site_instance()->has_site()); + EXPECT_EQ(url, host->site_instance()->site()); + + // 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); +} + +// Tests that chrome: URLs that are not Web UI pages do not get grouped into +// Web UI renderers, even if --process-per-tab is enabled. In that mode, we +// still swap processes if ShouldSwapProcessesForNavigation is true. +// Regression test for bug 46290. +TEST_F(RenderViewHostManagerTest, NonWebUIChromeURLs) { + BrowserThread thread(BrowserThread::UI, &message_loop_); + SiteInstance* instance = SiteInstance::CreateSiteInstance(profile_.get()); + TestTabContents tab_contents(profile_.get(), instance); + RenderViewHostManager manager(&tab_contents, &tab_contents); + manager.Init(profile_.get(), instance, MSG_ROUTING_NONE); + + // NTP is a Web UI page. + GURL ntp_url(chrome::kChromeUINewTabURL); + NavigationEntry ntp_entry(NULL /* instance */, -1 /* page_id */, ntp_url, + GURL() /* referrer */, string16() /* title */, + PageTransition::TYPED); + + // about: URLs are not Web UI pages. + GURL about_url(chrome::kAboutMemoryURL); + // Rewrite so it looks like chrome://about/memory + bool reverse_on_redirect = false; + BrowserURLHandler::RewriteURLIfNecessary( + &about_url, profile_.get(), &reverse_on_redirect); + NavigationEntry about_entry(NULL /* instance */, -1 /* page_id */, about_url, + GURL() /* referrer */, string16() /* title */, + PageTransition::TYPED); + + EXPECT_TRUE(ShouldSwapProcesses(&manager, &ntp_entry, &about_entry)); +} + +// 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) { + GURL url1("http://www.google.com/"); + GURL url2("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(url1); + contents()->NavigateAndCommit(url2); + RenderViewHost* evil_rvh = contents()->render_view_host(); + + // 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()->render_manager()->pending_render_view_host()); + + // Before that RVH has committed, the evil page reloads itself. + ViewHostMsg_FrameNavigate_Params params; + params.page_id = 1; + params.url = url2; + params.transition = PageTransition::CLIENT_REDIRECT; + params.should_update_history = false; + params.gesture = NavigationGestureAuto; + params.was_within_same_page = false; + params.is_post = false; + contents()->TestDidNavigate(evil_rvh, params); + + // That should have cancelled the pending RVH, and the evil RVH should be the + // current one. + EXPECT_TRUE(contents()->render_manager()->pending_render_view_host() == NULL); + EXPECT_EQ(evil_rvh, contents()->render_manager()->current_host()); + + // Also we should not have a pending navigation entry. + NavigationEntry* entry = contents()->controller().GetActiveEntry(); + ASSERT_TRUE(entry != NULL); + EXPECT_EQ(url2, entry->url()); +} diff --git a/content/browser/tab_contents/tab_contents.cc b/content/browser/tab_contents/tab_contents.cc new file mode 100644 index 0000000..30b91e9 --- /dev/null +++ b/content/browser/tab_contents/tab_contents.cc @@ -0,0 +1,2851 @@ +// Copyright (c) 2011 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/tab_contents/tab_contents.h" + +#include <cmath> + +#include "base/auto_reset.h" +#include "base/metrics/histogram.h" +#include "base/metrics/stats_counters.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/autocomplete_history_manager.h" +#include "chrome/browser/autofill/autofill_manager.h" +#include "chrome/browser/blocked_content_container.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_shutdown.h" +#include "chrome/browser/character_encoding.h" +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/browser/content_settings/content_settings_details.h" +#include "chrome/browser/content_settings/host_content_settings_map.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/defaults.h" +#include "chrome/browser/desktop_notification_handler.h" +#include "chrome/browser/dom_operation_notification_details.h" +#include "chrome/browser/download/download_item_model.h" +#include "chrome/browser/download/download_manager.h" +#include "chrome/browser/download/download_request_limiter.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/external_protocol_handler.h" +#include "chrome/browser/favicon_service.h" +#include "chrome/browser/file_select_helper.h" +#include "chrome/browser/google/google_util.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/history/top_sites.h" +#include "chrome/browser/host_zoom_map.h" +#include "chrome/browser/hung_renderer_dialog.h" +#include "chrome/browser/in_process_webkit/session_storage_namespace.h" +#include "chrome/browser/load_from_memory_cache_details.h" +#include "chrome/browser/load_notification_details.h" +#include "chrome/browser/metrics/metric_event_duration_details.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/modal_html_dialog_delegate.h" +#include "chrome/browser/omnibox_search_hint.h" +#include "chrome/browser/pdf_unsupported_feature.h" +#include "chrome/browser/platform_util.h" +#include "chrome/browser/plugin_observer.h" +#include "chrome/browser/prerender/prerender_manager.h" +#include "chrome/browser/prerender/prerender_plt_recorder.h" +#include "chrome/browser/printing/print_preview_message_handler.h" +#include "chrome/browser/printing/print_preview_tab_controller.h" +#include "chrome/browser/printing/print_view_manager.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/render_widget_host_view.h" +#include "chrome/browser/renderer_host/resource_request_details.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/renderer_host/web_cache_manager.h" +#include "chrome/browser/renderer_preferences_util.h" +#include "chrome/browser/safe_browsing/client_side_detection_host.h" +#include "chrome/browser/sessions/session_types.h" +#include "chrome/browser/tab_contents/tab_contents_ssl_helper.h" +#include "chrome/browser/tab_contents/thumbnail_generator.h" +#include "chrome/browser/translate/page_translated_details.h" +#include "chrome/browser/ui/app_modal_dialogs/message_box_handler.h" +#include "chrome/browser/webui/web_ui.h" +#include "chrome/common/bindings_policy.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/content_restriction.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_action.h" +#include "chrome/common/extensions/extension_icon_set.h" +#include "chrome/common/extensions/extension_resource.h" +#include "chrome/common/extensions/url_pattern.h" +#include "chrome/common/navigation_types.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/url_constants.h" +#include "content/browser/tab_contents/infobar_delegate.h" +#include "content/browser/tab_contents/interstitial_page.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/browser/tab_contents/provisional_load_details.h" +#include "content/browser/tab_contents/tab_contents_delegate.h" +#include "content/browser/tab_contents/tab_contents_observer.h" +#include "content/browser/tab_contents/tab_contents_view.h" +#include "net/base/net_util.h" +#include "net/base/registry_controlled_domain.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/codec/png_codec.h" +#include "webkit/glue/password_form.h" +#include "webkit/glue/webpreferences.h" + +#if defined(OS_MACOSX) +#include "app/surface/io_surface_support_mac.h" +#endif // defined(OS_MACOSX) + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/locale_change_guard.h" +#endif // defined(OS_CHROMEOS) + +// Cross-Site Navigations +// +// If a TabContents is told to navigate to a different web site (as determined +// by SiteInstance), it will replace its current RenderViewHost with a new +// RenderViewHost dedicated to the new SiteInstance. This works as follows: +// +// - Navigate determines whether the destination is cross-site, and if so, +// it creates a pending_render_view_host_ and moves into the PENDING +// RendererState. +// - The pending RVH is "suspended," so that no navigation messages are sent to +// its renderer until the onbeforeunload JavaScript handler has a chance to +// run in the current RVH. +// - The pending RVH tells CrossSiteRequestManager (a thread-safe singleton) +// that it has a pending cross-site request. ResourceDispatcherHost will +// check for this when the response arrives. +// - The current RVH runs its onbeforeunload handler. If it returns false, we +// cancel all the pending logic and go back to NORMAL. Otherwise we allow +// the pending RVH to send the navigation request to its renderer. +// - ResourceDispatcherHost receives a ResourceRequest on the IO thread. It +// checks CrossSiteRequestManager to see that the RVH responsible has a +// pending cross-site request, and then installs a CrossSiteEventHandler. +// - When RDH receives a response, the BufferedEventHandler determines whether +// it is a download. If so, it sends a message to the new renderer causing +// it to cancel the request, and the download proceeds in the download +// thread. For now, we stay in a PENDING state (with a pending RVH) until +// the next DidNavigate event for this TabContents. This isn't ideal, but it +// doesn't affect any functionality. +// - After RDH receives a response and determines that it is safe and not a +// download, it pauses the response to first run the old page's onunload +// handler. It does this by asynchronously calling the OnCrossSiteResponse +// method of TabContents on the UI thread, which sends a ClosePage message +// to the current RVH. +// - Once the onunload handler is finished, a ClosePage_ACK message is sent to +// the ResourceDispatcherHost, who unpauses the response. Data is then sent +// to the pending RVH. +// - The pending renderer sends a FrameNavigate message that invokes the +// DidNavigate method. This replaces the current RVH with the +// pending RVH and goes back to the NORMAL RendererState. + +namespace { + +// Amount of time we wait between when a key event is received and the renderer +// is queried for its state and pushed to the NavigationEntry. +const int kQueryStateDelay = 5000; + +const int kSyncWaitDelay = 40; + +// If another javascript message box is displayed within +// kJavascriptMessageExpectedDelay of a previous javascript message box being +// dismissed, display an option to suppress future message boxes from this +// contents. +const int kJavascriptMessageExpectedDelay = 1000; + +// The list of prefs we want to observe. +const char* kPrefsToObserve[] = { + prefs::kAlternateErrorPagesEnabled, + prefs::kDefaultZoomLevel, + prefs::kWebKitJavaEnabled, + prefs::kWebKitJavascriptEnabled, + prefs::kWebKitLoadsImagesAutomatically, + prefs::kWebKitPluginsEnabled, + prefs::kWebKitUsesUniversalDetector, + prefs::kWebKitSerifFontFamily, + prefs::kWebKitSansSerifFontFamily, + prefs::kWebKitFixedFontFamily, + prefs::kWebKitDefaultFontSize, + prefs::kWebKitDefaultFixedFontSize, + prefs::kWebKitMinimumFontSize, + prefs::kWebKitMinimumLogicalFontSize, + prefs::kWebkitTabsToLinks, + prefs::kDefaultCharset + // kWebKitStandardFontIsSerif needs to be added + // if we let users pick which font to use, serif or sans-serif when + // no font is specified or a CSS generic family (serif or sans-serif) + // is not specified. +}; + +const int kPrefsToObserveLength = arraysize(kPrefsToObserve); + +#if defined(OS_WIN) + +BOOL CALLBACK InvalidateWindow(HWND hwnd, LPARAM lparam) { + // Note: erase is required to properly paint some widgets borders. This can + // be seen with textfields. + InvalidateRect(hwnd, NULL, TRUE); + return TRUE; +} +#endif + +ViewMsg_Navigate_Params::NavigationType GetNavigationType( + Profile* profile, const NavigationEntry& entry, + NavigationController::ReloadType reload_type) { + switch (reload_type) { + case NavigationController::RELOAD: + return ViewMsg_Navigate_Params::RELOAD; + case NavigationController::RELOAD_IGNORING_CACHE: + return ViewMsg_Navigate_Params::RELOAD_IGNORING_CACHE; + case NavigationController::NO_RELOAD: + break; // Fall through to rest of function. + } + + if (entry.restore_type() == NavigationEntry::RESTORE_LAST_SESSION && + profile->DidLastSessionExitCleanly()) + return ViewMsg_Navigate_Params::RESTORE; + + return ViewMsg_Navigate_Params::NORMAL; +} + +void MakeNavigateParams(const NavigationEntry& entry, + const NavigationController& controller, + NavigationController::ReloadType reload_type, + ViewMsg_Navigate_Params* params) { + params->page_id = entry.page_id(); + params->pending_history_list_offset = controller.GetIndexOfEntry(&entry); + params->current_history_list_offset = controller.last_committed_entry_index(); + params->current_history_list_length = controller.entry_count(); + params->url = entry.url(); + params->referrer = entry.referrer(); + params->transition = entry.transition_type(); + params->state = entry.content_state(); + params->navigation_type = + GetNavigationType(controller.profile(), entry, reload_type); + params->request_time = base::Time::Now(); +} + +} // namespace + + +// TabContents ---------------------------------------------------------------- + +TabContents::TabContents(Profile* profile, + SiteInstance* site_instance, + int routing_id, + const TabContents* base_tab_contents, + SessionStorageNamespace* session_storage_namespace) + : delegate_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(controller_( + this, profile, session_storage_namespace)), + ALLOW_THIS_IN_INITIALIZER_LIST(view_( + TabContentsView::Create(this))), + ALLOW_THIS_IN_INITIALIZER_LIST(render_manager_(this, this)), + bookmark_drag_(NULL), + is_loading_(false), + crashed_status_(base::TERMINATION_STATUS_STILL_RUNNING), + crashed_error_code_(0), + waiting_for_response_(false), + max_page_id_(-1), + load_state_(net::LOAD_STATE_IDLE), + upload_size_(0), + upload_position_(0), + received_page_title_(false), + blocked_contents_(NULL), + all_contents_blocked_(false), + dont_notify_render_view_(false), + displayed_insecure_content_(false), + extension_app_(NULL), + capturing_contents_(false), + is_being_destroyed_(false), + notify_disconnection_(false), +#if defined(OS_WIN) + message_box_active_(CreateEvent(NULL, TRUE, FALSE, NULL)), +#endif + suppress_javascript_messages_(false), + is_showing_before_unload_dialog_(false), + opener_web_ui_type_(WebUIFactory::kNoWebUI), + language_state_(&controller_), + closed_by_user_gesture_(false), + minimum_zoom_percent_( + static_cast<int>(WebKit::WebView::minTextSizeMultiplier * 100)), + maximum_zoom_percent_( + static_cast<int>(WebKit::WebView::maxTextSizeMultiplier * 100)), + temporary_zoom_settings_(false), + content_restrictions_(0) { + renderer_preferences_util::UpdateFromSystemSettings( + &renderer_preferences_, profile); + + content_settings_delegate_.reset( + new TabSpecificContentSettings(this, profile)); + + render_manager_.Init(profile, site_instance, routing_id); + + // We have the initial size of the view be based on the size of the passed in + // tab contents (normally a tab from the same window). + view_->CreateView(base_tab_contents ? + base_tab_contents->view()->GetContainerSize() : gfx::Size()); + + // Register for notifications about all interested prefs change. + PrefService* prefs = profile->GetPrefs(); + pref_change_registrar_.Init(prefs); + if (prefs) { + for (int i = 0; i < kPrefsToObserveLength; ++i) + pref_change_registrar_.Add(kPrefsToObserve[i], this); + } + + registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED, + NotificationService::AllSources()); +#if defined(OS_LINUX) + registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, + NotificationService::AllSources()); +#endif + + registrar_.Add(this, NotificationType::USER_STYLE_SHEET_UPDATED, + NotificationService::AllSources()); + + // Register for notifications about content setting changes. + registrar_.Add(this, NotificationType::CONTENT_SETTINGS_CHANGED, + NotificationService::AllSources()); + + // Listen for extension changes so we can update extension_for_current_page_. + registrar_.Add(this, NotificationType::EXTENSION_LOADED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, + NotificationService::AllSources()); + + // Listen for Google URL changes + registrar_.Add(this, NotificationType::GOOGLE_URL_UPDATED, + NotificationService::AllSources()); + + // Set-up the showing of the omnibox search infobar if applicable. + if (OmniboxSearchHint::IsEnabled(profile)) + omnibox_search_hint_.reset(new OmniboxSearchHint(this)); + + // Can only add observers after render_manager_.Init() is called, since that's + // what sets up the render_view_host which TabContentObserver's constructor + // uses to get the routing_id. + AddObservers(); +} + +TabContents::~TabContents() { + is_being_destroyed_ = true; + + // We don't want any notifications while we're running our destructor. + registrar_.RemoveAll(); + pref_change_registrar_.RemoveAll(); + + NotifyDisconnected(); + hung_renderer_dialog::HideForTabContents(this); + + // First cleanly close all child windows. + // TODO(mpcomplete): handle case if MaybeCloseChildWindows() already asked + // some of these to close. CloseWindows is async, so it might get called + // twice before it runs. + CloseConstrainedWindows(); + + // Close all blocked contents. + if (blocked_contents_) + blocked_contents_->Destroy(); + + // Notify any observer that have a reference on this tab contents. + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_DESTROYED, + Source<TabContents>(this), + NotificationService::NoDetails()); + + // Notify any lasting InfobarDelegates that have not yet been removed that + // whatever infobar they were handling in this TabContents has closed, + // because the TabContents is going away entirely. + // This must happen after the TAB_CONTENTS_DESTROYED notification as the + // notification may trigger infobars calls that access their delegate. (and + // some implementations of InfoBarDelegate do delete themselves on + // InfoBarClosed()). + for (size_t i = 0; i < infobar_count(); ++i) { + InfoBarDelegate* delegate = GetInfoBarDelegateAt(i); + delegate->InfoBarClosed(); + } + infobar_delegates_.clear(); + + // TODO(brettw) this should be moved to the view. +#if defined(OS_WIN) + // If we still have a window handle, destroy it. GetNativeView can return + // NULL if this contents was part of a window that closed. + if (GetNativeView()) { + RenderViewHost* host = render_view_host(); + if (host && host->view()) { + host->view()->WillWmDestroy(); + } + ::DestroyWindow(GetNativeView()); + } +#endif + + // OnCloseStarted isn't called in unit tests. + if (!tab_close_start_time_.is_null()) { + UMA_HISTOGRAM_TIMES("Tab.Close", + base::TimeTicks::Now() - tab_close_start_time_); + } + + FOR_EACH_OBSERVER(TabContentsObserver, observers_, set_tab_contents(NULL)); +} + +void TabContents::AddObservers() { + printing_.reset(new printing::PrintViewManager(this)); + print_preview_.reset(new printing::PrintPreviewMessageHandler(this)); + fav_icon_helper_.reset(new FavIconHelper(this)); + autofill_manager_.reset(new AutoFillManager(this)); + autocomplete_history_manager_.reset(new AutocompleteHistoryManager(this)); + prerender_plt_recorder_.reset(new prerender::PrerenderPLTRecorder(this)); + desktop_notification_handler_.reset( + new DesktopNotificationHandlerForTC(this, GetRenderProcessHost())); + plugin_observer_.reset(new PluginObserver(this)); + safebrowsing_detection_host_.reset(new safe_browsing::ClientSideDetectionHost( + this)); +} + +bool TabContents::OnMessageReceived(const IPC::Message& message) { + ObserverListBase<TabContentsObserver>::Iterator it(observers_); + TabContentsObserver* observer; + while ((observer = it.GetNext()) != NULL) + if (observer->OnMessageReceived(message)) + return true; + + bool handled = true; + bool message_is_ok = true; + IPC_BEGIN_MESSAGE_MAP_EX(TabContents, message, message_is_ok) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidStartProvisionalLoadForFrame, + OnDidStartProvisionalLoadForFrame) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidRedirectProvisionalLoad, + OnDidRedirectProvisionalLoad) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidFailProvisionalLoadWithError, + OnDidFailProvisionalLoadWithError) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidLoadResourceFromMemoryCache, + OnDidLoadResourceFromMemoryCache) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidDisplayInsecureContent, + OnDidDisplayInsecureContent) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidRunInsecureContent, + OnDidRunInsecureContent) + IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentLoadedInFrame, + OnDocumentLoadedInFrame) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidFinishLoad, OnDidFinishLoad) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateContentRestrictions, + OnUpdateContentRestrictions) + IPC_MESSAGE_HANDLER(ViewHostMsg_PDFHasUnsupportedFeature, + OnPDFHasUnsupportedFeature) + IPC_MESSAGE_HANDLER(ViewHostMsg_GoToEntryAtOffset, OnGoToEntryAtOffset) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidGetApplicationInfo, + OnDidGetApplicationInfo) + IPC_MESSAGE_HANDLER(ViewHostMsg_InstallApplication, + OnInstallApplication) + IPC_MESSAGE_HANDLER(ViewHostMsg_PageContents, OnPageContents) + IPC_MESSAGE_HANDLER(ViewHostMsg_PageTranslated, OnPageTranslated) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetSuggestions, OnSetSuggestions) + IPC_MESSAGE_HANDLER(ViewHostMsg_InstantSupportDetermined, + OnInstantSupportDetermined) + IPC_MESSAGE_HANDLER(ViewHostMsg_RunFileChooser, OnRunFileChooser) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + if (!message_is_ok) { + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_RVD")); + GetRenderProcessHost()->ReceivedBadMessage(); + } + + return handled; +} + +// Returns true if contains content rendered by an extension. +bool TabContents::HostsExtension() const { + return GetURL().SchemeIs(chrome::kExtensionScheme); +} + +TabContentsSSLHelper* TabContents::GetSSLHelper() { + if (ssl_helper_.get() == NULL) + ssl_helper_.reset(new TabContentsSSLHelper(this)); + return ssl_helper_.get(); +} + +RenderProcessHost* TabContents::GetRenderProcessHost() const { + return render_manager_.current_host()->process(); +} + +void TabContents::SetExtensionApp(const Extension* extension) { + DCHECK(!extension || extension->GetFullLaunchURL().is_valid()); + extension_app_ = extension; + + UpdateExtensionAppIcon(extension_app_); + + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, + Source<TabContents>(this), + NotificationService::NoDetails()); +} + +void TabContents::SetExtensionAppById(const std::string& extension_app_id) { + if (extension_app_id.empty()) + return; + + ExtensionService* extension_service = profile()->GetExtensionService(); + if (!extension_service || !extension_service->is_ready()) + return; + + const Extension* extension = + extension_service->GetExtensionById(extension_app_id, false); + if (extension) + SetExtensionApp(extension); +} + +SkBitmap* TabContents::GetExtensionAppIcon() { + if (extension_app_icon_.empty()) + return NULL; + + return &extension_app_icon_; +} + +const GURL& TabContents::GetURL() const { + // We may not have a navigation entry yet + NavigationEntry* entry = controller_.GetActiveEntry(); + return entry ? entry->virtual_url() : GURL::EmptyGURL(); +} + +const string16& TabContents::GetTitle() const { + // Transient entries take precedence. They are used for interstitial pages + // that are shown on top of existing pages. + NavigationEntry* entry = controller_.GetTransientEntry(); + if (entry) { + return entry->GetTitleForDisplay(profile()->GetPrefs()-> + GetString(prefs::kAcceptLanguages)); + } + WebUI* our_web_ui = render_manager_.pending_web_ui() ? + render_manager_.pending_web_ui() : render_manager_.web_ui(); + if (our_web_ui) { + // Don't override the title in view source mode. + entry = controller_.GetActiveEntry(); + if (!(entry && entry->IsViewSourceMode())) { + // Give the Web UI the chance to override our title. + const string16& title = our_web_ui->overridden_title(); + if (!title.empty()) + return title; + } + } + + // We use the title for the last committed entry rather than a pending + // navigation entry. For example, when the user types in a URL, we want to + // keep the old page's title until the new load has committed and we get a new + // title. + entry = controller_.GetLastCommittedEntry(); + if (entry) { + return entry->GetTitleForDisplay(profile()->GetPrefs()-> + GetString(prefs::kAcceptLanguages)); + } + return EmptyString16(); +} + +int32 TabContents::GetMaxPageID() { + if (GetSiteInstance()) + return GetSiteInstance()->max_page_id(); + else + return max_page_id_; +} + +void TabContents::UpdateMaxPageID(int32 page_id) { + // Ensure both the SiteInstance and RenderProcessHost update their max page + // IDs in sync. Only TabContents will also have site instances, except during + // testing. + if (GetSiteInstance()) + GetSiteInstance()->UpdateMaxPageID(page_id); + GetRenderProcessHost()->UpdateMaxPageID(page_id); +} + +SiteInstance* TabContents::GetSiteInstance() const { + return render_manager_.current_host()->site_instance(); +} + +bool TabContents::ShouldDisplayURL() { + // Don't hide the url in view source mode and with interstitials. + NavigationEntry* entry = controller_.GetActiveEntry(); + if (entry && (entry->IsViewSourceMode() || + entry->page_type() == INTERSTITIAL_PAGE)) { + return true; + } + + // We always display the URL for non-WebUI URLs to prevent spoofing. + if (entry && !WebUIFactory::HasWebUIScheme(entry->url())) + return true; + + WebUI* web_ui = GetWebUIForCurrentState(); + if (web_ui) + return !web_ui->should_hide_url(); + return true; +} + +SkBitmap TabContents::GetFavIcon() const { + // Like GetTitle(), we also want to use the favicon for the last committed + // entry rather than a pending navigation entry. + NavigationEntry* entry = controller_.GetTransientEntry(); + if (entry) + return entry->favicon().bitmap(); + + entry = controller_.GetLastCommittedEntry(); + if (entry) + return entry->favicon().bitmap(); + return SkBitmap(); +} + +bool TabContents::FavIconIsValid() const { + NavigationEntry* entry = controller_.GetTransientEntry(); + if (entry) + return entry->favicon().is_valid(); + + entry = controller_.GetLastCommittedEntry(); + if (entry) + return entry->favicon().is_valid(); + + return false; +} + +bool TabContents::ShouldDisplayFavIcon() { + // Always display a throbber during pending loads. + if (controller_.GetLastCommittedEntry() && controller_.pending_entry()) + return true; + + WebUI* web_ui = GetWebUIForCurrentState(); + if (web_ui) + return !web_ui->hide_favicon(); + return true; +} + +void TabContents::AddObserver(TabContentsObserver* observer) { + observers_.AddObserver(observer); +} + +void TabContents::RemoveObserver(TabContentsObserver* observer) { + observers_.RemoveObserver(observer); +} + +void TabContents::SetIsCrashed(base::TerminationStatus status, int error_code) { + if (status == crashed_status_) + return; + + crashed_status_ = status; + crashed_error_code_ = error_code; + NotifyNavigationStateChanged(INVALIDATE_TAB); +} + +void TabContents::PageActionStateChanged() { + NotifyNavigationStateChanged(TabContents::INVALIDATE_PAGE_ACTIONS); +} + +void TabContents::NotifyNavigationStateChanged(unsigned changed_flags) { + if (delegate_) + delegate_->NavigationStateChanged(this, changed_flags); +} + +void TabContents::DidBecomeSelected() { + controller_.SetActive(true); + RenderWidgetHostView* rwhv = GetRenderWidgetHostView(); + if (rwhv) { + rwhv->DidBecomeSelected(); +#if defined(OS_MACOSX) + rwhv->SetActive(true); +#endif + } + + WebCacheManager::GetInstance()->ObserveActivity(GetRenderProcessHost()->id()); + last_selected_time_ = base::TimeTicks::Now(); +#if defined(OS_CHROMEOS) + chromeos::LocaleChangeGuard::Check(this); +#endif +} + +void TabContents::FadeForInstant(bool animate) { + RenderWidgetHostView* rwhv = GetRenderWidgetHostView(); + SkColor whitish = SkColorSetARGB(192, 255, 255, 255); + if (rwhv) + rwhv->SetVisuallyDeemphasized(&whitish, animate); +} + +void TabContents::CancelInstantFade() { + RenderWidgetHostView* rwhv = GetRenderWidgetHostView(); + if (rwhv) + rwhv->SetVisuallyDeemphasized(NULL, false); +} + +void TabContents::WasHidden() { + if (!capturing_contents()) { + // |render_view_host()| can be NULL if the user middle clicks a link to open + // a tab in then background, then closes the tab before selecting it. This + // is because closing the tab calls TabContents::Destroy(), which removes + // the |render_view_host()|; then when we actually destroy the window, + // OnWindowPosChanged() notices and calls HideContents() (which calls us). + RenderWidgetHostView* rwhv = GetRenderWidgetHostView(); + if (rwhv) + rwhv->WasHidden(); + } + + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_HIDDEN, + Source<TabContents>(this), + NotificationService::NoDetails()); +} + +void TabContents::Activate() { + if (delegate_) + delegate_->ActivateContents(this); +} + +void TabContents::Deactivate() { + if (delegate_) + delegate_->DeactivateContents(this); +} + +void TabContents::ShowContents() { + RenderWidgetHostView* rwhv = GetRenderWidgetHostView(); + if (rwhv) + rwhv->DidBecomeSelected(); +} + +void TabContents::HideContents() { + // TODO(pkasting): http://b/1239839 Right now we purposefully don't call + // our superclass HideContents(), because some callers want to be very picky + // about the order in which these get called. In addition to making the code + // here practically impossible to understand, this also means we end up + // calling TabContents::WasHidden() twice if callers call both versions of + // HideContents() on a TabContents. + WasHidden(); +} + +bool TabContents::NeedToFireBeforeUnload() { + // TODO(creis): Should we fire even for interstitial pages? + return notify_disconnection() && + !showing_interstitial_page() && + !render_view_host()->SuddenTerminationAllowed(); +} + +void TabContents::OpenURL(const GURL& url, const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition) { + if (delegate_) + delegate_->OpenURLFromTab(this, url, referrer, disposition, transition); +} + +bool TabContents::NavigateToPendingEntry( + NavigationController::ReloadType reload_type) { + return NavigateToEntry(*controller_.pending_entry(), reload_type); +} + +bool TabContents::NavigateToEntry( + const NavigationEntry& entry, + NavigationController::ReloadType reload_type) { + RenderViewHost* dest_render_view_host = render_manager_.Navigate(entry); + if (!dest_render_view_host) + return false; // Unable to create the desired render view host. + + if (delegate_ && delegate_->ShouldEnablePreferredSizeNotifications()) { + dest_render_view_host->EnablePreferredSizeChangedMode( + kPreferredSizeWidth | kPreferredSizeHeightThisIsSlow); + } + + // For security, we should never send non-Web-UI URLs to a Web UI renderer. + // Double check that here. + int enabled_bindings = dest_render_view_host->enabled_bindings(); + bool is_allowed_in_web_ui_renderer = + WebUIFactory::IsURLAcceptableForWebUI(profile(), entry.url()); + CHECK(!BindingsPolicy::is_web_ui_enabled(enabled_bindings) || + is_allowed_in_web_ui_renderer); + + // Tell DevTools agent that it is attached prior to the navigation. + DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); + if (devtools_manager) { // NULL in unit tests. + devtools_manager->OnNavigatingToPendingEntry(render_view_host(), + dest_render_view_host, + entry.url()); + } + + // Used for page load time metrics. + current_load_start_ = base::TimeTicks::Now(); + + // Navigate in the desired RenderViewHost. + ViewMsg_Navigate_Params navigate_params; + MakeNavigateParams(entry, controller_, reload_type, &navigate_params); + if (delegate_) { + navigate_params.extra_headers = + delegate_->GetNavigationHeaders(navigate_params.url); + } + dest_render_view_host->Navigate(navigate_params); + + if (entry.page_id() == -1) { + // HACK!! This code suppresses javascript: URLs from being added to + // session history, which is what we want to do for javascript: URLs that + // do not generate content. What we really need is a message from the + // renderer telling us that a new page was not created. The same message + // could be used for mailto: URLs and the like. + if (entry.url().SchemeIs(chrome::kJavaScriptScheme)) + return false; + } + + // Notify observers about navigation. + FOR_EACH_OBSERVER(TabContentsObserver, observers_, NavigateToPendingEntry()); + + if (reload_type != NavigationController::NO_RELOAD && + !profile()->IsOffTheRecord()) { + FaviconService* favicon_service = + profile()->GetFaviconService(Profile::IMPLICIT_ACCESS); + if (favicon_service) + favicon_service->SetFaviconOutOfDateForPage(entry.url()); + } + + return true; +} + +void TabContents::Stop() { + render_manager_.Stop(); + printing_->Stop(); +} + +void TabContents::DisassociateFromPopupCount() { + render_view_host()->DisassociateFromPopupCount(); +} + +TabContents* TabContents::Clone() { + // We create a new SiteInstance so that the new tab won't share processes + // with the old one. This can be changed in the future if we need it to share + // processes for some reason. + TabContents* tc = new TabContents(profile(), + SiteInstance::CreateSiteInstance(profile()), + MSG_ROUTING_NONE, this, NULL); + tc->controller().CopyStateFrom(controller_); + tc->extension_app_ = extension_app_; + tc->extension_app_icon_ = extension_app_icon_; + return tc; +} + +void TabContents::ShowPageInfo(const GURL& url, + const NavigationEntry::SSLStatus& ssl, + bool show_history) { + if (!delegate_) + return; + + delegate_->ShowPageInfo(profile(), url, ssl, show_history); +} + +void TabContents::SaveFavicon() { + NavigationEntry* entry = controller_.GetActiveEntry(); + if (!entry || entry->url().is_empty()) + return; + + // Make sure the page is in history, otherwise adding the favicon does + // nothing. + HistoryService* history = profile()->GetOriginalProfile()->GetHistoryService( + Profile::IMPLICIT_ACCESS); + if (!history) + return; + history->AddPageNoVisitForBookmark(entry->url()); + + FaviconService* service = profile()->GetOriginalProfile()->GetFaviconService( + Profile::IMPLICIT_ACCESS); + if (!service) + return; + const NavigationEntry::FaviconStatus& favicon(entry->favicon()); + if (!favicon.is_valid() || favicon.url().is_empty() || + favicon.bitmap().empty()) { + return; + } + std::vector<unsigned char> image_data; + gfx::PNGCodec::EncodeBGRASkBitmap(favicon.bitmap(), false, &image_data); + service->SetFavicon(entry->url(), favicon.url(), image_data); +} + +ConstrainedWindow* TabContents::CreateConstrainedDialog( + ConstrainedWindowDelegate* delegate) { + ConstrainedWindow* window = + ConstrainedWindow::CreateConstrainedDialog(this, delegate); + child_windows_.push_back(window); + + if (child_windows_.size() == 1) { + window->ShowConstrainedWindow(); + BlockTabContent(true); + } + + return window; +} + +void TabContents::BlockTabContent(bool blocked) { + RenderWidgetHostView* rwhv = GetRenderWidgetHostView(); + // 70% opaque grey. + SkColor greyish = SkColorSetARGB(178, 0, 0, 0); + if (rwhv) + rwhv->SetVisuallyDeemphasized(blocked ? &greyish : NULL, false); + render_view_host()->set_ignore_input_events(blocked); + if (delegate_) + delegate_->SetTabContentBlocked(this, blocked); +} + +void TabContents::AddNewContents(TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) { + if (all_contents_blocked_) { + if (!blocked_contents_) + blocked_contents_ = new BlockedContentContainer(this); + blocked_contents_->AddTabContents( + new_contents, disposition, initial_pos, user_gesture); + return; + } + + if (!delegate_) + return; + + if ((disposition == NEW_POPUP) && !user_gesture && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisablePopupBlocking)) { + // Unrequested popups from normal pages are constrained unless they're in + // the whitelist. The popup owner will handle checking this. + delegate_->GetConstrainingContents(this)->AddPopup( + new_contents, initial_pos); + } else { + new_contents->DisassociateFromPopupCount(); + delegate_->AddNewContents(this, new_contents, disposition, initial_pos, + user_gesture); + NotificationService::current()->Notify( + NotificationType::TAB_ADDED, + Source<TabContentsDelegate>(delegate_), + Details<TabContents>(this)); + } + + // TODO(pkasting): Why is this necessary? + PopupNotificationVisibilityChanged(blocked_contents_ != NULL); +} + +bool TabContents::ExecuteCode(int request_id, const std::string& extension_id, + bool is_js_code, const std::string& code_string, + bool all_frames) { + RenderViewHost* host = render_view_host(); + if (!host) + return false; + + return host->Send(new ViewMsg_ExecuteCode(host->routing_id(), + ViewMsg_ExecuteCode_Params(request_id, extension_id, + is_js_code, code_string, all_frames))); +} + +void TabContents::PopupNotificationVisibilityChanged(bool visible) { + if (is_being_destroyed_) + return; + content_settings_delegate_->SetPopupsBlocked(visible); + if (!dont_notify_render_view_) + render_view_host()->AllowScriptToClose(!visible); +} + +gfx::NativeView TabContents::GetContentNativeView() const { + return view_->GetContentNativeView(); +} + +gfx::NativeView TabContents::GetNativeView() const { + return view_->GetNativeView(); +} + +void TabContents::GetContainerBounds(gfx::Rect *out) const { + view_->GetContainerBounds(out); +} + +void TabContents::Focus() { + view_->Focus(); +} + +void TabContents::FocusThroughTabTraversal(bool reverse) { + if (showing_interstitial_page()) { + render_manager_.interstitial_page()->FocusThroughTabTraversal(reverse); + return; + } + render_view_host()->SetInitialFocus(reverse); +} + +bool TabContents::FocusLocationBarByDefault() { + WebUI* web_ui = GetWebUIForCurrentState(); + if (web_ui) + return web_ui->focus_location_bar_by_default(); + NavigationEntry* entry = controller_.GetActiveEntry(); + if (entry && entry->url() == GURL(chrome::kAboutBlankURL)) + return true; + return false; +} + +void TabContents::SetFocusToLocationBar(bool select_all) { + if (delegate()) + delegate()->SetFocusToLocationBar(select_all); +} + +void TabContents::AddInfoBar(InfoBarDelegate* delegate) { + if (delegate_ && !delegate_->infobars_enabled()) { + delegate->InfoBarClosed(); + return; + } + + // Look through the existing InfoBarDelegates we have for a match. If we've + // already got one that matches, then we don't add the new one. + for (size_t i = 0; i < infobar_count(); ++i) { + if (GetInfoBarDelegateAt(i)->EqualsDelegate(delegate)) { + // Tell the new infobar to close so that it can clean itself up. + delegate->InfoBarClosed(); + return; + } + } + + infobar_delegates_.push_back(delegate); + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_INFOBAR_ADDED, Source<TabContents>(this), + Details<InfoBarDelegate>(delegate)); + + // Add ourselves as an observer for navigations the first time a delegate is + // added. We use this notification to expire InfoBars that need to expire on + // page transitions. + if (infobar_delegates_.size() == 1) { + registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, + Source<NavigationController>(&controller_)); + } +} + +void TabContents::RemoveInfoBar(InfoBarDelegate* delegate) { + if (delegate_ && !delegate_->infobars_enabled()) { + return; + } + + std::vector<InfoBarDelegate*>::iterator it = + find(infobar_delegates_.begin(), infobar_delegates_.end(), delegate); + if (it != infobar_delegates_.end()) { + InfoBarDelegate* delegate = *it; + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_INFOBAR_REMOVED, + Source<TabContents>(this), + Details<InfoBarDelegate>(delegate)); + + infobar_delegates_.erase(it); + // Remove ourselves as an observer if we are tracking no more InfoBars. + if (infobar_delegates_.empty()) { + registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED, + Source<NavigationController>(&controller_)); + } + } +} + +void TabContents::ReplaceInfoBar(InfoBarDelegate* old_delegate, + InfoBarDelegate* new_delegate) { + if (delegate_ && !delegate_->infobars_enabled()) { + new_delegate->InfoBarClosed(); + return; + } + + std::vector<InfoBarDelegate*>::iterator it = + find(infobar_delegates_.begin(), infobar_delegates_.end(), old_delegate); + DCHECK(it != infobar_delegates_.end()); + + // Notify the container about the change of plans. + scoped_ptr<std::pair<InfoBarDelegate*, InfoBarDelegate*> > details( + new std::pair<InfoBarDelegate*, InfoBarDelegate*>( + old_delegate, new_delegate)); + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_INFOBAR_REPLACED, + Source<TabContents>(this), + Details<std::pair<InfoBarDelegate*, InfoBarDelegate*> >(details.get())); + + // Remove the old one. + infobar_delegates_.erase(it); + + // Add the new one. + DCHECK(find(infobar_delegates_.begin(), + infobar_delegates_.end(), new_delegate) == + infobar_delegates_.end()); + infobar_delegates_.push_back(new_delegate); +} + +bool TabContents::ShouldShowBookmarkBar() { + if (showing_interstitial_page()) + return false; + + // Do not show bookmarks bar if bookmarks aren't enabled. + if (!browser_defaults::bookmarks_enabled) + return false; + + // See GetWebUIForCurrentState() comment for more info. This case is very + // similar, but for non-first loads, we want to use the committed entry. This + // is so the bookmarks bar disappears at the same time the page does. + if (controller_.GetLastCommittedEntry()) { + // Not the first load, always use the committed Web UI. + return (render_manager_.web_ui() == NULL) ? + false : render_manager_.web_ui()->force_bookmark_bar_visible(); + } + + // When it's the first load, we know either the pending one or the committed + // one will have the Web UI in it (see GetWebUIForCurrentState), and only one + // of them will be valid, so we can just check both. + if (render_manager_.pending_web_ui()) + return render_manager_.pending_web_ui()->force_bookmark_bar_visible(); + return (render_manager_.web_ui() == NULL) ? + false : render_manager_.web_ui()->force_bookmark_bar_visible(); +} + +void TabContents::ToolbarSizeChanged(bool is_animating) { + TabContentsDelegate* d = delegate(); + if (d) + d->ToolbarSizeChanged(this, is_animating); +} + +bool TabContents::CanDownload(int request_id) { + TabContentsDelegate* d = delegate(); + if (d) + return d->CanDownload(request_id); + return true; +} + +void TabContents::OnStartDownload(DownloadItem* download) { + DCHECK(download); + + // Download in a constrained popup is shown in the tab that opened it. + TabContents* tab_contents = delegate()->GetConstrainingContents(this); + + if (tab_contents && tab_contents->delegate()) + tab_contents->delegate()->OnStartDownload(download, this); +} + +void TabContents::WillClose(ConstrainedWindow* window) { + ConstrainedWindowList::iterator i( + std::find(child_windows_.begin(), child_windows_.end(), window)); + bool removed_topmost_window = i == child_windows_.begin(); + if (i != child_windows_.end()) + child_windows_.erase(i); + if (child_windows_.empty()) { + BlockTabContent(false); + } else { + if (removed_topmost_window) + child_windows_[0]->ShowConstrainedWindow(); + BlockTabContent(true); + } +} + +void TabContents::WillCloseBlockedContentContainer( + BlockedContentContainer* container) { + DCHECK(blocked_contents_ == container); + blocked_contents_ = NULL; + PopupNotificationVisibilityChanged(false); +} + +void TabContents::DidMoveOrResize(ConstrainedWindow* window) { +#if defined(OS_WIN) + UpdateWindow(GetNativeView()); +#endif +} + +void TabContents::OnSavePage() { + // If we can not save the page, try to download it. + if (!SavePackage::IsSavableContents(contents_mime_type())) { + DownloadManager* dlm = profile()->GetDownloadManager(); + const GURL& current_page_url = GetURL(); + if (dlm && current_page_url.is_valid()) + dlm->DownloadUrl(current_page_url, GURL(), "", this); + return; + } + + Stop(); + + // Create the save package and possibly prompt the user for the name to save + // the page as. The user prompt is an asynchronous operation that runs on + // another thread. + save_package_ = new SavePackage(this); + save_package_->GetSaveInfo(); +} + +// Used in automated testing to bypass prompting the user for file names. +// Instead, the names and paths are hard coded rather than running them through +// file name sanitation and extension / mime checking. +bool TabContents::SavePage(const FilePath& main_file, const FilePath& dir_path, + SavePackage::SavePackageType save_type) { + // Stop the page from navigating. + Stop(); + + save_package_ = new SavePackage(this, save_type, main_file, dir_path); + return save_package_->Init(); +} + +void TabContents::EmailPageLocation() { + std::string title = EscapeQueryParamValue(UTF16ToUTF8(GetTitle()), false); + std::string page_url = EscapeQueryParamValue(GetURL().spec(), false); + std::string mailto = std::string("mailto:?subject=Fwd:%20") + + title + "&body=%0A%0A" + page_url; + platform_util::OpenExternal(GURL(mailto)); +} + +void TabContents::PrintPreview() { + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnablePrintPreview)) { + if (showing_interstitial_page()) + return; + + printing::PrintPreviewTabController* tab_controller = + printing::PrintPreviewTabController::GetInstance(); + if (!tab_controller) + return; + tab_controller->GetOrCreatePreviewTab(this, controller().window_id().id()); + + render_view_host()->PrintPreview(); + } else { + PrintNow(); + } +} + +bool TabContents::PrintNow() { + // We can't print interstitial page for now. + if (showing_interstitial_page()) + return false; + + return render_view_host()->PrintPages(); +} + +void TabContents::PrintingDone(int document_cookie, bool success) { + render_view_host()->PrintingDone(document_cookie, success); +} + +bool TabContents::IsActiveEntry(int32 page_id) { + NavigationEntry* active_entry = controller_.GetActiveEntry(); + return (active_entry != NULL && + active_entry->site_instance() == GetSiteInstance() && + active_entry->page_id() == page_id); +} + +void TabContents::SetOverrideEncoding(const std::string& encoding) { + set_encoding(encoding); + render_view_host()->SetPageEncoding(encoding); +} + +void TabContents::ResetOverrideEncoding() { + reset_encoding(); + render_view_host()->ResetPageEncodingToDefault(); +} + +void TabContents::WindowMoveOrResizeStarted() { + render_view_host()->WindowMoveOrResizeStarted(); +} + +void TabContents::SetAllContentsBlocked(bool value) { + if (all_contents_blocked_ == value) + return; + + all_contents_blocked_ = value; + if (!all_contents_blocked_ && blocked_contents_) { + std::vector<TabContents*> blocked; + blocked_contents_->GetBlockedContents(&blocked); + for (size_t i = 0; i < blocked.size(); ++i) + blocked_contents_->LaunchForContents(blocked[i]); + } +} + +void TabContents::LogNewTabTime(const std::string& event_name) { + // Not all new tab pages get timed. In those cases, we don't have a + // new_tab_start_time_. + if (new_tab_start_time_.is_null()) + return; + + base::TimeDelta duration = base::TimeTicks::Now() - new_tab_start_time_; + MetricEventDurationDetails details(event_name, + static_cast<int>(duration.InMilliseconds())); + + if (event_name == "Tab.NewTabScriptStart") { + UMA_HISTOGRAM_TIMES("Tab.NewTabScriptStart", duration); + } else if (event_name == "Tab.NewTabDOMContentLoaded") { + UMA_HISTOGRAM_TIMES("Tab.NewTabDOMContentLoaded", duration); + } else if (event_name == "Tab.NewTabOnload") { + UMA_HISTOGRAM_TIMES("Tab.NewTabOnload", duration); + // The new tab page has finished loading; reset it. + new_tab_start_time_ = base::TimeTicks(); + } else { + NOTREACHED(); + } + NotificationService::current()->Notify( + NotificationType::METRIC_EVENT_DURATION, + Source<TabContents>(this), + Details<MetricEventDurationDetails>(&details)); +} + +void TabContents::OnCloseStarted() { + if (tab_close_start_time_.is_null()) + tab_close_start_time_ = base::TimeTicks::Now(); +} + +bool TabContents::ShouldAcceptDragAndDrop() const { +#if defined(OS_CHROMEOS) + // ChromeOS panels (pop-ups) do not take drag-n-drop. + // See http://crosbug.com/2413 + if (delegate() && delegate()->IsPopup(this)) + return false; + return true; +#else + return true; +#endif +} + +void TabContents::SystemDragEnded() { + if (render_view_host()) + render_view_host()->DragSourceSystemDragEnded(); + if (delegate()) + delegate()->DragEnded(); +} + +void TabContents::UpdateHistoryForNavigation( + scoped_refptr<history::HistoryAddPageArgs> add_page_args) { + if (profile()->IsOffTheRecord()) + return; + + // Add to history service. + HistoryService* hs = profile()->GetHistoryService(Profile::IMPLICIT_ACCESS); + if (hs) + hs->AddPage(*add_page_args); +} + +void TabContents::UpdateHistoryPageTitle(const NavigationEntry& entry) { + if (profile()->IsOffTheRecord()) + return; + + HistoryService* hs = profile()->GetHistoryService(Profile::IMPLICIT_ACCESS); + if (hs) + hs->SetPageTitle(entry.virtual_url(), entry.title()); +} + +double TabContents::GetZoomLevel() const { + HostZoomMap* zoom_map = profile()->GetHostZoomMap(); + if (!zoom_map) + return 0; + + double zoom_level; + if (temporary_zoom_settings_) { + zoom_level = zoom_map->GetTemporaryZoomLevel( + GetRenderProcessHost()->id(), render_view_host()->routing_id()); + } else { + zoom_level = zoom_map->GetZoomLevel(GetURL()); + } + return zoom_level; +} + +int TabContents::GetZoomPercent(bool* enable_increment, + bool* enable_decrement) { + *enable_decrement = *enable_increment = false; + int percent = static_cast<int>( + WebKit::WebView::zoomLevelToZoomFactor(GetZoomLevel()) * 100); + *enable_decrement = percent > minimum_zoom_percent_; + *enable_increment = percent < maximum_zoom_percent_; + return percent; +} + +void TabContents::ViewSource() { + if (!delegate_) + return; + + NavigationEntry* active_entry = controller().GetActiveEntry(); + if (!active_entry) + return; + + delegate_->ViewSourceForTab(this, active_entry->url()); +} + +void TabContents::OnDidStartProvisionalLoadForFrame(int64 frame_id, + bool is_main_frame, + const GURL& url) { + bool is_error_page = (url.spec() == chrome::kUnreachableWebDataURL); + GURL validated_url(url); + render_view_host()->FilterURL(ChildProcessSecurityPolicy::GetInstance(), + GetRenderProcessHost()->id(), &validated_url); + + ProvisionalLoadDetails details( + is_main_frame, + controller_.IsURLInPageNavigation(validated_url), + validated_url, std::string(), false, is_error_page, frame_id); + NotificationService::current()->Notify( + NotificationType::FRAME_PROVISIONAL_LOAD_START, + Source<NavigationController>(&controller_), + Details<ProvisionalLoadDetails>(&details)); + if (is_main_frame) { + // If we're displaying a network error page do not reset the content + // settings delegate's cookies so the user has a chance to modify cookie + // settings. + if (!is_error_page) + content_settings_delegate_->ClearCookieSpecificContentSettings(); + content_settings_delegate_->ClearGeolocationContentSettings(); + + // Check if the URL we are about to load has been prerendered by any chance, + // and use it if possible. + MaybeUsePreloadedPage(url); + } +} + +void TabContents::OnDidRedirectProvisionalLoad(int32 page_id, + const GURL& source_url, + const GURL& target_url) { + NavigationEntry* entry; + if (page_id == -1) + entry = controller_.pending_entry(); + else + entry = controller_.GetEntryWithPageID(GetSiteInstance(), page_id); + if (!entry || entry->url() != source_url) + return; + entry->set_url(target_url); + + // Check if the URL we are about to load has been prerendered by any chance, + // and use it if possible. + MaybeUsePreloadedPage(target_url); +} + +void TabContents::OnDidFailProvisionalLoadWithError( + int64 frame_id, + bool is_main_frame, + int error_code, + const GURL& url, + bool showing_repost_interstitial) { + VLOG(1) << "Failed Provisional Load: " << url.possibly_invalid_spec() + << ", error_code: " << error_code + << " is_main_frame: " << is_main_frame + << " showing_repost_interstitial: " << showing_repost_interstitial + << " frame_id: " << frame_id; + GURL validated_url(url); + render_view_host()->FilterURL(ChildProcessSecurityPolicy::GetInstance(), + GetRenderProcessHost()->id(), &validated_url); + + if (net::ERR_ABORTED == error_code) { + // EVIL HACK ALERT! Ignore failed loads when we're showing interstitials. + // This means that the interstitial won't be torn down properly, which is + // bad. But if we have an interstitial, go back to another tab type, and + // then load the same interstitial again, we could end up getting the first + // interstitial's "failed" message (as a result of the cancel) when we're on + // the second one. + // + // We can't tell this apart, so we think we're tearing down the current page + // which will cause a crash later one. There is also some code in + // RenderViewHostManager::RendererAbortedProvisionalLoad that is commented + // out because of this problem. + // + // http://code.google.com/p/chromium/issues/detail?id=2855 + // Because this will not tear down the interstitial properly, if "back" is + // back to another tab type, the interstitial will still be somewhat alive + // in the previous tab type. If you navigate somewhere that activates the + // tab with the interstitial again, you'll see a flash before the new load + // commits of the interstitial page. + if (showing_interstitial_page()) { + LOG(WARNING) << "Discarding message during interstitial."; + return; + } + + // This will discard our pending entry if we cancelled the load (e.g., if we + // decided to download the file instead of load it). Only discard the + // pending entry if the URLs match, otherwise the user initiated a navigate + // before the page loaded so that the discard would discard the wrong entry. + NavigationEntry* pending_entry = controller_.pending_entry(); + if (pending_entry && pending_entry->url() == validated_url) { + controller_.DiscardNonCommittedEntries(); + // Update the URL display. + NotifyNavigationStateChanged(TabContents::INVALIDATE_URL); + } + + render_manager_.RendererAbortedProvisionalLoad(render_view_host()); + } + + // Send out a notification that we failed a provisional load with an error. + ProvisionalLoadDetails details( + is_main_frame, controller_.IsURLInPageNavigation(validated_url), + validated_url, std::string(), false, false, frame_id); + details.set_error_code(error_code); + + NotificationService::current()->Notify( + NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR, + Source<NavigationController>(&controller_), + Details<ProvisionalLoadDetails>(&details)); +} + +void TabContents::OnDidLoadResourceFromMemoryCache( + const GURL& url, + const std::string& security_info) { + static base::StatsCounter cache("WebKit.CacheHit"); + cache.Increment(); + + // Send out a notification that we loaded a resource from our memory cache. + int cert_id = 0, cert_status = 0, security_bits = -1, connection_status = 0; + SSLManager::DeserializeSecurityInfo(security_info, + &cert_id, &cert_status, + &security_bits, + &connection_status); + LoadFromMemoryCacheDetails details(url, GetRenderProcessHost()->id(), + cert_id, cert_status); + + NotificationService::current()->Notify( + NotificationType::LOAD_FROM_MEMORY_CACHE, + Source<NavigationController>(&controller_), + Details<LoadFromMemoryCacheDetails>(&details)); +} + +void TabContents::OnDidDisplayInsecureContent() { + displayed_insecure_content_ = true; + SSLManager::NotifySSLInternalStateChanged(); +} + +void TabContents::OnDidRunInsecureContent( + const std::string& security_origin, const GURL& target_url) { + LOG(INFO) << security_origin << " ran insecure content from " + << target_url.possibly_invalid_spec(); + controller_.ssl_manager()->DidRunInsecureContent(security_origin); +} + +void TabContents::OnDocumentLoadedInFrame(int64 frame_id) { + controller_.DocumentLoadedInFrame(); + NotificationService::current()->Notify( + NotificationType::FRAME_DOM_CONTENT_LOADED, + Source<NavigationController>(&controller_), + Details<int64>(&frame_id)); +} + +void TabContents::OnDidFinishLoad(int64 frame_id) { + NotificationService::current()->Notify( + NotificationType::FRAME_DID_FINISH_LOAD, + Source<NavigationController>(&controller_), + Details<int64>(&frame_id)); +} + +void TabContents::OnUpdateContentRestrictions(int restrictions) { + content_restrictions_ = restrictions; + delegate()->ContentRestrictionsChanged(this); +} + +void TabContents::OnPDFHasUnsupportedFeature() { + PDFHasUnsupportedFeature(this); +} + +// Notifies the RenderWidgetHost instance about the fact that the page is +// loading, or done loading and calls the base implementation. +void TabContents::SetIsLoading(bool is_loading, + LoadNotificationDetails* details) { + if (is_loading == is_loading_) + return; + + if (!is_loading) { + load_state_ = net::LOAD_STATE_IDLE; + load_state_host_.clear(); + upload_size_ = 0; + upload_position_ = 0; + } + + render_manager_.SetIsLoading(is_loading); + + is_loading_ = is_loading; + waiting_for_response_ = is_loading; + + if (delegate_) + delegate_->LoadingStateChanged(this); + NotifyNavigationStateChanged(INVALIDATE_LOAD); + + NotificationType type = is_loading ? NotificationType::LOAD_START : + NotificationType::LOAD_STOP; + NotificationDetails det = NotificationService::NoDetails(); + if (details) + det = Details<LoadNotificationDetails>(details); + NotificationService::current()->Notify(type, + Source<NavigationController>(&controller_), + det); +} + +void TabContents::AddPopup(TabContents* new_contents, + const gfx::Rect& initial_pos) { + // A page can't spawn popups (or do anything else, either) until its load + // commits, so when we reach here, the popup was spawned by the + // NavigationController's last committed entry, not the active entry. For + // example, if a page opens a popup in an onunload() handler, then the active + // entry is the page to be loaded as we navigate away from the unloading + // page. For this reason, we can't use GetURL() to get the opener URL, + // because it returns the active entry. + NavigationEntry* entry = controller_.GetLastCommittedEntry(); + GURL creator = entry ? entry->virtual_url() : GURL::EmptyGURL(); + + if (creator.is_valid() && + profile()->GetHostContentSettingsMap()->GetContentSetting( + creator, CONTENT_SETTINGS_TYPE_POPUPS, "") == CONTENT_SETTING_ALLOW) { + AddNewContents(new_contents, NEW_POPUP, initial_pos, true); + } else { + if (!blocked_contents_) + blocked_contents_ = new BlockedContentContainer(this); + blocked_contents_->AddTabContents(new_contents, NEW_POPUP, initial_pos, + true); + content_settings_delegate_->OnContentBlocked(CONTENT_SETTINGS_TYPE_POPUPS, + std::string()); + } +} + +void TabContents::ExpireInfoBars( + const NavigationController::LoadCommittedDetails& details) { + // Only hide InfoBars when the user has done something that makes the main + // frame load. We don't want various automatic or subframe navigations making + // it disappear. + if (!details.is_user_initiated_main_frame_load()) + return; + + // NOTE: It is not safe to change the following code to count upwards or use + // iterators, as the RemoveInfoBar() call synchronously modifies our delegate + // list. + for (size_t i = infobar_count(); i > 0; --i) { + InfoBarDelegate* delegate = GetInfoBarDelegateAt(i - 1); + if (delegate->ShouldExpire(details)) + RemoveInfoBar(delegate); + } +} + +WebUI* TabContents::GetWebUIForCurrentState() { + // When there is a pending navigation entry, we want to use the pending WebUI + // that goes along with it to control the basic flags. For example, we want to + // show the pending URL in the URL bar, so we want the display_url flag to + // be from the pending entry. + // + // The confusion comes because there are multiple possibilities for the + // initial load in a tab as a side effect of the way the RenderViewHostManager + // works. + // + // - For the very first tab the load looks "normal". The new tab Web UI is + // the pending one, and we want it to apply here. + // + // - For subsequent new tabs, they'll get a new SiteInstance which will then + // get switched to the one previously associated with the new tab pages. + // This switching will cause the manager to commit the RVH/WebUI. So we'll + // have a committed Web UI in this case. + // + // This condition handles all of these cases: + // + // - First load in first tab: no committed nav entry + pending nav entry + + // pending dom ui: + // -> Use pending Web UI if any. + // + // - First load in second tab: no committed nav entry + pending nav entry + + // no pending Web UI: + // -> Use the committed Web UI if any. + // + // - Second navigation in any tab: committed nav entry + pending nav entry: + // -> Use pending Web UI if any. + // + // - Normal state with no load: committed nav entry + no pending nav entry: + // -> Use committed Web UI. + if (controller_.pending_entry() && + (controller_.GetLastCommittedEntry() || + render_manager_.pending_web_ui())) + return render_manager_.pending_web_ui(); + return render_manager_.web_ui(); +} + +void TabContents::DidNavigateMainFramePostCommit( + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { + if (opener_web_ui_type_ != WebUIFactory::kNoWebUI) { + // If this is a window.open navigation, use the same WebUI as the renderer + // that opened the window, as long as both renderers have the same + // privileges. + if (opener_web_ui_type_ == + WebUIFactory::GetWebUIType(profile(), GetURL())) { + WebUI* web_ui = WebUIFactory::CreateWebUIForURL(this, GetURL()); + // web_ui might be NULL if the URL refers to a non-existent extension. + if (web_ui) { + render_manager_.SetWebUIPostCommit(web_ui); + web_ui->RenderViewCreated(render_view_host()); + } + } + opener_web_ui_type_ = WebUIFactory::kNoWebUI; + } + + if (details.is_user_initiated_main_frame_load()) { + // Clear the status bubble. This is a workaround for a bug where WebKit + // doesn't let us know that the cursor left an element during a + // transition (this is also why the mouse cursor remains as a hand after + // clicking on a link); see bugs 1184641 and 980803. We don't want to + // clear the bubble when a user navigates to a named anchor in the same + // page. + UpdateTargetURL(details.entry->page_id(), GURL()); + } + + // Allow the new page to set the title again. + received_page_title_ = false; + + // Get the favicon, either from history or request it from the net. + fav_icon_helper_->FetchFavIcon(details.entry->url()); + + // Clear all page actions, blocked content notifications and browser actions + // for this tab, unless this is an in-page navigation. + if (!details.is_in_page) { + ExtensionService* service = profile()->GetExtensionService(); + if (service) { + for (size_t i = 0; i < service->extensions()->size(); ++i) { + ExtensionAction* browser_action = + service->extensions()->at(i)->browser_action(); + if (browser_action) { + browser_action->ClearAllValuesForTab(controller().session_id().id()); + NotificationService::current()->Notify( + NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, + Source<ExtensionAction>(browser_action), + NotificationService::NoDetails()); + } + + ExtensionAction* page_action = + service->extensions()->at(i)->page_action(); + if (page_action) { + page_action->ClearAllValuesForTab(controller().session_id().id()); + PageActionStateChanged(); + } + } + } + + // Close blocked popups. + if (blocked_contents_) { + AutoReset<bool> auto_reset(&dont_notify_render_view_, true); + blocked_contents_->Destroy(); + blocked_contents_ = NULL; + } + + // Clear "blocked" flags. + content_settings_delegate_->ClearBlockedContentSettingsExceptForCookies(); + content_settings_delegate_->GeolocationDidNavigate(details); + + // Once the main frame is navigated, we're no longer considered to have + // displayed insecure content. + displayed_insecure_content_ = false; + } + + // Close constrained windows if necessary. + if (!net::RegistryControlledDomainService::SameDomainOrHost( + details.previous_url, details.entry->url())) + CloseConstrainedWindows(); + + // Notify observers about navigation. + FOR_EACH_OBSERVER(TabContentsObserver, observers_, + DidNavigateMainFramePostCommit(details, params)); +} + +void TabContents::DidNavigateAnyFramePostCommit( + RenderViewHost* render_view_host, + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { + // If we navigate, start showing messages again. This does nothing to prevent + // a malicious script from spamming messages, since the script could just + // reload the page to stop blocking. + suppress_javascript_messages_ = false; + + // Notify observers about navigation. + FOR_EACH_OBSERVER(TabContentsObserver, observers_, + DidNavigateAnyFramePostCommit(details, params)); + + // Let the LanguageState clear its state. + language_state_.DidNavigate(details); +} + +void TabContents::CloseConstrainedWindows() { + // Clear out any constrained windows since we are leaving this page entirely. + // We use indices instead of iterators in case CloseWindow does something + // that may invalidate an iterator. + for (size_t i = 0; i < child_windows_.size(); ++i) { + ConstrainedWindow* window = child_windows_[child_windows_.size() - 1 - i]; + if (window) { + window->CloseConstrainedWindow(); + BlockTabContent(false); + } + } +} + +void TabContents::UpdateAlternateErrorPageURL() { + GURL url = GetAlternateErrorPageURL(); + render_view_host()->SetAlternateErrorPageURL(url); +} + +void TabContents::UpdateWebPreferences() { + render_view_host()->UpdateWebPreferences(GetWebkitPrefs()); +} + +void TabContents::UpdateZoomLevel() { + render_view_host()->SetZoomLevel(GetZoomLevel()); +} + +void TabContents::UpdateMaxPageIDIfNecessary(SiteInstance* site_instance, + RenderViewHost* rvh) { + // If we are creating a RVH for a restored controller, then we might + // have more page IDs than the SiteInstance's current max page ID. We must + // make sure that the max page ID is larger than any restored page ID. + // Note that it is ok for conflicting page IDs to exist in another tab + // (i.e., NavigationController), but if any page ID is larger than the max, + // the back/forward list will get confused. + int max_restored_page_id = controller_.max_restored_page_id(); + if (max_restored_page_id > 0) { + int curr_max_page_id = site_instance->max_page_id(); + if (max_restored_page_id > curr_max_page_id) { + // Need to update the site instance immediately. + site_instance->UpdateMaxPageID(max_restored_page_id); + + // Also tell the renderer to update its internal representation. We + // need to reserve enough IDs to make all restored page IDs less than + // the max. + if (curr_max_page_id < 0) + curr_max_page_id = 0; + rvh->ReservePageIDRange(max_restored_page_id - curr_max_page_id); + } + } +} + +scoped_refptr<history::HistoryAddPageArgs> +TabContents::CreateHistoryAddPageArgs( + const GURL& virtual_url, + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { + scoped_refptr<history::HistoryAddPageArgs> add_page_args( + new history::HistoryAddPageArgs( + params.url, base::Time::Now(), this, params.page_id, params.referrer, + params.redirects, params.transition, history::SOURCE_BROWSED, + details.did_replace_entry)); + if (PageTransition::IsMainFrame(params.transition) && + virtual_url != params.url) { + // Hack on the "virtual" URL so that it will appear in history. For some + // types of URLs, we will display a magic URL that is different from where + // the page is actually navigated. We want the user to see in history what + // they saw in the URL bar, so we add the virtual URL as a redirect. This + // only applies to the main frame, as the virtual URL doesn't apply to + // sub-frames. + add_page_args->url = virtual_url; + if (!add_page_args->redirects.empty()) + add_page_args->redirects.back() = virtual_url; + } + return add_page_args; +} + +bool TabContents::UpdateTitleForEntry(NavigationEntry* entry, + const std::wstring& title) { + // For file URLs without a title, use the pathname instead. In the case of a + // synthesized title, we don't want the update to count toward the "one set + // per page of the title to history." + string16 final_title; + bool explicit_set; + if (entry->url().SchemeIsFile() && title.empty()) { + final_title = UTF8ToUTF16(entry->url().ExtractFileName()); + explicit_set = false; // Don't count synthetic titles toward the set limit. + } else { + TrimWhitespace(WideToUTF16Hack(title), TRIM_ALL, &final_title); + explicit_set = true; + } + + if (final_title == entry->title()) + return false; // Nothing changed, don't bother. + + entry->set_title(final_title); + + if (!received_page_title_) { + UpdateHistoryPageTitle(*entry); + received_page_title_ = explicit_set; + } + + // Lastly, set the title for the view. + view_->SetPageTitle(UTF16ToWideHack(final_title)); + + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_TITLE_UPDATED, + Source<TabContents>(this), + NotificationService::NoDetails()); + + return true; +} + +void TabContents::NotifySwapped() { + // After sending out a swap notification, we need to send a disconnect + // notification so that clients that pick up a pointer to |this| can NULL the + // pointer. See Bug 1230284. + notify_disconnection_ = true; + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_SWAPPED, + Source<TabContents>(this), + NotificationService::NoDetails()); +} + +void TabContents::NotifyConnected() { + notify_disconnection_ = true; + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_CONNECTED, + Source<TabContents>(this), + NotificationService::NoDetails()); +} + +void TabContents::NotifyDisconnected() { + if (!notify_disconnection_) + return; + + notify_disconnection_ = false; + NotificationService::current()->Notify( + NotificationType::TAB_CONTENTS_DISCONNECTED, + Source<TabContents>(this), + NotificationService::NoDetails()); +} + +void TabContents::OnGoToEntryAtOffset(int offset) { + if (!delegate_ || delegate_->OnGoToEntryOffset(offset)) { + NavigationEntry* entry = controller_.GetEntryAtOffset(offset); + if (!entry) + return; + // Note that we don't call NavigationController::GotToOffset() as we don't + // want to create a pending navigation entry (it might end up lingering + // http://crbug.com/51680). + entry->set_transition_type(entry->transition_type() | + PageTransition::FORWARD_BACK); + NavigateToEntry(*entry, NavigationController::NO_RELOAD); + } +} + +void TabContents::OnDidGetApplicationInfo(int32 page_id, + const WebApplicationInfo& info) { + web_app_info_ = info; + + if (delegate()) + delegate()->OnDidGetApplicationInfo(this, page_id); +} + +void TabContents::OnInstallApplication(const WebApplicationInfo& info) { + if (delegate()) + delegate()->OnInstallApplication(this, info); +} + +void TabContents::OnPageContents(const GURL& url, + int32 page_id, + const string16& contents, + const std::string& language, + bool page_translatable) { + // Don't index any https pages. People generally don't want their bank + // accounts, etc. indexed on their computer, especially since some of these + // things are not marked cachable. + // TODO(brettw) we may want to consider more elaborate heuristics such as + // the cachability of the page. We may also want to consider subframes (this + // test will still index subframes if the subframe is SSL). + // TODO(zelidrag) bug chromium-os:2808 - figure out if we want to reenable + // content indexing for chromeos in some future releases. +#if !defined(OS_CHROMEOS) + if (!url.SchemeIsSecure()) { + Profile* p = profile(); + if (p && !p->IsOffTheRecord()) { + HistoryService* hs = p->GetHistoryService(Profile::IMPLICIT_ACCESS); + if (hs) + hs->SetPageContents(url, contents); + } + } +#endif + + language_state_.LanguageDetermined(language, page_translatable); + + std::string lang = language; + NotificationService::current()->Notify( + NotificationType::TAB_LANGUAGE_DETERMINED, + Source<TabContents>(this), + Details<std::string>(&lang)); + + // Generate the thumbnail here if the in-browser thumbnailing is enabled. + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableInBrowserThumbnailing)) { + ThumbnailGenerator::UpdateThumbnailIfNecessary(this, url); + } +} + +void TabContents::OnPageTranslated(int32 page_id, + const std::string& original_lang, + const std::string& translated_lang, + TranslateErrors::Type error_type) { + language_state_.set_current_language(translated_lang); + language_state_.set_translation_pending(false); + PageTranslatedDetails details(original_lang, translated_lang, error_type); + NotificationService::current()->Notify( + NotificationType::PAGE_TRANSLATED, + Source<TabContents>(this), + Details<PageTranslatedDetails>(&details)); +} + +void TabContents::OnSetSuggestions( + int32 page_id, + const std::vector<std::string>& suggestions) { + if (delegate()) + delegate()->OnSetSuggestions(page_id, suggestions); +} + +void TabContents::OnInstantSupportDetermined(int32 page_id, bool result) { + if (delegate()) + delegate()->OnInstantSupportDetermined(page_id, result); +} + +void TabContents::OnRunFileChooser( + const ViewHostMsg_RunFileChooser_Params& params) { + if (file_select_helper_.get() == NULL) + file_select_helper_.reset(new FileSelectHelper(profile())); + file_select_helper_->RunFileChooser(render_view_host(), params); +} + + +void TabContents::OnContentSettingsAccessed(bool content_was_blocked) { + if (delegate_) + delegate_->OnContentSettingsChange(this); +} + +RenderViewHostDelegate::View* TabContents::GetViewDelegate() { + return view_.get(); +} + +RenderViewHostDelegate::RendererManagement* +TabContents::GetRendererManagementDelegate() { + return &render_manager_; +} + +RenderViewHostDelegate::ContentSettings* +TabContents::GetContentSettingsDelegate() { + return content_settings_delegate_.get(); +} + +RenderViewHostDelegate::SSL* TabContents::GetSSLDelegate() { + return GetSSLHelper(); +} + +AutomationResourceRoutingDelegate* +TabContents::GetAutomationResourceRoutingDelegate() { + return delegate(); +} + +RenderViewHostDelegate::BookmarkDrag* TabContents::GetBookmarkDragDelegate() { + return bookmark_drag_; +} + +void TabContents::SetBookmarkDragDelegate( + RenderViewHostDelegate::BookmarkDrag* bookmark_drag) { + bookmark_drag_ = bookmark_drag; +} + +TabSpecificContentSettings* TabContents::GetTabSpecificContentSettings() const { + return content_settings_delegate_.get(); +} + +RendererPreferences TabContents::GetRendererPrefs(Profile* profile) const { + return renderer_preferences_; +} + +TabContents* TabContents::GetAsTabContents() { + return this; +} + +ViewType::Type TabContents::GetRenderViewType() const { + return ViewType::TAB_CONTENTS; +} + +int TabContents::GetBrowserWindowID() const { + return controller().window_id().id(); +} + +void TabContents::RenderViewCreated(RenderViewHost* render_view_host) { + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, + Source<TabContents>(this), + Details<RenderViewHost>(render_view_host)); + NavigationEntry* entry = controller_.GetActiveEntry(); + if (!entry) + return; + + // When we're creating views, we're still doing initial setup, so we always + // use the pending Web UI rather than any possibly existing committed one. + if (render_manager_.pending_web_ui()) { + render_manager_.pending_web_ui()->RenderViewCreated(render_view_host); + } + + if (entry->IsViewSourceMode()) { + // Put the renderer in view source mode. + render_view_host->Send( + new ViewMsg_EnableViewSourceMode(render_view_host->routing_id())); + } + + view()->RenderViewCreated(render_view_host); +} + +void TabContents::RenderViewReady(RenderViewHost* rvh) { + if (rvh != render_view_host()) { + // Don't notify the world, since this came from a renderer in the + // background. + return; + } + + NotifyConnected(); + bool was_crashed = is_crashed(); + SetIsCrashed(base::TERMINATION_STATUS_STILL_RUNNING, 0); + + // Restore the focus to the tab (otherwise the focus will be on the top + // window). + if (was_crashed && !FocusLocationBarByDefault() && + (!delegate_ || delegate_->ShouldFocusPageAfterCrash())) { + Focus(); + } +} + +void TabContents::RenderViewGone(RenderViewHost* rvh, + base::TerminationStatus status, + int error_code) { + // Ask the print preview if this renderer was valuable. + if (!printing_->OnRenderViewGone(rvh)) + return; + if (rvh != render_view_host()) { + // The pending page's RenderViewHost is gone. + return; + } + + SetIsLoading(false, NULL); + NotifyDisconnected(); + SetIsCrashed(status, error_code); + + // Remove all infobars. + while (!infobar_delegates_.empty()) + RemoveInfoBar(GetInfoBarDelegateAt(infobar_count() - 1)); + + // Tell the view that we've crashed so it can prepare the sad tab page. + // Only do this if we're not in browser shutdown, so that TabContents + // objects that are not in a browser (e.g., HTML dialogs) and thus are + // visible do not flash a sad tab page. + if (browser_shutdown::GetShutdownType() == browser_shutdown::NOT_VALID) + view_->OnTabCrashed(status, error_code); + + // Hide any visible hung renderer warning for this web contents' process. + hung_renderer_dialog::HideForTabContents(this); +} + +void TabContents::RenderViewDeleted(RenderViewHost* rvh) { + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_DELETED, + Source<TabContents>(this), + Details<RenderViewHost>(rvh)); + render_manager_.RenderViewDeleted(rvh); +} + +void TabContents::DidNavigate(RenderViewHost* rvh, + const ViewHostMsg_FrameNavigate_Params& params) { + int extra_invalidate_flags = 0; + + if (PageTransition::IsMainFrame(params.transition)) { + if (MaybeUsePreloadedPage(params.url)) { + return; + } + + bool was_bookmark_bar_visible = ShouldShowBookmarkBar(); + + render_manager_.DidNavigateMainFrame(rvh); + + if (was_bookmark_bar_visible != ShouldShowBookmarkBar()) + extra_invalidate_flags |= INVALIDATE_BOOKMARK_BAR; + } + + // Update the site of the SiteInstance if it doesn't have one yet. + if (!GetSiteInstance()->has_site()) + GetSiteInstance()->SetSite(params.url); + + // Need to update MIME type here because it's referred to in + // UpdateNavigationCommands() called by RendererDidNavigate() to + // determine whether or not to enable the encoding menu. + // It's updated only for the main frame. For a subframe, + // RenderView::UpdateURL does not set params.contents_mime_type. + // (see http://code.google.com/p/chromium/issues/detail?id=2929 ) + // TODO(jungshik): Add a test for the encoding menu to avoid + // regressing it again. + if (PageTransition::IsMainFrame(params.transition)) + contents_mime_type_ = params.contents_mime_type; + + NavigationController::LoadCommittedDetails details; + bool did_navigate = controller_.RendererDidNavigate( + params, extra_invalidate_flags, &details); + + // Send notification about committed provisional loads. This notification is + // different from the NAV_ENTRY_COMMITTED notification which doesn't include + // the actual URL navigated to and isn't sent for AUTO_SUBFRAME navigations. + if (details.type != NavigationType::NAV_IGNORE) { + // For AUTO_SUBFRAME navigations, an event for the main frame is generated + // that is not recorded in the navigation history. For the purpose of + // tracking navigation events, we treat this event as a sub frame navigation + // event. + bool is_main_frame = did_navigate ? details.is_main_frame : false; + ProvisionalLoadDetails load_details( + is_main_frame, details.is_in_page, params.url, std::string(), false, + false, params.frame_id); + load_details.set_transition_type(params.transition); + // Whether or not a page transition was triggered by going backward or + // forward in the history is only stored in the navigation controller's + // entry list. + if (did_navigate && + (controller_.GetActiveEntry()->transition_type() & + PageTransition::FORWARD_BACK)) { + load_details.set_transition_type( + params.transition | PageTransition::FORWARD_BACK); + } + NotificationService::current()->Notify( + NotificationType::FRAME_PROVISIONAL_LOAD_COMMITTED, + Source<NavigationController>(&controller_), + Details<ProvisionalLoadDetails>(&load_details)); + } + + // Update history. Note that this needs to happen after the entry is complete, + // which WillNavigate[Main,Sub]Frame will do before this function is called. + if (params.should_update_history) { + // Most of the time, the displayURL matches the loaded URL, but for about: + // URLs, we use a data: URL as the real value. We actually want to save the + // about: URL to the history db and keep the data: URL hidden. This is what + // the TabContents' URL getter does. + scoped_refptr<history::HistoryAddPageArgs> add_page_args( + CreateHistoryAddPageArgs(GetURL(), details, params)); + if (!delegate() || + delegate()->ShouldAddNavigationToHistory(*add_page_args, + details.type)) { + UpdateHistoryForNavigation(add_page_args); + } + } + + if (!did_navigate) + return; // No navigation happened. + + // DO NOT ADD MORE STUFF TO THIS FUNCTION! Your component should either listen + // for the appropriate notification (best) or you can add it to + // DidNavigateMainFramePostCommit / DidNavigateAnyFramePostCommit (only if + // necessary, please). + + // Run post-commit tasks. + if (details.is_main_frame) + DidNavigateMainFramePostCommit(details, params); + DidNavigateAnyFramePostCommit(rvh, details, params); +} + +void TabContents::UpdateState(RenderViewHost* rvh, + int32 page_id, + const std::string& state) { + DCHECK(rvh == render_view_host()); + + // We must be prepared to handle state updates for any page, these occur + // when the user is scrolling and entering form data, as well as when we're + // leaving a page, in which case our state may have already been moved to + // the next page. The navigation controller will look up the appropriate + // NavigationEntry and update it when it is notified via the delegate. + + int entry_index = controller_.GetEntryIndexWithPageID( + GetSiteInstance(), page_id); + if (entry_index < 0) + return; + NavigationEntry* entry = controller_.GetEntryAtIndex(entry_index); + + if (state == entry->content_state()) + return; // Nothing to update. + entry->set_content_state(state); + controller_.NotifyEntryChanged(entry, entry_index); +} + +void TabContents::UpdateTitle(RenderViewHost* rvh, + int32 page_id, const std::wstring& title) { + // If we have a title, that's a pretty good indication that we've started + // getting useful data. + SetNotWaitingForResponse(); + + DCHECK(rvh == render_view_host()); + NavigationEntry* entry = controller_.GetEntryWithPageID(rvh->site_instance(), + page_id); + if (!entry || !UpdateTitleForEntry(entry, title)) + return; + + // Broadcast notifications when the UI should be updated. + if (entry == controller_.GetEntryAtOffset(0)) + NotifyNavigationStateChanged(INVALIDATE_TITLE); +} + +void TabContents::UpdateEncoding(RenderViewHost* render_view_host, + const std::string& encoding) { + set_encoding(encoding); +} + +void TabContents::UpdateTargetURL(int32 page_id, const GURL& url) { + if (delegate()) + delegate()->UpdateTargetURL(this, url); +} + +void TabContents::UpdateThumbnail(const GURL& url, + const SkBitmap& bitmap, + const ThumbnailScore& score) { + if (profile()->IsOffTheRecord()) + return; + + // Tell History about this thumbnail + history::TopSites* ts = profile()->GetTopSites(); + if (ts) + ts->SetPageThumbnail(url, bitmap, score); +} + +void TabContents::UpdateInspectorSetting(const std::string& key, + const std::string& value) { + RenderViewHostDelegateHelper::UpdateInspectorSetting(profile(), key, value); +} + +void TabContents::ClearInspectorSettings() { + RenderViewHostDelegateHelper::ClearInspectorSettings(profile()); +} + +void TabContents::Close(RenderViewHost* rvh) { + // The UI may be in an event-tracking loop, such as between the + // mouse-down and mouse-up in text selection or a button click. + // Defer the close until after tracking is complete, so that we + // don't free objects out from under the UI. + // TODO(shess): This could probably be integrated with the + // IsDoingDrag() test below. Punting for now because I need more + // research to understand how this impacts platforms other than Mac. + // TODO(shess): This could get more fine-grained. For instance, + // closing a tab in another window while selecting text in the + // current window's Omnibox should be just fine. + if (view()->IsEventTracking()) { + view()->CloseTabAfterEventTracking(); + return; + } + + // If we close the tab while we're in the middle of a drag, we'll crash. + // Instead, cancel the drag and close it as soon as the drag ends. + if (view()->IsDoingDrag()) { + view()->CancelDragAndCloseTab(); + return; + } + + // Ignore this if it comes from a RenderViewHost that we aren't showing. + if (delegate() && rvh == render_view_host()) + delegate()->CloseContents(this); +} + +void TabContents::RequestMove(const gfx::Rect& new_bounds) { + if (delegate() && delegate()->IsPopup(this)) + delegate()->MoveContents(this, new_bounds); +} + +void TabContents::DidStartLoading() { + SetIsLoading(true, NULL); + + if (delegate()) { + bool is_print_preview_tab = + printing::PrintPreviewTabController::IsPrintPreviewTab(this); + if (content_restrictions_ || is_print_preview_tab) { + content_restrictions_= is_print_preview_tab ? + CONTENT_RESTRICTION_PRINT : 0; + delegate()->ContentRestrictionsChanged(this); + } + } + + // Notify observers about navigation. + FOR_EACH_OBSERVER(TabContentsObserver, observers_, DidStartLoading()); +} + +void TabContents::DidStopLoading() { + scoped_ptr<LoadNotificationDetails> details; + + NavigationEntry* entry = controller_.GetActiveEntry(); + // An entry may not exist for a stop when loading an initial blank page or + // if an iframe injected by script into a blank page finishes loading. + if (entry) { + base::TimeDelta elapsed = base::TimeTicks::Now() - current_load_start_; + + details.reset(new LoadNotificationDetails( + entry->virtual_url(), + entry->transition_type(), + elapsed, + &controller_, + controller_.GetCurrentEntryIndex())); + } + + SetIsLoading(false, details.get()); + + // Notify observers about navigation. + FOR_EACH_OBSERVER(TabContentsObserver, observers_, DidStopLoading()); +} + +void TabContents::DidChangeLoadProgress(double progress) { + if (delegate()) + delegate()->LoadProgressChanged(progress); +} + +void TabContents::DocumentOnLoadCompletedInMainFrame( + RenderViewHost* render_view_host, + int32 page_id) { + NotificationService::current()->Notify( + NotificationType::LOAD_COMPLETED_MAIN_FRAME, + Source<TabContents>(this), + Details<int>(&page_id)); +} + +void TabContents::RequestOpenURL(const GURL& url, const GURL& referrer, + WindowOpenDisposition disposition) { + if (render_manager_.web_ui()) { + // When we're a Web UI, it will provide a page transition type for us (this + // is so the new tab page can specify AUTO_BOOKMARK for automatically + // generated suggestions). + // + // Note also that we hide the referrer for Web UI pages. We don't really + // want web sites to see a referrer of "chrome://blah" (and some + // chrome: URLs might have search terms or other stuff we don't want to + // send to the site), so we send no referrer. + OpenURL(url, GURL(), disposition, + render_manager_.web_ui()->link_transition_type()); + } else { + OpenURL(url, referrer, disposition, PageTransition::LINK); + } +} + +void TabContents::DomOperationResponse(const std::string& json_string, + int automation_id) { +} + +void TabContents::ProcessWebUIMessage( + const ViewHostMsg_DomMessage_Params& params) { + if (!render_manager_.web_ui()) { + // This can happen if someone uses window.open() to open an extension URL + // from a non-extension context. + render_view_host()->BlockExtensionRequest(params.request_id); + return; + } + render_manager_.web_ui()->ProcessWebUIMessage(params); +} + +void TabContents::ProcessExternalHostMessage(const std::string& message, + const std::string& origin, + const std::string& target) { + if (delegate()) + delegate()->ForwardMessageToExternalHost(message, origin, target); +} + +void TabContents::RunJavaScriptMessage( + const std::wstring& message, + const std::wstring& default_prompt, + const GURL& frame_url, + const int flags, + IPC::Message* reply_msg, + bool* did_suppress_message) { + // Suppress javascript messages when requested and when inside a constrained + // popup window (because that activates them and breaks them out of the + // constrained window jail). + // Also suppress messages when showing an interstitial. The interstitial is + // shown over the previous page, we don't want the hidden page dialogs to + // interfere with the interstitial. + bool suppress_this_message = + suppress_javascript_messages_ || + showing_interstitial_page() || + (delegate() && delegate()->ShouldSuppressDialogs()); + if (delegate()) + suppress_this_message |= + (delegate()->GetConstrainingContents(this) != this); + + *did_suppress_message = suppress_this_message; + + if (!suppress_this_message) { + base::TimeDelta time_since_last_message( + base::TimeTicks::Now() - last_javascript_message_dismissal_); + bool show_suppress_checkbox = false; + // Show a checkbox offering to suppress further messages if this message is + // being displayed within kJavascriptMessageExpectedDelay of the last one. + if (time_since_last_message < + base::TimeDelta::FromMilliseconds(kJavascriptMessageExpectedDelay)) + show_suppress_checkbox = true; + + RunJavascriptMessageBox(profile(), this, frame_url, flags, message, + default_prompt, show_suppress_checkbox, reply_msg); + } else { + // If we are suppressing messages, just reply as is if the user immediately + // pressed "Cancel". + OnMessageBoxClosed(reply_msg, false, std::wstring()); + } +} + +void TabContents::RunBeforeUnloadConfirm(const std::wstring& message, + IPC::Message* reply_msg) { + if (delegate()) + delegate()->WillRunBeforeUnloadConfirm(); + if (delegate() && delegate()->ShouldSuppressDialogs()) { + render_view_host()->JavaScriptMessageBoxClosed(reply_msg, true, + std::wstring()); + return; + } + is_showing_before_unload_dialog_ = true; + RunBeforeUnloadDialog(this, message, reply_msg); +} + +void TabContents::ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + IPC::Message* reply_msg) { + if (delegate()) { + HtmlDialogUIDelegate* dialog_delegate = + new ModalHtmlDialogDelegate(url, width, height, json_arguments, + reply_msg, this); + delegate()->ShowHtmlDialog(dialog_delegate, NULL); + } +} + +GURL TabContents::GetAlternateErrorPageURL() const { + GURL url; + // Disable alternate error pages when in OffTheRecord/Incognito mode. + if (profile()->IsOffTheRecord()) + return url; + + PrefService* prefs = profile()->GetPrefs(); + DCHECK(prefs); + if (prefs->GetBoolean(prefs::kAlternateErrorPagesEnabled)) { + url = google_util::AppendGoogleLocaleParam( + GURL(google_util::kLinkDoctorBaseURL)); + url = google_util::AppendGoogleTLDParam(url); + } + return url; +} + +WebPreferences TabContents::GetWebkitPrefs() { + Profile* profile = render_view_host()->process()->profile(); + bool is_web_ui = false; + WebPreferences web_prefs = + RenderViewHostDelegateHelper::GetWebkitPrefs(profile, is_web_ui); + + // Force accelerated compositing and 2d canvas off for chrome: and + // chrome-extension: pages. + if (GetURL().SchemeIs(chrome::kChromeDevToolsScheme) || + GetURL().SchemeIs(chrome::kChromeUIScheme)) { + web_prefs.accelerated_compositing_enabled = false; + web_prefs.accelerated_2d_canvas_enabled = false; + } + +#if defined(OS_MACOSX) + // Disable accelerated compositing if IOSurface's are not supported, + // as is the case in 10.5. + if (!IOSurfaceSupport::Initialize()) + web_prefs.accelerated_compositing_enabled = false; +#endif + + return web_prefs; +} + +void TabContents::OnUserGesture() { + // See comment in RenderViewHostDelegate::OnUserGesture as to why we do this. + DownloadRequestLimiter* limiter = + g_browser_process->download_request_limiter(); + if (limiter) + limiter->OnUserGesture(this); + ExternalProtocolHandler::PermitLaunchUrl(); +} + +void TabContents::OnIgnoredUIEvent() { + if (constrained_window_count()) { + ConstrainedWindow* window = *constrained_window_begin(); + window->FocusConstrainedWindow(); + } +} + +void TabContents::OnCrossSiteResponse(int new_render_process_host_id, + int new_request_id) { + // Allows the TabContents to react when a cross-site response is ready to be + // delivered to a pending RenderViewHost. We must first run the onunload + // handler of the old RenderViewHost before we can allow it to proceed. + render_manager_.OnCrossSiteResponse(new_render_process_host_id, + new_request_id); +} + +void TabContents::RendererUnresponsive(RenderViewHost* rvh, + bool is_during_unload) { + if (is_during_unload) { + // Hang occurred while firing the beforeunload/unload handler. + // Pretend the handler fired so tab closing continues as if it had. + rvh->set_sudden_termination_allowed(true); + + if (!render_manager_.ShouldCloseTabOnUnresponsiveRenderer()) + return; + + // If the tab hangs in the beforeunload/unload handler there's really + // nothing we can do to recover. Pretend the unload listeners have + // all fired and close the tab. If the hang is in the beforeunload handler + // then the user will not have the option of cancelling the close. + Close(rvh); + return; + } + + if (render_view_host() && render_view_host()->IsRenderViewLive() && + (!delegate() || delegate()->ShouldShowHungRendererDialog())) { + hung_renderer_dialog::ShowForTabContents(this); + } +} + +void TabContents::RendererResponsive(RenderViewHost* render_view_host) { + hung_renderer_dialog::HideForTabContents(this); +} + +void TabContents::LoadStateChanged(const GURL& url, + net::LoadState load_state, + uint64 upload_position, + uint64 upload_size) { + load_state_ = load_state; + upload_position_ = upload_position; + upload_size_ = upload_size; + std::wstring languages = + UTF8ToWide(profile()->GetPrefs()->GetString(prefs::kAcceptLanguages)); + std::string host = url.host(); + load_state_host_ = WideToUTF16Hack( + net::IDNToUnicode(host.c_str(), host.size(), languages, NULL)); + if (load_state_ == net::LOAD_STATE_READING_RESPONSE) + SetNotWaitingForResponse(); + if (is_loading()) + NotifyNavigationStateChanged(INVALIDATE_LOAD | INVALIDATE_TAB); +} + +bool TabContents::IsExternalTabContainer() const { + if (!delegate()) + return false; + + return delegate()->IsExternalTabContainer(); +} + +void TabContents::DidInsertCSS() { + // This RVHDelegate function is used for extensions and not us. +} + +void TabContents::FocusedNodeChanged(bool is_editable_node) { + NotificationService::current()->Notify( + NotificationType::FOCUS_CHANGED_IN_PAGE, + Source<TabContents>(this), + Details<const bool>(&is_editable_node)); +} + +void TabContents::UpdateZoomLimits(int minimum_percent, + int maximum_percent, + bool remember) { + minimum_zoom_percent_ = minimum_percent; + maximum_zoom_percent_ = maximum_percent; + temporary_zoom_settings_ = !remember; +} + +void TabContents::WorkerCrashed() { + if (delegate()) + delegate()->WorkerCrashed(); +} + +void TabContents::BeforeUnloadFiredFromRenderManager( + bool proceed, + bool* proceed_to_fire_unload) { + if (delegate()) + delegate()->BeforeUnloadFired(this, proceed, proceed_to_fire_unload); +} + +void TabContents::DidStartLoadingFromRenderManager( + RenderViewHost* render_view_host) { + DidStartLoading(); +} + +void TabContents::RenderViewGoneFromRenderManager( + RenderViewHost* render_view_host) { + DCHECK(crashed_status_ != base::TERMINATION_STATUS_STILL_RUNNING); + RenderViewGone(render_view_host, crashed_status_, crashed_error_code_); +} + +void TabContents::UpdateRenderViewSizeForRenderManager() { + // TODO(brettw) this is a hack. See TabContentsView::SizeContents. + gfx::Size size = view_->GetContainerSize(); + // 0x0 isn't a valid window size (minimal window size is 1x1) but it may be + // here during container initialization and normal window size will be set + // later. In case of tab duplication this resizing to 0x0 prevents setting + // normal size later so just ignore it. + if (!size.IsEmpty()) + view_->SizeContents(size); +} + +void TabContents::NotifySwappedFromRenderManager() { + NotifySwapped(); +} + +NavigationController& TabContents::GetControllerForRenderManager() { + return controller(); +} + +WebUI* TabContents::CreateWebUIForRenderManager(const GURL& url) { + return WebUIFactory::CreateWebUIForURL(this, url); +} + +NavigationEntry* +TabContents::GetLastCommittedNavigationEntryForRenderManager() { + return controller_.GetLastCommittedEntry(); +} + +bool TabContents::CreateRenderViewForRenderManager( + RenderViewHost* render_view_host) { + RenderWidgetHostView* rwh_view = view_->CreateViewForWidget(render_view_host); + + if (!render_view_host->CreateRenderView(string16())) + return false; + + // Now that the RenderView has been created, we need to tell it its size. + rwh_view->SetSize(view_->GetContainerSize()); + + UpdateMaxPageIDIfNecessary(render_view_host->site_instance(), + render_view_host); + return true; +} + +void TabContents::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::PREF_CHANGED: { + std::string* pref_name_in = Details<std::string>(details).ptr(); + DCHECK(Source<PrefService>(source).ptr() == profile()->GetPrefs()); + if (*pref_name_in == prefs::kAlternateErrorPagesEnabled) { + UpdateAlternateErrorPageURL(); + } else if ((*pref_name_in == prefs::kDefaultCharset) || + StartsWithASCII(*pref_name_in, "webkit.webprefs.", true)) { + UpdateWebPreferences(); + } else if (*pref_name_in == prefs::kDefaultZoomLevel) { + UpdateZoomLevel(); + } else { + NOTREACHED() << "unexpected pref change notification" << *pref_name_in; + } + break; + } + case NotificationType::RENDER_WIDGET_HOST_DESTROYED: + view_->RenderWidgetHostDestroyed(Source<RenderWidgetHost>(source).ptr()); + break; + + case NotificationType::NAV_ENTRY_COMMITTED: { + DCHECK(&controller_ == Source<NavigationController>(source).ptr()); + + NavigationController::LoadCommittedDetails& committed_details = + *(Details<NavigationController::LoadCommittedDetails>(details).ptr()); + ExpireInfoBars(committed_details); + break; + } + +#if defined(OS_LINUX) + case NotificationType::BROWSER_THEME_CHANGED: { + renderer_preferences_util::UpdateFromSystemSettings( + &renderer_preferences_, profile()); + render_view_host()->SyncRendererPrefs(); + break; + } +#endif + + case NotificationType::USER_STYLE_SHEET_UPDATED: + UpdateWebPreferences(); + break; + + case NotificationType::CONTENT_SETTINGS_CHANGED: { + Details<const ContentSettingsDetails> settings_details(details); + NavigationEntry* entry = controller_.GetActiveEntry(); + GURL entry_url; + if (entry) + entry_url = entry->url(); + if (settings_details.ptr()->update_all() || + settings_details.ptr()->pattern().Matches(entry_url)) { + render_view_host()->SendContentSettings(entry_url, + profile()->GetHostContentSettingsMap()-> + GetContentSettings(entry_url)); + } + break; + } + + case NotificationType::EXTENSION_LOADED: + break; + + case NotificationType::EXTENSION_UNLOADED: + break; + + case NotificationType::GOOGLE_URL_UPDATED: + UpdateAlternateErrorPageURL(); + break; + + default: + NOTREACHED(); + } +} + +void TabContents::UpdateExtensionAppIcon(const Extension* extension) { + extension_app_icon_.reset(); + + if (extension) { + extension_app_image_loader_.reset(new ImageLoadingTracker(this)); + extension_app_image_loader_->LoadImage( + extension, + extension->GetIconResource(Extension::EXTENSION_ICON_SMALLISH, + ExtensionIconSet::MATCH_EXACTLY), + gfx::Size(Extension::EXTENSION_ICON_SMALLISH, + Extension::EXTENSION_ICON_SMALLISH), + ImageLoadingTracker::CACHE); + } else { + extension_app_image_loader_.reset(NULL); + } +} + +const Extension* TabContents::GetExtensionContaining(const GURL& url) { + ExtensionService* extensions_service = profile()->GetExtensionService(); + if (!extensions_service) + return NULL; + + const Extension* extension = extensions_service->GetExtensionByURL(url); + return extension ? + extension : extensions_service->GetExtensionByWebExtent(url); +} + +void TabContents::OnImageLoaded(SkBitmap* image, ExtensionResource resource, + int index) { + if (image) { + extension_app_icon_ = *image; + NotifyNavigationStateChanged(INVALIDATE_TAB); + } +} + +gfx::NativeWindow TabContents::GetMessageBoxRootWindow() { + return view_->GetTopLevelNativeWindow(); +} + +void TabContents::OnMessageBoxClosed(IPC::Message* reply_msg, + bool success, + const std::wstring& prompt) { + last_javascript_message_dismissal_ = base::TimeTicks::Now(); + if (is_showing_before_unload_dialog_ && !success) { + // If a beforeunload dialog is canceled, we need to stop the throbber from + // spinning, since we forced it to start spinning in Navigate. + DidStopLoading(); + + tab_close_start_time_ = base::TimeTicks(); + } + is_showing_before_unload_dialog_ = false; + render_view_host()->JavaScriptMessageBoxClosed(reply_msg, success, prompt); +} + +void TabContents::SetSuppressMessageBoxes(bool suppress_message_boxes) { + set_suppress_javascript_messages(suppress_message_boxes); +} + +TabContents* TabContents::AsTabContents() { + return this; +} + +ExtensionHost* TabContents::AsExtensionHost() { + return NULL; +} + +void TabContents::set_encoding(const std::string& encoding) { + encoding_ = CharacterEncoding::GetCanonicalEncodingNameByAliasName(encoding); +} + +void TabContents::SetAppIcon(const SkBitmap& app_icon) { + app_icon_ = app_icon; + NotifyNavigationStateChanged(INVALIDATE_TITLE); +} + +void TabContents::SwapInRenderViewHost(RenderViewHost* rvh) { + render_manager_.SwapInRenderViewHost(rvh); +} + +void TabContents::CreateViewAndSetSizeForRVH(RenderViewHost* rvh) { + RenderWidgetHostView* rwh_view = view()->CreateViewForWidget(rvh); + rwh_view->SetSize(view()->GetContainerSize()); +} + +bool TabContents::MaybeUsePreloadedPage(const GURL& url) { + prerender::PrerenderManager* pm = profile()->GetPrerenderManager(); + if (pm != NULL) { + if (pm->MaybeUsePreloadedPage(this, url)) + return true; + } + return false; +} diff --git a/content/browser/tab_contents/tab_contents.h b/content/browser/tab_contents/tab_contents.h new file mode 100644 index 0000000..318e9fc --- /dev/null +++ b/content/browser/tab_contents/tab_contents.h @@ -0,0 +1,1258 @@ +// Copyright (c) 2011 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_TAB_CONTENTS_TAB_CONTENTS_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_H_ +#pragma once + +#include <deque> +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/scoped_ptr.h" +#include "base/string16.h" +#include "chrome/browser/download/save_package.h" +#include "chrome/browser/extensions/image_loading_tracker.h" +#include "chrome/browser/fav_icon_helper.h" +#include "chrome/browser/prefs/pref_change_registrar.h" +#include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/browser/tab_contents/tab_specific_content_settings.h" +#include "chrome/browser/ui/app_modal_dialogs/js_modal_dialog.h" +#include "chrome/browser/webui/web_ui_factory.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/property_bag.h" +#include "chrome/common/renderer_preferences.h" +#include "chrome/common/translate_errors.h" +#include "chrome/common/web_apps.h" +#include "content/browser/tab_contents/constrained_window.h" +#include "content/browser/tab_contents/language_state.h" +#include "content/browser/tab_contents/navigation_controller.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/browser/tab_contents/page_navigator.h" +#include "content/browser/tab_contents/render_view_host_manager.h" +#include "net/base/load_states.h" +#include "ui/gfx/native_widget_types.h" + +#if defined(OS_WIN) +#include "base/win/scoped_handle.h" +#endif + +namespace gfx { +class Rect; +} + +namespace history { +class HistoryAddPageArgs; +} + +namespace prerender { +class PrerenderManager; +class PrerenderPLTRecorder; +} + +namespace printing { +class PrintPreviewMessageHandler; +class PrintViewManager; +} + +namespace safe_browsing { +class ClientSideDetectionHost; +} + +class AutocompleteHistoryManager; +class AutoFillManager; +class BlockedContentContainer; +class WebUI; +class DesktopNotificationHandlerForTC; +class DownloadItem; +class Extension; +class FileSelectHelper; +class InfoBarDelegate; +class LoadNotificationDetails; +class OmniboxSearchHint; +class PluginObserver; +class Profile; +class RenderViewHost; +class SessionStorageNamespace; +class SiteInstance; +class SkBitmap; +class TabContents; +class TabContentsDelegate; +class TabContentsObserver; +class TabContentsSSLHelper; +class TabContentsView; +class URLPattern; +struct RendererPreferences; +struct ThumbnailScore; +struct ViewHostMsg_DomMessage_Params; +struct ViewHostMsg_FrameNavigate_Params; +struct ViewHostMsg_RunFileChooser_Params; +struct WebPreferences; + +// Describes what goes in the main content area of a tab. TabContents is +// the only type of TabContents, and these should be merged together. +class TabContents : public PageNavigator, + public NotificationObserver, + public RenderViewHostDelegate, + public RenderViewHostManager::Delegate, + public JavaScriptAppModalDialogDelegate, + public ImageLoadingTracker::Observer, + public TabSpecificContentSettings::Delegate { + public: + // Flags passed to the TabContentsDelegate.NavigationStateChanged to tell it + // what has changed. Combine them to update more than one thing. + enum InvalidateTypes { + INVALIDATE_URL = 1 << 0, // The URL has changed. + INVALIDATE_TAB = 1 << 1, // The favicon, app icon, or crashed + // state changed. + INVALIDATE_LOAD = 1 << 2, // The loading state has changed. + INVALIDATE_PAGE_ACTIONS = 1 << 3, // Page action icons have changed. + INVALIDATE_BOOKMARK_BAR = 1 << 4, // State of ShouldShowBookmarkBar + // changed. + INVALIDATE_TITLE = 1 << 5, // The title changed. + }; + + // |base_tab_contents| is used if we want to size the new tab contents view + // based on an existing tab contents view. This can be NULL if not needed. + // + // The session storage namespace parameter allows multiple render views and + // tab contentses to share the same session storage (part of the WebStorage + // spec) space. This is useful when restoring tabs, but most callers should + // pass in NULL which will cause a new SessionStorageNamespace to be created. + TabContents(Profile* profile, + SiteInstance* site_instance, + int routing_id, + const TabContents* base_tab_contents, + SessionStorageNamespace* session_storage_namespace); + virtual ~TabContents(); + + // Intrinsic tab state ------------------------------------------------------- + + // Returns the property bag for this tab contents, where callers can add + // extra data they may wish to associate with the tab. Returns a pointer + // rather than a reference since the PropertyAccessors expect this. + const PropertyBag* property_bag() const { return &property_bag_; } + PropertyBag* property_bag() { return &property_bag_; } + + TabContentsDelegate* delegate() const { return delegate_; } + void set_delegate(TabContentsDelegate* d) { delegate_ = d; } + + // Gets the controller for this tab contents. + NavigationController& controller() { return controller_; } + const NavigationController& controller() const { return controller_; } + + // Returns the user profile associated with this TabContents (via the + // NavigationController). + Profile* profile() const { return controller_.profile(); } + + // Returns true if contains content rendered by an extension. + bool HostsExtension() const; + + // Returns the TabContentsSSLHelper, creating it if necessary. + TabContentsSSLHelper* GetSSLHelper(); + + // Returns the SavePackage which manages the page saving job. May be NULL. + SavePackage* save_package() const { return save_package_.get(); } + + // Return the currently active RenderProcessHost and RenderViewHost. Each of + // these may change over time. + RenderProcessHost* GetRenderProcessHost() const; + RenderViewHost* render_view_host() const { + return render_manager_.current_host(); + } + + WebUI* web_ui() const { + return render_manager_.web_ui() ? render_manager_.web_ui() + : render_manager_.pending_web_ui(); + } + + // Returns the currently active RenderWidgetHostView. This may change over + // time and can be NULL (during setup and teardown). + RenderWidgetHostView* GetRenderWidgetHostView() const { + return render_manager_.GetRenderWidgetHostView(); + } + + // The TabContentsView will never change and is guaranteed non-NULL. + TabContentsView* view() const { + return view_.get(); + } + + // Returns the FavIconHelper of this TabContents. + FavIconHelper& fav_icon_helper() { + return *fav_icon_helper_.get(); + } + + // App extensions ------------------------------------------------------------ + + // Sets the extension denoting this as an app. If |extension| is non-null this + // tab becomes an app-tab. TabContents does not listen for unload events for + // the extension. It's up to consumers of TabContents to do that. + // + // NOTE: this should only be manipulated before the tab is added to a browser. + // TODO(sky): resolve if this is the right way to identify an app tab. If it + // is, than this should be passed in the constructor. + void SetExtensionApp(const Extension* extension); + + // Convenience for setting the app extension by id. This does nothing if + // |extension_app_id| is empty, or an extension can't be found given the + // specified id. + void SetExtensionAppById(const std::string& extension_app_id); + + const Extension* extension_app() const { return extension_app_; } + bool is_app() const { return extension_app_ != NULL; } + + // If an app extension has been explicitly set for this TabContents its icon + // is returned. + // + // NOTE: the returned icon is larger than 16x16 (its size is + // Extension::EXTENSION_ICON_SMALLISH). + SkBitmap* GetExtensionAppIcon(); + + // Tab navigation state ------------------------------------------------------ + + // Returns the current navigation properties, which if a navigation is + // pending may be provisional (e.g., the navigation could result in a + // download, in which case the URL would revert to what it was previously). + virtual const GURL& GetURL() const; + virtual const string16& GetTitle() const; + + // The max PageID of any page that this TabContents has loaded. PageIDs + // increase with each new page that is loaded by a tab. If this is a + // TabContents, then the max PageID is kept separately on each SiteInstance. + // Returns -1 if no PageIDs have yet been seen. + int32 GetMaxPageID(); + + // Updates the max PageID to be at least the given PageID. + void UpdateMaxPageID(int32 page_id); + + // Returns the site instance associated with the current page. By default, + // there is no site instance. TabContents overrides this to provide proper + // access to its site instance. + virtual SiteInstance* GetSiteInstance() const; + + // Defines whether this tab's URL should be displayed in the browser's URL + // bar. Normally this is true so you can see the URL. This is set to false + // for the new tab page and related pages so that the URL bar is empty and + // the user is invited to type into it. + virtual bool ShouldDisplayURL(); + + // Returns the favicon for this tab, or an isNull() bitmap if the tab does not + // have a favicon. The default implementation uses the current navigation + // entry. + SkBitmap GetFavIcon() const; + + // Returns true if we are not using the default favicon. + bool FavIconIsValid() const; + + // Returns whether the favicon should be displayed. If this returns false, no + // space is provided for the favicon, and the favicon is never displayed. + virtual bool ShouldDisplayFavIcon(); + + // Add and remove observers for page navigation notifications. Adding or + // removing multiple times has no effect. The order in which notifications + // are sent to observers is undefined. Clients must be sure to remove the + // observer before they go away. + void AddObserver(TabContentsObserver* observer); + void RemoveObserver(TabContentsObserver* observer); + + // Return whether this tab contents is loading a resource. + bool is_loading() const { return is_loading_; } + + // Returns whether this tab contents is waiting for a first-response for the + // main resource of the page. This controls whether the throbber state is + // "waiting" or "loading." + bool waiting_for_response() const { return waiting_for_response_; } + + net::LoadState load_state() const { return load_state_; } + string16 load_state_host() const { return load_state_host_; } + uint64 upload_size() const { return upload_size_; } + uint64 upload_position() const { return upload_position_; } + + const std::string& encoding() const { return encoding_; } + void set_encoding(const std::string& encoding); + void reset_encoding() { + encoding_.clear(); + } + + const WebApplicationInfo& web_app_info() const { + return web_app_info_; + } + + const SkBitmap& app_icon() const { return app_icon_; } + + // Sets an app icon associated with TabContents and fires an INVALIDATE_TITLE + // navigation state change to trigger repaint of title. + void SetAppIcon(const SkBitmap& app_icon); + + bool displayed_insecure_content() const { + return displayed_insecure_content_; + } + + // Internal state ------------------------------------------------------------ + + // This flag indicates whether the tab contents is currently being + // screenshotted by the DraggedTabController. + bool capturing_contents() const { return capturing_contents_; } + void set_capturing_contents(bool cap) { capturing_contents_ = cap; } + + // Indicates whether this tab should be considered crashed. The setter will + // also notify the delegate when the flag is changed. + bool is_crashed() const { + return (crashed_status_ == base::TERMINATION_STATUS_PROCESS_CRASHED || + crashed_status_ == base::TERMINATION_STATUS_ABNORMAL_TERMINATION || + crashed_status_ == base::TERMINATION_STATUS_PROCESS_WAS_KILLED); + } + base::TerminationStatus crashed_status() const { return crashed_status_; } + int crashed_error_code() const { return crashed_error_code_; } + void SetIsCrashed(base::TerminationStatus status, int error_code); + + // Call this after updating a page action to notify clients about the changes. + void PageActionStateChanged(); + + // Whether the tab is in the process of being destroyed. + // Added as a tentative work-around for focus related bug #4633. This allows + // us not to store focus when a tab is being closed. + bool is_being_destroyed() const { return is_being_destroyed_; } + + // Convenience method for notifying the delegate of a navigation state + // change. See TabContentsDelegate. + void NotifyNavigationStateChanged(unsigned changed_flags); + + // Invoked when the tab contents becomes selected. If you override, be sure + // and invoke super's implementation. + virtual void DidBecomeSelected(); + base::TimeTicks last_selected_time() const { + return last_selected_time_; + } + + // Invoked when the tab contents becomes hidden. + // NOTE: If you override this, call the superclass version too! + virtual void WasHidden(); + + // Activates this contents within its containing window, bringing that window + // to the foreground if necessary. + void Activate(); + + // Deactivates this contents by deactivating its containing window. + void Deactivate(); + + // TODO(brettw) document these. + virtual void ShowContents(); + virtual void HideContents(); + + // Returns true if the before unload and unload listeners need to be + // fired. The value of this changes over time. For example, if true and the + // before unload listener is executed and allows the user to exit, then this + // returns false. + bool NeedToFireBeforeUnload(); + +#ifdef UNIT_TEST + // Expose the render manager for testing. + RenderViewHostManager* render_manager() { return &render_manager_; } +#endif + + // In the underlying RenderViewHostManager, swaps in the provided + // RenderViewHost to replace the current RenderViewHost. The current RVH + // will be shutdown and ultimately deleted. + void SwapInRenderViewHost(RenderViewHost* rvh); + + // Commands ------------------------------------------------------------------ + + // Implementation of PageNavigator. + virtual void OpenURL(const GURL& url, const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition); + + // Called by the NavigationController to cause the TabContents to navigate to + // the current pending entry. The NavigationController should be called back + // with CommitPendingEntry/RendererDidNavigate on success or + // DiscardPendingEntry. The callbacks can be inside of this function, or at + // some future time. + // + // The entry has a PageID of -1 if newly created (corresponding to navigation + // to a new URL). + // + // If this method returns false, then the navigation is discarded (equivalent + // to calling DiscardPendingEntry on the NavigationController). + virtual bool NavigateToPendingEntry( + NavigationController::ReloadType reload_type); + + // Stop any pending navigation. + virtual void Stop(); + + // Called on a TabContents when it isn't a popup, but a new window. + virtual void DisassociateFromPopupCount(); + + // Creates a new TabContents with the same state as this one. The returned + // heap-allocated pointer is owned by the caller. + virtual TabContents* Clone(); + + // Shows the page info. + void ShowPageInfo(const GURL& url, + const NavigationEntry::SSLStatus& ssl, + bool show_history); + + // Saves the favicon for the current page. + void SaveFavicon(); + + // Window management --------------------------------------------------------- + + // Create a new window constrained to this TabContents' clip and visibility. + // The window is initialized by using the supplied delegate to obtain basic + // window characteristics, and the supplied view for the content. Note that + // the returned ConstrainedWindow might not yet be visible. + ConstrainedWindow* CreateConstrainedDialog( + ConstrainedWindowDelegate* delegate); + + // Adds a new tab or window with the given already-created contents + void AddNewContents(TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture); + + // Execute code in this tab. Returns true if the message was successfully + // sent. + bool ExecuteCode(int request_id, const std::string& extension_id, + bool is_js_code, const std::string& code_string, + bool all_frames); + + // Called when the blocked popup notification is shown or hidden. + virtual void PopupNotificationVisibilityChanged(bool visible); + + // Returns the number of constrained windows in this tab. Used by tests. + size_t constrained_window_count() { return child_windows_.size(); } + + typedef std::deque<ConstrainedWindow*> ConstrainedWindowList; + + // Return an iterator for the first constrained window in this tab contents. + ConstrainedWindowList::iterator constrained_window_begin() + { return child_windows_.begin(); } + + // Return an iterator for the last constrained window in this tab contents. + ConstrainedWindowList::iterator constrained_window_end() + { return child_windows_.end(); } + + // Views and focus ----------------------------------------------------------- + // TODO(brettw): Most of these should be removed and the caller should call + // the view directly. + + // Returns the actual window that is focused when this TabContents is shown. + gfx::NativeView GetContentNativeView() const; + + // Returns the NativeView associated with this TabContents. Outside of + // automation in the context of the UI, this is required to be implemented. + gfx::NativeView GetNativeView() const; + + // Returns the bounds of this TabContents in the screen coordinate system. + void GetContainerBounds(gfx::Rect *out) const; + + // Makes the tab the focused window. + void Focus(); + + // Focuses the first (last if |reverse| is true) element in the page. + // Invoked when this tab is getting the focus through tab traversal (|reverse| + // is true when using Shift-Tab). + void FocusThroughTabTraversal(bool reverse); + + // These next two functions are declared on RenderViewHostManager::Delegate + // but also accessed directly by other callers. + + // 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(); + + // Focuses the location bar. + virtual void SetFocusToLocationBar(bool select_all); + + // Creates a view and sets the size for the specified RVH. + virtual void CreateViewAndSetSizeForRVH(RenderViewHost* rvh); + + // Infobars ------------------------------------------------------------------ + + // Adds an InfoBar for the specified |delegate|. + virtual void AddInfoBar(InfoBarDelegate* delegate); + + // Removes the InfoBar for the specified |delegate|. + void RemoveInfoBar(InfoBarDelegate* delegate); + + // Replaces one infobar with another, without any animation in between. + void ReplaceInfoBar(InfoBarDelegate* old_delegate, + InfoBarDelegate* new_delegate); + + // Enumeration and access functions. + size_t infobar_count() const { return infobar_delegates_.size(); } + // WARNING: This does not sanity-check |index|! + InfoBarDelegate* GetInfoBarDelegateAt(size_t index) { + return infobar_delegates_[index]; + } + + // Toolbars and such --------------------------------------------------------- + + // Returns true if a Bookmark Bar should be shown for this tab. + virtual bool ShouldShowBookmarkBar(); + + // Notifies the delegate that a download is about to be started. + // This notification is fired before a local temporary file has been created. + bool CanDownload(int request_id); + + // Notifies the delegate that a download started. + void OnStartDownload(DownloadItem* download); + + // Notify our delegate that some of our content has animated. + void ToolbarSizeChanged(bool is_animating); + + // Called when a ConstrainedWindow we own is about to be closed. + void WillClose(ConstrainedWindow* window); + + // Called when a BlockedContentContainer we own is about to be closed. + void WillCloseBlockedContentContainer(BlockedContentContainer* container); + + // Called when a ConstrainedWindow we own is moved or resized. + void DidMoveOrResize(ConstrainedWindow* window); + + // Interstitials ------------------------------------------------------------- + + // Various other systems need to know about our interstitials. + bool showing_interstitial_page() const { + return render_manager_.interstitial_page() != NULL; + } + + // 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(InterstitialPage* interstitial_page) { + render_manager_.set_interstitial_page(interstitial_page); + } + + // Unsets the currently showing interstitial. + void remove_interstitial_page() { + render_manager_.remove_interstitial_page(); + } + + // Returns the currently showing interstitial, NULL if no interstitial is + // showing. + InterstitialPage* interstitial_page() const { + return render_manager_.interstitial_page(); + } + + // Misc state & callbacks ---------------------------------------------------- + + // Set whether the contents should block javascript message boxes or not. + // Default is not to block any message boxes. + void set_suppress_javascript_messages(bool suppress_javascript_messages) { + suppress_javascript_messages_ = suppress_javascript_messages; + } + + // Prepare for saving the current web page to disk. + void OnSavePage(); + + // Save page with the main HTML file path, the directory for saving resources, + // and the save type: HTML only or complete web page. Returns true if the + // saving process has been initiated successfully. + bool SavePage(const FilePath& main_file, const FilePath& dir_path, + SavePackage::SavePackageType save_type); + + // Tells the user's email client to open a compose window containing the + // current page's URL. + void EmailPageLocation(); + + // Displays asynchronously a print preview (generated by the renderer) if not + // already displayed and ask the user for its preferred print settings with + // the "Print..." dialog box. (managed by the print worker thread). + // TODO(maruel): Creates a snapshot of the renderer to be used for the new + // tab for the printing facility. + void PrintPreview(); + + // Prints the current document immediately. Since the rendering is + // asynchronous, the actual printing will not be completed on the return of + // this function. Returns false if printing is impossible at the moment. + bool PrintNow(); + + // Notify the completion of a printing job. + void PrintingDone(int document_cookie, bool success); + + // Returns true if the active NavigationEntry's page_id equals page_id. + bool IsActiveEntry(int32 page_id); + + const std::string& contents_mime_type() const { + return contents_mime_type_; + } + + // Returns true if this TabContents will notify about disconnection. + bool notify_disconnection() const { return notify_disconnection_; } + + // Override the encoding and reload the page by sending down + // ViewMsg_SetPageEncoding to the renderer. |UpdateEncoding| is kinda + // the opposite of this, by which 'browser' is notified of + // the encoding of the current tab from 'renderer' (determined by + // auto-detect, http header, meta, bom detection, etc). + void SetOverrideEncoding(const std::string& encoding); + + // Remove any user-defined override encoding and reload by sending down + // ViewMsg_ResetPageEncodingToDefault to the renderer. + void ResetOverrideEncoding(); + + void WindowMoveOrResizeStarted(); + + // Sets whether all TabContents added by way of |AddNewContents| should be + // blocked. Transitioning from all blocked to not all blocked results in + // reevaluating any blocked TabContents, which may result in unblocking some + // of the blocked TabContents. + void SetAllContentsBlocked(bool value); + + BlockedContentContainer* blocked_content_container() const { + return blocked_contents_; + } + + RendererPreferences* GetMutableRendererPrefs() { + return &renderer_preferences_; + } + + void set_opener_web_ui_type(WebUITypeID opener_web_ui_type) { + opener_web_ui_type_ = opener_web_ui_type; + } + + // We want to time how long it takes to create a new tab page. This method + // gets called as parts of the new tab page have loaded. + void LogNewTabTime(const std::string& event_name); + + // Set the time when we started to create the new tab page. This time is + // from before we created this TabContents. + void set_new_tab_start_time(const base::TimeTicks& time) { + new_tab_start_time_ = time; + } + + // Notification that tab closing has started. This can be called multiple + // times, subsequent calls are ignored. + void OnCloseStarted(); + + LanguageState& language_state() { + return language_state_; + } + + // Returns true if underlying TabContentsView should accept drag-n-drop. + bool ShouldAcceptDragAndDrop() const; + + // A render view-originated drag has ended. Informs the render view host and + // tab contents delegate. + void SystemDragEnded(); + + // Indicates if this tab was explicitly closed by the user (control-w, close + // tab menu item...). This is false for actions that indirectly close the tab, + // such as closing the window. The setter is maintained by TabStripModel, and + // the getter only useful from within TAB_CLOSED notification + void set_closed_by_user_gesture(bool value) { + closed_by_user_gesture_ = value; + } + bool closed_by_user_gesture() const { return closed_by_user_gesture_; } + + // Overridden from JavaScriptAppModalDialogDelegate: + virtual void OnMessageBoxClosed(IPC::Message* reply_msg, + bool success, + const std::wstring& prompt); + virtual void SetSuppressMessageBoxes(bool suppress_message_boxes); + virtual gfx::NativeWindow GetMessageBoxRootWindow(); + virtual TabContents* AsTabContents(); + virtual ExtensionHost* AsExtensionHost(); + + // The BookmarkDragDelegate is used to forward bookmark drag and drop events + // to extensions. + virtual RenderViewHostDelegate::BookmarkDrag* GetBookmarkDragDelegate(); + + // It is up to callers to call SetBookmarkDragDelegate(NULL) when + // |bookmark_drag| is deleted since this class does not take ownership of + // |bookmark_drag|. + virtual void SetBookmarkDragDelegate( + RenderViewHostDelegate::BookmarkDrag* bookmark_drag); + + // The TabSpecificContentSettings object is used to query the blocked content + // state by various UI elements. + TabSpecificContentSettings* GetTabSpecificContentSettings() const; + + // Updates history with the specified navigation. This is called by + // OnMsgNavigate to update history state. + void UpdateHistoryForNavigation( + scoped_refptr<history::HistoryAddPageArgs> add_page_args); + + // Sends the page title to the history service. This is called when we receive + // the page title and we know we want to update history. + void UpdateHistoryPageTitle(const NavigationEntry& entry); + + // Gets the zoom level for this tab. + double GetZoomLevel() const; + + // Gets the zoom percent for this tab. + int GetZoomPercent(bool* enable_increment, bool* enable_decrement); + + // Shows a fade effect over this tab contents. Repeated calls will be ignored + // until the fade is canceled. If |animate| is true the fade should animate. + void FadeForInstant(bool animate); + + // Immediately removes the fade. + void CancelInstantFade(); + + // Opens view-source tab for this contents. + void ViewSource(); + + // Gets the minimum/maximum zoom percent. + int minimum_zoom_percent() const { return minimum_zoom_percent_; } + int maximum_zoom_percent() const { return maximum_zoom_percent_; } + + int content_restrictions() const { return content_restrictions_; } + + AutocompleteHistoryManager* autocomplete_history_manager() { + return autocomplete_history_manager_.get(); + } + AutoFillManager* autofill_manager() { return autofill_manager_.get(); } + + safe_browsing::ClientSideDetectionHost* safebrowsing_detection_host() { + return safebrowsing_detection_host_.get(); + } + + protected: + // from RenderViewHostDelegate. + virtual bool OnMessageReceived(const IPC::Message& message); + + private: + friend class NavigationController; + // Used to access the child_windows_ (ConstrainedWindowList) for testing + // automation purposes. + friend class TestingAutomationProvider; + + FRIEND_TEST_ALL_PREFIXES(TabContentsTest, NoJSMessageOnInterstitials); + FRIEND_TEST_ALL_PREFIXES(TabContentsTest, UpdateTitle); + FRIEND_TEST_ALL_PREFIXES(TabContentsTest, CrossSiteCantPreemptAfterUnload); + FRIEND_TEST_ALL_PREFIXES(FormStructureBrowserTest, HTMLFiles); + FRIEND_TEST_ALL_PREFIXES(NavigationControllerTest, HistoryNavigate); + FRIEND_TEST_ALL_PREFIXES(RenderViewHostManagerTest, PageDoesBackAndReload); + + // Temporary until the view/contents separation is complete. + friend class TabContentsView; +#if defined(OS_WIN) + friend class TabContentsViewWin; +#elif defined(OS_MACOSX) + friend class TabContentsViewMac; +#elif defined(TOOLKIT_USES_GTK) + friend class TabContentsViewGtk; +#endif + + // So InterstitialPage can access SetIsLoading. + friend class InterstitialPage; + + // TODO(brettw) TestTabContents shouldn't exist! + friend class TestTabContents; + + // Used to access the CreateHistoryAddPageArgs member function. + friend class ExternalTabContainer; + + // Used to access RVH Delegates. + friend class prerender::PrerenderManager; + + // Add all the TabContentObservers. + void AddObservers(); + + // Message handlers. + void OnDidStartProvisionalLoadForFrame(int64 frame_id, + bool main_frame, + const GURL& url); + void OnDidRedirectProvisionalLoad(int32 page_id, + const GURL& source_url, + const GURL& target_url); + void OnDidFailProvisionalLoadWithError(int64 frame_id, + bool main_frame, + int error_code, + const GURL& url, + bool showing_repost_interstitial); + void OnDidLoadResourceFromMemoryCache(const GURL& url, + const std::string& security_info); + void OnDidDisplayInsecureContent(); + void OnDidRunInsecureContent(const std::string& security_origin, + const GURL& target_url); + void OnDocumentLoadedInFrame(int64 frame_id); + void OnDidFinishLoad(int64 frame_id); + void OnUpdateContentRestrictions(int restrictions); + void OnPDFHasUnsupportedFeature(); + + void OnGoToEntryAtOffset(int offset); + void OnDidGetApplicationInfo(int32 page_id, const WebApplicationInfo& info); + void OnInstallApplication(const WebApplicationInfo& info); + void OnPageContents(const GURL& url, + int32 page_id, + const string16& contents, + const std::string& language, + bool page_translatable); + void OnPageTranslated(int32 page_id, + const std::string& original_lang, + const std::string& translated_lang, + TranslateErrors::Type error_type); + void OnSetSuggestions(int32 page_id, + const std::vector<std::string>& suggestions); + void OnInstantSupportDetermined(int32 page_id, bool result); + void OnRunFileChooser(const ViewHostMsg_RunFileChooser_Params& params); + + // Changes the IsLoading state and notifies delegate as needed + // |details| is used to provide details on the load that just finished + // (but can be null if not applicable). Can be overridden. + void SetIsLoading(bool is_loading, + LoadNotificationDetails* details); + + // Adds the incoming |new_contents| to the |blocked_contents_| container. + void AddPopup(TabContents* new_contents, + const gfx::Rect& initial_pos); + + // Called by derived classes to indicate that we're no longer waiting for a + // response. This won't actually update the throbber, but it will get picked + // up at the next animation step if the throbber is going. + void SetNotWaitingForResponse() { waiting_for_response_ = false; } + + ConstrainedWindowList child_windows_; + + // Expires InfoBars that need to be expired, according to the state carried + // in |details|, in response to a new NavigationEntry being committed (the + // user navigated to another page). + void ExpireInfoBars( + const NavigationController::LoadCommittedDetails& details); + + // Returns the WebUI for the current state of the tab. This will either be + // the pending WebUI, the committed WebUI, or NULL. + WebUI* GetWebUIForCurrentState(); + + // Navigation helpers -------------------------------------------------------- + // + // These functions are helpers for Navigate() and DidNavigate(). + + // Handles post-navigation tasks in DidNavigate AFTER the entry has been + // committed to the navigation controller. Note that the navigation entry is + // not provided since it may be invalid/changed after being committed. The + // current navigation entry is in the NavigationController at this point. + void DidNavigateMainFramePostCommit( + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params); + void DidNavigateAnyFramePostCommit( + RenderViewHost* render_view_host, + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params); + + // Closes all constrained windows. + void CloseConstrainedWindows(); + + // Send the alternate error page URL to the renderer. This method is virtual + // so special html pages can override this (e.g., the new tab page). + virtual void UpdateAlternateErrorPageURL(); + + // Send webkit specific settings to the renderer. + void UpdateWebPreferences(); + + // Instruct the renderer to update the zoom level. + void UpdateZoomLevel(); + + // If our controller was restored and the page id is > than the site + // instance's page id, the site instances page id is updated as well as the + // renderers max page id. + void UpdateMaxPageIDIfNecessary(SiteInstance* site_instance, + RenderViewHost* rvh); + + // Returns the history::HistoryAddPageArgs to use for adding a page to + // history. + scoped_refptr<history::HistoryAddPageArgs> CreateHistoryAddPageArgs( + const GURL& virtual_url, + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params); + + // Saves the given title to the navigation entry and does associated work. It + // will update history and the view for the new title, and also synthesize + // titles for file URLs that have none (so we require that the URL of the + // entry already be set). + // + // This is used as the backend for state updates, which include a new title, + // or the dedicated set title message. It returns true if the new title is + // different and was therefore updated. + bool UpdateTitleForEntry(NavigationEntry* entry, const std::wstring& title); + + // Causes the TabContents to navigate in the right renderer to |entry|, which + // must be already part of the entries in the navigation controller. + // This does not change the NavigationController state. + bool NavigateToEntry(const NavigationEntry& entry, + NavigationController::ReloadType reload_type); + + // Misc non-view stuff ------------------------------------------------------- + + // Helper functions for sending notifications. + void NotifySwapped(); + void NotifyConnected(); + void NotifyDisconnected(); + + // TabSpecificContentSettings::Delegate implementation. + virtual void OnContentSettingsAccessed(bool content_was_blocked); + + // RenderViewHostDelegate ---------------------------------------------------- + + // RenderViewHostDelegate implementation. + virtual RenderViewHostDelegate::View* GetViewDelegate(); + virtual RenderViewHostDelegate::RendererManagement* + GetRendererManagementDelegate(); + virtual RenderViewHostDelegate::ContentSettings* GetContentSettingsDelegate(); + virtual RenderViewHostDelegate::SSL* GetSSLDelegate(); + virtual AutomationResourceRoutingDelegate* + GetAutomationResourceRoutingDelegate(); + virtual TabContents* GetAsTabContents(); + virtual ViewType::Type GetRenderViewType() const; + virtual int GetBrowserWindowID() const; + virtual void RenderViewCreated(RenderViewHost* render_view_host); + virtual void RenderViewReady(RenderViewHost* render_view_host); + virtual void RenderViewGone(RenderViewHost* render_view_host, + base::TerminationStatus status, + int error_code); + virtual void RenderViewDeleted(RenderViewHost* render_view_host); + virtual void DidNavigate(RenderViewHost* render_view_host, + const ViewHostMsg_FrameNavigate_Params& params); + virtual void UpdateState(RenderViewHost* render_view_host, + int32 page_id, + const std::string& state); + virtual void UpdateTitle(RenderViewHost* render_view_host, + int32 page_id, + const std::wstring& title); + virtual void UpdateEncoding(RenderViewHost* render_view_host, + const std::string& encoding); + virtual void UpdateTargetURL(int32 page_id, const GURL& url); + virtual void UpdateThumbnail(const GURL& url, + const SkBitmap& bitmap, + const ThumbnailScore& score); + virtual void UpdateInspectorSetting(const std::string& key, + const std::string& value); + virtual void ClearInspectorSettings(); + virtual void Close(RenderViewHost* render_view_host); + virtual void RequestMove(const gfx::Rect& new_bounds); + virtual void DidStartLoading(); + virtual void DidStopLoading(); + virtual void DidChangeLoadProgress(double progress); + virtual void DocumentOnLoadCompletedInMainFrame( + RenderViewHost* render_view_host, + int32 page_id); + virtual void RequestOpenURL(const GURL& url, const GURL& referrer, + WindowOpenDisposition disposition); + virtual void DomOperationResponse(const std::string& json_string, + int automation_id); + virtual void ProcessWebUIMessage(const ViewHostMsg_DomMessage_Params& params); + virtual void ProcessExternalHostMessage(const std::string& message, + const std::string& origin, + const std::string& target); + virtual void RunJavaScriptMessage(const std::wstring& message, + const std::wstring& default_prompt, + const GURL& frame_url, + const int flags, + IPC::Message* reply_msg, + bool* did_suppress_message); + virtual void RunBeforeUnloadConfirm(const std::wstring& message, + IPC::Message* reply_msg); + virtual void ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + IPC::Message* reply_msg); + virtual GURL GetAlternateErrorPageURL() const; + virtual RendererPreferences GetRendererPrefs(Profile* profile) const; + virtual WebPreferences GetWebkitPrefs(); + virtual void OnUserGesture(); + virtual void OnIgnoredUIEvent(); + virtual void OnCrossSiteResponse(int new_render_process_host_id, + int new_request_id); + virtual void RendererUnresponsive(RenderViewHost* render_view_host, + bool is_during_unload); + virtual void RendererResponsive(RenderViewHost* render_view_host); + virtual void LoadStateChanged(const GURL& url, net::LoadState load_state, + uint64 upload_position, uint64 upload_size); + virtual bool IsExternalTabContainer() const; + virtual void DidInsertCSS(); + virtual void FocusedNodeChanged(bool is_editable_node); + virtual void UpdateZoomLimits(int minimum_percent, + int maximum_percent, + bool remember); + virtual void WorkerCrashed(); + + // RenderViewHostManager::Delegate ------------------------------------------- + + // Blocks/unblocks interaction with renderer process. + void BlockTabContent(bool blocked); + + virtual void BeforeUnloadFiredFromRenderManager( + bool proceed, + bool* proceed_to_fire_unload); + virtual void DidStartLoadingFromRenderManager( + RenderViewHost* render_view_host); + virtual void RenderViewGoneFromRenderManager( + RenderViewHost* render_view_host); + virtual void UpdateRenderViewSizeForRenderManager(); + virtual void NotifySwappedFromRenderManager(); + virtual NavigationController& GetControllerForRenderManager(); + virtual WebUI* CreateWebUIForRenderManager(const GURL& url); + virtual NavigationEntry* GetLastCommittedNavigationEntryForRenderManager(); + + // 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 TabContents 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); + + // NotificationObserver ------------------------------------------------------ + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // App extensions related methods: + + // Returns the first extension whose extent contains |url|. + const Extension* GetExtensionContaining(const GURL& url); + + // Resets app_icon_ and if |extension| is non-null creates a new + // ImageLoadingTracker to load the extension's image. + void UpdateExtensionAppIcon(const Extension* extension); + + // ImageLoadingTracker::Observer. + virtual void OnImageLoaded(SkBitmap* image, ExtensionResource resource, + int index); + + // Checks with the PrerenderManager if the specified URL has been preloaded, + // and if so, swap the RenderViewHost with the preload into this TabContents + // object. + bool MaybeUsePreloadedPage(const GURL& url); + + // Data for core operation --------------------------------------------------- + + // Delegate for notifying our owner about stuff. Not owned by us. + TabContentsDelegate* delegate_; + + // Handles the back/forward list and loading. + NavigationController controller_; + + // The corresponding view. + scoped_ptr<TabContentsView> view_; + + // Helper classes ------------------------------------------------------------ + + // Manages creation and swapping of render views. + RenderViewHostManager render_manager_; + + // Stores random bits of data for others to associate with this object. + PropertyBag property_bag_; + + // Registers and unregisters us for notifications. + NotificationRegistrar registrar_; + + // Registers and unregisters for pref notifications. + PrefChangeRegistrar pref_change_registrar_; + + // Handles print job for this contents. + scoped_ptr<printing::PrintViewManager> printing_; + + // Handles print preview for this contents. + scoped_ptr<printing::PrintPreviewMessageHandler> print_preview_; + + // SavePackage, lazily created. + scoped_refptr<SavePackage> save_package_; + + // AutocompleteHistoryManager. + scoped_ptr<AutocompleteHistoryManager> autocomplete_history_manager_; + + // AutoFillManager. + scoped_ptr<AutoFillManager> autofill_manager_; + + // Handles plugin messages. + scoped_ptr<PluginObserver> plugin_observer_; + + // Prerender PageLoadTime Recorder. + scoped_ptr<prerender::PrerenderPLTRecorder> prerender_plt_recorder_; + + // TabContentsSSLHelper, lazily created. + scoped_ptr<TabContentsSSLHelper> ssl_helper_; + + // FileSelectHelper, lazily created. + scoped_ptr<FileSelectHelper> file_select_helper_; + + // Handles drag and drop event forwarding to extensions. + BookmarkDrag* bookmark_drag_; + + // Handles downloading favicons. + scoped_ptr<FavIconHelper> fav_icon_helper_; + + // Cached web app info data. + WebApplicationInfo web_app_info_; + + // Cached web app icon. + SkBitmap app_icon_; + + // RenderViewHost::ContentSettingsDelegate. + scoped_ptr<TabSpecificContentSettings> content_settings_delegate_; + + // Handles desktop notification IPCs. + scoped_ptr<DesktopNotificationHandlerForTC> desktop_notification_handler_; + + // Handles IPCs related to SafeBrowsing client-side phishing detection. + scoped_ptr<safe_browsing::ClientSideDetectionHost> + safebrowsing_detection_host_; + + // Data for loading state ---------------------------------------------------- + + // Indicates whether we're currently loading a resource. + bool is_loading_; + + // Indicates if the tab is considered crashed. + base::TerminationStatus crashed_status_; + int crashed_error_code_; + + // See waiting_for_response() above. + bool waiting_for_response_; + + // Indicates the largest PageID we've seen. This field is ignored if we are + // a TabContents, in which case the max page ID is stored separately with + // each SiteInstance. + // TODO(brettw) this seems like it can be removed according to the comment. + int32 max_page_id_; + + // System time at which the current load was started. + base::TimeTicks current_load_start_; + + // The current load state and the URL associated with it. + net::LoadState load_state_; + string16 load_state_host_; + // Upload progress, for displaying in the status bar. + // Set to zero when there is no significant upload happening. + uint64 upload_size_; + uint64 upload_position_; + + // Data for current page ----------------------------------------------------- + + // Whether we have a (non-empty) title for the current page. + // Used to prevent subsequent title updates from affecting history. This + // prevents some weirdness because some AJAXy apps use titles for status + // messages. + bool received_page_title_; + + // When a navigation occurs, we record its contents MIME type. It can be + // used to check whether we can do something for some special contents. + std::string contents_mime_type_; + + // Character encoding. + std::string encoding_; + + // Object that holds any blocked TabContents spawned from this TabContents. + BlockedContentContainer* blocked_contents_; + + // Should we block all child TabContents this attempts to spawn. + bool all_contents_blocked_; + + // TODO(pkasting): Hack to try and fix Linux browser tests. + bool dont_notify_render_view_; + + // True if this is a secure page which displayed insecure content. + bool displayed_insecure_content_; + + // Data for shelves and stuff ------------------------------------------------ + + // Delegates for InfoBars associated with this TabContents. + std::vector<InfoBarDelegate*> infobar_delegates_; + + // Data for app extensions --------------------------------------------------- + + // If non-null this tab is an app tab and this is the extension the tab was + // created for. + const Extension* extension_app_; + + // Icon for extension_app_ (if non-null) or extension_for_current_page_. + SkBitmap extension_app_icon_; + + // Used for loading extension_app_icon_. + scoped_ptr<ImageLoadingTracker> extension_app_image_loader_; + + // Data for misc internal state ---------------------------------------------- + + // See capturing_contents() above. + bool capturing_contents_; + + // See getter above. + bool is_being_destroyed_; + + // Indicates whether we should notify about disconnection of this + // TabContents. This is used to ensure disconnection notifications only + // happen if a connection notification has happened and that they happen only + // once. + bool notify_disconnection_; + + // Maps from handle to page_id. + typedef std::map<FaviconService::Handle, int32> HistoryRequestMap; + HistoryRequestMap history_requests_; + +#if defined(OS_WIN) + // Handle to an event that's set when the page is showing a message box (or + // equivalent constrained window). Plugin processes check this to know if + // they should pump messages then. + base::win::ScopedHandle message_box_active_; +#endif + + // The time that the last javascript message was dismissed. + base::TimeTicks last_javascript_message_dismissal_; + + // True if the user has decided to block future javascript messages. This is + // reset on navigations to false on navigations. + bool suppress_javascript_messages_; + + // Set to true when there is an active "before unload" dialog. When true, + // we've forced the throbber to start in Navigate, and we need to remember to + // turn it off in OnJavaScriptMessageBoxClosed if the navigation is canceled. + bool is_showing_before_unload_dialog_; + + // Shows an info-bar to users when they search from a known search engine and + // have never used the monibox for search before. + scoped_ptr<OmniboxSearchHint> omnibox_search_hint_; + + // Settings that get passed to the renderer process. + RendererPreferences renderer_preferences_; + + // If this tab was created from a renderer using window.open, this will be + // non-NULL and represent the WebUI of the opening renderer. + WebUITypeID opener_web_ui_type_; + + // The time that we started to create the new tab page. + base::TimeTicks new_tab_start_time_; + + // The time that we started to close the tab. + base::TimeTicks tab_close_start_time_; + + // The time that this tab was last selected. + base::TimeTicks last_selected_time_; + + // Information about the language the page is in and has been translated to. + LanguageState language_state_; + + // See description above setter. + bool closed_by_user_gesture_; + + // Minimum/maximum zoom percent. + int minimum_zoom_percent_; + int maximum_zoom_percent_; + // If true, the default zoom limits have been overriden for this tab, in which + // case we don't want saved settings to apply to it and we don't want to + // remember it. + bool temporary_zoom_settings_; + + // A list of observers notified when page state changes. Weak references. + ObserverList<TabContentsObserver> observers_; + + // Content restrictions, used to disable print/copy etc based on content's + // (full-page plugins for now only) permissions. + int content_restrictions_; + + DISALLOW_COPY_AND_ASSIGN(TabContents); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_H_ diff --git a/content/browser/tab_contents/tab_contents_delegate.cc b/content/browser/tab_contents/tab_contents_delegate.cc new file mode 100644 index 0000000..0510adf --- /dev/null +++ b/content/browser/tab_contents/tab_contents_delegate.cc @@ -0,0 +1,227 @@ +// Copyright (c) 2011 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/tab_contents/tab_contents_delegate.h" + +#include "chrome/common/url_constants.h" +#include "ui/gfx/rect.h" + +std::string TabContentsDelegate::GetNavigationHeaders(const GURL& url) { + return std::string(); +} + +void TabContentsDelegate::LoadProgressChanged(double progress) { +} + +void TabContentsDelegate::DetachContents(TabContents* source) { +} + +bool TabContentsDelegate::IsPopup(const TabContents* source) const { + return false; +} + +TabContents* TabContentsDelegate::GetConstrainingContents(TabContents* source) { + return source; +} + +bool TabContentsDelegate::ShouldFocusConstrainedWindow() { + return true; +} + +void TabContentsDelegate::WillShowConstrainedWindow(TabContents* source) { +} + +void TabContentsDelegate::ContentsMouseEvent( + TabContents* source, const gfx::Point& location, bool motion) { +} + +void TabContentsDelegate::ContentsZoomChange(bool zoom_in) { } + +void TabContentsDelegate::OnContentSettingsChange(TabContents* source) { } + +bool TabContentsDelegate::IsApplication() const { return false; } + +void TabContentsDelegate::ConvertContentsToApplication(TabContents* source) { } + +bool TabContentsDelegate::CanReloadContents(TabContents* source) const { + return true; +} + +void TabContentsDelegate::ShowHtmlDialog(HtmlDialogUIDelegate* delegate, + gfx::NativeWindow parent_window) { +} + +void TabContentsDelegate::WillRunBeforeUnloadConfirm() { +} + +bool TabContentsDelegate::ShouldSuppressDialogs() { + return false; +} + +void TabContentsDelegate::BeforeUnloadFired(TabContents* tab, + bool proceed, + bool* proceed_to_fire_unload) { + *proceed_to_fire_unload = true; +} + +void TabContentsDelegate::ForwardMessageToExternalHost( + const std::string& message, + const std::string& origin, + const std::string& target) { +} + +bool TabContentsDelegate::IsExternalTabContainer() const { return false; } + +void TabContentsDelegate::SetFocusToLocationBar(bool select_all) {} + +bool TabContentsDelegate::ShouldFocusPageAfterCrash() { + return true; +} + +void TabContentsDelegate::RenderWidgetShowing() {} + +bool TabContentsDelegate::TakeFocus(bool reverse) { + return false; +} + +void TabContentsDelegate::LostCapture() { +} + +void TabContentsDelegate::SetTabContentBlocked( + TabContents* contents, bool blocked) { +} + +void TabContentsDelegate::TabContentsFocused(TabContents* tab_content) { +} + +int TabContentsDelegate::GetExtraRenderViewHeight() const { + return 0; +} + +bool TabContentsDelegate::CanDownload(int request_id) { + return true; +} + +void TabContentsDelegate::OnStartDownload(DownloadItem* download, + TabContents* tab) { +} + +bool TabContentsDelegate::HandleContextMenu(const ContextMenuParams& params) { + return false; +} + +bool TabContentsDelegate::ExecuteContextMenuCommand(int command) { + return false; +} + +void TabContentsDelegate::ShowPageInfo(Profile* profile, + const GURL& url, + const NavigationEntry::SSLStatus& ssl, + bool show_history) { +} + +void TabContentsDelegate::ViewSourceForTab(TabContents* source, + const GURL& page_url) { + // Fall back implementation based entirely on the view-source scheme. + // It suffers from http://crbug.com/523 and that is why browser overrides + // it with proper implementation. + GURL url = GURL(chrome::kViewSourceScheme + std::string(":") + + page_url.spec()); + OpenURLFromTab(source, + url, + GURL(), + NEW_FOREGROUND_TAB, + PageTransition::LINK); +} + +bool TabContentsDelegate::PreHandleKeyboardEvent( + const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) { + return false; +} + +void TabContentsDelegate::HandleKeyboardEvent( + const NativeWebKeyboardEvent& event) { +} + +void TabContentsDelegate::HandleMouseUp() { +} + +void TabContentsDelegate::HandleMouseActivate() { +} + +void TabContentsDelegate::DragEnded() { +} + +void TabContentsDelegate::ShowRepostFormWarningDialog( + TabContents* tab_contents) { +} + +void TabContentsDelegate::ShowContentSettingsWindow( + ContentSettingsType content_type) { +} + +void TabContentsDelegate::ShowCollectedCookiesDialog( + TabContents* tab_contents) { +} + +bool TabContentsDelegate::OnGoToEntryOffset(int offset) { + return true; +} + +bool TabContentsDelegate::ShouldAddNavigationToHistory( + const history::HistoryAddPageArgs& add_page_args, + NavigationType::Type navigation_type) { + return true; +} + +void TabContentsDelegate::OnDidGetApplicationInfo(TabContents* tab_contents, + int32 page_id) { +} + +// Notification when an application programmatically requests installation. +void TabContentsDelegate::OnInstallApplication( + TabContents* tab_contents, + const WebApplicationInfo& app_info) { +} + +gfx::NativeWindow TabContentsDelegate::GetFrameNativeWindow() { + return NULL; +} + +void TabContentsDelegate::TabContentsCreated(TabContents* new_contents) { +} + +bool TabContentsDelegate::infobars_enabled() { + return true; +} + +bool TabContentsDelegate::ShouldEnablePreferredSizeNotifications() { + return false; +} + +void TabContentsDelegate::UpdatePreferredSize(const gfx::Size& pref_size) { +} + +void TabContentsDelegate::OnSetSuggestions( + int32 page_id, + const std::vector<std::string>& suggestions) { +} + +void TabContentsDelegate::OnInstantSupportDetermined(int32 page_id, + bool result) { +} + +void TabContentsDelegate::ContentRestrictionsChanged(TabContents* source) { +} + +bool TabContentsDelegate::ShouldShowHungRendererDialog() { + return true; +} + +void TabContentsDelegate::WorkerCrashed() { +} + +TabContentsDelegate::~TabContentsDelegate() { +} diff --git a/content/browser/tab_contents/tab_contents_delegate.h b/content/browser/tab_contents/tab_contents_delegate.h new file mode 100644 index 0000000..8d2805b --- /dev/null +++ b/content/browser/tab_contents/tab_contents_delegate.h @@ -0,0 +1,331 @@ +// Copyright (c) 2011 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_TAB_CONTENTS_TAB_CONTENTS_DELEGATE_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_DELEGATE_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "chrome/browser/automation/automation_resource_routing_delegate.h" +#include "chrome/common/content_settings_types.h" +#include "chrome/common/navigation_types.h" +#include "chrome/common/page_transition_types.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "ui/gfx/native_widget_types.h" +#include "webkit/glue/window_open_disposition.h" + +namespace gfx { +class Point; +class Rect; +class Size; +} + +namespace history { +class HistoryAddPageArgs; +} + +struct ContextMenuParams; +class DownloadItem; +class GURL; +class HtmlDialogUIDelegate; +struct NativeWebKeyboardEvent; +class Profile; +class RenderViewHost; +class TabContents; +struct WebApplicationInfo; + +// Objects implement this interface to get notified about changes in the +// TabContents and to provide necessary functionality. +class TabContentsDelegate : public AutomationResourceRoutingDelegate { + public: + // Opens a new URL inside the passed in TabContents (if source is 0 open + // in the current front-most tab), unless |disposition| indicates the url + // should be opened in a new tab or window. + // + // A NULL source indicates the current tab (callers should probably use + // OpenURL() for these cases which does it for you). + virtual void OpenURLFromTab(TabContents* source, + const GURL& url, const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition) = 0; + + // Called to inform the delegate that the tab content's navigation state + // changed. The |changed_flags| indicates the parts of the navigation state + // that have been updated, and is any combination of the + // |TabContents::InvalidateTypes| bits. + virtual void NavigationStateChanged(const TabContents* source, + unsigned changed_flags) = 0; + + // Returns the set of headers to add to the navigation request. Use + // net::HttpUtil::AppendHeaderIfMissing to build the set of headers. + virtual std::string GetNavigationHeaders(const GURL& url); + + // Creates a new tab with the already-created TabContents 'new_contents'. + // The window for the added contents should be reparented correctly when this + // method returns. If |disposition| is NEW_POPUP, |pos| should hold the + // initial position. + virtual void AddNewContents(TabContents* source, + TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) = 0; + + // Selects the specified contents, bringing its container to the front. + virtual void ActivateContents(TabContents* contents) = 0; + + // Deactivates the specified contents by deactivating its container and + // potentialy moving it to the back of the Z order. + virtual void DeactivateContents(TabContents* contents) = 0; + + // Notifies the delegate that this contents is starting or is done loading + // some resource. The delegate should use this notification to represent + // loading feedback. See TabContents::is_loading() + virtual void LoadingStateChanged(TabContents* source) = 0; + + // Notifies the delegate that the page has made some progress loading. + // |progress| is a value between 0.0 (nothing loaded) to 1.0 (page fully + // loaded). + // Note that to receive this notification, you must have called + // SetReportLoadProgressEnabled(true) in the render view. + virtual void LoadProgressChanged(double progress); + + // Request the delegate to close this tab contents, and do whatever cleanup + // it needs to do. + virtual void CloseContents(TabContents* source) = 0; + + // Request the delegate to move this tab contents to the specified position + // in screen coordinates. + virtual void MoveContents(TabContents* source, const gfx::Rect& pos) = 0; + + // Causes the delegate to detach |source| and clean up any internal data + // pointing to it. After this call ownership of |source| passes to the + // caller, and it is safe to call "source->set_delegate(someone_else);". + virtual void DetachContents(TabContents* source); + + // Called to determine if the TabContents is contained in a popup window. + virtual bool IsPopup(const TabContents* source) const; + + // If |source| is constrained, returns the tab containing it. Otherwise + // returns |source|. + virtual TabContents* GetConstrainingContents(TabContents* source); + + // Returns true if constrained windows should be focused. Default is true. + virtual bool ShouldFocusConstrainedWindow(); + + // Invoked prior to the TabContents showing a constrained window. + virtual void WillShowConstrainedWindow(TabContents* source); + + // Notification that some of our content has changed size as + // part of an animation. + virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) = 0; + + // Notification that the target URL has changed. + virtual void UpdateTargetURL(TabContents* source, const GURL& url) = 0; + + // Notification that there was a mouse event, along with the absolute + // coordinates of the mouse pointer and whether it was a normal motion event + // (otherwise, the pointer left the contents area). + virtual void ContentsMouseEvent( + TabContents* source, const gfx::Point& location, bool motion); + + // Request the delegate to change the zoom level of the current tab. + virtual void ContentsZoomChange(bool zoom_in); + + // Notifies the delegate that something has changed about what content the + // TabContents is blocking. Interested parties should call + // TabContents::IsContentBlocked() to see if something they care about has + // changed. + virtual void OnContentSettingsChange(TabContents* source); + + // Check whether this contents is inside a window dedicated to running a web + // application. + virtual bool IsApplication() const; + + // Detach the given tab and convert it to a "webapp" view. The tab must be + // a TabContents with a valid WebApp set. + virtual void ConvertContentsToApplication(TabContents* source); + + // Whether the specified tab can be reloaded. + // Reloading can be disabled e. g. for the DevTools window. + virtual bool CanReloadContents(TabContents* source) const; + + // Show a dialog with HTML content. |delegate| contains a pointer to the + // delegate who knows how to display the dialog (which file URL and JSON + // string input to use during initialization). |parent_window| is the window + // that should be parent of the dialog, or NULL for the default. + virtual void ShowHtmlDialog(HtmlDialogUIDelegate* delegate, + gfx::NativeWindow parent_window); + + // Invoked prior to showing before unload handler confirmation dialog. + virtual void WillRunBeforeUnloadConfirm(); + + // Returns true if javascript dialogs and unload alerts are suppressed. + // Default is false. + virtual bool ShouldSuppressDialogs(); + + // Tells us that we've finished firing this tab's beforeunload event. + // The proceed bool tells us whether the user chose to proceed closing the + // tab. Returns true if the tab can continue on firing it's unload event. + // If we're closing the entire browser, then we'll want to delay firing + // unload events until all the beforeunload events have fired. + virtual void BeforeUnloadFired(TabContents* tab, + bool proceed, + bool* proceed_to_fire_unload); + + // Send IPC to external host. Default implementation is do nothing. + virtual void ForwardMessageToExternalHost(const std::string& message, + const std::string& origin, + const std::string& target); + + // If the delegate is hosting tabs externally. + virtual bool IsExternalTabContainer() const; + + // Sets focus to the location bar or some other place that is appropriate. + // This is called when the tab wants to encourage user input, like for the + // new tab page. + virtual void SetFocusToLocationBar(bool select_all); + + // Returns whether the page should be focused when transitioning from crashed + // to live. Default is true. + virtual bool ShouldFocusPageAfterCrash(); + + // Called when a popup select is about to be displayed. The delegate can use + // this to disable inactive rendering for the frame in the window the select + // is opened within if necessary. + virtual void RenderWidgetShowing(); + + // This is called when WebKit tells us that it is done tabbing through + // controls on the page. Provides a way for TabContentsDelegates to handle + // this. Returns true if the delegate successfully handled it. + virtual bool TakeFocus(bool reverse); + + // Invoked when the page loses mouse capture. + virtual void LostCapture(); + + // Changes the blocked state of the tab at |index|. TabContents are + // considered blocked while displaying a tab modal dialog. During that time + // renderer host will ignore any UI interaction within TabContent outside of + // the currently displaying dialog. + virtual void SetTabContentBlocked(TabContents* contents, bool blocked); + + // Notification that |tab_contents| has gained focus. + virtual void TabContentsFocused(TabContents* tab_content); + + // Return much extra vertical space should be allotted to the + // render view widget during various animations (e.g. infobar closing). + // This is used to make painting look smoother. + virtual int GetExtraRenderViewHeight() const; + + virtual bool CanDownload(int request_id); + + virtual void OnStartDownload(DownloadItem* download, TabContents* tab); + + // Returns true if the context menu operation was handled by the delegate. + virtual bool HandleContextMenu(const ContextMenuParams& params); + + // Returns true if the context menu command was handled + virtual bool ExecuteContextMenuCommand(int command); + + // Shows the page info using the specified information. + // |url| is the url of the page/frame the info applies to, |ssl| is the SSL + // information for that page/frame. If |show_history| is true, a section + // showing how many times that URL has been visited is added to the page info. + virtual void ShowPageInfo(Profile* profile, + const GURL& url, + const NavigationEntry::SSLStatus& ssl, + bool show_history); + + // Opens source view for given tab contents that is navigated to the given + // page url. + virtual void ViewSourceForTab(TabContents* source, const GURL& page_url); + + // Allows delegates to handle keyboard events before sending to the renderer. + // Returns true if the |event| was handled. Otherwise, if the |event| would be + // handled in HandleKeyboardEvent() method as a normal keyboard shortcut, + // |*is_keyboard_shortcut| should be set to true. + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut); + + // Allows delegates to handle unhandled keyboard messages coming back from + // the renderer. + virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event); + + virtual void HandleMouseUp(); + virtual void HandleMouseActivate(); + + // Render view drag n drop ended. + virtual void DragEnded(); + + // Shows the repost form confirmation dialog box. + virtual void ShowRepostFormWarningDialog(TabContents* tab_contents); + + // Shows the Content Settings dialog for a given content type. + virtual void ShowContentSettingsWindow(ContentSettingsType content_type); + + // Shows the cookies collected in the tab contents. + virtual void ShowCollectedCookiesDialog(TabContents* tab_contents); + + // Allows delegate to override navigation to the history entries. + // Returns true to allow TabContents to continue with the default processing. + virtual bool OnGoToEntryOffset(int offset); + + // Returns whether this tab contents should add the specified navigation to + // history. + virtual bool ShouldAddNavigationToHistory( + const history::HistoryAddPageArgs& add_page_args, + NavigationType::Type navigation_type); + + // Notification that a user's request to install an application has completed. + virtual void OnDidGetApplicationInfo(TabContents* tab_contents, + int32 page_id); + + // Notification when an application programmatically requests installation. + virtual void OnInstallApplication(TabContents* tab_contents, + const WebApplicationInfo& app_info); + + // Returns the native window framing the view containing the tab contents. + virtual gfx::NativeWindow GetFrameNativeWindow(); + + // Notifies the delegate about the creation of a new TabContents. This + // typically happens when popups are created. + virtual void TabContentsCreated(TabContents* new_contents); + + // Returns whether infobars are enabled. Overrideable by child classes. + virtual bool infobars_enabled(); + + // Whether the renderer should report its preferred size when it changes by + // calling UpdatePreferredSize(). + // Note that this is set when the RenderViewHost is created and cannot be + // changed after that. + virtual bool ShouldEnablePreferredSizeNotifications(); + + // Notification that the preferred size of the contents has changed. + // Only called if ShouldEnablePreferredSizeNotifications() returns true. + virtual void UpdatePreferredSize(const gfx::Size& pref_size); + + // Notifies the delegate that the page has a suggest result. + virtual void OnSetSuggestions(int32 page_id, + const std::vector<std::string>& result); + + // Notifies the delegate whether the page supports instant-style interaction. + virtual void OnInstantSupportDetermined(int32 page_id, bool result); + + // Notifies the delegate that the content restrictions for this tab has + // changed. + virtual void ContentRestrictionsChanged(TabContents* source); + + // Returns true if the hung renderer dialog should be shown. Default is true. + virtual bool ShouldShowHungRendererDialog(); + + // Notification that a worker associated with this tab has crashed. + virtual void WorkerCrashed(); + + protected: + virtual ~TabContentsDelegate(); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_DELEGATE_H_ diff --git a/content/browser/tab_contents/tab_contents_observer.cc b/content/browser/tab_contents/tab_contents_observer.cc new file mode 100644 index 0000000..00c8210 --- /dev/null +++ b/content/browser/tab_contents/tab_contents_observer.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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/tab_contents/tab_contents_observer.h" + +#include "chrome/browser/renderer_host/render_view_host.h" +#include "content/browser/tab_contents/tab_contents.h" + +TabContentsObserver::TabContentsObserver(TabContents* tab_contents) + : tab_contents_(tab_contents), + routing_id_(tab_contents->render_view_host()->routing_id()) { + tab_contents_->AddObserver(this); +} + +TabContentsObserver::~TabContentsObserver() { + if (tab_contents_) + tab_contents_->RemoveObserver(this); +} + +bool TabContentsObserver::OnMessageReceived(const IPC::Message& message) { + return false; +} + +bool TabContentsObserver::Send(IPC::Message* message) { + if (!tab_contents_->render_view_host()) { + delete message; + return false; + } + + return tab_contents_->render_view_host()->Send(message); +} diff --git a/content/browser/tab_contents/tab_contents_observer.h b/content/browser/tab_contents/tab_contents_observer.h new file mode 100644 index 0000000..b8bb343 --- /dev/null +++ b/content/browser/tab_contents/tab_contents_observer.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011 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_TAB_CONTENTS_TAB_CONTENTS_OBSERVER_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_OBSERVER_H_ + +#include "content/browser/tab_contents/navigation_controller.h" +#include "ipc/ipc_channel.h" + +struct ViewHostMsg_FrameNavigate_Params; + +// An observer API implemented by classes which are interested in various page +// load events from TabContents. They also get a chance to filter IPC messages. +class TabContentsObserver : public IPC::Channel::Listener { + public: + virtual void NavigateToPendingEntry() { } + + virtual void DidNavigateMainFramePostCommit( + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { } + virtual void DidNavigateAnyFramePostCommit( + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { } + + virtual void DidStartLoading() { } + virtual void DidStopLoading() { } + +#if 0 + // For unifying with delegate... + + // Notifies the delegate that this contents is starting or is done loading + // some resource. The delegate should use this notification to represent + // loading feedback. See TabContents::is_loading() + virtual void LoadingStateChanged(TabContents* contents) { } + // Called to inform the delegate that the tab content's navigation state + // changed. The |changed_flags| indicates the parts of the navigation state + // that have been updated, and is any combination of the + // |TabContents::InvalidateTypes| bits. + virtual void NavigationStateChanged(const TabContents* source, + unsigned changed_flags) { } +#endif + + protected: + TabContentsObserver(TabContents* tab_contents); + virtual ~TabContentsObserver(); + + // IPC::Channel::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message); + + // IPC::Message::Sender implementation. + virtual bool Send(IPC::Message* message); + + TabContents* tab_contents() { return tab_contents_; } + int routing_id() { return routing_id_; } + + private: + friend class TabContents; + + void set_tab_contents(TabContents* tc) { tab_contents_ = tc; } + + TabContents* tab_contents_; + // The routing ID of the associated TabContents. + int routing_id_; + + DISALLOW_COPY_AND_ASSIGN(TabContentsObserver); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_OBSERVER_H_ diff --git a/content/browser/tab_contents/tab_contents_view.cc b/content/browser/tab_contents/tab_contents_view.cc new file mode 100644 index 0000000..8c395af --- /dev/null +++ b/content/browser/tab_contents/tab_contents_view.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2011 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/tab_contents/tab_contents_view.h" + +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/render_widget_host.h" +#include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/browser/renderer_host/render_widget_host_view.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages_params.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_delegate.h" + +TabContentsView::TabContentsView(TabContents* tab_contents) + : tab_contents_(tab_contents) { +} + +TabContentsView::~TabContentsView() {} + +void TabContentsView::RenderWidgetHostDestroyed(RenderWidgetHost* host) { + if (host->view()) + host->view()->WillDestroyRenderWidget(host); + delegate_view_helper_.RenderWidgetHostDestroyed(host); +} + +void TabContentsView::RenderViewCreated(RenderViewHost* host) { + // Default implementation does nothing. Platforms may override. +} + +void TabContentsView::CreateNewWindow( + int route_id, + const ViewHostMsg_CreateWindow_Params& params) { + TabContents* new_contents = delegate_view_helper_.CreateNewWindow( + route_id, + tab_contents_->profile(), + tab_contents_->GetSiteInstance(), + WebUIFactory::GetWebUIType(tab_contents_->profile(), + tab_contents_->GetURL()), + tab_contents_, + params.window_container_type, + params.frame_name); + + if (new_contents) { + NotificationService::current()->Notify( + NotificationType::CREATING_NEW_WINDOW, + Source<TabContents>(tab_contents_), + Details<const ViewHostMsg_CreateWindow_Params>(¶ms)); + + if (tab_contents_->delegate()) + tab_contents_->delegate()->TabContentsCreated(new_contents); + } +} + +void TabContentsView::CreateNewWidget(int route_id, + WebKit::WebPopupType popup_type) { + CreateNewWidgetInternal(route_id, popup_type); +} + +void TabContentsView::CreateNewFullscreenWidget(int route_id) { + CreateNewFullscreenWidgetInternal(route_id); +} + +void TabContentsView::ShowCreatedWindow(int route_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) { + TabContents* contents = delegate_view_helper_.GetCreatedWindow(route_id); + if (contents) { + tab_contents()->AddNewContents(contents, disposition, initial_pos, + user_gesture); + } +} + +void TabContentsView::ShowCreatedWidget(int route_id, + const gfx::Rect& initial_pos) { + RenderWidgetHostView* widget_host_view = + delegate_view_helper_.GetCreatedWidget(route_id); + ShowCreatedWidgetInternal(widget_host_view, initial_pos); +} + +void TabContentsView::Activate() { + tab_contents_->Activate(); +} + +void TabContentsView::Deactivate() { + tab_contents_->Deactivate(); +} + +void TabContentsView::ShowCreatedFullscreenWidget(int route_id) { + RenderWidgetHostView* widget_host_view = + delegate_view_helper_.GetCreatedWidget(route_id); + ShowCreatedFullscreenWidgetInternal(widget_host_view); +} + +void TabContentsView::LostCapture() { + if (tab_contents_->delegate()) + tab_contents_->delegate()->LostCapture(); +} + +bool TabContentsView::PreHandleKeyboardEvent( + const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { + return tab_contents_->delegate() && + tab_contents_->delegate()->PreHandleKeyboardEvent( + event, is_keyboard_shortcut); +} + +void TabContentsView::UpdatePreferredSize(const gfx::Size& pref_size) { + if (tab_contents_->delegate()) + tab_contents_->delegate()->UpdatePreferredSize(pref_size); +} + +bool TabContentsView::IsDoingDrag() const { + return false; +} + +bool TabContentsView::IsEventTracking() const { + return false; +} + +TabContentsView::TabContentsView() : tab_contents_(NULL) {} + +void TabContentsView::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) { + if (tab_contents_->delegate()) + tab_contents_->delegate()->HandleKeyboardEvent(event); +} + +void TabContentsView::HandleMouseUp() { + if (tab_contents_->delegate()) + tab_contents_->delegate()->HandleMouseUp(); +} + +void TabContentsView::HandleMouseActivate() { + if (tab_contents_->delegate()) + tab_contents_->delegate()->HandleMouseActivate(); +} + +RenderWidgetHostView* TabContentsView::CreateNewWidgetInternal( + int route_id, WebKit::WebPopupType popup_type) { + return delegate_view_helper_.CreateNewWidget(route_id, popup_type, + tab_contents()->render_view_host()->process()); +} + +RenderWidgetHostView* TabContentsView::CreateNewFullscreenWidgetInternal( + int route_id) { + return delegate_view_helper_.CreateNewFullscreenWidget( + route_id, tab_contents()->render_view_host()->process()); +} + +void TabContentsView::ShowCreatedWidgetInternal( + RenderWidgetHostView* widget_host_view, const gfx::Rect& initial_pos) { + if (tab_contents_->delegate()) + tab_contents_->delegate()->RenderWidgetShowing(); + + widget_host_view->InitAsPopup(tab_contents_->GetRenderWidgetHostView(), + initial_pos); + widget_host_view->GetRenderWidgetHost()->Init(); +} + +void TabContentsView::ShowCreatedFullscreenWidgetInternal( + RenderWidgetHostView* widget_host_view) { + if (tab_contents_->delegate()) + tab_contents_->delegate()->RenderWidgetShowing(); + + widget_host_view->InitAsFullscreen(); + widget_host_view->GetRenderWidgetHost()->Init(); +} diff --git a/content/browser/tab_contents/tab_contents_view.h b/content/browser/tab_contents/tab_contents_view.h new file mode 100644 index 0000000..9d5a04f --- /dev/null +++ b/content/browser/tab_contents/tab_contents_view.h @@ -0,0 +1,218 @@ +// Copyright (c) 2010 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_TAB_CONTENTS_TAB_CONTENTS_VIEW_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_VIEW_H_ +#pragma once + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/browser/tab_contents/render_view_host_delegate_helper.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +class RenderViewHost; +class RenderWidgetHost; +class RenderWidgetHostView; +class TabContents; + +// The TabContentsView is an interface that is implemented by the platform- +// dependent web contents views. The TabContents uses this interface to talk to +// them. View-related messages will also get forwarded directly to this class +// from RenderViewHost via RenderViewHostDelegate::View. +// +// It contains a small amount of logic with respect to creating new sub-view +// that should be the same for all platforms. +class TabContentsView : public RenderViewHostDelegate::View { + public: + explicit TabContentsView(TabContents* tab_contents); + virtual ~TabContentsView(); + + // Creates the appropriate type of TabContentsView for the current system. + // The return value is a new heap allocated view with ownership passing to + // the caller. + static TabContentsView* Create(TabContents* tab_contents); + + TabContents* tab_contents() const { return tab_contents_; } + + virtual void CreateView(const gfx::Size& initial_size) = 0; + + // Sets up the View that holds the rendered web page, receives messages for + // it and contains page plugins. The host view should be sized to the current + // size of the TabContents. + virtual RenderWidgetHostView* CreateViewForWidget( + RenderWidgetHost* render_widget_host) = 0; + + // Returns the native widget that contains the contents of the tab. + virtual gfx::NativeView GetNativeView() const = 0; + + // Returns the native widget with the main content of the tab (i.e. the main + // render view host, though there may be many popups in the tab as children of + // the container). + virtual gfx::NativeView GetContentNativeView() const = 0; + + // Returns the outermost native view. This will be used as the parent for + // dialog boxes. + virtual gfx::NativeWindow GetTopLevelNativeWindow() const = 0; + + // Computes the rectangle for the native widget that contains the contents of + // the tab relative to its parent. + virtual void GetContainerBounds(gfx::Rect *out) const = 0; + + // Helper function for GetContainerBounds. Most callers just want to know the + // size, and this makes it more clear. + gfx::Size GetContainerSize() const { + gfx::Rect rc; + GetContainerBounds(&rc); + return gfx::Size(rc.width(), rc.height()); + } + + // Sets the page title for the native widgets corresponding to the view. This + // is not strictly necessary and isn't expected to be displayed anywhere, but + // can aid certain debugging tools such as Spy++ on Windows where you are + // trying to find a specific window. + virtual void SetPageTitle(const std::wstring& title) = 0; + + // Used to notify the view that a tab has crashed so each platform can + // prepare the sad tab. + virtual void OnTabCrashed(base::TerminationStatus status, + int error_code) = 0; + + // TODO(brettw) this is a hack. It's used in two places at the time of this + // writing: (1) when render view hosts switch, we need to size the replaced + // one to be correct, since it wouldn't have known about sizes that happened + // while it was hidden; (2) in constrained windows. + // + // (1) will be fixed once interstitials are cleaned up. (2) seems like it + // should be cleaned up or done some other way, since this works for normal + // TabContents without the special code. + virtual void SizeContents(const gfx::Size& size) = 0; + + // Invoked from the platform dependent web contents view when a + // RenderWidgetHost is deleted. Removes |host| from internal maps. + void RenderWidgetHostDestroyed(RenderWidgetHost* host); + + // Invoked when the TabContents is notified that the RenderView has been + // fully created. The default implementation does nothing; override + // for platform-specific behavior is needed. + virtual void RenderViewCreated(RenderViewHost* host); + + // Sets focus to the native widget for this tab. + virtual void Focus() = 0; + + // Sets focus to the appropriate element when the tab contents is shown the + // first time. + virtual void SetInitialFocus() = 0; + + // Stores the currently focused view. + virtual void StoreFocus() = 0; + + // Restores focus to the last focus view. If StoreFocus has not yet been + // invoked, SetInitialFocus is invoked. + virtual void RestoreFocus() = 0; + + // RenderViewHostDelegate::View method. Forwards to the TabContentsDelegate. + virtual void LostCapture(); + + // Keyboard events forwarding from the RenderViewHost. + // The default implementation just forward the events to the + // TabContentsDelegate object. + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut); + + // Keyboard events forwarding from the RenderViewHost. + // The default implementation just forward the events to the + // TabContentsDelegate object. + virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event); + + // Simple mouse event forwarding from the RenderViewHost. + virtual void HandleMouseMove() {} + virtual void HandleMouseDown() {} + virtual void HandleMouseLeave() {} + virtual void HandleMouseUp(); + virtual void HandleMouseActivate(); + + // Notification that the preferred size of the contents has changed. + virtual void UpdatePreferredSize(const gfx::Size& pref_size); + + // If we try to close the tab while a drag is in progress, we crash. These + // methods allow the tab contents to determine if a drag is in progress and + // postpone the tab closing. + virtual bool IsDoingDrag() const; + virtual void CancelDragAndCloseTab() {} + + // If we close the tab while a UI control is in an event-tracking + // loop, the control may message freed objects and crash. + // TabContents::Close() calls IsEventTracking(), and if it returns + // true CloseTabAfterEventTracking() is called and the close is not + // completed. + virtual bool IsEventTracking() const; + virtual void CloseTabAfterEventTracking() {} + + // Get the bounds of the View, relative to the parent. + virtual void GetViewBounds(gfx::Rect* out) const = 0; + + protected: + TabContentsView(); // Abstract interface. + + // Internal functions used to support the CreateNewWidget() method. If a + // platform requires plugging into widget creation at a lower level then a + // subclass might want to override these functions, but otherwise they should + // be fine just implementing RenderWidgetHostView::InitAsPopup(). + // + // The Create function returns the newly created widget so it can be + // associated with the given route. When the widget needs to be shown later, + // we'll look it up again and pass the object to the Show functions rather + // than the route ID. + virtual RenderWidgetHostView* CreateNewWidgetInternal( + int route_id, + WebKit::WebPopupType popup_type); + virtual void ShowCreatedWidgetInternal(RenderWidgetHostView* widget_host_view, + const gfx::Rect& initial_pos); + virtual void ShowCreatedFullscreenWidgetInternal( + RenderWidgetHostView* widget_host_view); + virtual RenderWidgetHostView* CreateNewFullscreenWidgetInternal(int route_id); + + // Common implementations of some RenderViewHostDelegate::View methods. + RenderViewHostDelegateViewHelper delegate_view_helper_; + + private: + // We implement these functions on RenderViewHostDelegate::View directly and + // do some book-keeping associated with the request. The request is then + // forwarded to *Internal which does platform-specific work. + virtual void CreateNewWindow( + int route_id, + const ViewHostMsg_CreateWindow_Params& params); + virtual void CreateNewWidget(int route_id, WebKit::WebPopupType popup_type); + virtual void CreateNewFullscreenWidget(int route_id); + virtual void ShowCreatedWindow(int route_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture); + virtual void ShowCreatedWidget(int route_id, const gfx::Rect& initial_pos); + virtual void Activate(); + virtual void Deactivate(); + virtual void ShowCreatedFullscreenWidget(int route_id); + + // The TabContents whose contents we display. + TabContents* tab_contents_; + + // Tracks created TabContents objects that have not been shown yet. They are + // identified by the route ID passed to CreateNewWindow. + typedef std::map<int, TabContents*> PendingContents; + PendingContents pending_contents_; + + // These maps hold on to the widgets that we created on behalf of the + // renderer that haven't shown yet. + typedef std::map<int, RenderWidgetHostView*> PendingWidgetViews; + PendingWidgetViews pending_widget_views_; + + DISALLOW_COPY_AND_ASSIGN(TabContentsView); +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_TAB_CONTENTS_VIEW_H_ diff --git a/content/browser/tab_contents/test_tab_contents.cc b/content/browser/tab_contents/test_tab_contents.cc new file mode 100644 index 0000000..69fc0cf --- /dev/null +++ b/content/browser/tab_contents/test_tab_contents.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2009 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 "chrome/browser/tab_contents/test_tab_contents.h" + +#include <utility> + +#include "chrome/browser/browser_url_handler.h" +#include "chrome/browser/renderer_host/mock_render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/renderer_host/test/test_render_view_host.h" +#include "chrome/browser/tab_contents/infobar_delegate.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/page_transition_types.h" + +TestTabContents::TestTabContents(Profile* profile, SiteInstance* instance) + : TabContents(profile, instance, MSG_ROUTING_NONE, NULL, NULL), + transition_cross_site(false) { + // Listen for infobar events so we can call InfoBarClosed() on the infobar + // delegates and give them an opportunity to delete themselves. (Since we + // have no InfobarContainer in TestTabContents, InfoBarClosed() is not called + // most likely leading to the infobar delegates being leaked.) + Source<TabContents> source(this); + registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_REMOVED, + source); + registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_REPLACED, + source); +} + +void TestTabContents::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + // TabContents does not handle TAB_CONTENTS_INFOBAR_* so we don't pass it + // these notifications. + switch (type.value) { + case NotificationType::TAB_CONTENTS_INFOBAR_REMOVED: + Details<InfoBarDelegate>(details).ptr()->InfoBarClosed(); + break; + case NotificationType::TAB_CONTENTS_INFOBAR_REPLACED: + Details<std::pair<InfoBarDelegate*, InfoBarDelegate*> >(details).ptr()-> + first->InfoBarClosed(); + break; + default: + TabContents::Observe(type, source, details); + break; + } +} + +TestRenderViewHost* TestTabContents::pending_rvh() const { + return static_cast<TestRenderViewHost*>( + render_manager_.pending_render_view_host_); +} + +bool TestTabContents::CreateRenderViewForRenderManager( + RenderViewHost* render_view_host) { + // This will go to a TestRenderViewHost. + render_view_host->CreateRenderView(string16()); + return true; +} + +TabContents* TestTabContents::Clone() { + TabContents* tc = new TestTabContents( + profile(), SiteInstance::CreateSiteInstance(profile())); + tc->controller().CopyStateFrom(controller_); + return tc; +} + +void TestTabContents::NavigateAndCommit(const GURL& url) { + controller().LoadURL(url, GURL(), PageTransition::LINK); + GURL loaded_url(url); + bool reverse_on_redirect = false; + BrowserURLHandler::RewriteURLIfNecessary( + &loaded_url, profile(), &reverse_on_redirect); + + // LoadURL created a navigation entry, now simulate the RenderView sending + // a notification that it actually navigated. + CommitPendingNavigation(); +} + +void TestTabContents::CommitPendingNavigation() { + // If we are doing a cross-site navigation, this simulates the current RVH + // notifying that it has unloaded so the pending RVH is resumed and can + // navigate. + ProceedWithCrossSiteNavigation(); + TestRenderViewHost* rvh = pending_rvh(); + if (!rvh) + rvh = static_cast<TestRenderViewHost*>(render_manager_.current_host()); + + const NavigationEntry* entry = controller().pending_entry(); + DCHECK(entry); + int page_id = entry->page_id(); + if (page_id == -1) { + // It's a new navigation, assign a never-seen page id to it. + page_id = + static_cast<MockRenderProcessHost*>(rvh->process())->max_page_id() + 1; + } + rvh->SendNavigate(page_id, entry->url()); +} + +void TestTabContents::ProceedWithCrossSiteNavigation() { + if (!pending_rvh()) + return; + render_manager_.ShouldClosePage(true, true); +} diff --git a/content/browser/tab_contents/test_tab_contents.h b/content/browser/tab_contents/test_tab_contents.h new file mode 100644 index 0000000..83c251a --- /dev/null +++ b/content/browser/tab_contents/test_tab_contents.h @@ -0,0 +1,81 @@ +// Copyright (c) 2011 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_TAB_CONTENTS_TEST_TAB_CONTENTS_H_ +#define CONTENT_BROWSER_TAB_CONTENTS_TEST_TAB_CONTENTS_H_ +#pragma once + +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/notification_registrar.h" +#include "webkit/glue/webpreferences.h" + +class Profile; +class TestRenderViewHost; + +// Subclass TabContents to ensure it creates TestRenderViewHosts and does +// not do anything involving views. +class TestTabContents : public TabContents { + public: + // The render view host factory will be passed on to the + TestTabContents(Profile* profile, SiteInstance* instance); + + TestRenderViewHost* pending_rvh() const; + + // State accessor. + bool cross_navigation_pending() { + return render_manager_.cross_navigation_pending_; + } + + // Overrides TabContents::ShouldTransitionCrossSite so that we can test both + // alternatives without using command-line switches. + bool ShouldTransitionCrossSite() { return transition_cross_site; } + + // Overrides TabContents::Observe. We are listening to infobar related + // notifications so we can call InfoBarClosed() on the infobar delegates to + // prevent them from leaking. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Promote DidNavigate to public. + void TestDidNavigate(RenderViewHost* render_view_host, + const ViewHostMsg_FrameNavigate_Params& params) { + DidNavigate(render_view_host, params); + } + + // Promote GetWebkitPrefs to public. + WebPreferences TestGetWebkitPrefs() { + return GetWebkitPrefs(); + } + + // Prevent interaction with views. + virtual bool CreateRenderViewForRenderManager( + RenderViewHost* render_view_host); + virtual void UpdateRenderViewSizeForRenderManager() {} + + // Returns a clone of this TestTabContents. The returned object is also a + // TestTabContents. The caller owns the returned object. + virtual TabContents* Clone(); + + // Creates a pending navigation to the given URL with the default parameters + // and then commits the load with a page ID one larger than any seen. This + // emulates what happens on a new navigation. + void NavigateAndCommit(const GURL& url); + + // Simulates the appropriate RenderView (pending if any, current otherwise) + // sending a navigate notification for the NavigationController pending entry. + void CommitPendingNavigation(); + + // Simulates the current RVH notifying that it has unloaded so that the + // pending RVH navigation can proceed. + // Does nothing if no cross-navigation is pending. + void ProceedWithCrossSiteNavigation(); + + // Set by individual tests. + bool transition_cross_site; + + NotificationRegistrar registrar_; +}; + +#endif // CONTENT_BROWSER_TAB_CONTENTS_TEST_TAB_CONTENTS_H_ diff --git a/content/content.gyp b/content/content.gyp new file mode 100644 index 0000000..db71289 --- /dev/null +++ b/content/content.gyp @@ -0,0 +1,13 @@ +# Copyright (c) 2011 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. + +{ + 'variables': { + 'chromium_code': 1, # Use higher warning level. + }, + 'includes': [ + 'content_browser.gypi', + 'content_common.gypi', + ], +} diff --git a/content/content_browser.gypi b/content/content_browser.gypi new file mode 100644 index 0000000..51bf09c --- /dev/null +++ b/content/content_browser.gypi @@ -0,0 +1,60 @@ +# Copyright (c) 2011 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. + +{ + 'targets': [ + { + 'target_name': 'content_browser', + 'type': '<(library)', + 'dependencies': [ + # Don't include now since it's empty and so will cause a linker error. + #'content_common', + '../app/app.gyp:app_resources', + '../skia/skia.gyp:skia', + '../ui/ui.gyp:ui_base', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'browser/tab_contents/background_contents.cc', + 'browser/tab_contents/background_contents.h', + 'browser/tab_contents/constrained_window.h', + 'browser/tab_contents/infobar_delegate.cc', + 'browser/tab_contents/infobar_delegate.h', + 'browser/tab_contents/interstitial_page.cc', + 'browser/tab_contents/interstitial_page.h', + 'browser/tab_contents/language_state.cc', + 'browser/tab_contents/language_state.h', + 'browser/tab_contents/navigation_controller.cc', + 'browser/tab_contents/navigation_controller.h', + 'browser/tab_contents/navigation_entry.cc', + 'browser/tab_contents/navigation_entry.h', + 'browser/tab_contents/page_navigator.h', + 'browser/tab_contents/provisional_load_details.cc', + 'browser/tab_contents/provisional_load_details.h', + 'browser/tab_contents/render_view_host_manager.cc', + 'browser/tab_contents/render_view_host_manager.h', + 'browser/tab_contents/tab_contents.cc', + 'browser/tab_contents/tab_contents.h', + 'browser/tab_contents/tab_contents_delegate.cc', + 'browser/tab_contents/tab_contents_delegate.h', + 'browser/tab_contents/tab_contents_observer.cc', + 'browser/tab_contents/tab_contents_observer.h', + 'browser/tab_contents/tab_contents_view.cc', + 'browser/tab_contents/tab_contents_view.h', + ], + 'conditions': [ + ['OS=="win"', { + 'msvs_guid': '639DB58D-32C2-435A-A711-65A12F62E442', + }], + ['OS=="linux" or OS=="freebsd" or OS=="openbsd" or OS=="solaris"', { + 'dependencies': [ + '../build/linux/system.gyp:gtk', + ], + }], + ], + }, + ], +} diff --git a/content/content_common.gypi b/content/content_common.gypi new file mode 100644 index 0000000..0ef8194 --- /dev/null +++ b/content/content_common.gypi @@ -0,0 +1,25 @@ +# Copyright (c) 2011 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. + +{ + 'targets': [ + { + 'target_name': 'content_common', + 'type': '<(library)', + 'dependencies': [ + '../ipc/ipc.gyp:ipc', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + ], + 'conditions': [ + ['OS=="win"', { + 'msvs_guid': '062E9260-304A-4657-A74C-0D3AA1A0A0A4', + }], + ], + }, + ], +} |