//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: //=============================================================================// #include "winlite.h" #include "html_chrome.h" #include "cef/include/cef_cookie.h" #include "imageloadwrapper.h" #include "html/ichromehtmlwrapper.h" #include "input/mousecursors.h" //#include "rtime.h" #ifdef OSX extern "C" void *CreateAutoReleasePool(); extern "C" void ReleaseAutoReleasePool( void *pool ); #endif #ifdef WIN32 #include #endif // memdbgon must be the last include file in a .cpp file!!! #include static uint64 sm_ulBuildID = 0; // will be appended to the user agent static CCEFThread g_CEFThread; // main thread for CEF to do its thinking // access for main thread to get to the html object CCEFThread &AccessHTMLWrapper() { return g_CEFThread; } // helper macro to send a command to the CEF thread #define DISPATCH_MESSAGE( eCmd ) \ cmd.Body().set_browser_handle( m_iBrowser ); \ HTMLCommandBuffer_t *pBuf = g_CEFThread.GetFreeCommandBuffer( eCmd, m_iBrowser ); \ cmd.SerializeCrossProc( &pBuf->m_Buffer ); \ g_CEFThread.PushResponse( pBuf ); \ // wrappers for our handle to list index/serial, lower 16 bits are list index, top 16 bits are the serial number #define BROWSER_HANDLE_FROM_INDEX_SERIAL( iList, nSerial ) iList + ((int)nSerial<<16) #define BROWSER_SERIAL_FROM_HANDLE( nHandle ) (uint32)nHandle>>16 const int k_ScreenshotQuality = 95; // quality value used when making a jpeg image of a web page const float k_flMaxScreenshotWaitTime = 5.0; // wait up to 5 sec before taking a screenshot //----------------------------------------------------------------------------- // Purpose: thread to manage pumping the CEF message loop //----------------------------------------------------------------------------- CCEFThread::CCEFThread() { m_bExit = false; m_nTargetFrameRate = 60; // 60 Hz by default m_nBrowserSerial = 0; m_bFullScreenFlashVisible = false; m_bSawUserInputThisFrame = false; } //----------------------------------------------------------------------------- // Purpose: destructor //----------------------------------------------------------------------------- CCEFThread::~CCEFThread() { // This can get called by the main thread before the actual thread exits, // if there is a forced process exit due to some error. In this case, // force the thread to exit before destroying the member variables it // is depending on. if ( !m_bExit ) { TriggerShutdown(); Join( 20 * k_nThousand ); } } //----------------------------------------------------------------------------- // Purpose: tell cef where to place its state dirs //----------------------------------------------------------------------------- void CCEFThread::SetCEFPaths( const char *pchHTMLCacheDir, const char *pchCookiePath ) { Assert( !IsAlive() ); m_sHTMLCacheDir = pchHTMLCacheDir; m_sCookiePath = pchCookiePath; } //----------------------------------------------------------------------------- // Purpose: set the exit event on the cef thread //----------------------------------------------------------------------------- void CCEFThread::TriggerShutdown() { m_bExit = true; m_WakeEvent.Set(); m_evWaitingForCommand.Set(); m_eventDidExit.Wait( k_nThousand ); // wait 1 second at most for it to go away } //----------------------------------------------------------------------------- // Purpose: tickle the run loop of the thread to run more commands //----------------------------------------------------------------------------- void CCEFThread::WakeThread() { m_WakeEvent.Set(); } //----------------------------------------------------------------------------- // Purpose: helper macro for message dispatch //----------------------------------------------------------------------------- #define HTML_MSG_FUNC( eHTMLCommand, bodyType, commandFunc ) \ case eHTMLCommand: \ { \ CHTMLProtoBufMsg< bodyType > cmd( pCmd->m_eCmd ); \ if ( !cmd.BDeserializeCrossProc( &pCmd->m_Buffer ) ) \ { \ bError = true; \ } \ else \ { \ cmd.Body().set_browser_handle( pCmd->m_iBrowser ); \ g_CEFThread.commandFunc( cmd ); \ } \ } \ break; #define HTML_MSG_FUNC_NOHANDLE( eHTMLCommand, bodyType, commandFunc ) \ case eHTMLCommand: \ { \ CHTMLProtoBufMsg< bodyType > cmd( pCmd->m_eCmd ); \ if ( !cmd.BDeserializeCrossProc( &pCmd->m_Buffer ) ) \ { \ bError = true; \ } \ else \ { \ g_CEFThread.commandFunc( cmd ); \ } \ } \ break; //----------------------------------------------------------------------------- // Purpose: clear any pending commands from the main thread //----------------------------------------------------------------------------- void CCEFThread::RunCurrentCommands() { bool bError = false; HTMLCommandBuffer_t *pCmd =NULL; while( GetCEFThreadCommand( &pCmd ) ) { //Msg( "Got command %d\n", pCmd->m_eCmd ); switch( pCmd->m_eCmd ) { HTML_MSG_FUNC_NOHANDLE( eHTMLCommands_BrowserCreate, CMsgBrowserCreate, ThreadCreateBrowser ); HTML_MSG_FUNC( eHTMLCommands_BrowserRemove, CMsgBrowserRemove, ThreadRemoveBrowser ); HTML_MSG_FUNC( eHTMLCommands_BrowserSize, CMsgBrowserSize, ThreadBrowserSize ); HTML_MSG_FUNC( eHTMLCommands_BrowserPosition, CMsgBrowserPosition, ThreadBrowserPosition ); HTML_MSG_FUNC( eHTMLCommands_PostURL, CMsgPostURL, ThreadBrowserPostURL ); HTML_MSG_FUNC( eHTMLCommands_StopLoad, CMsgStopLoad, ThreadBrowserStopLoad ); HTML_MSG_FUNC( eHTMLCommands_Reload, CMsgReload, ThreadBrowserReload ); HTML_MSG_FUNC( eHTMLCommands_GoForward, CMsgGoForward, ThreadBrowserGoForward ); HTML_MSG_FUNC( eHTMLCommands_GoBack, CMsgGoBack, ThreadBrowserGoBack ); HTML_MSG_FUNC( eHTMLCommands_Copy, CMsgCopy, ThreadBrowserCopy ); HTML_MSG_FUNC( eHTMLCommands_Paste, CMsgPaste, ThreadBrowserPaste ); HTML_MSG_FUNC( eHTMLCommands_ExecuteJavaScript, CMsgExecuteJavaScript, ThreadBrowserExecuteJavascript ); HTML_MSG_FUNC( eHTMLCommands_SetFocus, CMsgSetFocus, ThreadBrowserSetFocus ); HTML_MSG_FUNC( eHTMLCommands_HorizontalScrollBarSize, CMsgHorizontalScrollBarSize, ThreadBrowserHorizontalScrollBarSize ); HTML_MSG_FUNC( eHTMLCommands_VerticalScrollBarSize, CMsgVerticalScrollBarSize, ThreadBrowserVerticalScrollBarSize ); HTML_MSG_FUNC( eHTMLCommands_Find, CMsgFind, ThreadBrowserFind ); HTML_MSG_FUNC( eHTMLCommands_StopFind, CMsgStopFind, ThreadBrowserStopFind ); HTML_MSG_FUNC( eHTMLCommands_SetHorizontalScroll, CMsgSetHorizontalScroll, ThreadBrowserSetHorizontalScroll ); HTML_MSG_FUNC( eHTMLCommands_SetVerticalScroll, CMsgSetVerticalScroll, ThreadBrowserSetVerticalScroll ); HTML_MSG_FUNC( eHTMLCommands_SetZoomLevel, CMsgSetZoomLevel, ThreadBrowserSetZoomLevel ); HTML_MSG_FUNC( eHTMLCommands_ViewSource, CMsgViewSource, ThreadBrowserViewSource ); HTML_MSG_FUNC( eHTMLCommands_NeedsPaintResponse, CMsgNeedsPaintResponse, ThreadNeedsPaintResponse ); HTML_MSG_FUNC( eHTMLCommands_BrowserErrorStrings, CMsgBrowserErrorStrings, ThreadBrowserErrorStrings ); HTML_MSG_FUNC( eHTMLCommands_AddHeader, CMsgAddHeader, ThreadAddHeader ); HTML_MSG_FUNC( eHTMLCommands_GetZoom, CMsgGetZoom, ThreadGetZoom ); HTML_MSG_FUNC_NOHANDLE( eHTMLCommands_SetCookie, CMsgSetCookie, ThreadSetCookie ); HTML_MSG_FUNC_NOHANDLE( eHTMLCommands_SetTargetFrameRate, CMsgSetTargetFrameRate, ThreadSetTargetFrameRate ); HTML_MSG_FUNC( eHTMLCommands_HidePopup, CMsgHidePopup, ThreadHidePopup ); HTML_MSG_FUNC( eHTMLCommands_FullRepaint, CMsgFullRepaint, ThreadFullRepaint ); HTML_MSG_FUNC( eHTMLCommands_GetCookiesForURL, CMsgGetCookiesForURL, ThreadGetCookiesForURL ); HTML_MSG_FUNC( eHTMLCommands_ZoomToCurrentlyFocusedNode, CMsgZoomToFocusedElement, ThreadZoomToFocusedElement ); HTML_MSG_FUNC( eHTMLCommands_GetFocusedNodeValue, CMsgFocusedNodeText, ThreadGetFocusedNodeText ); HTML_MSG_FUNC( eHTMLCommands_MouseDown, CMsgMouseDown, ThreadMouseButtonDown ); HTML_MSG_FUNC( eHTMLCommands_MouseUp, CMsgMouseUp, ThreadMouseButtonUp ); HTML_MSG_FUNC( eHTMLCommands_MouseDblClick, CMsgMouseDblClick, ThreadMouseButtonDlbClick ); HTML_MSG_FUNC( eHTMLCommands_MouseWheel, CMsgMouseWheel, ThreadMouseWheel ); HTML_MSG_FUNC( eHTMLCommands_KeyDown, CMsgKeyDown, ThreadKeyDown ); HTML_MSG_FUNC( eHTMLCommands_KeyUp, CMsgKeyUp, ThreadKeyUp ); HTML_MSG_FUNC( eHTMLCommands_KeyChar, CMsgKeyChar, ThreadKeyTyped ); HTML_MSG_FUNC( eHTMLCommands_MouseMove, CMsgMouseMove, ThreadMouseMove ); HTML_MSG_FUNC( eHTMLCommands_MouseLeave, CMsgMouseLeave, ThreadMouseLeave ); HTML_MSG_FUNC( eHTMLCommands_LinkAtPosition, CMsgLinkAtPosition, ThreadLinkAtPosition ); HTML_MSG_FUNC( eHTMLCommands_ZoomToElementAtPosition, CMsgZoomToElementAtPosition, ThreadZoomToElementAtPosition ); HTML_MSG_FUNC( eHTMLCommands_SavePageToJPEG, CMsgSavePageToJPEG, ThreadSavePageToJPEG ); HTML_MSG_FUNC( eHTMLCommands_SetPageScale, CMsgScalePageToValue, ThreadSetPageScale ); HTML_MSG_FUNC( eHTMLCommands_ExitFullScreen, CMsgExitFullScreen, ThreadExitFullScreen ); HTML_MSG_FUNC( eHTMLCommands_CloseFullScreenFlashIfOpen, CMsgCloseFullScreenFlashIfOpen, ThreadCloseFullScreenFlashIfOpen ); HTML_MSG_FUNC( eHTMLCommands_PauseFullScreenFlashMovieIfOpen, CMsgPauseFullScreenFlashMovieIfOpen, ThreadPauseFullScreenFlashMovieIfOpen ); default: bError = true; AssertMsg1( false, "Invalid message in browser stream (%d)", pCmd->m_eCmd ); break; } if ( pCmd->m_eCmd == eHTMLCommands_MouseDown || pCmd->m_eCmd == eHTMLCommands_MouseDblClick || pCmd->m_eCmd == eHTMLCommands_KeyDown ) m_bSawUserInputThisFrame = true; ReleaseCommandBuffer( pCmd ); } } //----------------------------------------------------------------------------- // Purpose: done with this buffer, put it in the free queue //----------------------------------------------------------------------------- void CCEFThread::ReleaseCommandBuffer( HTMLCommandBuffer_t *pBuf ) { pBuf->m_eCmd = eHTMLCommands_None; pBuf->m_iBrowser = -1; pBuf->m_Buffer.Clear(); m_tslUnsedBuffers.PushItem( pBuf ); } //----------------------------------------------------------------------------- // Purpose: get the next free cef thread command buffer to write into //----------------------------------------------------------------------------- HTMLCommandBuffer_t *CCEFThread::GetFreeCommandBuffer( EHTMLCommands eCmd, int iBrowser ) { HTMLCommandBuffer_t *pBuf; if ( !m_tslUnsedBuffers.PopItem( &pBuf ) ) // if nothing in the free queue just make a new one pBuf = new HTMLCommandBuffer_t; pBuf->m_eCmd = eCmd; pBuf->m_iBrowser = iBrowser; return pBuf; } //----------------------------------------------------------------------------- // Purpose: wait for a command of this type on the cef thread //----------------------------------------------------------------------------- HTMLCommandBuffer_t *CCEFThread::BWaitForCommand( EHTMLCommands eCmd, int iBrowser ) { while ( !m_bExit ) { if ( m_bSleepForValidate ) { m_bSleepingForValidate = true; ThreadSleep( 100 ); continue; } m_bSleepingForValidate = false; HTMLCommandBuffer_t *pBuf = NULL; while ( m_tslCommandBuffers.PopItem( &pBuf ) && pBuf ) { if ( pBuf->m_iBrowser == iBrowser ) { if ( pBuf->m_eCmd == eCmd ) // it is what we have been waiting for return pBuf; if ( pBuf->m_eCmd == eHTMLCommands_BrowserRemove ) // check to see if this is being deleted while its launching return NULL; } m_vecQueueCommands.AddToTail( pBuf ); pBuf = NULL; } Assert( pBuf == NULL ); m_evWaitingForCommand.Wait(); } return NULL; } //----------------------------------------------------------------------------- // Purpose: wait for a command of this type on the cef thread //----------------------------------------------------------------------------- HTMLCommandBuffer_t *CCEFThread::BWaitForResponse( EHTMLCommands eCmd, int iBrowser ) { while ( !m_bExit ) { if ( m_bSleepForValidate ) { m_bSleepingForValidate = true; ThreadSleep( 100 ); continue; } m_bSleepingForValidate = false; HTMLCommandBuffer_t *pBuf = NULL; while ( m_tslResponseBuffers.PopItem( &pBuf ) && pBuf ) { if ( pBuf->m_iBrowser == iBrowser ) { if ( pBuf->m_eCmd == eCmd ) // it is what we have been waiting for return pBuf; if ( pBuf->m_eCmd == eHTMLCommands_BrowserRemove ) // check to see if this is being deleted while its launching return NULL; } m_vecQueueResponses.AddToTail( pBuf ); pBuf = NULL; } Assert( pBuf == NULL ); m_evWaitingForResponse.Wait(); } return NULL; } //----------------------------------------------------------------------------- // Purpose: add a command for the cef thread //----------------------------------------------------------------------------- void CCEFThread::PushCommand( HTMLCommandBuffer_t *pBuf ) { m_tslCommandBuffers.PushItem( pBuf ); m_evWaitingForCommand.Set(); } //----------------------------------------------------------------------------- // Purpose: add a command for the main thread //----------------------------------------------------------------------------- void CCEFThread::PushResponse( HTMLCommandBuffer_t *pBuf ) { m_tslResponseBuffers.PushItem( pBuf ); m_evWaitingForResponse.Set(); } //----------------------------------------------------------------------------- // Purpose: get any cef responses on the main thread, returns false if there are none //----------------------------------------------------------------------------- bool CCEFThread::GetMainThreadCommand( HTMLCommandBuffer_t **pBuf ) { if ( m_vecQueueResponses.Count() ) { *pBuf = m_vecQueueResponses[0]; m_vecQueueResponses.Remove(0); return true; } else return m_tslResponseBuffers.PopItem( pBuf ); } //----------------------------------------------------------------------------- // Purpose: get the next cef thread protobuf command to run, return false if there are none //----------------------------------------------------------------------------- bool CCEFThread::GetCEFThreadCommand( HTMLCommandBuffer_t **pBuf ) { if ( m_vecQueueCommands.Count() ) { *pBuf = m_vecQueueCommands[0]; m_vecQueueCommands.Remove(0); return true; } else return m_tslCommandBuffers.PopItem( pBuf ); } //----------------------------------------------------------------------------- // Purpose: the user agent to report when using this browser control //----------------------------------------------------------------------------- const char *CCEFThread::PchWebkitUserAgent() { return "Mozilla/5.0 (%s; U; %s; en-US; %s/%llu; %s) AppleWebKit/535.15 (KHTML, like Gecko) Chrome/18.0.989.0 Safari/535.11"; } //----------------------------------------------------------------------------- // Purpose: make a new browser object //----------------------------------------------------------------------------- void CCEFThread::ThreadCreateBrowser( const CHTMLProtoBufMsg &htmlCommand ) { int idx = m_listClientHandlers.AddToTail(); ++m_nBrowserSerial; CClientHandler *pBrowser = new CClientHandler( BROWSER_HANDLE_FROM_INDEX_SERIAL( idx, m_nBrowserSerial ), PchWebkitUserAgent(), m_nBrowserSerial ); pBrowser->SetUserAgentIdentifier( htmlCommand.BodyConst().useragent().c_str() ); // send the handle info back to the main thread, needs to be before the CreateBrowser below // to stop a race condition of the browser being ready before the main thread knows its handle CHTMLProtoBufMsg cmd( eHTMLCommands_BrowserCreateResponse ); cmd.Body().set_browser_handle( BROWSER_HANDLE_FROM_INDEX_SERIAL( idx, m_nBrowserSerial ) ); cmd.Body().set_request_id( htmlCommand.BodyConst().request_id() ); HTMLCommandBuffer_t *pBuf = GetFreeCommandBuffer( eHTMLCommands_BrowserCreateResponse, idx ); cmd.SerializeCrossProc( &pBuf->m_Buffer ); PushResponse( pBuf ); m_listClientHandlers[idx] = pBrowser; pBrowser->AddRef(); CefWindowInfo info; info.SetAsOffScreen( NULL ); info.m_bPopupWindow = htmlCommand.BodyConst().popup(); CefBrowserSettings settings; settings.fullscreen_enabled = true; settings.threaded_compositing_enabled = true; settings.java_disabled = true; //settings.accelerated_compositing_enabled = true; // not supported when going into fullscreen mode // Drag and drop is supposed to be disabled automatically for offscreen views, but // ports for Mac and Linux have bugs where it is not really disabled, causing havoc settings.drag_drop_disabled = true; #ifdef LINUX // Turn off web features here that don't work on Linux settings.webgl_disabled = true; #endif CefBrowser::CreateBrowserSync( info, pBrowser, "", settings ); CefDoMessageLoopWork(); } //----------------------------------------------------------------------------- // Purpose: return true if this browser handle resolves into a currently valid browser handler object //----------------------------------------------------------------------------- bool CCEFThread::BIsValidBrowserHandle( uint32 nHandle, int &iClient ) { iClient = nHandle & 0xffff; if ( m_listClientHandlers.IsValidIndex( nHandle & 0xffff ) ) { if ( m_listClientHandlers[ iClient ]->NSerial() == BROWSER_SERIAL_FROM_HANDLE( nHandle ) ) return true; } iClient = -1; return false; } //----------------------------------------------------------------------------- // Purpose: delete this browser object, we are done with it //----------------------------------------------------------------------------- void CCEFThread::ThreadRemoveBrowser( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { m_listClientHandlers[iClient]->CloseBrowser(); m_listClientHandlers[iClient]->Release(); m_listClientHandlers[iClient] = NULL; m_listClientHandlers.Remove( iClient ); } } // helper macro to call browser functions if you have a valid handle #define GET_BROSWER_FUNC( msg, cmd ) \ int iClient; \ if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) \ { \ if ( m_listClientHandlers[iClient]->GetBrowser() ) \ m_listClientHandlers[iClient]->GetBrowser()->cmd; \ } \ //else \ // Assert( false ); //----------------------------------------------------------------------------- // Purpose: make the web page this many pixels wide&tall //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserSize( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, SetSize( PET_VIEW, htmlCommand.BodyConst().width(), htmlCommand.BodyConst().height() ) ); if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) m_listClientHandlers[iClient]->SetSize( htmlCommand.BodyConst().width(), htmlCommand.BodyConst().height() ); } //----------------------------------------------------------------------------- // Purpose: set the global position of the browser to these co-ords, used by some activeX controls //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserPosition( const CHTMLProtoBufMsg &htmlCommand ) { // no longer used - BUGBUG remove me } //----------------------------------------------------------------------------- // Purpose: load this url in the browser with optional post data //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserPostURL( const CHTMLProtoBufMsg &htmlCommand ) { if ( htmlCommand.BodyConst().url().empty() ) return; // they asked us to load nothing, ignore them const char *pchPostData = htmlCommand.BodyConst().post().c_str(); if ( !pchPostData || !pchPostData[0] ) { GET_BROSWER_FUNC( htmlCommand, GetMainFrame()->LoadURL( std::wstring( CStrAutoEncode( htmlCommand.BodyConst().url().c_str() ).ToWString() ) ) ); } else { // Create a new request CefRefPtr request(CefRequest::CreateRequest()); // Set the request URL request->SetURL( CStrAutoEncode( htmlCommand.BodyConst().url().c_str() ).ToWString() ); // Add post data to the request. The correct method and content- // type headers will be set by CEF. CefRefPtr postDataElement( CefPostDataElement::CreatePostDataElement() ); std::string data = pchPostData; postDataElement->SetToBytes(data.length(), data.c_str()); CefRefPtr postData(CefPostData::CreatePostData()); postData->AddElement(postDataElement); request->SetPostData(postData); GET_BROSWER_FUNC( htmlCommand, GetMainFrame()->LoadRequest( request ) ); } int iClient; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) m_listClientHandlers[iClient]->SetPendingPageSerial( htmlCommand.BodyConst().pageserial() ); CefDoMessageLoopWork(); // make sure CEF processes this load before we do anything else (like delete the browser control) } //----------------------------------------------------------------------------- // Purpose: stop loading the page we are on //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserStopLoad( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, StopLoad() ); } //----------------------------------------------------------------------------- // Purpose: reload the current page //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserReload( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, Reload() ); } //----------------------------------------------------------------------------- // Purpose: go forward one page in its history //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserGoForward( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, GoForward() ); } //----------------------------------------------------------------------------- // Purpose: go back one page in its history //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserGoBack( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, GoBack() ); } //----------------------------------------------------------------------------- // Purpose: copy selected text to the clipboard //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserCopy( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, GetMainFrame()->Copy() ); } //----------------------------------------------------------------------------- // Purpose: paste from the clipboard into the web page //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserPaste( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, GetMainFrame()->Paste() ); } //----------------------------------------------------------------------------- // Purpose: run this javascript on the current page //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserExecuteJavascript( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, GetMainFrame()->ExecuteJavaScript( CStrAutoEncode( htmlCommand.BodyConst().script().c_str() ).ToWString(), L"", 0 ) ); } //----------------------------------------------------------------------------- // Purpose: tell the browser it has key focus //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserSetFocus( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, SetFocus( htmlCommand.BodyConst().focus() ) ); } //----------------------------------------------------------------------------- // Purpose: get the size of the horizontal scroll bar //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserHorizontalScrollBarSize( const CHTMLProtoBufMsg &htmlCommand ) { ThreadBrowserHorizontalScrollBarSizeHelper( htmlCommand.BodyConst().browser_handle(), true ); } //----------------------------------------------------------------------------- // Purpose: tell the main thread details of the horiztonal scrollbar //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserHorizontalScrollBarSizeHelper( int iBrowser, bool bForceSendUpdate ) { CClientHandler::CachedScrollBarState_t scroll = { }; float flZoom = 0.0f; int iClient = 0; if ( BIsValidBrowserHandle( iBrowser, iClient ) ) { CClientHandler *pHandler = m_listClientHandlers[iClient]; if ( pHandler->GetBrowser() ) { scroll.m_nMax = pHandler->GetBrowser()->HorizontalScrollMax(); scroll.m_nScroll = pHandler->GetBrowser()->HorizontalScroll(); scroll.m_bVisible = pHandler->GetBrowser()->IsHorizontalScrollBarVisible(); pHandler->GetBrowser()->HorizontalScrollBarSize( scroll.m_nX, scroll.m_nY, scroll.m_nWide, scroll.m_nTall ); flZoom = pHandler->GetBrowser()->GetZoomLevel(); if ( scroll.m_nMax < 0 ) scroll.m_nMax = 0; if ( flZoom == 0.0f ) flZoom = 1.0f; } if ( bForceSendUpdate || 0 != memcmp( &pHandler->m_CachedHScroll, &scroll, sizeof( scroll ) ) ) { pHandler->m_CachedHScroll = scroll; CHTMLProtoBufMsg cmd( eHTMLCommands_HorizontalScrollBarSizeResponse ); cmd.Body().set_x( scroll.m_nX ); cmd.Body().set_y( scroll.m_nY ); cmd.Body().set_wide( scroll.m_nWide ); cmd.Body().set_tall( scroll.m_nTall ); cmd.Body().set_scroll_max( scroll.m_nMax ); cmd.Body().set_scroll( scroll.m_nScroll ); cmd.Body().set_visible( scroll.m_bVisible != 0 ); cmd.Body().set_zoom( flZoom ); int m_iBrowser = iBrowser; DISPATCH_MESSAGE( eHTMLCommands_HorizontalScrollBarSizeResponse ); } } } //----------------------------------------------------------------------------- // Purpose: get the size of the verical scrollbar //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserVerticalScrollBarSize( const CHTMLProtoBufMsg &htmlCommand ) { ThreadBrowserVerticalScrollBarSizeHelper( htmlCommand.BodyConst().browser_handle(), true ); } //----------------------------------------------------------------------------- // Purpose: tell the main thread details of the vertical scrollbar //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserVerticalScrollBarSizeHelper( int iBrowser, bool bForceSendUpdate ) { CClientHandler::CachedScrollBarState_t scroll = { }; float flZoom = 0.0f; int iClient = 0; if ( BIsValidBrowserHandle( iBrowser, iClient ) ) { CClientHandler *pHandler = m_listClientHandlers[iClient]; if ( pHandler->GetBrowser() ) { scroll.m_nMax = pHandler->GetBrowser()->VerticalScrollMax(); scroll.m_nScroll = pHandler->GetBrowser()->VerticalScroll(); scroll.m_bVisible = pHandler->GetBrowser()->IsVeritcalScrollBarVisible(); pHandler->GetBrowser()->VerticalScrollBarSize( scroll.m_nX, scroll.m_nY, scroll.m_nWide, scroll.m_nTall ); flZoom = pHandler->GetBrowser()->GetZoomLevel(); if ( scroll.m_nMax < 0 ) scroll.m_nMax = 0; if ( flZoom == 0.0f ) flZoom = 1.0f; } if ( bForceSendUpdate || 0 != memcmp( &pHandler->m_CachedVScroll, &scroll, sizeof( scroll ) ) ) { pHandler->m_CachedVScroll = scroll; CHTMLProtoBufMsg cmd( eHTMLCommands_VerticalScrollBarSizeResponse ); cmd.Body().set_x( scroll.m_nX ); cmd.Body().set_y( scroll.m_nY ); cmd.Body().set_wide( scroll.m_nWide ); cmd.Body().set_tall( scroll.m_nTall ); cmd.Body().set_scroll_max( scroll.m_nMax ); cmd.Body().set_scroll( scroll.m_nScroll ); cmd.Body().set_visible( scroll.m_bVisible != 0 ); cmd.Body().set_zoom( flZoom ); int m_iBrowser = iBrowser; DISPATCH_MESSAGE( eHTMLCommands_VerticalScrollBarSizeResponse ); } } } //----------------------------------------------------------------------------- // Purpose: start a find in a web page //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserFind( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, Find( 1, htmlCommand.BodyConst().find().c_str(), !htmlCommand.BodyConst().reverse(), false, htmlCommand.BodyConst().infind() ) ); } //----------------------------------------------------------------------------- // Purpose: dismiss a current find //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserStopFind( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, StopFinding( 1 ) ); } //----------------------------------------------------------------------------- // Purpose: scroll the page horizontally to this pos //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserSetHorizontalScroll( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, SetHorizontalScroll( htmlCommand.BodyConst().scroll() ) ); ThreadBrowserHorizontalScrollBarSizeHelper( htmlCommand.BodyConst().browser_handle(), true ); } //----------------------------------------------------------------------------- // Purpose: scroll the page vertically to this pos //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserSetVerticalScroll( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, SetVerticalScroll( htmlCommand.BodyConst().scroll() ) ); ThreadBrowserVerticalScrollBarSizeHelper( htmlCommand.BodyConst().browser_handle(), true ); } //----------------------------------------------------------------------------- // Purpose: set the zoom to this level, 100% means normal size, 200% double size //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserSetZoomLevel( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, SetZoomLevel( htmlCommand.BodyConst().zoom() ) ); CefDoMessageLoopWork(); // tell cef to think one frame, Zoom is in a work queue and we want it to apply right away so think now // now tell if about the scrollbars we now have ThreadBrowserHorizontalScrollBarSizeHelper( htmlCommand.BodyConst().browser_handle(), true ); ThreadBrowserVerticalScrollBarSizeHelper( htmlCommand.BodyConst().browser_handle(), true ); } //----------------------------------------------------------------------------- // Purpose: show the page source in notepad //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserViewSource( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, GetMainFrame()->ViewSource() ); } //----------------------------------------------------------------------------- // Purpose: add a header to any requests we make //----------------------------------------------------------------------------- void CCEFThread::ThreadAddHeader( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { m_listClientHandlers[iClient]->AddHeader( htmlCommand.BodyConst().key().c_str(), htmlCommand.BodyConst().value().c_str() ); } } //----------------------------------------------------------------------------- // Purpose: main thread is done with a paint buffer, tell our handler //----------------------------------------------------------------------------- void CCEFThread::ThreadNeedsPaintResponse( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { m_listClientHandlers[iClient]->SetTextureUploaded( htmlCommand.BodyConst().textureid() ); } } //----------------------------------------------------------------------------- // Purpose: set the error strings to display on page load //----------------------------------------------------------------------------- void CCEFThread::ThreadBrowserErrorStrings( const CHTMLProtoBufMsg &cmd ) { int iClient = 0; if ( BIsValidBrowserHandle( cmd.BodyConst().browser_handle(), iClient ) ) { m_listClientHandlers[iClient]->SetErrorStrings( cmd.BodyConst().title().c_str(), cmd.BodyConst().header().c_str(), cmd.BodyConst().cache_miss().c_str(), cmd.BodyConst().bad_url().c_str(), cmd.BodyConst().connection_problem().c_str(), cmd.BodyConst().proxy_problem().c_str(), cmd.BodyConst().unknown().c_str() ); } } //----------------------------------------------------------------------------- // Purpose: return the zoom level for the current page, 100% means actual size, 200% double size //----------------------------------------------------------------------------- void CCEFThread::ThreadGetZoom( const CHTMLProtoBufMsg &htmlCmd ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCmd.BodyConst().browser_handle(), iClient ) ) { float flZoom = 0.0f; if ( m_listClientHandlers[iClient]->GetBrowser() ) flZoom = m_listClientHandlers[iClient]->GetBrowser()->GetZoomLevel(); if ( flZoom == 0.0f ) flZoom = 1.0f; if ( flZoom > 100.0f ) flZoom /= 100.0f; CHTMLProtoBufMsg cmd( eHTMLCommands_GetZoomResponse ); cmd.Body().set_zoom( flZoom ); int m_iBrowser = htmlCmd.BodyConst().browser_handle(); DISPATCH_MESSAGE( eHTMLCommands_GetZoomResponse ); } } //----------------------------------------------------------------------------- // Purpose: handler func to throw the cookie call to the cef IO thread //----------------------------------------------------------------------------- void IOT_SetCookie(const CefString& url, CefCookie* cookie, CThreadEvent *pEvent ) { CefSetCookie(url, *cookie); pEvent->Set(); } //----------------------------------------------------------------------------- // Purpose: set this cookie into the cef instance //----------------------------------------------------------------------------- void CCEFThread::ThreadSetCookie( const CHTMLProtoBufMsg &htmlCommand ) { CefCookie cookie; cef_string_utf8_copy( htmlCommand.BodyConst().key().c_str(), htmlCommand.BodyConst().key().size(), &cookie.name ); cef_string_utf8_copy( htmlCommand.BodyConst().value().c_str(), htmlCommand.BodyConst().value().size(), &cookie.value ); cef_string_utf8_copy( htmlCommand.BodyConst().path().c_str(), htmlCommand.BodyConst().path().size(), &cookie.path ); if ( htmlCommand.BodyConst().has_expires() ) { AssertMsg( false, "Cookie expiration not implemented -- rtime.cpp needs to be ported." ); /* cookie.has_expires = true; CRTime rtExpire( (RTime32)htmlCommand.BodyConst().expires() ); cookie.expires.year = rtExpire.GetLocalYear(); cookie.expires.month = rtExpire.GetLocalMonth(); #if !defined(OS_MACOSX) cookie.expires.day_of_week = rtExpire.GetLocalDayOfWeek(); #endif cookie.expires.day_of_month = rtExpire.GetLocalMonth(); cookie.expires.hour = rtExpire.GetLocalHour(); cookie.expires.minute = rtExpire.GetLocalMinute(); cookie.expires.second = rtExpire.GetLocalSecond(); */ } CThreadEvent event; CefPostTask(TID_IO, NewCefRunnableFunction( IOT_SetCookie, htmlCommand.BodyConst().host().c_str(), &cookie, &event)); event.Wait(); } struct CCefCookie { CUtlString sValue; CUtlString sName; CUtlString sDomain; CUtlString sPath; }; //----------------------------------------------------------------------------- // Purpose: helper class to iterator cookies //----------------------------------------------------------------------------- class CookieVisitor : public CefCookieVisitor { public: CookieVisitor( CUtlVector *pVec, CThreadEvent *pEvent ) { m_pVecCookies = pVec; m_pEvent = pEvent; } ~CookieVisitor() { m_pEvent->Set(); } virtual bool Visit(const CefCookie& cookie, int count, int total, bool& deleteCookie) { CCefCookie cookieCopy; cookieCopy.sValue = cookie.value.str; cookieCopy.sName = cookie.name.str; cookieCopy.sDomain = cookie.domain.str; cookieCopy.sPath = cookie.path.str; m_pVecCookies->AddToTail( cookieCopy ); return true; } private: CUtlVector *m_pVecCookies; CThreadEvent *m_pEvent; IMPLEMENT_REFCOUNTING(CookieVisitor); }; //----------------------------------------------------------------------------- // Purpose: handler func to throw the cookie call to the cef IO thread //----------------------------------------------------------------------------- void IOT_CookiesForURL(const CefString& url, CThreadEvent *pEvent, CUtlVector *pVecCookies ) { CefVisitUrlCookies( url, false, new CookieVisitor( pVecCookies, pEvent ) ); } //----------------------------------------------------------------------------- // Purpose: get all the cookies for this URL //----------------------------------------------------------------------------- void CCEFThread::ThreadGetCookiesForURL( const CHTMLProtoBufMsg &htmlCommand ) { CUtlVector vecCookies; CThreadEvent event; CefPostTask(TID_IO, NewCefRunnableFunction( IOT_CookiesForURL, htmlCommand.BodyConst().url().c_str(), &event, &vecCookies )); event.Wait(); CHTMLProtoBufMsg cmd( eHTMLCommands_GetCookiesForURLResponse ); int m_iBrowser = htmlCommand.BodyConst().browser_handle(); cmd.Body().set_url( htmlCommand.BodyConst().url() ); FOR_EACH_VEC( vecCookies, i ) { CCookie *pCookie = cmd.Body().add_cookies(); pCookie->set_name( vecCookies[i].sName ); pCookie->set_value( vecCookies[i].sValue ); pCookie->set_domain( vecCookies[i].sDomain ); pCookie->set_path( vecCookies[i].sPath ); } DISPATCH_MESSAGE( eHTMLCommands_GetCookiesForURLResponse ); } //----------------------------------------------------------------------------- // Purpose: set the framerate to run CEF at //----------------------------------------------------------------------------- void CCEFThread::ThreadSetTargetFrameRate( const CHTMLProtoBufMsg &htmlCommand ) { m_nTargetFrameRate = htmlCommand.BodyConst().ntargetframerate(); } //----------------------------------------------------------------------------- // Purpose: hide any showing popup for this browser //----------------------------------------------------------------------------- void CCEFThread::ThreadHidePopup( const CHTMLProtoBufMsg &htmlCommand ) { GET_BROSWER_FUNC( htmlCommand, HidePopup() ); } //----------------------------------------------------------------------------- // Purpose: request a full redraw of the client //----------------------------------------------------------------------------- void CCEFThread::ThreadFullRepaint( const CHTMLProtoBufMsg &htmlCommand ) { int iClient; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { if ( m_listClientHandlers[iClient]->GetBrowser()->IsVisuallyNonEmpty() ) { int wide, tall; m_listClientHandlers[iClient]->GetExpectedSize( wide, tall); CefRect rect; rect.x = rect.y = 0; rect.width = wide; rect.height = tall; m_listClientHandlers[iClient]->GetBrowser()->Invalidate( rect ); } else m_listClientHandlers[iClient]->GetBrowser()->Reload(); } } //----------------------------------------------------------------------------- // Purpose: configure the options we want for cef //----------------------------------------------------------------------------- void CCEFThread::AppGetSettings(CefSettings& settings, CefRefPtr& app) { settings.multi_threaded_message_loop = false; #if defined(OS_WIN) settings.auto_detect_proxy_settings_enabled = true; #endif CefString(&settings.cache_path) = m_sHTMLCacheDir; CefString(&settings.product_version) = "Steam"; // CefString(&settings.log_file) = /*#ifdef WIN32 settings.graphics_implementation = ANGLE_IN_PROCESS_COMMAND_BUFFER; #else*/ settings.graphics_implementation = DESKTOP_IN_PROCESS_COMMAND_BUFFER; //#endif } //----------------------------------------------------------------------------- // Purpose: clean up the temp folders cef can leave around on crash //----------------------------------------------------------------------------- void CCEFThread::CleanupTempFolders() { /* // Temporarily commented out to avoid bringing in additional code. #if defined( WIN32 ) char rgchPath[MAX_PATH]; if ( GetTempPathA( Q_ARRAYSIZE( rgchPath ), rgchPath ) == 0 ) return; CUtlString strPath = rgchPath; #elif defined( LINUX ) || defined( OSX ) // TMPDIR first, T_tmpdir next, /tmp last. char *pszDir = getenv( "TMPDIR" ); if ( pszDir == NULL ) { pszDir = P_tmpdir; if ( pszDir == NULL ) pszDir = "/tmp"; } if ( pszDir == NULL ) return; CUtlString strPath = pszDir; #endif if ( strPath[strPath.Length()-1] != CORRECT_PATH_SEPARATOR ) strPath += CORRECT_PATH_SEPARATOR_S; CUtlString strSearch = strPath; strSearch += "scoped_dir*"; CDirIterator tempDirIterator( strSearch.String() ); while ( tempDirIterator.BNextFile() ) { if ( tempDirIterator.BCurrentIsDir() ) { CUtlString sStrDir = strPath; sStrDir += tempDirIterator.CurrentFileName(); BRemoveDirectoryRecursive( sStrDir ); } } */ } //----------------------------------------------------------------------------- // Purpose: main thread for pumping CEF, get commands from the main thread and dispatches responses to it //----------------------------------------------------------------------------- int CCEFThread::Run() { { // scope to trigger destructors before setting the we have exited event CefSettings settings; CefRefPtr app; // blow away any temp folders CEF left lying around if we crashed last CleanupTempFolders(); // Populate the settings based on command line arguments. AppGetSettings(settings, app); // Initialize CEF. CefInitialize(settings, app, ""); #if defined( VPROF_ENABLED ) //CVProfile *pProfile = GetVProfProfileForCurrentThread(); #endif CLimitTimer timer; CLimitTimer timerLastCefThink; // track when we think cef so we can do it at a minimum of 10hz timerLastCefThink.SetLimit( k_nMillion ); // 1Hz min think time #ifdef WIN32 CLimitTimer timerLastFlashFullscreenThink; // track when we think cef so we can do it at a minimum of 10hz timerLastFlashFullscreenThink.SetLimit( k_nMillion * k_nMillion ); // think again in the distance future bool bInitialThinkAfterInput = false; #endif while( !m_bExit ) { #ifdef OSX void *pool = CreateAutoReleasePool(); #endif if ( m_bSleepForValidate ) { m_bSleepingForValidate = true; ThreadSleep( 100 ); continue; } m_bSleepingForValidate = false; m_bSawUserInputThisFrame = false; #if defined( VPROF_ENABLED ) //if ( pProfile ) // pProfile->MarkFrame( "UI CEF HTML Thread" ); #endif // Limit animation frame rate timer.SetLimit( k_nMillion/m_nTargetFrameRate ); // run any pending commands, get ack'd paint buffers { VPROF_BUDGET( "CCEFThread - RunCurrentCommands()", VPROF_BUDGETGROUP_VGUI ); RunCurrentCommands(); } // now let cef think if ( m_listClientHandlers.Count() || timerLastCefThink.BLimitReached() ) { VPROF_BUDGET( "CCEFThread - CefDoMessageLoopWork()", VPROF_BUDGETGROUP_TENFOOT ); CefDoMessageLoopWork(); timerLastCefThink.SetLimit( k_nMillion ); // 1Hz min think time } #ifdef OSX ReleaseAutoReleasePool( pool ); #endif // push any changes to scrollbar data and refresh HTML element hover states { VPROF_BUDGET( "CCEFThread - Scroll", VPROF_BUDGETGROUP_VGUI ); FOR_EACH_LL( m_listClientHandlers, i ) { int nBrowser = BROWSER_HANDLE_FROM_INDEX_SERIAL( i, m_listClientHandlers[i]->NSerial() ); ThreadBrowserHorizontalScrollBarSizeHelper( nBrowser, false ); ThreadBrowserVerticalScrollBarSizeHelper( nBrowser, false ); // workaround a CEF issue where mouse hover states don't update after scrolling m_listClientHandlers[i]->RefreshCEFHoverStatesAfterScroll(); } } { VPROF_BUDGET( "CCEFThread - SendSizeChangeEvents", VPROF_BUDGETGROUP_VGUI ); SendSizeChangeEvents(); } //see if we need a paint { VPROF_BUDGET( "CCEFThread - Paint", VPROF_BUDGETGROUP_VGUI ); FOR_EACH_LL( m_listClientHandlers, i ) { if ( !m_listClientHandlers[i]->GetBrowser() ) continue; m_listClientHandlers[i]->Paint(); if ( m_listClientHandlers[i]->BPaintBufferReady() && m_listClientHandlers[i]->BNeedsPaint() ) { uint32 textureID = m_listClientHandlers[i]->FlipTexture(); const byte *pRGBA = m_listClientHandlers[i]->PComposedTextureData( textureID ); if ( pRGBA ) { CClientHandler *pHandler = m_listClientHandlers[i]; pHandler->Lock(); if ( pHandler->BPendingScreenShot() ) { pHandler->SavePageToJPEGIfNeeded( pHandler->GetBrowser(), pRGBA, pHandler->GetTextureWide(), pHandler->GetTextureTall() ); } CHTMLProtoBufMsg cmd( eHTMLCommands_NeedsPaint ); cmd.Body().set_browser_handle( BROWSER_HANDLE_FROM_INDEX_SERIAL( i, pHandler->NSerial() ) ); cmd.Body().set_wide( pHandler->GetTextureWide() ); cmd.Body().set_tall( pHandler->GetTextureTall() ); cmd.Body().set_rgba( (uint64)pRGBA ); cmd.Body().set_pageserial( pHandler->GetPageSerial() ); cmd.Body().set_textureid( textureID ); cmd.Body().set_updatex( pHandler->GetUpdateX() ); cmd.Body().set_updatey( pHandler->GetUpdateY() ); cmd.Body().set_updatewide( pHandler->GetUpdateWide() ); cmd.Body().set_updatetall( pHandler->GetUpdateTall() ); cmd.Body().set_scrollx( pHandler->GetBrowser()->HorizontalScroll() ); cmd.Body().set_scrolly( pHandler->GetBrowser()->VerticalScroll() ); if ( pHandler->BPopupVisibleAndPainted() ) // add in the combo box's texture data if visible { int x,y,wide,tall; // popup sizes pHandler->PopupRect( x, y, wide, tall ); cmd.Body().set_combobox_rgba( (uint64)pHandler->PPopupTextureDataCached() ); cmd.Body().set_combobox_wide( wide ); cmd.Body().set_combobox_tall( tall ); } // Texture update rect has now been pushed to main thread pHandler->ClearUpdateRect(); HTMLCommandBuffer_t *pBuf = g_CEFThread.GetFreeCommandBuffer( eHTMLCommands_NeedsPaint, i ); cmd.SerializeCrossProc( &pBuf->m_Buffer ); pHandler->Unlock(); g_CEFThread.PushResponse( pBuf ); } else { m_listClientHandlers[i]->SetTextureUploaded( textureID ); } } } } #ifdef WIN32 if ( m_bSawUserInputThisFrame ) { if ( timerLastFlashFullscreenThink.CMicroSecLeft() > k_nMillion/10 || timerLastFlashFullscreenThink.BLimitReached() ) { timerLastFlashFullscreenThink.SetLimit( k_nMillion/10 ); // check in 100msec } bInitialThinkAfterInput = true; } if ( m_listClientHandlers.Count() && timerLastFlashFullscreenThink.BLimitReached() ) { CheckForFullScreenFlashControl(); if ( ( !m_bFullScreenFlashVisible && bInitialThinkAfterInput ) || m_bFullScreenFlashVisible ) { timerLastFlashFullscreenThink.SetLimit( k_nMillion ); // could be a slow machine, check again in 1 sec } else { timerLastFlashFullscreenThink.SetLimit( k_nMillion * k_nMillion ); // think again in the distance future } bInitialThinkAfterInput= false; } #endif { VPROF_BUDGET( "Sleep - FPS Limiting", VPROF_BUDGETGROUP_TENFOOT ); if ( timer.BLimitReached() ) ThreadSleep( 1 ); else m_WakeEvent.Wait( timer.CMicroSecLeft() / 1000 ); } } FOR_EACH_LL( m_listClientHandlers, i ) { if ( m_listClientHandlers[i] ) m_listClientHandlers[i]->CloseBrowser(); m_listClientHandlers[i] = NULL;; } m_listClientHandlers.RemoveAll(); CefDoMessageLoopWork(); // pump the message loop to clear the close browser calls from above CefShutdown(); } m_eventDidExit.Set(); return 0; } //----------------------------------------------------------------------------- // Purpose: special code to sniff for flash doing its usual naughty things //----------------------------------------------------------------------------- void CCEFThread::CheckForFullScreenFlashControl() { #ifdef WIN32 VPROF_BUDGET( "Searching for Flash fullscreen - FindWindowEx", VPROF_BUDGETGROUP_TENFOOT ); // see if we need to drag the flash fullscreen window to front HWND flashfullscreenHWND = ::FindWindowEx( NULL, NULL, "ShockwaveFlashFullScreen", NULL ); if ( flashfullscreenHWND ) { DWORD proccess_id; GetWindowThreadProcessId( flashfullscreenHWND, &proccess_id); TCHAR exe_path[MAX_PATH]; GetModuleFileName( GetModuleHandle(NULL), exe_path, MAX_PATH); HANDLE hmodule = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, proccess_id); MODULEENTRY32 mod = { sizeof(MODULEENTRY32) }; if ( Module32First( hmodule, &mod) ) { if ( Q_stricmp(mod.szExePath, exe_path) == 0 ) { if ( !m_bFullScreenFlashVisible ) { m_bFullScreenFlashVisible = true; m_flashfullscreenHWND = flashfullscreenHWND; FOR_EACH_LL( m_listClientHandlers, i ) { if ( m_listClientHandlers[i] ) m_listClientHandlers[i]->GetRenderHandler()->OnEnterFullScreen( m_listClientHandlers[i]->GetBrowser() ); } SetForegroundWindow( m_flashfullscreenHWND ); } } else { if ( m_bFullScreenFlashVisible ) { m_bFullScreenFlashVisible = false; m_flashfullscreenHWND = NULL; FOR_EACH_LL( m_listClientHandlers, i ) { if ( m_listClientHandlers[i] ) m_listClientHandlers[i]->GetRenderHandler()->OnExitFullScreen( m_listClientHandlers[i]->GetBrowser() ); } } } } CloseHandle( hmodule ); } else { if ( m_bFullScreenFlashVisible ) { m_bFullScreenFlashVisible = false; m_flashfullscreenHWND = NULL; FOR_EACH_LL( m_listClientHandlers, i ) { if ( m_listClientHandlers[i] ) m_listClientHandlers[i]->GetRenderHandler()->OnExitFullScreen( m_listClientHandlers[i]->GetBrowser() ); } } } #else #warning "Do we need to sniff for fullscreen flash and it breaking us?" #endif } #ifdef DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: validate mem //----------------------------------------------------------------------------- void CCEFThread::Validate( CValidator &validator, const tchar *pchName ) { // hacky but reliable way to avoid both vgui and panorama validating all this stuff twice if ( !validator.IsClaimed( m_sHTMLCacheDir.Access() ) ) { VALIDATE_SCOPE(); ValidateObj( m_sHTMLCacheDir ); ValidateObj( m_sCookiePath ); ValidateObj( m_listClientHandlers ); FOR_EACH_LL( m_listClientHandlers, i ) { ValidatePtr( m_listClientHandlers[i] ); } ValidateObj( m_vecQueueCommands ); FOR_EACH_VEC( m_vecQueueCommands, i ) { ValidatePtr( m_vecQueueCommands[i] ); } ValidateObj( m_vecQueueResponses ); FOR_EACH_VEC( m_vecQueueResponses, i ) { ValidatePtr( m_vecQueueResponses[i] ); } ValidateObj( m_tslUnsedBuffers ); { CTSList::Node_t *pNode = m_tslUnsedBuffers.Detach(); while ( pNode ) { CTSList::Node_t *pNext = (CTSList::Node_t *)pNode->Next; ValidatePtr( pNode->elem ); m_tslUnsedBuffers.Push( pNode ); pNode = pNext; } } ValidateObj( m_tslCommandBuffers ); ValidateObj( m_tslResponseBuffers ); } } #endif //----------------------------------------------------------------------------- // Purpose: turn on CEF and its supporting thread //----------------------------------------------------------------------------- void ChromeInit( const char *pchHTMLCacheDir, const char *pchCookiePath ) { Assert( !g_CEFThread.IsAlive() ); g_CEFThread.SetCEFPaths( pchHTMLCacheDir, pchCookiePath ); g_CEFThread.SetName( "UICEFThread" ); g_CEFThread.Start(); } //----------------------------------------------------------------------------- // Purpose: turn off CEF //----------------------------------------------------------------------------- void ChromeShutdown() { g_CEFThread.TriggerShutdown(); g_CEFThread.Join( 20 *k_nThousand ); } #ifdef DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: suspend the cef thread so we can validate mem //----------------------------------------------------------------------------- bool ChromePrepareForValidate() { g_CEFThread.SleepForValidate(); while ( !g_CEFThread.BSleepingForValidate() ) ThreadSleep( 100 ); return true; } //----------------------------------------------------------------------------- // Purpose: wake the cef thread back up //----------------------------------------------------------------------------- bool ChromeResumeFromValidate() { g_CEFThread.WakeFromValidate(); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void ChromeValidate( CValidator &validator, const char *pchName ) { g_CEFThread.Validate( validator, "g_CEFThread" ); } #endif //----------------------------------------------------------------------------- // Purpose: set this cookie to be used by cef //----------------------------------------------------------------------------- bool ChromeSetWebCookie( const char *pchHostname, const char *pchName, const char *pchValue, const char *pchPath, RTime32 nExpires ) { CHTMLProtoBufMsg< CMsgSetCookie > cmd( eHTMLCommands_SetCookie ); cmd.Body().set_value( pchValue ); cmd.Body().set_key( pchName ); cmd.Body().set_path( pchPath ); cmd.Body().set_host( pchHostname ); if ( nExpires ) cmd.Body().set_expires( nExpires ); HTMLCommandBuffer_t *pBuf = g_CEFThread.GetFreeCommandBuffer( eHTMLCommands_SetCookie, -1 ); cmd.SerializeCrossProc( &pBuf->m_Buffer ); g_CEFThread.PushCommand( pBuf ); g_CEFThread.WakeThread(); return true; } //----------------------------------------------------------------------------- // Purpose: set this cookie to be used by cef //----------------------------------------------------------------------------- bool ChromeGetWebCookiesForURL( CUtlString *pstrValue, const char *pchURL, const char *pchName ) { pstrValue->Clear(); { CHTMLProtoBufMsg cmd( eHTMLCommands_GetCookiesForURL ); cmd.Body().set_url( pchURL ); HTMLCommandBuffer_t *pCmd = g_CEFThread.GetFreeCommandBuffer( eHTMLCommands_GetCookiesForURL, -1 ); cmd.SerializeCrossProc( &pCmd->m_Buffer ); g_CEFThread.PushCommand( pCmd ); } HTMLCommandBuffer_t *pBuf = g_CEFThread.BWaitForResponse( eHTMLCommands_GetCookiesForURLResponse, -1 ); if ( pBuf ) { CHTMLProtoBufMsg< CMsgGetCookiesForURLResponse > cmdResponse( eHTMLCommands_GetCookiesForURLResponse ); if ( cmdResponse.BDeserializeCrossProc( &pBuf->m_Buffer ) ) { for ( int i = 0; i < cmdResponse.BodyConst().cookies_size(); i++ ) { const CCookie &cookie = cmdResponse.BodyConst().cookies(i); if ( cookie.name() == pchName ) pstrValue->Set( cookie.value().c_str() ); } } g_CEFThread.ReleaseCommandBuffer( pBuf ); } return true; } //----------------------------------------------------------------------------- // Purpose: set the build number to report in our user agent //----------------------------------------------------------------------------- void ChromeSetClientBuildID( uint64 ulBuildID ) { sm_ulBuildID = ulBuildID; } //----------------------------------------------------------------------------- // Purpose: constructor //----------------------------------------------------------------------------- CChromePainter::CChromePainter( CClientHandler *pParent ) { m_iNextTexture = 0; m_iTexturesInFlightBits = 0; m_pParent = pParent; m_bUpdated = false; m_bPopupVisible = false; } //----------------------------------------------------------------------------- // Purpose: destructor //----------------------------------------------------------------------------- CChromePainter::~CChromePainter() { } //----------------------------------------------------------------------------- // Purpose: cef is calling us back and saying it updated the html texture //----------------------------------------------------------------------------- void CChromePainter::OnPaint(CefRefPtr browser, PaintElementType type, const RectList& dirtyRects, const void* buffer) { VPROF_BUDGET( "CChromePainter::DrawSubTextureRGBA", VPROF_BUDGETGROUP_VGUI ); int wide, tall; browser->GetSize( type, wide, tall ); if ( wide <= 0 || tall <= 0 ) return; if ( type == PET_POPUP ) { m_nPopupWide = wide; m_nPopupTall = tall; m_PopupTexture.EnsureCount( tall*wide*4 ); Q_memcpy( m_PopupTexture.Base(), buffer, tall*wide*4 ); // force a recomposition + display whenever painting a popup m_bUpdated = true; } else { // main browser painting if ( !m_pParent->IsVisuallyNonEmpty() ) { return; } // If there were no dirty regions (unlikely), perhaps due to a bug, be conservative and paint all if ( dirtyRects.empty() ) { m_MainTexture.MarkAllDirty(); } else { for ( RectList::const_iterator iter = dirtyRects.begin(); iter != dirtyRects.end(); ++iter ) { m_MainTexture.MarkDirtyRect( iter->x, iter->y, iter->x + iter->width, iter->y + iter->height ); } } // Refresh all dirty main texture pixels from the chromium rendering buffer if ( m_MainTexture.BUpdatePixels( (byte*)buffer, wide, tall ) ) { // at least one pixel in the main texture has changed m_bUpdated = true; // Notify the main thread that this newly painted region is dirty m_UpdateRect.MarkDirtyRect( m_MainTexture ); // Merge update region into all buffer textures so that at composition time, // they know to copy the union of all updates since the last composition for ( size_t i = 0; i < Q_ARRAYSIZE(m_Texture); ++i ) { m_Texture[i].MarkDirtyRect( m_MainTexture ); } } // The main texture is now a clean copy of chromium's canvas backing m_MainTexture.MarkAllClean(); } } //----------------------------------------------------------------------------- // Purpose: true if we have had a paint call from cef //----------------------------------------------------------------------------- bool CChromePainter::BUpdated() { return m_bUpdated; } //----------------------------------------------------------------------------- // Purpose: force the updated state //----------------------------------------------------------------------------- void CChromePainter::SetUpdated( bool state ) { m_bUpdated = state; } //----------------------------------------------------------------------------- // Purpose: move to the next html texture to render into //----------------------------------------------------------------------------- uint32 CChromePainter::FlipTexture() { int iTex = m_iNextTexture; m_iTexturesInFlightBits |= ( 1< 0 && m_MainTexture.GetTall() > 0) && !( m_iTexturesInFlightBits & (1< parentBrowser, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, const CefString& url, bool bForeground, CefRefPtr& client, CefBrowserSettings& settings ) { CHTMLProtoBufMsg cmd( eHTMLCommands_OpenNewTab ); cmd.Body().set_url( url.c_str() ); cmd.Body().set_bforeground( bForeground ); DISPATCH_MESSAGE( eHTMLCommands_OpenNewTab ); return true; } //----------------------------------------------------------------------------- // Purpose: // Called before a new popup window is created. The |parentBrowser| parameter // will point to the parent browser window. The |popupFeatures| parameter will // contain information about the style of popup window requested. Return false // to have the framework create the new popup window based on the parameters // in |windowInfo|. Return true to cancel creation of the popup window. By // default, a newly created popup window will have the same client and // settings as the parent window. To change the client for the new window // modify the object that |client| points to. To change the settings for the // new window modify the |settings| structure. //----------------------------------------------------------------------------- bool CClientHandler::OnBeforePopup(CefRefPtr parentBrowser, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, const CefString& url, CefRefPtr& client, CefBrowserSettings& settings) { // If it's a steam:// url we already have a scheme handler installed, however that's too late to block the frame navigating // and we'll end up loading a blank new window. So preempt that happening here and handle early returning that we've handled so // chromium won't actually change URLs or navigate at all. if ( url.size() > 0 && ( Q_stristr( url.c_str(), "steam://" ) || Q_stristr( url.c_str(), "steambeta://" ) ) && Q_stristr( url.c_str(), "/close" ) == NULL ) { CStrAutoEncode urlString( url.c_str() ); CHTMLProtoBufMsg cmd( eHTMLCommands_OpenSteamURL ); cmd.Body().set_url( urlString.ToString() ); DISPATCH_MESSAGE( eHTMLCommands_OpenSteamURL ); return true; } CStrAutoEncode urlString( url.c_str() ); static bool bInPopup = false; // if we get an empty url string when loading a new popup page just don't load it, we don't support the make a // new popup and .write() into the buffer to make the contents because we want to make a whole new VGUI HTML object // that contains the webkit widget for that popup, not allow this inline one if ( urlString.ToString() && Q_strlen( urlString.ToString() ) > 0 ) { if ( !bInPopup ) { bInPopup = true; CHTMLProtoBufMsg cmd( eHTMLCommands_PopupHTMLWindow ); cmd.Body().set_url( urlString.ToString() ); if ( popupFeatures.xSet ) cmd.Body().set_x( popupFeatures.x ); if ( popupFeatures.ySet ) cmd.Body().set_y( popupFeatures.y ); if ( popupFeatures.widthSet ) cmd.Body().set_wide( popupFeatures.width ); if ( popupFeatures.heightSet ) cmd.Body().set_tall( popupFeatures.height ); DISPATCH_MESSAGE( eHTMLCommands_PopupHTMLWindow ); bInPopup = false; return true; } } if ( !bInPopup ) { return true; } return false; } //----------------------------------------------------------------------------- // Purpose: // Event called after a new window is created. The return value is currently // ignored. //----------------------------------------------------------------------------- void CClientHandler::OnAfterCreated(CefRefPtr browser) { Lock(); if ( !m_Browser ) { // We need to keep the main child window, but not popup windows m_Browser = browser; browser->SetShowScrollBars( false ); SetBrowserAgent( browser ); if ( m_nExpectedWide > 0 && m_nExpectedTall > 0 ) browser->SetSize( PET_VIEW, m_nExpectedWide, m_nExpectedTall ); CHTMLProtoBufMsg cmd( eHTMLCommands_BrowserReady ); DISPATCH_MESSAGE( eHTMLCommands_BrowserReady ); } Unlock(); } //----------------------------------------------------------------------------- // Purpose: // Event called when the page title changes. The return value is currently // ignored. //----------------------------------------------------------------------------- void CClientHandler::OnTitleChange(CefRefPtr browser, const CefString& title) { if ( !title.empty() ) { CHTMLProtoBufMsg cmd( eHTMLCommands_SetHTMLTitle ); cmd.Body().set_title( title ); DISPATCH_MESSAGE( eHTMLCommands_SetHTMLTitle ); } } //----------------------------------------------------------------------------- // Purpose: // Event called before browser navigation. The client has an opportunity to // modify the |request| object if desired. Return RV_HANDLED to cancel // navigation. //----------------------------------------------------------------------------- bool CClientHandler::OnBeforeBrowse(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, NavType navType, bool isRedirect, bool isNewTabRequest ) { std::string sURL = request->GetURL(); // If it's a steam:// url we already have a scheme handler installed, however that's too late to block the frame navigating // and we'll end up loading a blank page. So preempt that happening here and handle early returning that we've handled so // chromium won't actually change URLs or navigate at all. if ( sURL.size() > 0 && (sURL.find( "steam://" ) == 0 || sURL.find( "steambeta://" ) == 0) && sURL.find( "/close" ) == std::wstring::npos ) { CHTMLProtoBufMsg cmd( eHTMLCommands_OpenSteamURL ); cmd.Body().set_url( CStrAutoEncode( request->GetURL().c_str() ).ToString() ); DISPATCH_MESSAGE( eHTMLCommands_OpenSteamURL ); return true ; } if ( isNewTabRequest ) return false; // We only care about these on the main frame if( !frame.get() || !frame->IsMain() ) return false; if ( request->GetPostData() ) { CefPostData::ElementVector elements; request->GetPostData()->GetElements( elements ); CefPostData::ElementVector::const_iterator it = elements.begin(); m_strPostData = ""; CUtlVector vecPostBytes; while ( it != elements.end() ) { if ( it->get()->GetType() == PDE_TYPE_BYTES ) { size_t nBytes = it->get()->GetBytesCount(); int curInsertPos = vecPostBytes.Count(); vecPostBytes.EnsureCount( curInsertPos + nBytes + 1 ); it->get()->GetBytes( nBytes, vecPostBytes.Base() + curInsertPos ); vecPostBytes[ curInsertPos + nBytes ] = 0; } it++; } m_strPostData = vecPostBytes.Base(); } else m_strPostData = ""; CStrAutoEncode strURL( sURL.c_str() ); if ( isRedirect ) m_strLastRedirectURL = strURL.ToString(); bool rv = false; { // scope this so the wait below doesn't keep this allocation on the stack CHTMLProtoBufMsg cmd( eHTMLCommands_StartRequest ); cmd.Body().set_url( strURL.ToString() ); CefString frameName = frame->GetName(); if ( !frameName.empty() ) cmd.Body().set_target( frameName.c_str() ); cmd.Body().set_postdata( m_strPostData ); cmd.Body().set_bisredirect( isRedirect ); DISPATCH_MESSAGE( eHTMLCommands_StartRequest ); } HTMLCommandBuffer_t *pBuf = g_CEFThread.BWaitForCommand( eHTMLCommands_StartRequestResponse, m_iBrowser ); if ( pBuf ) { CHTMLProtoBufMsg< CMsgStartRequestResponse > cmd( eHTMLCommands_StartRequestResponse ); if ( cmd.BDeserializeCrossProc( &pBuf->m_Buffer ) ) { if ( !cmd.BodyConst().ballow() ) rv = true ; } g_CEFThread.ReleaseCommandBuffer( pBuf ); } if ( m_Snapshot.m_sURLSnapshot.IsValid() ) m_Snapshot.m_flRequestTimeout = Plat_FloatTime(); // before we change URL lets queue up a snapshot for the next paint return rv; } //----------------------------------------------------------------------------- // Purpose: // Event called when the browser begins loading a page. The |frame| pointer // will be empty if the event represents the overall load status and not the // load status for a particular frame. The return value is currently ignored. //----------------------------------------------------------------------------- void CClientHandler::OnLoadStart(CefRefPtr browser, CefRefPtr frame, bool bIsNewNavigation ) { if ( !frame.get() ) return; { if ( !frame->IsMain() ) return; std::wstring sURL = frame->GetURL(); if ( sURL.empty() ) return; CStrAutoEncode url( sURL.c_str() ); m_strCurrentUrl = url.ToString(); if ( m_strCurrentUrl.IsEmpty() ) return; bool bIsRedirect = false; if ( m_strCurrentUrl == m_strLastRedirectURL ) bIsRedirect = true; CHTMLProtoBufMsg cmd( eHTMLCommands_URLChanged ); cmd.Body().set_url( url.ToString() ); cmd.Body().set_bnewnavigation( bIsNewNavigation ); if ( !m_strPostData.IsEmpty() ) cmd.Body().set_postdata( m_strPostData.String() ); cmd.Body().set_bisredirect( bIsRedirect ); CefString frameName = frame->GetName(); if ( !frameName.empty() ) cmd.Body().set_pagetitle( frameName.c_str() ); DISPATCH_MESSAGE( eHTMLCommands_URLChanged ); } { CHTMLProtoBufMsg cmd( eHTMLCommands_CanGoBackandForward ); cmd.Body().set_bgoback( browser->CanGoBack() ); cmd.Body().set_bgoforward( browser->CanGoForward() ); DISPATCH_MESSAGE( eHTMLCommands_CanGoBackandForward ); } m_nPageSerial = m_nPendingPageSerial; } //----------------------------------------------------------------------------- // Purpose: // Event called when the browser is done loading a page. The |frame| pointer // will be empty if the event represents the overall load status and not the // load status for a particular frame. This event will be generated // irrespective of whether the request completes successfully. The return // value is currently ignored. //----------------------------------------------------------------------------- void CClientHandler::OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode, CefRefPtr request ) { // We only care about these on the main frame if( !frame.get() || !frame->IsMain() ) return; { CHTMLProtoBufMsg cmd( eHTMLCommands_FinishedRequest ); cmd.Body().set_url( CStrAutoEncode( browser->GetMainFrame()->GetURL().c_str() ).ToString() ); CefString frameName = browser->GetMainFrame()->GetName(); if ( !frameName.empty() ) cmd.Body().set_pagetitle( frameName.c_str() ); if ( request.get() ) { CefRequest::HeaderMap headerMap; request->GetHeaderMap( headerMap ); CefRequest::HeaderMap::const_iterator it; for(it = headerMap.begin(); it != headerMap.end(); ++it) { CHTMLHeader *pHeader = cmd.Body().add_headers(); if ( !it->first.empty() ) pHeader->set_key( it->first.c_str() ); if ( !it->second.empty() ) pHeader->set_value( it->second.c_str() ); } CefRefPtr pRefSecurityDetails = request->SecurityDetails(); CHTMLPageSecurityInfo *pSecurityInfo = cmd.Body().mutable_security_info(); if ( pRefSecurityDetails.get() ) { pSecurityInfo->set_bissecure( pRefSecurityDetails->BIsSecure() ); pSecurityInfo->set_bhascerterror( pRefSecurityDetails->BHasCertError() ); pSecurityInfo->set_issuername( pRefSecurityDetails->PchCertIssuer() ); pSecurityInfo->set_certname( pRefSecurityDetails->PchCertCommonName() ); pSecurityInfo->set_certexpiry( pRefSecurityDetails->TCertExpiry() ); pSecurityInfo->set_bisevcert( pRefSecurityDetails->BIsEVCert() ); pSecurityInfo->set_ncertbits( pRefSecurityDetails->NCertBits() ); } else { pSecurityInfo->set_bissecure( false ); } } DISPATCH_MESSAGE( eHTMLCommands_FinishedRequest ); } { CHTMLProtoBufMsg cmd( eHTMLCommands_CanGoBackandForward ); cmd.Body().set_bgoback( browser->CanGoBack() ); cmd.Body().set_bgoforward( browser->CanGoForward() ); DISPATCH_MESSAGE( eHTMLCommands_CanGoBackandForward ); } if ( m_Snapshot.m_sURLSnapshot.IsValid() ) m_Snapshot.m_flRequestTimeout = Plat_FloatTime(); // finished page load, lets queue up a snapshot for the next paint } //----------------------------------------------------------------------------- // Purpose: // Called when the browser fails to load a resource. |errorCode| is the // error code number and |failedUrl| is the URL that failed to load. To // provide custom error text assign the text to |errorText| and return // RV_HANDLED. Otherwise, return RV_CONTINUE for the default error text. //----------------------------------------------------------------------------- bool CClientHandler::OnLoadError(CefRefPtr browser, CefRefPtr frame, ErrorCode errorCode, const CefString& failedUrl, CefString& errorText) { // If it's a steam:// url we always get an error, but we handle it ok internally already, just ignore if ( failedUrl.size() > 0 && ( Q_stristr( failedUrl.c_str(), "steam://" ) || Q_stristr( failedUrl.c_str(), "steambeta://" ) ) ) return false; const char *pchDetail = NULL; switch ( errorCode ) { case ERR_ABORTED: // We'll get this in cases where we just start another URL before finishing a previous and such, don't show it. return false; break; case ERR_CACHE_MISS: pchDetail = m_sErrorCacheMiss; break; case ERR_UNKNOWN_URL_SCHEME: case ERR_INVALID_URL: pchDetail = m_sErrorBadURL; break; case ERR_CONNECTION_CLOSED: case ERR_CONNECTION_RESET: case ERR_CONNECTION_REFUSED: case ERR_CONNECTION_ABORTED: case ERR_CONNECTION_FAILED: case ERR_NAME_NOT_RESOLVED: case ERR_INTERNET_DISCONNECTED: case ERR_CONNECTION_TIMED_OUT: pchDetail = m_sErrorConnectionProblem; break; case ERR_UNEXPECTED_PROXY_AUTH: case ERR_EMPTY_PROXY_LIST: pchDetail = m_sErrorProxyProblem; break; default: pchDetail = m_sErrorUnknown; break; } char rgchError[4096]; Q_snprintf( rgchError, Q_ARRAYSIZE( rgchError ), "" "" "%s" "" "" "

%s%d

" "

" "%s" "

" "" "", m_sErrorTitle.String(), m_sErrorHeader.String(), errorCode, pchDetail ); errorText = rgchError; return true; } //----------------------------------------------------------------------------- // Purpose: // Event called before a resource is loaded. To allow the resource to load // normally return RV_CONTINUE. To redirect the resource to a new url // populate the |redirectUrl| value and return RV_CONTINUE. To specify // data for the resource return a CefStream object in |resourceStream|, set // 'mimeType| to the resource stream's mime type, and return RV_CONTINUE. // To cancel loading of the resource return RV_HANDLED. //----------------------------------------------------------------------------- bool CClientHandler::OnBeforeResourceLoad(CefRefPtr browser, CefRefPtr request, CefString& redirectUrl, CefRefPtr& resourceStream, CefRefPtr response, int loadFlags) { CHTMLProtoBufMsg cmd( eHTMLCommands_LoadingResource ); cmd.Body().set_url( CStrAutoEncode( request->GetURL().c_str() ).ToString() ); DISPATCH_MESSAGE( eHTMLCommands_LoadingResource ); // insert custom headers CefRequest::HeaderMap headerMap; request->GetHeaderMap( headerMap ); FOR_EACH_VEC( m_vecHeaders, i ) { headerMap.insert( m_vecHeaders[i] ); } request->SetHeaderMap( headerMap ); return false; } //----------------------------------------------------------------------------- // Purpose: // Run a JS alert message. Return RV_CONTINUE to display the default alert // or RV_HANDLED if you displayed a custom alert. //----------------------------------------------------------------------------- bool CClientHandler::OnJSAlert(CefRefPtr browser, CefRefPtr frame, const CefString& message) { { // scope this so the wait below doesn't keep this allocation on the stack CHTMLProtoBufMsg cmd( eHTMLCommands_JSAlert ); cmd.Body().set_message( message.c_str() ); DISPATCH_MESSAGE( eHTMLCommands_JSAlert ); } HTMLCommandBuffer_t *pBuf = g_CEFThread.BWaitForCommand( eHTMLCommands_JSDialogResponse, m_iBrowser ); if ( pBuf ) { CHTMLProtoBufMsg< CMsgJSDialogResponse > cmd( eHTMLCommands_JSDialogResponse ); g_CEFThread.ReleaseCommandBuffer( pBuf ); } return true; } //----------------------------------------------------------------------------- // Purpose: // Run a JS confirm request. Return RV_CONTINUE to display the default alert // or RV_HANDLED if you displayed a custom alert. If you handled the alert // set |CefHandler::RetVal| to true if the user accepted the confirmation. //----------------------------------------------------------------------------- bool CClientHandler::OnJSConfirm(CefRefPtr browser, CefRefPtr frame, const CefString& message, bool& retval) { { // scope this so the wait below doesn't keep this allocation on the stack CHTMLProtoBufMsg cmd( eHTMLCommands_JSConfirm ); cmd.Body().set_message( message.c_str() ); DISPATCH_MESSAGE( eHTMLCommands_JSConfirm ); } retval = false; HTMLCommandBuffer_t *pBuf = g_CEFThread.BWaitForCommand( eHTMLCommands_JSDialogResponse, m_iBrowser ); if ( pBuf ) { CHTMLProtoBufMsg< CMsgJSDialogResponse > cmd( eHTMLCommands_JSDialogResponse ); if ( cmd.BDeserializeCrossProc( &pBuf->m_Buffer ) ) { if ( cmd.BodyConst().result() ) retval = true ; } g_CEFThread.ReleaseCommandBuffer( pBuf ); } return true; } //----------------------------------------------------------------------------- // Purpose: // Run a JS prompt request. Return RV_CONTINUE to display the default prompt // or RV_HANDLED if you displayed a custom prompt. If you handled the prompt // set |CefHandler::RetVal| to true if the user accepted the prompt and request and // |result| to the resulting value. //----------------------------------------------------------------------------- bool CClientHandler::OnJSPrompt(CefRefPtr browser, CefRefPtr frame, const CefString& message, const CefString& defaultValue, bool& retval, CefString& result) { retval = false; // just don't pop JS prompts for now result = defaultValue; return true; } //----------------------------------------------------------------------------- // Purpose: // Called just before a window is closed. //----------------------------------------------------------------------------- void CClientHandler::OnBeforeClose(CefRefPtr browser) { Lock(); if ( m_Browser ) { // Free the browser pointer so that the browser can be destroyed m_Browser = NULL; if ( !m_bBrowserClosing ) { CHTMLProtoBufMsg cmd( eHTMLCommands_Close ); DISPATCH_MESSAGE( eHTMLCommands_Close ); } } Unlock(); } //----------------------------------------------------------------------------- // Purpose: show a html popup, a pulldown menu or the like //----------------------------------------------------------------------------- void CChromePainter::OnPopupShow(CefRefPtr browser, bool show) { m_bPopupVisible = show; int m_iBrowser = m_pParent->m_iBrowser; if ( show ) { CHTMLProtoBufMsg cmd( eHTMLCommands_ShowPopup ); DISPATCH_MESSAGE( eHTMLCommands_ShowPopup ); } else { CHTMLProtoBufMsg cmd( eHTMLCommands_HidePopup ); DISPATCH_MESSAGE( eHTMLCommands_HidePopup ); // redraw the buffered texture behind the rectangle that was previously composited for ( size_t i = 0; i < Q_ARRAYSIZE( m_Texture ); ++i ) { m_Texture[i].MarkDirtyRect( m_nPopupX, m_nPopupY, m_nPopupX + m_nPopupWide, m_nPopupY + m_nPopupTall ); } // and notify the main thread to redraw the previously composited area as well m_UpdateRect.MarkDirtyRect( m_nPopupX, m_nPopupY, m_nPopupX + m_nPopupWide, m_nPopupY + m_nPopupTall ); } } //----------------------------------------------------------------------------- // Purpose: make the popup this big //----------------------------------------------------------------------------- void CChromePainter::OnPopupSize(CefRefPtr browser, const CefRect& rect ) { if ( m_bPopupVisible ) { // redraw the buffered texture behind the rectangle that was previously composited for ( size_t i = 0; i < Q_ARRAYSIZE( m_Texture ); ++i ) { m_Texture[i].MarkDirtyRect( m_nPopupX, m_nPopupY, m_nPopupX + m_nPopupWide, m_nPopupY + m_nPopupTall ); } // and notify the main thread to redraw the previously composited area as well m_UpdateRect.MarkDirtyRect( m_nPopupX, m_nPopupY, m_nPopupX + m_nPopupWide, m_nPopupY + m_nPopupTall ); } m_bPopupVisible = true; m_nPopupX = rect.x; m_nPopupWide = rect.width; m_nPopupY = rect.y; m_nPopupTall = rect.height; int m_iBrowser = m_pParent->m_iBrowser; CHTMLProtoBufMsg cmd( eHTMLCommands_SizePopup ); cmd.Body().set_x( m_nPopupX ); cmd.Body().set_y( m_nPopupY ); cmd.Body().set_wide( m_nPopupWide ); cmd.Body().set_tall( m_nPopupTall ); DISPATCH_MESSAGE( eHTMLCommands_SizePopup ); } //----------------------------------------------------------------------------- // Purpose: cef has status text for us //----------------------------------------------------------------------------- void CClientHandler::OnStatusMessage(CefRefPtr browser, const CefString& value, StatusType type) { if ( !value.empty() ) { CHTMLProtoBufMsg cmd( eHTMLCommands_StatusText ); cmd.Body().set_text( value.c_str() ); DISPATCH_MESSAGE( eHTMLCommands_StatusText ); } } //----------------------------------------------------------------------------- // Purpose: show tooltip please //----------------------------------------------------------------------------- bool CClientHandler::OnTooltip(CefRefPtr browser, CefString& text) { if ( !m_bShowingToolTip && !text.empty() ) { m_bShowingToolTip = true; m_strToolTip = text.c_str(); CHTMLProtoBufMsg cmd( eHTMLCommands_ShowToolTip ); cmd.Body().set_text( m_strToolTip ); DISPATCH_MESSAGE( eHTMLCommands_ShowToolTip ); } else if ( m_bShowingToolTip && !text.empty() ) { if ( m_strToolTip != text.c_str() ) { m_strToolTip = text.c_str(); CHTMLProtoBufMsg cmd( eHTMLCommands_UpdateToolTip ); cmd.Body().set_text( m_strToolTip ); DISPATCH_MESSAGE( eHTMLCommands_UpdateToolTip ); } } else if ( m_bShowingToolTip ) { CHTMLProtoBufMsg cmd( eHTMLCommands_HideToolTip ); DISPATCH_MESSAGE( eHTMLCommands_HideToolTip ); m_bShowingToolTip = false; m_strToolTip.Clear(); } return true; } //----------------------------------------------------------------------------- // Purpose: set the mouse cursor to this image //----------------------------------------------------------------------------- bool CChromePainter::OnSetCursor( CefRefPtr browser, const CursorType type, const void *pchIconData, int iWide, int iTall, int xHotSpot, int yHotSpot ) { int m_iBrowser = m_pParent->m_iBrowser; CHTMLProtoBufMsg cmd( eHTMLCommands_SetCursor ); EMouseCursor cursor; switch( type ) { case TypeCustom: cursor = dc_last; break; case TypeCross: cursor = dc_crosshair; break; case TypeHand: cursor = dc_hand; break; case TypeIBeam: cursor = dc_ibeam; break; case TypeWait: cursor = dc_hourglass; break; case TypeHelp: cursor = dc_help; break; case TypeEastResize: cursor = dc_sizee; break; case TypeNorthResize: cursor = dc_sizen; break; case TypeNorthEastResize: cursor = dc_sizene; break; case TypeNorthWestResize: cursor = dc_sizenw; break; case TypeSouthResize: cursor = dc_sizes; break; case TypeSouthEastResize: cursor = dc_sizese; break; case TypeSouthWestResize: cursor = dc_sizesw; break; case TypeNorthSouthResize: cursor = dc_sizes; break; case TypeEastWestResize: cursor = dc_sizew; break; case TypeNorthEastSouthWestResize: cursor = dc_sizeall; break; case TypeColumnResize: cursor = dc_colresize; break; case TypeRowResize: cursor = dc_rowresize; break; case TypeMiddlePanning: cursor = dc_middle_pan; break; case TypeEastPanning: cursor = dc_east_pan; break; case TypeNorthPanning: cursor = dc_north_pan; break; case TypeNorthEastPanning: cursor = dc_north_east_pan; break; case TypeNorthWestPanning: cursor = dc_north_west_pan; break; case TypeSouthPanning: cursor = dc_south_pan; break; case TypeSouthEastPanning: cursor = dc_south_east_pan; break; case TypeSouthWestPanning: cursor = dc_south_west_pan; break; case TypeWestPanning: cursor = dc_west_pan; break; case TypeMove: cursor = dc_sizeall; break; case TypeVerticalText: cursor = dc_verticaltext; break; case TypeCell: cursor = dc_cell; break; case TypeContextMenu: cursor = dc_none; break; case TypeAlias: cursor = dc_alias; break; case TypeProgress: cursor = dc_waitarrow; break; case TypeNoDrop: cursor = dc_no; break; case TypeCopy: cursor = dc_copycur; break; case TypeNone: cursor = dc_none; break; case TypeNotAllowed: cursor = dc_no; break; case TypeZoomIn: cursor = dc_zoomin; break; case TypeZoomOut: cursor = dc_zoomout; break; case TypePointer: default: cursor = dc_arrow; } cmd.Body().set_cursor( cursor ); cmd.Body().set_data( (uint32)pchIconData ); // we are relying on chrome keeping around the cursor data after this call completes, it does right now. cmd.Body().set_wide( iWide ); cmd.Body().set_tall( iTall ); cmd.Body().set_xhotspot( xHotSpot ); cmd.Body().set_yhotspot( yHotSpot ); DISPATCH_MESSAGE( eHTMLCommands_SetCursor ); return true; } //----------------------------------------------------------------------------- // Purpose: file open dialog to be shown //----------------------------------------------------------------------------- bool CChromePainter::OnFileOpenDialog( CefRefPtr browser, bool bMultiSelect, const CefString &default_title, const CefString &default_file, CefWebFileChooserCallback *pCallback ) { if ( !pCallback ) return true; int m_iBrowser = m_pParent->m_iBrowser; { // scope this so this allocation doesn't stay on the stack during validate CHTMLProtoBufMsg cmd( eHTMLCommands_FileLoadDialog ); if ( !default_title.empty() ) cmd.Body().set_title( default_title ); if ( !default_file.empty() ) cmd.Body().set_initialfile( default_file ); DISPATCH_MESSAGE( eHTMLCommands_FileLoadDialog ); } HTMLCommandBuffer_t *pBuf = g_CEFThread.BWaitForCommand( eHTMLCommands_FileLoadDialogResponse, m_iBrowser ); if ( pBuf ) { CHTMLProtoBufMsg< CMsgFileLoadDialogResponse > cmd( eHTMLCommands_FileLoadDialogResponse ); if ( cmd.BDeserializeCrossProc( &pBuf->m_Buffer ) ) { std::vector files; for ( int i = 0; i < cmd.BodyConst().files_size(); i++ ) { if ( !cmd.BodyConst().files(i).empty() ) { CPathString path( cmd.BodyConst().files(i).c_str() ); files.push_back( path.GetWCharPathPrePended() ); } } // if you have a DEBUG build and are crashing here it is because // Chrome is a release library and the std::vector iterator isn't crossing // the interface happyily. Build release and you will run fine. #if defined(DEBUG) && defined(WIN32) Assert( !"File select dialog not available in debug due to STL debug/release issues\n" ); #else pCallback->OnFileChoose( files ); #endif } g_CEFThread.ReleaseCommandBuffer( pBuf ); } return true; } //----------------------------------------------------------------------------- // Purpose: CEF is asking if it can show itself fullscreen //----------------------------------------------------------------------------- bool CChromePainter::OnEnterFullScreen( CefRefPtr browser ) { int m_iBrowser = m_pParent->m_iBrowser; { // scope this so this allocation doesn't stay on the stack during validate CHTMLProtoBufMsg cmd( eHTMLCommands_RequestFullScreen ); DISPATCH_MESSAGE( eHTMLCommands_RequestFullScreen ); } HTMLCommandBuffer_t *pBuf = g_CEFThread.BWaitForCommand( eHTMLCommands_RequestFullScreenResponse, m_iBrowser ); if ( pBuf ) { CHTMLProtoBufMsg< CMsgRequestFullScreenResponse > cmd( eHTMLCommands_RequestFullScreenResponse ); if ( cmd.BDeserializeCrossProc( &pBuf->m_Buffer ) ) { return cmd.BodyConst().ballow(); } g_CEFThread.ReleaseCommandBuffer( pBuf ); } return false; } //----------------------------------------------------------------------------- // Purpose: cef is spewing to its console, print it //----------------------------------------------------------------------------- bool CChromePainter::OnExitFullScreen( CefRefPtr browser ) { int m_iBrowser = m_pParent->m_iBrowser; { // scope this so this allocation doesn't stay on the stack during validate // tell the main thread we are exiting fullscreen CHTMLProtoBufMsg cmd( eHTMLCommands_ExitFullScreen ); DISPATCH_MESSAGE( eHTMLCommands_ExitFullScreen ); } // BUGUBG - add a request/response here so you can disallow leaving fullscreen?? return true; } //----------------------------------------------------------------------------- // Purpose: cef is spewing to its console, print it //----------------------------------------------------------------------------- bool CClientHandler::OnConsoleMessage(CefRefPtr browser, const CefString& message, const CefString& source, int line) { // the console is very chatty and doesn't provide useful information for us app developers, just for html/css editors, so lets ignore this for now //Msg( "Browser Message: %s - %s:%d\n", message.c_str(), source.c_str(), line ); return true; } //----------------------------------------------------------------------------- // Purpose: helper function to recurvisely search down the DOM for any input nodes and an input button name //----------------------------------------------------------------------------- void SearchForInputButtonAndOtherInputs_R( CefRefPtr root, CefRefPtr focusNode, bool &bHasMultipleTextInputNodes, CUtlString &sSearchButtonName, int nMaxRecurse ) { CefRefPtr nodeChildren = root->GetFirstChild(); while ( nodeChildren.get() ) { if ( !nodeChildren->IsSame( focusNode ) && nodeChildren->IsElement() ) { CUtlString sElementType = nodeChildren->GetElementTagName().c_str(); if ( !Q_stricmp( "input", sElementType ) ) { CUtlString sChildControlType = nodeChildren->GetFormControlElementType().c_str(); if ( sSearchButtonName.IsEmpty() && !Q_stricmp( sChildControlType, "submit" ) ) { if ( nodeChildren->HasElementAttribute( "value" ) ) sSearchButtonName = nodeChildren->GetElementAttribute( "value" ).c_str(); } else if ( !Q_stricmp( "text", sChildControlType ) || !Q_stricmp( "password", sChildControlType ) || !Q_stricmp( "email", sChildControlType ) ) { //CefDOMNode::AttributeMap attrMap; //nodeChildren->GetElementAttributes( attrMap ); if ( !nodeChildren->HasElementAttribute( "disabled" ) ) bHasMultipleTextInputNodes = true; } } else if ( !Q_stricmp( "textarea", sElementType ) ) { if ( !nodeChildren->HasElementAttribute( "disabled" ) ) bHasMultipleTextInputNodes = true; } else if ( nMaxRecurse > 0 /*&& !Q_stricmp( "div", sElementType ) || !Q_stricmp( "tr", sElementType ) || !Q_stricmp( "td", sElementType ) || !Q_stricmp( "table", sElementType ) || !Q_stricmp( "tbody", sElementType )*/ ) { SearchForInputButtonAndOtherInputs_R( nodeChildren, focusNode, bHasMultipleTextInputNodes, sSearchButtonName, nMaxRecurse - 1 ); } } nodeChildren = nodeChildren->GetNextSibling(); if ( bHasMultipleTextInputNodes && sSearchButtonName.IsValid() ) break; // if we found both multiple nodes and a search button name we can bail early } } //----------------------------------------------------------------------------- // Purpose: a new node in the DOM has focus now //----------------------------------------------------------------------------- void CClientHandler::OnFocusedNodeChanged(CefRefPtr browser, CefRefPtr frame, CefRefPtr node) { VPROF_BUDGET( "CCEFThread - CClientHandler::OnFocusedNodeChanged()", VPROF_BUDGETGROUP_VGUI ); bool bIsInputNode = false; CUtlString sElementType; if ( node.get() ) sElementType = node->GetElementTagName().c_str(); CUtlString sName; if ( node.get() ) sName = node->GetName().c_str(); CUtlString sSearchButtonName; CUtlString sControlType; bool bInputNode = !Q_stricmp( "input", sElementType ); bool bHasMultipleInputNodes = false; if ( sElementType.IsValid() && ( bInputNode || !Q_stricmp( "textarea", sElementType ) ) ) { sControlType = node->GetFormControlElementType().c_str(); // lets go searching for the submit button and grab the text it shows bIsInputNode = true; CefRefPtr nodeParent = node->GetParent(); while( nodeParent.get() ) { CUtlString sParentElementType = nodeParent->GetElementTagName().c_str(); if ( !Q_stricmp( "form", sParentElementType ) ) break; nodeParent = nodeParent->GetParent(); } if ( nodeParent.get() ) SearchForInputButtonAndOtherInputs_R( nodeParent, node, bHasMultipleInputNodes, sSearchButtonName, 32 ); } if ( sElementType.IsValid() && !Q_stricmp( "div", sElementType ) ) { if ( node->HasElementAttribute( "contenteditable" ) ) bIsInputNode = true; } if ( sSearchButtonName.IsEmpty() ) sSearchButtonName = "#Web_FormSubmit"; CHTMLProtoBufMsg cmd( eHTMLCommands_NodeGotFocus ); cmd.Body().set_binput( bIsInputNode ); if ( sName.IsValid() ) cmd.Body().set_name( sName ); if ( sElementType.IsValid() ) cmd.Body().set_elementtagname( sElementType ); if ( sSearchButtonName.IsValid() ) cmd.Body().set_searchbuttontext( sSearchButtonName ); cmd.Body().set_bhasmultipleinputs( bHasMultipleInputNodes ); if ( sControlType.IsValid() ) cmd.Body().set_input_type( sControlType ); DISPATCH_MESSAGE( eHTMLCommands_NodeGotFocus ); } //----------------------------------------------------------------------------- // Purpose: a find has found matches on the page, feed back that info //----------------------------------------------------------------------------- void CClientHandler::OnFindResult(CefRefPtr browser, int identifier, int count, const CefRect& selectionRect, int activeMatchOrdinal, bool finalUpdate) { CHTMLProtoBufMsg cmd( eHTMLCommands_SearchResults ); cmd.Body().set_activematch( activeMatchOrdinal ); cmd.Body().set_results( count ); DISPATCH_MESSAGE( eHTMLCommands_SearchResults ); } //----------------------------------------------------------------------------- // Purpose: return a pointer to the CEF browser object //----------------------------------------------------------------------------- CefRefPtr CClientHandler::GetBrowser() { return m_Browser; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CClientHandler::CloseBrowser() { if ( m_Browser ) { m_bBrowserClosing = true; m_Browser->CloseBrowser(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CClientHandler::SetMouseLocation( int nMouseX, int nMouseY ) { m_nMouseX = nMouseX; m_nMouseY = nMouseY; if ( m_Browser.get() ) { m_nMouseScrolledX = nMouseX + m_Browser->VerticalScroll(); m_nMouseScrolledY = nMouseY + m_Browser->HorizontalScroll(); m_bMouseFocus = true; m_Browser->SendMouseMoveEvent( m_nMouseX, m_nMouseY, false ); } } //----------------------------------------------------------------------------- // Purpose: check if window has scrolled and generate a fake mouse move event // to force CEF to check for hover state changes (seems like a CEF bug...) //----------------------------------------------------------------------------- void CClientHandler::RefreshCEFHoverStatesAfterScroll() { if ( m_Browser.get() && m_bMouseFocus ) { int nScrolledX = m_nMouseX + m_Browser->VerticalScroll(); int nScrolledY = m_nMouseY + m_Browser->HorizontalScroll(); if ( nScrolledX != m_nMouseScrolledX || nScrolledY != m_nMouseScrolledY ) { m_nMouseScrolledX = nScrolledX; m_nMouseScrolledY = nScrolledY; m_Browser->SendMouseMoveEvent( m_nMouseX, m_nMouseY, false ); } } } //----------------------------------------------------------------------------- // Purpose: make the user agent for this browser //----------------------------------------------------------------------------- void CClientHandler::SetBrowserAgent( CefRefPtr browser ) { static bool bGotIEVersion = false; static char rgchWindowsVersion[64] = "Windows NT 5.1"; // XP SP1 static char *rgchOS = ""; if ( !bGotIEVersion ) { #ifdef WIN32 rgchOS = "Windows"; // First get windows version OSVERSIONINFO verInfo; memset( &verInfo, 0, sizeof(OSVERSIONINFO) ); verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if ( ::GetVersionEx( &verInfo ) ) { // We only run on "Windows NT" os's, so we just need to append the major/minor version dynamically Q_snprintf( rgchWindowsVersion, sizeof(rgchWindowsVersion), "Windows NT %u.%u", verInfo.dwMajorVersion, verInfo.dwMinorVersion ); //Log( "Windows Version is: %u.%u\n", verInfo.dwMajorVersion, verInfo.dwMinorVersion ); } #elif defined(OSX) Q_snprintf( rgchWindowsVersion, sizeof(rgchWindowsVersion), "Macintosh" ); rgchOS = "Macintosh"; #elif defined(LINUX) Q_snprintf( rgchWindowsVersion, sizeof(rgchWindowsVersion), "X11" );// strange, but that's what Firefox uses rgchOS = "Linux"; #endif } // user agent is process wide for Chrome so you can only set it once and it appplies to everything you open { char szAgent[ 2048 ]; Q_snprintf( szAgent, sizeof(szAgent), m_strUserAgent.String(), rgchOS, rgchWindowsVersion, m_strUserAgentIdentifier.String(), sm_ulBuildID, m_szUserAgentExtras ); browser->SetUserAgent( szAgent ); } } //----------------------------------------------------------------------------- // Purpose: paint the cef control //----------------------------------------------------------------------------- void CClientHandler::Paint() { Lock(); if ( m_Browser ) { m_bPendingPaint |= m_Painter.BUpdated(); m_Painter.SetUpdated( false ); } Unlock(); } //----------------------------------------------------------------------------- // Purpose: did we have a paint that updated the texture buffer? //----------------------------------------------------------------------------- bool CClientHandler::BNeedsPaint() { bool bVal = m_bPendingPaint; m_bPendingPaint = false; return bVal; } //----------------------------------------------------------------------------- // Purpose: get the texture data for the control //----------------------------------------------------------------------------- const byte *CClientHandler::PComposedTextureData( uint32 iTexture ) { VPROF_BUDGET( "CClientHandler::PTextureData", VPROF_BUDGETGROUP_VGUI ); return m_Painter.PComposedTextureData( iTexture ); } //----------------------------------------------------------------------------- // Purpose: true if something valid has rendered (i.e not the blank page) //----------------------------------------------------------------------------- bool CClientHandler::IsVisuallyNonEmpty() { if ( m_Browser ) return m_Browser->IsVisuallyNonEmpty(); return false; } //----------------------------------------------------------------------------- // Purpose: set the loc strings to display on error //----------------------------------------------------------------------------- void CClientHandler::SetErrorStrings( const char *pchTitle, const char *pchHeader, const char *pchCacheMiss, const char *pchBadURL, const char *pchConnectionProblem, const char *pchProxyProblem, const char *pchUnknown ) { m_sErrorTitle = pchTitle; m_sErrorHeader = pchHeader; m_sErrorCacheMiss = pchCacheMiss; m_sErrorBadURL = pchBadURL; m_sErrorConnectionProblem = pchConnectionProblem; m_sErrorProxyProblem = pchProxyProblem; m_sErrorUnknown = pchUnknown; } //----------------------------------------------------------------------------- // Purpose: the user wants us to take a screenshot of a page //----------------------------------------------------------------------------- void CClientHandler::RequestScreenShot( const CHTMLProtoBufMsg &cmd ) { m_Snapshot.m_sURLSnapshot = cmd.BodyConst().url().c_str(); m_Snapshot.m_sFileNameSnapshot = cmd.BodyConst().filename().c_str(); m_Snapshot.m_nWide = cmd.BodyConst().width(); m_Snapshot.m_nTall = cmd.BodyConst().height(); m_Snapshot.m_flRequestTimeout = Plat_FloatTime() + k_flMaxScreenshotWaitTime; } //----------------------------------------------------------------------------- // Purpose: save a screenshot of the current html page to this file with this size //----------------------------------------------------------------------------- void CClientHandler::SavePageToJPEGIfNeeded( CefRefPtr browser, const byte *pRGBA, int wide, int tall ) { if ( m_Snapshot.m_sURLSnapshot.IsValid() && wide && tall && m_Snapshot.m_sURLSnapshot == CStrAutoEncode( browser->GetMainFrame()->GetURL().c_str() ).ToString() ) { VPROF_BUDGET( "CClientHandler::SavePageToJPEGIfNeeded", VPROF_BUDGETGROUP_TENFOOT ); CUtlBuffer bufRGB; bufRGB.Put( pRGBA, wide * tall *4 ); if ( !BConvertRGBAToRGB( bufRGB, wide, tall ) ) return; BResizeImageRGB( bufRGB, wide, tall, m_Snapshot.m_nWide, m_Snapshot.m_nTall ); // input format is actually BGRA so now swizzle to rgb byte *pBGR = (byte *)bufRGB.Base(); for ( int i = 0; i < m_Snapshot.m_nTall; i++ ) { for ( int j = 0; j < m_Snapshot.m_nWide; j++ ) { char cR = pBGR[0]; pBGR[0] = pBGR[2]; pBGR[2] = cR; pBGR += 3; } } if ( !ConvertRGBToJpeg( m_Snapshot.m_sFileNameSnapshot, k_ScreenshotQuality, m_Snapshot.m_nWide, m_Snapshot.m_nTall, bufRGB ) ) return; CHTMLProtoBufMsg cmd( eHTMLCommands_SavePageToJPEGResponse ); cmd.Body().set_url( m_Snapshot.m_sURLSnapshot ); cmd.Body().set_filename( m_Snapshot.m_sFileNameSnapshot ); DISPATCH_MESSAGE( eHTMLCommands_SavePageToJPEGResponse ); m_Snapshot.m_sURLSnapshot.Clear(); m_Snapshot.m_flRequestTimeout = 0.0f; } } //----------------------------------------------------------------------------- // Purpose: return the url located at this position if there is one //----------------------------------------------------------------------------- void CCEFThread::ThreadLinkAtPosition( const CHTMLProtoBufMsg &htmlCommand ) { CefString pchURL; int iClient = 0; int bLiveLink = false; int bInput = false; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { if ( m_listClientHandlers[iClient]->GetBrowser() ) pchURL = m_listClientHandlers[iClient]->GetBrowser()->GetLinkAtPosition( htmlCommand.BodyConst().x(), htmlCommand.BodyConst().y(), bLiveLink, bInput ); } CHTMLProtoBufMsg cmd( eHTMLCommands_LinkAtPositionResponse ); cmd.Body().set_x( htmlCommand.BodyConst().x() ); cmd.Body().set_y( htmlCommand.BodyConst().y() ); cmd.Body().set_blivelink( bLiveLink>0 ? true: false ); cmd.Body().set_binput( bInput>0 ? true: false ); if ( !pchURL.empty() ) cmd.Body().set_url( pchURL ); int m_iBrowser = htmlCommand.BodyConst().browser_handle(); DISPATCH_MESSAGE( eHTMLCommands_LinkAtPositionResponse ); } //----------------------------------------------------------------------------- // Purpose: zoom the screen to the element at this position //----------------------------------------------------------------------------- void CCEFThread::ThreadZoomToElementAtPosition( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { if ( m_listClientHandlers[iClient]->GetBrowser() ) { CefRect initialRect, finalRect; float zoomLevel = m_listClientHandlers[iClient]->GetBrowser()->scalePageToFitElementAt( htmlCommand.BodyConst().x(), htmlCommand.BodyConst().y(), initialRect, finalRect ); int m_iBrowser = htmlCommand.BodyConst().browser_handle(); ThreadBrowserVerticalScrollBarSizeHelper( m_iBrowser, true ); ThreadBrowserHorizontalScrollBarSizeHelper( m_iBrowser, true ); { CHTMLProtoBufMsg cmd( eHTMLCommands_ZoomToElementAtPositionResponse ); cmd.Body().set_zoom( zoomLevel ); cmd.Body().set_initial_x( initialRect.x ); cmd.Body().set_initial_y( initialRect.y ); cmd.Body().set_initial_width( initialRect.width ); cmd.Body().set_initial_height( initialRect.height ); cmd.Body().set_final_x( finalRect.x ); cmd.Body().set_final_y( finalRect.y ); cmd.Body().set_final_width( finalRect.width ); cmd.Body().set_final_height( finalRect.height ); DISPATCH_MESSAGE( eHTMLCommands_ZoomToElementAtPositionResponse ); } } } } //----------------------------------------------------------------------------- // Purpose: zoom the screen to the element at this position //----------------------------------------------------------------------------- void CCEFThread::ThreadZoomToFocusedElement( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { if ( m_listClientHandlers[iClient]->GetBrowser() ) { CefRect initialRect, finalRect; float zoomLevel = m_listClientHandlers[iClient]->GetBrowser()->scalePageToFocusedElement( htmlCommand.BodyConst().leftoffset(), htmlCommand.BodyConst().topoffset(), initialRect, finalRect ); int m_iBrowser = htmlCommand.BodyConst().browser_handle(); ThreadBrowserVerticalScrollBarSizeHelper( m_iBrowser, true ); ThreadBrowserHorizontalScrollBarSizeHelper( m_iBrowser, true ); { CHTMLProtoBufMsg cmd( eHTMLCommands_ZoomToElementAtPositionResponse ); cmd.Body().set_zoom( zoomLevel ); cmd.Body().set_initial_x( initialRect.x ); cmd.Body().set_initial_y( initialRect.y ); cmd.Body().set_initial_width( initialRect.width ); cmd.Body().set_initial_height( initialRect.height ); cmd.Body().set_final_x( finalRect.x ); cmd.Body().set_final_y( finalRect.y ); cmd.Body().set_final_width( finalRect.width ); cmd.Body().set_final_height( finalRect.height ); DISPATCH_MESSAGE( eHTMLCommands_ZoomToElementAtPositionResponse ); } } } } //----------------------------------------------------------------------------- // Purpose: increment the scale factor on the page by an increment //----------------------------------------------------------------------------- void CCEFThread::ThreadSetPageScale( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CClientHandler *pHandler = m_listClientHandlers[iClient]; if ( pHandler->GetBrowser() ) { int nPageHeightBefore = pHandler->GetBrowser()->VerticalScrollMax(); int nPageWidthBefore = pHandler->GetBrowser()->HorizontalScrollMax(); float zoomLevel = pHandler->GetBrowser()->setPageScaleFactor( htmlCommand.BodyConst().scale(), htmlCommand.BodyConst().x(), htmlCommand.BodyConst().y() ); int idx = m_mapSizeChangesPending.Find( htmlCommand.BodyConst().browser_handle() ); if ( idx == m_mapSizeChangesPending.InvalidIndex() ) { SizeChange_t &sizeChange = m_mapSizeChangesPending [ m_mapSizeChangesPending.Insert( htmlCommand.BodyConst().browser_handle() ) ]; sizeChange.iBrowser = htmlCommand.BodyConst().browser_handle(); sizeChange.nBeforeHeight = nPageHeightBefore; sizeChange.nBeforeWidth = nPageWidthBefore; sizeChange.flNewZoom = zoomLevel; } } } } //----------------------------------------------------------------------------- // Purpose: fire off the details of any page scale changes we had pending from last frame //----------------------------------------------------------------------------- void CCEFThread::SendSizeChangeEvents() { FOR_EACH_MAP_FAST( m_mapSizeChangesPending, i ) { int iClient = 0; if ( BIsValidBrowserHandle( m_mapSizeChangesPending[i].iBrowser, iClient ) ) { CClientHandler *pHandler = m_listClientHandlers[iClient]; if ( pHandler->GetBrowser() ) { int nPageHeightAfter = pHandler->GetBrowser()->VerticalScrollMax(); int nPageWidthAfter = pHandler->GetBrowser()->HorizontalScrollMax(); int m_iBrowser = m_mapSizeChangesPending[i].iBrowser; ThreadBrowserVerticalScrollBarSizeHelper( m_iBrowser, true ); ThreadBrowserHorizontalScrollBarSizeHelper( m_iBrowser, true ); { CHTMLProtoBufMsg cmd( eHTMLCommands_ScaleToValueResponse ); cmd.Body().set_zoom( m_mapSizeChangesPending[i].flNewZoom ); cmd.Body().set_width_delta( nPageWidthAfter - m_mapSizeChangesPending[i].nBeforeWidth ); cmd.Body().set_height_delta( nPageHeightAfter - m_mapSizeChangesPending[i].nBeforeHeight ); DISPATCH_MESSAGE( eHTMLCommands_ScaleToValueResponse ); } } } } m_mapSizeChangesPending.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: exit from fullscreen if in it //----------------------------------------------------------------------------- void CCEFThread::ThreadExitFullScreen( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { m_listClientHandlers[ iClient ]->GetBrowser()->ExitFullScreen(); } } //----------------------------------------------------------------------------- // Purpose: the user has requested we save this url to a file on local disk //----------------------------------------------------------------------------- void CCEFThread::ThreadSavePageToJPEG( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { m_listClientHandlers[ iClient ]->RequestScreenShot( htmlCommand ); } } //----------------------------------------------------------------------------- // Purpose: mouse moved to this x,y on the page //----------------------------------------------------------------------------- void CCEFThread::ThreadMouseMove( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { m_listClientHandlers[ iClient ]->SetMouseLocation( htmlCommand.BodyConst().x(), htmlCommand.BodyConst().y() ); } } //----------------------------------------------------------------------------- // Purpose: mouse left the control, tell cef //----------------------------------------------------------------------------- void CCEFThread::ThreadMouseLeave( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; m_listClientHandlers[ iClient ]->SetMouseFocus( false ); int mx, my; m_listClientHandlers[ iClient ]->GetMouseLocation( mx, my ); browser->SendMouseMoveEvent( mx, my, true ); } } //----------------------------------------------------------------------------- // Purpose: helper to convert UI mouse codes to CEF ones //----------------------------------------------------------------------------- CefBrowser::MouseButtonType ConvertMouseCodeToCEFCode( int code ) { // BUGBUG switch( code ) { case 0: return MBT_LEFT; break; case 1: return MBT_RIGHT; break; case 2: return MBT_MIDDLE; break; default: return MBT_LEFT; break; } } //----------------------------------------------------------------------------- // Purpose: mouse button pressed //----------------------------------------------------------------------------- void CCEFThread::ThreadMouseButtonDown( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; int nMouseX, nMouseY; m_listClientHandlers[ iClient ]->GetMouseLocation( nMouseX, nMouseY ); browser->SendMouseClickEvent( nMouseX, nMouseY, ConvertMouseCodeToCEFCode( htmlCommand.BodyConst().mouse_button() ), false, 1 ); } } //----------------------------------------------------------------------------- // Purpose: mouse button released //----------------------------------------------------------------------------- void CCEFThread::ThreadMouseButtonUp( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; int nMouseX, nMouseY; m_listClientHandlers[ iClient ]->GetMouseLocation( nMouseX, nMouseY ); browser->SendMouseClickEvent( nMouseX, nMouseY, ConvertMouseCodeToCEFCode( htmlCommand.BodyConst().mouse_button() ), true, 1 ); } } //----------------------------------------------------------------------------- // Purpose: mouse button double pressed //----------------------------------------------------------------------------- void CCEFThread::ThreadMouseButtonDlbClick( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; int nMouseX, nMouseY; m_listClientHandlers[ iClient ]->GetMouseLocation( nMouseX, nMouseY ); browser->SendMouseClickEvent( nMouseX, nMouseY, ConvertMouseCodeToCEFCode( htmlCommand.BodyConst().mouse_button() ), false, 2 ); } } //----------------------------------------------------------------------------- // Purpose: mouse was wheeled //----------------------------------------------------------------------------- void CCEFThread::ThreadMouseWheel( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; int nMouseX, nMouseY; m_listClientHandlers[ iClient ]->GetMouseLocation( nMouseX, nMouseY ); browser->SendMouseWheelEvent( nMouseX, nMouseY, htmlCommand.BodyConst().delta() ); } } //----------------------------------------------------------------------------- // Purpose: unicode character was typed //----------------------------------------------------------------------------- void CCEFThread::ThreadKeyTyped( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; browser->SendKeyEvent( KT_CHAR, htmlCommand.BodyConst().unichar(), 0, false, false ); } } //----------------------------------------------------------------------------- // Purpose: raw key was pressed //----------------------------------------------------------------------------- void CCEFThread::ThreadKeyDown( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; browser->SendKeyEvent( KT_KEYDOWN, htmlCommand.BodyConst().keycode(), htmlCommand.BodyConst().modifiers(), false, false ); } } //----------------------------------------------------------------------------- // Purpose: raw key was released //----------------------------------------------------------------------------- void CCEFThread::ThreadKeyUp( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; browser->SendKeyEvent( KT_KEYUP, htmlCommand.BodyConst().keycode(), htmlCommand.BodyConst().modifiers(), false, false ); } } //----------------------------------------------------------------------------- // Purpose: please close any fullscreen flash controls you see //----------------------------------------------------------------------------- void CCEFThread::ThreadCloseFullScreenFlashIfOpen( const CHTMLProtoBufMsg &htmlCommand ) { CheckForFullScreenFlashControl(); if ( m_bFullScreenFlashVisible ) { #ifdef WIN32 ::PostMessageA( m_flashfullscreenHWND, WM_KEYDOWN, VK_ESCAPE, 0 ); ::PostMessageA( m_flashfullscreenHWND, WM_KEYUP, VK_ESCAPE, 0 ); #endif } } //----------------------------------------------------------------------------- // Purpose: please close any fullscreen flash controls you see //----------------------------------------------------------------------------- void CCEFThread::ThreadPauseFullScreenFlashMovieIfOpen( const CHTMLProtoBufMsg &htmlCommand ) { CheckForFullScreenFlashControl(); if ( m_bFullScreenFlashVisible ) { #ifdef WIN32 ::PostMessageA( m_flashfullscreenHWND, WM_KEYDOWN, VK_SPACE, 0 ); ::PostMessageA( m_flashfullscreenHWND, WM_KEYUP, VK_SPACE, 0 ); #endif } } //----------------------------------------------------------------------------- // Purpose: helper class to get the focused node in the dom //----------------------------------------------------------------------------- class CVisitor : public CefDOMVisitor { public: CVisitor( CThreadEvent *pEvent, CUtlString *psValue ) { m_pEvent = pEvent; m_psValue = psValue; } ~CVisitor() { m_pEvent->Set(); } virtual void Visit(CefRefPtr document) { CefRefPtr focusedNode = document->GetFocusedNode(); *m_psValue = focusedNode->GetValue().c_str(); } private: CThreadEvent *m_pEvent; CUtlString *m_psValue; IMPLEMENT_REFCOUNTING(CVisitor); }; //----------------------------------------------------------------------------- // Purpose: get the text out of the current dom field that has focus //----------------------------------------------------------------------------- void CCEFThread::ThreadGetFocusedNodeText( const CHTMLProtoBufMsg &htmlCommand ) { int iClient = 0; if ( BIsValidBrowserHandle( htmlCommand.BodyConst().browser_handle(), iClient ) ) { CefRefPtr browser = m_listClientHandlers[ iClient ]->GetBrowser(); if ( !browser.get() ) return; CThreadEvent event; CUtlString sValue; browser->GetFocusedFrame()->VisitDOM( new CVisitor( &event, &sValue ) ); do { CefDoMessageLoopWork(); } while ( !event.Wait( 100 ) ); // keep pumping CEF until it has done our walk { int m_iBrowser = htmlCommand.BodyConst().browser_handle(); CHTMLProtoBufMsg cmd( eHTMLCommands_GetFocusedNodeValueResponse ); cmd.Body().set_value( sValue ); DISPATCH_MESSAGE( eHTMLCommands_GetFocusedNodeValueResponse ); } } }