// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "ScreenshotCommandHandler.h" #include #include #include "errorcodes.h" #include "logging.h" #include "../Browser.h" #include "../IECommandExecutor.h" namespace webdriver { ScreenshotCommandHandler::ScreenshotCommandHandler(void) { this->image_ = NULL; } ScreenshotCommandHandler::~ScreenshotCommandHandler(void) { } void ScreenshotCommandHandler::ExecuteInternal( const IECommandExecutor& executor, const ParametersMap& command_parameters, Response* response) { LOG(TRACE) << "Entering ScreenshotCommandHandler::ExecuteInternal"; BrowserHandle browser_wrapper; int status_code = executor.GetCurrentBrowser(&browser_wrapper); if (status_code != WD_SUCCESS) { response->SetErrorResponse(status_code, "Unable to get browser"); return; } status_code = this->GenerateScreenshotImage(browser_wrapper); if (status_code != WD_SUCCESS) { // TODO: Return a meaningful error here. response->SetSuccessResponse(""); return; } // now either correct or single color image is got std::string base64_screenshot = ""; HRESULT hr = this->GetBase64Data(base64_screenshot); if (FAILED(hr)) { // TODO: Return a meaningful error here. LOGHR(WARN, hr) << "Unable to transform browser image to Base64 format"; this->ClearImage(); response->SetSuccessResponse(""); return; } this->ClearImage(); response->SetSuccessResponse(base64_screenshot); } int ScreenshotCommandHandler::GenerateScreenshotImage(BrowserHandle browser_wrapper) { bool is_same_colour = true; HRESULT hr; int i = 0; int tries = 4; do { this->ClearImage(); this->image_ = new CImage(); hr = this->CaptureViewport(browser_wrapper); if (FAILED(hr)) { LOGHR(WARN, hr) << "Failed to capture browser image at " << i << " try"; this->ClearImage(); return EUNHANDLEDERROR; } is_same_colour = IsSameColour(); if (is_same_colour) { ::Sleep(2000); LOG(DEBUG) << "Failed to capture non single color browser image at " << i << " try"; } ++i; } while ((i < tries) && is_same_colour); return WD_SUCCESS; } void ScreenshotCommandHandler::ClearImage() { if (this->image_ != NULL) { delete this->image_; this->image_ = NULL; } } HRESULT ScreenshotCommandHandler::CaptureViewport(BrowserHandle browser) { LOG(TRACE) << "Entering ScreenshotCommandHandler::CaptureViewport"; HWND content_window_handle = browser->GetContentWindowHandle(); int content_width = 0, content_height = 0; this->GetWindowDimensions(content_window_handle, &content_width, &content_height); this->CaptureWindow(content_window_handle, content_width, content_height); return S_OK; } void ScreenshotCommandHandler::CaptureWindow(HWND window_handle, long width, long height) { // Capture the window's canvas to a DIB. // If there are any scroll bars in the window, they should // be explicitly cropped out of the image, because of the // size of image we are creating.. BOOL created = this->image_->Create(width, height, /*numbers of bits per pixel = */ 32); if (!created) { LOG(WARN) << "Unable to initialize image object"; } HDC device_context_handle = this->image_->GetDC(); BOOL print_result = ::PrintWindow(window_handle, device_context_handle, PW_CLIENTONLY); if (!print_result) { LOG(WARN) << "PrintWindow API is not able to get content window screenshot"; } this->image_->ReleaseDC(); } bool ScreenshotCommandHandler::IsSameColour() { int bytes_per_pixel = this->image_->GetBPP() / 8; if (bytes_per_pixel >= 3) { BYTE* root_pixel_pointer = reinterpret_cast(this->image_->GetBits()); int pitch = this->image_->GetPitch(); int first_pixel_red = *(root_pixel_pointer); int first_pixel_green = *(root_pixel_pointer + 1); int first_pixel_blue = *(root_pixel_pointer + 2); for (int i = 0; i < this->image_->GetWidth(); ++i) { for (int j = 0; j < this->image_->GetHeight(); ++j) { int current_pixel_offset = (pitch * j) + (bytes_per_pixel * i); int current_pixel_red = *(root_pixel_pointer + current_pixel_offset); int current_pixel_green = *(root_pixel_pointer + current_pixel_offset + 1); int current_pixel_blue = *(root_pixel_pointer + current_pixel_offset + 2); if (first_pixel_red != current_pixel_red || first_pixel_green != current_pixel_green || first_pixel_blue != current_pixel_blue) { return false; } } } } else { COLORREF firstPixelColour = this->image_->GetPixel(0, 0); for (int i = 0; i < this->image_->GetWidth(); ++i) { for (int j = 0; j < this->image_->GetHeight(); ++j) { if (this->image_->GetPixel(i, j)) { return false; } } } } return true; } HRESULT ScreenshotCommandHandler::GetBase64Data(std::string& data) { LOG(TRACE) << "Entering ScreenshotCommandHandler::GetBase64Data"; if (this->image_ == NULL) { LOG(DEBUG) << "CImage was not initialized."; return E_POINTER; } CComPtr stream; HRESULT hr = ::CreateStreamOnHGlobal(NULL, TRUE, &stream); if (FAILED(hr)) { LOGHR(WARN, hr) << "Error is occured during creating IStream"; return hr; } GUID image_format = Gdiplus::ImageFormatPNG /*Gdiplus::ImageFormatJPEG*/; hr = this->image_->Save(stream, image_format); if (FAILED(hr)) { LOGHR(WARN, hr) << "Saving screenshot image is failed"; return hr; } // Get the size of the stream. STATSTG statstg; hr = stream->Stat(&statstg, STATFLAG_DEFAULT); if (FAILED(hr)) { LOGHR(WARN, hr) << "No stat on stream is got"; return hr; } HGLOBAL global_memory_handle = NULL; hr = ::GetHGlobalFromStream(stream, &global_memory_handle); if (FAILED(hr)) { LOGHR(WARN, hr) << "No HGlobal in stream"; return hr; } // TODO: What if the file is bigger than max_int? int stream_size = static_cast(statstg.cbSize.QuadPart); LOG(DEBUG) << "Size of screenshot image stream is " << stream_size; int length = ::Base64EncodeGetRequiredLength(stream_size, ATL_BASE64_FLAG_NOCRLF); if (length <= 0) { LOG(WARN) << "Got zero or negative length from base64 required length"; return E_FAIL; } BYTE* global_lock = reinterpret_cast(::GlobalLock(global_memory_handle)); if (global_lock == NULL) { LOGERR(WARN) << "Unable to lock memory for base64 encoding"; ::GlobalUnlock(global_memory_handle); return E_FAIL; } char* data_array = new char[length + 1]; if (!::Base64Encode(global_lock, stream_size, data_array, &length, ATL_BASE64_FLAG_NOCRLF)) { delete[] data_array; ::GlobalUnlock(global_memory_handle); LOG(WARN) << "Unable to encode image stream to base64"; return E_FAIL; } data_array[length] = '\0'; data = data_array; delete[] data_array; ::GlobalUnlock(global_memory_handle); return S_OK; } void ScreenshotCommandHandler::GetWindowDimensions(HWND window_handle, int* width, int* height) { RECT window_rect; ::GetWindowRect(window_handle, &window_rect); *width = window_rect.right - window_rect.left; *height = window_rect.bottom - window_rect.top; } void ScreenshotCommandHandler::CropImage(HWND content_window_handle, LocationInfo element_location) { RECT viewport_rect; ::GetWindowRect(content_window_handle, &viewport_rect); ::OffsetRect(&viewport_rect, -1 * viewport_rect.left, -1 * viewport_rect.top); POINT element_rect_origin; element_rect_origin.x = element_location.x; element_rect_origin.y = element_location.y; RECT element_rect = { element_rect_origin.x, element_rect_origin.y, element_rect_origin.x + element_location.width, element_rect_origin.y + element_location.height }; RECT screenshot_rect; ::IntersectRect(&screenshot_rect, &viewport_rect, &element_rect); long width = screenshot_rect.right - screenshot_rect.left; long height = screenshot_rect.bottom - screenshot_rect.top; RECT destination_rect = { 0, 0, width, height }; CImage* image = new CImage(); image->Create(width, height, 32); HDC device_context = image->GetDC(); this->image_->Draw(device_context, destination_rect, screenshot_rect); image->ReleaseDC(); this->ClearImage(); this->image_ = new CImage(); this->image_->Create(width, height, 32); HDC dc = this->image_->GetDC(); image->BitBlt(dc, 0, 0); this->image_->ReleaseDC(); image->Destroy(); delete image; } } // namespace webdriver