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