Home | History | Annotate | Download | only in news_a11y
      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <style>
      5 body {
      6   font-family: helvetica, arial, sans-serif;
      7   font-size: 12px;
      8   overflow: hidden;
      9 }
     10 
     11 a {
     12   color:#0000CC;
     13   text-decoration: underline;
     14   cursor: pointer;
     15 }
     16 
     17 .open_box {
     18   display: block;
     19   overflow: hidden;
     20   margin-right: 4px;
     21   margin-top: 2px;
     22   height: 12px;
     23   width: 12px;
     24   float: left;
     25   clear: left;
     26   background-image: url(sprite_arrows.gif);
     27   background-position: 0px -24px;
     28   cursor: pointer;
     29 }
     30 
     31 .opened .open_box {
     32   background-position:-12px -24px;
     33 }
     34 
     35 .item {
     36   padding: 2px 0px;
     37 }
     38 
     39 .item_title {
     40   display: block;
     41   min-width: 300px;
     42   padding-left: 15px;
     43   cursor: pointer;
     44 }
     45 
     46 .item_desc {
     47   min-width: 500px;
     48   height: 0px;
     49   display: block;
     50   border: none;
     51   padding: 0px;
     52   margin: 0px;
     53   -webkit-transition: height 0.2s ease-out;
     54 }
     55 
     56 #title {
     57   display: block;
     58   margin-left: auto;
     59 }
     60 
     61 .error {
     62   white-space: nowrap;
     63   color: red;
     64 }
     65 
     66 .more {
     67   display: block;
     68   text-align: right;
     69   padding-top: 20px;
     70   padding-right: 10px;
     71   color: #88C;
     72 }
     73 
     74 </style>
     75 <script id="iframe_script">
     76 function reportHeight() {
     77   var msg = JSON.stringify({type:"size", size:document.body.offsetHeight});
     78   parent.postMessage(msg, "*");
     79 }
     80 
     81 function frameLoaded() {
     82   var links = document.getElementsByTagName("A");
     83   for (i = 0; i < links.length; i++) {
     84     var class = links[i].className;
     85     if (class != "item_title" && class != "open_box") {
     86       links[i].addEventListener("click", showStory);
     87     }
     88   }
     89   window.addEventListener("message", messageHandler);
     90 }
     91 
     92 function showStory(event) {
     93   var href = event.currentTarget.href;
     94   parent.postMessage(JSON.stringify({type:"show", url:href}), "*");
     95   event.preventDefault();
     96 }
     97 
     98 function messageHandler(event) {
     99   reportHeight();
    100 }
    101 
    102 </script>
    103 <script>
    104 // Feed URL.
    105 var feedUrl = 'http://news.google.com/?output=rss';
    106 
    107 // The XMLHttpRequest object that tries to load and parse the feed.
    108 var req;
    109 
    110 function main() {
    111   req = new XMLHttpRequest();
    112   req.onload = handleResponse;
    113   req.onerror = handleError;
    114   req.open("GET", feedUrl, true);
    115   req.send(null);
    116 }
    117 
    118 // Handles feed parsing errors.
    119 function handleFeedParsingFailed(error) {
    120   var feed = document.getElementById("feed");
    121   feed.className = "error";
    122   feed.innerText = "Error: " + error;
    123 }
    124 
    125 // Handles errors during the XMLHttpRequest.
    126 function handleError() {
    127   handleFeedParsingFailed('Failed to fetch RSS feed.');
    128 }
    129 
    130 // Handles parsing the feed data we got back from XMLHttpRequest.
    131 function handleResponse() {
    132   var doc = req.responseXML;
    133   if (!doc) {
    134     handleFeedParsingFailed("Not a valid feed.");
    135     return;
    136   }
    137   buildPreview(doc);
    138 }
    139 
    140 // The maximum number of feed items to show in the preview.
    141 var maxFeedItems = 5;
    142 
    143 // Where the more stories link should navigate to.
    144 var moreStoriesUrl;
    145 
    146 function buildPreview(doc) {
    147   // Get the link to the feed source.
    148   var link = doc.getElementsByTagName("link");
    149   var parentTag = link[0].parentNode.tagName;
    150   if (parentTag != "item" && parentTag != "entry") {
    151     moreStoriesUrl = link[0].textContent;
    152   }
    153 
    154   // Setup the title image.
    155   var images = doc.getElementsByTagName("image");
    156   var titleImg;
    157   if (images.length != 0) {
    158     var urls = images[0].getElementsByTagName("url");
    159     if (urls.length != 0) {
    160       titleImg = urls[0].textContent;
    161     }
    162   }
    163   var img = document.getElementById("title");
    164   // Listen for mouse and key events
    165   if (titleImg) {
    166     img.src = titleImg;
    167     if (moreStoriesUrl) {
    168       document.getElementById("title_a").addEventListener("click", 	                                                     moreStories);
    169       document.getElementById("title_a").addEventListener("keydown",
    170                                          function(event) {
    171                                            if (event.keyCode == 13) {
    172                                              moreStories(event);
    173                                            }});
    174     }
    175   } else {
    176     img.style.display = "none";
    177   }
    178 
    179   // Construct the iframe's HTML.
    180   var iframe_src = "<!doctype html><html><head><script>" +
    181                    document.getElementById("iframe_script").textContent + "<" +
    182                    "/script></head><body onload='frameLoaded();' " +
    183                    "style='padding:0px;margin:0px;'>";
    184 
    185   var feed = document.getElementById("feed");
    186   // Set ARIA role indicating the feed element has a tree structure
    187   feed.setAttribute("role", "tree");
    188 
    189   var entries = doc.getElementsByTagName('entry');
    190   if (entries.length == 0) {
    191     entries = doc.getElementsByTagName('item');
    192   }
    193   var count = Math.min(entries.length, maxFeedItems);
    194   for (var i = 0; i < count; i++) {
    195     item = entries.item(i);
    196 
    197     // Grab the title for the feed item.
    198     var itemTitle = item.getElementsByTagName('title')[0];
    199     if (itemTitle) {
    200       itemTitle = itemTitle.textContent;
    201     } else {
    202       itemTitle = "Unknown title";
    203     }
    204 
    205     // Grab the description.
    206     var itemDesc = item.getElementsByTagName('description')[0];
    207     if (!itemDesc) {
    208       itemDesc = item.getElementsByTagName('summary')[0];
    209       if (!itemDesc) {
    210         itemDesc = item.getElementsByTagName('content')[0];
    211       }
    212     }
    213     if (itemDesc) {
    214       itemDesc = itemDesc.childNodes[0].nodeValue;
    215     } else {
    216       itemDesc = '';
    217     }
    218 
    219     var item = document.createElement("div");
    220     item.className = "item";
    221     var box = document.createElement("div");
    222     box.className = "open_box";
    223     box.addEventListener("click", showDesc);
    224     // Disable focusing on box image separately from rest of tree item
    225     box.tabIndex = -1;
    226     item.appendChild(box);
    227 
    228     var title = document.createElement("a");
    229     title.className = "item_title";
    230     // Give title an ID for use with ARIA
    231     title.id = "item" + i;
    232     title.innerText = itemTitle;
    233     title.addEventListener("click", showDesc);
    234     title.addEventListener("keydown", keyHandlerShowDesc);
    235     // Update aria-activedescendant property in response to focus change
    236     // within the tree
    237     title.addEventListener("focus", function(event) {
    238                                       feed.setAttribute(
    239                                         "aria-activedescendant", this.id);
    240                                     });
    241     // Enable keyboard focus on the item title element
    242     title.tabIndex = 0;
    243     // Set ARIA role role indicating that the title element is a node in the
    244     // tree structure
    245     title.setAttribute("role", "treeitem");
    246     // Set the ARIA state indicating this tree item is currently collapsed.
    247     title.setAttribute("aria-expanded", "false");
    248     // Set ARIA property indicating that all items are at the same hierarchical
    249     // level (no nesting)
    250     title.setAttribute("aria-level", "1");
    251     item.appendChild(title);
    252 
    253     var desc = document.createElement("iframe");
    254     desc.scrolling = "no";
    255     desc.className = "item_desc";
    256     // Disable keyboard focus on elements in iFrames that have not been
    257     // displayed yet
    258     desc.tabIndex = -1;
    259 
    260     item.appendChild(desc);
    261     feed.appendChild(item);
    262 
    263     // The story body is created as an iframe with a data: URL in order to
    264     // isolate it from this page and protect against XSS.  As a data URL, it
    265     // has limited privileges and must communicate back using postMessage().
    266     desc.src="data:text/html," + iframe_src + itemDesc + "</body></html>";
    267   }
    268 
    269   if (moreStoriesUrl) {
    270     var more = document.createElement("a");
    271     more.className = "more";
    272     more.innerText = "More stories \u00BB";
    273     more.tabIndex = 0;
    274     more.addEventListener("click", moreStories);
    275     more.addEventListener("keydown", function(event) {
    276                                        if (event.keyCode == 13) {
    277                                          moreStories(event);
    278                                        }});
    279     feed.appendChild(more);
    280   }
    281 }
    282 
    283 // Show |url| in a new tab.
    284 function showUrl(url) {
    285   // Only allow http and https URLs.
    286   if (url.indexOf("http:") != 0 && url.indexOf("https:") != 0) {
    287     return;
    288   }
    289   chrome.tabs.create({url: url});
    290 }
    291 
    292 function moreStories(event) {
    293   showUrl(moreStoriesUrl);
    294 }
    295 
    296 function keyHandlerShowDesc(event) {
    297 // Display content under heading when spacebar or right-arrow pressed
    298 // Hide content when spacebar pressed again or left-arrow pressed
    299 // Move to next heading when down-arrow pressed
    300 // Move to previous heading when up-arrow pressed
    301   if (event.keyCode == 32) {
    302     showDesc(event);
    303   } else if ((this.parentNode.className == "item opened") &&
    304            (event.keyCode == 37)) {
    305     showDesc(event);
    306   } else if ((this.parentNode.className == "item") && (event.keyCode == 39)) {
    307     showDesc(event);
    308   } else if (event.keyCode == 40) {
    309     if (this.parentNode.nextSibling) {
    310       this.parentNode.nextSibling.children[1].focus();
    311     }
    312   } else if (event.keyCode == 38) {
    313     if (this.parentNode.previousSibling) {
    314       this.parentNode.previousSibling.children[1].focus();
    315     }
    316   }
    317 }
    318 
    319 function showDesc(event) {
    320   var item = event.currentTarget.parentNode;
    321   var items = document.getElementsByClassName("item");
    322   for (var i = 0; i < items.length; i++) {
    323     var iframe = items[i].getElementsByClassName("item_desc")[0];
    324     if (items[i] == item && items[i].className == "item") {
    325       items[i].className = "item opened";
    326       iframe.contentWindow.postMessage("reportHeight", "*");
    327       // Set the ARIA state indicating the tree item is currently expanded.
    328       items[i].getElementsByClassName("item_title")[0].
    329         setAttribute("aria-expanded", "true");
    330       iframe.tabIndex = 0;
    331     } else {
    332       items[i].className = "item";
    333       iframe.style.height = "0px";
    334       // Set the ARIA state indicating the tree item is currently collapsed.
    335       items[i].getElementsByClassName("item_title")[0].
    336         setAttribute("aria-expanded", "false");
    337       iframe.tabIndex = -1;
    338     }
    339   }
    340 }
    341 
    342 function iframeMessageHandler(e) {
    343   // Only listen to messages from one of our own iframes.
    344   var iframes = document.getElementsByTagName("IFRAME");
    345   for (var i = 0; i < iframes.length; i++) {
    346     if (iframes[i].contentWindow == e.source) {
    347       var msg = JSON.parse(e.data);
    348       if (msg) {
    349         if (msg.type == "size") {
    350           iframes[i].style.height = msg.size + "px";
    351         } else if (msg.type == "show") {
    352           var url = msg.url;
    353           if (url.indexOf("http://news.google.com") == 0) {
    354             // If the URL is a redirect URL, strip of the destination and go to
    355             // that directly.  This is necessary because the Google news
    356             // redirector blocks use of the redirects in this case.
    357             var index = url.indexOf("&url=");
    358             if (index >= 0) {
    359               url = url.substring(index + 5);
    360               index = url.indexOf("&");
    361               if (index >= 0)
    362                 url = url.substring(0, index);
    363             }
    364           }
    365           showUrl(url);
    366         }
    367       }
    368       return;
    369     }
    370   }
    371 }
    372 
    373 window.addEventListener("message", iframeMessageHandler);
    374 </script>
    375 </head>
    376 <body onload="main();">
    377 <a id="title_a" tabIndex="0"><img id='title' alt="Google News logo"></a>
    378 <div id="feed"></div>
    379 </body>
    380 </html>
    381 
    382