Skip to content

Commit

Permalink
Removing use of Windows hooks for taking screenshots in IE.
Browse files Browse the repository at this point in the history
This will fix the issue of taking full-page screenshots when using
the 32-bit IE driver on 64-bit Windows installations.
  • Loading branch information
jimevans committed Sep 22, 2015
1 parent 8f310fc commit 005c5c9
Show file tree
Hide file tree
Showing 7 changed files with 7,351 additions and 7,610 deletions.
174 changes: 38 additions & 136 deletions cpp/iedriver/CommandHandlers/ScreenshotCommandHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#define WEBDRIVER_IE_SCREENSHOTCOMMANDHANDLER_H_

#include "../Browser.h"
#include "../HookProcessor.h"
#include "../IECommandHandler.h"
#include "../IECommandExecutor.h"
#include "logging.h"
Expand Down Expand Up @@ -129,76 +128,56 @@ class ScreenshotCommandHandler : public IECommandHandler {
LOG(DEBUG) << "Initial chrome sizes are (w, h): "
<< chrome_width << ", " << chrome_height;

// Technically, we could use a custom structure here, and save a
// few bytes, but RECT is already a well-known structure, and
// one that is included as part of the Windows SDK, so we'll
// leverage it.
RECT max_image_dimensions;
max_image_dimensions.left = 0;
max_image_dimensions.top = 0;
max_image_dimensions.right = document_info.width + chrome_width;
max_image_dimensions.bottom = document_info.height + chrome_height;
int target_window_width = document_info.width + chrome_width;
int target_window_height = document_info.height + chrome_height;

// For some reason, this technique does not allow the user to resize
// the browser window to greater than SIZE_LIMIT x SIZE_LIMIT. This is pretty
// big, so we'll cap the allowable screenshot size to that.
//
// GDI+ limit after which it may report Generic error for some image types
int SIZE_LIMIT = 65534;
if (max_image_dimensions.bottom > SIZE_LIMIT) {
if (target_window_height > SIZE_LIMIT) {
LOG(WARN) << "Required height is greater than limit. Truncating screenshot height.";
max_image_dimensions.bottom = SIZE_LIMIT;
document_info.height = max_image_dimensions.bottom - chrome_height;
target_window_height = SIZE_LIMIT;
document_info.height = target_window_height - chrome_height;
}
if (max_image_dimensions.right > SIZE_LIMIT) {
if (target_window_width > SIZE_LIMIT) {
LOG(WARN) << "Required width is greater than limit. Truncating screenshot width.";
max_image_dimensions.right = SIZE_LIMIT;
document_info.width = max_image_dimensions.right - chrome_width;
target_window_width = SIZE_LIMIT;
document_info.width = target_window_width - chrome_width;
}

long original_width = browser->GetWidth();
long original_height = browser->GetHeight();
LOG(DEBUG) << "Initial browser window sizes are (w, h): "
<< original_width << ", " << original_height;

bool requires_rezise = original_width <= max_image_dimensions.right ||
original_height <= max_image_dimensions.bottom;

// The resize message is being ignored if the window appears to be
// maximized. There's likely a way to bypass that. The kludgy way
// is to unmaximize the window, then move on with setting the window
// to the dimensions we really want. This is okay because we revert
// back to the original dimensions afterward.
BOOL is_maximized = ::IsZoomed(ie_window_handle);
if (is_maximized) {
LOG(DEBUG) << "Window is maximized currently. Demaximizing.";
::ShowWindow(ie_window_handle, SW_SHOWNORMAL);
}
bool requires_resize = original_width < target_window_width ||
original_height < target_window_height;

if (requires_resize) {
// The resize message is being ignored if the window appears to be
// maximized. There's likely a way to bypass that. The kludgy way
// is to unmaximize the window, then move on with setting the window
// to the dimensions we really want. This is okay because we revert
// back to the original dimensions afterward.
if (is_maximized) {
LOG(DEBUG) << "Window is maximized currently. Demaximizing.";
::ShowWindow(ie_window_handle, SW_SHOWNORMAL);
}

HookSettings hook_settings;
hook_settings.window_handle = ie_window_handle;
hook_settings.hook_procedure_name = "ScreenshotWndProc";
hook_settings.hook_procedure_type = WH_CALLWNDPROC;
hook_settings.communication_type = OneWay;

HookProcessor hook;
if (!hook.CanSetWindowsHook(ie_window_handle)) {
LOG(WARN) << "Screenshot will be truncated! There is a mismatch "
<< "in the bitness between the driver and browser. In "
<< "particular, you are likely using a 32-bit "
<< "IEDriverServer.exe and a 64-bit version of IE.";
RECT ie_window_rect;
::GetWindowRect(ie_window_handle, &ie_window_rect);
::SetWindowPos(ie_window_handle,
NULL,
ie_window_rect.left,
ie_window_rect.top,
target_window_width,
target_window_height,
SWP_NOSENDCHANGING);
}
hook.Initialize(hook_settings);

hook.PushData(sizeof(max_image_dimensions), &max_image_dimensions);
browser->SetWidth(max_image_dimensions.right);

// Must re-push data because the resize causes a message to the
// IE window, and reading the data clears the buffer.
hook.PushData(sizeof(max_image_dimensions), &max_image_dimensions);
browser->SetHeight(max_image_dimensions.bottom);

hook.Dispose();

// Capture the window's canvas to a DIB.
BOOL created = this->image_->Create(document_info.width,
Expand All @@ -216,12 +195,14 @@ class ScreenshotCommandHandler : public IECommandHandler {
LOG(WARN) << "PrintWindow API is not able to get content window screenshot";
}

// Restore the browser to the original dimensions.
if (is_maximized) {
::ShowWindow(ie_window_handle, SW_MAXIMIZE);
} else {
browser->SetHeight(original_height);
browser->SetWidth(original_width);
if (requires_resize) {
// Restore the browser to the original dimensions.
if (is_maximized) {
::ShowWindow(ie_window_handle, SW_MAXIMIZE);
} else {
browser->SetHeight(original_height);
browser->SetWidth(original_width);
}
}

this->image_->ReleaseDC();
Expand Down Expand Up @@ -352,84 +333,5 @@ class ScreenshotCommandHandler : public IECommandHandler {

} // namespace webdriver

#ifdef __cplusplus
extern "C" {
#endif

// This function is our message processor that we inject into the IEFrame
// process. Its sole purpose is to process WM_GETMINMAXINFO messages and
// modify the max tracking size so that we can resize the IEFrame window to
// greater than the virtual screen resolution. All other messages are
// delegated to the original IEFrame message processor. This function
// uninjects itself immediately upon execution.
LRESULT CALLBACK MinMaxInfoHandler(HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam) {
// Grab a reference to the original message processor.
HANDLE original_message_proc = ::GetProp(hwnd,
L"__original_message_processor__");
::RemoveProp(hwnd, L"__original_message_processor__");

// Uninject this method.
::SetWindowLongPtr(hwnd,
GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(original_message_proc));

if (WM_GETMINMAXINFO == message) {
MINMAXINFO* minMaxInfo = reinterpret_cast<MINMAXINFO*>(lParam);
RECT max_size;
webdriver::HookProcessor::CopyDataFromBuffer(sizeof(RECT), reinterpret_cast<void*>(&max_size));
minMaxInfo->ptMaxTrackSize.x = max_size.right;
minMaxInfo->ptMaxTrackSize.y = max_size.bottom;

// We're not going to pass this message onto the original message
// processor, so we should return 0, per the documentation for
// the WM_GETMINMAXINFO message.
return 0;
}

// All other messages should be handled by the original message processor.
return ::CallWindowProc(reinterpret_cast<WNDPROC>(original_message_proc),
hwnd,
message,
wParam,
lParam);
}

// Many thanks to sunnyandy for helping out with this approach. What we're
// doing here is setting up a Windows hook to see incoming messages to the
// IEFrame's message processor. Once we find one that's WM_GETMINMAXINFO,
// we inject our own message processor into the IEFrame process to handle
// that one message. WM_GETMINMAXINFO is sent on a resize event so the process
// can see how large a window can be. By modifying the max values, we can allow
// a window to be sized greater than the (virtual) screen resolution would
// otherwise allow.
//
// See the discussion here: http://www.codeguru.com/forum/showthread.php?p=1889928
LRESULT CALLBACK ScreenshotWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
CWPSTRUCT* call_window_proc_struct = reinterpret_cast<CWPSTRUCT*>(lParam);
if (WM_COPYDATA == call_window_proc_struct->message) {
COPYDATASTRUCT* data = reinterpret_cast<COPYDATASTRUCT*>(call_window_proc_struct->lParam);
webdriver::HookProcessor::CopyDataToBuffer(data->cbData, data->lpData);
} else if (WM_GETMINMAXINFO == call_window_proc_struct->message) {
// Inject our own message processor into the process so we can modify
// the WM_GETMINMAXINFO message. It is not possible to modify the
// message from this hook, so the best we can do is inject a function
// that can.
LONG_PTR proc = ::SetWindowLongPtr(call_window_proc_struct->hwnd,
GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(MinMaxInfoHandler));
::SetProp(call_window_proc_struct->hwnd,
L"__original_message_processor__",
reinterpret_cast<HANDLE>(proc));
}

return ::CallNextHookEx(NULL, nCode, wParam, lParam);
}

#ifdef __cplusplus
}
#endif

#endif // WEBDRIVER_IE_SCREENSHOTCOMMANDHANDLER_H_
Loading

0 comments on commit 005c5c9

Please sign in to comment.