// 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; }