1/**
2 * Copyright (c) 2010 The Chromium Authors. All rights reserved.  Use of this
3 * source code is governed by a BSD-style license that can be found in the
4 * LICENSE file.
5 */
6
7/**
8 * @fileoverview This file retrieves news feed and shows news in pop-up
9 * page according to country, topics and no. of stories selected in the
10 * option page.
11 */
12
13// Store value retrieved from locale.
14var moreStoriesLocale = chrome.i18n.getMessage('more_stories') + ' \u00BB ';
15var directionLocale = chrome.i18n.getMessage('direction');
16
17// Feed URL.
18var feedUrl = DEFAULT_NEWS_URL;
19
20//The XMLHttpRequest object that tries to load and parse the feed.
21var req;
22
23/**
24 * Sends request to Google News server
25 */
26function main() {
27  req = new XMLHttpRequest();
28  req.onload = handleResponse;
29  req.onerror = handleError;
30  req.open('GET', feedUrl, true);
31  req.send(null);
32}
33
34/**
35 * Handles feed parsing errors.
36 * @param {String} error The localized error message.
37 */
38function handleFeedParsingFailed(error) {
39  var feed = $('feed');
40  $('noStories').style.display = 'none';
41  feed.className = 'error';
42  feed.innerText = error;
43}
44
45/**
46 * Handles errors during the XMLHttpRequest.
47 */
48function handleError() {
49  handleFeedParsingFailed(chrome.i18n.getMessage('fetchError'));
50  $('topics').style.display = 'none';
51}
52
53/**
54 * Parses the feed response.
55 */
56function handleResponse() {
57  var doc = req.responseXML;
58  if (!doc) {
59    handleFeedParsingFailed(chrome.i18n.getMessage('wrongTopic'));
60    var img = $('title');
61    if(!img.src) {
62      img.src = "/images/news.gif";
63    }
64
65    document.querySelector('body').style.minHeight = 0;
66    return;
67  }
68  buildPreview(doc);
69}
70
71// Stores no. of stories selected in options page.
72var maxFeedItems = (window.localStorage.getItem('count')) ?
73    window.localStorage.getItem('count') : 5;
74
75// Where the more stories link should navigate to.
76var moreStoriesUrl;
77
78/**
79 * Generates news iframe in pop-up page by parsing retrieved feed.
80 * @param {HTMLDocument} doc HTML Document received in feed.
81 */
82function buildPreview(doc) {
83  // Get the link to the feed source.
84  var link = doc.querySelector('link');
85  var parentTag = link.parentNode.tagName;
86  if (parentTag != 'item' && parentTag != 'entry') {
87    moreStoriesUrl = link.textContent;
88  }
89
90  // Setup the title image.
91  var image = doc.querySelector('image');
92  var titleImg;
93
94  // Stores whether language script is Right to Left or not for setting style
95  // of share buttons(Facebook, Twitter and Google Buzz) in iframe.
96  var isRtl = 'lTR';
97
98  if (image) {
99    var url = image.querySelector('url');
100    if (url) {
101      titleImg = url.textContent;
102
103      // Stores URL of title image to be shown on pop-up page.
104      var titleImgUrl = titleImg;
105      var pattern = /ar_/gi;
106      var result = titleImgUrl.match(pattern);
107      if (result != null || titleImgUrl == ISRAEL_IMAGE_URL) {
108        isRtl = 'rTL';
109      }
110    }
111  }
112
113  var img = $('title');
114  if (titleImg) {
115    img.src = titleImg;
116    if (moreStoriesUrl) {
117      $('title_a').addEventListener('click', moreStories);
118    }
119  } else {
120    img.style.display = 'none';
121  }
122
123  // Construct the iframe's HTML.
124  var iframe_src = '<!doctype html><html><head><script>' +
125                   $('iframe_script').textContent + '<' +
126                   '/script><style> ' +
127                   '.rTL {margin-right: 102px; text-align: right;} ' +
128                   '.lTR {margin-left: 102px; text-align: left;} ' +
129                   '</style></head><body onload="frameLoaded();" ' +
130                   'style="padding:0px;margin:0px;">';
131
132  var feed = $('feed');
133  feed.className = '';
134  var entries = doc.getElementsByTagName('entry');
135  if (entries.length == 0) {
136    entries = doc.getElementsByTagName('item');
137  }
138  var count = Math.min(entries.length, maxFeedItems);
139
140  // Stores required height by pop-up page.
141  var minHeight = 19;
142  minHeight = (minHeight * (count - 1)) + 100;
143  document.querySelector('body').style.minHeight = minHeight + 'px';
144  $('feed').innerHTML = '';
145
146  for (var i = 0; i < count; i++) {
147    item = entries.item(i);
148
149    // Grab the title for the feed item.
150    var itemTitle = item.querySelector('title');
151    if (itemTitle) {
152      itemTitle = itemTitle.textContent;
153    } else {
154      itemTitle = 'Unknown title';
155    }
156
157    // Grab the description.
158    var itemDesc = item.querySelector('description');
159    if (!itemDesc) {
160      itemDesc = item.querySelector('summary');
161      if (!itemDesc) {
162        itemDesc = item.querySelector('content');
163      }
164    }
165    if (itemDesc) {
166      itemDesc = itemDesc.childNodes[0].nodeValue;
167
168    } else {
169      itemDesc = '';
170    }
171    var itemLink = item.querySelector('link');
172    if (itemLink) {
173      itemLink = itemLink.textContent;
174    } else {
175      itemLink = 'Unknown itemLink';
176    }
177    var item = document.createElement('div');
178    item.className = 'item';
179    var box = document.createElement('div');
180    box.className = 'open_box';
181    box.addEventListener('click', showDesc);
182    item.appendChild(box);
183
184    var title = document.createElement('a');
185    title.className = 'item_title';
186    title.innerText = itemTitle;
187    title.addEventListener('click', showDesc);
188    item.appendChild(title);
189
190    var desc = document.createElement('iframe');
191    desc.scrolling = 'no';
192    desc.className = 'item_desc';
193    item.appendChild(desc);
194    feed.appendChild(item);
195
196    // Adds share buttons images(Facebook, Twitter and Google Buzz).
197    itemDesc += "<div class = '" + isRtl + "'>";
198    itemDesc += "<a style='cursor: pointer' id='fb' " +
199      "onclick='openNewsShareWindow(this.id,\"" + itemLink + "\")'>" +
200      "<img src='" + chrome.extension.getURL('/images/fb.png') + "'/></a>";
201    itemDesc += " <a style='cursor: pointer' id='twitter' " +
202      "onclick='openNewsShareWindow(this.id,\"" + itemLink + "\")'>" +
203      "<img src='" + chrome.extension.getURL('/images/twitter.png') + "'/></a>";
204    itemDesc += " <a style='cursor: pointer' id='buzz' " +
205      "onclick='openNewsShareWindow(this.id,\"" + itemLink + "\")'>" +
206      "<img src='" + chrome.extension.getURL('/images/buzz.png') + "'/></a>";
207    itemDesc += '</div>';
208
209    // The story body is created as an iframe with a data: URL in order to
210    // isolate it from this page and protect against XSS.  As a data URL, it
211    // has limited privileges and must communicate back using postMessage().
212    desc.src = 'data:text/html;charset=utf-8,' + iframe_src + itemDesc +
213      '</body></html>';
214  }
215  if (moreStoriesUrl && entries.length != 0) {
216    var more = document.createElement('a');
217    more.className = 'more';
218    more.innerText = moreStoriesLocale;
219    more.addEventListener('click', moreStories);
220    feed.appendChild(more);
221  }
222  setStyleByLang(titleImgUrl);
223
224  // Checks whether feed retrieved has news story or not. If not, then shows
225  // error message accordingly.
226  if (entries.length == 0) {
227    $('noStories').innerText = chrome.i18n.getMessage('noStory');
228    $('noStories').style.display = 'block';
229  } else {
230    $('noStories').style.display = 'none';
231  }
232}
233
234/**
235 * Show |url| in a new tab.
236 * @param {String} url The news URL.
237 */
238function showUrl(url) {
239  // Only allow http and https URLs.
240  if (url.indexOf('http:') != 0 && url.indexOf('https:') != 0) {
241    return;
242  }
243  chrome.tabs.create({url: url});
244}
245
246/**
247 * Redirects to Google news site for more stories.
248 * @param {Object} event Onclick event.
249 */
250function moreStories(event) {
251  showUrl(moreStoriesUrl);
252}
253
254/**
255 * Shows description of the news when users clicks on news title.
256 * @param {Object} event Onclick event.
257 */
258function showDesc(event) {
259  var item_ = event.currentTarget.parentNode;
260  var items = document.getElementsByClassName('item');
261  for (var i = 0, item; item = items[i]; i++) {
262    var iframe = item.querySelector('.item_desc');
263    if (item == item_ && item.className == 'item') {
264      item.className = 'item opened';
265      iframe.contentWindow.postMessage('reportHeight', '*');
266    } else {
267      item.className = 'item';
268      iframe.style.height = '0px';
269    }
270  }
271}
272
273/**
274 * Handles messages between different iframes and sets the display of iframe.
275 * @param {Object} e Onmessage event.
276 */
277function iframeMessageHandler(e) {
278  var iframes = document.getElementsByTagName('IFRAME');
279  for (var i = 0, iframe; iframe = iframes[i]; i++) {
280    if (iframe.contentWindow == e.source) {
281      var msg = JSON.parse(e.data);
282      if (msg) {
283        if (msg.type == 'size') {
284          iframe.style.height = msg.size + 'px';
285        } else if (msg.type == 'show') {
286          var url = msg.url;
287          if (url.indexOf('http://news.google.com') == 0) {
288            // If the URL is a redirect URL, strip of the destination and go to
289            // that directly.  This is necessary because the Google news
290            // redirector blocks use of the redirects in this case.
291            var index = url.indexOf('&url=');
292            if (index >= 0) {
293              url = url.substring(index + 5);
294              index = url.indexOf('&');
295              if (index >= 0)
296                url = url.substring(0, index);
297            }
298          }
299          showUrl(url);
300        }
301      }
302      return;
303    }
304  }
305}
306
307/**
308 * Saves last viewed topic by user in local storage on unload of pop-up page.
309 */
310function saveLastTopic() {
311  var topicVal = $('topics').value;
312  window.localStorage.setItem('lastTopic', topicVal);
313}
314
315/**
316 * Sets the URL according to selected topic(or default topic), then retrieves
317 * feed and sets pop-up page.
318 */
319function getNewsByTitle() {
320  var country = window.localStorage.getItem('country');
321  country = (country == 'noCountry' || !country) ? '' : country;
322
323  // Sets direction of topics showed under dropdown in pop-up page according
324  // to set language in browser.
325  $('topics').className = (directionLocale == 'rtl') ? 'topicsRTL' :
326      'topicsLTR';
327
328  var topicVal = $('topics').value;
329
330  // Sets Feed URL in case of custom topic selected.
331  var keywords = JSON.parse(window.localStorage.getItem('keywords'));
332  var isFound = false;
333  if (keywords) {
334    for (i = 0; i < keywords.length; i++) {
335      if (topicVal == keywords[i]) {
336      isFound = true;
337      feedUrl = DEFAULT_NEWS_URL + '&cf=all&ned=' + country + '&q=' + topicVal +
338          '&hl=' + country;
339      break;
340      }
341    }
342  }
343  if (!isFound) {
344    feedUrl = DEFAULT_NEWS_URL + '&cf=all&ned=' + country +
345        '&topic=' + topicVal;
346  }
347  main();
348}
349
350/**
351 * Shows topic list retrieved from local storage(if any),else shows
352 * default topics list.
353 */
354function getTopics() {
355  var topics = JSON.parse(window.localStorage.getItem('topics'));
356  var keywords = JSON.parse(window.localStorage.getItem('keywords'));
357  var element = $('topics');
358
359  // Sets all topics as default list if no list is found from local storage.
360  if (!topics && !keywords) {
361    topics = [' ','n','w','b','t','e','s','m','po'];
362  }
363
364  if (topics) {
365    for (var i = 0; i < (topics.length); i++) {
366      var val = (topics[i] == ' ') ? '1' : topics[i];
367      element.options[element.options.length] = new Option(
368          chrome.i18n.getMessage(val), topics[i]);
369    }
370  }
371
372  // Shows custom topics in list(if any).
373  if (keywords) {
374    for (i = 0; i < (keywords.length); i++) {
375      element.options[element.options.length] = new Option(keywords[i],
376          keywords[i]);
377    }
378  }
379
380  $('option_link').innerText = chrome.i18n.getMessage('options');
381
382  var topicVal = window.localStorage.getItem('lastTopic');
383  if (topicVal) {
384    $('topics').value = topicVal;
385  }
386}
387
388window.addEventListener('message', iframeMessageHandler);
389