| /******************************************************************************* |
| * Copyright (c) 2012 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| |
| #include "stdafx.h" |
| #include "IECrossfireBHO.h" |
| |
| /* initialize constants */ |
| const UINT IECrossfireBHO::ServerStateChangeMsg = RegisterWindowMessage(L"IECrossfireServerStateChanged"); |
| const wchar_t* IECrossfireBHO::ServerWindowClass = L"_IECrossfireServer"; |
| const wchar_t* IECrossfireBHO::WindowClass = L"_IECrossfireBHOMessageWindow"; |
| |
| const wchar_t* IECrossfireBHO::ABOUT_BLANK = L"about:blank"; |
| const wchar_t* IECrossfireBHO::DEBUG_START = L"-crossfire-server-port"; |
| |
| |
| IECrossfireBHO::IECrossfireBHO() { |
| m_contextCreated = false; |
| m_eventsHooked = false; |
| m_firstNavigate = true; |
| m_htmlToDisplay = NULL; |
| m_isCurrentContext = false; |
| m_lastUrl = NULL; |
| m_server = NULL; |
| m_serverState = STATE_DISCONNECTED; |
| m_webBrowser = NULL; |
| |
| /* create a message-only window to receive server state change notifications */ |
| HINSTANCE module = GetModuleHandle(NULL); |
| WNDCLASS ex; |
| ex.style = 0; |
| ex.lpfnWndProc = WndProc; |
| ex.cbClsExtra = 0; |
| ex.cbWndExtra = 0; |
| ex.hInstance = module; |
| ex.hIcon = NULL; |
| ex.hCursor = NULL; |
| ex.hbrBackground = NULL; |
| ex.lpszMenuName = NULL; |
| ex.lpszClassName = WindowClass; |
| RegisterClass(&ex); |
| m_messageWindow = CreateWindow(WindowClass, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, module, NULL); |
| if (!m_messageWindow) { |
| Logger::error("IECrossfireBHO ctor(): failed to create message-only window", GetLastError()); |
| } else { |
| SetWindowLongPtr(m_messageWindow, GWL_USERDATA, (__int3264)(LONG_PTR)this); |
| } |
| } |
| |
| IECrossfireBHO::~IECrossfireBHO() { |
| DWORD processId = GetCurrentProcessId(); |
| if (m_server) { |
| m_server->contextDestroyed(processId); |
| m_server->removeBrowser(processId); |
| m_server->Release(); |
| } |
| if (m_htmlToDisplay) { |
| free(m_htmlToDisplay); |
| } |
| if (m_lastUrl) { |
| free(m_lastUrl); |
| } |
| if (m_messageWindow) { |
| DestroyWindow(m_messageWindow); |
| UnregisterClass(WindowClass, GetModuleHandle(NULL)); |
| } |
| } |
| |
| /* DWebBrowserEvents2 */ |
| |
| void STDMETHODCALLTYPE IECrossfireBHO::OnBeforeNavigate2(IDispatch* pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, VARIANT_BOOL* Cancel) { |
| m_contextCreated = false; |
| if (!m_firstNavigate) { |
| return; |
| } |
| |
| m_firstNavigate = false; |
| std::wstring string(URL->bstrVal); |
| size_t index = string.find(DEBUG_START); |
| if (index == std::string::npos) { |
| return; |
| } |
| |
| *Cancel = VARIANT_TRUE; |
| |
| size_t equalsIndex; |
| if ((equalsIndex = string.find('=', index)) != index + wcslen(DEBUG_START)) { |
| if ((equalsIndex = string.find(L"%3D", index)) != index + wcslen(DEBUG_START)) { |
| displayHTML(L"<html><body>Did not start Crossfire Server<p>Command-line syntax: <tt>iexplore.exe -crossfire-server-port=<port></tt></body></html>"); |
| return; |
| } |
| equalsIndex += 2; /* because found "%3D" instead of "=" */ |
| } |
| |
| index = equalsIndex + 1; |
| int port = _wtoi(string.substr(index, 5).c_str()); |
| |
| if (1000 <= port && port <= 65534) { |
| if (startDebugging(port)) { |
| std::wstringstream stream; |
| stream << "<html><body>Crossfire Server started on port "; |
| stream << port; |
| stream << "</body></html>"; |
| displayHTML((wchar_t*)stream.str().c_str()); |
| } else { |
| std::wstringstream stream; |
| stream << "<html><body>Failed to start Crossfire Server on port "; |
| stream << port; |
| stream << "</body></html>"; |
| displayHTML((wchar_t*)stream.str().c_str()); |
| } |
| } else { |
| displayHTML(L"<html><body>Did not start Crossfire Server<p>Valid port range is 1000-65534</body></html>"); |
| } |
| } |
| |
| void STDMETHODCALLTYPE IECrossfireBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { |
| BSTR bstr = pvarURL->bstrVal; |
| if (bstr && m_htmlToDisplay && wcscmp(bstr, ABOUT_BLANK) == 0) { |
| CComPtr<IUnknown> webBrowserIUnknown = NULL; |
| HRESULT hr = m_webBrowser->QueryInterface(IID_IUnknown, (void**)&webBrowserIUnknown); |
| if (SUCCEEDED(hr)) { |
| CComPtr<IUnknown> pDispIUnknown = NULL; |
| hr = pDisp->QueryInterface(IID_IUnknown, (void**)&pDispIUnknown); |
| if (SUCCEEDED(hr)) { |
| if (webBrowserIUnknown == pDispIUnknown) { |
| /* this is the top-level page frame */ |
| size_t length = (wcslen(m_htmlToDisplay) + 1) * 2; |
| HGLOBAL buffer = GlobalAlloc(GPTR, length); |
| if (buffer) { |
| wcscpy_s((wchar_t*)buffer, length, m_htmlToDisplay); |
| CComPtr<IStream> stream = NULL; |
| HRESULT hr = CreateStreamOnHGlobal(buffer, false, &stream); |
| if (SUCCEEDED(hr)) { |
| CComPtr<IDispatch> document = NULL; |
| HRESULT hr = m_webBrowser->get_Document(&document); |
| if (SUCCEEDED(hr)) { |
| CComPtr<IPersistStreamInit> persistStreamInit = NULL; |
| hr = document->QueryInterface(IID_IPersistStreamInit, (void**)&persistStreamInit); |
| if (SUCCEEDED(hr)) { |
| hr = persistStreamInit->InitNew(); |
| if (SUCCEEDED(hr)) { |
| hr = persistStreamInit->Load(stream); |
| } |
| } |
| } |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnDocumentComplete(): failed setting page content", hr); |
| } |
| } else { |
| Logger::error("IECrossfireBHO.OnDocumentComplete(): CreateStreamOnHGlobal() failed", hr); |
| } |
| GlobalFree(buffer); |
| } |
| free(m_htmlToDisplay); |
| m_htmlToDisplay = NULL; |
| } |
| } |
| } |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnDocumentComplete() failed", hr); |
| } |
| return; |
| } |
| |
| if (m_serverState != STATE_CONNECTED) { |
| return; |
| } |
| |
| CComPtr<IUnknown> webBrowserIUnknown = NULL; |
| HRESULT hr = m_webBrowser->QueryInterface(IID_IUnknown, (void**)&webBrowserIUnknown); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnDocumentComplete(): QI(IUnknown)[1] failed", hr); |
| } else { |
| CComPtr<IUnknown> pDispIUnknown = NULL; |
| hr = pDisp->QueryInterface(IID_IUnknown, (void**)&pDispIUnknown); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnDocumentComplete(): QI(IUnknown)[2] failed", hr); |
| } else { |
| if (webBrowserIUnknown == pDispIUnknown) { |
| /* this is the top-level page frame */ |
| HRESULT hr = m_server->contextLoaded(GetCurrentProcessId()); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnDocumentComplete(): contextLoaded() failed", hr); |
| } |
| } |
| } |
| } |
| } |
| |
| void STDMETHODCALLTYPE IECrossfireBHO::OnNavigateComplete2(IDispatch *pDisp, VARIANT *pvarURL) { |
| if (m_contextCreated || m_serverState != STATE_CONNECTED) { |
| return; |
| } |
| |
| CComPtr<IUnknown> webBrowserIUnknown = NULL; |
| HRESULT hr = m_webBrowser->QueryInterface(IID_IUnknown, (void**)&webBrowserIUnknown); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnNavigateComplete2(): QI(IUnknown)[1] failed", hr); |
| } else { |
| CComPtr<IUnknown> pDispIUnknown = NULL; |
| hr = pDisp->QueryInterface(IID_IUnknown, (void**)&pDispIUnknown); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnNavigateComplete2(): QI(IUnknown)[2] failed", hr); |
| } else { |
| if (webBrowserIUnknown == pDispIUnknown) { |
| /* this is the top-level page frame */ |
| wchar_t* url = pvarURL->bstrVal; |
| wchar_t* hash = wcschr(url, wchar_t('#')); |
| if (!hash || !m_lastUrl || wcsncmp(url, m_lastUrl, hash - url) != 0) { |
| DWORD processId = GetCurrentProcessId(); |
| DWORD threadId = GetCurrentThreadId(); |
| HRESULT hr = m_server->contextCreated(processId, threadId, url); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnNavigateComplete2(): contextCreated() failed", hr); |
| } else { |
| m_contextCreated = true; |
| } |
| } |
| if (m_lastUrl) { |
| free(m_lastUrl); |
| } |
| m_lastUrl = _wcsdup(url); |
| } |
| } |
| } |
| } |
| |
| void STDMETHODCALLTYPE IECrossfireBHO::OnWindowStateChanged(LONG dwFlags, LONG dwValidFlagMask) { |
| m_isCurrentContext = (dwFlags == (OLECMDIDF_WINDOWSTATE_USERVISIBLE | OLECMDIDF_WINDOWSTATE_ENABLED)); |
| if (!m_isCurrentContext || m_serverState != STATE_CONNECTED) { |
| return; |
| } |
| |
| DWORD processId = GetCurrentProcessId(); |
| HRESULT hr = m_server->setCurrentContext(processId); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.OnWindowStateChanged(): setCurrentContext() failed"); |
| return; |
| } |
| } |
| |
| /* IBrowserContext */ |
| |
| STDMETHODIMP IECrossfireBHO::displayMessage(OLECHAR* message) { |
| MessageBox(NULL, message, L"Crossfire Server Error", 0); |
| return S_OK; |
| } |
| |
| STDMETHODIMP IECrossfireBHO::navigate(OLECHAR* url, boolean openNewTab) { |
| VARIANT variant_null; |
| variant_null.vt = VT_NULL; |
| |
| VARIANT variant_url; |
| variant_url.vt = VT_BSTR; |
| variant_url.bstrVal = url; |
| |
| VARIANT variant_flags; |
| if (openNewTab) { |
| variant_flags.vt = VT_I4; |
| variant_flags.intVal = navOpenInNewTab; |
| } else { |
| variant_flags = variant_null; |
| } |
| |
| HRESULT hr = m_webBrowser->Navigate2(&variant_url, &variant_flags, &variant_null, &variant_null, &variant_null); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.navigate(): Navigate2() failed", hr); |
| } |
| return hr; |
| } |
| |
| /* IObjectWithSite */ |
| |
| STDMETHODIMP IECrossfireBHO::GetSite(REFIID riid, LPVOID *ppvReturn) { |
| *ppvReturn = NULL; |
| if (m_webBrowser) { |
| return m_webBrowser->QueryInterface(riid, ppvReturn); |
| } |
| return E_FAIL; |
| } |
| |
| STDMETHODIMP IECrossfireBHO::SetSite(IUnknown* pUnkSite) { |
| HRESULT hr = S_OK; |
| if (pUnkSite) { |
| if (m_webBrowser) { |
| m_webBrowser->Release(); |
| m_webBrowser = NULL; |
| } |
| hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void**)&m_webBrowser); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.SetSite(): QI(IWebBrowser2) failed", hr); |
| } else { |
| hr = DispEventAdvise(m_webBrowser); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.SetSite(): DispEventAdvise() failed", hr); |
| } |
| } |
| initServer(false); |
| } else { |
| if (m_eventsHooked) { |
| HRESULT hr = DispEventUnadvise(m_webBrowser); |
| if (SUCCEEDED(hr)) { |
| m_eventsHooked = false; |
| } else { |
| Logger::error("IECrossfireBHO.SetSite(): DispEventUnadvise() failed", hr); |
| } |
| } |
| m_webBrowser->Release(); |
| m_webBrowser = NULL; |
| } |
| |
| //return IObjectWithSiteImpl<IECrossfireBHO>::SetSite(pUnkSite); |
| return hr; |
| } |
| |
| /* IECrossfireBHO */ |
| |
| bool IECrossfireBHO::displayHTML(wchar_t* htmlText) { |
| VARIANT variant; |
| variant.vt = VT_NULL; |
| CComBSTR bstr(ABOUT_BLANK); |
| HRESULT hr = m_webBrowser->Navigate(bstr, &variant, &variant, &variant, &variant); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.displayHTML(): Navigate failed", hr); |
| return false; |
| } |
| m_htmlToDisplay = _wcsdup(htmlText); |
| return true; |
| } |
| |
| //int IECrossfireBHO::getServerState() { |
| // initServer(false); |
| // if (!m_server) { |
| // return STATE_DISCONNECTED; |
| // } |
| // |
| // int state; |
| // HRESULT hr = m_server->getState(&state); |
| // if (FAILED(hr)) { |
| // return STATE_DISCONNECTED; |
| // } |
| // |
| // return state; |
| //} |
| |
| bool IECrossfireBHO::initServer(bool startIfNeeded) { |
| if (m_server) { |
| return true; |
| } |
| if (!startIfNeeded && !FindWindow(ServerWindowClass, NULL)) { |
| return false; |
| } |
| |
| /* the following is intentionally commented */ |
| |
| // CComPtr<IDispatch> dispatch = NULL; |
| // long applicationHwnd = 0; |
| // HRESULT hr = m_webBrowser->get_Application(&dispatch); |
| // if (SUCCEEDED(hr)) { |
| // DISPID dispId; |
| // CComBSTR name("HWND"); |
| // hr = dispatch->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_SYSTEM_DEFAULT, &dispId); |
| // if (SUCCEEDED(hr)) { |
| // DISPPARAMS params; |
| // memset(¶ms, 0, sizeof(DISPPARAMS)); |
| // VARIANT variant; |
| // hr = dispatch->Invoke(dispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &variant, NULL, NULL); |
| // if (SUCCEEDED(hr)) { |
| // applicationHwnd = variant.lVal; |
| // } |
| // } |
| // } |
| // if (!applicationHwnd) { |
| // Logger::error("IECrossfireBHO.initServer(): failed to get the application HWND", hr); |
| // return false; |
| // } |
| |
| CComPtr<ICrossfireServerClass> serverClass = NULL; |
| HRESULT hr = CoGetClassObject(CLSID_CrossfireServer, CLSCTX_ALL, 0, IID_ICrossfireServerClass, (LPVOID*)&serverClass); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.initServer(): CoGetClassObject() failed", hr); |
| return false; |
| } |
| |
| hr = serverClass->GetServer(/*applicationHwnd,*/ &m_server); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.initServer(): GetController() failed", hr); |
| return false; |
| } |
| |
| hr = m_server->getState(&m_serverState); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.initServer(): getState() failed", hr); |
| } |
| |
| if (m_serverState == STATE_CONNECTED) { |
| hr = m_server->registerBrowser(GetCurrentProcessId(), this); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.initServer(): registerBrowser() failed", hr); |
| /* continue */ |
| } |
| } |
| |
| return true; |
| } |
| |
| void IECrossfireBHO::onServerStateChanged(WPARAM wParam, LPARAM lParam) { |
| m_serverState = wParam; |
| initServer(false); |
| |
| /* If a connection was just established then create a context on the server for the current page */ |
| |
| if (m_serverState != STATE_CONNECTED) { |
| return; |
| } |
| |
| HRESULT hr = m_server->registerBrowser(GetCurrentProcessId(), this); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.onServerStateChanged(): registerBrowser() failed", hr); |
| /* continue */ |
| } |
| |
| CComBSTR url = NULL; |
| hr = m_webBrowser->get_LocationURL(&url); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.onServerStateChanged(): get_LocationURL() failed", hr); |
| return; |
| } |
| |
| DWORD processId = GetCurrentProcessId(); |
| DWORD threadId = GetCurrentThreadId(); |
| hr = m_server->contextCreated(processId, threadId, url); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.onServerStateChanged(): contextCreated() failed", hr); |
| return; |
| } |
| |
| if (m_isCurrentContext) { |
| hr = m_server->setCurrentContext(processId); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.onServerStateChanged(): setCurrentContext() failed", hr); |
| } |
| } |
| |
| /* |
| * If the current page is fully-loaded then inform the server. If the current |
| * page is still loading then the server will be notified of its completion |
| * from the usual OnDocumentComplete listener. |
| */ |
| VARIANT_BOOL busy; |
| hr = m_webBrowser->get_Busy(&busy); |
| if (SUCCEEDED(hr) && !busy) { |
| hr = m_server->contextLoaded(processId); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.onServerStateChanged(): contextLoaded() failed", hr); |
| return; |
| } |
| } |
| } |
| |
| bool IECrossfireBHO::startDebugging(unsigned int port) { |
| if (!Util::VerifyActiveScriptDebugger() || !Util::VerifyDebugPreference()) { |
| return false; |
| } |
| |
| if (!initServer(true) || m_serverState != STATE_DISCONNECTED) { |
| return false; |
| } |
| |
| HRESULT hr = m_server->start(port, 54124 /* debug port */); |
| if (FAILED(hr)) { |
| Logger::error("IECrossfireBHO.startDebugging(): start() failed"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| LRESULT CALLBACK IECrossfireBHO::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { |
| if (message == ServerStateChangeMsg) { |
| IECrossfireBHO *pThis = (IECrossfireBHO*)GetWindowLongPtr(hWnd, GWL_USERDATA); |
| if (!pThis) { |
| Logger::error("IECrossfireBHO.WndProc(): GetWindowLongPtr() failed"); |
| } else { |
| pThis->onServerStateChanged(wParam, lParam); |
| } |
| return 0; |
| } |
| return DefWindowProc(hWnd, message, wParam, lParam); |
| } |