diff options
author | kurrik@chromium.org <kurrik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-18 18:24:50 +0000 |
---|---|---|
committer | kurrik@chromium.org <kurrik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-18 18:24:50 +0000 |
commit | 16f03c44d162be8278afda756ded6bbd0d370a7d (patch) | |
tree | d76d2b0cbba5e52fa3e22f07e959da881858d896 /chrome | |
parent | cb84dc2b98f8132e71994f08e13d0e9c30ac1c1e (diff) | |
download | chromium_src-16f03c44d162be8278afda756ded6bbd0d370a7d.zip chromium_src-16f03c44d162be8278afda756ded6bbd0d370a7d.tar.gz chromium_src-16f03c44d162be8278afda756ded6bbd0d370a7d.tar.bz2 |
Adding Rachel Shearer's updated feed.html with keyboard nav and ARIA attributes.
BUG=None
TEST=None
Review URL: http://codereview.chromium.org/2711005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50262 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/common/extensions/docs/examples/extensions/news_a11y/feed.html | 381 | ||||
-rw-r--r-- | chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json | 15 | ||||
-rw-r--r-- | chrome/common/extensions/docs/examples/extensions/news_a11y/news_action.png | bin | 0 -> 1109 bytes | |||
-rw-r--r-- | chrome/common/extensions/docs/examples/extensions/news_a11y/news_icon.png | bin | 0 -> 10782 bytes | |||
-rw-r--r-- | chrome/common/extensions/docs/examples/extensions/news_a11y/sprite_arrows.gif | bin | 0 -> 1328 bytes |
5 files changed, 396 insertions, 0 deletions
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.html b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.html new file mode 100644 index 0000000..1c0121f --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.html @@ -0,0 +1,381 @@ +<!DOCTYPE html> +<html> +<head> +<style> +body { + font-family: helvetica, arial, sans-serif; + font-size: 12px; + overflow: hidden; +} + +a { + color:#0000CC; + text-decoration: underline; + cursor: pointer; +} + +.open_box { + display: block; + overflow: hidden; + margin-right: 4px; + margin-top: 2px; + height: 12px; + width: 12px; + float: left; + clear: left; + background-image: url(sprite_arrows.gif); + background-position: 0px -24px; + cursor: pointer; +} + +.opened .open_box { + background-position:-12px -24px; +} + +.item { + padding: 2px 0px; +} + +.item_title { + display: block; + min-width: 300px; + padding-left: 15px; + cursor: pointer; +} + +.item_desc { + min-width: 500px; + height: 0px; + display: block; + border: none; + padding: 0px; + margin: 0px; + -webkit-transition: height 0.2s ease-out; +} + +#title { + display: block; + margin-left: auto; +} + +.error { + white-space: nowrap; + color: red; +} + +.more { + display: block; + text-align: right; + padding-top: 20px; + padding-right: 10px; + color: #88C; +} + +</style> +<script id="iframe_script"> +function reportHeight() { + var msg = JSON.stringify({type:"size", size:document.body.offsetHeight}); + parent.postMessage(msg, "*"); +} + +function frameLoaded() { + var links = document.getElementsByTagName("A"); + for (i = 0; i < links.length; i++) { + var class = links[i].className; + if (class != "item_title" && class != "open_box") { + links[i].addEventListener("click", showStory); + } + } + window.addEventListener("message", messageHandler); +} + +function showStory(event) { + var href = event.currentTarget.href; + parent.postMessage(JSON.stringify({type:"show", url:href}), "*"); + event.preventDefault(); +} + +function messageHandler(event) { + reportHeight(); +} + +</script> +<script> +// Feed URL. +var feedUrl = 'http://news.google.com/?output=rss'; + +// The XMLHttpRequest object that tries to load and parse the feed. +var req; + +function main() { + req = new XMLHttpRequest(); + req.onload = handleResponse; + req.onerror = handleError; + req.open("GET", feedUrl, true); + req.send(null); +} + +// Handles feed parsing errors. +function handleFeedParsingFailed(error) { + var feed = document.getElementById("feed"); + feed.className = "error"; + feed.innerText = "Error: " + error; +} + +// Handles errors during the XMLHttpRequest. +function handleError() { + handleFeedParsingFailed('Failed to fetch RSS feed.'); +} + +// Handles parsing the feed data we got back from XMLHttpRequest. +function handleResponse() { + var doc = req.responseXML; + if (!doc) { + handleFeedParsingFailed("Not a valid feed."); + return; + } + buildPreview(doc); +} + +// The maximum number of feed items to show in the preview. +var maxFeedItems = 5; + +// Where the more stories link should navigate to. +var moreStoriesUrl; + +function buildPreview(doc) { + // Get the link to the feed source. + var link = doc.getElementsByTagName("link"); + var parentTag = link[0].parentNode.tagName; + if (parentTag != "item" && parentTag != "entry") { + moreStoriesUrl = link[0].textContent; + } + + // Setup the title image. + var images = doc.getElementsByTagName("image"); + var titleImg; + if (images.length != 0) { + var urls = images[0].getElementsByTagName("url"); + if (urls.length != 0) { + titleImg = urls[0].textContent; + } + } + var img = document.getElementById("title"); + // Listen for mouse and key events + if (titleImg) { + img.src = titleImg; + if (moreStoriesUrl) { + document.getElementById("title_a").addEventListener("click", moreStories); + document.getElementById("title_a").addEventListener("keydown", + function(event) { + if (event.keyCode == 13) { + moreStories(event); + }}); + } + } else { + img.style.display = "none"; + } + + // Construct the iframe's HTML. + var iframe_src = "<!doctype html><html><head><script>" + + document.getElementById("iframe_script").textContent + "<" + + "/script></head><body onload='frameLoaded();' " + + "style='padding:0px;margin:0px;'>"; + + var feed = document.getElementById("feed"); + // Set ARIA role indicating the feed element has a tree structure + feed.setAttribute("role", "tree"); + + var entries = doc.getElementsByTagName('entry'); + if (entries.length == 0) { + entries = doc.getElementsByTagName('item'); + } + var count = Math.min(entries.length, maxFeedItems); + for (var i = 0; i < count; i++) { + item = entries.item(i); + + // Grab the title for the feed item. + var itemTitle = item.getElementsByTagName('title')[0]; + if (itemTitle) { + itemTitle = itemTitle.textContent; + } else { + itemTitle = "Unknown title"; + } + + // Grab the description. + var itemDesc = item.getElementsByTagName('description')[0]; + if (!itemDesc) { + itemDesc = item.getElementsByTagName('summary')[0]; + if (!itemDesc) { + itemDesc = item.getElementsByTagName('content')[0]; + } + } + if (itemDesc) { + itemDesc = itemDesc.childNodes[0].nodeValue; + } else { + itemDesc = ''; + } + + var item = document.createElement("div"); + item.className = "item"; + var box = document.createElement("div"); + box.className = "open_box"; + box.addEventListener("click", showDesc); + // Disable focusing on box image separately from rest of tree item + box.tabIndex = -1; + item.appendChild(box); + + var title = document.createElement("a"); + title.className = "item_title"; + // Give title an ID for use with ARIA + title.id = "item" + i; + title.innerText = itemTitle; + title.addEventListener("click", showDesc); + title.addEventListener("keydown", keyHandlerShowDesc); + // Update aria-activedescendant property in response to focus change + // within the tree + title.addEventListener("focus", function(event) { + feed.setAttribute( + "aria-activedescendant", this.id); + }); + // Enable keyboard focus on the item title element + title.tabIndex = 0; + // Set ARIA role role indicating that the title element is a node in the + // tree structure + title.setAttribute("role", "treeitem"); + // Set the ARIA state indicating this tree item is currently collapsed. + title.setAttribute("aria-expanded", "false"); + // Set ARIA property indicating that all items are at the same hierarchical + // level (no nesting) + title.setAttribute("aria-level", "1"); + item.appendChild(title); + + var desc = document.createElement("iframe"); + desc.scrolling = "no"; + desc.className = "item_desc"; + // Disable keyboard focus on elements in iFrames that have not been + // displayed yet + desc.tabIndex = -1; + + item.appendChild(desc); + feed.appendChild(item); + + // The story body is created as an iframe with a data: URL in order to + // isolate it from this page and protect against XSS. As a data URL, it + // has limited privileges and must communicate back using postMessage(). + desc.src="data:text/html," + iframe_src + itemDesc + "</body></html>"; + } + + if (moreStoriesUrl) { + var more = document.createElement("a"); + more.className = "more"; + more.innerText = "More stories \u00BB"; + more.tabIndex = 0; + more.addEventListener("click", moreStories); + more.addEventListener("keydown", function(event) { + if (event.keyCode == 13) { + moreStories(event); + }}); + feed.appendChild(more); + } +} + +// Show |url| in a new tab. +function showUrl(url) { + // Only allow http and https URLs. + if (url.indexOf("http:") != 0 && url.indexOf("https:") != 0) { + return; + } + chrome.tabs.create({url: url}); +} + +function moreStories(event) { + showUrl(moreStoriesUrl); +} + +function keyHandlerShowDesc(event) { +// Display content under heading when spacebar or right-arrow pressed +// Hide content when spacebar pressed again or left-arrow pressed +// Move to next heading when down-arrow pressed +// Move to previous heading when up-arrow pressed + if (event.keyCode == 32) { + showDesc(event); + } else if ((this.parentNode.className == "item opened") && + (event.keyCode == 37)) { + showDesc(event); + } else if ((this.parentNode.className == "item") && (event.keyCode == 39)) { + showDesc(event); + } else if (event.keyCode == 40) { + if (this.parentNode.nextSibling) { + this.parentNode.nextSibling.children[1].focus(); + } + } else if (event.keyCode == 38) { + if (this.parentNode.previousSibling) { + this.parentNode.previousSibling.children[1].focus(); + } + } +} + +function showDesc(event) { + var item = event.currentTarget.parentNode; + var items = document.getElementsByClassName("item"); + for (var i = 0; i < items.length; i++) { + var iframe = items[i].getElementsByClassName("item_desc")[0]; + if (items[i] == item && items[i].className == "item") { + items[i].className = "item opened"; + iframe.contentWindow.postMessage("reportHeight", "*"); + // Set the ARIA state indicating the tree item is currently expanded. + items[i].getElementsByClassName("item_title")[0]. + setAttribute("aria-expanded", "true"); + iframe.tabIndex = 0; + } else { + items[i].className = "item"; + iframe.style.height = "0px"; + // Set the ARIA state indicating the tree item is currently collapsed. + items[i].getElementsByClassName("item_title")[0]. + setAttribute("aria-expanded", "false"); + iframe.tabIndex = -1; + } + } +} + +function iframeMessageHandler(e) { + // Only listen to messages from one of our own iframes. + var iframes = document.getElementsByTagName("IFRAME"); + for (var i = 0; i < iframes.length; i++) { + if (iframes[i].contentWindow == e.source) { + var msg = JSON.parse(e.data); + if (msg) { + if (msg.type == "size") { + iframes[i].style.height = msg.size + "px"; + } else if (msg.type == "show") { + var url = msg.url; + if (url.indexOf("http://news.google.com") == 0) { + // If the URL is a redirect URL, strip of the destination and go to + // that directly. This is necessary because the Google news + // redirector blocks use of the redirects in this case. + var index = url.indexOf("&url="); + if (index >= 0) { + url = url.substring(index + 5); + index = url.indexOf("&"); + if (index >= 0) + url = url.substring(0, index); + } + } + showUrl(url); + } + } + return; + } + } +} + +window.addEventListener("message", iframeMessageHandler); +</script> +</head> +<body onload="main();"> +<a id="title_a" tabIndex="0"><img id='title' alt="Google News logo"></a> +<div id="feed"></div> +</body> +</html> + diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json b/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json new file mode 100644 index 0000000..f0c7e0e --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json @@ -0,0 +1,15 @@ +{ + "name": "News Reader", + "version": "1.1", + "description": "Displays the first 5 items from the 'Google News - top news' RSS feed in a popup.", + "icons": { "128": "news_icon.png" }, + "browser_action": { + "default_title": "Google News", + "default_icon": "news_action.png", + "popup": "feed.html" + }, + "permissions": [ + "tabs", + "http://news.google.com/*" + ] +} diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/news_action.png b/chrome/common/extensions/docs/examples/extensions/news_a11y/news_action.png Binary files differnew file mode 100644 index 0000000..24b0ca7 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/news_action.png diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/news_icon.png b/chrome/common/extensions/docs/examples/extensions/news_a11y/news_icon.png Binary files differnew file mode 100644 index 0000000..42da406 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/news_icon.png diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/sprite_arrows.gif b/chrome/common/extensions/docs/examples/extensions/news_a11y/sprite_arrows.gif Binary files differnew file mode 100644 index 0000000..4560faf --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/sprite_arrows.gif |