The problem was about a bug in a specialy tailored browser application which happens to be a .NET windows application. The client uses this browser to access their web aplpications in a very secure way. The browser application is somewhat based on this article published in http://www.codeproject.com/ modified in some way to support single session sign-on and role based authentication. As a security feature of this application, some keyboard strokes are monitored (just like a keylogger) to determine what the user is trying to do and to react appropriately based on the user's permissions. Including copy and paste functions are controlled by user permissions or what other architects called modules. Enough on the security stuff...
The application utilizes the .NET WebBrowser control. This is basically a wrapper to the existing native Browser Control which is a part of Win32 API collections commonly used in VB6 and unmanaged VC++ applications. In .NET this component is called System.Windows.Forms.WebBrowser.
The bug was discovered when a user accessing one of the clients applications tried to copy a text in the application and paste it somewhere else. Nothing was copied, the copy paste function did not work. I dig in to the code, and found out that all commands that tends to alter the contained HTML document such as copy, cut, paste, delete, etc... works only if the document allows it to depending on the document's command state.
This state can be determined by calling the queryCommandEnabled() method of the IHTMLDocument interface. If this method returns true on a specific command, then we can allow that command to operate on the document object otherwise we can't. One example of this behaviour is that of MS Office (e.g. Word), you will notice that the COPY and CUT button in the toolbar will only get enabled when you select something within the document. Try it, you may never have noticed this before. Another thing I learned is that document objects invokes an event called CommandStateChange whenever a mouse is moved over the WebBrowser control.
The problem only becomes a problem when the HTML Document contained in the WebBrowser control has HTML Frames or iFrames. This is becuase when you select a text in a document inside an iFrame the commandState of the "Copy" command returns false because it only tests the command state of the parent HTMLDocument object only (outermost page) not including the child HTML documents contained in frames. I have tested this by creating an HTML page that contains Frames inside a frameset just like a typical classic webpage and then in the body part i placed iframes that contains several other iframes (nested iframes). The purpose is to test if the queryCommandEnabled() method also tests for documents contained in iframes. The test application looks like the image on the right.
After testing, I have proven that this is the primary cause of the Copy/Cut and Paste problem. The queryCommandEnabled() method returns true only if the selected text/object is in the root/parent HTMLDocument object therefore it does not enable the copy/cut & paste functions even if the selected text or object is inside the the same browser control.
Initially, the client architect's sugesstion was to disabled the commandState checking for web applications that uses frames and iframes specialy when the application is written in AJAX as they did not find ways to access every HTMLDocument object in the child frame. So, I cunsulted MSDN and figured out that you can get the instance of the HTMLDocument object thru IHTMLWindow2 interface thru the property .frames which returns a collection of IHTMLWindow2 interface . The code below will show how to get the HTMLDocument of the first child f$ame of an HTMLDocument interface.
IHTMLDocument2 t = Document.DomDocument as IHTMLDocument2;
object refIndex = 0;
frame = (IHTMLWindow2)t.frames.item(ref refIndex);
ITHMLDocument2 childDoc = t.document;
The indexing looks kinda weird since we are dealing with native data types here (Win32 API).
Bingo! We can now loop through each frame. But, how? Later, I realized that the structure of the nested frames is a Tree structure. Therefore you cannot just do a simple loop thru it because we do not know how many child frames each frmae has. How can I make sure that all the frames will be tested for querycommandEnabled()? The answer is pretty simple. The term is "traverse" not loop. I need to make a code snippet that will traverse thru the Tree structure and test each node for queryCommandEnabled(). I needed to use Tree-traversal teniques.
I haven't completed my graduate degree but I can pretty much remember all i've learned from it, including how to learn a completely new language (Scheme) in less than 15 minutes. I learned about tree-traversal algorithm also refered to as graph-search algorithm when I took the Artificial Intelligence course back in graduate school. This was one of the search algorithms I used to create my "talking travel advisor" AI program. Now I need to decide which approach to use. Either Breadth-First Search or Depth-First Search will do since each step to a node has a uniform cost but I needed to know which is more appropriate for this application.
Here's a graphical presentation of how both algorithm works:
I did a careful selection by using some criterias such as the common structure of most Framed web sites. I also considered AJAX generated pages. Then i finally decided to use BFS. The algorithm goes like this:
Enqueue the root node.
Dequeue a node and examine it.
If the searched element is found in this node, quit the search and return a result.
Otherwise enqueue all the (so-far-unexamined) leftmost successors (the direct child nodes), if there are any.
If the queue is empty, every node on the graph has been examined -- quit the search and return "not found".
Repeat from Step 2.
Note: Using a stack instead of a queue to store the nodes yet to be visited would turn this algorithm into a depth-first search.
It worked on the test page i created but still not tested on the application where the bug was discovered. I am currently building the solution at the moment and hoping that this will work fine.
to be continued...