Programming magic, glory, and juices.

Opening a OLE Structured Storage Set, The Right Way!

March 3rd, 2008


There are many code examples, that don’t open a composite document properly but instead throw an error like “Cannot open DocFile”. Well, that is not good enough for me to be able to open it only “sometimes”. If Windows Explorer can edit the properties of a file in the file’s Summary tab, then why can’t I? I mean, it is not like this is new technology (NT)… it has been part of Windows, since Windows 95. The trick is has to do with how you attempt to open it. You have to open it using StgOpenStorageEx asking first for a IStorage interface and if that fails you have to attempt it again only this time asking it for an IPropertySetStorage interface. Take a look.

IStorage *Storage;
IPropertySetStorage *PropertySetStorage;
int32 Flags;
GUID *FormatId;

Flags      = STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;
FormatId   = &FMTID_SummaryInformation;
Result     = StgOpenStorageEx(Filename, Flags, STGFMT_ANY, 0, NULL, NULL, &IID_IStorage, (void**)&Storage);      

if (SUCCEEDED(Result))
   {
   if (FAILED(IStorage_QueryInterface(Storage, &IID_IPropertySetStorage, (void**)&PropertySetStorage)))
      return FALSE;
   }
else
   {
   if (Result == E_NOINTERFACE)
      {
      Result = StgOpenStorageEx(Filename, Flags, STGFMT_ANY, 0, NULL, NULL, &IID_IPropertySetStorage,
      (void**)&PropertySetStorage);
      }      

   if (FAILED(Result))
      {
      if (Verbose == TRUE)
         {
         if (Result == STG_E_FILENOTFOUND)
            Debug_Print("Error: File not found [%s]\n", Filename);
         if (Result == STG_E_FILEALREADYEXISTS)
            Debug_Print("Error: File is not a compound file [%s]\n", Filename);
         }

      return FALSE;
      }
   }

if (FAILED(IPropertySetStorage_Open(PropertySetStorage, FormatId, Flags, &PropertyStorage)))
   {
   if (FAILED(IPropertySetStorage_Create(PropertySetStorage, FormatId, NULL, PROPSETFLAG_DEFAULT,
      Flags | STGM_CREATE, &PropertyStorage)))
      return FALSE;
   }

This way you are able to edit the extended file properties of any file, just like Explorer.

Re: Script error notification is not sent to Exec method of WebBrowser Host

April 10th, 2007


Microsoft’s KB support article, Script error notification is not sent to Exec method of WebBrowser Host mentions a method to correct OLECMDID_SHOWSCRIPTERROR notification bugs found in older versions of Internet Explorer.

Specifically, the example it gives uses the insertAdjacentHTML function to insert the following javascript code into each document to handle the onerror javascript window notification.

&#xa0;<SCRIPT For='window' Event='onerror'>var noOp = null;</SCRIPT>

One of the problems with this method is that it requires you to attach a null character &#xa0;< to the html due to the fact that “Internet Explorer will not parse and recognize the script block without some visual HTML to accompany it.”. The purpose of this weird null character is to add something to the document that is a visual element, but is in essense not so visual. In several instances, when attaching this code, it actually does change the visuality of the page. When added to some IFRAME documents, it moves the whole page down 20 pixels or so. Below is a screenshot that you could consider a “proof of concept”.

You might think that changing “afterBegin” to “beforeEnd” in their example code will help, but in fact it will not because the javascript code you are inserting will not take effect until it is encountered by the javascript parser, which would be at the end of the document. This means that any previous scripting errors won’t be caught if you try this.

The best solution I have come up with is to get the IHTMLDocument’s parentWindow and then run the execScript function with the following equivalent javascript code.

window.onerror = (function() { null; });

For more ways on how to stop scripting popups when hosting your own web browser control, please see my other article entitled IWebBrowser2 Stopping and Disabling Popups.

Timers and the Web Browser Control

March 23rd, 2007


In Windows, the SetTimer function causes problems when used in an application that hosts a web browser control or implements the IWebBrowser interface. In most cases I have seen the web browser control just stall out with particular sites and I have always been able to trace the problem back to the SetTimer call. It does not matter if you specify an hWnd or not or specify a TIMERPROC or not.. when calling SetTimer, it just ends up interferring with your IWebBrowser OLE object.

If not SetTimer, then what? Well.. according the ancient MSDN support article, “INFO: SetTimer() Should Not Be Used in Console Applications“, you can use the Windows Multimedia SDK function timeSetEvent instead. You can use any of the other Windows API timer queue functions as well, just so long as they are not message based.

timeSetEvent creates a separate “waiting” thread that loops until the specified time has elapsed. Then timeSetEvent executes your callback function from that separate thread. If you create an IWebBrowser object with OleCreate and then try and access that IWebBrowser object from this separate thread your program will crash. Since OLE objects are not thread-safe and they should only be invoked within the thread they were created. (See OleInitialize) The way you can get around this is to have your timer callback function send custom window messages back to your main window to notify it that the required time has elapsed. Therefore you won’t be trying to access your OLE object within a separate thread and you won’t be using the SetTimer function or the WM_TIMER message which are obviously incompatible with the Internet Explorer web browser control.

IWebBrowser2 Stopping and Disabling Popups

July 4th, 2006


With the WebBrowser control it is very difficult to host it and to stop all the different kinds of popups that can occur. All these methods took a huge amount of time to research and to develop.

Popups occuring through javascript when calling windows.open

This is probably the easily popup to handle. The DWebBrowserEvents2 interface has notifications that allow you to cancel popups that were created using windows.open. See notifications DISPID_NEWWINDOW2 and DISPID_NEWWINDOW3.

Popups are opened when a javascript contains windows.showModalDialog or window.showModallessDialog

<script>window.showModalDialog("http://www...", "", "");</script>

To stop this method of popup you can easily disable the javascript that causes it to happen. Setting the functions to null value in javascript will disable them. Use IHTMLWindow2::execScript during the NavigateComplete event to get the job done.

Popups are opened using javascript's alert function

To disable the javascript alert function you can set it's value to null like mentioned above. I also recommend checking for windows that popup in the current thread called "Microsoft Internet Explorer". These are alert window messages and should first be sent IDNO and then sent IDOK. Note that you cannot set alert to null but you have to set window.alert to null to disable the function.

Popups are opened when a page hosts a Windows Media Player object and uses javascript to redirect the user to the new page

<object id=iie width=0 height=0
classid='CLSID:6BF52A52-394A-11D3-B153-00C04F79FAA6'
style='display:none'></object>
<script>iie.launchURL(popURL);</script>

To prevent Windows Media Player from opening urls you have iterate through all the OBJECT elements and search for ones that look suspicious within DocumentComplete. Once you've found an object that is trying to open urls or that has a small size or that is not displayed, you can use IHTMLDocument->createElement to create a new OBJECT element and replace it with the old OBJECT element. When you create the new element be sure to use the same name attribute as the previous element and not to assign a classid to it. To replace the elements use IHTMLDOMNode::replaceNode.

If you try using IHTMLObjectElement2::put_classid to try and remove the classid of the OBJECT element it won't work. Best thing to do is to create an new OBJECT element with no classid and replace it with the old OBJECT element that contains the Windows Media Player classid.

Popups are opened via script errors that occur while using the WebBrowser control

One way you'll want to implement requires calling EnumWindows and iterating through all the open windows. You can compare the title of the windows with generic Microsoft Internet Explorer error windows and close any that you find match.

I also recommend implementing the IOleCommandTarget interface to remove any further popups. You can watch for the OLECMDID_SHOWSCRIPTERROR and OLECMDID_SHOWMESSAGE notifications and stop them before they occur. How to handle script errors as a WebBrowser control host.

Of course that method does not always work as Internet Explorer does not always send the OLECMDID_SHOWSCRIPTERROR notification. Microsoft provides a hack solution to try and resolve the issue. Script error notification is not sent to Exec method of WebBrowser host

Stopping popups using the WebBrower control's ambient properties

I do not use this method nor do I recommend this method. What some people have done is use the DISPID_AMBIENT_DLCONTROL notification to limit the functionality of the WebBrowser control. There really is no option that allows you to straight out limit popups. Whenever you try to use DLCTL_NO_SCRIPTS or DLCTL_NO_DLACTIVEXCTLS or any combinations of the flags that are available to you it always results in the final page rendering differently that it normally would. I do not recommend using the DISPID_AMBIENT_USERMODE notification either. With both of these the page will not render as it should and during some DWebBrowserEvents2 notifications you'll find you won't be able to access the document's body or the document itself or something like that.

Canceling file download windows

Sometimes Internet Explorer will try and download a file and thus display the file download dialog. You can stop this by handling the DISPID_FILEDOWNLOAD message in DWebBrowserEvents2.

An idea to stop popups using the browser UserAgent

You could always change the UserAgent so that it does not appear that you are using Internet Explorer or using Windows XP. I won't venture there because changing the UserAgent could affect the rendering of the page even though a lot of popup scripts do rely on the UserAgent to determine what methods it will try when opening a new window.

Resources
MSDN WebBrowser Customization
MSDN Reusing the WebBrowser Control
Google Groups - Problems Sinking and OnUnload
CodeProject - Popup Blocker

If I have skipped over a method or anything please let me know.

IHTMLElementRender opacity

June 9th, 2006


Using IHTMLElementRender::DrawToDC is the wrong way to draw the contents of an webpage to a device drawing context. The reason why the IHTMLElementRender interface is not good is because it does not draw opacity or transpancy that might be visible in a web page. Instead it is best to query the IViewObject interface from the IHTMLDocument2 interface and call the function Draw.

BAD
IHTMLDocument2 — IHTMLElement – IHTMLElementRender::DrawToDC.

GOOD
IHTMLDocument2 – IViewObject::Draw using DVASPECT_DOCPRINT

You can also use OleDraw instead of IViewObject::Draw and pass OleDraw the IHTMLDocument2 interface.