summaryrefslogtreecommitdiffstats
path: root/chrome_frame
diff options
context:
space:
mode:
authorerikwright@chromium.org <erikwright@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-03 16:22:00 +0000
committererikwright@chromium.org <erikwright@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-03 16:22:00 +0000
commitd7b59a0c525807f1eaf0456c3db3b4f659bcccc4 (patch)
treee26dd48c52a7755ec84f9c44b8a8e03fc9d39dde /chrome_frame
parent385a2b73c0e717d27145521f7c3f557004e0453e (diff)
downloadchromium_src-d7b59a0c525807f1eaf0456c3db3b4f659bcccc4.zip
chromium_src-d7b59a0c525807f1eaf0456c3db3b4f659bcccc4.tar.gz
chromium_src-d7b59a0c525807f1eaf0456c3db3b4f659bcccc4.tar.bz2
Add an infobar facility to Chrome Frame. Allows clients to display and hide arbitrary content at the top or botom of an IE tab.
BUG=None TEST=chrome_frame_unittests --gtest_filter=Infobars* Review URL: http://codereview.chromium.org/4766003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@68176 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame')
-rw-r--r--chrome_frame/chrome_frame.gyp11
-rw-r--r--chrome_frame/infobars/infobar_content.h50
-rw-r--r--chrome_frame/infobars/infobar_manager.cc102
-rw-r--r--chrome_frame/infobars/infobar_manager.h50
-rw-r--r--chrome_frame/infobars/internal/displaced_window_manager.cc42
-rw-r--r--chrome_frame/infobars/internal/displaced_window_manager.h45
-rw-r--r--chrome_frame/infobars/internal/host_window_manager.cc119
-rw-r--r--chrome_frame/infobars/internal/host_window_manager.h48
-rw-r--r--chrome_frame/infobars/internal/infobar_window.cc183
-rw-r--r--chrome_frame/infobars/internal/infobar_window.h108
-rw-r--r--chrome_frame/infobars/internal/subclassing_window_with_delegate.h107
-rw-r--r--chrome_frame/test/infobar_unittests.cc592
12 files changed, 1457 insertions, 0 deletions
diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp
index 054817b..1daca741 100644
--- a/chrome_frame/chrome_frame.gyp
+++ b/chrome_frame/chrome_frame.gyp
@@ -171,6 +171,7 @@
'test/exception_barrier_unittest.cc',
'test/html_util_unittests.cc',
'test/http_negotiate_unittest.cc',
+ 'test/infobar_unittests.cc',
'test/dll_redirector_test.cc',
'test/policy_settings_unittest.cc',
'test/simulate_input.h',
@@ -748,6 +749,16 @@
'http_negotiate.h',
'iids.cc',
'in_place_menu.h',
+ 'infobars/infobar_content.h',
+ 'infobars/internal/displaced_window_manager.cc',
+ 'infobars/internal/displaced_window_manager.h',
+ 'infobars/internal/host_window_manager.cc',
+ 'infobars/internal/host_window_manager.h',
+ 'infobars/internal/infobar_window.cc',
+ 'infobars/internal/infobar_window.h',
+ 'infobars/internal/subclassing_window_with_delegate.h',
+ 'infobars/infobar_manager.h',
+ 'infobars/infobar_manager.cc',
'metrics_service.cc',
'metrics_service.h',
'ole_document_impl.h',
diff --git a/chrome_frame/infobars/infobar_content.h b/chrome_frame/infobars/infobar_content.h
new file mode 100644
index 0000000..fd80cd0
--- /dev/null
+++ b/chrome_frame/infobars/infobar_content.h
@@ -0,0 +1,50 @@
+// 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 CHROME_FRAME_INFOBARS_INFOBAR_CONTENT_H_
+#define CHROME_FRAME_INFOBARS_INFOBAR_CONTENT_H_
+
+#include <windows.h>
+
+// Provides an interface between content to be displayed in an infobar and the
+// infobar facility. Pass an instance of your implementation to
+// InfobarManager::Show, which will result in a call to InstallInFrame to
+// initialize the InfobarContent.
+//
+// The instance will be deleted by the infobar facility when it is no longer
+// being displayed (or immediately, in the case of a failure to display).
+class InfobarContent {
+ public:
+ // Provides access to the content's parent window and allows the
+ // InfobarContent to close itself, such as in response to user interaction.
+ class Frame {
+ public:
+ virtual ~Frame() {}
+
+ // Returns the window in which the content should display itself.
+ virtual HWND GetFrameWindow() = 0;
+
+ // Initiates closing of the infobar.
+ virtual void CloseInfobar() = 0;
+ }; // class Frame
+
+ virtual ~InfobarContent() {}
+
+ // Prepares the content to display in the provided frame.
+ //
+ // The frame pointer remains valid until the InfobarContent instance is
+ // deleted.
+ virtual bool InstallInFrame(Frame* frame) = 0;
+
+ // Provides the content with the dimensions available to it for display.
+ // Dimensions are relative to the origin of the frame window.
+ virtual void SetDimensions(const RECT& dimensions) = 0;
+
+ // Finds the desired value for one dimension given a fixed value for the other
+ // dimension. The fixed dimension parameter is non-zero whereas the requested
+ // dimension parameter will be zero.
+ virtual size_t GetDesiredSize(size_t width, size_t height) = 0;
+}; // class InfobarContent
+
+#endif // CHROME_FRAME_INFOBARS_INFOBAR_CONTENT_H_
diff --git a/chrome_frame/infobars/infobar_manager.cc b/chrome_frame/infobars/infobar_manager.cc
new file mode 100644
index 0000000..7d8e072
--- /dev/null
+++ b/chrome_frame/infobars/infobar_manager.cc
@@ -0,0 +1,102 @@
+// 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 "chrome_frame/infobars/infobar_manager.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "chrome_frame/infobars/internal/host_window_manager.h"
+#include "chrome_frame/infobars/internal/infobar_window.h"
+
+// Connects InfobarWindow to HostWindowManager, and exposes the result as an
+// InfobarManager.
+class InfobarManagerImpl
+ : public InfobarManager,
+ public InfobarWindow::Host,
+ public HostWindowManager::Delegate {
+ public:
+ explicit InfobarManagerImpl(HostWindowManager* manager);
+
+ // Implementation of InfobarManager
+ virtual bool Show(InfobarContent* content, InfobarType type);
+ virtual void Hide(InfobarType type);
+ virtual void HideAll();
+
+ // Implementation of HostWindowManager::Delegate
+ virtual void AdjustDisplacedWindowDimensions(RECT* rect);
+
+ // Implementation of InfobarWindow::Host
+ virtual HWND GetContainerWindow();
+ virtual void UpdateLayout();
+
+ private:
+ // Not owned by this instance.
+ HostWindowManager* manager_;
+ // Infobar windows.
+ scoped_ptr<InfobarWindow> infobars_[END_OF_INFOBAR_TYPE];
+ DISALLOW_COPY_AND_ASSIGN(InfobarManagerImpl);
+}; // class InfobarManagerImpl
+
+InfobarManagerImpl::InfobarManagerImpl(HostWindowManager* manager)
+ : manager_(manager) {
+ for (int index = 0; index < END_OF_INFOBAR_TYPE; ++index) {
+ infobars_[index].reset(
+ new InfobarWindow(static_cast<InfobarType>(index)));
+ infobars_[index]->SetHost(this);
+ }
+}
+
+bool InfobarManagerImpl::Show(InfobarContent* content, InfobarType type) {
+ DCHECK(type >= FIRST_INFOBAR_TYPE && type < END_OF_INFOBAR_TYPE);
+ return infobars_[type]->Show(content);
+}
+
+void InfobarManagerImpl::Hide(InfobarType type) {
+ DCHECK(type >= FIRST_INFOBAR_TYPE && type < END_OF_INFOBAR_TYPE);
+ infobars_[type]->Hide();
+}
+
+void InfobarManagerImpl::HideAll() {
+ for (int index = 0; index < END_OF_INFOBAR_TYPE; ++index)
+ Hide(static_cast<InfobarType>(index));
+}
+
+void InfobarManagerImpl::AdjustDisplacedWindowDimensions(RECT* rect) {
+ for (int index = 0; index < END_OF_INFOBAR_TYPE; ++index) {
+ if (infobars_[index] != NULL)
+ infobars_[index]->ReserveSpace(rect);
+ }
+}
+
+HWND InfobarManagerImpl::GetContainerWindow() {
+ return *manager_;
+}
+
+void InfobarManagerImpl::UpdateLayout() {
+ manager_->UpdateLayout();
+}
+
+InfobarManager::~InfobarManager() {
+}
+
+InfobarManager* InfobarManager::Get(HWND tab_window) {
+ HostWindowManager::Delegate* delegate =
+ HostWindowManager::GetDelegateForHwnd(tab_window);
+
+ if (delegate != NULL)
+ return static_cast<InfobarManagerImpl*>(delegate);
+
+ scoped_ptr<HostWindowManager> host_manager(new HostWindowManager());
+
+ // Transferred to host_manager in call to Initialize.
+ InfobarManagerImpl* infobar_manager = new InfobarManagerImpl(
+ host_manager.get());
+
+ if (host_manager->Initialize(tab_window, infobar_manager)) {
+ host_manager.release(); // takes ownership of itself
+ return infobar_manager;
+ }
+
+ return NULL;
+}
diff --git a/chrome_frame/infobars/infobar_manager.h b/chrome_frame/infobars/infobar_manager.h
new file mode 100644
index 0000000..e4dd3b7
--- /dev/null
+++ b/chrome_frame/infobars/infobar_manager.h
@@ -0,0 +1,50 @@
+// 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 CHROME_FRAME_INFOBARS_INFOBAR_MANAGER_H_
+#define CHROME_FRAME_INFOBARS_INFOBAR_MANAGER_H_
+
+#include <windows.h>
+
+class InfobarContent;
+
+enum InfobarType {
+ FIRST_INFOBAR_TYPE = 0,
+ TOP_INFOBAR = 0, // Infobar at the top.
+ BOTTOM_INFOBAR = 1, // Infobar at the bottom.
+ END_OF_INFOBAR_TYPE = 2
+};
+
+// Creates and manages infobars at the top or bottom of an IE content window.
+// Instances must only be retrieved and used within the UI thread of the IE
+// content window.
+class InfobarManager {
+ public:
+ // Returns an InfobarManager for the specified IE tab window. Caller does not
+ // own the pointer (resources will be freed when the window is destroyed).
+ //
+ // The pointer may be invalidated by further processing of window events, and
+ // as such should be immediately discarded after use.
+ //
+ // Returns NULL in case of failure.
+ static InfobarManager* Get(HWND tab_window);
+
+ virtual ~InfobarManager();
+
+ // Shows the supplied content in an infobar of the specified type.
+ // Normally, InfobarContent::InstallInFrame will be called with an interface
+ // the content may use to interact with the Infobar facility.
+ //
+ // InfobarContent is deleted when the Infobar facility is finished with the
+ // content (either through failure or when successfully hidden).
+ virtual bool Show(InfobarContent* content, InfobarType type) = 0;
+
+ // Hides the infobar of the specified type.
+ virtual void Hide(InfobarType type) = 0;
+
+ // Hides all infobars.
+ virtual void HideAll() = 0;
+}; // class InfobarManager
+
+#endif // CHROME_FRAME_INFOBARS_INFOBAR_MANAGER_H_
diff --git a/chrome_frame/infobars/internal/displaced_window_manager.cc b/chrome_frame/infobars/internal/displaced_window_manager.cc
new file mode 100644
index 0000000..0afce36
--- /dev/null
+++ b/chrome_frame/infobars/internal/displaced_window_manager.cc
@@ -0,0 +1,42 @@
+// 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 "chrome_frame/infobars/internal/displaced_window_manager.h"
+
+DisplacedWindowManager::DisplacedWindowManager() {
+}
+
+void DisplacedWindowManager::UpdateLayout() {
+ // Call SetWindowPos with SWP_FRAMECHANGED for displaced window.
+ // Displaced window will receive WM_NCCALCSIZE to recalculate its client size.
+ ::SetWindowPos(m_hWnd,
+ NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+ SWP_FRAMECHANGED);
+}
+
+LRESULT DisplacedWindowManager::OnNcCalcSize(BOOL calc_valid_rects,
+ LPARAM lparam) {
+ // Ask the original window proc to calculate the 'natural' size of the window.
+ LRESULT ret = DefWindowProc(WM_NCCALCSIZE,
+ static_cast<WPARAM>(calc_valid_rects), lparam);
+ if (lparam == NULL)
+ return ret;
+
+ // Whether calc_valid_rects is true or false, we could treat beginning of
+ // lparam as a RECT object.
+ RECT* rect = reinterpret_cast<RECT*>(lparam);
+ RECT natural_rect = *rect;
+ if (delegate() != NULL)
+ delegate()->AdjustDisplacedWindowDimensions(rect);
+
+ // If we modified the window's dimensions, there is no way to respect a custom
+ // "client-area preservation strategy", so we must force a redraw to be safe.
+ if (calc_valid_rects && ret == WVR_VALIDRECTS &&
+ !EqualRect(&natural_rect, rect)) {
+ ret = WVR_REDRAW;
+ }
+
+ return ret;
+}
diff --git a/chrome_frame/infobars/internal/displaced_window_manager.h b/chrome_frame/infobars/internal/displaced_window_manager.h
new file mode 100644
index 0000000..3110abd
--- /dev/null
+++ b/chrome_frame/infobars/internal/displaced_window_manager.h
@@ -0,0 +1,45 @@
+// 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 CHROME_FRAME_INFOBARS_INTERNAL_DISPLACED_WINDOW_MANAGER_H_
+#define CHROME_FRAME_INFOBARS_INTERNAL_DISPLACED_WINDOW_MANAGER_H_
+
+#include <atlbase.h>
+#include <atlcrack.h>
+#include <atlwin.h>
+
+#include "base/basictypes.h"
+#include "chrome_frame/infobars/internal/subclassing_window_with_delegate.h"
+
+// DisplacedWindowManager observes the HWND passed to Initialize and:
+// 1) Intercepts NCCALCSIZE events allowing the client to modify the window's
+// requested dimensions.
+// 2) Allows the client to request a recalculation of the window's dimensions
+// (resulting in a deferred callback as in [1]).
+// 3) Is destroyed only when the window is destroyed.
+class DisplacedWindowManager
+ : public SubclassingWindowWithDelegate<DisplacedWindowManager> {
+ public:
+ DisplacedWindowManager();
+
+ // Triggers an immediate re-evaluation of the dimensions of the displaced
+ // window. Delegate::AdjustDisplacedWindowDimensions will be called with the
+ // natural dimensions of the displaced window.
+ void UpdateLayout();
+
+ BEGIN_MSG_MAP_EX(DisplacedWindowManager)
+ MSG_WM_NCCALCSIZE(OnNcCalcSize)
+ CHAIN_MSG_MAP(SubclassingWindowWithDelegate<DisplacedWindowManager>)
+ END_MSG_MAP()
+
+ private:
+ // The size of the displaced window is being calculated. Allow
+ // InfobarWindows to reserve a part of the space for themselves, if they are
+ // visible.
+ LRESULT OnNcCalcSize(BOOL calc_valid_rects, LPARAM lparam);
+
+ DISALLOW_COPY_AND_ASSIGN(DisplacedWindowManager);
+}; // class DisplacedWindowManager
+
+#endif // CHROME_FRAME_INFOBARS_INTERNAL_DISPLACED_WINDOW_MANAGER_H_
diff --git a/chrome_frame/infobars/internal/host_window_manager.cc b/chrome_frame/infobars/internal/host_window_manager.cc
new file mode 100644
index 0000000..34bcf90
--- /dev/null
+++ b/chrome_frame/infobars/internal/host_window_manager.cc
@@ -0,0 +1,119 @@
+// 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 "chrome_frame/infobars/internal/host_window_manager.h"
+
+#include "chrome_frame/infobars/internal/displaced_window_manager.h"
+
+namespace {
+
+const wchar_t kIeTabContentParentWindowClass[] = L"Shell DocObject View";
+
+} // namespace
+
+// Receives notification when the displaced window is destroyed, and forwards
+// displaced window dimensions on to HostWindowManager::Delegate.
+class HostWindowManager::DisplacedWindowDelegate
+ : public DisplacedWindowManager::Delegate {
+ public:
+ explicit DisplacedWindowDelegate(HostWindowManager* manager);
+ virtual ~DisplacedWindowDelegate();
+
+ // DisplacedWindowManager::Delegate implementation
+ virtual void AdjustDisplacedWindowDimensions(RECT* rect);
+
+ private:
+ HostWindowManager* manager_; // Not owned by this instance
+ DISALLOW_COPY_AND_ASSIGN(DisplacedWindowDelegate);
+}; // class HostWindowManager::DisplacedWindowDelegate
+
+HostWindowManager::DisplacedWindowDelegate::DisplacedWindowDelegate(
+ HostWindowManager* manager) : manager_(manager) {
+}
+
+// Called when the displaced window is destroyed. Try to find a new displaced
+// window.
+HostWindowManager::DisplacedWindowDelegate::~DisplacedWindowDelegate() {
+ HWND old_window = *manager_->displaced_window_manager_;
+ // Will be deleted in its OnFinalMessage
+ manager_->displaced_window_manager_ = NULL;
+
+ // Check to see if a new window has already been created.
+ if (manager_->FindDisplacedWindow(old_window))
+ manager_->UpdateLayout();
+}
+
+// Forward this on to our delegate
+void HostWindowManager::DisplacedWindowDelegate::
+ AdjustDisplacedWindowDimensions(RECT* rect) {
+ manager_->delegate()->AdjustDisplacedWindowDimensions(rect);
+}
+
+// Callback function for EnumChildWindows (looks for a window with class
+// kIeTabContentParentWindowClass).
+//
+// lparam must point to an HWND that is either NULL or the HWND of the displaced
+// window that is being destroyed. We will ignore that window if we come across
+// it, and update lparam to point to the new displaced window if it is found.
+static BOOL CALLBACK FindDisplacedWindowProc(HWND hwnd, LPARAM lparam) {
+ DCHECK(lparam != NULL);
+ HWND* window_handle = reinterpret_cast<HWND*>(lparam);
+
+ if (hwnd == *window_handle)
+ return TRUE; // Skip this, it's the old displaced window.
+
+ // Variable to hold the class name. The size does not matter as long as it
+ // is at least can hold kIeTabContentParentWindowClass.
+ wchar_t class_name[100];
+ if (::GetClassName(hwnd, class_name, arraysize(class_name)) &&
+ lstrcmpi(kIeTabContentParentWindowClass, class_name) == 0) {
+ // We found the window. Return its handle and stop enumeration.
+ *window_handle = hwnd;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+HostWindowManager::HostWindowManager() : displaced_window_manager_(NULL) {
+}
+
+HostWindowManager::~HostWindowManager() {
+ // If we are holding a displaced_window_manager_, it means that
+ // OnDisplacedWindowManagerDestroyed has not been called yet, and therefore
+ // our DisplacedWindowDelegate might still be around, ready to invoke us.
+ // Fail fast to prevent a call into lala-land.
+ CHECK(displaced_window_manager_ == NULL);
+}
+
+void HostWindowManager::UpdateLayout() {
+ if (FindDisplacedWindow(NULL))
+ displaced_window_manager_->UpdateLayout();
+}
+
+bool HostWindowManager::FindDisplacedWindow(HWND old_window) {
+ if (displaced_window_manager_ == NULL ||
+ *displaced_window_manager_ == old_window) {
+ // Find the window which is the container for the HTML view (parent of
+ // the content). When the displaced window is destroyed, the new one might
+ // already exist, so we say "find a displaced window that is not this
+ // (old) one".
+ HWND displaced_window = old_window;
+ ::EnumChildWindows(*this, FindDisplacedWindowProc,
+ reinterpret_cast<LPARAM>(&displaced_window));
+
+ if (displaced_window == old_window) {
+ LOG(ERROR) << "Failed to locate IE renderer HWND to displace for "
+ << "Infobar installation.";
+ } else {
+ scoped_ptr<DisplacedWindowManager> displaced_window_manager(
+ new DisplacedWindowManager());
+ if (displaced_window_manager->Initialize(
+ displaced_window, new DisplacedWindowDelegate(this))) {
+ displaced_window_manager_ = displaced_window_manager.release();
+ }
+ }
+ }
+
+ return displaced_window_manager_ != NULL;
+}
diff --git a/chrome_frame/infobars/internal/host_window_manager.h b/chrome_frame/infobars/internal/host_window_manager.h
new file mode 100644
index 0000000..cf2abac
--- /dev/null
+++ b/chrome_frame/infobars/internal/host_window_manager.h
@@ -0,0 +1,48 @@
+// 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 CHROME_FRAME_INFOBARS_INTERNAL_HOST_WINDOW_MANAGER_H_
+#define CHROME_FRAME_INFOBARS_INTERNAL_HOST_WINDOW_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "chrome_frame/infobars/internal/subclassing_window_with_delegate.h"
+
+class DisplacedWindowManager;
+
+// HostWindowManager observes the HWND passed to Initialize and:
+// 1) Monitors the lifecycle of a specific child window (as identified by
+// FindDisplacedWindow).
+// 2) Intercepts NCCALCSIZE events on the child window, allowing the client to
+// modify the child window's requested dimensions.
+// 3) Allows the client to request a recalculation of the child window's
+// dimensions (resulting in a callback as in [2]).
+//
+// See documentation of SubclasingWindowWithDelegate for further information.
+class HostWindowManager
+ : public SubclassingWindowWithDelegate<HostWindowManager> {
+ public:
+ HostWindowManager();
+ virtual ~HostWindowManager();
+
+ // Triggers an immediate re-evaluation of the dimensions of the displaced
+ // window. Delegate::AdjustDisplacedWindowDimensions will be called with the
+ // natural dimensions of the displaced window.
+ void UpdateLayout();
+
+ private:
+ class DisplacedWindowDelegate;
+ friend class DisplacedWindowDelegate;
+
+ // Finds the window to be displaced and instantiate a DisplacedWindowManager
+ // for it if one does not already exist. Returns true if
+ // displaced_window_manager_ is non-NULL at the end of the call.
+ bool FindDisplacedWindow(HWND old_window);
+
+ // Subclasses and observes changes to the displaced window.
+ DisplacedWindowManager* displaced_window_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostWindowManager);
+}; // class HostWindowManager
+
+#endif // CHROME_FRAME_INFOBARS_INTERNAL_HOST_WINDOW_MANAGER_H_
diff --git a/chrome_frame/infobars/internal/infobar_window.cc b/chrome_frame/infobars/internal/infobar_window.cc
new file mode 100644
index 0000000..92c332a
--- /dev/null
+++ b/chrome_frame/infobars/internal/infobar_window.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.
+//
+// Implementation of the manager for infobar windows.
+
+#include "chrome_frame/infobars/internal/infobar_window.h"
+
+#include <algorithm>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "chrome_frame/function_stub.h"
+
+namespace {
+
+// length of each step when opening or closing
+const UINT kInfobarSlidingTimerIntervalMs = 50U;
+// pixels per step, when opening or closing
+const int kInfobarSlideOpenStep = 2;
+const int kInfobarSlideCloseStep = 6;
+
+} // namespace
+
+void OnSliderTimer(InfobarWindow::Host* host) {
+ host->UpdateLayout();
+}
+
+InfobarWindow::InfobarWindow(InfobarType type)
+ : type_(type),
+ host_(NULL),
+ target_height_(0),
+ initial_height_(0),
+ current_height_(0),
+ current_width_(0),
+ timer_id_(0),
+ timer_stub_(NULL),
+ frame_impl_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ DCHECK(type_ >= FIRST_INFOBAR_TYPE);
+ DCHECK(type_ < END_OF_INFOBAR_TYPE);
+}
+
+InfobarWindow::~InfobarWindow() {
+ StopTimer();
+ if (timer_stub_ != NULL)
+ FunctionStub::Destroy(timer_stub_);
+}
+
+void InfobarWindow::SetHost(Host* host) {
+ DCHECK(host_ == NULL);
+ DCHECK(timer_stub_ == NULL);
+ DCHECK(host != NULL);
+ host_ = host;
+ timer_stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(host),
+ OnSliderTimer);
+}
+
+bool InfobarWindow::Show(InfobarContent* content) {
+ DCHECK(host_ != NULL);
+ if (host_ == NULL)
+ return false;
+
+ scoped_ptr<InfobarContent> new_content(content);
+ content_.reset();
+
+ if (!new_content->InstallInFrame(&frame_impl_))
+ return false;
+
+ // Force a call to ReserveSpace, which will capture the width of the displaced
+ // window.
+ if (current_width_ == 0)
+ host_->UpdateLayout();
+ if (current_width_ == 0)
+ return false; // Might not be any displaced window.. then we can't display.
+
+ content_.swap(new_content);
+ StartSlidingTowards(content_->GetDesiredSize(current_width_, 0));
+
+ return true;
+}
+
+void InfobarWindow::Hide() {
+ DCHECK(host_ != NULL);
+ if (host_ == NULL)
+ return;
+
+ StartSlidingTowards(0);
+}
+
+void InfobarWindow::ReserveSpace(RECT* rect) {
+ DCHECK(rect);
+ DCHECK(host_ != NULL);
+ if (rect == NULL || host_ == NULL)
+ return;
+
+ current_width_ = rect->right - rect->left;
+ current_height_ = CalculateHeight();
+
+ RECT infobar_rect = *rect;
+
+ switch (type_) {
+ case TOP_INFOBAR:
+ infobar_rect.bottom = rect->top + current_height_;
+ rect->top = std::min(rect->bottom, infobar_rect.bottom);
+ break;
+ case BOTTOM_INFOBAR:
+ infobar_rect.top = rect->bottom - current_height_;
+ rect->bottom = std::max(rect->top, infobar_rect.top);
+ break;
+ default:
+ NOTREACHED() << "Unknown InfobarType value.";
+ break;
+ }
+
+ if (content_ != NULL)
+ content_->SetDimensions(infobar_rect);
+
+ // Done sliding?
+ if (current_height_ == target_height_) {
+ StopTimer();
+ if (current_height_ == 0)
+ content_.reset();
+ }
+}
+
+void InfobarWindow::StartSlidingTowards(int target_height) {
+ initial_height_ = current_height_;
+ target_height_ = target_height;
+
+ if (StartTimer())
+ slide_start_ = base::Time::Now();
+ else
+ slide_start_ = base::Time(); // NULL time means don't slide, resize now
+
+ // Trigger an immediate re-laying out. The timer will handle remaining steps.
+ host_->UpdateLayout();
+}
+
+bool InfobarWindow::StartTimer() {
+ timer_id_ = ::SetTimer(NULL,
+ timer_id_,
+ kInfobarSlidingTimerIntervalMs,
+ reinterpret_cast<TIMERPROC>(timer_stub_->code()));
+
+ DPLOG_IF(ERROR, timer_id_ == 0) << "Failure in SetTimer.";
+
+ return timer_id_ != 0;
+}
+
+void InfobarWindow::StopTimer() {
+ ::KillTimer(NULL, timer_id_);
+}
+
+int InfobarWindow::CalculateHeight() {
+ if (slide_start_.is_null())
+ return target_height_;
+
+ base::TimeDelta elapsed = base::Time::Now() - slide_start_;
+ int elapsed_steps = static_cast<int>(elapsed.InMilliseconds()) /
+ kInfobarSlidingTimerIntervalMs;
+
+ if (initial_height_ < target_height_) {
+ return std::min(initial_height_ + elapsed_steps * kInfobarSlideOpenStep,
+ target_height_);
+ } else if (initial_height_ > target_height_) {
+ return std::max(initial_height_ - elapsed_steps * kInfobarSlideCloseStep,
+ target_height_);
+ } else {
+ return target_height_;
+ }
+}
+
+InfobarWindow::FrameImpl::FrameImpl(InfobarWindow* infobar_window)
+ : infobar_window_(infobar_window) {
+}
+
+HWND InfobarWindow::FrameImpl::GetFrameWindow() {
+ return infobar_window_->host_->GetContainerWindow();
+}
+
+void InfobarWindow::FrameImpl::CloseInfobar() {
+ infobar_window_->Hide();
+}
diff --git a/chrome_frame/infobars/internal/infobar_window.h b/chrome_frame/infobars/internal/infobar_window.h
new file mode 100644
index 0000000..cc81bcc
--- /dev/null
+++ b/chrome_frame/infobars/internal/infobar_window.h
@@ -0,0 +1,108 @@
+// 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 CHROME_FRAME_INFOBARS_INTERNAL_INFOBAR_WINDOW_H_
+#define CHROME_FRAME_INFOBARS_INTERNAL_INFOBAR_WINDOW_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+
+#include "chrome_frame/infobars/infobar_content.h"
+#include "chrome_frame/infobars/infobar_manager.h"
+
+struct FunctionStub;
+
+// Manages the display of an InfobarContent instance within a container window.
+// Positions the infobar content by displacing other "natural" content of the
+// window (see ReserveSpace). Allows positioning either above or below the
+// natural content.
+class InfobarWindow {
+ public:
+ // Integrates the InfobarWindow with its environment.
+ class Host {
+ public:
+ virtual ~Host() {}
+
+ // Returns a handle to the window within which infobar content should be
+ // created. All windows associated with the infobar content should be
+ // descendants of the container window.
+ virtual HWND GetContainerWindow() = 0;
+
+ // Triggers an immediate re-evaluation of the dimensions of the displaced
+ // content. InfobarWindow::ReserveSpace will be called with the natural
+ // dimensions of the displaced content.
+ virtual void UpdateLayout() = 0;
+ }; // class Host
+
+ explicit InfobarWindow(InfobarType type);
+ ~InfobarWindow();
+
+ void SetHost(Host* host);
+
+ // Shows the supplied content in this InfobarWindow. Normally,
+ // InfobarContent::InstallInFrame will be called with an InfobarContent::Frame
+ // instance the content may use to interact with the InfobarWindow.
+ //
+ // InfobarContent is deleted when the InfobarWindow is finished with the
+ // content (either through failure or when successfully hidden).
+ bool Show(InfobarContent* content);
+
+ // Hides the infobar.
+ void Hide();
+
+ // Receives the total space requested by the displaced content and subtracts
+ // any space required by this infobar. Passes the reserved dimensions to
+ // InfobarContent::SetDimensions.
+ void ReserveSpace(RECT* rect);
+
+ private:
+ // Provides InfobarContent with access to this InfobarWindow.
+ class FrameImpl : public InfobarContent::Frame {
+ public:
+ explicit FrameImpl(InfobarWindow* infobar_window);
+
+ // InfobarContent::Frame implementation
+ virtual HWND GetFrameWindow();
+ virtual void CloseInfobar();
+
+ private:
+ InfobarWindow* infobar_window_;
+ DISALLOW_COPY_AND_ASSIGN(FrameImpl);
+ }; // class FrameImpl
+
+ // Sets up our state to show or hide and calls Host::UpdateLayout to cause a
+ // call to ReserveSpace. Sets up a timer to periodically call UpdateLayout.
+ void StartSlidingTowards(int height);
+
+ // Based on the initial height, how long (and if) we have been sliding, and
+ // the target height, decides what the current height should be.
+ int CalculateHeight();
+
+ // Manage a timer that repeatedly calls Host::UpdateLayout
+ bool StartTimer();
+ void StopTimer();
+
+ scoped_ptr<InfobarContent> content_;
+ Host* host_;
+ FrameImpl frame_impl_;
+ InfobarType type_;
+ int current_width_;
+ int current_height_;
+
+ // These variables control our state for sliding and are used to calculate
+ // the desired height at any given time.
+ base::Time slide_start_; // When we started sliding, or the null time if we
+ // are not sliding.
+ int initial_height_; // Where we started sliding from
+ int target_height_; // Where we are sliding to
+
+ // ID and thunk for the slide-effect timer
+ int timer_id_;
+ FunctionStub* timer_stub_;
+
+ DISALLOW_COPY_AND_ASSIGN(InfobarWindow);
+}; // class InfobarWindow
+
+#endif // CHROME_FRAME_INFOBARS_INTERNAL_INFOBAR_WINDOW_H_
diff --git a/chrome_frame/infobars/internal/subclassing_window_with_delegate.h b/chrome_frame/infobars/internal/subclassing_window_with_delegate.h
new file mode 100644
index 0000000..0321f2c9
--- /dev/null
+++ b/chrome_frame/infobars/internal/subclassing_window_with_delegate.h
@@ -0,0 +1,107 @@
+// 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 CHROME_FRAME_INFOBARS_INTERNAL_SUBCLASSING_WINDOW_WITH_DELEGATE_H_
+#define CHROME_FRAME_INFOBARS_INTERNAL_SUBCLASSING_WINDOW_WITH_DELEGATE_H_
+
+#include <atlbase.h>
+#include <atlcrack.h>
+#include <atlwin.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "chrome_frame/utils.h"
+
+// Implements behavior common to HostWindowManager and DisplacedWindowManager.
+template<typename T> class SubclassingWindowWithDelegate
+ : public CWindowImpl<T> {
+ public:
+ // Allows clients to modify the dimensions of the displaced window.
+ // Through its destructor, allows clients to know when the subclassed window
+ // is destroyed.
+ class Delegate {
+ public:
+ // The delegate will be deleted when the subclassed window is destroyed.
+ virtual ~Delegate() {}
+
+ // Receives the natural dimensions of the displaced window. Upon return,
+ // rect should contain the adjusted dimensions (i.e., possibly reduced to
+ // accomodate an infobar).
+ virtual void AdjustDisplacedWindowDimensions(RECT* rect) = 0;
+ }; // class Delegate
+
+ SubclassingWindowWithDelegate() {}
+
+ // Returns true if the window is successfully subclassed, in which case this
+ // instance will take responsibility for its own destruction when the window
+ // is destroyed. If this method returns false, the caller should delete the
+ // instance immediately.
+ //
+ // Takes ownership of delegate in either case, deleting it when the window
+ // is destroyed (or immediately, in case of failure).
+ bool Initialize(HWND hwnd, Delegate* delegate) {
+ DCHECK(delegate != NULL);
+ DCHECK(delegate_ == NULL);
+ scoped_ptr<Delegate> new_delegate(delegate);
+
+ if (!::IsWindow(hwnd) || !SubclassWindow(hwnd)) {
+ PLOG(ERROR) << "Failed to subclass an HWND";
+ return false;
+ }
+
+ // Ensure we won't be unloaded while our window proc is attached to the tab
+ // window.
+ PinModule();
+
+ delegate_.swap(new_delegate);
+
+ return true;
+ }
+
+ // Returns the delegate associated with the specified window, if any.
+ static Delegate* GetDelegateForHwnd(HWND hwnd) {
+ return reinterpret_cast<Delegate*>(
+ ::SendMessage(hwnd, RegisterGetDelegateMessage(), NULL, NULL));
+ }
+
+ BEGIN_MSG_MAP_EX(SubclassingWindowWithDelegate)
+ MESSAGE_HANDLER(RegisterGetDelegateMessage(), OnGetDelegate)
+ MSG_WM_DESTROY(OnDestroy)
+ END_MSG_MAP()
+
+ // This instance is now free to be deleted.
+ virtual void OnFinalMessage(HWND hwnd) {
+ delete this;
+ }
+
+ protected:
+ scoped_ptr<Delegate>& delegate() { return delegate_; }
+
+ private:
+ // Registers a unique ID for our custom event.
+ static UINT RegisterGetDelegateMessage() {
+ static UINT message_id(
+ RegisterWindowMessage(L"SubclassingWindowWithDelegate::OnGetDelegate"));
+ return message_id;
+ }
+
+ // The subclassed window has been destroyed. Delete the delegate. We will
+ // delete ourselves in OnFinalMessage.
+ void OnDestroy() {
+ delegate_.reset();
+ }
+
+ LRESULT OnGetDelegate(UINT message,
+ WPARAM wparam,
+ LPARAM lparam,
+ BOOL& handled) {
+ return reinterpret_cast<LRESULT>(delegate_.get());
+ }
+
+ scoped_ptr<Delegate> delegate_;
+ DISALLOW_COPY_AND_ASSIGN(SubclassingWindowWithDelegate);
+}; // class SubclassingWindowWithDelegate
+
+#endif // CHROME_FRAME_INFOBARS_INTERNAL_SUBCLASSING_WINDOW_WITH_DELEGATE_H_
diff --git a/chrome_frame/test/infobar_unittests.cc b/chrome_frame/test/infobar_unittests.cc
new file mode 100644
index 0000000..b84c79a
--- /dev/null
+++ b/chrome_frame/test/infobar_unittests.cc
@@ -0,0 +1,592 @@
+// 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 <atlbase.h>
+#include <atlapp.h>
+#include <atlcrack.h>
+#include <atlmisc.h>
+#include <atlwin.h>
+
+#include "base/string_number_conversions.h"
+#include "base/win_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+#include "chrome_frame/infobars/infobar_content.h"
+#include "chrome_frame/infobars/internal/displaced_window_manager.h"
+#include "chrome_frame/infobars/internal/host_window_manager.h"
+#include "chrome_frame/infobars/internal/infobar_window.h"
+#include "chrome_frame/infobars/internal/subclassing_window_with_delegate.h"
+#include "chrome_frame/test/chrome_frame_test_utils.h"
+
+DISABLE_RUNNABLE_METHOD_REFCOUNT(InfobarContent::Frame);
+DISABLE_RUNNABLE_METHOD_REFCOUNT(InfobarManager);
+
+namespace {
+
+RECT kInitialParentWindowRect = {20, 20, 300, 300};
+RECT kInitialChildWindowRect = {20, 20, 280, 280};
+
+MATCHER_P(EqualRect, expected, "") {
+ return ::EqualRect(expected, arg);
+}
+
+ACTION_P2(RespondToNcCalcSize, result, rect) {
+ *reinterpret_cast<RECT*>(arg1) = *rect;
+ return result;
+}
+
+ACTION_P4(RespondToNcCalcSize, result, rect1, rect2, rect3) {
+ reinterpret_cast<RECT*>(arg1)[0] = rect1;
+ reinterpret_cast<RECT*>(arg1)[1] = rect2;
+ reinterpret_cast<RECT*>(arg1)[2] = rect3;
+ return result;
+}
+
+class ParentTraits : public CFrameWinTraits {
+ public:
+ static const wchar_t* kClassName;
+}; // class ParentTraits
+
+class ChildTraits : public CControlWinTraits {
+ public:
+ static const wchar_t* kClassName;
+}; // class ChildTraits
+
+const wchar_t* ParentTraits::kClassName = NULL;
+const wchar_t* ChildTraits::kClassName = L"Shell DocObject View";
+
+template<typename TRAITS> class MockWindow
+ : public CWindowImpl<MockWindow<TRAITS>, CWindow, TRAITS> {
+ public:
+ virtual ~MockWindow() {
+ if (IsWindow())
+ DestroyWindow();
+ }
+ MOCK_METHOD1(OnCreate, int(LPCREATESTRUCT lpCreateStruct));
+ MOCK_METHOD0(OnDestroy, void());
+ MOCK_METHOD2(OnSize, void(UINT nType, CSize size));
+ MOCK_METHOD1(OnMove, void(CPoint ptPos));
+ MOCK_METHOD2(OnNcCalcSize, LRESULT(BOOL bCalcValidRects, LPARAM lParam));
+ DECLARE_WND_CLASS(TRAITS::kClassName);
+ BEGIN_MSG_MAP_EX(MockWindow)
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_DESTROY(OnDestroy)
+ MSG_WM_SIZE(OnSize)
+ MSG_WM_MOVE(OnMove)
+ MSG_WM_NCCALCSIZE(OnNcCalcSize)
+ END_MSG_MAP()
+}; // class MockWindow
+
+typedef MockWindow<ParentTraits> MockTopLevelWindow;
+typedef MockWindow<ChildTraits> MockChildWindow;
+
+class MockWindowSubclass
+ : public SubclassingWindowWithDelegate<MockWindowSubclass> {
+ public:
+ MOCK_METHOD2(OnNcCalcSize, LRESULT(BOOL bCalcValidRects, LPARAM lParam));
+ BEGIN_MSG_MAP_EX(MockWindowSubclass)
+ MSG_WM_NCCALCSIZE(OnNcCalcSize)
+ CHAIN_MSG_MAP(SubclassingWindowWithDelegate<MockWindowSubclass>)
+ END_MSG_MAP()
+ virtual ~MockWindowSubclass() { Die(); }
+ MOCK_METHOD0(Die, void());
+}; // class MockWindowSubclass
+
+template<typename T> class MockDelegate
+ : public SubclassingWindowWithDelegate<T>::Delegate {
+ public:
+ virtual ~MockDelegate() { Die(); }
+ MOCK_METHOD0(Die, void());
+ MOCK_METHOD1(AdjustDisplacedWindowDimensions, void(RECT* rect));
+}; // clas MockDelegate
+
+template<typename T> T* Initialize(T* t,
+ HWND hwnd,
+ typename T::Delegate* delegate) {
+ if (t->Initialize(hwnd, delegate)) {
+ return t;
+ } else {
+ delete t;
+ return NULL;
+ }
+}
+
+}; // namespace
+
+TEST(InfobarsSubclassingWindowWithDelegateTest, BasicTest) {
+ testing::NiceMock<MockTopLevelWindow> window;
+ ASSERT_TRUE(window.Create(NULL, kInitialParentWindowRect) != NULL);
+
+ HWND hwnd = static_cast<HWND>(window);
+
+ ASSERT_TRUE(MockWindowSubclass::GetDelegateForHwnd(hwnd) == NULL);
+
+ MockDelegate<MockWindowSubclass>* delegate =
+ new testing::StrictMock<MockDelegate<MockWindowSubclass> >();
+ MockWindowSubclass* swwd = Initialize(
+ new testing::StrictMock<MockWindowSubclass>(), hwnd, delegate);
+ ASSERT_TRUE(swwd != NULL);
+ ASSERT_EQ(static_cast<MockWindowSubclass::Delegate*>(delegate),
+ MockWindowSubclass::GetDelegateForHwnd(hwnd));
+
+ // Since expectations are only validated upon object destruction, this test
+ // would normally pass even if the expected deletions never happened.
+ // By expecting another call, in sequence, after the deletions, we force a
+ // failure if those deletions don't occur.
+ testing::MockFunction<void(std::string check_point_name)> check;
+
+ {
+ testing::InSequence s;
+ EXPECT_CALL(*delegate, Die());
+ EXPECT_CALL(*swwd, Die());
+ EXPECT_CALL(check, Call("checkpoint"));
+ }
+
+ window.DestroyWindow();
+
+ check.Call("checkpoint");
+
+ ASSERT_TRUE(MockWindowSubclass::GetDelegateForHwnd(hwnd) == NULL);
+}
+
+TEST(InfobarsSubclassingWindowWithDelegateTest, InvalidHwndTest) {
+ testing::NiceMock<MockTopLevelWindow> window;
+ ASSERT_TRUE(window.Create(NULL, kInitialParentWindowRect) != NULL);
+
+ HWND hwnd = static_cast<HWND>(window);
+
+ window.DestroyWindow();
+
+ MockDelegate<MockWindowSubclass>* delegate =
+ new testing::StrictMock<MockDelegate<MockWindowSubclass> >();
+ MockWindowSubclass* swwd = new testing::StrictMock<MockWindowSubclass>();
+
+ testing::MockFunction<void(std::string check_point_name)> check;
+
+ {
+ testing::InSequence s;
+ EXPECT_CALL(*delegate, Die());
+ EXPECT_CALL(check, Call("checkpoint"));
+ EXPECT_CALL(*swwd, Die());
+ }
+
+ ASSERT_FALSE(swwd->Initialize(hwnd, delegate));
+ check.Call("checkpoint"); // Make sure the delegate has been deleted
+ delete swwd;
+
+ ASSERT_TRUE(MockWindowSubclass::GetDelegateForHwnd(hwnd) == NULL);
+}
+
+template <typename WINDOW, typename DELEGATE> void ExpectNcCalcSizeSequence(
+ WINDOW* mock_window,
+ DELEGATE* delegate,
+ RECT* natural_rect,
+ RECT* modified_rect) {
+ testing::InSequence s;
+ EXPECT_CALL(*mock_window, OnNcCalcSize(true, testing::_)).WillOnce(
+ RespondToNcCalcSize(0, natural_rect));
+ EXPECT_CALL(*delegate,
+ AdjustDisplacedWindowDimensions(EqualRect(natural_rect)))
+ .WillOnce(testing::SetArgumentPointee<0>(*modified_rect));
+ EXPECT_CALL(*mock_window, OnMove(CPoint(modified_rect->left,
+ modified_rect->top)));
+ EXPECT_CALL(*mock_window,
+ OnSize(0, CSize(modified_rect->right - modified_rect->left,
+ modified_rect->bottom - modified_rect->top)));
+}
+
+template <typename WINDOW, typename DELEGATE, typename MANAGER>
+ void DoNcCalcSizeSequence(WINDOW* mock_window,
+ DELEGATE* delegate,
+ MANAGER* manager) {
+ RECT natural_rects[] = { {0, 0, 100, 100}, {10, 10, 120, 120} };
+ RECT modified_rects[] = { {10, 5, 90, 95}, {25, 35, 80, 70} };
+
+ ExpectNcCalcSizeSequence(
+ mock_window, delegate, &natural_rects[0], &natural_rects[0]);
+ // The first time through, trigger the sizing via the manager.
+ // This is required for the HostWindowManager, since it only looks up
+ // and subclasses the displaced window on demand.
+ manager->UpdateLayout();
+
+ ExpectNcCalcSizeSequence(
+ mock_window, delegate, &natural_rects[1], &natural_rects[1]);
+ // The second time through, trigger it through the original window.
+ // By now, we expect to be observing the window in all cases.
+ ::SetWindowPos(static_cast<HWND>(*mock_window),
+ NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+ SWP_FRAMECHANGED);
+
+ testing::Mock::VerifyAndClearExpectations(mock_window);
+ testing::Mock::VerifyAndClearExpectations(delegate);
+}
+
+TEST(InfobarsDisplacedWindowManagerTest, BasicTest) {
+ testing::NiceMock<MockTopLevelWindow> window;
+ ASSERT_TRUE(window.Create(NULL, kInitialParentWindowRect) != NULL);
+
+ MockDelegate<DisplacedWindowManager>* delegate =
+ new testing::StrictMock<MockDelegate<DisplacedWindowManager> >();
+
+ DisplacedWindowManager* dwm = new DisplacedWindowManager();
+ ASSERT_TRUE(dwm->Initialize(static_cast<HWND>(window), delegate));
+ ASSERT_NO_FATAL_FAILURE(DoNcCalcSizeSequence(&window, delegate, dwm));
+
+ EXPECT_CALL(*delegate, Die());
+ window.DestroyWindow();
+}
+
+TEST(InfobarsHostWindowManagerTest, BasicTest) {
+ testing::NiceMock<MockTopLevelWindow> window;
+ ASSERT_TRUE(window.Create(NULL, kInitialParentWindowRect) != NULL);
+ testing::NiceMock<MockChildWindow> child_window;
+ ASSERT_TRUE(child_window.Create(window, kInitialChildWindowRect) != NULL);
+ testing::NiceMock<MockChildWindow> child_window2;
+ testing::NiceMock<MockChildWindow> child_window3;
+
+ MockDelegate<HostWindowManager>* delegate =
+ new testing::StrictMock<MockDelegate<HostWindowManager> >();
+
+ HostWindowManager* hwm = new HostWindowManager();
+
+ ASSERT_TRUE(hwm->Initialize(static_cast<HWND>(window), delegate));
+ ASSERT_NO_FATAL_FAILURE(DoNcCalcSizeSequence(&child_window, delegate, hwm));
+
+ // First, destroy window 1 and subsequently create window 2
+ child_window.DestroyWindow();
+
+ ASSERT_TRUE(child_window2.Create(window, kInitialChildWindowRect) != NULL);
+ ASSERT_NO_FATAL_FAILURE(DoNcCalcSizeSequence(&child_window2, delegate, hwm));
+
+ // Next, create window 3 just before destroying window 2
+ RECT natural_rect = {10, 15, 40, 45};
+ RECT modified_rect = {15, 20, 35, 40};
+
+ ASSERT_TRUE(child_window3.Create(window, kInitialChildWindowRect) != NULL);
+ ExpectNcCalcSizeSequence(
+ &child_window3, delegate, &natural_rect, &natural_rect);
+ child_window2.DestroyWindow();
+
+ ASSERT_NO_FATAL_FAILURE(DoNcCalcSizeSequence(&child_window3, delegate, hwm));
+
+ EXPECT_CALL(*delegate, Die());
+ window.DestroyWindow();
+}
+
+// Must be declared in same namespace as RECT
+void PrintTo(const RECT& rect, ::std::ostream* os) {
+ *os << "{" << rect.left << ", " << rect.top << ", " << rect.right << ", "
+ << rect.bottom << "}";
+}
+
+namespace {
+
+class MockHost : public InfobarWindow::Host {
+ public:
+ MockHost(InfobarWindow* infobar_window,
+ const RECT& natural_dimensions,
+ HWND hwnd)
+ : infobar_window_(infobar_window),
+ natural_dimensions_(natural_dimensions),
+ hwnd_(hwnd) {
+ }
+
+ void SetNaturalDimensions(const RECT& natural_dimensions) {
+ natural_dimensions_ = natural_dimensions;
+ UpdateLayout();
+ }
+
+ virtual HWND GetContainerWindow() {
+ return hwnd_;
+ }
+
+ virtual void UpdateLayout() {
+ RECT temp(natural_dimensions_);
+ infobar_window_->ReserveSpace(&temp);
+ CheckReservedSpace(&temp);
+ }
+
+ MOCK_METHOD0(Die, void(void));
+
+ // Convenience method for checking the result of InfobarWindow::ReserveSpace
+ MOCK_METHOD1(CheckReservedSpace, void(RECT* rect));
+
+ private:
+ InfobarWindow* infobar_window_;
+ RECT natural_dimensions_;
+ HWND hwnd_;
+}; // class MockHost
+
+class MockInfobarContent : public InfobarContent {
+ public:
+ virtual ~MockInfobarContent() { Die(); }
+
+ MOCK_METHOD1(InstallInFrame, bool(Frame* frame));
+ MOCK_METHOD1(SetDimensions, void(const RECT& dimensions));
+ MOCK_METHOD2(GetDesiredSize, size_t(size_t width, size_t height));
+ MOCK_METHOD0(Die, void(void));
+}; // class MockInfobarContent
+
+MATCHER_P(
+ RectHeightIsLTEToRectXHeight, rect_x, "height is <= to height of rect x") {
+ return arg.bottom - arg.top <= rect_x->bottom - rect_x->top;
+}
+
+MATCHER_P(
+ RectHeightIsGTEToRectXHeight, rect_x, "height is >= to height of rect x") {
+ return arg.bottom - arg.top >= rect_x->bottom - rect_x->top;
+}
+
+MATCHER_P3(RectIsTopXToYOfRectZ, x, y, rect_z, "is top x to y of rect z" ) {
+ return rect_z->top == arg.top &&
+ rect_z->left == arg.left &&
+ rect_z->right == arg.right &&
+ arg.bottom - arg.top >= x &&
+ arg.bottom - arg.top <= y;
+}
+
+MATCHER_P2(RectAddedToRectXMakesRectY,
+ rect_x,
+ rect_y,
+ "rect added to rect x makes rect y") {
+ if (::IsRectEmpty(rect_x))
+ return ::EqualRect(arg, rect_y);
+
+ if (::IsRectEmpty(arg))
+ return ::EqualRect(rect_x, rect_y);
+
+ // Either they are left and right slices, or top and bottom slices
+ if (!((rect_x->left == rect_y->left && rect_x->right == rect_y->right &&
+ arg->left == rect_y->left && arg->right == rect_y->right) ||
+ (rect_x->top == rect_y->top && rect_x->bottom== rect_y->bottom &&
+ arg->top == rect_y->top && arg->bottom == rect_y->bottom))) {
+ return false;
+ }
+
+ RECT expected_arg;
+
+ if (!::SubtractRect(&expected_arg, rect_y, rect_x))
+ return false; // Given above checks, the difference should not be empty
+
+ return ::EqualRect(arg, &expected_arg);
+}
+
+MATCHER_P(FrameHwndIs, hwnd, "") {
+ return arg != NULL && arg->GetFrameWindow() == hwnd;
+}
+
+ACTION_P(CheckSetFlag, flag) {
+ ASSERT_FALSE(*flag);
+ *flag = true;
+}
+
+ACTION_P(ResetFlag, flag) {
+ *flag = false;
+}
+
+ACTION_P2(AsynchronousCloseOnFrame, loop, frame) {
+ loop->PostDelayedTask(
+ FROM_HERE,
+ NewRunnableMethod(*frame, &InfobarContent::Frame::CloseInfobar),
+ 0);
+}
+
+ACTION_P2(AsynchronousHideOnManager, loop, manager) {
+ loop->PostDelayedTask(
+ FROM_HERE,
+ NewRunnableMethod(manager, &InfobarManager::Hide, TOP_INFOBAR),
+ 0);
+}
+
+}; // namespace
+
+// The test ensures that the content is sized at least once in each of the
+// following ranges while fully opening and closing:
+//
+// [0, infobar_height / 2)
+// [infobar_height / 2, infobar_height)
+// [infobar_height, infobar_height]
+// (infobar_height / 2, infobar_height]
+// [0, infobar_height / 2]
+//
+// If the test turns out to be flaky (i.e., because timers are not firing
+// frequently enough to hit all the ranges), increasing the infobar_height
+// should increase the margin (by increasing the time spent in each range).
+TEST(InfobarsInfobarWindowTest, SlidingTest) {
+ int infobar_height = 40;
+
+ chrome_frame_test::TimedMsgLoop message_loop;
+
+ RECT natural_dimensions = {10, 20, 90, 100 + infobar_height};
+
+ // Used to verify that the last RECT given to SetDimensions is the same RECT
+ // reserved by ReserveSpace.
+ RECT current_infobar_dimensions = {0, 0, 0, 0}; // Used to verify that the
+
+ // Used to make sure that each SetDimensions is matched by a return from
+ // ReserveSpace.
+ bool pending_reserve_space = false;
+
+ InfobarWindow infobar_window(TOP_INFOBAR);
+
+ testing::NiceMock<MockTopLevelWindow> window;
+ ASSERT_TRUE(window.Create(NULL, kInitialParentWindowRect));
+ HWND hwnd = static_cast<HWND>(window);
+ MockInfobarContent* content = new MockInfobarContent();
+ scoped_ptr<MockHost> host(new MockHost(&infobar_window,
+ natural_dimensions,
+ hwnd));
+
+ infobar_window.SetHost(host.get());
+
+ // Used to ensure that GetDesiredSize is only called on an installed
+ // InfobarContent.
+ testing::Expectation installed;
+
+ // Will hold the frame given to us in InfobarContent::InstallInFrame.
+ InfobarContent::Frame* frame = NULL;
+
+ // We could get any number of calls to UpdateLayout. Make sure that, each
+ // time, the space reserved by the InfobarWindow equals the space offered to
+ // the InfobarContent.
+ EXPECT_CALL(*host, CheckReservedSpace(RectAddedToRectXMakesRectY(
+ &current_infobar_dimensions, &natural_dimensions)))
+ .Times(testing::AnyNumber())
+ .WillRepeatedly(ResetFlag(&pending_reserve_space));
+
+ testing::MockFunction<void(std::string check_point_name)> check;
+
+ {
+ testing::InSequence s;
+ // During Show(), we get an InstallInFrame
+ installed = EXPECT_CALL(*content, InstallInFrame(FrameHwndIs(hwnd)))
+ .WillOnce(testing::DoAll(testing::SaveArg<0>(&frame),
+ testing::Return(true)));
+
+ // Allow a call to SetDimensions before InstallInFrame returns.
+ EXPECT_CALL(*content, SetDimensions(testing::AllOf(
+ RectIsTopXToYOfRectZ(0, infobar_height / 2 - 1, &natural_dimensions),
+ RectHeightIsGTEToRectXHeight(&current_infobar_dimensions))))
+ .Times(testing::AnyNumber()).WillRepeatedly(testing::DoAll(
+ testing::SaveArg<0>(&current_infobar_dimensions),
+ CheckSetFlag(&pending_reserve_space)));
+
+ EXPECT_CALL(check, Call("returned from Show"));
+
+ EXPECT_CALL(*content, SetDimensions(testing::AllOf(
+ RectIsTopXToYOfRectZ(0, infobar_height / 2 - 1, &natural_dimensions),
+ RectHeightIsGTEToRectXHeight(&current_infobar_dimensions))))
+ .Times(testing::AtLeast(1)).WillRepeatedly(testing::DoAll(
+ testing::SaveArg<0>(&current_infobar_dimensions),
+ CheckSetFlag(&pending_reserve_space)));
+ EXPECT_CALL(*content, SetDimensions(testing::AllOf(
+ RectIsTopXToYOfRectZ(infobar_height / 2,
+ infobar_height - 1,
+ &natural_dimensions),
+ RectHeightIsGTEToRectXHeight(&current_infobar_dimensions))))
+ .Times(testing::AtLeast(1)).WillRepeatedly(testing::DoAll(
+ testing::SaveArg<0>(&current_infobar_dimensions),
+ CheckSetFlag(&pending_reserve_space)));
+ EXPECT_CALL(*content, SetDimensions(
+ RectIsTopXToYOfRectZ(infobar_height,
+ infobar_height,
+ &natural_dimensions)))
+ .WillOnce(testing::DoAll(
+ testing::SaveArg<0>(&current_infobar_dimensions),
+ CheckSetFlag(&pending_reserve_space),
+ AsynchronousCloseOnFrame(&message_loop, &frame)));
+
+ EXPECT_CALL(*content, SetDimensions(testing::AllOf(
+ RectIsTopXToYOfRectZ(infobar_height / 2 + 1,
+ infobar_height,
+ &natural_dimensions),
+ RectHeightIsLTEToRectXHeight(&current_infobar_dimensions))))
+ .Times(testing::AtLeast(1)).WillRepeatedly(testing::DoAll(
+ testing::SaveArg<0>(&current_infobar_dimensions),
+ CheckSetFlag(&pending_reserve_space)));
+ EXPECT_CALL(*content, SetDimensions(testing::AllOf(
+ RectIsTopXToYOfRectZ(0, infobar_height / 2, &natural_dimensions),
+ RectHeightIsLTEToRectXHeight(&current_infobar_dimensions))))
+ .Times(testing::AtLeast(1)).WillRepeatedly(testing::DoAll(
+ testing::SaveArg<0>(&current_infobar_dimensions),
+ CheckSetFlag(&pending_reserve_space)));
+
+ EXPECT_CALL(*content, Die()).WillOnce(QUIT_LOOP(message_loop));
+ }
+
+ EXPECT_CALL(*content, GetDesiredSize(80, 0))
+ .Times(testing::AnyNumber()).After(installed)
+ .WillRepeatedly(testing::Return(infobar_height));
+
+ ASSERT_NO_FATAL_FAILURE(infobar_window.Show(content));
+
+ ASSERT_NO_FATAL_FAILURE(check.Call("returned from Show"));
+
+ ASSERT_NO_FATAL_FAILURE(message_loop.RunFor(10)); // seconds
+
+ window.DestroyWindow();
+
+ ASSERT_FALSE(message_loop.WasTimedOut());
+}
+
+TEST(InfobarsInfobarManagerTest, BasicTest) {
+ chrome_frame_test::TimedMsgLoop message_loop;
+
+ int infobar_height = 40;
+ RECT natural_dimensions = {10, 20, 90, 100 + infobar_height};
+
+ testing::NiceMock<MockTopLevelWindow> window;
+ ASSERT_TRUE(window.Create(NULL, kInitialParentWindowRect) != NULL);
+ testing::NiceMock<MockChildWindow> child_window;
+ ASSERT_TRUE(child_window.Create(window, kInitialChildWindowRect) != NULL);
+
+ HWND parent_hwnd = static_cast<HWND>(window);
+ HWND child_hwnd = static_cast<HWND>(child_window);
+
+ MockInfobarContent* content = new MockInfobarContent();
+ InfobarContent::Frame* frame = NULL;
+
+ InfobarManager* manager = InfobarManager::Get(parent_hwnd);
+ ASSERT_FALSE(manager == NULL);
+
+ EXPECT_CALL(*content, GetDesiredSize(80, 0))
+ .Times(testing::AnyNumber())
+ .WillRepeatedly(testing::Return(infobar_height));
+
+ EXPECT_CALL(child_window, OnNcCalcSize(true, testing::_))
+ .Times(testing::AnyNumber()).WillRepeatedly(
+ RespondToNcCalcSize(0, &natural_dimensions));
+
+ EXPECT_CALL(*content, InstallInFrame(FrameHwndIs(parent_hwnd)))
+ .WillOnce(testing::DoAll(testing::SaveArg<0>(&frame),
+ testing::Return(true)));
+
+ EXPECT_CALL(*content, SetDimensions(testing::Not(
+ RectIsTopXToYOfRectZ(infobar_height,
+ infobar_height,
+ &natural_dimensions)))).Times(testing::AnyNumber());
+
+ EXPECT_CALL(*content, SetDimensions(
+ RectIsTopXToYOfRectZ(infobar_height,
+ infobar_height,
+ &natural_dimensions)))
+ .Times(testing::AnyNumber())
+ .WillOnce(AsynchronousHideOnManager(&message_loop, manager))
+ .WillRepeatedly(testing::Return());
+
+ EXPECT_CALL(*content, Die()).WillOnce(QUIT_LOOP(message_loop));
+
+ ASSERT_TRUE(manager->Show(content, TOP_INFOBAR));
+
+ message_loop.RunFor(10); // seconds
+
+ window.DestroyWindow();
+
+ ASSERT_FALSE(message_loop.WasTimedOut());
+}
+
+// TODO(erikwright): Write test for variations on return from default
+// OnNcCalcValidRects