/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// // ActionManager.cpp : Implementation of CActionManager // // © JetBrains Inc, 2005 // Written by (H) Serge Baltic #include "StdAfx.h" #include "JetIe.h" #include "ActionManager.h" IActionManagerPtr CActionManager::m_oMainThreadInstance = NULL; DWORD CActionManager::m_dwMainThreadID = NULL; CActionManager::CActionManager() : m_mutexDataFilesAccessLock(NULL, FALSE, _T("JetBrains.JetIe.") + CJetIe::LoadStringT(IDS_PLUGIN_NAME) + _T("ActionManager.DataFilesAccessLock")) { // {35402C00-1777-4159-9ABA-3480BA70D95A} GUID guidBase = ACTIONMANAGER_GUID_BASE; m_guidBase = guidBase; m_nGuidRange = 0xFF; } CActionManager::~CActionManager() { } // CActionManager XmlElement CActionManager::GetActions() { // TODO: guard loading/saving by exclusive file access or a mutex if(m_xmlActions == NULL) LoadData(true); return m_xmlActions->selectSingleNode(L"/JetIe/Actions"); } XmlElement CActionManager::GetAction(_bstr_t bsID) { XmlElement xmlAction = GetActions()->selectSingleNode(L"Action[@ID='" + bsID + L"']"); if(xmlAction == NULL) // Action with such ID was not found { CStringW sError; sError.Format(CJetIe::LoadString(IDS_E_NOSUCHACTION), (LPCWSTR)bsID); ThrowError(sError); } return xmlAction; } XmlElement CActionManager::GetAction2(XmlElement xmlControl) { CStringW sControlType = xmlControl->baseName; if(sControlType == L"Control") return GetAction((_bstr_t)xmlControl->getAttribute(L"Action")); else if(sControlType == L"DropDownButton") return GetAction2(ControlFromEntryID(xmlControl->getAttribute(L"Default"), xmlControl, false)); // Find a control referenced as the default else if(sControlType == L"Separator") ThrowError(CJetIe::LoadString(IDS_E_INVALIDCONTROLOPERATION)); else ThrowError(CJetIe::LoadString(IDS_E_UNKNOWNCONTROLTYPE)); return NULL; // Dummy return for the implicit Throw operations } XmlElement CActionManager::GetControls(_bstr_t bsType, _bstr_t bsName) { // TODO: guard loading/saving by exclusive file access or a mutex if(m_xmlControls == NULL) LoadData(true); XmlElement xmlRet = m_xmlControls->selectSingleNode((_bstr_t)L"/JetIe/Controls[@Type='" + bsType + L"' and @Name='" + bsName + L"']"); if(xmlRet == NULL) { CStringW sError; sError.Format(L"Cannot locate a definiton for the set of controls of type %s named %s.", (LPCWSTR)bsType, (LPCWSTR)bsName); ThrowError(sError); } return xmlRet; } void CActionManager::Execute(_bstr_t bsID, _variant_t vtParam) { XmlElement xmlAction = GetAction(bsID); XmlElement xmlExec = xmlAction->selectSingleNode(L"Exec"); if(xmlExec == NULL) { TRACE(L"Warning: execution handler not defined for \"%s\" action.", (LPCWSTR)bsID); return; } // Query the action status when trying to execute the action, and avoid execution if it is disabled or hidden DWORD dwOleCmdf; QueryStatus(bsID, vtParam, &dwOleCmdf, NULL, NULL, NULL, true); if((!(dwOleCmdf & OLECMDF_SUPPORTED)) || (!(dwOleCmdf & OLECMDF_ENABLED))) { TRACE(L"Warning: trying to execute a disabled or hidden (%#03X) action \"%s\".", dwOleCmdf, (LPCWSTR)bsID); return; } // Look for the available handlers among all the elements under the QueryStatus element XmlNodeList xmlHandlers = xmlExec->selectNodes(L"*"); XmlElement xmlHandler; CStringW sBaseName; while((xmlHandler = xmlHandlers->nextNode()) != NULL) { try { sBaseName = (LPCWSTR)xmlHandler->baseName; // Check the handler type if(sBaseName == L"DispatchHandler") { // Create an instance of the UI action dispatch handler object IDispatchPtr oHandler = GetDispatchHandler((LPCWSTR)(_bstr_t)xmlHandler->getAttribute(L"ClassID")); // Try to execute DISPID dispid; _variant_t avtParams[] = { vtParam }; DISPPARAMS dispparams = { avtParams, NULL, sizeof(avtParams) / sizeof(*avtParams), 0 }; CStringW sMember = (LPCWSTR)(_bstr_t)xmlHandler->getAttribute(L"Method"); // This buffer holds the string while it's in use OLECHAR FAR *szMember = (LPWSTR)(LPCWSTR)sMember; COM_CHECK(oHandler, GetIDsOfNames(IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid)); COM_CHECK(oHandler, Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL)); // We're not interested in the return value here } else TRACE(L"Unknown or unspecified type of handler for \"%s\" action exec.", (LPCWSTR)bsID); } catch(_com_error e) // Do not fail the whole process in case one of the handlers fails { COM_TRACE(); ASSERT(FALSE && "Failed to execute the action's Dispatch Handler."); } } } void CActionManager::Execute2(XmlElement xmlControl, _variant_t vtParam) { Execute((_bstr_t)xmlControl->getAttribute(L"Action"), vtParam); } void CActionManager::QueryStatus(_bstr_t bsID, _variant_t vtParam, DWORD *pdwOleCmdF, CStringW *psTitle, CStringW *psInfoTip, CStringW *psDescription, bool bDynamic) { // Retrieve the action of question XmlElement xmlAction = GetAction(bsID); // Hidden, disabled by default if(pdwOleCmdF != NULL) *pdwOleCmdF = 0; ////////////////////////////////// // Start with the static values XmlNode xmlNode; // Title if(psTitle != NULL) { psTitle->Empty(); if((xmlNode = xmlAction->selectSingleNode(L"Title/@Text")) != NULL) *psTitle = (LPCWSTR)(_bstr_t)xmlNode->nodeValue; } // InfoTip if(psInfoTip != NULL) { psInfoTip->Empty(); if((xmlNode = xmlAction->selectSingleNode(L"Title/@InfoTip")) != NULL) *psInfoTip = (LPCWSTR)(_bstr_t)xmlNode->nodeValue; } // Description if(psDescription != NULL) { psDescription->Empty(); if((xmlNode = xmlAction->selectSingleNode(L"Title/@Description")) != NULL) *psDescription = (LPCWSTR)(_bstr_t)xmlNode->nodeValue; } //////////////////////////////////////////////////////////////////////// // Static stage is off, if we don't need any dynamic info, we may exit if(!bDynamic) return; ///////////////////////////////////////////////// // Dynamic stage: locate and query the handlers XmlElement xmlQueryStatus = xmlAction->selectSingleNode(L"QueryStatus"); if(xmlQueryStatus == NULL) // No handlers, act as if in the static case { TRACE(L"Warning: query-status handler not defined for \"%s\" action.", (LPCWSTR)bsID); return; } // Look for the available handlers among all the elements under the QueryStatus element XmlNodeList xmlHandlers = xmlQueryStatus->selectNodes(L"*"); XmlElement xmlHandler; CStringW sBaseName; while((xmlHandler = xmlHandlers->nextNode()) != NULL) { try { sBaseName = (LPCWSTR)xmlHandler->baseName; if(sBaseName == L"Constant") // A constant handler that defines constant state information { if(pdwOleCmdF != NULL) { if((long)xmlHandler->getAttribute(L"Visible")) *pdwOleCmdF |= OLECMDF_SUPPORTED; if((long)xmlHandler->getAttribute(L"Enabled")) *pdwOleCmdF |= OLECMDF_ENABLED; if((long)xmlHandler->getAttribute(L"Checked")) *pdwOleCmdF |= OLECMDF_LATCHED; } } else if(sBaseName == L"DispatchHandler") // A simple dispatch handler (for the OleCmdF constant) { if(pdwOleCmdF != NULL) { // Create an instance of the UI action dispatch handler object IDispatchPtr oHandler = GetDispatchHandler((LPCWSTR)(_bstr_t)xmlHandler->getAttribute(L"ClassID")); // Prepare the parameters long nOleCmdF = (long)*pdwOleCmdF; _variant_t vtOleCmdF; V_VT(&vtOleCmdF) = VT_I4 | VT_BYREF; V_I4REF(&vtOleCmdF) = &nOleCmdF; // Try to execute DISPID dispid; _variant_t avtParams[] = { vtOleCmdF, vtParam }; DISPPARAMS dispparams = { avtParams, NULL, sizeof(avtParams) / sizeof(*avtParams), 0 }; CStringW sMember = (LPCWSTR)(_bstr_t)xmlHandler->getAttribute(L"Method"); // This buffer holds the string while it's in use OLECHAR FAR *szMember = (LPWSTR)(LPCWSTR)sMember; COM_CHECK(oHandler, GetIDsOfNames(IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid)); COM_CHECK(oHandler, Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL)); // Process the updated values *pdwOleCmdF = (DWORD)nOleCmdF; } } else if(sBaseName == L"DispatchHandlerEx") // An extended dispatch handler (for the state and text strings) { if((pdwOleCmdF != NULL) || (psTitle != NULL) || (psInfoTip != NULL) || (psDescription != NULL)) { // Create an instance of the UI action dispatch handler object IDispatchPtr oHandler = GetDispatchHandler((LPCWSTR)(_bstr_t)xmlHandler->getAttribute(L"ClassID")); /////////////////////////// // Prepare the parameters // OleCmdF long nOleCmdF; _variant_t vtOleCmdF; V_VT(&vtOleCmdF) = VT_I4 | VT_BYREF; if(pdwOleCmdF != NULL) { nOleCmdF = (long)*pdwOleCmdF; V_I4REF(&vtOleCmdF) = &nOleCmdF; } else V_I4REF(&vtOleCmdF) = NULL; // Title _bstr_t bsTitle; _variant_t vtTitle; V_VT(&vtTitle) = VT_BSTR | VT_BYREF; if(psTitle != NULL) { bsTitle = (LPCWSTR)*psTitle; V_BSTRREF(&vtTitle) = &bsTitle.GetBSTR(); } else V_BSTRREF(&vtTitle) = NULL; // InfoTip _bstr_t bsInfoTip; _variant_t vtInfoTip; V_VT(&vtInfoTip) = VT_BSTR | VT_BYREF; if(psInfoTip != NULL) { bsInfoTip = (LPCWSTR)*psInfoTip; V_BSTRREF(&vtInfoTip) = &bsInfoTip.GetBSTR(); } else V_BSTRREF(&vtInfoTip) = NULL; // Description _bstr_t bsDescription; _variant_t vtDescription; V_VT(&vtDescription) = VT_BSTR | VT_BYREF; if(psDescription != NULL) { bsDescription = (LPCWSTR)*psDescription; V_BSTRREF(&vtDescription) = &bsDescription.GetBSTR(); } else V_BSTRREF(&vtDescription) = NULL; // Try to execute DISPID dispid; _variant_t avtParams[] = { vtDescription, vtInfoTip, vtTitle, vtOleCmdF, vtParam }; DISPPARAMS dispparams = { avtParams, NULL, sizeof(avtParams) / sizeof(*avtParams), 0 }; CStringW sMember = (LPCWSTR)(_bstr_t)xmlHandler->getAttribute(L"Method"); // This buffer holds the string while it's in use OLECHAR FAR *szMember = (LPWSTR)(LPCWSTR)sMember; COM_CHECK(oHandler, GetIDsOfNames(IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT, &dispid)); COM_CHECK(oHandler, Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL)); // Process the updated values if(pdwOleCmdF != NULL) *pdwOleCmdF = (DWORD)nOleCmdF; if(psTitle != NULL) *psTitle = (LPCWSTR)bsTitle; if(psInfoTip != NULL) *psInfoTip =(LPCWSTR)bsInfoTip; if(psDescription != NULL) *psDescription = (LPCWSTR)bsDescription; } } else TRACE(L"Unknown or unspecified type of handler for \"%s\" action query status.", (LPCWSTR)bsID); } catch(_com_error e) // Do not fail the whole process in case one of the handlers fails { COM_TRACE(); ASSERT(FALSE && "Failed to query the action status."); } } } CStringW CActionManager::GetStaticInfoTip(_bstr_t bsID) { CStringW sInfoTip; QueryStatus(bsID, vtMissing, NULL, NULL, &sInfoTip, NULL, false); return sInfoTip; } CStringW CActionManager::GetStaticInfoTip2(XmlElement xmlControl) { return GetStaticInfoTip((_bstr_t)xmlControl->getAttribute(L"Action")); } void CActionManager::RegisterControls() { // Registering the controls involves updating some of the additional data in the controls set. That's why it has to be reloaded and locked until saved. CMutexLock(m_mutexDataFilesAccessLock, true); // First, erase all the old registration data. UnregisterControls(); TRACE(L"ActionManager is registering the controls."); // Make the configuration up-to-date LoadData(false); // Already locked CString sGuid; // An LPCTSTR textual GUID rep int nNextGuid = 0; // Shift for the next GUID XmlElement xmlControls, xmlControl, xmlAction; XmlNodeList xmlItems; CRegKey rkMain; ////////////////////////////////////////////////// // Remove the now-outdated RegClassID attributes try { xmlItems = m_xmlControls->selectNodes(L"//Control[string(@RegClassID) != '']"); TRACE(L"ActionManager is erasing %d old RegClassID attributes.", xmlItems->length); while((xmlControl = xmlItems->nextNode()) != NULL) xmlControl->removeAttribute(L"RegClassID"); } COM_CATCH(); // Not a fatal error, though ///////////////////// // Tools Menu Items try { xmlControls = GetControls(L"Menu", L"Tools"); xmlItems = xmlControls->selectNodes(L"Control"); while((xmlControl = xmlItems->nextNode()) != NULL) { xmlAction = GetAction((_bstr_t)xmlControl->getAttribute(L"Action")); // Action for this control sGuid = StringFromRangeGuid(nNextGuid++); // Generate GUID for the COM object that will handle the control events xmlControl->setAttribute(L"RegClassID", (_bstr_t)(LPCTSTR)sGuid); // Store // Register the extension rkMain.Create(HKEY_CURRENT_USER, _T("SOFTWARE\\Microsoft\\Internet Explorer\\Extensions\\") + StringFromRangeGuid(nNextGuid++)); // Here we use just some generic extension's GUID rkMain.SetStringValue(_T("MenuCustomize"), _T("")); rkMain.SetStringValue(_T("CLSID"), _T("{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}")); // CLSID of the type of extension, not our control one. Points to %SystemRoot%\system32\shdocvw.dll rkMain.SetStringValue(_T("MenuText"), CW2T((LPCWSTR)GetStaticTitle2(xmlControl))); // Do not register the current dynamic value rkMain.SetStringValue(_T("MenuStatusBar"), CW2T((LPCWSTR)GetStaticInfoTip2(xmlControl))); rkMain.SetStringValue(_T("ClsidExtension"), sGuid); // This is the GUID of the handler rkMain.Close(); // Register the handler object RegisterElementClassId(xmlControl); } } COM_CATCH(); //////////////////////////// // IE Main Toolbar Buttons try { xmlControls = GetControls(L"Toolbar", L"Main"); xmlItems = xmlControls->selectNodes(L"Control"); CString sIcon; while((xmlControl = xmlItems->nextNode()) != NULL) { xmlAction = GetAction((_bstr_t)xmlControl->getAttribute(L"Action")); // Action for this control sGuid = StringFromRangeGuid(nNextGuid++); // Generate GUID for the COM object that will handle the control events xmlControl->setAttribute(L"RegClassID", (_bstr_t)(LPCTSTR)sGuid); // Store // Register the extension rkMain.Create(HKEY_CURRENT_USER, _T("SOFTWARE\\Microsoft\\Internet Explorer\\Extensions\\") + StringFromRangeGuid(nNextGuid++)); // Here we use just some generic extension's GUID rkMain.SetStringValue(_T("CLSID"), _T("{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}")); // CLSID of the type of extension, not our control one. Points to %SystemRoot%\system32\shdocvw.dll rkMain.SetStringValue(_T("ClsidExtension"), sGuid); // This is the GUID of the handler try{ _variant_t vtValue = xmlControl->getAttribute(L"DefaultVisible"); if((V_VT(&vtValue) != VT_NULL) && ((_bstr_t)vtValue == (_bstr_t)L"1")) rkMain.SetStringValue(_T("Default Visible"), _T("Yes")); }COM_CATCH(); rkMain.SetStringValue(_T("ButtonText"), CW2T((LPCWSTR)GetStaticTitle2(xmlControl))); // Do not register the current dynamic value // Normal icon if(xmlAction->selectSingleNode(L"Image/@Normal") != NULL) { sIcon = (LPCTSTR)(_bstr_t)xmlAction->selectSingleNode(L"Image/@Normal")->nodeValue; sIcon.Replace(_T("%JETIE%"), CJetIe::GetModuleFileName()); // Substitute with the real file name rkMain.SetStringValue(_T("Icon"), sIcon); } // Hot icon if(xmlAction->selectSingleNode(L"Image/@Hot") != NULL) { sIcon = (LPCTSTR)(_bstr_t)xmlAction->selectSingleNode(L"Image/@Hot")->nodeValue; sIcon.Replace(_T("%JETIE%"), CJetIe::GetModuleFileName()); // Substitute with the real file name rkMain.SetStringValue(_T("HotIcon"), sIcon); } rkMain.Close(); // Register the handler object RegisterElementClassId(xmlControl); } } COM_CATCH(); ///////////////////// // IE Context Menu try { xmlControls = GetControls(L"ContextMenu", L"Main"); xmlItems = xmlControls->selectNodes(L"Control"); CString sURI; DWORD dwContext; int nContextMenuItem = 1000; // Index of the slot, up to 16 slots available while((xmlControl = xmlItems->nextNode()) != NULL) { xmlAction = GetAction((_bstr_t)xmlControl->getAttribute(L"Action")); // Action for this control if(nContextMenuItem >= 1016) ThrowError(L"An attempt was made to generate a context menu item ID out of available range. There are too many context menu items."); xmlControl->setAttribute(L"RegClassID", (_variant_t)nContextMenuItem); // Generate & Store // Prepare parameters sURI.Format(_T("res://") + CJetIe::GetModuleFileName() + _T("/%d"), nContextMenuItem); dwContext = 0; if((long)xmlControl->getAttribute(L"OnDefault")) dwContext |= (0x1 << CONTEXT_MENU_DEFAULT); if((long)xmlControl->getAttribute(L"OnImages")) dwContext |= (0x1 << CONTEXT_MENU_IMAGE); if((long)xmlControl->getAttribute(L"OnControls")) dwContext |= (0x1 << CONTEXT_MENU_CONTROL); if((long)xmlControl->getAttribute(L"OnTables")) dwContext |= (0x1 << CONTEXT_MENU_TABLE); if((long)xmlControl->getAttribute(L"OnTextSelection")) dwContext |= (0x1 << CONTEXT_MENU_TEXTSELECT); if((long)xmlControl->getAttribute(L"OnAnchor")) dwContext |= (0x1 << CONTEXT_MENU_ANCHOR); if((long)xmlControl->getAttribute(L"OnUnknown")) dwContext |= (0x1 << CONTEXT_MENU_UNKNOWN); // Register the MenuExt rkMain.Create(HKEY_CURRENT_USER, (CString)_T("SOFTWARE\\Microsoft\\Internet Explorer\\MenuExt\\") + (LPCTSTR)CW2T((LPCWSTR)GetStaticTitle2(xmlControl))); // Menu items are identified by titles rkMain.SetStringValue(NULL, sURI); // Script that executes the action rkMain.SetDWORDValue(_T("Contexts"), dwContext); rkMain.SetDWORDValue(_T("Flags"), 0); rkMain.SetDWORDValue(CJetIe::LoadStringT(IDS_PLUGIN_NAME), 1); // A flag that allows to identify this item upon unregistering rkMain.Close(); nContextMenuItem++; } } COM_CATCH(); ///////////////////// // Custom Toolbars try { xmlItems = m_xmlControls->selectNodes(L"/JetIe/Controls[@Type='Toolbar' and @Name!='Main']"); XmlElement xmlToolbar; while((xmlToolbar = xmlItems->nextNode()) != NULL) { sGuid = StringFromRangeGuid(nNextGuid++); // Generate GUID for the COM object that will handle the toolbar xmlToolbar->setAttribute(L"RegClassID", (_bstr_t)(LPCTSTR)sGuid); // Store // Register the extension rkMain.Create(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Internet Explorer\\Toolbar")); // TODO: HKLM or HKCU? rkMain.SetDWORDValue(sGuid, 1); //rkMain.SetBinaryValue(sGuid, &"", 0); rkMain.Close(); // Register the handler object RegisterElementClassId(xmlToolbar); } } COM_CATCH(); //////////////////////// // Flush the IE Caches TRACE(L"Flushing the Internet Explorer caches."); CRegKey rkHKCR(HKEY_CLASSES_ROOT); CRegKey rkHKCU(HKEY_CURRENT_USER); CRegKey rkHKLM(HKEY_LOCAL_MACHINE); rkHKCR.RecurseDeleteKey(_T("Component Categories\\{00021492-0000-0000-C000-000000000046}\\Enum")); rkHKCU.RecurseDeleteKey(_T("Software\\Microsoft\\Windows\\Current Version\\Explorer\\Discardable\\PostSetup\\Component Categories\\{00021493-0000-0000-C000-000000000046}\\Enum")); rkHKLM.RecurseDeleteKey(_T("Software\\Microsoft\\Windows\\Current Version\\Explorer\\Discardable\\PostSetup\\Component Categories\\{00021493-0000-0000-C000-000000000046}\\Enum")); rkHKCU.RecurseDeleteKey(_T("Software\\Microsoft\\Windows\\Current Version\\Explorer\\Discardable\\PostSetup\\Component Categories\\{00021494-0000-0000-C000-000000000046}\\Enum")); rkHKLM.RecurseDeleteKey(_T("Software\\Microsoft\\Windows\\Current Version\\Explorer\\Discardable\\PostSetup\\Component Categories\\{00021494-0000-0000-C000-000000000046}\\Enum")); TRACE(L"ActionManager has completed registering the controls."); SaveData(false); // Already locked } void CActionManager::UnregisterControls() { TRACE(L"ActionManager is unregistering the controls."); ///////////////////////////// // Remove Registry records CString sGuid; // An LPCTSTR textual GUID rep int nCleaned = 0; // Number of erased keys CRegKey rkHKCR(HKEY_CLASSES_ROOT); CRegKey rkHKCU(HKEY_CURRENT_USER); CRegKey rkUserToolbars; rkUserToolbars.Open(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Internet Explorer\\Toolbar")); CRegKey rkMachineToolbars; rkMachineToolbars.Open(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Internet Explorer\\Toolbar")); // Extenstions and objects that use CLSIDs for(int a = 0; a < m_nGuidRange; a++) { // Construct the next GUID and its string rep sGuid = StringFromRangeGuid(a); // COM object registration in HKCU/Software/Classes (WinNT case) nCleaned += (rkHKCU.RecurseDeleteKey(_T("Software\\Classes\\CLSID\\") + sGuid) == ERROR_SUCCESS); // COM object registration in HKCU/Software/Classes (Win9x case) nCleaned += (rkHKCR.RecurseDeleteKey(_T("CLSID\\") + sGuid) == ERROR_SUCCESS); // Toolbars nCleaned += (rkUserToolbars.DeleteValue(sGuid) == ERROR_SUCCESS); nCleaned += (rkMachineToolbars.DeleteValue(sGuid) == ERROR_SUCCESS); // TODO: remove one of these // IE Toolbar Buttons, IE Tools Menu Items, etc nCleaned += (rkHKCU.RecurseDeleteKey(_T("Software\\Microsoft\\Internet Explorer\\Extensions\\") + sGuid) == ERROR_SUCCESS); // IE Extenstions Cache nCleaned += (rkHKCU.RecurseDeleteKey(_T("Software\\Microsoft\\Windows\\CurrentVersion\\Ext\\Stats\\") + sGuid) == ERROR_SUCCESS); } // Context menu items CRegKey rkMenuExtRoot, rkMenuExt; CString sPluginName = CJetIe::LoadStringT(IDS_PLUGIN_NAME); rkMenuExtRoot.Create(HKEY_CURRENT_USER, _T("SOFTWARE\\Microsoft\\Internet Explorer\\MenuExt")); TCHAR szMenuExtName[0x400]; DWORD dwNameLen = sizeof(szMenuExtName)/sizeof(*szMenuExtName); DWORD dwValue; for( // Enumerate the subkeys int a = 0, nIndex = 0; // Seed (a < 0x1000) && (rkMenuExtRoot.EnumKey(nIndex, szMenuExtName, &dwNameLen) != ERROR_NO_MORE_ITEMS); // Try getting the next item, plus 0x1000 for a safeguard a++, dwNameLen = sizeof(szMenuExtName)/sizeof(*szMenuExtName)) // Refresh the name length { if(rkMenuExt.Open(rkMenuExtRoot, szMenuExtName) == ERROR_SUCCESS) // Check if it was created by us { if((rkMenuExt.QueryDWORDValue(sPluginName, dwValue) == ERROR_SUCCESS) && (dwValue)) // Yes, it was created by us { rkMenuExt.Close(); // Close first nCleaned += (rkMenuExtRoot.RecurseDeleteKey(szMenuExtName) == ERROR_SUCCESS); // Delete } else { rkMenuExt.Close(); // Just close, it's not ours nIndex++; // Not increased in case of a successful search } } } rkMenuExtRoot.Close(); // Delete the actions and controls files DeleteFile(CJetIe::GetDataFolderPathName(FALSE) + _T("\\UIActions.xml")); DeleteFile(CJetIe::GetDataFolderPathName(FALSE) + _T("\\UIControls.xml")); TRACE(L"ActionManager has completed unregistering the controls by erasing %d Registry records.", nCleaned); } CString CActionManager::StringFromRangeGuid(int nShift) { ASSERT(nShift >= 0); ASSERT(nShift < 0xFF); if(nShift >= 0xFF) ThrowError(L"An attempt was made to generate a control GUID out of available range."); GUID guid; OLECHAR szwGuid[0x100]; // An OLESTR buffer for the textual GUID rep guid = m_guidBase; guid.Data4[7] = nShift; StringFromGUID2(guid, szwGuid, 0x100); return (LPCTSTR)(_bstr_t)szwGuid; } void CActionManager::LoadData(bool bLock) { CMutexLock(m_mutexDataFilesAccessLock, bLock); #ifdef _TRACE DWORD dwTicks = GetTickCount(); #endif bool bLoaded; CString sFileName; // TODO: ensure the Default property is set for the drop-down-button controls // TODO: ensure the EntryIDs are set and do not duplicate /////////////////// // Actions bLoaded = false; // Try loading from the disk sFileName = CJetIe::GetDataFolderPathName(FALSE) + _T("\\UIActions.xml"); if(PathFileExists(sFileName)) { try { m_xmlActions = CJetIe::CreateXmlDocument(); //m_xmlActions->setProperty(L"SelectionLanguage", L"XPath"); m_xmlActions->load((_bstr_t)(LPCTSTR)sFileName); // TODO: validation if(m_xmlActions->parseError->errorCode == 0) { bLoaded = true; TRACE(L"Loaded the UI actions set from \"%s\".", ToW(sFileName)); } } COM_CATCH(); } // Try using the default copy if(!bLoaded) { try { m_xmlActions = CJetIe::CreateXmlDocument(); //m_xmlActions->setProperty(L"SelectionLanguage", L"XPath"); CJetIe::SerializeResource(RT_HTML, _T("UIActions.xml"), m_xmlActions, false); // TODO: validation if(m_xmlActions->parseError->errorCode == 0) { bLoaded = true; TRACE(L"Loaded the UI actions set from the resources."); } } COM_CATCH(); } // Fail if not there if(!bLoaded) ThrowError(CJetIe::LoadString(IDS_E_CANNOTLOADACTIONS)); /////////////////// // Controls bLoaded = false; // Try loading from the disk sFileName = CJetIe::GetDataFolderPathName(FALSE) + _T("\\UIControls.xml"); if(PathFileExists(sFileName)) { try { m_xmlControls = CJetIe::CreateXmlDocument(); //m_xmlActions->setProperty(L"SelectionLanguage", L"XPath"); m_xmlControls->load((_bstr_t)(LPCTSTR)sFileName); // TODO: validation if(m_xmlControls->parseError->errorCode == 0) { bLoaded = true; TRACE(L"Loaded the UI controls set from \"%s\".", ToW(sFileName)); } } COM_CATCH(); } // Try using the default copy if(!bLoaded) { try { m_xmlControls = CJetIe::CreateXmlDocument(); //m_xmlActions->setProperty(L"SelectionLanguage", L"XPath"); CJetIe::SerializeResource(RT_HTML, _T("UIControls.xml"), m_xmlControls, false); // TODO: validation if(m_xmlControls->parseError->errorCode == 0) { bLoaded = true; TRACE(L"Loaded the UI controls set from the resources."); } // As we're loading the UI controls set from the resources, it's the first time we're accessing it // Do some housekeeping like assigning the missing entry-ids ValidateControls(); } COM_CATCH(); } // Fail if not there if(!bLoaded) ThrowError(CJetIe::LoadString(IDS_E_CANNOTLOADCOMTROLS)); #ifdef _TRACE TRACE(L"Action Manager has loaded the UI Actions and Controls in %f sec.", (float)(GetTickCount() - dwTicks) / 1000); #endif } void CActionManager::SaveData(bool bLock) { CMutexLock(m_mutexDataFilesAccessLock, bLock); CString sFileName; /////////////////// // Actions sFileName = CJetIe::GetDataFolderPathName(FALSE) + _T("\\UIActions.xml"); try { m_xmlActions->save((_bstr_t)(LPCTSTR)sFileName); } catch(_com_error e) { CStringW sError; sError.Format(L"Cannot save the UI Actions set to \"%s\". %s", ToW(sFileName), COM_REASON(e)); COM_TRACE(); ThrowError(sError); } /////////////////// // Controls sFileName = CJetIe::GetDataFolderPathName(FALSE) + _T("\\UIControls.xml"); try { m_xmlControls->save((_bstr_t)(LPCTSTR)sFileName); } catch(_com_error e) { CStringW sError; sError.Format(L"Cannot save the UI Controls set to \"%s\". %s", ToW(sFileName), COM_REASON(e)); COM_TRACE(); ThrowError(sError); } } CStringW CActionManager::GetStaticTitle2(XmlElement xmlControl) { // TODO: Implement the Dynamic case! // TODO: Implement taking the Control's title override CStringW sTitle; QueryStatus((_bstr_t)xmlControl->getAttribute(L"Action"), vtMissing, NULL, &sTitle, NULL, NULL, false); return sTitle; } void CActionManager::RegisterElementClassId(XmlElement xmlControl) { CRegKey rkClsid; // First, try for the current user (works best for NT) // TODO: check the OS type! if((CJetIe::IsWinNT()) && (rkClsid.Create(HKEY_CURRENT_USER, _T("Software\\Classes\\CLSID\\" + (CString)(LPCTSTR)(_bstr_t)xmlControl->getAttribute(L"RegClassID"))) != ERROR_SUCCESS)) ThrowError(L"Cannot write object registration into Registry, HKCU key on WinNT."); if((!CJetIe::IsWinNT()) && (rkClsid.Create(HKEY_CLASSES_ROOT, _T("CLSID\\") + (CString)(LPCTSTR)(_bstr_t)xmlControl->getAttribute(L"RegClassID")) != ERROR_SUCCESS)) ThrowError(L"Cannot write object registration into Registry, HKCR key on Win98."); if(xmlControl->baseName == (_bstr_t)L"Control") rkClsid.SetStringValue(NULL, (CString)_T("JetIe ") + (LPCTSTR)CW2T((LPCWSTR)GetStaticTitle2(xmlControl)) + _T(" UI Control Hanlder")); // This is not quite useful, just for instance else if(xmlControl->baseName == (_bstr_t)L"Controls") rkClsid.SetStringValue(NULL, (LPCTSTR)(_bstr_t)xmlControl->getAttribute(L"Title")); // Title is retrieved from here, so just post it else rkClsid.SetStringValue(NULL, _T("JetIe UI Hanlder")); CRegKey rkInprocServer; rkInprocServer.Create(rkClsid, _T("InprocServer32")); rkInprocServer.SetStringValue(NULL, CJetIe::GetModuleFileName()); rkInprocServer.SetStringValue(_T("ThreadingModel"), _T("Apartment")); rkInprocServer.Close(); rkClsid.Close(); } XmlElement CActionManager::ControlFromGuid2(_bstr_t bsGuid) { if(m_xmlControls == NULL) LoadData(true); // Just look for a Control element with such a reference XmlElement xmlControl = m_xmlControls->selectSingleNode(L"//Control[@RegClassID = '" + bsGuid + L"']"); // Check for a failure if(xmlControl == NULL) { CStringW sError; sError.Format(CJetIe::LoadString(IDS_E_NOCONTROLGUID), (LPCWSTR)bsGuid); ThrowError(sError); } return xmlControl; } XmlElement CActionManager::ControlFromGuid(REFGUID guid) { OLECHAR szwGuid[0x100]; // An OLESTR buffer for the textual GUID rep StringFromGUID2(guid, szwGuid, 0x100); return ControlFromGuid2((_bstr_t)szwGuid); } XmlElement CActionManager::ControlFromEntryID(int nID, XmlElement xmlParent /*= NULL*/, bool bDeep /*= true*/) { if(m_xmlControls == NULL) LoadData(true); if(xmlParent == NULL) // If parent is not set, lookup all the controls xmlParent = m_xmlControls; XmlElement xmlRet; if(bDeep) // Recurse to sub-trees xmlRet = xmlParent->selectSingleNode((_bstr_t)L".//*[@EntryID='" + (_bstr_t)(_variant_t)nID + L"']"); else // No recurse, lookup immediate children only xmlRet = xmlParent->selectSingleNode((_bstr_t)L"*[@EntryID='" + (_bstr_t)(_variant_t)nID + L"']"); if(xmlRet == NULL) // If a control is not found, report an error (do not return NULL as it will most probably cause a mute "E_POINTER" exception { CStringW sError; sError.Format(CJetIe::LoadString(IDS_E_NOCONTROLENTRYID), nID); ThrowError(sError); } return xmlRet; } XmlElement CActionManager::ControlFamilyFromGuid(REFGUID guid, _bstr_t bsControlType) { // GUID to string OLECHAR szwGuid[0x100]; // An OLESTR buffer for the textual GUID rep StringFromGUID2(guid, szwGuid, 0x100); if(m_xmlControls == NULL) LoadData(true); // Just look for a Controls element with such a type and reference XmlElement xmlControls = m_xmlControls->selectSingleNode(L"/JetIe/Controls[@Type='" + bsControlType + L"' and @RegClassID = '" + (_bstr_t)szwGuid + L"']"); // Check for a failure if(xmlControls == NULL) { CStringW sError; sError.Format(L"There is no control family of type \"%s\" registered as %s.", (LPCWSTR)bsControlType, szwGuid); ThrowError(sError); } return xmlControls; } void CActionManager::ThrowError(CStringW sError) { TRACE(L"" + sError + L"\n"); _com_issue_errorex(Error(sError), static_cast(this), __uuidof(IActionManager)); } void CActionManager::ThrowSystemError(DWORD dwError /*= GetLastError()*/, LPCWSTR szComment /*= NULL*/) { // Throw the formatted error if(szComment == NULL) ThrowError(CJetIe::GetSystemError(dwError)); else ThrowError((CStringW)szComment + L'\n' + CJetIe::GetSystemError(dwError)); } STDMETHODIMP CActionManager::ExecuteContextMenuAction(VARIANT ActionRef, VARIANT Parameter) { try { // Get the corresponding control (or throw & return error, if nonexistent) XmlElement xmlControl = ControlFromGuid2((_bstr_t)(_variant_t)ActionRef); // Prepare the browser parameter IServiceProviderPtr oServiceProvider = (IUnknown*)(_variant_t)Parameter; // The passed-in Service Provider SHDocVw::IWebBrowser2Ptr oBrowser; oServiceProvider->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, reinterpret_cast(&oBrowser)); // Get the Web browser object interface // Execute the action associated with the referenced control Execute2(xmlControl, (_variant_t)(IDispatch*)(IDispatchPtr)oBrowser); } COM_CATCH_RETURN(); return S_OK; } void CActionManager::ValidateControls() { /////////////////////////////////////////////////////////////// // Ensure that all the EntryID's are set and do not duplicate std::map mapEntryIds; // Stores the entry IDs encountered in the controls list to check for duplicates std::vector xmlEntriless; // Elements without an entry-ID that should be granted a new entry-id // Enumerate all the control families XmlNodeList xmlControlFamilies = m_xmlControls->selectNodes(L"/JetIe/Controls"); XmlElement xmlControlFamily; while((xmlControlFamily = xmlControlFamilies->nextNode()) != NULL) { // Enumerate all the controls within the control families XmlNodeList xmlControls = xmlControlFamily->selectNodes(L".//*"); XmlElement xmlControl; while((xmlControl = xmlControls->nextNode()) != NULL) { try { if(xmlControl->getAttributeNode(L"EntryID") != NULL) { // If EntryID is present, check for duplicates int nID = xmlControl->getAttribute(L"EntryID"); if((nID == 0) || (mapEntryIds.find(nID) != mapEntryIds.end())) // This ID is duplicated by an existing one; zero entry-ids are also disallowed { // Schedule for generation of a new ID xmlControl->removeAttribute(L"EntryID"); xmlEntriless.insert(xmlEntriless.end(), xmlControl); } else // This is a new ID, add it mapEntryIds[nID] = true; } else // No EntryID speicifed, schedulle for assigning a new one xmlEntriless.insert(xmlEntriless.end(), xmlControl); } catch(_com_error e) { // An error has occured, probably when reading/coercing the ID, schedulle for regeneration COM_TRACE(); xmlEntriless.insert(xmlEntriless.end(), xmlControl); } } } // Generate the new entry-IDs, as necessary std::vector::iterator it; int nNextEntryID = 1; // Start looking for vacant IDs from this point for(it = xmlEntriless.begin(); it != xmlEntriless.end(); ++it) { // Find the next vacant ID for( ; (mapEntryIds.find(nNextEntryID) != mapEntryIds.end()) && (nNextEntryID < 0x7FFF); nNextEntryID++) ; // Apply (*it)->setAttribute(L"EntryID", (_variant_t)nNextEntryID); mapEntryIds[nNextEntryID] = true; } ///////////////////////////////////////////////////////////////////// // Check that the drop-down-buttons have their default controls set XmlNodeList xmlDropDownButtons = m_xmlControls->selectNodes(L"/JetIe/Controls//DropDownButton"); XmlElement xmlDropDownButton; while((xmlDropDownButton = xmlDropDownButtons->nextNode()) != NULL) { // If the drop-down-button has no default control (that is, entry of that control assigned to the Default attribute), or there is no such control beneath the button if((xmlDropDownButton->getAttributeNode(L"Default") == NULL) || (xmlDropDownButton->selectSingleNode((_bstr_t)L"*[@EntryID='" + (_bstr_t)xmlDropDownButton->getAttribute(L"EntryID") + L"']") == NULL)) { // If the button has no suitable children, just kill it if(xmlDropDownButton->selectSingleNode(L"*[string(@EntryID) != '']") == NULL) xmlDropDownButton->parentNode->removeChild(xmlDropDownButton); else // There are suitable children, take EntryID of the first of them as a default xmlDropDownButton->setAttribute(L"Default", XmlElement(xmlDropDownButton->selectSingleNode(L"*[string(@EntryID) != '']"))->getAttribute(L"EntryID")); } } } IRawActionManagerPtr CActionManager::GetInstance() { // If the cached instance is valid for the current thread, return it if(m_dwMainThreadID == GetCurrentThreadId()) return m_oMainThreadInstance; // If there is a cached instance, but it's invalid in this thread, create a new one if(m_dwMainThreadID != NULL) { IRawActionManagerPtr oInstance(__uuidof(CActionManager)); return oInstance; } // There is no caching instance. Create a new one, but under a lock { CMutex mutexStaticLock(NULL, TRUE, _T("JetBrains.JetIe.") + CJetIe::LoadStringT(IDS_PLUGIN_NAME) + _T("ActionManager.StaticLock")); // Lock now IRawActionManagerPtr oInstance(__uuidof(CActionManager)); if(m_dwMainThreadID != NULL) // May have became non-null in the time since the last check return oInstance; // Just throw out the temp instance // Set up cache for this thread m_dwMainThreadID = GetCurrentThreadId(); m_oMainThreadInstance = oInstance; return oInstance; } } void CActionManager::Load() { LoadData(true); } void CActionManager::Save() { SaveData(true); } bool CActionManager::HasText(_bstr_t bsID) { XmlNode xmlNode = GetAction(bsID)->selectSingleNode(L"Style/@Text"); if(xmlNode == NULL) return false; // Not specified => not shown return !!(int)xmlNode->nodeValue; // Coerce as int to a boolean value } IDispatchPtr CActionManager::GetDispatchHandler(CStringW sClassID) throw(_com_error) { IDispatchPtr oHandler; // Try to look up an existing handler std::map::iterator itHandler = m_mapDispatchHandlers.find(sClassID); if(itHandler == m_mapDispatchHandlers.end()) { // Cache missed, create a new instance and add it to the cache oHandler.CreateInstance(sClassID); m_mapDispatchHandlers[sClassID] = oHandler; } else oHandler = itHandler->second; // Cache hit, take the existing instance return oHandler; } XmlElement CActionManager::ShowPopupMenu(XmlElement xmlParent, HWND hwndParent, POINT ptScreenCoordinates, _variant_t vtParam) throw(_com_error) { return ShowPopupMenu(xmlParent->selectNodes(L"*"), hwndParent, ptScreenCoordinates, vtParam); } XmlElement CActionManager::ShowPopupMenu(XmlNodeList xmlControls, HWND hwndParent, POINT ptScreenCoordinates, _variant_t vtParam) throw(_com_error) { // Create a popup menu that will hold the top-level items CPopupMenuHandle menu; menu.Attach(CreatePopupMenu()); // An array of the submenus whose only duty is to free the resources when they're needed no more CPopupMenuHandleArray arSubmenus; // Maps the temporary IDs assigned to the menu items to action IDs that should be executed upon activating a particular item std::map mapItemControls; // Fill the top-level items and recurse to submenus, if needed FillSubmenu(xmlControls, menu, arSubmenus, mapItemControls, vtParam); // Show the menu int nSelectedId = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_TOPALIGN | TPM_RETURNCMD, ptScreenCoordinates.x, ptScreenCoordinates.y, NULL, hwndParent, NULL); // Check for errors or user-cancel // A false return value means either a failure or user's cancelling the menu, treat as to an error to the first case only if(!nSelectedId) { DWORD dwPossibleError; if((dwPossibleError = GetLastError()) != ERROR_SUCCESS) CJetIeException::ThrowSystemError(); return NULL; // User-cancelled } // Find the action that was selected std::map::iterator itAction = mapItemControls.find(nSelectedId); if(itAction == mapItemControls.end()) CJetIeException::ThrowComError(E_UNEXPECTED); // Execute the action Execute2(itAction->second, vtParam); return itAction->second; // The control that was selected } void CActionManager::FillSubmenu(XmlNodeList xmlControls, HMENU menuParent, CPopupMenuHandleArray &arSubmenus, std::map &mapItemControls, _variant_t vtParam) throw(_com_error) { XmlElement xmlControl; // Enumerate and process all the controls while((xmlControl = xmlControls->nextNode()) != NULL) { try // Per-control failures do not cause the global failure { // Further processing depends on the control type if(xmlControl->baseName == (_bstr_t)L"Control") // Just an ordinary control { XmlElement xmlAction = GetAction2(xmlControl); _bstr_t bsActionId = xmlAction->getAttribute(L"ID"); // Query for the control text, visibility, and enabled-state DWORD dwOleCmdF = 0; CStringW sTitle; QueryStatus(bsActionId, vtParam, &dwOleCmdF, &sTitle, NULL, NULL, true); // Append, if visible if(dwOleCmdF & OLECMDF_SUPPORTED) { int nID = (int)mapItemControls.size() + 1; // An unique ID for the next item CHECK_BOOL(AppendMenu(menuParent, MF_STRING | ((dwOleCmdF & OLECMDF_ENABLED) ? MF_ENABLED : MF_DISABLED), nID, ToT(sTitle))); mapItemControls[nID] = xmlControl; } } // "Control" else if(xmlControl->baseName == (_bstr_t)L"DropDownButton") // A drop-down button with a drop-arrow and default action, both clickable: [button|v] { // Get the title string for the dropdown CStringW sTitle; QueryStatus((_bstr_t)ControlFromEntryID(xmlControl->getAttribute(L"Default"), xmlControl, false)->getAttribute(L"Action"), vtParam, NULL, &sTitle, NULL, NULL, true); // Add a popup menu, register for disposal HMENU menuSub = CreateMenu(); arSubmenus.Add(menuSub); // Mount the submenu into the current menu CHECK_BOOL(AppendMenu(menuParent, MF_POPUP, (UINT_PTR)menuSub, ToT(sTitle))); // Fill in the submenu FillSubmenu(xmlControl->selectNodes(L"*"), menuSub, arSubmenus, mapItemControls, vtParam); } else if(xmlControl->baseName == (_bstr_t)L"Separator") // Add a separator to the toolbar { // Add a separator to the menu CHECK_BOOL(AppendMenu(menuParent, MF_SEPARATOR, 0, NULL)); } else // Yet another control type { ASSERT(FALSE); ThrowError(CJetIe::LoadString(IDS_E_CONTROLTYPE)); } } catch(_com_error e) { COM_TRACE(); TRACE(L"Failed to add the %s control to the popup menu.", (LPCWSTR)(_bstr_t)xmlControl->xml); } } } // TODO: resolve the collision that we add extensions for the current user but have to register the handlers globally under Windows 9x !! Most probably, by having an individual GUID range for each user.