| Index: src/plugin/WebBrowserEventsListener.cpp | 
| diff --git a/src/plugin/WebBrowserEventsListener.cpp b/src/plugin/WebBrowserEventsListener.cpp | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..e856991006b03ed51e547ed2f593a5b1b74ae595 | 
| --- /dev/null | 
| +++ b/src/plugin/WebBrowserEventsListener.cpp | 
| @@ -0,0 +1,117 @@ | 
| +#include "PluginStdAfx.h" | 
| +#include "WebBrowserEventsListener.h" | 
| +#include "PluginClientBase.h" // for UnescapeUrl | 
| + | 
| +WebBrowserEventsListener::WebBrowserEventsListener() | 
| +  : m_isDocumentEvents2Connected(false) | 
| +  , m_state(State::FirstTimeLoading) | 
| +{ | 
| +} | 
| + | 
| +WebBrowserEventsListener::~WebBrowserEventsListener() | 
| +{ | 
| +  if (!!m_onDestroy) | 
| +  { | 
| +    m_onDestroy(); | 
| +  } | 
| +} | 
| + | 
| +HRESULT STDMETHODCALLTYPE WebBrowserEventsListener::OnDocumentComplete(IDispatch* dispFrameBrowser, VARIANT* /*variantUrl*/) | 
| +{ | 
| +  if (!dispFrameBrowser) | 
| +  { | 
| +    return E_POINTER; | 
| +  } | 
| + | 
| +  // if it's a signal from another browser (sub-frame for-example) then ignore it. | 
| +  if (!m_browser.IsEqualObject(dispFrameBrowser)) | 
| +  { | 
| +    return S_OK; | 
| +  } | 
| + | 
| +  if (!m_isDocumentEvents2Connected) | 
| +  { | 
| +    ATL::CComPtr<IDispatch> dispDocument; | 
| +    ATL::CComQIPtr<IHTMLDocument2> htmlDocument2; | 
| +    bool isHtmlDocument2 = SUCCEEDED(m_browser->get_Document(&dispDocument)) && (htmlDocument2 = dispDocument); | 
| +    isHtmlDocument2 && (m_isDocumentEvents2Connected = SUCCEEDED(HTMLDocumentEvents2Listener::DispEventAdvise(htmlDocument2))); | 
| +  } | 
| + | 
| +  if (m_state == State::FirstTimeLoading) | 
| +  { | 
| +    m_state = State::Loaded; | 
| +    emitReloaded(); | 
| +  } | 
| +  return S_OK; | 
| +} | 
| + | 
| +void STDMETHODCALLTYPE WebBrowserEventsListener::OnReadyStateChange(IHTMLEventObj* /*pEvtObj*/) | 
| +{ | 
| +  auto documentReadyState = [this]()->std::wstring | 
| +  { | 
| +    std::wstring notAvailableReadyState; | 
| +    ATL::CComPtr<IDispatch> pDocDispatch; | 
| +    m_browser->get_Document(&pDocDispatch); | 
| +    ATL::CComQIPtr<IHTMLDocument2> htmlDocument2 = pDocDispatch; | 
| +    if (!htmlDocument2) | 
| +    { | 
| +      assert(false && "htmlDocument2 in OnReadyStateChange should not be nullptr"); | 
| +      return notAvailableReadyState; | 
| +    } | 
| +    ATL::CComBSTR readyState; | 
| +    if (FAILED(htmlDocument2->get_readyState(&readyState)) || !readyState) | 
| +    { | 
| +      assert(false && "cannot obtain document readyState in OnReadyStateChange"); | 
| +      return notAvailableReadyState; | 
| +    } | 
| +    return std::wstring(readyState, readyState.Length()); | 
| +  }(); | 
| +  if (documentReadyState == L"loading") | 
| +  { | 
| +    m_state = State::Loading; | 
| +  } | 
| +  else if (documentReadyState == L"interactive") | 
| +  { | 
| +  } | 
| +  else if (documentReadyState == L"complete") | 
| +  { | 
| +    if (m_state == State::Loading) | 
| +    { | 
| +      m_state = State::Loaded; | 
| +      emitReloaded(); | 
| +    } | 
| +    else | 
| +    { | 
| +      assert(false); | 
| +    } | 
| +  } | 
| +  else if (documentReadyState == L"uninitialized") | 
| +  { | 
| +  } | 
| +  else | 
| +  { | 
| +    assert(false); | 
| +  } | 
| +} | 
| + | 
| +HRESULT WebBrowserEventsListener::Init(IWebBrowser2* webBrowser, const OnDestroy& onDestroy) | 
| +{ | 
| +  if (!(m_browser = webBrowser)) | 
| +  { | 
| +    return E_POINTER; | 
| +  } | 
| +  m_onDestroy = onDestroy; | 
| +  if (FAILED(WebBrowserEvents2Listener::DispEventAdvise(m_browser, &DIID_DWebBrowserEvents2))) | 
| +  { | 
| +    return E_FAIL; | 
| +  } | 
| +  return S_OK; | 
| +} | 
| + | 
| +void WebBrowserEventsListener::emitReloaded() | 
| +{ | 
| +  if (reloaded) | 
| +  { | 
| +    reloaded(); | 
| +  } | 
| +} | 
|  |