• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7/**
8 * PHASES
9 * 1) Load next event from server refresh every 30 minutes or every time
10 *   you go to calendar or every time you logout drop in a data object.
11 * 2) Display on screen periodically once per minute or on demand.
12 */
13
14// Message shown in badge title when no title is given to an event.
15var MSG_NO_TITLE = chrome.i18n.getMessage('noTitle');
16
17// Time between server polls = 30 minutes.
18var POLL_INTERVAL = 30 * 60 * 1000;
19
20// Redraw interval is 1 min.
21var DRAW_INTERVAL = 60 * 1000;
22
23// The time when we last polled.
24var lastPollTime_ = 0;
25
26// Object for BadgeAnimation
27var badgeAnimation_;
28
29//Object for CanvasAnimation
30var canvasAnimation_;
31
32// Object containing the event.
33var nextEvent_ = null;
34
35// Storing events.
36var eventList = [];
37var nextEvents = [];
38
39// Storing calendars.
40var calendars = [];
41
42var pollUnderProgress = false;
43var defaultAuthor = '';
44var isMultiCalendar = false;
45
46//URL for getting feed of individual calendar support.
47var SINGLE_CALENDAR_SUPPORT_URL = 'https://www.google.com/calendar/feeds' +
48    '/default/private/embed?toolbar=true&max-results=10';
49
50//URL for getting feed of multiple calendar support.
51var MULTIPLE_CALENDAR_SUPPORT_URL = 'https://www.google.com/calendar/feeds' +
52    '/default/allcalendars/full';
53
54//URL for opening Google Calendar in new tab.
55var GOOGLE_CALENDAR_URL = 'http://www.google.com/calendar/render';
56
57//URL for declining invitation of the event.
58var DECLINED_URL = 'http://schemas.google.com/g/2005#event.declined';
59
60//This is used to poll only once per second at most, and delay that if
61//we keep hitting pages that would otherwise force a load.
62var pendingLoadId_ = null;
63
64/**
65 * A "loading" animation displayed while we wait for the first response from
66 * Calendar. This animates the badge text with a dot that cycles from left to
67 * right.
68 * @constructor
69 */
70function BadgeAnimation() {
71  this.timerId_ = 0;
72  this.maxCount_ = 8;  // Total number of states in animation
73  this.current_ = 0;  // Current state
74  this.maxDot_ = 4;  // Max number of dots in animation
75};
76
77/**
78 * Paints the badge text area while loading the data.
79 */
80BadgeAnimation.prototype.paintFrame = function() {
81  var text = '';
82  for (var i = 0; i < this.maxDot_; i++) {
83    text += (i == this.current_) ? '.' : ' ';
84  }
85
86  chrome.browserAction.setBadgeText({text: text});
87  this.current_++;
88  if (this.current_ == this.maxCount_) {
89    this.current_ = 0;
90  }
91};
92
93/**
94 * Starts the animation process.
95 */
96BadgeAnimation.prototype.start = function() {
97  if (this.timerId_) {
98    return;
99  }
100
101  var self = this;
102  this.timerId_ = window.setInterval(function() {
103    self.paintFrame();
104  }, 100);
105};
106
107/**
108 * Stops the animation process.
109 */
110BadgeAnimation.prototype.stop = function() {
111  if (!this.timerId_) {
112    return;
113  }
114
115  window.clearInterval(this.timerId_);
116  this.timerId_ = 0;
117};
118
119/**
120 * Animates the canvas after loading the data from all the calendars. It
121 * rotates the icon and defines the badge text and title.
122 * @constructor
123 */
124function CanvasAnimation() {
125  this.animationFrames_ = 36;  // The number of animation frames
126  this.animationSpeed_ = 10;  // Time between each frame(in ms).
127  this.canvas_ = $('canvas');  // The canvas width + height.
128  this.canvasContext_ = this.canvas_.getContext('2d');  // Canvas context.
129  this.loggedInImage_ = $('logged_in');
130  this.rotation_ = 0;  //Keeps count of rotation angle of extension icon.
131  this.w = this.canvas_.width;  // Setting canvas width.
132  this.h = this.canvas_.height;  // Setting canvas height.
133  this.RED = [208, 0, 24, 255];  //Badge color of extension icon in RGB format.
134  this.BLUE = [0, 24, 208, 255];
135  this.currentBadge_ = null;  // The text in the current badge.
136};
137
138/**
139 * Flips the icon around and draws it.
140 */
141CanvasAnimation.prototype.animate = function() {
142  this.rotation_ += (1 / this.animationFrames_);
143  this.drawIconAtRotation();
144  var self = this;
145  if (this.rotation_ <= 1) {
146    setTimeout(function() {
147      self.animate();
148    }, self.animationSpeed_);
149  } else {
150    this.drawFinal();
151  }
152};
153
154/**
155 * Renders the icon.
156 */
157CanvasAnimation.prototype.drawIconAtRotation = function() {
158  this.canvasContext_.save();
159  this.canvasContext_.clearRect(0, 0, this.w, this.h);
160  this.canvasContext_.translate(Math.ceil(this.w / 2), Math.ceil(this.h / 2));
161  this.canvasContext_.rotate(2 * Math.PI * this.getSector(this.rotation_));
162  this.canvasContext_.drawImage(this.loggedInImage_, -Math.ceil(this.w / 2),
163    -Math.ceil(this.h / 2));
164  this.canvasContext_.restore();
165  chrome.browserAction.setIcon(
166      {imageData: this.canvasContext_.getImageData(0, 0, this.w, this.h)});
167};
168
169/**
170 * Calculates the sector which has to be traversed in a single call of animate
171 * function(360/animationFrames_ = 360/36 = 10 radians).
172 * @param {integer} sector angle to be rotated(in radians).
173 * @return {integer} value in radian of the sector which it has to cover.
174 */
175CanvasAnimation.prototype.getSector = function(sector) {
176  return (1 - Math.sin(Math.PI / 2 + sector * Math.PI)) / 2;
177};
178
179/**
180 * Draws the event icon and determines the badge title and icon title.
181 */
182CanvasAnimation.prototype.drawFinal = function() {
183  badgeAnimation_.stop();
184
185  if (!nextEvent_) {
186    this.showLoggedOut();
187  } else {
188    this.drawIconAtRotation();
189    this.rotation_ = 0;
190
191    var ms = nextEvent_.startTime.getTime() - getCurrentTime();
192    var nextEventMin = ms / (1000 * 60);
193    var bgColor = (nextEventMin < 60) ? this.RED : this.BLUE;
194
195    chrome.browserAction.setBadgeBackgroundColor({color: bgColor});
196    currentBadge_ = this.getBadgeText(nextEvent_);
197    chrome.browserAction.setBadgeText({text: currentBadge_});
198
199    if (nextEvents.length > 0) {
200      var text = '';
201      for (var i = 0, event; event = nextEvents[i]; i++) {
202        text += event.title;
203        if (event.author || event.location) {
204          text += '\n';
205        }
206        if (event.location) {
207          text += event.location + ' ';
208        }
209        if (event.author) {
210          text += event.author;
211        }
212        if (i < (nextEvents.length - 1)) {
213          text += '\n----------\n';
214        }
215      }
216      text = filterSpecialChar(text);
217      chrome.browserAction.setTitle({'title' : text});
218    }
219  }
220  pollUnderProgress = false;
221
222  chrome.extension.sendRequest({
223    message: 'enableSave'
224  }, function() {
225  });
226
227  return;
228};
229
230/**
231 * Shows the user logged out.
232 */
233CanvasAnimation.prototype.showLoggedOut = function() {
234  currentBadge_ = '?';
235  chrome.browserAction.setIcon({path: '../images/icon-16_bw.gif'});
236  chrome.browserAction.setBadgeBackgroundColor({color: [190, 190, 190, 230]});
237  chrome.browserAction.setBadgeText({text: '?'});
238  chrome.browserAction.setTitle({ 'title' : ''});
239};
240
241/**
242 * Gets the badge text.
243 * @param {Object} nextEvent_ next event in the calendar.
244 * @return {String} text Badge text to be shown in extension icon.
245 */
246CanvasAnimation.prototype.getBadgeText = function(nextEvent_) {
247  if (!nextEvent_) {
248    return '';
249  }
250
251  var ms = nextEvent_.startTime.getTime() - getCurrentTime();
252  var nextEventMin = Math.ceil(ms / (1000 * 60));
253
254  var text = '';
255  if (nextEventMin < 60) {
256    text = chrome.i18n.getMessage('minutes', nextEventMin.toString());
257  } else if (nextEventMin < 1440) {
258    text = chrome.i18n.getMessage('hours',
259               Math.round(nextEventMin / 60).toString());
260  } else if (nextEventMin < (1440 * 10)) {
261    text = chrome.i18n.getMessage('days',
262               Math.round(nextEventMin / 60 / 24).toString());
263  }
264  return text;
265};
266
267/**
268 * Provides all the calendar related utils.
269 */
270CalendarManager = {};
271
272/**
273 * Extracts event from the each entry of the calendar.
274 * @param {Object} elem The XML node to extract the event from.
275 * @param {Object} mailId email of the owner of calendar in multiple calendar
276 *     support.
277 * @return {Object} out An object containing the event properties.
278 */
279CalendarManager.extractEvent = function(elem, mailId) {
280  var out = {};
281
282  for (var node = elem.firstChild; node != null; node = node.nextSibling) {
283    if (node.nodeName == 'title') {
284        out.title = node.firstChild ? node.firstChild.nodeValue : MSG_NO_TITLE;
285    } else if (node.nodeName == 'link' &&
286               node.getAttribute('rel') == 'alternate') {
287      out.url = node.getAttribute('href');
288    } else if (node.nodeName == 'gd:where') {
289      out.location = node.getAttribute('valueString');
290    } else if (node.nodeName == 'gd:who') {
291      if (node.firstChild) {
292        if ((!isMultiCalendar) || (isMultiCalendar && mailId &&
293            node.getAttribute('email') == mailId)) {
294          out.attendeeStatus = node.firstChild.getAttribute('value');
295        }
296      }
297    } else if (node.nodeName == 'gd:eventStatus') {
298      out.status = node.getAttribute('value');
299    } else if (node.nodeName == 'gd:when') {
300      var startTimeStr = node.getAttribute('startTime');
301      var endTimeStr = node.getAttribute('endTime');
302
303      startTime = rfc3339StringToDate(startTimeStr);
304      endTime = rfc3339StringToDate(endTimeStr);
305
306      if (startTime == null || endTime == null) {
307        continue;
308      }
309
310      out.isAllDay = (startTimeStr.length <= 11);
311      out.startTime = startTime;
312      out.endTime = endTime;
313    }
314  }
315  return out;
316};
317
318/**
319 * Polls the server to get the feed of the user.
320 */
321CalendarManager.pollServer = function() {
322  if (! pollUnderProgress) {
323    eventList = [];
324    pollUnderProgress = true;
325    pendingLoadId_ = null;
326    calendars = [];
327    lastPollTime_ = getCurrentTime();
328    var url;
329    var xhr = new XMLHttpRequest();
330    try {
331      xhr.onreadystatechange = CalendarManager.genResponseChangeFunc(xhr);
332      xhr.onerror = function(error) {
333        console.log('error: ' + error);
334        nextEvent_ = null;
335        canvasAnimation_.drawFinal();
336      };
337      if (isMultiCalendar) {
338        url = MULTIPLE_CALENDAR_SUPPORT_URL;
339      } else {
340        url = SINGLE_CALENDAR_SUPPORT_URL;
341      }
342
343      xhr.open('GET', url);
344      xhr.send(null);
345    } catch (e) {
346      console.log('ex: ' + e);
347      nextEvent_ = null;
348      canvasAnimation_.drawFinal();
349    }
350  }
351};
352
353/**
354 * Gathers the list of all calendars of a specific user for multiple calendar
355 * support and event entries in single calendar.
356 * @param {xmlHttpRequest} xhr xmlHttpRequest object containing server response.
357 * @return {Object} anonymous function which returns to onReadyStateChange.
358 */
359CalendarManager.genResponseChangeFunc = function(xhr) {
360  return function() {
361    if (xhr.readyState != 4) {
362      return;
363    }
364    if (!xhr.responseXML) {
365      console.log('No responseXML');
366      nextEvent_ = null;
367      canvasAnimation_.drawFinal();
368      return;
369    }
370    if (isMultiCalendar) {
371      var entry_ = xhr.responseXML.getElementsByTagName('entry');
372      if (entry_ && entry_.length > 0) {
373        calendars = [];
374        for (var i = 0, entry; entry = entry_[i]; ++i) {
375          if (!i) {
376            defaultAuthor = entry.querySelector('title').textContent;
377          }
378          // Include only those calendars which are not hidden and selected
379          var isHidden = entry.querySelector('hidden');
380          var isSelected = entry.querySelector('selected');
381          if (isHidden && isHidden.getAttribute('value') == 'false') {
382            if (isSelected && isSelected.getAttribute('value') == 'true') {
383              var calendar_content = entry.querySelector('content');
384              var cal_src = calendar_content.getAttribute('src');
385              cal_src += '?toolbar=true&max-results=10';
386              calendars.push(cal_src);
387            }
388          }
389        }
390        CalendarManager.getCalendarFeed(0);
391        return;
392      }
393    } else {
394      calendars = [];
395      calendars.push(SINGLE_CALENDAR_SUPPORT_URL);
396      CalendarManager.parseCalendarEntry(xhr.responseXML, 0);
397      return;
398    }
399
400    console.error('Error: feed retrieved, but no event found');
401    nextEvent_ = null;
402    canvasAnimation_.drawFinal();
403  };
404};
405
406/**
407 * Retrieves feed for a calendar
408 * @param {integer} calendarId Id of the calendar in array of calendars.
409 */
410CalendarManager.getCalendarFeed = function(calendarId) {
411  var xmlhttp = new XMLHttpRequest();
412  try {
413    xmlhttp.onreadystatechange = CalendarManager.onCalendarResponse(xmlhttp,
414                                     calendarId);
415    xmlhttp.onerror = function(error) {
416      console.log('error: ' + error);
417      nextEvent_ = null;
418      canvasAnimation_.drawFinal();
419    };
420
421    xmlhttp.open('GET', calendars[calendarId]);
422    xmlhttp.send(null);
423  }
424  catch (e) {
425    console.log('ex: ' + e);
426    nextEvent_ = null;
427    canvasAnimation_.drawFinal();
428  }
429};
430
431/**
432 * Gets the event entries of every calendar subscribed in default user calendar.
433 * @param {xmlHttpRequest} xmlhttp xmlHttpRequest containing server response
434 *     for the feed of a specific calendar.
435 * @param {integer} calendarId Variable for storing the no of calendars
436 *     processed.
437 * @return {Object} anonymous function which returns to onReadyStateChange.
438 */
439CalendarManager.onCalendarResponse = function(xmlhttp, calendarId) {
440  return function() {
441    if (xmlhttp.readyState != 4) {
442      return;
443    }
444    if (!xmlhttp.responseXML) {
445      console.log('No responseXML');
446      nextEvent_ = null;
447      canvasAnimation_.drawFinal();
448      return;
449    }
450    CalendarManager.parseCalendarEntry(xmlhttp.responseXML, calendarId);
451  };
452};
453
454/**
455 * Parses events from calendar response XML
456 * @param {string} responseXML Response XML for calendar.
457 * @param {integer} calendarId  Id of the calendar in array of calendars.
458 */
459CalendarManager.parseCalendarEntry = function(responseXML, calendarId) {
460  var entry_ = responseXML.getElementsByTagName('entry');
461  var mailId = null;
462  var author = null;
463
464  if (responseXML.querySelector('author name')) {
465    author = responseXML.querySelector('author name').textContent;
466  }
467  if (responseXML.querySelector('author email')) {
468    mailId = responseXML.querySelector('author email').textContent;
469  }
470
471  if (entry_ && entry_.length > 0) {
472    for (var i = 0, entry; entry = entry_[i]; ++i) {
473     var event_ = CalendarManager.extractEvent(entry, mailId);
474
475      // Get the time from then to now
476      if (event_.startTime) {
477        var t = event_.startTime.getTime() - getCurrentTime();
478        if (t >= 0 && (event_.attendeeStatus != DECLINED_URL)) {
479            if (isMultiCalendar && author) {
480              event_.author = author;
481            }
482            eventList.push(event_);
483        }
484      }
485    }
486  }
487
488  calendarId++;
489  //get the next calendar
490  if (calendarId < calendars.length) {
491    CalendarManager.getCalendarFeed(calendarId);
492  } else {
493    CalendarManager.populateLatestEvent(eventList);
494  }
495};
496
497/**
498 * Fills the event list with the events acquired from the calendar(s).
499 * Parses entire event list and prepares an array of upcoming events.
500 * @param {Array} eventList List of all events.
501 */
502CalendarManager.populateLatestEvent = function(eventList) {
503  nextEvents = [];
504  if (isMultiCalendar) {
505    eventList.sort(sortByDate);
506  }
507
508  //populating next events array.
509  if (eventList.length > 0) {
510    nextEvent_ = eventList[0];
511    nextEvents.push(nextEvent_);
512    var startTime = nextEvent_.startTime.setSeconds(0, 0);
513    for (var i = 1, event; event = eventList[i]; i++) {
514      var time = event.startTime.setSeconds(0, 0);
515      if (time == startTime) {
516        nextEvents.push(event);
517      } else {
518        break;
519      }
520    }
521    if (nextEvents.length > 1 && isMultiCalendar) {
522      nextEvents.sort(sortByAuthor);
523    }
524    canvasAnimation_.animate();
525    return;
526  } else {
527    console.error('Error: feed retrieved, but no event found');
528    nextEvent_ = null;
529    canvasAnimation_.drawFinal();
530  }
531};
532
533var DATE_TIME_REGEX =
534  /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)\.\d+(\+|-)(\d\d):(\d\d)$/;
535var DATE_TIME_REGEX_Z = /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)\.\d+Z$/;
536var DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
537
538/**
539* Convert the incoming date into a javascript date.
540* @param {String} rfc3339 The rfc date in string format as following
541*     2006-04-28T09:00:00.000-07:00
542*     2006-04-28T09:00:00.000Z
543*     2006-04-19.
544* @return {Date} The javascript date format of the incoming date.
545*/
546function rfc3339StringToDate(rfc3339) {
547  var parts = DATE_TIME_REGEX.exec(rfc3339);
548
549  // Try out the Z version
550  if (!parts) {
551    parts = DATE_TIME_REGEX_Z.exec(rfc3339);
552  }
553
554  if (parts && parts.length > 0) {
555    var d = new Date();
556    d.setUTCFullYear(parts[1], parseInt(parts[2], 10) - 1, parts[3]);
557    d.setUTCHours(parts[4]);
558    d.setUTCMinutes(parts[5]);
559    d.setUTCSeconds(parts[6]);
560
561    var tzOffsetFeedMin = 0;
562    if (parts.length > 7) {
563      tzOffsetFeedMin = parseInt(parts[8], 10) * 60 + parseInt(parts[9], 10);
564      if (parts[7] != '-') { // This is supposed to be backwards.
565        tzOffsetFeedMin = -tzOffsetFeedMin;
566      }
567    }
568    return new Date(d.getTime() + tzOffsetFeedMin * 60 * 1000);
569  }
570
571  parts = DATE_REGEX.exec(rfc3339);
572  if (parts && parts.length > 0) {
573    return new Date(parts[1], parseInt(parts[2], 10) - 1, parts[3]);
574  }
575  return null;
576};
577
578/**
579 * Sorts all the events by date and time.
580 * @param {object} event_1 Event object.
581 * @param {object} event_2 Event object.
582 * @return {integer} timeDiff Difference in time.
583 */
584function sortByDate(event_1, event_2) {
585  return (event_1.startTime.getTime() - event_2.startTime.getTime());
586};
587
588/**
589 * Sorts all the events by author name.
590 * @param {object} event_1 Event object.
591 * @param {object} event_2 Event object.
592 * @return {integer} nameDiff Difference in default author and others.
593 */
594function sortByAuthor(event_1, event_2) {
595  var nameDiff;
596  if (event_1.author && event_2.author && event_2.author == defaultAuthor) {
597    nameDiff = 1;
598  } else {
599    return 0;
600  }
601  return nameDiff;
602};
603
604/**
605 * Fires once per minute to redraw extension icon.
606 */
607function redraw() {
608  // If the next event just passed, re-poll.
609  if (nextEvent_) {
610    var t = nextEvent_.startTime.getTime() - getCurrentTime();
611    if (t <= 0) {
612      CalendarManager.pollServer();
613      return;
614    }
615  }
616  canvasAnimation_.animate();
617
618  // if 30 minutes have passed re-poll
619  if (getCurrentTime() - lastPollTime_ >= POLL_INTERVAL) {
620    CalendarManager.pollServer();
621  }
622};
623
624/**
625 * Returns the current time in milliseconds.
626 * @return {Number} Current time in milliseconds.
627 */
628function getCurrentTime() {
629  return (new Date()).getTime();
630};
631
632/**
633* Replaces ASCII characters from the title.
634* @param {String} data String containing ASCII code for special characters.
635* @return {String} data ASCII characters replaced with actual characters.
636*/
637function filterSpecialChar(data) {
638  if (data) {
639    data = data.replace(/&lt;/g, '<');
640    data = data.replace(/&gt;/g, '>');
641    data = data.replace(/&amp;/g, '&');
642    data = data.replace(/%7B/g, '{');
643    data = data.replace(/%7D/g, '}');
644    data = data.replace(/&quot;/g, '"');
645    data = data.replace(/&#39;/g, '\'');
646  }
647  return data;
648};
649
650/**
651 * Called from options.js page on saving the settings
652 */
653function onSettingsChange() {
654  isMultiCalendar = JSON.parse(localStorage.multiCalendar);
655  badgeAnimation_.start();
656  CalendarManager.pollServer();
657};
658
659/**
660 * Function runs on updating a tab having url of google applications.
661 * @param {integer} tabId Id of the tab which is updated.
662 * @param {String} changeInfo Gives the information of change in url.
663 * @param {String} tab Gives the url of the tab updated.
664 */
665function onTabUpdated(tabId, changeInfo, tab) {
666  var url = tab.url;
667  if (!url) {
668    return;
669  }
670
671  if ((url.indexOf('www.google.com/calendar/') != -1) ||
672      ((url.indexOf('www.google.com/a/') != -1) &&
673      (url.lastIndexOf('/acs') == url.length - 4)) ||
674      (url.indexOf('www.google.com/accounts/') != -1)) {
675
676    // The login screen isn't helpful
677    if (url.indexOf('https://www.google.com/accounts/ServiceLogin?') == 0) {
678      return;
679    }
680
681    if (pendingLoadId_) {
682      clearTimeout(pendingLoadId_);
683      pendingLoadId_ = null;
684    }
685
686    // try to poll in 2 second [which makes the redirects settle down]
687    pendingLoadId_ = setTimeout(CalendarManager.pollServer, 2000);
688  }
689};
690
691/**
692 * Called when the user clicks on extension icon and opens calendar page.
693 */
694function onClickAction() {
695  chrome.tabs.getAllInWindow(null, function(tabs) {
696    for (var i = 0, tab; tab = tabs[i]; i++) {
697      if (tab.url && isCalendarUrl(tab.url)) {
698        chrome.tabs.update(tab.id, {selected: true});
699        CalendarManager.pollServer();
700        return;
701      }
702    }
703    chrome.tabs.create({url: GOOGLE_CALENDAR_URL});
704    CalendarManager.pollServer();
705  });
706};
707
708/**
709 * Checks whether an instance of Google calendar is already open.
710 * @param {String} url Url of the tab visited.
711 * @return {boolean} true if the url is a Google calendar relative url, false
712 *     otherwise.
713 */
714function isCalendarUrl(url) {
715  return url.indexOf('www.google.com/calendar') != -1 ? true : false;
716};
717
718/**
719 * Initializes everything.
720 */
721function init() {
722  badgeAnimation_ = new BadgeAnimation();
723  canvasAnimation_ = new CanvasAnimation();
724
725  isMultiCalendar = JSON.parse(localStorage.multiCalendar || false);
726
727  chrome.browserAction.setIcon({path: '../images/icon-16.gif'});
728  badgeAnimation_.start();
729  CalendarManager.pollServer();
730  window.setInterval(redraw, DRAW_INTERVAL);
731
732  chrome.tabs.onUpdated.addListener(onTabUpdated);
733
734  chrome.browserAction.onClicked.addListener(function(tab) {
735    onClickAction();
736  });
737};
738
739//Adding listener when body is loaded to call init function.
740window.addEventListener('load', init, false);
741