10926 : When hosting ASP.NET Ajax applications in a frame or iframe I get “access denied”

Question

 When hosting ASP.NET Ajax applications in a frame or iframe that’s in a different domain from the top-level window. If you try to do that and browse to the page using IE, you’ll receive an “access denied” error client-side  issue with < ?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />Ajax 1.0< ?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Do you have any solutions for this?  I am calling my web app from within salesforce.com page and it doesn’t work.

Answer

There is a very good description and solution here, and I quote the important part of text just in case it goes offline:

How to work around the access denied cross-domain frame issue in ASP.NET Ajax 1.0

Some users have run into an issue when hosting ASP.NET Ajax applications in a frame or iframe that’s in a different domain from the top-level window. If you try to do that and browse to the page using IE, you’ll receive an “access denied” error client-side any time a DOM event is raised in the frame. The code that’s responsible for that is Sys.UI.getLocation. This is a tricky piece of code that determines the pixel coordinates of a DOM element relative to the top-left corner of the page, so that an absolutely positioned child of the body using these coordinates would exactly cover the element you measured. This is useful for drag & drop, popup scenarios like auto-complete and when an event is raised to get mouse coordinates relative to the event source. This is this last piece that explains the problem. The code in this method is different for each of the browsers that we support because each one of them has its own behavior that cannot be determined as we usually do by dynamically looking at capabilities. You just have to know for example that browser X is not counting an element’s scroll position if it’s absolutely positioned and a direct child of body (names changed to protect the guilty). That’s the kind of problem we had to work around. Luckily, IE has two very handy methods to retrieve this kind of coordinates that enables us to bypass completely a number of bugs that we just couldn’t efficiently work around: getClientRects, which gets all rectangles the element occupies on the page, and getBoundingClientRect, which returns a single rectangle that bounds the whole element. In the method that we shipped, we’ve been using getClientRects and getting the first rectangle because we wanted to have consistent behavior across browsers even if the element is a wrapping element such as a span: in this case, the top-left corner of the element is the top-left corner of the first bounding rectangle, which is different from the top-left corner of the global bounding rectangle:

 

And this is where we made a mistake, unfortunately too late. There is a subtle difference between getClientRects and getBoundingClientRect, which is that getClientRects, when in an iframe, gives coordinates that include the offset of that frame in the top window, whereas getBoundingClientRect gives the right coordinates directly. Both need to include the frameborder to be perfectly accurate. To correct the behavior of getClientRects, we had to look at the coordinates of the frame relative to the top window, to subtract them, and this is the operation that is not allowed if the frames are in different domains.

The fix is to use getBoundingClientRect instead, which will introduce a small inconsistency across browsers in the case of wrapping elements but is a lot better than just failing. The new version of the function still needs to try/catch around the code that fixes the frameborder, so for cross-domain frames you may get a 2 pixel offset in the coordinates but this is the best you can get.

How to apply the fix

First, you’ll need to use the external script files instead of the resource-based ones. You do this by setting a general ScriptPath on the ScriptManager. The external script files can be found in the Microsoft Ajax Library (http://ajax.asp.net/downloads/library/default.aspx?tabid=47&subtabid=471) which is under the MSPL (which allows you to modify the files). Copy the System.Web.Extensions folder found in the Library zip into the folder you pointed ScriptPath to.

Alternatively, if you don’t want to have all your script references path-based, you can point only the core framework to a file and leave the others to use web resources as usual. This makes things easier when using other resource-based libraries such as the toolkit. This is easily done by adding the following script reference to your script manager:

<asp:ScriptReference
    Name=”MicrosoftAjax.js” ScriptMode=”Auto”
   
Path=”~/[Your Script Directory]/System.Web.Extensions/1.0.61025.0/MicrosoftAjax.js”

/>

Of course, don’t forget to replace the part of the path between the brackets with the name of the script directory you chose. Don’t set a ScriptPath on the script manager if you choose to use this script reference. 

Once you’ve done that, you can check that the application still works and loads the script from the new location using a network monitoring tool such as Fiddler.

The second step is to patch the files. You’ll need to patch the debug and release versions.

The debug version is MicrosoftAjax.debug.js. Look for the following code:

switch(Sys.Browser.agent) {
    case Sys.Browser.InternetExplorer:

then replace everything between that and “case Sys.Browser.Safari:” with the following code:

Sys.UI.DomElement.getLocation = function(element) {
    if (element.self || element.nodeType === 9) return new Sys.UI.Point(0,0);
    var clientRect = element.getBoundingClientRect();
    if (!clientRect) {
        return new Sys.UI.Point(0,0);
    }
    var ownerDocument = element.document.documentElement;
    var offsetX = clientRect.left - 2 + ownerDocument.scrollLeft,
        offsetY = clientRect.top - 2 + ownerDocument.scrollTop;
    
    try {
        var f = element.ownerDocument.parentWindow.frameElement || null;
        if (f) {
            var offset = 2 - (f.frameBorder || 1) * 2;
            offsetX += offset;
            offsetY += offset;
        }
    }
    catch(ex) {
    }    
    
    return new Sys.UI.Point(offsetX, offsetY);
}
break;

For the release version (MicrosoftAjax.js), the process is pretty much the same except that the file is a little more difficult to manipulate. Look for “switch(Sys.Browser.agent){case Sys.Browser.InternetExplorer:” and replace everything between that and “case Sys.Browser.Safari:” with the following:

Sys.UI.DomElement.getLocation=function(a){if(a.self||a.nodeType===9)return new Sys.UI.Point(0,0);var b=a.getBoundingClientRect();if(!b)return new Sys.UI.Point(0,0);var c=a.document.documentElement,d=b.left-2+c.scrollLeft,e=b.top-2+c.scrollTop;try{var g=a.ownerDocument.parentWindow.frameElement||null;if(g){var f=2-(g.frameBorder||1)*2;d+=f;e+=f}}catch(h){}return new Sys.UI.Point(d,e)};break;

(no line breaks)

The site should now work without the exceptions.

Leave a Reply