// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/aeropeek_manager.h"
#include <dwmapi.h>
#include <shobjidl.h>
#include "app/win/shell.h"
#include "base/command_line.h"
#include "base/memory/scoped_native_library.h"
#include "base/synchronization/waitable_event.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/scoped_hdc.h"
#include "base/win/windows_version.h"
#include "chrome/browser/app_icon_win.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/tab_contents/thumbnail_generator.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/installer/util/browser_distribution.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/backing_store.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_delegate.h"
#include "content/browser/tab_contents/tab_contents_view.h"
#include "skia/ext/image_operations.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/win/window_impl.h"
#include "ui/gfx/gdi_util.h"
#include "ui/gfx/icon_util.h"
#include "views/widget/widget_win.h"
namespace {
// Macros and COM interfaces used in this file.
// These interface declarations are copied from Windows SDK 7.
// TODO(hbono): Bug 16903: to be deleted when we use Windows SDK 7.
// Windows SDK 7 defines these macros only when _WIN32_WINNT >= 0x0601.
// Since Chrome currently sets _WIN32_WINNT to 0x0600, copy these defines here
// so we can use them.
#ifndef WM_DWMSENDICONICTHUMBNAIL
#define WM_DWMSENDICONICTHUMBNAIL 0x0323
#endif
#ifndef WM_DWMSENDICONICLIVEPREVIEWBITMAP
#define WM_DWMSENDICONICLIVEPREVIEWBITMAP 0x0326
#endif
// COM interfaces defined only in Windows SDK 7.
#ifndef __ITaskbarList2_INTERFACE_DEFINED__
#define __ITaskbarList2_INTERFACE_DEFINED__
// EXTERN_C const IID IID_ITaskbarList2;
MIDL_INTERFACE("602D4995-B13A-429b-A66E-1935E44F4317")
ITaskbarList2 : public ITaskbarList {
public:
virtual HRESULT STDMETHODCALLTYPE MarkFullscreenWindow(
/* [in] */ __RPC__in HWND hwnd,
/* [in] */ BOOL fFullscreen) = 0;
};
#endif /* __ITaskbarList2_INTERFACE_DEFINED__ */
#ifndef __ITaskbarList3_INTERFACE_DEFINED__
#define __ITaskbarList3_INTERFACE_DEFINED__
typedef struct tagTHUMBBUTTON {
DWORD dwMask;
UINT iId;
UINT iBitmap;
HICON hIcon;
WCHAR szTip[ 260 ];
DWORD dwFlags;
} THUMBBUTTON;
typedef struct tagTHUMBBUTTON *LPTHUMBBUTTON;
// THUMBBUTTON flags
#define THBF_ENABLED 0x0000
#define THBF_DISABLED 0x0001
#define THBF_DISMISSONCLICK 0x0002
#define THBF_NOBACKGROUND 0x0004
#define THBF_HIDDEN 0x0008
// THUMBBUTTON mask
#define THB_BITMAP 0x0001
#define THB_ICON 0x0002
#define THB_TOOLTIP 0x0004
#define THB_FLAGS 0x0008
#define THBN_CLICKED 0x1800
typedef /* [v1_enum] */ enum TBPFLAG {
TBPF_NOPROGRESS = 0,
TBPF_INDETERMINATE = 0x1,
TBPF_NORMAL = 0x2,
TBPF_ERROR = 0x4,
TBPF_PAUSED = 0x8
} TBPFLAG;
// EXTERN_C const IID IID_ITaskbarList3;
MIDL_INTERFACE("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")
ITaskbarList3 : public ITaskbarList2 {
public:
virtual HRESULT STDMETHODCALLTYPE SetProgressValue(
/* [in] */ __RPC__in HWND hwnd,
/* [in] */ ULONGLONG ullCompleted,
/* [in] */ ULONGLONG ullTotal) = 0;
virtual HRESULT STDMETHODCALLTYPE SetProgressState(
/* [in] */ __RPC__in HWND hwnd,
/* [in] */ TBPFLAG tbpFlags) = 0;
virtual HRESULT STDMETHODCALLTYPE RegisterTab(
/* [in] */ __RPC__in HWND hwndTab,
/* [in] */ __RPC__in HWND hwndMDI) = 0;
virtual HRESULT STDMETHODCALLTYPE UnregisterTab(
/* [in] */ __RPC__in HWND hwndTab) = 0;
virtual HRESULT STDMETHODCALLTYPE SetTabOrder(
/* [in] */ __RPC__in HWND hwndTab,
/* [in] */ __RPC__in HWND hwndInsertBefore) = 0;
virtual HRESULT STDMETHODCALLTYPE SetTabActive(
/* [in] */ __RPC__in HWND hwndTab,
/* [in] */ __RPC__in HWND hwndMDI,
/* [in] */ DWORD dwReserved) = 0;
virtual HRESULT STDMETHODCALLTYPE ThumbBarAddButtons(
/* [in] */ __RPC__in HWND hwnd,
/* [in] */ UINT cButtons,
/* [size_is][in] */ __RPC__in_ecount_full(cButtons)
LPTHUMBBUTTON pButton) = 0;
virtual HRESULT STDMETHODCALLTYPE ThumbBarUpdateButtons(
/* [in] */ __RPC__in HWND hwnd,
/* [in] */ UINT cButtons,
/* [size_is][in] */ __RPC__in_ecount_full(cButtons)
LPTHUMBBUTTON pButton) = 0;
virtual HRESULT STDMETHODCALLTYPE ThumbBarSetImageList(
/* [in] */ __RPC__in HWND hwnd,
/* [in] */ __RPC__in_opt HIMAGELIST himl) = 0;
virtual HRESULT STDMETHODCALLTYPE SetOverlayIcon(
/* [in] */ __RPC__in HWND hwnd,
/* [in] */ __RPC__in HICON hIcon,
/* [string][in] */ __RPC__in_string LPCWSTR pszDescription) = 0;
virtual HRESULT STDMETHODCALLTYPE SetThumbnailTooltip(
/* [in] */ __RPC__in HWND hwnd,
/* [string][in] */ __RPC__in_string LPCWSTR pszTip) = 0;
virtual HRESULT STDMETHODCALLTYPE SetThumbnailClip(
/* [in] */ __RPC__in HWND hwnd,
/* [in] */ __RPC__in RECT *prcClip) = 0;
};
#endif // __ITaskbarList3_INTERFACE_DEFINED__
// END OF WINDOWS SDK 7.0
} // namespace
namespace {
// Sends a thumbnail bitmap to Windows. Windows assumes this function is called
// when a WM_DWMSENDICONICTHUMBNAIL message sent to a place-holder window. We
// can use DwmInvalidateIconicBitmap() to force Windows to send the message.
HRESULT CallDwmSetIconicThumbnail(HWND window, HBITMAP bitmap, DWORD flags) {
FilePath dwmapi_path(base::GetNativeLibraryName(L"dwmapi"));
base::ScopedNativeLibrary dwmapi(dwmapi_path);
typedef HRESULT (STDAPICALLTYPE *DwmSetIconicThumbnailProc)(
HWND, HBITMAP, DWORD);
DwmSetIconicThumbnailProc dwm_set_iconic_thumbnail =
static_cast<DwmSetIconicThumbnailProc>(
dwmapi.GetFunctionPointer("DwmSetIconicThumbnail"));
if (!dwm_set_iconic_thumbnail)
return E_FAIL;
return dwm_set_iconic_thumbnail(window, bitmap, flags);
}
// Sends a preview bitmap to Windows. Windows assumes this function is called
// when a WM_DWMSENDICONICLIVEPREVIEWBITMAP message sent to a place-holder
// window.
HRESULT CallDwmSetIconicLivePreviewBitmap(HWND window,
HBITMAP bitmap,
POINT* client,
DWORD flags) {
FilePath dwmapi_path(base::GetNativeLibraryName(L"dwmapi"));
base::ScopedNativeLibrary dwmapi(dwmapi_path);
typedef HRESULT (STDAPICALLTYPE *DwmSetIconicLivePreviewBitmapProc)(
HWND, HBITMAP, POINT*, DWORD);
DwmSetIconicLivePreviewBitmapProc dwm_set_live_preview_bitmap =
static_cast<DwmSetIconicLivePreviewBitmapProc>(
dwmapi.GetFunctionPointer("DwmSetIconicLivePreviewBitmap"));
if (!dwm_set_live_preview_bitmap)
return E_FAIL;
return dwm_set_live_preview_bitmap(window, bitmap, client, flags);
}
// Invalidates the thumbnail image of the specified place-holder window. (See
// the comments in CallDwmSetIconicThumbnai()).
HRESULT CallDwmInvalidateIconicBitmaps(HWND window) {
FilePath dwmapi_path(base::GetNativeLibraryName(L"dwmapi"));
base::ScopedNativeLibrary dwmapi(dwmapi_path);
typedef HRESULT (STDAPICALLTYPE *DwmInvalidateIconicBitmapsProc)(HWND);
DwmInvalidateIconicBitmapsProc dwm_invalidate_iconic_bitmaps =
static_cast<DwmInvalidateIconicBitmapsProc>(
dwmapi.GetFunctionPointer("DwmInvalidateIconicBitmaps"));
if (!dwm_invalidate_iconic_bitmaps)
return E_FAIL;
return dwm_invalidate_iconic_bitmaps(window);
}
} // namespace
namespace {
// Tasks used in this file.
// This file uses three I/O tasks to implement AeroPeek:
// * RegisterThumbnailTask
// Register a tab into the thumbnail list of Windows.
// * SendThumbnailTask
// Create a thumbnail image and send it to Windows.
// * SendLivePreviewTask
// Create a preview image and send it to Windows.
// These I/O tasks indirectly access the specified tab through the
// AeroPeekWindowDelegate interface to prevent these tasks from accessing the
// deleted tabs.
// A task that registers a thumbnail window as a child of the specified
// browser application.
class RegisterThumbnailTask : public Task {
public:
RegisterThumbnailTask(HWND frame_window, HWND window, bool active)
: frame_window_(frame_window),
window_(window),
active_(active) {
}
private:
void Run() {
// Set the App ID of the browser for this place-holder window to tell
// that this window is a child of the browser application, i.e. to tell
// that this thumbnail window should be displayed when we hover the
// browser icon in the taskbar.
// TODO(mattm): This should use ShellIntegration::GetChromiumAppId to work
// properly with multiple profiles.
app::win::SetAppIdForWindow(
BrowserDistribution::GetDistribution()->GetBrowserAppId(), window_);
// Register this place-holder window to the taskbar as a child of
// the browser window and add it to the end of its tab list.
// Correctly, this registration should be called after this browser window
// receives a registered window message "TaskbarButtonCreated", which
// means that Windows creates a taskbar button for this window in its
// taskbar. But it seems to be OK to register it without checking the
// message.
// TODO(hbono): we need to check this registered message?
base::win::ScopedComPtr<ITaskbarList3> taskbar;
if (FAILED(taskbar.CreateInstance(CLSID_TaskbarList, NULL,
CLSCTX_INPROC_SERVER)) ||
FAILED(taskbar->HrInit()) ||
FAILED(taskbar->RegisterTab(window_, frame_window_)) ||
FAILED(taskbar->SetTabOrder(window_, NULL)))
return;
if (active_)
taskbar->SetTabActive(window_, frame_window_, 0);
}
private:
// An application window to which we are going to register a tab window.
// This "application window" is a browser frame in terms of Chrome.
HWND frame_window_;
// A tab window.
// After we register this window as a child of the above application window,
// Windows sends AeroPeek events to this window.
// It seems this window MUST be a tool window.
HWND window_;
// Whether or not we need to activate this tab by default.
bool active_;
};
// A task which creates a thumbnail image used by AeroPeek and sends it to
// Windows.
class SendThumbnailTask : public Task {
public:
SendThumbnailTask(HWND aeropeek_window,
const gfx::Rect& content_bounds,
const gfx::Size& aeropeek_size,
const SkBitmap& tab_bitmap,
base::WaitableEvent* ready)
: aeropeek_window_(aeropeek_window),
content_bounds_(content_bounds),
aeropeek_size_(aeropeek_size),
tab_bitmap_(tab_bitmap),
ready_(ready) {
}
~SendThumbnailTask() {
if (ready_)
ready_->Signal();
}
private:
void Run() {
// Calculate the size of the aeropeek thumbnail and resize the tab bitmap
// to the size. When the given bitmap is an empty bitmap, we create a dummy
// bitmap from the content-area rectangle to create a DIB. (We don't need to
// allocate pixels for this case since we don't use them.)
gfx::Size thumbnail_size;
SkBitmap thumbnail_bitmap;
if (tab_bitmap_.isNull() || tab_bitmap_.empty()) {
GetThumbnailSize(content_bounds_.width(), content_bounds_.height(),
&thumbnail_size);
thumbnail_bitmap.setConfig(SkBitmap::kARGB_8888_Config,
thumbnail_size.width(),
thumbnail_size.height());
} else {
GetThumbnailSize(tab_bitmap_.width(), tab_bitmap_.height(),
&thumbnail_size);
thumbnail_bitmap = skia::ImageOperations::Resize(
tab_bitmap_,
skia::ImageOperations::RESIZE_LANCZOS3,
thumbnail_size.width(),
thumbnail_size.height());
}
// Create a DIB, copy the resized image, and send the DIB to Windows.
// We can delete this DIB after sending it to Windows since Windows creates
// a copy of the DIB and use it.
base::win::ScopedHDC hdc(CreateCompatibleDC(NULL));
if (!hdc.Get()) {
LOG(ERROR) << "cannot create a memory DC: " << GetLastError();
return;
}
BITMAPINFOHEADER header;
gfx::CreateBitmapHeader(thumbnail_size.width(), thumbnail_size.height(),
&header);
void* bitmap_data = NULL;
base::win::ScopedBitmap bitmap(
CreateDIBSection(hdc,
reinterpret_cast<BITMAPINFO*>(&header),
DIB_RGB_COLORS,
&bitmap_data,
NULL,
0));
if (!bitmap.Get() || !bitmap_data) {
LOG(ERROR) << "cannot create a bitmap: " << GetLastError();
return;
}
SkAutoLockPixels lock(thumbnail_bitmap);
int* content_pixels = reinterpret_cast<int*>(bitmap_data);
for (int y = 0; y < thumbnail_size.height(); ++y) {
for (int x = 0; x < thumbnail_size.width(); ++x) {
content_pixels[y * thumbnail_size.width() + x] =
GetPixel(thumbnail_bitmap, x, y);
}
}
HRESULT result = CallDwmSetIconicThumbnail(aeropeek_window_, bitmap, 0);
if (FAILED(result))
LOG(ERROR) << "cannot set a tab thumbnail: " << result;
}
// Calculates the thumbnail size sent to Windows so we can preserve the pixel
// aspect-ratio of the source bitmap. Since Windows returns an error when we
// send an image bigger than the given size, we decrease either the thumbnail
// width or the thumbnail height so we can fit the longer edge of the source
// window.
void GetThumbnailSize(int width, int height, gfx::Size* output) const {
float thumbnail_width = static_cast<float>(aeropeek_size_.width());
float thumbnail_height = static_cast<float>(aeropeek_size_.height());
float source_width = static_cast<float>(width);
float source_height = static_cast<float>(height);
DCHECK(source_width && source_height);
float ratio_width = thumbnail_width / source_width;
float ratio_height = thumbnail_height / source_height;
if (ratio_width > ratio_height) {
thumbnail_width = source_width * ratio_height;
} else {
thumbnail_height = source_height * ratio_width;
}
output->set_width(static_cast<int>(thumbnail_width));
output->set_height(static_cast<int>(thumbnail_height));
}
// Returns a pixel of the specified bitmap. If this bitmap is a dummy bitmap,
// this function returns an opaque white pixel instead.
int GetPixel(const SkBitmap& bitmap, int x, int y) const {
const int* tab_pixels = reinterpret_cast<const int*>(bitmap.getPixels());
if (!tab_pixels)
return 0xFFFFFFFF;
return tab_pixels[y * bitmap.width() + x];
}
private:
// A window handle to the place-holder window used by AeroPeek.
HWND aeropeek_window_;
// The bounding rectangle of the user-perceived content area.
// This rectangle is used only for creating a fall-back bitmap.
gfx::Rect content_bounds_;
// The size of an output image to be sent to Windows.
gfx::Size aeropeek_size_;
// The source bitmap.
SkBitmap tab_bitmap_;
// An event to notify when this task finishes.
base::WaitableEvent* ready_;
};
// A task which creates a preview image used by AeroPeek and sends it to
// Windows.
// This task becomes more complicated than SendThumbnailTask because this task
// calculates the rectangle of the user-perceived content area (infobars +
// content area) so Windows can paste the preview image on it.
// This task is used if an AeroPeek window receives a
// WM_DWMSENDICONICLIVEPREVIEWBITMAP message.
class SendLivePreviewTask : public Task {
public:
SendLivePreviewTask(HWND aeropeek_window,
const gfx::Rect& content_bounds,
const SkBitmap& tab_bitmap)
: aeropeek_window_(aeropeek_window),
content_bounds_(content_bounds),
tab_bitmap_(tab_bitmap) {
}
~SendLivePreviewTask() {
}
private:
void Run() {
// Create a DIB for the user-perceived content area of the tab, copy the
// tab image into the DIB, and send it to Windows.
// We don't need to paste this tab image onto the frame image since Windows
// automatically pastes it for us.
base::win::ScopedHDC hdc(CreateCompatibleDC(NULL));
if (!hdc.Get()) {
LOG(ERROR) << "cannot create a memory DC: " << GetLastError();
return;
}
BITMAPINFOHEADER header;
gfx::CreateBitmapHeader(content_bounds_.width(), content_bounds_.height(),
&header);
void* bitmap_data = NULL;
base::win::ScopedBitmap bitmap(
CreateDIBSection(hdc.Get(),
reinterpret_cast<BITMAPINFO*>(&header),
DIB_RGB_COLORS, &bitmap_data,
NULL, 0));
if (!bitmap.Get() || !bitmap_data) {
LOG(ERROR) << "cannot create a bitmap: " << GetLastError();
return;
}
// Copy the tab image onto the DIB.
SkAutoLockPixels lock(tab_bitmap_);
int* content_pixels = reinterpret_cast<int*>(bitmap_data);
for (int y = 0; y < content_bounds_.height(); ++y) {
for (int x = 0; x < content_bounds_.width(); ++x)
content_pixels[y * content_bounds_.width() + x] = GetTabPixel(x, y);
}
// Send the preview image to Windows.
// We can set its offset to the top left corner of the user-perceived
// content area so Windows can paste this bitmap onto the correct
// position.
POINT content_offset = {content_bounds_.x(), content_bounds_.y()};
HRESULT result = CallDwmSetIconicLivePreviewBitmap(
aeropeek_window_, bitmap, &content_offset, 0);
if (FAILED(result))
LOG(ERROR) << "cannot send a content image: " << result;
}
int GetTabPixel(int x, int y) const {
// Return the opaque while pixel to prevent old foreground tab from being
// shown when we cannot get the specified pixel.
const int* tab_pixels = reinterpret_cast<int*>(tab_bitmap_.getPixels());
if (!tab_pixels || x >= tab_bitmap_.width() || y >= tab_bitmap_.height())
return 0xFFFFFFFF;
// DWM uses alpha values to distinguish opaque colors and transparent ones.
// Set the alpha value of this source pixel to prevent the original window
// from being shown through.
return 0xFF000000 | tab_pixels[y * tab_bitmap_.width() + x];
}
private:
// A window handle to the AeroPeek window.
HWND aeropeek_window_;
// The bounding rectangle of the user-perceived content area. When a tab
// hasn't been rendered since a browser window is resized, this size doesn't
// become the same as the bitmap size as shown below.
// +----------------------+
// | frame window |
// | +---------------------+
// | | tab contents |
// | +---------------------+
// | | old tab contents | |
// | +------------------+ |
// +----------------------+
// This rectangle is used for clipping the width and height of the bitmap and
// cleaning the old tab contents.
// +----------------------+
// | frame window |
// | +------------------+ |
// | | tab contents | |
// | +------------------+ |
// | | blank | |
// | +------------------+ |
// +----------------------+
gfx::Rect content_bounds_;
// The bitmap of the source tab.
SkBitmap tab_bitmap_;
};
} // namespace
// A class which implements a place-holder window used by AeroPeek.
// The major work of this class are:
// * Updating the status of Tab Thumbnails;
// * Receiving messages from Windows, and;
// * Translating received messages for TabStrip.
// This class is used by the AeroPeekManager class, which is a proxy
// between TabStrip and Windows 7.
class AeroPeekWindow : public ui::WindowImpl {
public:
AeroPeekWindow(HWND frame_window,
AeroPeekWindowDelegate* delegate,
int tab_id,
bool tab_active,
const std::wstring& title,
const SkBitmap& favicon_bitmap);
~AeroPeekWindow();
// Activates or deactivates this window.
// This window uses this information not only for highlighting the selected
// tab when Windows shows the thumbnail list, but also for preventing us
// from rendering AeroPeek images for deactivated windows so often.
void Activate();
void Deactivate();
// Updates the image of this window.
// When the AeroPeekManager class calls this function, this window starts
// a task which updates its thumbnail image.
// NOTE: to prevent sending lots of tasks that update the thumbnail images
// and hurt the system performance, we post a task only when |is_loading| is
// false for non-active tabs. (On the other hand, we always post an update
// task for an active tab as IE8 does.)
void Update(bool is_loading);
// Destroys this window.
// This function removes this window from the thumbnail list and deletes
// all the resources attached to this window, i.e. this object is not valid
// any longer after calling this function.
void Destroy();
// Updates the title of this window.
// This function just sends a WM_SETTEXT message to update the window title.
void SetTitle(const std::wstring& title);
// Updates the icon used for AeroPeek. Unlike SetTitle(), this function just
// saves a copy of the given bitmap since it takes time to create a Windows
// icon from this bitmap set it as the window icon. We will create a Windows
// when Windows sends a WM_GETICON message to retrieve it.
void SetFavicon(const SkBitmap& favicon);
// Returns the tab ID associated with this window.
int tab_id() { return tab_id_; }
// Message handlers.
BEGIN_MSG_MAP_EX(TabbedThumbnailWindow)
MESSAGE_HANDLER_EX(WM_DWMSENDICONICTHUMBNAIL, OnDwmSendIconicThumbnail)
MESSAGE_HANDLER_EX(WM_DWMSENDICONICLIVEPREVIEWBITMAP,
OnDwmSendIconicLivePreviewBitmap)
MSG_WM_ACTIVATE(OnActivate)
MSG_WM_CLOSE(OnClose)
MSG_WM_CREATE(OnCreate)
MSG_WM_GETICON(OnGetIcon)
END_MSG_MAP()
private:
// Updates the thumbnail image of this window.
// This function is a wrapper function of CallDwmInvalidateIconicBitmaps()
// but it invalidates the thumbnail only when |ready_| is signaled to prevent
// us from posting two or more tasks.
void UpdateThumbnail();
// Returns the user-perceived content area.
gfx::Rect GetContentBounds() const;
// Message-handler functions.
// Called when a window has been created.
LRESULT OnCreate(LPCREATESTRUCT create_struct);
// Called when this thumbnail window is activated, i.e. a user clicks this
// thumbnail window.
void OnActivate(UINT action, BOOL minimized, HWND window);
// Called when this thumbnail window is closed, i.e. a user clicks the close
// button of this thumbnail window.
void OnClose();
// Called when Windows needs a thumbnail image for this thumbnail window.
// Windows can send a WM_DWMSENDICONICTHUMBNAIL message anytime when it
// needs the thumbnail bitmap for this place-holder window (e.g. when we
// register this place-holder window to Windows, etc.)
// When this window receives a WM_DWMSENDICONICTHUMBNAIL message, it HAS TO
// create a thumbnail bitmap and send it to Windows through a
// DwmSendIconicThumbnail() call. (Windows shows a "page-loading" animation
// while it waits for a thumbnail bitmap.)
LRESULT OnDwmSendIconicThumbnail(UINT message,
WPARAM wparam,
LPARAM lparam);
// Called when Windows needs a preview image for this thumbnail window.
// Same as above, Windows can send a WM_DWMSENDICONICLIVEPREVIEWBITMAP
// message anytime when it needs a preview bitmap and we have to create and
// send the bitmap when it needs it.
LRESULT OnDwmSendIconicLivePreviewBitmap(UINT message,
WPARAM wparam,
LPARAM lparam);
// Called when Windows needs an icon for this thumbnail window.
// Windows sends a WM_GETICON message with ICON_SMALL when it needs an
// AeroPeek icon. we handle WM_GETICON messages by ourselves so we can create
// a custom icon from a favicon only when Windows need it.
HICON OnGetIcon(UINT index);
private:
// An application window which owns this tab.
// We show this thumbnail image of this window when a user hovers a mouse
// cursor onto the taskbar icon of this application window.
HWND frame_window_;
// An interface which dispatches events received from Window.
// This window notifies events received from Windows to TabStrip through
// this interface.
// We should not directly access TabContents members since Windows may send
// AeroPeek events to a tab closed by Chrome.
// To prevent such race condition, we get access to TabContents through
// AeroPeekManager.
AeroPeekWindowDelegate* delegate_;
// A tab ID associated with this window.
int tab_id_;
// A flag that represents whether or not this tab is active.
// This flag is used for preventing us from updating the thumbnail images
// when this window is not active.
bool tab_active_;
// An event that represents whether or not we can post a task which updates
// the thumbnail image of this window.
// We post a task only when this event is signaled.
base::WaitableEvent ready_to_update_thumbnail_;
// The title of this tab.
std::wstring title_;
// The favicon for this tab.
SkBitmap favicon_bitmap_;
base::win::ScopedHICON favicon_;
// The icon used by the frame window.
// This icon is used when this tab doesn't have a favicon.
HICON frame_icon_;
DISALLOW_COPY_AND_ASSIGN(AeroPeekWindow);
};
AeroPeekWindow::AeroPeekWindow(HWND frame_window,
AeroPeekWindowDelegate* delegate,
int tab_id,
bool tab_active,
const std::wstring& title,
const SkBitmap& favicon_bitmap)
: frame_window_(frame_window),
delegate_(delegate),
tab_id_(tab_id),
tab_active_(tab_active),
ready_to_update_thumbnail_(false, true),
title_(title),
favicon_bitmap_(favicon_bitmap),
frame_icon_(NULL) {
// Set the class styles and window styles for this thumbnail window.
// An AeroPeek window should be a tool window. (Otherwise,
// Windows doesn't send WM_DWMSENDICONICTHUMBNAIL messages.)
set_initial_class_style(0);
set_window_style(WS_POPUP | WS_BORDER | WS_SYSMENU | WS_CAPTION);
set_window_ex_style(WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
}
AeroPeekWindow::~AeroPeekWindow() {
}
void AeroPeekWindow::Activate() {
tab_active_ = true;
// Create a place-holder window and add it to the tab list if it has not been
// created yet. (This case happens when we re-attached a detached window.)
if (!IsWindow(hwnd())) {
Update(false);
return;
}
// Notify Windows to set the thumbnail focus to this window.
base::win::ScopedComPtr<ITaskbarList3> taskbar;
HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL,
CLSCTX_INPROC_SERVER);
if (FAILED(result)) {
LOG(ERROR) << "failed creating an ITaskbarList3 interface.";
return;
}
result = taskbar->HrInit();
if (FAILED(result)) {
LOG(ERROR) << "failed initializing an ITaskbarList3 interface.";
return;
}
result = taskbar->ActivateTab(hwnd());
if (FAILED(result)) {
LOG(ERROR) << "failed activating a thumbnail window.";
return;
}
// Update the thumbnail image to the up-to-date one.
UpdateThumbnail();
}
void AeroPeekWindow::Deactivate() {
tab_active_ = false;
}
void AeroPeekWindow::Update(bool is_loading) {
// Create a place-holder window used by AeroPeek if it has not been created
// so Windows can send events used by AeroPeek to this window.
// Windows automatically sends a WM_DWMSENDICONICTHUMBNAIL message after this
// window is registered to Windows. So, we don't have to invalidate the
// thumbnail image of this window now.
if (!hwnd()) {
gfx::Rect bounds;
WindowImpl::Init(frame_window_, bounds);
return;
}
// Invalidate the thumbnail image of this window.
// When we invalidate the thumbnail image, we HAVE TO handle a succeeding
// WM_DWMSENDICONICTHUMBNAIL message and update the thumbnail image with a
// DwmSetIconicThumbnail() call. So, we should not call this function when
// we don't have enough information to create a thumbnail.
if (tab_active_ || !is_loading)
UpdateThumbnail();
}
void AeroPeekWindow::Destroy() {
if (!IsWindow(hwnd()))
return;
// Remove this window from the tab list of Windows.
base::win::ScopedComPtr<ITaskbarList3> taskbar;
HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL,
CLSCTX_INPROC_SERVER);
if (FAILED(result))
return;
result = taskbar->HrInit();
if (FAILED(result))
return;
result = taskbar->UnregisterTab(hwnd());
// Destroy this window.
DestroyWindow(hwnd());
}
void AeroPeekWindow::SetTitle(const std::wstring& title) {
title_ = title;
}
void AeroPeekWindow::SetFavicon(const SkBitmap& favicon) {
favicon_bitmap_ = favicon;
}
void AeroPeekWindow::UpdateThumbnail() {
// We post a task to actually create a new thumbnail. So, this function may
// be called while we are creating a thumbnail. To prevent this window from
// posting two or more tasks, we don't invalidate the current thumbnail
// when this event is not signaled.
if (ready_to_update_thumbnail_.IsSignaled())
CallDwmInvalidateIconicBitmaps(hwnd());
}
gfx::Rect AeroPeekWindow::GetContentBounds() const {
RECT content_rect;
GetClientRect(frame_window_, &content_rect);
gfx::Insets content_insets;
delegate_->GetContentInsets(&content_insets);
gfx::Rect content_bounds(content_rect);
content_bounds.Inset(content_insets.left(),
content_insets.top(),
content_insets.right(),
content_insets.bottom());
return content_bounds;
}
// message handlers
void AeroPeekWindow::OnActivate(UINT action,
BOOL minimized,
HWND window) {
// Windows sends a WM_ACTIVATE message not only when a user clicks this
// window (i.e. this window gains the thumbnail focus) but also a user clicks
// another window (i.e. this window loses the thumbnail focus.)
// Return when this window loses the thumbnail focus since we don't have to
// do anything for this case.
if (action == WA_INACTIVE)
return;
// Ask Chrome to activate the tab associated with this thumbnail window.
// Since TabStripModel calls AeroPeekManager::TabSelectedAt() when it
// finishes activating the tab. We will move the tab focus of AeroPeek there.
if (delegate_)
delegate_->ActivateTab(tab_id_);
}
LRESULT AeroPeekWindow::OnCreate(LPCREATESTRUCT create_struct) {
// Initialize the window title now since WindowImpl::Init() always calls
// CreateWindowEx() with its window name NULL.
if (!title_.empty()) {
SendMessage(hwnd(), WM_SETTEXT, 0,
reinterpret_cast<LPARAM>(title_.c_str()));
}
// Window attributes for DwmSetWindowAttribute().
// These enum values are copied from Windows SDK 7 so we can compile this
// file with or without it.
// TODO(hbono): Bug 16903: to be deleted when we use Windows SDK 7.
enum {
DWMWA_NCRENDERING_ENABLED = 1,
DWMWA_NCRENDERING_POLICY,
DWMWA_TRANSITIONS_FORCEDISABLED,
DWMWA_ALLOW_NCPAINT,
DWMWA_CAPTION_BUTTON_BOUNDS,
DWMWA_NONCLIENT_RTL_LAYOUT,
DWMWA_FORCE_ICONIC_REPRESENTATION,
DWMWA_FLIP3D_POLICY,
DWMWA_EXTENDED_FRAME_BOUNDS,
DWMWA_HAS_ICONIC_BITMAP,
DWMWA_DISALLOW_PEEK,
DWMWA_EXCLUDED_FROM_PEEK,
DWMWA_LAST
};
// Set DWM attributes to tell Windows that this window can provide the
// bitmaps used by AeroPeek.
BOOL force_iconic_representation = TRUE;
DwmSetWindowAttribute(hwnd(),
DWMWA_FORCE_ICONIC_REPRESENTATION,
&force_iconic_representation,
sizeof(force_iconic_representation));
BOOL has_iconic_bitmap = TRUE;
DwmSetWindowAttribute(hwnd(),
DWMWA_HAS_ICONIC_BITMAP,
&has_iconic_bitmap,
sizeof(has_iconic_bitmap));
// Post a task that registers this thumbnail window to Windows because it
// may take some time. (For example, when we create an ITaskbarList3
// interface for the first time, Windows loads DLLs and we need to wait for
// some time.)
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
new RegisterThumbnailTask(frame_window_, hwnd(), tab_active_));
return 0;
}
void AeroPeekWindow::OnClose() {
// Unregister this window from the tab list of Windows and destroy this
// window.
// The resources attached to this object will be deleted when TabStrip calls
// AeroPeekManager::TabClosingAt(). (Please read the comment in TabClosingAt()
// for its details.)
Destroy();
// Ask AeroPeekManager to close the tab associated with this thumbnail
// window.
if (delegate_)
delegate_->CloseTab(tab_id_);
}
LRESULT AeroPeekWindow::OnDwmSendIconicThumbnail(UINT message,
WPARAM wparam,
LPARAM lparam) {
// Update the window title to synchronize the title.
SendMessage(hwnd(), WM_SETTEXT, 0, reinterpret_cast<LPARAM>(title_.c_str()));
// Create an I/O task since it takes long time to resize these images and
// send them to Windows. This task signals |ready_to_update_thumbnail_| in
// its destructor to notify us when this task has been finished. (We create an
// I/O task even when the given thumbnail is empty to stop the "loading"
// animation.)
DCHECK(delegate_);
SkBitmap thumbnail;
delegate_->GetTabThumbnail(tab_id_, &thumbnail);
gfx::Size aeropeek_size(HIWORD(lparam), LOWORD(lparam));
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
new SendThumbnailTask(hwnd(),
GetContentBounds(),
aeropeek_size,
thumbnail,
&ready_to_update_thumbnail_));
return 0;
}
LRESULT AeroPeekWindow::OnDwmSendIconicLivePreviewBitmap(UINT message,
WPARAM wparam,
LPARAM lparam) {
// Same as OnDwmSendIconicThumbnail(), we create an I/O task which creates
// a preview image used by AeroPeek and send it to Windows. Unlike
// OnDwmSendIconicThumbnail(), we don't have to use events for preventing this
// window from sending two or more tasks because Windows doesn't send
// WM_DWMSENDICONICLIVEPREVIEWBITMAP messages before we send the preview image
// to Windows.
DCHECK(delegate_);
SkBitmap preview;
delegate_->GetTabPreview(tab_id_, &preview);
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
new SendLivePreviewTask(hwnd(), GetContentBounds(), preview));
return 0;
}
HICON AeroPeekWindow::OnGetIcon(UINT index) {
// Return the application icon if this window doesn't have favicons.
// We save this application icon to avoid calling LoadIcon() twice or more.
if (favicon_bitmap_.isNull()) {
if (!frame_icon_) {
frame_icon_ = GetAppIcon();
}
return frame_icon_;
}
// Create a Windows icon from SkBitmap and send it to Windows. We set this
// icon to the ScopedIcon object to delete it in the destructor.
favicon_.Set(IconUtil::CreateHICONFromSkBitmap(favicon_bitmap_));
return favicon_.Get();
}
AeroPeekManager::AeroPeekManager(HWND application_window)
: application_window_(application_window),
border_left_(0),
border_top_(0),
toolbar_top_(0) {
}
AeroPeekManager::~AeroPeekManager() {
// Delete all AeroPeekWindow objects.
for (std::list<AeroPeekWindow*>::iterator i = tab_list_.begin();
i != tab_list_.end(); ++i) {
AeroPeekWindow* window = *i;
delete window;
}
}
void AeroPeekManager::SetContentInsets(const gfx::Insets& insets) {
content_insets_ = insets;
}
// static
bool AeroPeekManager::Enabled() {
// We enable our custom AeroPeek only when:
// * Chrome is running on Windows 7 and Aero is enabled,
// * Chrome is not launched in application mode, and
// * Chrome is launched with the "--enable-aero-peek-tabs" option.
// TODO(hbono): Bug 37957 <http://crbug.com/37957>: find solutions that avoid
// flooding users with tab thumbnails.
const CommandLine* command_line = CommandLine::ForCurrentProcess();
return base::win::GetVersion() >= base::win::VERSION_WIN7 &&
views::WidgetWin::IsAeroGlassEnabled() &&
!command_line->HasSwitch(switches::kApp) &&
command_line->HasSwitch(switches::kEnableAeroPeekTabs);
}
void AeroPeekManager::DeleteAeroPeekWindow(int tab_id) {
// This function does NOT call AeroPeekWindow::Destroy() before deleting
// the AeroPeekWindow instance.
for (std::list<AeroPeekWindow*>::iterator i = tab_list_.begin();
i != tab_list_.end(); ++i) {
AeroPeekWindow* window = *i;
if (window->tab_id() == tab_id) {
tab_list_.erase(i);
delete window;
return;
}
}
}
void AeroPeekManager::DeleteAeroPeekWindowForTab(TabContentsWrapper* tab) {
// Delete the AeroPeekWindow object associated with this tab and all its
// resources. (AeroPeekWindow::Destory() also removes this tab from the tab
// list of Windows.)
AeroPeekWindow* window = GetAeroPeekWindow(GetTabID(tab->tab_contents()));
if (!window)
return;
window->Destroy();
DeleteAeroPeekWindow(GetTabID(tab->tab_contents()));
}
AeroPeekWindow* AeroPeekManager::GetAeroPeekWindow(int tab_id) const {
size_t size = tab_list_.size();
for (std::list<AeroPeekWindow*>::const_iterator i = tab_list_.begin();
i != tab_list_.end(); ++i) {
AeroPeekWindow* window = *i;
if (window->tab_id() == tab_id)
return window;
}
return NULL;
}
void AeroPeekManager::CreateAeroPeekWindowIfNecessary(TabContentsWrapper* tab,
bool foreground) {
if (GetAeroPeekWindow(GetTabID(tab->tab_contents())))
return;
AeroPeekWindow* window =
new AeroPeekWindow(application_window_,
this,
GetTabID(tab->tab_contents()),
foreground,
tab->tab_contents()->GetTitle(),
tab->tab_contents()->GetFavicon());
tab_list_.push_back(window);
}
TabContents* AeroPeekManager::GetTabContents(int tab_id) const {
for (TabContentsIterator iterator; !iterator.done(); ++iterator) {
TabContents* target_contents = (*iterator)->tab_contents();
if (target_contents->controller().session_id().id() == tab_id)
return target_contents;
}
return NULL;
}
int AeroPeekManager::GetTabID(TabContents* contents) const {
if (!contents)
return -1;
return contents->controller().session_id().id();
}
///////////////////////////////////////////////////////////////////////////////
// AeroPeekManager, TabStripModelObserver implementation:
void AeroPeekManager::TabInsertedAt(TabContentsWrapper* contents,
int index,
bool foreground) {
if (!contents)
return;
CreateAeroPeekWindowIfNecessary(contents, foreground);
}
void AeroPeekManager::TabDetachedAt(TabContentsWrapper* contents, int index) {
if (!contents)
return;
// Chrome will call TabInsertedAt() when this tab is inserted to another
// TabStrip. We will re-create an AeroPeekWindow object for this tab and
// re-add it to the tab list there.
DeleteAeroPeekWindowForTab(contents);
}
void AeroPeekManager::TabSelectedAt(TabContentsWrapper* old_contents,
TabContentsWrapper* new_contents,
int index,
bool user_gesture) {
if (old_contents == new_contents)
return;
// Deactivate the old window in the thumbnail list and activate the new one
// to synchronize the thumbnail list with TabStrip.
if (old_contents) {
AeroPeekWindow* old_window =
GetAeroPeekWindow(GetTabID(old_contents->tab_contents()));
if (old_window)
old_window->Deactivate();
}
if (new_contents) {
AeroPeekWindow* new_window =
GetAeroPeekWindow(GetTabID(new_contents->tab_contents()));
if (new_window)
new_window->Activate();
}
}
void AeroPeekManager::TabReplacedAt(TabStripModel* tab_strip_model,
TabContentsWrapper* old_contents,
TabContentsWrapper* new_contents,
int index) {
DeleteAeroPeekWindowForTab(old_contents);
CreateAeroPeekWindowIfNecessary(new_contents,
(index == tab_strip_model->active_index()));
// We don't need to update the selection as if |new_contents| is selected the
// TabStripModel will send TabSelectedAt.
}
void AeroPeekManager::TabMoved(TabContentsWrapper* contents,
int from_index,
int to_index,
bool pinned_state_changed) {
// TODO(hbono): we need to reorder the thumbnail list of Windows here?
// (Unfortunately, it is not so trivial to reorder the thumbnail list when
// we detach/attach tabs.)
}
void AeroPeekManager::TabChangedAt(TabContentsWrapper* contents,
int index,
TabChangeType change_type) {
if (!contents)
return;
// Retrieve the AeroPeekWindow object associated with this tab, update its
// title, and post a task that update its thumbnail image if necessary.
AeroPeekWindow* window =
GetAeroPeekWindow(GetTabID(contents->tab_contents()));
if (!window)
return;
// Update the title, the favicon, and the thumbnail used for AeroPeek.
// These function don't actually update the icon and the thumbnail until
// Windows needs them (e.g. when a user hovers a taskbar icon) to avoid
// hurting the rendering performance. (These functions just save the
// information needed for handling update requests from Windows.)
window->SetTitle(contents->tab_contents()->GetTitle());
window->SetFavicon(contents->tab_contents()->GetFavicon());
window->Update(contents->tab_contents()->is_loading());
}
///////////////////////////////////////////////////////////////////////////////
// AeroPeekManager, AeroPeekWindowDelegate implementation:
void AeroPeekManager::ActivateTab(int tab_id) {
// Ask TabStrip to activate this tab.
// We don't have to update thumbnails now since TabStrip will call
// TabSelectedAt() when it actually activates this tab.
TabContents* contents = GetTabContents(tab_id);
if (contents && contents->delegate())
contents->delegate()->ActivateContents(contents);
}
void AeroPeekManager::CloseTab(int tab_id) {
// Ask TabStrip to close this tab.
// TabStrip will call TabClosingAt() when it actually closes this tab. We
// will delete the AeroPeekWindow object attached to this tab there.
TabContents* contents = GetTabContents(tab_id);
if (contents && contents->delegate())
contents->delegate()->CloseContents(contents);
}
void AeroPeekManager::GetContentInsets(gfx::Insets* insets) {
*insets = content_insets_;
}
bool AeroPeekManager::GetTabThumbnail(int tab_id, SkBitmap* thumbnail) {
DCHECK(thumbnail);
// Copy the thumbnail image and the favicon of this tab. We will resize the
// images and send them to Windows.
TabContents* contents = GetTabContents(tab_id);
if (!contents)
return false;
ThumbnailGenerator* generator = g_browser_process->GetThumbnailGenerator();
DCHECK(generator);
*thumbnail = generator->GetThumbnailForRenderer(contents->render_view_host());
return true;
}
bool AeroPeekManager::GetTabPreview(int tab_id, SkBitmap* preview) {
DCHECK(preview);
// Retrieve the BackingStore associated with the given tab and return its
// SkPlatformCanvas.
TabContents* contents = GetTabContents(tab_id);
if (!contents)
return false;
RenderViewHost* render_view_host = contents->render_view_host();
if (!render_view_host)
return false;
BackingStore* backing_store = render_view_host->GetBackingStore(false);
if (!backing_store)
return false;
// Create a copy of this BackingStore image.
// This code is just copied from "thumbnail_generator.cc".
skia::PlatformCanvas canvas;
if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()),
&canvas))
return false;
const SkBitmap& bitmap = canvas.getTopPlatformDevice().accessBitmap(false);
bitmap.copyTo(preview, SkBitmap::kARGB_8888_Config);
return true;
}