blob: 5dfbd94909ad4244175e1ba46b9b18fd8a7080ab [file] [log] [blame]
/*******************************************************************************
* 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=&lt;port&gt;</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(&params, 0, sizeof(DISPPARAMS));
// VARIANT variant;
// hr = dispatch->Invoke(dispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &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);
}