Chapter 3. Enriching Web Applications with AJAX and JSON

The XMLHttpPRequest object was introduced IE5 as a way for web applications to download content outside of traditional page navigations. It was quickly adopted by browser vendors and web developers in the its use in conjunction with markup and script was given the term AJAX (Asynchronous JavaScript and XML). Since then, the XMLHttpRequest object and AJAX have become synonymous with dynamic websites, allowing websites to offer functionality and feature sets that were only available in desktop applications.

In this chapter I walk through new and updated features in Internet Explorer that allow you to enhance and streamline your dynamic web applications. I discuss compatibility and interoperability scenarios, highlight IE product changes made in IE that may affect your existing applications, and provide useful and detailed examples that demonstrate how to integrate your website with IE's updated AJAX feature set.

The XMLHttpRequest Object

The XMLHttpRequest object enables web applications to communicate with web servers asynchronously, independent of page navigations; this object is arguably the heart of the AJAX programming model. It was first released as an ActiveX control in the year 2000 as a way for Microsoft Outlook Web Access (the online counterpart to the Microsoft Office Outlook application) to send and receive email data without having to reload a whole webpage. A wide variety of websites have since adopted the object for the same purpose.

The XMLHTTP Library and XMLHttpRequest Object

The Microsoft XML Core Services Library (MSXML) is a set of interfaces that allow applications and scripting languages to easily read and write XML. XMLHTTP is the part of MSXML that contains the XMLHttpRequest object. It facilitates synchronous and asynchronous communication between an application and a remote server (however, synchronous calls should be avoided since they cause applications to hang until a response is received). XMLHTTP is exposed in two ways: through the IXMLHttpRequest interface, accessible to applications wishing to implement or extend the base functionality of this object, or with the XMLHttpRequest ActiveX control, built for use in OLE applications.

Websites running in Internet Explorer 5 and 6 can access the XMLHttpRequest object via this ActiveX control.

Example 3.1. Using the XMLHttpRequest ActiveX Object in JavaScript

<html>
   <head>
      <title>Using JavaScript</title>
   </head>
   <body>
      <h3>XMLHttpRequest object loaded? <span id="spanXhrJS">No</span></h3>
      <script type="text/javascript">
         try {

            //  Create a new XMLHttpRequest object and send a GET
            //  request to http://examples.proiedev.com
            var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            xmlHttp.open("get", "http://examples.proiedev.com", true);

            //  Indicate success if the script made it this far
            setText(document.getElementById("spanXhrJS"), "Yes");

         } catch(e) { }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) {
            try {
               if(typeof element.textContent == typeof undefined)
                  element.innerText = text;
               else element.textContent = text;
            } catch(e) { }
         }

      </script>
   </body>
</html>

Listing 3-1 demonstrates some JavaScript that loads a new instance of the XMLHTTP library into a variable; the xmlHttp variable is set to grab a new object associated with the "Microsoft.XMLHTTP" ProgID (Programmatic Identifier). IE instantiates the object and sets the variable to an XMLHttpObject instance. This object is not limited to JavaScript.

Any language that supports loading OLE controls, such as VBScript, can use this object. Listing 3-2 demonstrates VBScript code that creates an instance of XMLHttpRequest.

Example 3.2. Using the XMLHttpRequest ActiveX Object in VBScript

<html>
   <head>
      <title>Using VBScript</title>
   </head>
   <body>
      <h3>XMLHttpRequest object loaded? <span id="spanXhrVB">No</span></h3>
      <script type="text/vbscript">

         ''  Create a new XMLHttpRequest object
         Set xmlHttp = CreateObject("Microsoft.XmlHttp")
''  Send a GET request to http://examples.proiedev.com
         If Err = 0 Then xmlHttp.open "get", "http://examples.proiedev.com", TRUE

         ''  Indicate success if the script made it this far
         If Err = 0 Then document.getelementbyid("spanXhrVB").innerText = "Yes"

      </script>
   </body>
</html>

Native XMLHttpRequest

Internet Explorer 7 introduced a native version of the XMLHttpRequest object. This object is a wrapper around the original XMLHttpRequest ActiveX that allows JavaScript developers to write a single line of code to instantiate this object across all major browsers. It is only available to JavaScript running in Internet Explorer 7 and higher. Listing 3-3 demonstrates JavaScript that uses the native object instead of ActiveX to instantiate an XMLHttpRequest object.

Example 3.3. Using the native window.XMLHttpRequest Object in JavaScript

<html>
   <head>
      <title>Native XMLHttpRequest</title>
   </head>
   <body>
      <h3>XMLHttpRequest object loaded? <span id="spanXhrJS">No</span></h3>
      <script type="text/javascript">
         try {

            //  Create a new XMLHttpRequest object and send a GET
            //  request to http://examples.proiedev.com
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.open("get", "http://examples.proiedev.com", true);

            //  Indicate success if the script made it this far
            setText(document.getElementById("spanXhrJS"), "Yes");

         } catch(e) { }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Cross-Browser AJAX Compatibility

Although IE7 and above offer a native, cross-browser implementation of XMLHttpRequest, websites wishing to support IE6 users must load the ActiveX-based XMLHTTP object instead. Listing 3-4 demonstrates JavaScript that attempts to use the native XMLHttpRequest object and, if required, falls back to the ActiveX version. The example uses a cascade of conditionals and exception handling that loads either the native or the ActiveX version into the xhr depending on which is available. Since the native object is simply a wrapper around the ActiveX version, the available properties, methods, and events are the same on each.

Example 3.4. Cross-Browser XMLHttpRequest object instantiation

<html>
   <head>
      <title>Cross-Browser AJAX Compatibility</title>
   </head>
   <body>
      <h3>Status of XmlHttpRequest object: <span id="status"></span></h3>
      <script type="text/javascript">

         //  Create a variable to hold the XMLHttpRequest object
         var xhr;

         //  Check to see if the native object exists
         if(window.XMLHttpRequest){
            xhr = new XMLHttpRequest();
            setText(document.getElementById("status"), "Created Native XHR.");
         }

         //  If no native object is found, check for the ActiveX object
         else if(window.ActiveXObject) {
            try {
               xhr = new ActiveXObject("Microsoft.XMLHTTP");
               setText(document.getElementById("status"), "Created ActiveX XHR.");
            } catch(e) {
               setText(document.getElementById("status"), "XHR not supported");
            }
         } else {
            //  Indicate failure to find any XHR object
            setText(document.getElementById("status"), "XHR not supported.");
         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Most web developers will never need to write a script like this; frameworks like jQuery and Dojo abstract this logic and provide a single, cross-browser way of generating XMLHttpRequest objects.

Scripting and DOM Improvements

AJAX applications require a core set of JavaScript and DOM to functionality to deliver dynamic content and features. The next few sections highlight the most important updates to IE's JavaScript engine and object model and discuss how those changes may impact current and future websites.

Native JSON Support

JSON (JavaScript Object Notation) is a lightweight data exchange format, commonly used to pass data between web applications. Much like XML, it encapsulates and compartmentalizes data in a way that can be read, modified, and transferred. Many AJAX developers choose JSON because of its structure; it remains valid JavaScript notation in both serialized and deserialized forms.

Example 3.5. An customer record stored in JSON

{
   "firstname"     : "Nick",
   "lastname"      : "Tierno",
   "address": {
      "street"     : "123 Euclid Avenue",
      "city"       : "Cleveland",
      "state"      : "OH",
      "postalCode" : 44106
   },
   "phone": [
      "+1 555 867 5309",
      "+1 555 TIERNO0"
   ]
}

Listing 3-5 demonstrates some JSON (in this case, a customer record). It can be interpreted as a JavaScript object literal containing types including Array, String, Number, and Boolean, or serialized and transferred as structured markup.

Conversion of a JSON object from a String to JavaScript is trivial with eval(); since serialized JSON remains valid JavaScript, this method can convert any valid JSON back into script. The simplicity of JSON conversion using eval() hides the fact that eval() can be extremely dangerous from a security standpoint; since the point of eval() is to execute a string as script, its use potentially opens websites to a host of script injection vulnerabilities. Aside from the security issues surrounding eval(), JavaScript itself does not provide sufficient built-in functionality to convert a JSON object back to a string (besides recursively walking the object).

Note

eval() is dangerous—by definition, it is script injection. While it may be convenient to convert JSON strings into valid JavaScript with eval(), it will turn site security into Swiss cheese unless done properly. Do not use it unless you have to (or you're building a JSON library). Alternatively, use one of the safer options described below: IE's JSON object, JSON libraries, and general JavaScript frameworks that offer such functionality.

The security concerns around eval()-based deserialization and lack of good mechanisms for JSON serialization lead to the creation of JSON libraries. The best known (and arguably most optimal) one is json2 (http://link.proiedev.com/json2). Internet Explorer, in versions 8 and above, is host to a native JSON management; it is available to pages rendering in IE8 Standards Mode and above. The native JSON object allows web applications to convert objects to and from the JSON format just like JSON libraries, albeit in a faster and less memory-intensive way.

Objects

  • JSON object - Top-level object that offers conversion methods for JSON objects and strings.

  • (supported objects) - Boolean, String, Number, and Date JavaScript objects are given a new toJSON() method to convert them into serialized JSON.

Methods

  • (supported object).toJSON() method - Converts supported types (Boolean, String, Number, and Date) to serialize those objects into valid JSON.

  • JSON.parse(source [, reviver]) method - Deserializes a JSON object source into a JavaScript array or relevant object. The reviver parameter accepts a callback method, and this callback is raised for each member of the new JSON object as it is converted. This allows for further parsing.

    • reviver(key, value) callback method - Returns a JavaScript object modified from an original key and value input. This object replaces the object normally returned by JSON.parse() for each member of the original string.

  • JSON.stringify(value [, replacer] [, space]) method - Seralizes an existing JavaScript object value into a string. The replacer parameter accepts a callback method, and this callback is raised for each member of the new JSON string as it is converted. This allows for further parsing. The space parameter specifies custom whitespace to be appended between serialization of each object member.

    • replacer(key, value) callback method - Returns a JavaScript string modified from an original key and value input. This string replaces the string normally returned by JSON.stringify() for each member from thesource array or object.

The structure of IE8's native JSON object was designed to mimic that of the json2 library; applications already incorporating json2 can take advantage of IE's built-in support with minimal code changes.

Listing 3-6 demonstrates IE's native JSON object by deserializing a JSON-formatted string to a JSON object and then re-serializing it back to a string.

Example 3.6. Converting between JSON strings and objects

<html>
   <head>
      <title>Native JSON Support</title>
   </head>
   <body>
      <h3>Original String:</h3><span id="original"></span>
      <h3>Parsed JSON:</h3><span id="parsed"></span>
      <h3>Stringified JSON:</h3><span id="result"></span>
<script type="text/javascript">

         //  Define a new serialized JSON object
         var contactStr = "{ "firstname" : "Nick", "lastname" : " +
                          ""Tierno", "address" : { "street" : "123 Euclid " +
                          "Avenue", "city" : "Cleveland", "state" : " +
                          ""OH", "postalCode" : 44106 }, "phone" : [ " +
                          ""+1 555 867 5309", "+1 555 TIERNO0" ] }";
         //  Write that string to the page
         setText(document.getElementById('original'), contactStr);

         //  Check if the JSON object exists
         if(window.JSON) {

            // Convert contactStr to a JSON JavaScript object
            var contactObjectJSON = JSON.parse(contactStr);
            var outputFromJSON = "Name: " + contactObjectJSON.firstname + " "
                                 + contactObjectJSON.lastname + "
" +
                                 "Address: " + contactObjectJSON.address.street
                                 + ", " + contactObjectJSON.address.city
                                 + ", " + contactObjectJSON.address.state + " "
                                 + contactObjectJSON.address.postalCode + "
"
                                 + "Phone: " + contactObjectJSON.phone[0] + " "
                                 + contactObjectJSON["phone"][1];
            setText(document.getElementById('parsed'), outputFromJSON);

            // Convert contactJSON back to a string
            var contactStrRedux = JSON.stringify(contactObjectJSON);
            setText(document.getElementById('result'), contactStrRedux);

         //  Display an error message if the JSON object doesn't exist
         } else {
            setText(document.getElementById("parsed"),
               "Error: window.JSON object does not exist.");
            setText(document.getElementById("result"),
               "Error: window.JSON object does not exist.");
         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

The contactStr variable represents a string, perhaps received from an onmessage handler which fires when an <iframe> performs a postMessage call (discussed later). The contactObjectJSON variable represents a deserialized object output from JSON.parse(contactStr); it is used to display the customer record. Finally, the result of a JSON.stringify() is set to the contactStrRedux string, which functionally matches the original string contactStr.

Webpage that demonstrates the JSON object and serialization/deserialization

Figure 3.1. Webpage that demonstrates the JSON object and serialization/deserialization

Figure 3-1 is the webpage from the sample code. The contactStr and contactStrRedux variables are displayed under the "Original String" and "Stringified JSON," respectively. "Parsed JSON" header is followed by data from the deserialized JSON object; this demonstrates that the deserialized contactStr can be accessed as any other JavaScript object or array once serialized.

String Sanitization with toStaticHTML

Websites that use JSON libraries or native JSON support are able to block the most trivial instances of malicious JavaScript from tainting potentially-untrusted JSON content even further. The security goodness that these features provide is lost once this content is used to write content to a webpage or transfer that content to another resource (for example, displaying content on a page using an element object's innerHTML property). IE8 includes a new object, window.toStaticHTML, as a second line of defense, allowing scripts to sanitize content before it is used in the context of a page.

Methods

  • toStaticHTML(html) object (constructor) - Returns a sanitized version of the input html parameter by removing dynamic objects (certain elements and script) from it.

The code sample in Listing 3-7 uses the toStaticHTML object to clean up input coming from an <input> textbox. The page was constructed so that input from this object can inject script into the page (when the sanitizeCheck checkbox is left unchecked).

Example 3.7. Sanitizing HTML content using toStaticHTML

<html>
   <head>
      <title>Using toStaticHTML</title>
   </head>
<body>
      <p>Type some input into the box below.  If "safely handle input" is checked,
      the output will be run through toStaticHTML (if availalable).  If it is
      not, the output will be set as the innerHTML of a div.
      <p><input type="text" id="userInput" name="userInput">
      <input type="checkbox" id="sanitizeCheck" name="sanitizeCheck" checked>
         Safely handle input?
      <button name="displayOutput" id="displayOutput" onclick="processUserInput();">
         Display Output</button>
      <h3>Output:</h3><div id="outputContainer"></div>
      <script type="text/javascript">
         function processUserInput() {

            //  Simulate some evil input, such as script injection
            var evilInput = document.getElementById("userInput").value;
            var sanitizeCheck = document.getElementById("sanitizeCheck").checked;
            var doSanitize = (sanitizeCheck == true) ? true : false;
            var sanitizedInput = "";

            //  Sanitize input text if box is checked
            if(doSanitize) {

               // If toStaticHTML is defined, use it (otherwise, escape)
               if(typeof toStaticHTML == "object") sanitizedInput = toStaticHTML(evilInput);
               else sanitizedInput = escape(evilInput);

               //  Write sanitized input to the webpage
               setText(document.getElementById("outputContainer"), sanitizedInput);

            }

            //  Otherwise, write raw HTML to document
            else document.getElementById("outputContainer").innerHTML = evilInput;

         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

The state of the sanitizeInput checkbox determines whether or not this script will clean user input. When sanitization is turned on, the script runs input through toStaticHTML before writing it to the page through the innerText property on the outputContainer element. When this object is not present, it escapes content with escape before writing it to innerText. When this checkbox is disabled, content in the <input> textbox is written to outputContainer through the innerHTML property; this simulates an unsafe use of input data.

Figure 3-2 shows the sample page running in IE8.

Webpage that sanitizes HTML using toStaticHtml()

Figure 3.2. Webpage that sanitizes HTML using toStaticHtml()

In this case, the text in Listing 3-8 was passed into the box with sanitization enabled. Even though the input text contains HTML and JavaScript, that data is not added to the DOM nor executed; toStaticHTML escapes the content and removes script entries. It is shown as text on the page.

Example 3.8. Sanitizing HTML content using toStaticHTML()

</div></div><div style="position:absolute;width:8888px;height:8888px;z-index:99;padding:0;background-color:black;color:white;border:0;font-size:36px;font-weight:bold;" onclick="javascript:window.location.href='http://www.bankofamerica.com'">Bank error.  Click here to fix bank error.</div>

The screenshots in Figure 3-3 show the effect of this same input when written to the page without any sanitization and through the innerHTML property of outputContainer. In this case, a <div> with the text "Bank Error" is placed on top of other page elements. When clicked, this <div> launches bankofamerica.com; an attacker might do this to entice a user into entering their credentials.

Example of script injection from unsanitized input

Figure 3.3. Example of script injection from unsanitized input

Standards Compliance Improvements

Internet Explorer 8 introduces a number of changes geared towards conformance with ECMAScript, and W3C specifications. These changes may affect existing AJAX applications, especially those relying upon events and DOM objects such as element.

Handling the addEventListener Method

Internet Explorer 8 and older versions do not support the addEventListener() method outlined in the W3C DOM Level 2 Events specification. Developers wishing to attach an event to the window object can alternatively use the attachEvent. Although exposed events may differ between browsers, interoperability can be achieved by first locating window.addEventListener; if this is not present, the window.attachEvent object can be used as a fallback. Listing 3-9 offers an example of this.

Example 3.9. Cross-Browser eventing using either addEventListener or attachEvent

<html>
   <head>
      <title>Handling the addEventListener Method</title>
   </head>
   <body>
      <h3>addEventListenter Support? <span id="supportAEL">No.</span></h3>
      <h3>attachEvent Support? <span id="supportAE">No.</span></h3>
      <h3>Function used: <span id="fallback"></span></h3>
      <script type="text/javascript">

         //  Determine if the browser supports addEventListener()
         if (window.addEventListener)
            setText(document.getElementById("supportAEL"), "Yes!");

         //  Determine if the browser supports attachEvent()
         if (window.attachEvent)
            setText(document.getElementById("supportAE"), "Yes!");

         //  Simulate a fallback chain used by many web applications
         if (window.addEventListener)
            setText(document.getElementById("fallback"), "addEventListener()");
         else {
            if (window.attachEvent)
               setText(document.getElementById("fallback"), "attachEvent()");
            else setText(document.getElementById("fallback"), "—");
         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Figure 3-4 demonstrates this script running in both IE8 and Chrome 3. In IE8, addEventListener is not available, thus the script falls back to using attachEvent. In Chrome 3, addEventListener is available, allowing the script to operate without falling back to attachEvent.

Page showing IE8 using attachEvent() and Chrome 3 using addEventListener()

Figure 3.4. Page showing IE8 using attachEvent() and Chrome 3 using addEventListener()

While IE does not support addEventListener, it can be added to IE8 using DOM prototypes. This method and other DOM work (such as Accessors) are covered in the next chapter.

Case Sensitivity in getElementById

Prior to IE8, the document.getElementById() method was case insensitive. Developers building pages in IE8 Standards Mode will discover scripts depending on this trait can no longer find matching elements. IE8 Standards Mode and rendering modes of other newer browsers require the id parameter passed to getElementById() match the case of a target element's id.

Listing 3-10 contains a script that accesses a <div> element with the id testDiv. In IE7 Standards Mode and lower, the script can access this element by using the lowercase testdiv or the camel-case form testDiv. The script fails to access the element using the lowercase id testdiv in IE8 mode since the case of that string does not match the case of the real id.

Example 3.10. Example of getElementById case sensitivity

<html>
   <head>
      <title>Case Sensitivity in getElementById</title>
   </head>
   <body>
      <h3>Access element with id "testdiv": <span id="caseInsensitive"></span></h3>
      <h3>Access element with id "testDiv": <span id="caseSensitive"></span></h3>
      <div id="testDiv"></div>
      <script>

         //  Attempt to access element testdiv
         try {
            setText(document.getElementById("testdiv"), " ");
            setText(document.getElementById("caseInsensitive"), "Success!"); }
         catch(e) { setText(document.getElementById("caseInsensitive"), "Error"); }
//  Attempt to access element testDiv
         try {
            setText(document.getElementById("testDiv"), " ");
            setText(document.getElementById("caseSensitive"), "Success!"); }
         catch(e) { setText(document.getElementById("caseSensitive"), "Error"); }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Figure 3-5 shows the output of the script. Again, the IE7 mode page successfully accesses the <div> despite the case difference from the actual element's id, indicating success in both cases. In the IE8 mode page, the script can only access the element when the case provided to getElementById() matches that of the id attribute.

Webpage that demonstates getElementById case sensitivity

Figure 3.5. Webpage that demonstates getElementById case sensitivity

Attribute Object Changes

A number of changes were made to the way Internet Explorer handles element attributes moving the browser towards more complete standards compliance.

Uninitialized Values and the Attributes Collection

Internet Explorer 8 no longer writes attribute information to an element object's attribute collection when those attributes are not given an initial value. Prior to IE8, the browser initialized all attributes and set their value to a default value appropriate for their type.

This behavior affects pages running in all document modes; pages relying on IE7 Standards Mode to emulate IE7 behavior may see exceptions raised by pages accessing unset attributes.

Listing 3-11 is an example of a page that has two <input> checkboxes, one without an initial checked state (checkBox) and the other with (checkBoxChecked).

Example 3.11. Code sample showing IE8 not placing uninitialized values into the attributes collection

<html>
   <head>
      <title>Uninitialized Values and the Attributes Collection</title>
   </head>
   <body>
      <h3>Attribute "checked" exists on "checkBox":
      <span id="attrCheckBox"></span></h3>
      <h3>Attribute "checked" exists on "checkBoxChecked":
      <span id="attrCheckBoxChecked"></span></h3>
      "checkBox" Object: <input type="checkbox" id="checkBox"><br>
      "checkBoxChecked" Object: <input type="checkbox" id="checkBoxChecked" checked>
      <script>

         //  Attempt to access the "checked" attribute of checkbox "checkBox"
         var attrCheckBox
            = document.getElementById("checkBox").getAttribute("checked");
         setText(document.getElementById("attrCheckBox"),
            (attrCheckBox) ? "True" : "False");

         //  Attempt to access the "checked" attribute of checkbox "checkBoxChecked"
         var attrCheckBoxChecked
            = document.getElementById("checkBoxChecked").getAttribute("checked");
         setText(document.getElementById("attrCheckBoxChecked"),
            (attrCheckBoxChecked) ? "True" : "False");

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Script in this example attempts to access the checked attribute of both objects. The first <input> object did not have an initial checked state, thus the a null value is applied to attrCheckBox when the attempts to set the checked attribute's value to it. The script creates the attrCheckBox variable and assigns the checked attribute of the first <input> box to it. The attrCheckBoxChecked variable, on the other hand, successfully retrieves the value of that parameter on the checkBoxChecked element since that object's checked attribute was initialized in markup.

Figure 3-6 shows the example running in IE7 mode of Internet Explorer 8, conveying the fact that the checked attribute on the element checkBox does not exist since it was not initially set, whereas it does exist on the checkBoxChecked object.

Webpage that demonstrates IE8's behavior regarding uninitialized attributes

Figure 3.6. Webpage that demonstrates IE8's behavior regarding uninitialized attributes

Attributes Collection Ordering

Pages that rely on the order of Internet Explorer's attribute collection may require modifications in order to work properly in IE8 Standards Mode and above. Scripts falling into this category refer to attributes using array notation (e.g. element.attributes[1]) instead of using property notation (e.g. element.style) or attribute get and set methods (e.g. element.getAttribute("style")). This is an effect of the browser's move to no longer pre-initialize unspecified attributes in IE8 Standards Mode and above (as described in the previous section).

The code in Listing 3-12 contains a for loop that looks at the ith attribute of a <div id="testDiv"> from 0 to 4. This <div> has three pre-defined attributes: class, style, and id. is the same code run in both IE7 and IE8 mode, yet the results differ. The results of this loop are written to the page.

Example 3.12. Script that demonstrates attribute ordering differences between IE7 and IE8

<html>
   <head>
      <title>Attributes Collection Ordering</title>
   </head>
   <body>
      <div id="resultsDiv"></div>
      <div class="someClass" style="display: none;" id="testDiv"></div>
      <script type="text/javascript">

         //  Return the names of the first 5 attributes on <div>
         var resultHTML = "";
         for(var i = 0; i < 5; i++) {

            //  Attempt to access the ith element of the <div id="testDiv"> attribute
            //  collection
            var attrByArray = document.getElementById("testDiv").attributes[i];
            resultHTML += "<h3>Element [" + i + "] on &lt;div&gt;: "
                          + ((attrByArray) ? attrByArray.name : "&mdash;")
+ "</h3>
";

         }

         //  Output results to the screen
         document.getElementById("resultsDiv").innerHTML = resultHTML;

      </script>
   </body>
</html>

Figure 3-7 displays the output of this script in both IE7 and IE8 Standards Modes. In IE7 mode, the first five attributes in the attributes collection exist (onresizeend, onrowenter, aria-haspopup, ondragleave, and onbeforepaste), albeit with empty values. In IE8 mode, only the first three exist (class, id, style), with coincide with the attributes explicitly set in the page markup; the last two accesses return null objects instead of empty attributes.

Screenshots verifying attribute ordering changes between IE document modes

Figure 3.7. Screenshots verifying attribute ordering changes between IE document modes

Accessing an Element's Class Information

An element's class information can be obtained in JavaScript via two methods: either through the className property of an element object, or through the getAttribute() method present on that same object. In IE7 and below (as well as in IE8's IE7 Standards Mode), the class attribute was accessible via getAttribute() when the string "className" into it. This did not conform to established standards.

Developers wishing to access the class attribute via getAttribute() must use the string "class" instead of "className" to access an element object's class attribute. The use of "className" will raise a TypeError exception.

The JavaScript in Listing 3-13 attempts to access the class attribute information of <div id="classTest"> three ways: by using the .className property, getAttribute("className"), and getAttribute("class"). The first two methods work properly in IE7 mode, whereas the last throws an exception. IE8 mode results in the same TypeError exception, but when calling getAttribute("className"); the call to getAttribute("class") is successful.

Example 3.13. Script that accesses element information using the class property and getAttribute() method

<html>
   <head>
      <title>Accessing an Element's Class Information</title>
   </head>
   <body>
      <h3>&lt;div&gt; "classTest", getAttribute("class"):
          <span id="attrClass"></span></h3>
      <h3>&lt;div&gt; "classTest", getAttribute("className"):
          <span id="attrClassName"></span></h3>
      <h3>&lt;div&gt; "classTest", property .className:
          <span id="propClassName"></span></h3>
      <div id="classTest" class="testClass"></div>
      <script type="text/javascript">

         //  Get the <div id="testClass"> element
         var divClassTest = document.getElementById("classTest");

         //  Attempt to access the attribute via getAttribute "class"
         try {
            var attrClass = divClassTest.getAttribute("class");
            setText(document.getElementById("attrClass"), attrClass.toString());
         } catch(e) {
            setText(document.getElementById("attrClass"), "—");
         }

         //  Attempt to access the attribute via getAttribute "className"
         try {
            var attrClassName = divClassTest.getAttribute("className");
            setText(document.getElementById("attrClassName"), attrClassName.toString());
         } catch(e) {
            setText(document.getElementById("attrClassName"), "—");
         }

         //  Attempt to access the attribute via property "className"
         try {
            var attrClass = divClassTest.className;
            setText(document.getElementById("propClassName"), attrClass.toString());
         } catch(e) {
            setText(document.getElementById("propClassName"), "—");
         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Figure 3-8 highlights this—the leftmost page, running in IE7 Standards Mode, displays the class attribute value of a <div> through the .className property as well as through getAttribute("className"). The page running in IE8 mode displays the class value through the .className property as well, but must use "class" to access the it via getAttribute().

Accessing the class information in both IE7 and IE8 Standards Modes

Figure 3.8. Accessing the class information in both IE7 and IE8 Standards Modes

Persisting Data with DOM Storage

Implementing interoperable data persistence in a web application is a difficult, especially since no well-adopted standard exists to support it. Traditionally, cookies have been used to achieve such persistent storage. Cookie storage has its drawbacks—there are size limitations for each cookie (4KB for IE7 and below, and 10KB in IE8), domains are limited to a fixed number of cookies, cookies are sent back and forth through every HTTP transaction, and each browser handles cookies in a slightly different way.

There has been a number of attempts to fill this void; Flash, Google Gears, and IE5's UserData feature were either created or used to circumvent the limitations of cookies. Each method has major drawbacks and no single one has solved the problem of interoperability.

The HTML 5 specification takes another crack at this problem with its DOM Storage features. Internet Explorer 8 implements these features to all document modes.

Objects

  • Storage object - Generic object that defines a storage mechanism. It complies with the HTML 5 DOM Storage specification.

  • sessionStorage object - Subclass of the Storage object that stores information for a single browser session.

  • localStorage object - Subclass of the Storage object that stores information across multiple browser sessions.

Methods

  • getItem(key) method - Returns a value in a Storage object identified by a key.

  • key(index) method - Returns a key located at the specified collection index.

  • removeItem(key) method - Removes an item from a Storage object specified by key.

  • setItem(key, value) method - Sets a value into a Storage object identified by a key.

  • clear() method - Clears all key/value pairs currently set in a Storage object.

Properties

  • length property - Returns the length of a Storage object's key/value list.

  • remainingSpace property - Returns the remaining space (in bytes) in a Storage object.

  • (expando) - Returns the value associated from a key, providing that the key name is not the same as a reserved name of a Storage object.

Events

  • onstorage event - Fired whenever a key/value pair is created, modified, or deleted.

The localStorage and sessionStorage objects both derive from the Storage object. They each contain the same methods, properties, and events. They differ only in their level of persistence; sessionStorage only persists its contents for the lifetime of a "session." Session in this case does not mean a browser session (such as those used by cookies)—it refers to data stored by a page and frames contained within a tab whose persistence lasts only for the lifetime of that tab. sessionStorage does not persist data between tabs and there is no reliable way to use this object to share data between frames.

Aside from the length of persistence, these objects are otherwise interchangeable; developers can switch between the two simply by changing the object reference during instantiation.

Most properties and methods on the Storage object provide access to data through key/value pairs. Each key can be read from or written to through the get/set methods on a Storage object or with an expando; for example, a value under a key "foo" in storage object "myStorage" can be accessed either by calling myStorage.getItem("foo") or via the expando myStorage.foo. The length property returns the number of key/value pairs on the current subdomain, and the remainingSpace property returns the free space remaining in a domain's storage quota (this includes data from subdomains as well). Whenever a key/value pair is added or removed from a Storage object, the onstorage event is fired.

Listing 3-14 represents a webpage that implements DOM storage to persist data.

Example 3.14. An example of cross-browser storage using HTML 5 DOM Storage

<html>
   <head>
      <title>Persisting Data with DOM Storage</title>
   </head>
   <body>
      <h3>Current Value: <span id="curVal"></span></h3>
      <h3>Current Value from <i>expando</i>: <span id="curValExpando"></span></h3>
      Set new text value:&nbsp;&nbsp;
      <input id="inputVal" size="20" type="text">
      <p><input onclick="setStorageData();" type="submit" value="Save Data">&nbsp;&nbsp;
      <button onclick="clearItems();">Clear Data</button>
      <h3>DOM Store information for <span id="infoDomain"></span></h3>
      <b>Length:</b> <span id="infoLength"></span> items<br>
      <b>Remaining Space:</b> <span id="infoRemaining"></span> KB<br>
      <script type="text/javascript">

         //  Create variables for the storage type
         var storageObject = localStorage;

         //  Read DOM Storage data for this domain
         function getStorageData() {
//  Get the storage data via getItem. Make sure this value
            //  is string (IE Bug: empty values are returned as VT_NULL
            //  instead of a blank BSTR ("")
            var getItemData = storageObject.getItem('DOMStorageExample'),
            if(getItemData == null) getItemData = "";

            //  Sanitize the data with toStaticHTML (if available)
            try { getItemData = toStaticHTML(getItemData); }
            catch(e) { escape(getItemData); }

            //  Write getItem results to the screen
            document.getElementById("inputVal").value = getItemData;
            setText(document.getElementById("curVal"),
               (getItemData == "") ? "—" : getItemData);

            //  Get the value via an expando, again compensating for
            //  the VT_NULL bug
            var expandoData = storageObject.DOMStorageExample;
            if(expandoData == null) expandoData = "—";

            //  Sanitize the data with toStaticHTML (if available)
            try { expandoData = toStaticHTML(expandoData); }
            catch(e) { escape(expandoData); }

            //  Write expando results to the screen
            setText(document.getElementById("curValExpando"), expandoData);

            //  Write domain info
            setText(document.getElementById("infoDomain"), document.domain);

            //  Display length if available
            var remainingSpace = "—";
            try { if(storageObject.remainingSpace != null &&
               typeof storageObject.remainingSpace != typeof undefined)
               remainingSpace = String(Math.round(storageObject.remainingSpace / 1024));
            } catch(e) { }
            setText(document.getElementById("infoLength"), storageObject.length);

            //  Display remainingSpace if available
            var remainingSpace = "—";
            try { if(storageObject.remainingSpace != null &&
               typeof storageObject.remainingSpace != typeof undefined)
               remainingSpace = String(Math.round(storageObject.remainingSpace / 1024));
            } catch(e) { }
            setText(document.getElementById("infoRemaining"), remainingSpace);

         }

         //  Write data into DOM Storage for this domain
         function setStorageData() {

            //  Set the contents of the input box to the storage
            var newValue = String(document.getElementById("inputVal").value);
            storageObject.setItem('DOMStorageExample', newValue);
//  Re-display the storage data
            getStorageData();

         }

         //  Clear DOM Storage for this domain
         function clearItems() {

            //  Clear out data in the storage object
            storageObject.clear();

            //  Re-display the storage data
            getStorageData();

         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

The following variables and methods are defined in the script:

  • storageObject variable - References a Storage object. In this example, that object is localStorage; since this and sessionStorage are interchangeable, session storage can be used by changing the reference.

  • storageKey variable - Represents the key used by the page to save data into the storageObject. While this example only uses one key, webpages can utilize more than one to save site settings.

  • getStorageData() method - Called to retrieve data in the storage object and display it in the webpage.

  • setStorageData() method - Called to write data in the page's input box into the storage object.

  • clearItems() method - Used to clear all data out of the storage object.

When the page is loaded, the <body> onload event triggers the getStorageData() method to retrieve data already in storage. The "Save Data" button triggers the setStorageData() method; it takes the contents of the <input> textbox and saves it to the "DOMStorageExample" key of the storageObject. The "Clear Data" button calls the clearItems() method, which deletes all entries currently in the storageObject.

Note

In IE8, Empty <input> elements return a VARIANT_NULL value rather than a blank BSTR that the storage object expects. When this value is placed directly into DOM Storage, IE crashes. To work around this, always save the value of an <input> box to a string variable or explicitly type it using String(...) before saving it to a storage object.

In addition to using getItem() to access a value for a specific key, that value can be accessed via its expando on the Storage object. The example demonstrates this in getStorageData(), setStorageData(), and clearItems(); the innerText (via setText) of curValExpando is set using the storageObject.DOMStorageExample expando for the key named DOMStorageExample. The getStorageData() method also grabs and displays information about the current storage object: the domain it's setting data to, the number of keys currently in the object, and the remaining space (if available).

Webpage that uses DOM Storage to persist data

Figure 3.9. Webpage that uses DOM Storage to persist data

Figure 3-9 displays a screenshot of the DOM storage example in both IE8 and Firefox 3.5. Contents of the storage object are displayed on the page and, when the localStorage object is being used, that data remains available even after the browser is closed and reopened. Data typed into the input box can be saved to the storage object via the "Save Data" button. The storage object can be cleared by clicking on the "Clear Data" button.

DOM Storage and Subdomains

Key/value pairs in Storage objects cannot be shared between domains, nor can they be shared between domains and their subdomains. Domains and subdomains do share one storage area even though their respective data is inaccessible to each other; both fall under one limit of 10MB in IE8.

Webpage that uses DOM storage to persist data

Figure 3.10. Webpage that uses DOM storage to persist data

In Figure 3-10, pages hosted examples.proiedev.com and subdomain.examples.proiedev.com both write to matching keys in their respective Storage objects. Even though they both reside on proiedev.com, they cannot access each other's data even when using the same key. They are, however, restricted to the same amount of shared space; they each must share one 10MB storage area.

Two subdomains that share the same domaion and storage limits yet can't share data

Figure 3.11. Two subdomains that share the same domaion and storage limits yet can't share data

Securing Persisted Storage

Persisted storage is a double-edged sword—while it enables offline scenarios for web applications, it also introduces a new class of security issues pertaining to data integrity.

Storage objects are isolated to the domains and subdomains accessing those objects. Data stored from one domain is not accessible from another, even if values are stored using the same key. For instance, a key/value pair saved from proiedev.com is not accessible from ie-examples.com. Domains and their subdomains share one 10MB block for data storage, but they cannot share data between each other. These domain restrictions also apply to the quota of Storage objects; only domains which have access to a certain Storage object can save data to that object.

Internet Explorer provides basic data security for storage objects based on context and origin policies. Developers, however, should not rely upon these mechanisms for complete data security; some attacks, such as cross-site scripting exploits, circumvent same-origin policy. Sensitive data (such as personally identifiable information) generally should not be kept in DOM storage; if there is no other option, strong encryption should be used. Data should be selectively removed with the removeItem() method or completely deleted using clear() if there is no need for it to be persisted. End-users can clear these items through Internet Explorer's Delete Browsing History feature.

Moving towards HTML 5 Storage

HTML 5 DOM Storage finally offers web developers a great option for persistent, cross-browser storage. Unfortunately, older versions of IE and other major browsers don't support this model yet; until these are eventually phased out, developers will need to use older methods to persist data in these scenarios.

A number of libraries create an abstraction layer this and older methods of persistent storage. These allow pages to take advantage of HTML 5 Storage when available and fall back to older methods when it isn't. Here are some common frameworks that were available at the time of publication:

  • PersistJS - Standalone, dedicated persisted storage library. No prerequisites. (http://link.proiedev.com/persistjs)

  • Dojo Offline - Plugin that works with the Dojo Library. Requires the Dojo library. (http://link.proiedev.com/dojooffline)

  • jQuery jStore - Plugin for jQuery. Requires the jQuery library. (http://link.proiedev.com/jstore)

Networking and Connectivity

The network and connectivity changes in Internet Explorer 8 and higher consist of new features and updates to old ones that ensure web applications can operate regardless of network connectivity.

Online and Offline Events

The window.navigator.onLine property was introduced in Internet Explorer 4 as a way for webpages to know when the browser was running "Offline Mode." This flag did not report the state of network connectivity. AJAX applications that provide offline features have worked around this limitation by "pinging" remote severs with XMLHttpRequest, however this method isn't completely reliable. Internet Explorer 8 and higher now use this property to reflect both the state of network connectivity and whether or not the browser is running in Offline Mode.

Properties

  • onLine property - Indicates whether or not the system is connected to the network with a Boolean value. When connected to a network and when IE is not running in "Offline Mode," true is returned; false is returned otherwise. In IE7 and below, this property indicates only if the browser is running normally (true) or in "Offline Mode" (false).

Events

  • onoffine event - Fired whenever Internet Explorer detects a loss of network connectivity or the browser has entered "Offline Mode."

  • ononline event - Fired whenever Internet Explorer detects that network connectivity has resumed or the browser has left "Offline Mode."

The events and property changes in the window.navigator and window.clientInformation objects are available to pages running in any document mode.

The code in Listing 3-15 uses three events to gather and act on connectivity information: onload, ononline, and onoffline. The onload event calls the onLoad() method; it gathers and displays the initial connectivity state from the window.navigator.onLine property. The onoffline event calls the onOffline() method whenever connectivity is lost; the method in this sample writes "Offline" to the page. ononline acts in the opposite manner, calling onOnline() when connectivity is restored and, in this case, writing "Online" to the page.

Example 3.15. Sample code demonstrating online/offline property and events

<html>
   <head>
      <title>Online/Offline Events</title>
   </head>
   <body ononline="onOnline();" onoffline="onOffline();" onload="onLoad();">
      <h3>Connectivity Status: <span id="status"></span></h3>
      <script type="text/javascript">

         //  When the browser goes online, write "Online" to the page
         function onOnline() {
            setText(document.getElementById("status"), "Online");
         }

         //  When the browser goes offline, write "Offline" to the page
         function onOffline() {
            setText(document.getElementById("status"), "Offline");
         }

         //  When the page loads, display the current connectivity status
         function onLoad() {
            setText(document.getElementById("status"),
               (window.navigator.onLine) ? "Online" : "Offline");
         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Figure 3-11 shows this sample code running in both IE7 and IE8 Standards Modes. The browser in this scenario initially connected to the internet and running the page in IE8 Standards Mode. The page is reloaded in IE7 mode, quickly followed by a good yank on the CAT-5 cable connected to the client machine. Once the connection is severed, the onoffline event is fired and the page reads "Offline."

Webpage that displays current connectivitiy information

Figure 3.11a. Webpage that displays current connectivitiy information

XMLHttpRequest Timeout Events

Internet Explorer 8 and higher include a timeout property and ontimeout event in the XMLHttpRequest object. Scripts using this object can apply a hard limit on the time a request should take using this property, and receive an event when this limit is reached.

Properties

  • timeout property - The amount of time in milliseconds that the XMLHttpRequest object should wait for a response from a target. The ontimeout event is raised when this limit is reached and the request is aborted.

Events

  • ontimeout event – Fires an associated callback function when time elapsed between the a XMLHttpRequest.send() call and the current time is reached.

Listing 3-16 is a webpage that uses server-side script (in this case, PHP) to simulate a webpage taking a long time to load. Whenever a request to this page is received by the server, the script pauses 5 seconds (with sleep(5)) before responding with the text "Success!"

Example 3.16. Webpage that forces a server to wait for 5 seconds before responding

<?php

   //  Simulate a page that takes 5 seconds to respond.  Once finished,
   //  return the string "Success!"
   sleep(5);
   echo "Success!";

?>

Listing 3-17 demonstrates a script that requests the page in Listing 3-16 using two XMLHttpRequest objects: xhrSmallTimeout and xhrLargeTimeout. The xhrSmallTimeout object requests the above page, but limits the wait time for that request to 2 seconds using its timeout property. The xhrLargeTimeout object attempts to pull down the same page, but allots a maximum of 10 seconds for a response. Each object has two callback methods: one for ontimeout (timeoutRaisedSmall() and timeoutRaisedLarge()), fired if a request's timeout is reached, and the other for onreadystate (readyStateHandlerSmall() and readyStateHandlerLarge()), fired during steps of the request/response process. Exceptions thrown during execution of the onreadystate callbacks are written to <div id="exceptions">.

Example 3.17. Code sample using the XMLHttpRequest timeout event

<html>
   <head>
      <title>XMLHttpRequest Timeout</title>
   </head>
   <body>
      <h3>Request with 2s timeout: <span id="xhrSmallStatus"></span></h3>
      <h3>Request with 10s timeout: <span id="xhrLargeStatus"></span></h3>
      <h3>Exceptions:</h3>
      <div id="exceptions"><p></div>
      <script type="text/javascript">

         //  Define the XMLHttpRequest variables, the target page and
         //  elements that will be written to
         var xhrSmallTimeout; var xhrLargeTimeout;
         var targetPage = "http://examples.proiedev.com/03/networking/timeout/result.php";
         var spanXhrSmallStatus = document.getElementById("xhrSmallStatus");
         var spanXhrLargeStatus = document.getElementById("xhrLargeStatus");
         var spanExceptions     = document.getElementById("exceptions");


         function displayException(e, objectName) {
            spanExceptions.innerHTML += "<b>Object:</b> " + objectName + "<br>"
                                     +  "<b>Name:</b> " +  e.name + "<br>"
                                     +  "<b>Message:</b> " + e.message + "<p>";
         }

         //  Timeout callback for request with 2s timeout
         function timeoutRaisedSmall(){
            setText(spanXhrSmallStatus, "Timeout");
         }

         //  Timeout callback for request with 10s timeout
         function timeoutRaisedLarge() {
            setText(spanXhrLargeStatus, "Timeout");
         }

         //  readyState callback for request with 2s timeout.  If an
         //  exception is raised, write it to the screen.
         function readyStateHandlerSmall() {
            if(xhrSmallTimeout.readyState == 4) {
               try {
                  setText(spanXhrSmallStatus, xhrSmallTimeout.responseText);
                } catch(e) { displayException(e, "xhrSmallTimeout"); }
            }
         }

         //  readyState callback for request with 10s timeout.  If an
         //  exception is raised, write it to the screen.
         function readyStateHandlerLarge() {
            if(xhrLargeTimeout.readyState == 4) {
               try {
setText(spanXhrLargeStatus, xhrLargeTimeout.responseText);
                } catch(e) { displayException(e, "xhrLargeTimeout"); }
            }
         }

         // Create a XMLHttpRequest object with a small (2 second)
         // timeout period
         xhrSmallTimeout = new XMLHttpRequest();
         xhrSmallTimeout.open("GET", targetPage, true);
         xhrSmallTimeout.timeout = 2000;
         xhrSmallTimeout.ontimeout = timeoutRaisedSmall;
         xhrSmallTimeout.onreadystatechange = readyStateHandlerSmall;

         // Create a XMLHttpRequest object with a large (10 second)
         // timeout period
         xhrLargeTimeout = new XMLHttpRequest();
         xhrLargeTimeout.open("GET", targetPage, true);
         xhrLargeTimeout.timeout = 10000;
         xhrLargeTimeout.ontimeout = timeoutRaisedLarge;
         xhrLargeTimeout.onreadystatechange = readyStateHandlerLarge;

         // Sent the XMLHttpRequests
         xhrSmallTimeout.send(null);
         xhrLargeTimeout.send(null);

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Figure 3-12 shows the sample code being run. The timeoutRaisedSmall() method is called and "Timeout" is shown on the page 2 seconds after the xhrSmallTimeout object makes a request (since the server won't respond for at least 5 seconds). The onreadystate event is raised after the timeout is hit, resulting in an exception—while the request technically completed (readyState == 4), the responseText returns a null type rather than a String. The second request made by xhrLargeTimeout finishes after about 5 seconds, triggering readyStateHandlerLarge() to write the associated responseText to the page. The method timeoutRaisedLarge() is never called since the second request completes before the 10 second limit.

Webpage demonstrating the XMLHttpRequest timeout event

Figure 3.12. Webpage demonstrating the XMLHttpRequest timeout event

This example raises an important point about XMLHttpRequest timeouts: transactions that timeout before completion are considered "finished" by the browser, thus onreadystatechange callbacks are fired with their readyState indicates completion. The responseXML and responseText properties of those objects, however, are not available. Scripts using the timeout property and ontimeout event must use onreadystatechange event handlers that fail gracefully when these properties are null.

AJAX Navigation Events

Many AJAX applications use in-page navigations (often called hash or fragment navigations) to trigger events or change states. In-page navigations allow pages to update the address bar without making a full navigation request; they give users a way to save a page's state through bookmarks or email a that state to someone else. Mapping sites like Google Maps and MapQuest are great examples—they use in-page navigations to zoom-in on a place or display a window to get directions to a location.

Address bar showing in-page AJAX navigations

Figure 3.13. Address bar showing in-page AJAX navigations

In older browsers and versions of Internet Explorer prior to IE8, these navigations were never appended to the travel log unless an anchor tag with the same value as the hash existed on the page (for more information on the travel log, see Chapter 1) The back and forward buttons were rendered useless in these cases, and users needed to rely on the web application to provide similar functionality.

IE8 breaks from this old behavior by allowing developers to add in-page navigations to the travel log when necessary. This feature is opt-in—webpages are given a chance to append hash information to the navigation history whenever the onhashchanged event is fired. Only pages running in IE8 Standards Mode and above may catch this event and persist navigations.

Properties

  • location.hash property - Returns the fragment component of the current URL, including the leading hash (#) mark. This property is read/write (in IE8 Standards Mode and above).

Events

  • onhashchange event - Fired when an in-page navigation occurs either by a user (through the IE user interface) or script (through navigation methods). This event is not fired during page load; an onload Event handler can examine the location.hash property and adjust the application's state accordingly.

The sample code in Listing 3-16 is a "Search Provider Generator." Search Provider extensions, discussed in Chapter 10, add search engines into the search box in Internet Explorer, Firefox, and any other browsers that support the OpenSearch XML format. Figure 3-14 gives a bit more context; it shows the search boxes in both in IE and Firefox where these extensions live.

Search boxes (top-left input box in each window) of IE8 and Firefox 3.5

Figure 3.14. Search boxes (top-left input box in each window) of IE8 and Firefox 3.5

The printed sample here omits the finer details of Search Providers and focuses mainly on how this tool uses AJAX navigation.

Concepts presented here represent a "wizard"-style interface; there are a series of ordered steps represented as panels. These panels either present information or wait for user action, the same concept used by setup applications. In-page navigations are a great solution for wizards; the hash can be used to store a user's current position within the wizard. In this case, there are four steps:

  1. Ask the user for the name of a Search Engine.

  2. Have the user perform a search query using the word "TEST" and place the URL of the result into a textbox.

  3. Ask the user for some metadata.

  4. Provide the user with information—in this case, a new Search Provider extension that can be either installed or downloaded to the system.

Figure 3-15 shows each of these four panels.

Webpage using in-page navigations to display a wizard interface with four steps/panels

Figure 3.15. Webpage using in-page navigations to display a wizard interface with four steps/panels

Listing 3-18 highlights the code for this example that pertains to in-page navigations. The page begins by throwing the onload event, which calls the onHashChange() method. This method reads the current hash value; it represents the current position within the wizard (which can range from minStep ()1 to maxStep (4). If the window.location.hash value is empty or outside of this range, the number is set to minStep. The loadPanelForStep() method is called once the hash is normalized; this method displays the panel <div> associated with the current step.

Example 3.18. Sample code for the Search Provider Generator using in-page navigation

<html>
   <head>
      <title>AJAX Navigation</title>
   </head>
   <body>
      <h2 id="title">Search Provider Wizard</h2>
      <div id="stepPanel1"> <!-- ... --> </div>
      <div id="stepPanel2"> <!-- ... --> </div>
      <div id="stepPanel3"> <!-- ... --> </div>
      <div id="stepPanel4"> <!-- ... --> </div>
<p>
      <button id="previousStep" onclick="previous()">Previous Step</button>&nbsp;&nbsp;
      <button id="nextStep" onclick="next()">Next Step</button>
      <script type="text/javascript">

         //  Define the current, min, and max steps.
         var currentStep = 1;
         var minStep = 1; var maxStep = 4;

         //  ...

         function next() {

            //  Stop if the current step is already at/above max
            if(currentStep >= maxStep) return;

            //  Increase the current position, set that position to the
            //  hash, and display the panel for this new step
            currentStep++;
            window.location.hash = currentStep;
            loadPanelForStep();

         }

         function previous() {

            //  Stop if the current step is already at/below min
            if(currentStep <= minStep) return;

            //  Decrease the current position, set that position to the hash, and
            //  display the panel for this new step
            currentStep--;
            window.location.hash = currentStep;
            loadPanelForStep();

         }

         function onHashChange() {

            //  Grab the step specified by the hash as an Integer
            var hashStep = parseInt(window.location.hash.substr(1));

            //  If the hash value isn't a valid number, start at the first step. If
            //  it is out of bounds, snap to the closest limit.  Otherwise, set the
            //  current step to be the one specified in the hash
            if(isNaN(hashStep))         currentStep = minStep;
            else if(hashStep < minStep) currentStep = minStep;
            else if(hashStep > maxStep) currentStep = minStep;
            else                        currentStep = hashStep;

            //  Display panel for the current step
            loadPanelForStep();

         }
function loadPanelForStep() {

            //  Disable the previous button if on the first panel and
            //  disable the next button of on the last
            document.getElementById("previousStep").disabled
               = (currentStep <= minStep) ? true : false;
            document.getElementById("nextStep").disabled
               = (currentStep >= maxStep) ? true : false;

            //  Show the current panel and hide all others
            for(var i = minStep; i <= maxStep; i++) {
               var display = ((i == currentStep) ? "block" : "none");
               document.getElementById("stepPanel" + i).style.display = display;
            }

            //  ...

         }

         //  ...

      </script>
   </body>
</html>

The "Previous Step" and "Next Step" buttons (previousStep and nextStep, respectively) move backwards and forward through the steps in the wizard. When previousStep is clicked, its onclick handler subtracts 1 from the currentStep position if possible and calls loadPanelForStep() to display the panel for this new value. The nextStep button does the same, but instead increases currentStep by 1. Since these panels are located on the same page, the data input into each remains while the user remains within the page context. Finally, each of these methods set the new hash value to window.location.hash—this "registers" the new in-page navigation with the travel log, thus making it available to IE's back and forward buttons as well as navigation history.

The last major piece of AJAX navigation is integration with the browser's forward and back buttons. The onhashchange event handler of the <body> tag is raised whenever these buttons refer back to a location within the page; to grab these events, the same onHashChange() method used for the onload handler is used to catch these events. This method reads the current hash value, normalizes it, and navigates to the step provided by this value.

Concurrent Connections

The HTTP 1.0 and 1.1 specifications suggest limits to the number of concurrent connections a requesting application (in this case, the browser) should make to each host server. These limits were based upon the infrastructure of the era (the 1990s). While the limits were wise at the time, improvements in network infrastructure have caused most browser developers to increase their connection limits.

Current versions of major browsers increased connection limits in order to speed page load and allow AJAX applications to utilize more concurrent connections. Internet Explorer 8 increases this number to 6 connections per domain in non-modem configurations. Comparative values are shown in Table 3-1.

Table 3.1. Table listing concurrent connections-per-host limits

 

IE <= 7

IE 8

HTTP 1.0 Server over modem

4

4

HTTP 1.1 Server over modem

2

2

HTTP 1.0 Server

4

6

HTTP 1.1 Server

2

6

IE8 also exposes a new property on the window object called macConnectionsPerServer. Scripts can query this property, potentially offering varying behavior and functionality based on the number of concurrent connections available.

Properties

  • maxConnectionsPerServer property - Returns number of connections available per domain based on the server HTTP version and the connection type. This property is read-only.

The page in Listing 3-19 attempts to download eight images from a server located at examples.proiedev.com.

Example 3.19. Code sample that highlights how concurrent connections operate

<html>
   <head>
      <title>Concurrent Connections</title>
   </head>
   <body>
      <h3>Maximum connections per host: <span id="maxCon"></span></h3>
      <img src="images/1.jpg"><br>
      <img src="images/2.jpg"><br>
      <img src="images/3.jpg"><br>
      <img src="images/4.jpg"><br>
      <img src="images/5.jpg"><br>
      <img src="images/6.jpg"><br>
      <img src="images/7.jpg"><br>
      <img src="images/8.jpg">
      <script type="text/javascript">

         //  If the number of maxConnectionsPerServer is readable from
         //  script, display that value to the screen
         var spanMaxCon = document.getElementById("maxCon");
         if(window.maxConnectionsPerServer)
            setText(spanMaxCon, window.maxConnectionsPerServer);
         else setText(spanMaxCon, "—");

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }
</script>
   </body>
</html>

The page, when running in IE8 and using a broadband connection, can download up to 6 items at a time from the server. In theory, this means the first six images in the sample will start downloading around the same time, and the seventh will begin when the first completes.

Network timeline highlighting concurrent connections

Figure 3.16. Network timeline highlighting concurrent connections

Figure 3-16 shows a network request timeline generated with Excel based on timing information captured by the Fiddler Web Debugger. When the example page is loaded, Internet Explorer checks the number of connections per host. Trident's parser subsystem (described in Chapter 1) issues download requests for embedded resources. The download subsystem begins issuing requests until the connections-per-host limit is reached, at which point subsequent requests are queued until a connection becomes free. In this case, six image files are downloading concurrently, and the seventh begins when an earlier request completes and frees a connection slot.

The 6 connection limit is directly tied to a specific hostname. Some websites use this definition to circumvent browser connection limits; by using multiple hostnames or CDNs (Content Delivery Networks), pages can increase the number of available concurrent connections. For example, a page served from the hostname examples.proiedev.com could increase the number of images downloaded concurrently by a client browser by serving some content on another hostname like www.proiedev.com.

Communicating Across Pages and Domains

The use of mixed content or mixed origins carries with it a risk of cross-site scripting vulnerabilities and request forgeries. Developers must take care to ensure that data transferred between origins is sanitized before it's use. The next few sections outline features of IE8 that help developers build secure cross-domain and cross-site communication channel which such mixing is necessary.

Cross-Domain Requests

The XDomainRequest object (and enhanced versions of XMLHttpRequest found in Firefox 3.5 and Safari 4) provides an asynchronous communication channel for domains with a pre-established trust relationship.

XDomainRequest is similar to XMLHttpRequest in terms of core functionality—one page (the requestee) makes a request to a remote page (the requestor) and, upon receiving and validating the request, the requestor returns a response to the requestee. Unlike XMLHttpRequest, XDomainRequest assumes that it's being used by two origins that have a trust relationship. The requestor confirms such trust by including the requestee's domain in a response header. It omits certain security features such as credential handling and cookie support as a result.

Objects

  • XDomainRequest objectA lightweight, asynchronous request object that enforces cross-domain access controls defined by the W3C Access Control for Cross-Site Requests standard.

Properties

  • contentType property – Same as XMLHttpRequest.contentType.

  • responseText property – Same as XMLHttpRequest.responseText.

  • timeout property – Same as XMLHttpRequest.timeout.

Methods

  • abort method – Same as XMLHttpRequest.abort().

  • open method – Same as XMLHttpRequest.open().

  • send method – Same as XMLHttpRequest.send().

Events

  • onload event - Raises a callback method when the requested server returns a response. This is similar the onreadystatechange event in XMLHttpRequest, but it only fires when a transaction is complete (thus there is no readyState property).

  • onerror event – Same as XMLHttpRequest's onerror event.

  • onprogress event – Same as XMLHttpRequest's onprogress event.

  • ontimeout event – Same as XMLHttpRequest's ontimeout event.

The XDomainRequest object does not have an onreadystatechange event. Instead, the onload event is fired when a transaction is finished. This coincides with the "completed" state (or readyState == 4) of the XMLHttpRequestObject's onreadystatechange event.

Asynchronous, origin-restricted communication is not limited to IE8. Firefox 3.5 and Safari 4 have also added this functionality. Instead of using the XDomainRequest object, this functionality was placed directly on the XMLHttpRequest object. This topic is discussed in-depth in the following sections.

Note

When writing scripts that are expected to run in all browsers that support Cross-Domain Requests, use the XDomainRequest object in IE8 and XMLHttpRequest for other browsers such as Firefox 3.5+ and Safari4+.

Building the Request

The sample page in Listing 3-20 attempts to access origin-restricted resources found at both examples.proiedev.com and www.ie-examples.com. The origin of the requesting page is http://examples.proiedev.com. Script in this sample uses either Internet Explorer 8's XDomainRequest object or origin-controlled versions of XMLHttpRequest (in browsers that support it) to attempt a trusted connection with the server. The proper object is chosen by detecting the browser with the Peter-Paul Koch's BrowserDetect.js script; it can be downloaded from http://link.proiedev.com/browserdetect.

Example 3.20. Code sample using Cross-Domain Requests

<html>
   <head>
      <title>Cross-Domain Requests</title>
   </head>
   <body>
      <div id="resultDiv">
         <h3>Request to examples.proiedev.com: <span id="xdrAllowed"></span></h3>
         <h3>Request to www.ie-examples.com: <span id="xdrBlocked"></span></h3>
      </div>
      <!-- Include PPK's BrowserDetect script from QuirksMode.org -->
      <script type="text/javascript" src="browserdetect.quirksmode.js"></script>
      <script type="text/javascript">

         //  Create variables to hold XDomainRequest or instances of
         //  XMLHttpRequest that offer origin header support
         var xdrAllowed = null;
         var xdrBlocked = null;

         //  Callback functions for onload and onerror events in
         //  XDomainRequest/XMLHttpRequest
         function onLoadAllowed()  { displayEvent("xdrAllowed", xdrAllowed.responseText); }
         function onLoadBlocked()  { displayEvent("xdrBlocked", xdrBlocked.responseText); }
         function onErrorAllowed() { displayEvent("xdrAllowed", "Error") }
         function onErrorBlocked() { displayEvent("xdrBlocked", "Error") }

         //  Create XDomainRequest objects (or XMLHttpRequest objects
         //  if not IE and on a browser that offers origin header support)
         if(window.XDomainRequest) {
            xdrAllowed = new XDomainRequest(); xdrBlocked = new XDomainRequest();
         } else if((BrowserDetect.browser = "Firefox" && BrowserDetect.version > 3.5) ||
                   (BrowserDetect.browser = "Safari"  && BrowserDetect.version > 4)) {
            xdrAllowed = new XMLHttpRequest(); xdrBlocked = new XMLHttpRequest();
         }

         //  Only proceed if cross-domain protections are available
//  otherwise write a "not-available" message to the page
         if(xdrAllowed != null && xdrBlocked != null) {

            //  Point the xdrAllowed object to examples.proiedev.com
            xdrAllowed.onload  = onLoadAllowed;
            xdrAllowed.onerror = onErrorAllowed;
            xdrAllowed.open("GET",
               "http://examples.proiedev.com/03/xcomm/xdr/alloworigin/allow.php",
               true
               );

            //  Point the xdrAllowed object to www.ie-examples.com
            xdrBlocked.onload  = onLoadBlocked;
            xdrBlocked.onerror = onErrorBlocked;
            xdrBlocked.open("GET",
               "http://www.ie-examples.com/03/xcomm/xdr/alloworigin/block.php",
               true
               );

            //  Send requests to each domain
            xdrAllowed.send(null);
            xdrBlocked.send(null);

         } else displayNotAvailableMessage();

         //  Display a message on the page indicating origin header support
         //  isn't available on the current browser
         function displayNotAvailableMessage() {
            var resultDiv = document.getElementById("resultDiv")
            resultDiv.innerHTML = "<h3>XDomainRequest and origin-restricted XMLHttpRequest "
                                + "are not available in this browser.</h3>";
         }

         //  Generic function to display data in an element
         function displayEvent(id, text) {
            var element = document.getElementById(id);
            setText(element, text);
         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

The xdrAllowed and xdrBlocked objects are initialized to either an XDomainRequest or an XMLHttpRequest object. They each register onload and onerror event handlers and open a GET request to http://examples.proiedev.com and http://www.ie-examples.com, respectively.

After the page loads, the xdrAllowed and xdrBlocked objects open a connection with their respective servers. If a given request is successful, the onload event callback is executed; the onerror callback is run if that request is denied.

Building the Response

Webpages that receive a request via XDomainRequest or access-control-enabled XMLHttpRequest objects may use the Access-Control-Allow-Origin HTTP response header to indicate whether or not a resource is accessible. The header may contain one of two values pertaining to allowed origins: either an asterisk (*), which indicates the response page is intended for all origins, or a specific origin containing a protocol and domain (e.g. http://examples.proiedev.com). This header does not support multiple URLs, but a web application can simulate this functionality by serving different headers through logic switching using the HTTP request's Origin value.

Example 3.21. Response allowing requests from examples.proiedev.com

<?php

     header('Access-Control-Allow-Origin: http://examples.proiedev.com'),

   header('Content-Type: text/plain'),
   echo "Success!";
?>

Listing 3-21 is a sample response page requested by the xdrAllowed object in Listing 3-20. In this case, the server includes an Access-Control-Allow-Origin-Header header in its response, pointing to http://examples.proiedev.com. Since the requesting page matched this origin, IE displayed the response data after reading the server headers.

Listing 3-22 is the same exact response page that was used in the prior example. Unlike the other response, this page is hosted on the domain www.ie-examples.com and requires the origin of any request derive from http://www.ie-examples.com. The xdrBlocked object throws an error once IE sees that the allowed origin doesn't match the page making the request.

Example 3.22. Response allowing requests from www.ie-examples.com

<?php
   header('Access-Control-Allow-Origin: http://www.ie-examples.com'),
   header('Content-Type: text/plain'),
   echo "Success!";
?>

Putting it Together: The Request/Response Sequence

Examples in Listings 3-20, 3-21, and 3-22 represent a sample a request/response sequence using the cross-domain request object.

The example page in Figure 3-17 is a screenshot of the request page in Listing 3-20. On load, the page makes two Cross-Domain Requests from the URL http://examples.proiedev.com. The first request made is by the xdrAllowed object to a page hosted on the same domain, http://examples.proiedev.com. The response handler in Listing 3-21 allows for requests originating from http://examples.proiedev.com, thus the transaction is successful and the transaction's responseText value is displayed after xdrAllowed's onload even is raised.

Webpage demonstrating Cross-Domain Requests in IE8

Figure 3.17. Webpage demonstrating Cross-Domain Requests in IE8

The second request is made by xdrBlocked to a response page hosted on a different domain, http://www.ie-examples.com. The second response page from Listing 3-22 only allows for requests originating from http://www.ie-examples.com, thus it denies the access request. The xdrBlocked object raises its onerror event upon rejection, and the callback displays the word "Error" on the screen.

Building Interoperable Cross-Domain Requests

The relatively recent introduction of the W3C Access Control for Cross-Site Requests (the standard covering cross-domain request functionality) means that current cross-browser support is limited. At the time of IE8's release, Firefox 3.5 and Safari 4 supported this specification as an extension of the XMLHttpRequest object, whereas IE uses a separate XDomainRequest object. IE and other browsers also differ in the access control headers they support; IE, for instance, only recognizes the Access-Control-Allow-Origin header, whereas Firefox and Safari implement most if not all headers outlined in the W3C's specification.

Listing 3-23 provides some sample code that demonstrates a cross-browser implementation of cross-domain access control in JavaScript.

Example 3.23. Cross-Browser compatible Cross-Domain Request sample

<html>
   <head>
      <title>Building Interoperable Cross-Domain Requests</title>
   </head>
   <body>
      <div id="resultDiv">
         <h3>XDR over XDomainRequest? <span id="useXDR">No</span></h3>
         <h3>XDR over XMLHttpRequest? <span id="useXHR">No</span></h3>
      </div>
      <!-- Include PPK's BrowserDetect script from QuirksMode.org -->
      <script type="text/javascript" src="browserdetect.quirksmode.js"></script>
      <script type="text/javascript">

         //  Create variables to hold XDomainRequest or instances of
         //  XMLHttpRequest that offer origin header support
         var xdrAllowed = null;
         var xdrBlocked = null;
//  Create XDomainRequest objects (or XMLHttpRequest objects
         //  if not IE and on a browser that offers origin header support)
         if(window.XDomainRequest) {
            xdrAllowed = new XDomainRequest(); xdrBlocked = new XDomainRequest();
            setText(document.getElementById("useXDR"), "Yes");
         } else if((BrowserDetect.browser = "Firefox" && BrowserDetect.version > 3.5) ||
                   (BrowserDetect.browser = "Safari"  && BrowserDetect.version > 4)) {
            xdrAllowed = new XMLHttpRequest(); xdrBlocked = new XMLHttpRequest();
            setText(document.getElementById("useXHR"), "Yes");
         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

The sample itself implements only those features of cross-domain access control that are interoperable: the existence of an object that supports access control, and the Access-Control-Allow-Origin header.

Note

In addition to a having a different object name versus other browsers (XDomainRequest versus XMLHttpRequest), Internet Explorer 8 only supports the Access-Control-Allow-Origin HTTP header.

Cross Frame Messaging with postMessage()

Cross-frame communication (that between a page and <iframe>) is been a pain point for developers building pages that contain multiple origins—commonly known as "mashups." IE and other browsers have put some strict security measures in place around what <iframe> objects and their hosts can do and how they can communicate. This isolation, however necessary, neuters communication between them. Developers have worked around this limitation by treating the document.location.hash as a mutual data store.

The HTML5 spec addresses establishes postMessage() and the onmessage event as a way for parents and their child <iframe> object to communicate. They can send and receive data between each other using this method as well as force the host browser to enforce origin-based restrictions on request.

Internet Explorer 8 implements this feature—the postMessage() method and onmessage event— for all document modes.

Objects

  • postMessage(msg [, targetOrigin]) method - Sends a message specified in the string msg. Further restrictions are placed through targetOrigin, which restricts the origin of content permitted to receive the message.

Events

  • onmessage event - Fired when a target window object receives a message sent using the postMessage() method. The message itself is stored in a property on the event object.

  • origin property - Returns the Origin of the document that sent the message.

  • data property - Returns the message sent by the origin document.

  • type property - The type of event, in this case message.

The code sample in Listing 3-24 shows a parent document that hosts an <iframe>. In IE8, these two documents can send messages to each other with postMessage. This page contains an <input> textbox and a submit button; the onclick event on the submit button sends the content of the <input> textbox to the iframe through postMessage located on the <iframe>'s contentWindow instance.

Example 3.24. Code for a document parent hosting a frame object and communicating with postMessage

<html>
   <head>
      <title>Cross Frame Messaging with postMessage</title>
   </head>
   <body>
      <h3>Parent Document (examples.proiedev.com)</h3>
      Post message to remote document (www.ie-examples.com):&nbsp;&nbsp;
      <input id="postDataInput" size="25" type="text">&nbsp;&nbsp;
      <input onclick="postToRemote();" type="submit" value="Post Message"><br><br>
      <iframe id="remoteFrame"
              src="http://www.ie-examples.com/03/xcomm/xdm/remote"
              width="400px" height="200px" class="highlightBorder"
              frameborder="no"></iframe>
      <script type="text/javascript">

         //  Post contents of an input box to a remote page
         function postToRemote() {

            //  Grab the input value from the box (explicitly type as a string to
            //  avoid pulling in a VT_NULL (IE Bug) and quit if empty
            var postData = String(document.getElementById("postDataInput").value);
            if (postData == "") return;

            //  Use postMessage to send the string message over to the remote page
            var remote = document.getElementById("remoteFrame");
            remote.contentWindow.postMessage(postData, "http://www.ie-examples.com");

         }

      </script>
   </body>
</html>

The parent page uses postMessage's targetOrigin parameter to restrict what page can receive the message; even if an injected script manages to change the location of the parent's <iframe>, IE will only transport the message to a target at http://www.ie-examples.com.

Listing 3-25 is the code for the "receiver" document hosted in the parent's <iframe>. The script opts-in to messages from the parent by attaching itself to the onmessage event handler; using attachEvent() in IE and with addEventListener() in other browsers. When script on the parent page calls postMessage(), the onmessage event is fired in this page and the receiveData() callback is executed. Script writes data from the event to the screen whenever a message is received.

Example 3.25. Code sample for child frame using the onmessage event

<html>
   <head>
      <title>Cross Frame Messaging with postMessage (Receiver)</title>
   </head>
   <body>
      <h3>Remote Page (www.ie-examples.com)</h3>
      Message Origin (e.origin): <span id="receivedDataOrigin"></span><br>
      Message Contents (e.data): <span id="receivedDataContents"></span><br>
      Message Type (e.type): <span id="receivedDataType"></span><br>
      <script type="text/javascript">

         //  Point the onmessage event callback to receiveData, either by
         //  using addEventListener or attachEvent
         if(window.addEventListener)
            window.addEventListener("message", receiveData, false);
         else window.attachEvent("onmessage", receiveData);

         //  Grab messages from the parent through this callback/event e
         function receiveData(e) {

            //  Make sure that the origin server is examples.proiedev.com
            if (e.data != "" && e.origin == "http://examples.proiedev.com") {

               //  Write message data to the webpage
               setText(document.getElementById("receivedDataOrigin"), e.origin);
               setText(document.getElementById("receivedDataContents"), e.data);
               setText(document.getElementById("receivedDataType"), e.type);

            }

         }

         //  Set element text (cross-browser innerText/textContent)
         function setText(element, text) { /*...*/ }

      </script>
   </body>
</html>

Figure 3-18 shows the pages described in the prior two code samples. The parent document, located at examples.proiedev.com, hosts an <iframe> that loads a page from www.ie-examples.com.

Sample page demonstrating Cross-Document Messaging with postMessage

Figure 3.18. Sample page demonstrating Cross-Document Messaging with postMessage

A The parent calls postMessage() method to send a message (the contents of an <input> box) to the child frame when the "Post Message" <input> is clicked. The child frame receives notification of the message from the onmessage event, and promptly displays that message to the user.

Note

IE does not permit webpages to communicate with popup windows or tabs through postMessage(), even those created by a webpage itself; Firefox and other browsers are more lenient.

Tips and Tricks for Secure Communication

The following tips and tricks provide insight into building websites that communicate across documents and domains securely.

  • Don't mix protocols. Ensure that HTTPS pages do not rely on content served over HTTP. Mixed content is more than just an annoying dialog; trusted connections could be compromised if insecure content is added to a secure document.

  • Sanitize incoming data. Web applications should sanitize all input being used by the web server. Data should be sanitized using server-side code and client-side methods such as toStaticHTML().

  • Use postMessage() to communicate between documents. It's reliable, broadly supported, and allows for mutually-suspicious cross-origin communication..

  • Protect against threats on both sides. Insecure connections are subject to man-in-the-middle attacks, and servers must not assume that the browser is a trustworthy client.

Summary

IE8's changes with respect to AJAX and JSON allow developers to take advantage of some more advanced scenarios that simplify development of dynamic web applications. I used the samples to provide ways of using these features in an interoperable way and have highlighted some of the pitfalls and quirks that you could encounter when testing these features in IE. In the next chapter, I'll focus on some more interoperability scenarios regarding the Document Object Model and IE's JavaScript engine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.136.165