//+============================================================================= // // File : persist.cxx // // contents : implementation of CPersistData xtag // //============================================================================= #include "headers.h" #pragma MARK_DATA(__FILE__) #pragma MARK_CODE(__FILE__) #pragma MARK_CONST(__FILE__) #ifndef X_MSHTML_H_ #define X_MSHTML_H_ #include "mshtml.h" // for IHTML*ELement* #endif #ifndef X_PERHIST_H_ #define X_PERHIST_H_ #include "perhist.h" // For IPersistHistory #endif #ifndef X_DISPEX_H_ #define X_DISPEX_H_ #include "dispex.h" // For IDispatchEx #endif #ifndef X_PERSIST_HXX_ #define X_PERSIST_HXX_ #include "persist.hxx" #endif #ifndef __X_IEXTAG_H_ #define __X_IEXTAG_H_ #include "iextag.h" #endif #ifndef __X_UTILS_HXX_ #define __X_UTILS_HXX_ #include "utils.hxx" #endif //+---------------------------------------------------------------------------- // // Member : Init // // Synopsis : this method is called by MSHTML.dll to initialize peer object // //----------------------------------------------------------------------------- HRESULT CPersistDataPeer::Init(IElementBehaviorSite * pPeerSite) { _pPeerSite = pPeerSite; _pPeerSite->AddRef(); _pPeerSite->QueryInterface(IID_IElementBehaviorSiteOM, (void**)&_pPeerSiteOM); // now register our 2 events if (_pPeerSiteOM) { _pPeerSiteOM->RegisterEvent (_T("onload"), 0, NULL); _pPeerSiteOM->RegisterEvent (_T("onsave"), 0, NULL); } return S_OK; } //+---------------------------------------------------------------------------- // // Member : Notify // // Synopsis : when document is ready for modifications, setup timer calls // //----------------------------------------------------------------------------- HRESULT CPersistDataPeer::Notify(LONG lEvent, VARIANT *) { return S_OK; } //+---------------------------------------------------------------------------- // // member : queryType // // Synopsis : IPersistData method, this is used by the calling routines in order // to avoid cocreating the XML object. This way they can query any of the // persist Tags, and find out what persist behaiors it supprots. //----------------------------------------------------------------------------- HRESULT CPersistDataPeer::queryType(long lType, VARIANT_BOOL * pfSupportsType) { if (!pfSupportsType) return E_POINTER; *pfSupportsType = (lType == (long)_eState) ? VB_TRUE : VB_FALSE; return S_OK; } //+---------------------------------------------------------------------------- // // Member : Save // // Synopsis : Implementation of the IHTMLPersistData method // //----------------------------------------------------------------------------- HRESULT CPersistDataPeer::save(IUnknown * pUnk, long lType, VARIANT_BOOL *pfBroadcast) { HRESULT hr; BSTR bstrEvent; if (!pfBroadcast) { hr = E_POINTER; goto Cleanup; } *pfBroadcast = VB_TRUE; // cache the OM pointers InitOM(pUnk, lType); bstrEvent = SysAllocString(L"onsave"); if (!bstrEvent) { hr = E_OUTOFMEMORY; goto Cleanup; } // allow script to save data hr = (FireEvent(bstrEvent, pfBroadcast, TRUE)); SysFreeString(bstrEvent); Cleanup: return( hr ); } //+---------------------------------------------------------------------------- // // Member : Load // // Synopsis : Implementation of the IHTMLPersistData method // //----------------------------------------------------------------------------- HRESULT CPersistDataPeer::load(IUnknown * pUnk, long lType, VARIANT_BOOL *pfContinue) { HRESULT hr; BSTR bstrEvent; if (!pfContinue) { hr = E_POINTER; goto Cleanup; } *pfContinue = VB_TRUE; // cache the OM pointers InitOM(pUnk, lType, INIT_USE_CACHED); bstrEvent = SysAllocString(L"onload"); if (!bstrEvent) { hr = E_OUTOFMEMORY; goto Cleanup; } // allow event handlers the chance to load from the cach hr = (FireEvent(bstrEvent, pfContinue, FALSE)); SysFreeString(bstrEvent); Cleanup: return( hr ); } //+-------------------------------------------------------------------------------- // // Member : FireEvent // // Synopsis : helper method to fire the persistence events // //--------------------------------------------------------------------------------- HRESULT CPersistDataPeer::FireEvent(BSTR bstrEvent, VARIANT_BOOL * pfContinue, BOOL fIsSaveEvent) { IHTMLEventObj * pEventObj = NULL; IHTMLEventObj2 * pEO2 = NULL; LONG lCookie; HRESULT hr = E_PENDING; VARIANT varRet; if (!_pPeerSiteOM) goto Cleanup; VariantInit(&varRet); // create an event object hr = _pPeerSiteOM->CreateEventObject(&pEventObj); if (hr || !pEventObj) goto Cleanup; // Now populate the event object with whatever properties are // appropriate for this event: hr = pEventObj->QueryInterface(IID_IHTMLEventObj2, (void**)&pEO2); if (hr==S_OK) { BSTR bstrEventType = SysAllocString( (fIsSaveEvent) ? L"save" : L"load" ); // we need to set the event type, the event type is either // "load" or "save". if (bstrEventType) { pEO2->put_type( bstrEventType ); SysFreeString(bstrEventType); } } // get the event cookie to fire the event hr = _pPeerSiteOM->GetEventCookie (bstrEvent, &lCookie); if (hr) goto Cleanup; hr = _pPeerSiteOM->FireEvent (lCookie, pEventObj); if (pfContinue) { hr = pEventObj->get_returnValue(&varRet); if (!hr) *pfContinue = ((V_VT(&varRet) == VT_BOOL) && (V_BOOL(&varRet) == VB_FALSE))? VB_FALSE : VB_TRUE; } Cleanup: VariantClear(&varRet); ReleaseInterface(pEventObj); ReleaseInterface(pEO2); return ( hr ); } //+----------------------------------------------------------------------- // // Member : ClearOMInterfaces () // // Synopsis : this helper function is called after the Save/Load persistenceCache // operations are finished, and is responsible for freeing up any of hte OM // interfaces that were cached. // //------------------------------------------------------------------------ void CPersistDataPeer::ClearOMInterfaces() { ClearInterface(&_pRoot); ClearInterface(&_pInnerXMLDoc); } //--------------------------------------------------------------------------- // // Member: CPersistDataPeer::InitOM // // Synopsis: IHTMLPersistData OM method implementation // //--------------------------------------------------------------------------- HRESULT CPersistDataPeer::InitOM(IUnknown * pUnk, long lType, DWORD dwFlags /* ==0*/) { HRESULT hr = S_OK; IHTMLElement * pPeerElement = NULL; long lElemID = -1; BSTR bstrTag =NULL; CBufferedStr cbsID; IXMLDOMNodeList * pChildren = NULL; IXMLDOMElement * pNewChild = NULL; IXMLDOMNode * pSubTree = NULL; if (!pUnk) return E_INVALIDARG; if (_pInnerXMLDoc) { if (dwFlags & INIT_USE_CACHED) goto Cleanup; else ClearOMInterfaces(); } // make sure we know what we have been given. hr = (pUnk->QueryInterface(IID_IXMLDOMDocument, (void**)&_pInnerXMLDoc)); if (hr) goto Cleanup; // find the data value... hr = _pInnerXMLDoc->get_documentElement( &_pRoot ); if (hr) goto Cleanup; // but in most cases the xmlObject is a big bucket, and we would like // to set the _pRoot to the subtree associated with our own _pPeerElement. // if this root doesn't exist, create it. we try to find an ID first, if // if it is not there, then create a unique name for this child subtree // but appending the srcID to the tagName. _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) goto Cleanup; hr = pPeerElement->get_id(&bstrTag); if (hr || !bstrTag || !SysStringLen(bstrTag)) { SysFreeString(bstrTag); hr = pPeerElement->get_sourceIndex(&lElemID); if (hr) goto Cleanup; hr = pPeerElement->get_tagName(&bstrTag); if (hr) goto Cleanup; hr = cbsID.QuickAppend(bstrTag); SysFreeString(bstrTag); if (hr) goto Cleanup; hr = cbsID.QuickAppend(lElemID); if (hr) goto Cleanup; bstrTag = SysAllocString(cbsID); } if (!bstrTag) { hr = E_OUTOFMEMORY; goto Cleanup; } // now we can actually see if there is a child of this name // We need to do this by looping over the childNode collection // and looking for one with our name. //-------------------------------------------------------------- hr = _pRoot->get_childNodes(&pChildren); if (FAILED(hr)) goto Cleanup; if (hr == S_OK) { long i, iLen; hr = pChildren->get_length(&iLen); if (FAILED(hr)) goto Cleanup; for (i=0; i < iLen; i++) { IXMLDOMNode * pTemp = NULL; BSTR bstrNodeName = NULL; BOOL fEqual = FALSE; hr= pChildren->get_item(i, &pTemp); if (FAILED(hr)) break; hr = pTemp->get_nodeName( &bstrNodeName ); if (FAILED(hr)) break; fEqual = ! _wcsicmp(bstrTag, bstrNodeName); SysFreeString(bstrNodeName); if (fEqual) { // transfer ownership pSubTree = pTemp; break; } ClearInterface( &pTemp ); } } if(pSubTree) { // yes there's a child so use it, we have a domnode so we need to // qi for the element ClearInterface(&_pRoot); hr = pSubTree->QueryInterface(IID_IXMLDOMElement, (void**)&_pRoot); } else { // no child yet so lets create one. hr = _pInnerXMLDoc->createElement(bstrTag, &pNewChild); if (hr || ! pNewChild) goto Cleanup; hr = _pRoot->appendChild(pNewChild, NULL); if (hr) goto Cleanup; ClearInterface(&_pRoot); _pRoot=pNewChild; pNewChild = NULL; // transfer ownership } Cleanup: ReleaseInterface(pPeerElement); SysFreeString(bstrTag); ReleaseInterface(pChildren); ReleaseInterface(pSubTree); ReleaseInterface(pNewChild); return( hr ); } //--------------------------------------------------------------------------- // // Member: CPersistDataPeer::getAttribute // // Synopsis: IHTMLPersistData OM method implementation // //--------------------------------------------------------------------------- HRESULT CPersistDataPeer::getAttribute (BSTR strName, VARIANT * pvarValue ) { HRESULT hr = S_OK; if (!pvarValue) { hr = E_POINTER; goto Cleanup; } VariantClear(pvarValue); if (!strName) { hr = E_INVALIDARG; goto Cleanup; } hr = InitOM(); if (hr) { // no OM pointers so fail silently hr = S_OK; goto Cleanup; } // get the child of the root that has the name strName if (_pRoot) { hr = _pRoot->getAttribute(strName, pvarValue); if (hr ==S_FALSE) hr = S_OK; } Cleanup: return hr ; } //--------------------------------------------------------------------------- // // Member: CPersistDataPeer::setAttribute // // Synopsis: IHTMLPersistData OM method implementation // //--------------------------------------------------------------------------- HRESULT CPersistDataPeer::setAttribute (BSTR strName, VARIANT varValue) { HRESULT hr = S_OK; if (!strName) { hr = E_INVALIDARG; goto Cleanup; } hr = InitOM(); if (hr) { // no OM pointers so fail silently hr = S_OK; goto Cleanup; } // save this value as an attribute on the root. if (_pRoot) { // TODO - look into just passing the variant into the xml object // and let them worry about all the types... We do the processing below // to be safe for now. VARIANT * pvar = NULL; CVariant cvarTemp; if ((V_VT(&varValue)==VT_BSTR) || V_VT(&varValue)==(VT_BYREF|VT_BSTR)) { pvar = (V_VT(&varValue) & VT_BYREF) ? V_VARIANTREF(&varValue) : &varValue; } else if ((V_VT(&varValue)==VT_BOOL || V_VT(&varValue)==(VT_BYREF|VT_BOOL))) { // sadly, do our own bool conversion... VARIANT_BOOL vbFlag = (V_VT(&varValue)==VT_BOOL) ? V_BOOL(&varValue) : V_BOOL( V_VARIANTREF(&varValue) ); V_VT(&cvarTemp) = VT_BSTR; V_BSTR(&cvarTemp) = vbFlag ? SysAllocString(L"true") : SysAllocString(L"false"); pvar = & cvarTemp; } else { pvar = &varValue; hr = VariantChangeTypeEx(pvar, pvar, LCID_SCRIPTING, 0, VT_BSTR); if (hr) goto Cleanup; } hr = _pRoot->setAttribute(strName, *pvar); if (hr ==S_FALSE) hr = S_OK; } Cleanup: return hr; } //--------------------------------------------------------------------------- // // Member: CPersistDataPeer::removeDataValue // // Synopsis: IHTMLPersistData OM method implementation // //--------------------------------------------------------------------------- HRESULT CPersistDataPeer::removeAttribute (BSTR strName) { HRESULT hr = S_OK; if (!strName) { hr = E_INVALIDARG; goto Cleanup; } hr = InitOM(); if (hr) { // no OM pointers so fail silently hr = S_OK; goto Cleanup; } // get the child of the root that has the name strName if (_pRoot) { hr = _pRoot->removeAttribute(strName); if (hr ==S_FALSE) hr = S_OK; } Cleanup: return hr ; } //--------------------------------------------------------------------------- // // Member: CPersistDataPeer::get_XMLDocument // // Synopsis: IHTMLPersistData OM proeprty implementation. this is the default // property for this object, and as such it exposes the XMLOM // of the user data. // //--------------------------------------------------------------------------- HRESULT CPersistDataPeer::get_XMLDocument (IDispatch ** ppDisp) { HRESULT hr = S_OK; if (!ppDisp) { hr = E_POINTER; goto Cleanup; } *ppDisp = NULL; hr = InitOM(); if (hr) { // no OM pointers so fail silently hr = S_OK; goto Cleanup; } if (_pInnerXMLDoc) { hr = _pInnerXMLDoc->QueryInterface(IID_IDispatch, (void**)ppDisp); } Cleanup: return hr; } //+----------------------------------------------------------- // // Member : GetSaveCategory // // Synopsis : this helper function turns the tagName fo the // pPeerElement, into the category for its save operations. // //+----------------------------------------------------------- ENUM_SAVE_CATEGORY CPersistDataPeer::GetSaveCategory() { HRESULT hr = S_OK; CVariant cvarTag; IHTMLElement * pPeerElement = NULL; ENUM_SAVE_CATEGORY escRet = ESC_UNKNOWN; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) goto Cleanup; V_VT(&cvarTag) = VT_BSTR; hr = pPeerElement->get_tagName(&V_BSTR(&cvarTag)); if (hr) goto Cleanup; // there's got to be a better way to do this, but for // now I will try to put the most common ones in front. if (0==_wcsicmp(V_BSTR(&cvarTag), L"input")) { // but wait!!!! don't save password type inputs BSTR bstrType = SysAllocString(L"type"); CVariant cvarVal; escRet = ESC_INTRINSIC; if (!bstrType) goto Cleanup; hr = (pPeerElement->getAttribute(bstrType, 0, &cvarVal)); SysFreeString(bstrType); if (hr) goto Cleanup; if (V_VT(&cvarVal) == VT_BSTR && 0==_wcsicmp(V_BSTR(&cvarVal), L"password")) { escRet = ESC_PASSWORD; goto Cleanup; } } else if (0==_wcsicmp(V_BSTR(&cvarTag), L"script")) { escRet = ESC_SCRIPT; } else if (0==_wcsicmp(V_BSTR(&cvarTag), L"select") || 0==_wcsicmp(V_BSTR(&cvarTag), L"textarea") || 0==_wcsicmp(V_BSTR(&cvarTag), L"richtext") || 0==_wcsicmp(V_BSTR(&cvarTag), L"button") || 0==_wcsicmp(V_BSTR(&cvarTag), L"fieldset") ) { escRet = ESC_INTRINSIC; } else if (0==_wcsicmp(V_BSTR(&cvarTag), L"object") || 0==_wcsicmp(V_BSTR(&cvarTag), L"embed") || 0==_wcsicmp(V_BSTR(&cvarTag), L"applet") ) { escRet = ESC_CONTROL; } Cleanup: ReleaseInterface(pPeerElement); return escRet; } //+----------------------------------------------------------- // // member : GetEngineClsidForLanguage () // // synopsis : another helper method that returns the CLSID of the // scripdEngine associated with the language progID. // //+----------------------------------------------------------- HRESULT CPersistDataPeer::GetEngineClsidForLanguage(CLSID * pclsid, IHTMLDocument2 * pBrowseDoc) { HRESULT hr = S_OK; BSTR bstrLanguage = NULL; IHTMLElement * pPeerElement = NULL; if (!pclsid) return E_POINTER; *pclsid = IID_NULL; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } // what language script engine to instantiate? hr = (pPeerElement->get_language(&bstrLanguage)); if (hr) goto Cleanup; if (!bstrLanguage || !SysStringLen(bstrLanguage)) { IHTMLElementCollection * pCollection = NULL; hr = E_FAIL; if (bstrLanguage) { SysFreeString(bstrLanguage); bstrLanguage = NULL; } // use the default language, we get this from the language of // first script block in the document. if (!FAILED(pBrowseDoc->get_scripts(&pCollection))) { CVariant cvarID; CVariant cvarEmpty; IDispatch * pDisp; V_VT(&cvarID) = VT_I4; V_I4(&cvarID) = 0; if (!FAILED(pCollection->item(cvarID, cvarEmpty, &pDisp))) { IHTMLElement * pFirstScript = NULL; if (pDisp && !FAILED(pDisp->QueryInterface(IID_IHTMLElement, (void**)&pFirstScript))) { pFirstScript->get_language(&bstrLanguage); if (bstrLanguage && SysStringLen(bstrLanguage)) hr = CLSIDFromProgID ( bstrLanguage, pclsid ); ReleaseInterface(pFirstScript); } ReleaseInterface(pDisp); } ReleaseInterface(pCollection); } if (hr) { hr = CLSIDFromProgID ( L"JScript", pclsid ); } } else { hr = CLSIDFromProgID ( bstrLanguage, pclsid ); } Cleanup: SysFreeString(bstrLanguage); ReleaseInterface(pPeerElement); return ( hr ); } //+----------------------------------------------------------- // // Member GetScriptEngine // // Synopsis : helper method - this creates the appropriate script // engine, and parses in the text of hte script block that we // are interested in. // //+----------------------------------------------------------- IActiveScript * CPersistDataPeer::GetScriptEngine(IHTMLDocument2 * pBrowseDoc, ULONG * puFlags) { HRESULT hr = S_OK; BSTR bstrCode = NULL; CLSID clsID; CLSID clsIDTarget; IActiveScript * pScriptEngine = NULL; IActiveScriptSite * pScriptSite = NULL; IActiveScriptParse * pASP = NULL; IHTMLScriptElement * pScriptElem = NULL; IHTMLElement * pPeerElement = NULL; hr = (GetEngineClsidForLanguage(&clsID, pBrowseDoc)); if (hr) goto Cleanup; // set the return flags, so that these tests only need to be done once // some callers need to know if the script block is jscript, vbscript, // of something else if (!FAILED(CLSIDFromProgID ( _T("JScript"), &clsIDTarget )) && (clsID == clsIDTarget)) { *puFlags = SCRIPT_ENGINE_JSCRIPT; } else if (!FAILED(CLSIDFromProgID ( _T("VBScript"), &clsIDTarget )) && (clsID == clsIDTarget)) { *puFlags = SCRIPT_ENGINE_VBSCRIPT; } else *puFlags = SCRIPT_ENGINE_OTHER; // create the script engine hr = CoCreateInstance( clsID, NULL, CLSCTX_INPROC_SERVER, IID_IActiveScript,(void **)&pScriptEngine); if ( hr ) goto Cleanup; // get the scriptSite interface from ourselves. hr = (QueryInterface(IID_IActiveScriptSite, (void**) &pScriptSite)); if (hr) goto Cleanup; hr = pScriptEngine->SetScriptSite(pScriptSite); ReleaseInterface(pScriptSite); // engine should have addref'd if ( FAILED(hr) ) goto Error; // prepare to load out text hr = (pScriptEngine->QueryInterface (IID_IActiveScriptParse, (void**)&pASP)); if ( hr ) goto Error; hr = (pASP->InitNew()); if ( hr ) goto Error; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } // get the text of the script element hr = (pPeerElement->QueryInterface(IID_IHTMLScriptElement, (void**)&pScriptElem)); if ( hr ) goto Error; hr = (pScriptElem->get_text(&bstrCode)); if ( hr || !bstrCode) goto Error; hr = pASP->ParseScriptText( bstrCode, NULL, // Item Name (Namespace) NULL, // context NULL, // delimiter 0, // srcContext Cookie 0, // ulStarting Line SCRIPTTEXT_ISVISIBLE, //dwFlags NULL, // pVarResult NULL ); // pException Info if ( hr ) goto Error; hr = pScriptEngine->SetScriptState(SCRIPTSTATE_CONNECTED); if ( FAILED(hr) ) goto Error; Cleanup: SysFreeString(bstrCode); ReleaseInterface(pPeerElement); ReleaseInterface(pScriptElem); ReleaseInterface(pASP); return pScriptEngine; Error: ClearInterface(&pScriptEngine); goto Cleanup; } //+----------------------------------------------------------- // // Member BuildNewScriptBlock // // Synopsis : helper method, this does the real work of snapshotting a // script block. IF the script is VBSCRIPT or JSCRIPT the appropriate // syntax is used. for any other language, we convert to JScript. // //+----------------------------------------------------------- HRESULT CPersistDataPeer::BuildNewScriptBlock(CBufferedStr * pstrBuffer, ULONG *puFlags) { HRESULT hr = S_OK; IDispatch * pScriptNameSpace = NULL; IDispatchEx * pDispScript = NULL; IDispatchEx * pBrowseWin = NULL; DISPID dispidName; BSTR bstrName = NULL; IHTMLWindow2 * pWin = NULL; IActiveScript * pScriptEngine = NULL; IDispatch * pdispBrowseDoc = NULL; IHTMLDocument2 * pBrowseDoc = NULL; IHTMLElement * pPeerElement = NULL; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } hr = (pPeerElement->get_document(&pdispBrowseDoc)); if (hr) goto Cleanup; hr = (pdispBrowseDoc->QueryInterface(IID_IHTMLDocument2, (void**)&pBrowseDoc)); if (hr) goto Cleanup; pScriptEngine = GetScriptEngine(pBrowseDoc, puFlags); if (!pScriptEngine) goto Cleanup; // fail silently // loop over all the dispatches in the script engine's name // space and get thier values.from..pSrcDoc hr = pScriptEngine->GetScriptDispatch( NULL, &pScriptNameSpace ); if ( hr ) goto Cleanup; hr = (pScriptNameSpace->QueryInterface( IID_IDispatchEx, (void**)&pDispScript)); if ( hr ) goto Cleanup; // inorder to derefernce the script variables we need the browse // documents window. hr = (pBrowseDoc->get_parentWindow(&pWin)); if(hr) goto Cleanup; hr = (pWin->QueryInterface(IID_IDispatchEx, (void**)&pBrowseWin)); if (hr) goto Cleanup; // now run through all the objects in this script engine and // find their values hr = (pDispScript->GetNextDispID(0, DISPID_STARTENUM, &dispidName)); if (hr) goto Cleanup; while (dispidName) { hr = (pDispScript->GetMemberName(dispidName, &bstrName)); if (!hr && bstrName) { DISPID dispidBrowser; if (!FAILED(pBrowseWin->GetDispID(bstrName, fdexNameCaseSensitive, &dispidBrowser))) { DISPPARAMS dp; CVariant cvarRes; BOOL fNeedsQuotesInScript = FALSE; dp.rgvarg = NULL; dp.rgdispidNamedArgs = NULL; dp.cArgs = 0; dp.cNamedArgs = 0; hr = (pBrowseWin->Invoke(dispidBrowser, IID_NULL, LCID_SCRIPTING, DISPATCH_PROPERTYGET, &dp, &cvarRes, NULL, NULL)); fNeedsQuotesInScript = (V_VT(&cvarRes) == VT_BSTR); // Filter out VT_IDISPATCH & VT_UNKNOWN if (!hr && V_VT(&cvarRes) != VT_DISPATCH && V_VT(&cvarRes) != VT_UNKNOWN && !FAILED(cvarRes.CoerceVariantArg(VT_BSTR))) { // assume JSCript if not VBScript LPTSTR pstrAssign = (*puFlags & SCRIPT_ENGINE_VBSCRIPT) ? _T("\n\r dim ") : _T("\n\r var "); // concatenate on the end of the buffer. // "\n\r var = " "; " hr = pstrBuffer->QuickAppend(pstrAssign); if (hr) goto Cleanup; hr = pstrBuffer->QuickAppend(bstrName); if (hr) goto Cleanup; // if vbscript, put assignment on its own line. if (*puFlags & SCRIPT_ENGINE_VBSCRIPT) { hr = pstrBuffer->QuickAppend(_T("\n\r ")); if (hr) goto Cleanup; hr = pstrBuffer->QuickAppend(bstrName); if (hr) goto Cleanup; } // now handle outputing the assignment itself, careful // to only put quotes around things that were strings. hr = pstrBuffer->QuickAppend(_T(" = ")); if (hr) goto Cleanup; if (fNeedsQuotesInScript ) { hr = pstrBuffer->QuickAppend(_T("\"")); if (hr) goto Cleanup; } if (V_VT(&cvarRes) != VT_EMPTY) { hr = pstrBuffer->QuickAppend(V_BSTR(&cvarRes)); } else { // someone had dim x = Empty in their script // or var x = document.expando // and this doesn't coerc to a bstr (returned S_FALSE) // don't reset the use quotes flag, // for jscript convert to null, for vb if (!(*puFlags & SCRIPT_ENGINE_VBSCRIPT)) { hr = pstrBuffer->QuickAppend(_T("null")); } else hr = pstrBuffer->QuickAppend(_T("Empty")); } if (hr) goto Cleanup; if (fNeedsQuotesInScript ) { hr = pstrBuffer->QuickAppend(_T("\"")); if (hr) goto Cleanup; } // VBSCRIPT doesn need ';' at the eol if (!(*puFlags & SCRIPT_ENGINE_VBSCRIPT)) { hr = pstrBuffer->QuickAppend(_T(";")); } if (hr) goto Cleanup; } } SysFreeString(bstrName); bstrName = NULL; } hr = (pDispScript->GetNextDispID(0, dispidName, &dispidName)); if (hr==S_FALSE) { hr = S_OK; break; } else if (hr) goto Cleanup; } Cleanup: if (pScriptEngine) pScriptEngine->Close(); if (bstrName) SysFreeString(bstrName); ReleaseInterface(pPeerElement); ReleaseInterface(pBrowseDoc); ReleaseInterface(pScriptEngine); ReleaseInterface(pdispBrowseDoc); ReleaseInterface(pWin); ReleaseInterface(pBrowseWin); ReleaseInterface(pDispScript); ReleaseInterface(pScriptNameSpace); // no partial string in an error case... if (hr) pstrBuffer->Set(); return( hr ); } //+----------------------------------------------------------- // // member : SaveHandler_GenericTag // // synopsis : this does the generic handling when a tag has the // persistence peer. It simply gets the outerHTML and saves that // in the XML object. on the load the element will be hit // with a put_outerHTML of this string. // //+----------------------------------------------------------- HRESULT CPersistDataPeer::SaveHandler_GenericTag() { HRESULT hr = S_OK; CVariant cvarOuter; IHTMLElement * pPeerParent = NULL; IHTMLElement * pPeerElement = NULL; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } // for elements in the head, we don't want to do this since // there is a tree limitation that doesn't allow the outerHTML // to be set. hr = pPeerElement->get_parentElement(&pPeerParent); if (hr) goto Cleanup; // the right way to do this is to ask the question: // are we in the body/frameset, NOT is our parent the // head, so... while (pPeerParent) { CVariant cvarTag; IHTMLElement * pTemp = NULL; V_VT(&cvarTag) = VT_BSTR; hr = pPeerParent->get_tagName(&V_BSTR(&cvarTag)); if (hr) goto Cleanup; if (0==_wcsicmp(V_BSTR(&cvarTag), L"body") || 0==_wcsicmp(V_BSTR(&cvarTag), L"frameset")) break; hr = pPeerParent->get_parentElement(&pTemp); if (hr) goto Cleanup; ClearInterface(&pPeerParent); pPeerParent = pTemp; } if (!pPeerParent) goto Cleanup; hr = pPeerElement->get_outerHTML(&V_BSTR(&cvarOuter)); if (hr) goto Cleanup; V_VT(&cvarOuter) = VT_BSTR; // now save the outerHTML string hr = _pRoot->setAttribute(_T("__NEW_TAG_OUTER"), cvarOuter); if (hr) goto Cleanup; Cleanup: ReleaseInterface(pPeerElement); ReleaseInterface(pPeerParent); return( hr ); } //+---------------------------------------------------------------------------- // // Member : LoadHandler_GenericTag() // // Synopsis : this restores the outerHTML of the tag as it is loaded // //+---------------------------------------------------------------------------- HRESULT CPersistDataPeer::LoadHandler_GenericTag() { HRESULT hr = S_OK; CVariant cvarOuter; IHTMLElement * pPeerElement = NULL; if (!_pRoot) goto Cleanup; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } // now get the outerHTML string hr = _pRoot->getAttribute(_T("__NEW_TAG_OUTER"), &cvarOuter); if (hr) goto Cleanup; hr = cvarOuter.CoerceVariantArg(VT_BSTR); if (hr) goto Cleanup; hr = pPeerElement->put_outerHTML(V_BSTR(&cvarOuter)); if (hr) goto Cleanup; Cleanup: ReleaseInterface(pPeerElement); return( hr ); } //+------------------------------------------------------------------------------ // // Member : SaveHandler_ScriptTag() // // Synopsis : saves the script into the xml store. We do this in the standard way: // 1- create a private script engin, // 2- load it with the text of the peer's script block // 3 - walk through its namespace pulling out the variables // 4 - create a list of name value pairs as the new script block // 5 - save this block in the appropriate place // //+------------------------------------------------------------------------------ HRESULT CPersistDataPeer::SaveHandler_ScriptTag() { HRESULT hr = S_OK; CVariant cvarText; BSTR bstrLanguage = NULL; CLSID clsIDTarget; CLSID clsIDLang; CBufferedStr cbsNewScript; IDispatch * pdispBrowseDoc = NULL; IHTMLDocument2 * pBrowseDoc = NULL; IHTMLElement * pPeerElement = NULL; ULONG uFlags = 0; if (!_pRoot) goto Cleanup; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } hr = pPeerElement->get_document(&pdispBrowseDoc); if (hr) goto Cleanup; hr = pdispBrowseDoc->QueryInterface(IID_IHTMLDocument2, (void**)&pBrowseDoc); if (hr) goto Cleanup; // get the new script block, and the language that it // originated from hr = BuildNewScriptBlock(&cbsNewScript, &uFlags); if (hr) goto Cleanup; // Language can only be set in design mode, as a result // we can't change the script block since we don't know the // syntax for an arbitrary script engine. so, only save // script if this is javascript or vbscript. if (uFlags & SCRIPT_ENGINE_OTHER) goto Cleanup; V_VT(&cvarText) = VT_BSTR; V_BSTR(&cvarText) = SysAllocString((LPTSTR)cbsNewScript); if (!V_BSTR(&cvarText)) { hr = E_OUTOFMEMORY; goto Cleanup; } hr = _pRoot->setAttribute(_T("__NEW_SCRIPT_TEXT"), cvarText); if (hr) goto Cleanup; Cleanup: SysFreeString(bstrLanguage); ReleaseInterface(pPeerElement); ReleaseInterface(pBrowseDoc); ReleaseInterface(pdispBrowseDoc); return( hr ); } //+---------------------------------------------------------------------------- // // Member : LoadHandler_ScriptTag() // // Synopsis : does the load - but wait... // NYI - put_text is design time only, so the peer needs a different way // of sliding this in. // //+---------------------------------------------------------------------------- HRESULT CPersistDataPeer::LoadHandler_ScriptTag() { HRESULT hr = S_OK; CVariant cvarText; if (!_pRoot) goto Cleanup; // try to get the new text from the saved XML hr = _pRoot->getAttribute(_T("__NEW_SCRIPT_TEXT"), &cvarText); if (hr) goto Cleanup; // if there is script text, then we need to set this into the // browse window's namespace Cleanup: if (hr == S_FALSE) hr = S_OK; return( hr ); } //=============================================================== // // Class : CPersistShortcut // // synopsis : derives from CPersistDataPeer and does the special handling // related to the shortcut functionality // //=============================================================== //+---------------------------------------------------------------- // // Member : save // // synopsis: over ride of parent functionality to deal with shortcut // specific stuff... // //----------------------------------------------------------------- HRESULT CPersistShortcut::save(IUnknown * pUnk, long lType, VARIANT_BOOL *pfContinue) { HRESULT hr; if (lType != (long)htmlPersistStateFavorite) { hr = S_OK; goto Cleanup; } hr = super::save(pUnk, lType, pfContinue); if (hr || (*pfContinue == VB_FALSE)) goto Cleanup; // do the particular tag handling related to shortcuts switch (GetSaveCategory()) { case ESC_UNKNOWN: case ESC_INTRINSIC: case ESC_CONTROL: hr = SaveHandler_GenericTag(); break; // case ESC_SCRIPT: // hr = SaveHandler_ScriptTag(); // break; default: break; } Cleanup: return hr; } //+---------------------------------------------------------------------------- // // Member : Load // // Synopsis : Implementation of the IHTMLPersistenceCache method // //----------------------------------------------------------------------------- HRESULT CPersistShortcut::load(IUnknown * pUnk, long lType, VARIANT_BOOL *pfDoDefault) { HRESULT hr; if (lType != (long)htmlPersistStateFavorite) { hr = S_OK; goto Cleanup; } // fire the event first, since the pPeerElement is about to be // removed from the tree entirely. Of course, if the user sets // expandoes on this, they will get lost, but the alternative is to // loose access to the xmlcache. So, if you think about it, they get // outerHTML behaviour, and using XMLCache behavior means // that you better cancel default behavior. hr = super::load(pUnk, lType, pfDoDefault); // is default load behavior canceled ? if (hr || (*pfDoDefault == VB_FALSE)) goto Cleanup; switch (GetSaveCategory()) { case ESC_UNKNOWN: case ESC_INTRINSIC: case ESC_CONTROL: hr = LoadHandler_GenericTag(); break; // case ESC_SCRIPT: // hr = LoadHandler_ScriptTag(); // break; default: break; } Cleanup: return hr; } //=============================================================== // // Class : CPersistHistory // // synopsis : derives from CPersistDataPeer and does the special handling // related to the History functionality // //=============================================================== //+---------------------------------------------------------------- // // Member : save // // synopsis: over ride of parent functionality to deal with shortcut // specific stuff... // //----------------------------------------------------------------- HRESULT CPersistHistory::save(IUnknown * pUnk, long lType, VARIANT_BOOL *pfContinue) { HRESULT hr; if (lType != (long)htmlPersistStateHistory) { hr = S_OK; goto Cleanup; } hr = super::save(pUnk, lType, pfContinue); if (hr || (*pfContinue == VB_FALSE)) goto Cleanup; // do any tag specific handling that history needs switch (GetSaveCategory()) { // if we don't know anything special to do with this tag, just // transfer over its outerHTML on the assumption that the author // knows what they are doing case ESC_UNKNOWN: case ESC_INTRINSIC: hr = SaveHandler_GenericTag(); break; // case ESC_SCRIPT: // hr = SaveHandler_ScriptTag(); // break; default : break; } Cleanup: return hr; } //+---------------------------------------------------------------------------- // // Member : Load // // Synopsis : Implementation of the IHTMLPersistenceCache method // //----------------------------------------------------------------------------- HRESULT CPersistHistory::load(IUnknown * pUnk, long lType, VARIANT_BOOL *pfDoDefault) { HRESULT hr; if (lType != (long)htmlPersistStateHistory) { hr = S_OK; goto Cleanup; } // cache the OM pointers hr = InitOM(pUnk, lType); if (hr) goto Cleanup; hr = super::load(pUnk, lType, pfDoDefault); // is default load behavior canceled ? if (hr || (*pfDoDefault == VB_FALSE)) goto Cleanup; // ISSUE (carled) NYI - script retoration presents a unique challenge // because this needs to execute inline instead of the original // script block text. switch(GetSaveCategory()) { // case ESC_SCRIPT: // hr = LoadHandler_ScriptTag(); // break; case ESC_UNKNOWN: case ESC_INTRINSIC: hr = LoadHandler_GenericTag(); break; default: break; } Cleanup: return hr; } //=============================================================== // // Class : CPersistSnapshot // // synopsis : derives from CPersistDataPeer and does the special handling // related to the run-time same (snapshot) functionality // //=============================================================== //+---------------------------------------------------------------- // // Member : CPersistSnapshot::save // // synopsis: over ride of parent functionality to deal with shortcut // specific stuff... // Since it doesn't make much sense to allow theXMLOM to be acccessed // in this case, calling super:: pretty much only fires the event. // //----------------------------------------------------------------- HRESULT CPersistSnapshot::save(IUnknown * pUnk, long lType, VARIANT_BOOL *pfContinue) { HRESULT hr; CVariant cvarTag; IHTMLDocument2 * pDesignDoc = NULL; if (lType != (long)htmlPersistStateSnapshot) { hr = S_OK; goto Cleanup; } if (!pUnk) { hr = E_POINTER; goto Cleanup; } hr = pUnk->QueryInterface(IID_IHTMLDocument2, (void**)&pDesignDoc); if (hr) goto Cleanup; // allow the onsave event to be fired. hr = super::save(NULL, lType, pfContinue); if (hr || (*pfContinue == VB_FALSE)) goto Cleanup; // now that the author has had the oppurtunity to set expandoes, // and value properties, we start the real work of snapshot saveing. switch (GetSaveCategory()) { case ESC_CONTROL: hr = TransferControlValues(pDesignDoc); break; case ESC_SCRIPT: hr = TransferScriptValues(pDesignDoc); break; case ESC_UNKNOWN: // if we don't know anything special to do with this tag, just // transfer over its outerHTML on the assumption that the author // knows what they are doing case ESC_INTRINSIC: hr = TransferIntrinsicValues(pDesignDoc); break; default: break; } Cleanup: ReleaseInterface(pDesignDoc); return hr; } //+---------------------------------------------------------------------------- // // Member : CPersistSnapshot::Load // // Synopsis : Implementation of the IHTMLPersistenceCache method // Since it doesn't make much sense to allow theXMLOM to be acccessed // in this case, calling super:: pretty much only fires the event. // //----------------------------------------------------------------------------- HRESULT CPersistSnapshot::load(IUnknown * pUnk, long lType, VARIANT_BOOL *pfDoDefault) { HRESULT hr; if (lType != (long)htmlPersistStateSnapshot) { hr = S_OK; goto Cleanup; } hr = super::load(pUnk, lType, pfDoDefault); Cleanup: return hr; } //+----------------------------------------------------------- // // member : GetDesignElem // // Synposis : this helper function is responsible for finding the // design document's element counterpart to our pPeerElement. // //+----------------------------------------------------------- IHTMLElement * CPersistSnapshot::GetDesignElem(IHTMLDocument2 * pDesignDoc) { HRESULT hr = S_OK; CVariant cvarID; CVariant cvarEmpty; IDispatch * pDisp = NULL; IHTMLElement * pRetElem = NULL; IHTMLElement * pPeerElement = NULL; IHTMLElementCollection * pCollection = NULL; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } V_VT(&cvarID) = VT_BSTR; hr = pPeerElement->get_id(&V_BSTR(&cvarID)); if (hr) goto Cleanup; // first get the documents all collection. hr = pDesignDoc->get_all(&pCollection); if (hr) goto Cleanup; // now find out elements coutner part inthe design document, // if it exits. Elements that were created by inline scripts, // or other script driven means, may not be found. hr = pCollection->item(cvarID, cvarEmpty, &pDisp); if (hr || !pDisp) goto Cleanup; // make sure that we do not have a collection... hr = pDisp->QueryInterface(IID_IHTMLElement, (void**)&pRetElem); Cleanup: ReleaseInterface(pPeerElement); ReleaseInterface(pDisp); ReleaseInterface(pCollection); return pRetElem; } //+----------------------------------------------------------- // // member : TransferIntrinsicValues // // synopsis : this transfers the value of the browse-control to the // design control by setting the outerHTML of the design control // to that of the browseControl // //+----------------------------------------------------------- HRESULT CPersistSnapshot::TransferIntrinsicValues (IHTMLDocument2 *pDesignDoc) { HRESULT hr = S_OK; BSTR bstrOuter = NULL; IHTMLElement * pDesignElem = NULL; IHTMLElement * pPeerElement = NULL; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } hr = pPeerElement->get_outerHTML(&bstrOuter); if (hr) goto Cleanup; pDesignElem = GetDesignElem(pDesignDoc); if (!pDesignElem) goto Cleanup; hr = pDesignElem->put_outerHTML(bstrOuter); Cleanup: SysFreeString(bstrOuter); ReleaseInterface(pPeerElement); ReleaseInterface(pDesignElem); return( hr ); } //+----------------------------------------------------------- // // Member : TransferControlValues // // Synopsis : Transfers the values of the control by cooking up a // stream and calling IPersitHistory::Save on the browse-control // and then feeding that stream into IPersistHistory::Load on // the design-control, Then when the design document is saved, // IPersistPropertyBag::save will get called (it is this that finally // allows the controls state to be written out as the altHTML attribute. // //+----------------------------------------------------------- HRESULT CPersistSnapshot::TransferControlValues (IHTMLDocument2 *pDesignDoc) { HRESULT hr = S_OK; IHTMLElement * pDesignElem = NULL; IHTMLElement * pPeerElement = NULL; IPersistHistory * pSrcPH = NULL; IPersistHistory * pDesignPH = NULL; IStream * pStream = NULL; HGLOBAL hg = NULL; static LARGE_INTEGER i64Zero = {0, 0}; _pPeerSite->GetElement(&pPeerElement); if (!pPeerElement) { hr = E_FAIL; goto Cleanup; } pDesignElem = GetDesignElem(pDesignDoc); if (!pDesignElem) goto Cleanup; // step 1. get the appropriate IPersistHistory interfaces. hr = pPeerElement->QueryInterface(IID_IPersistHistory, (void**)&pSrcPH); if (hr) goto Cleanup; hr = (pDesignElem->QueryInterface(IID_IPersistHistory, (void**)&pDesignPH)); if (hr) goto Cleanup; // step 2. create a temporary stream hg = GlobalAlloc(GMEM_MOVEABLE, 0); if (!hg) { hr = E_OUTOFMEMORY; goto Cleanup; } hr = (CreateStreamOnHGlobal(hg, TRUE, &pStream)); if (hr) goto Cleanup; // Step 3. save the browse object hr = (pSrcPH->SaveHistory(pStream)); if (hr) goto Cleanup; // Step 4. reset the stream hr = (pStream->Seek(i64Zero, STREAM_SEEK_SET, NULL)); if (hr) goto Cleanup; // Stpe 5. Load the design object. hr = (pDesignPH->LoadHistory(pStream, NULL)); if (hr) goto Cleanup; Cleanup: // ISSUE (carled) is there a memory leak here? the hGlobal was allocated, // a stream was created, and freed but where does the memory get freed? ReleaseInterface(pPeerElement); ReleaseInterface(pStream); ReleaseInterface(pSrcPH); ReleaseInterface(pDesignPH); ReleaseInterface(pDesignElem); return( (hr == E_NOINTERFACE)? S_OK : hr ); } //+----------------------------------------------------------- // // Member : TransferScriptValues // // Synopsis: transfers the values of script blocks, by loading this // script into a private script engine, and then iterating over all // the values and getting the current values from the browse document. // These (Variable, Value) pairs are then spit out as the new innerHTML // of the design-script element. // //+----------------------------------------------------------- HRESULT CPersistSnapshot::TransferScriptValues (IHTMLDocument2 *pDesignDoc) { HRESULT hr = S_OK; IHTMLElement * pDesignElem = NULL; BSTR bstrNewBlock = NULL; CBufferedStr cbsNewScript; ULONG uFlags = 0; IHTMLScriptElement * pScriptElem = NULL; pDesignElem = GetDesignElem(pDesignDoc); if (!pDesignElem) goto Cleanup; // get the text of the new script block hr = (BuildNewScriptBlock(&cbsNewScript, &uFlags)); if (hr) goto Cleanup; bstrNewBlock = SysAllocString((LPTSTR)cbsNewScript); if (!bstrNewBlock) { hr = E_OUTOFMEMORY; goto Cleanup; } // Set the new script assignments hr = (pDesignElem->QueryInterface(IID_IHTMLScriptElement, (void**)&pScriptElem)); if ( hr ) goto Cleanup; hr = (pScriptElem->put_text(bstrNewBlock)); if ( hr ) goto Cleanup; // since all the namespaces exist on the "window" // we need to make sure that the script block we // write out is in a language whose syntax we know. As // a result a script block from any ActiveScript language // will get persisted, but unless it is VBScript or JScript, // in the saveing, it is translated into JScript. if (uFlags & SCRIPT_ENGINE_OTHER) { BSTR bstrForceLanguage = SysAllocString(L"JScript"); if (!bstrForceLanguage) { hr = E_OUTOFMEMORY; goto Cleanup; } hr = (pDesignElem->put_language(bstrForceLanguage)); SysFreeString(bstrForceLanguage); if (hr) goto Cleanup; } Cleanup: SysFreeString(bstrNewBlock); ReleaseInterface(pScriptElem); ReleaseInterface(pDesignElem); return( hr ); }