Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
| 2 <html> | 2 <html> |
| 3 <head> | 3 <head> |
| 4 <style> | 4 <link href="feed.css" rel="stylesheet" type="text/css"> |
| 5 body { | 5 <script id="iframe_script" type="text/not-javascript-i-swear"> |
|
Boris Smus
2011/10/18 21:51:38
How come we're doing this? Also, we should use an
Mike West
2011/10/19 09:43:57
We're doing this because I was too lazy to rewrite
| |
| 6 font-family: helvetica, arial, sans-serif; | 6 function reportHeight() { |
| 7 font-size: 12px; | 7 var msg = JSON.stringify({type:"size", size:document.body.offsetHeight}) ; |
| 8 overflow: hidden; | 8 parent.postMessage(msg, "*"); |
| 9 } | 9 } |
| 10 | 10 |
| 11 a { | 11 function frameLoaded() { |
| 12 color:#0000CC; | 12 var links = document.getElementsByTagName("A"); |
| 13 text-decoration: underline; | 13 for (i = 0; i < links.length; i++) { |
| 14 cursor: pointer; | 14 var c = links[i].className; |
| 15 } | 15 if (c != "item_title" && c != "open_box") { |
| 16 links[i].addEventListener("click", showStory); | |
| 17 } | |
| 18 } | |
| 19 window.addEventListener("message", messageHandler); | |
| 20 } | |
| 16 | 21 |
| 17 .open_box { | 22 function showStory(event) { |
| 18 display: block; | 23 var href = event.currentTarget.href; |
| 19 overflow: hidden; | 24 parent.postMessage(JSON.stringify({type:"show", url:href}), "*"); |
| 20 margin-right: 4px; | 25 event.preventDefault(); |
| 21 margin-top: 2px; | 26 } |
| 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 | 27 |
| 31 .opened .open_box { | 28 function messageHandler(event) { |
| 32 background-position:-12px -24px; | 29 reportHeight(); |
| 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 } | 30 } |
| 212 } | 31 </script> |
| 213 if (itemDesc) { | 32 <script src="feed.js"></script> |
| 214 itemDesc = itemDesc.childNodes[0].nodeValue; | 33 </head> |
| 215 } else { | 34 <body> |
| 216 itemDesc = ''; | 35 <a id="title_a" tabIndex="0"><img id='title' alt="Google News logo"></a> |
| 217 } | 36 <div id="feed"></div> |
| 218 | 37 </body> |
| 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> | 38 </html> |
| 381 | 39 |
| OLD | NEW |