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 |