diff options
author | erikwright@chromium.org <erikwright@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-03 16:22:00 +0000 |
---|---|---|
committer | erikwright@chromium.org <erikwright@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-03 16:22:00 +0000 |
commit | d7b59a0c525807f1eaf0456c3db3b4f659bcccc4 (patch) | |
tree | e26dd48c52a7755ec84f9c44b8a8e03fc9d39dde /chrome_frame | |
parent | 385a2b73c0e717d27145521f7c3f557004e0453e (diff) | |
download | chromium_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.gyp | 11 | ||||
-rw-r--r-- | chrome_frame/infobars/infobar_content.h | 50 | ||||
-rw-r--r-- | chrome_frame/infobars/infobar_manager.cc | 102 | ||||
-rw-r--r-- | chrome_frame/infobars/infobar_manager.h | 50 | ||||
-rw-r--r-- | chrome_frame/infobars/internal/displaced_window_manager.cc | 42 | ||||
-rw-r--r-- | chrome_frame/infobars/internal/displaced_window_manager.h | 45 | ||||
-rw-r--r-- | chrome_frame/infobars/internal/host_window_manager.cc | 119 | ||||
-rw-r--r-- | chrome_frame/infobars/internal/host_window_manager.h | 48 | ||||
-rw-r--r-- | chrome_frame/infobars/internal/infobar_window.cc | 183 | ||||
-rw-r--r-- | chrome_frame/infobars/internal/infobar_window.h | 108 | ||||
-rw-r--r-- | chrome_frame/infobars/internal/subclassing_window_with_delegate.h | 107 | ||||
-rw-r--r-- | chrome_frame/test/infobar_unittests.cc | 592 |
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( + ¤t_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(¤t_infobar_dimensions)))) + .Times(testing::AnyNumber()).WillRepeatedly(testing::DoAll( + testing::SaveArg<0>(¤t_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(¤t_infobar_dimensions)))) + .Times(testing::AtLeast(1)).WillRepeatedly(testing::DoAll( + testing::SaveArg<0>(¤t_infobar_dimensions), + CheckSetFlag(&pending_reserve_space))); + EXPECT_CALL(*content, SetDimensions(testing::AllOf( + RectIsTopXToYOfRectZ(infobar_height / 2, + infobar_height - 1, + &natural_dimensions), + RectHeightIsGTEToRectXHeight(¤t_infobar_dimensions)))) + .Times(testing::AtLeast(1)).WillRepeatedly(testing::DoAll( + testing::SaveArg<0>(¤t_infobar_dimensions), + CheckSetFlag(&pending_reserve_space))); + EXPECT_CALL(*content, SetDimensions( + RectIsTopXToYOfRectZ(infobar_height, + infobar_height, + &natural_dimensions))) + .WillOnce(testing::DoAll( + testing::SaveArg<0>(¤t_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(¤t_infobar_dimensions)))) + .Times(testing::AtLeast(1)).WillRepeatedly(testing::DoAll( + testing::SaveArg<0>(¤t_infobar_dimensions), + CheckSetFlag(&pending_reserve_space))); + EXPECT_CALL(*content, SetDimensions(testing::AllOf( + RectIsTopXToYOfRectZ(0, infobar_height / 2, &natural_dimensions), + RectHeightIsLTEToRectXHeight(¤t_infobar_dimensions)))) + .Times(testing::AtLeast(1)).WillRepeatedly(testing::DoAll( + testing::SaveArg<0>(¤t_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 |