История одного Google Chrome расширения

от автора

В один прекрасный день, за пару часов до конца работы, мне приходит задача: «Нужно написать приложение для браузера, которое должно по клику пользователя отправлять данные со страницы на сайт клиента. Что за приложение и какой браузер — полностью на ваш выбор…».

Немного поразмыслив я пришел к варианту google chrome extension:

  • Crome использует Chromium движок, который является форком WebKit (а это Safari), так же не забываем Blink (а это уже новая (хотя я все еще использую старую с bookmarks’ами) Opera). Таким образом, написав расширения для chrome, мы с минимальными переделками (а то и без них) сможем его портировать на еще 2 браузера
  • Нет опыта работы с API Google Chrome
  • Google все-таки компания добра 🙂

Когда мысли немного улеглись, первое что я сделал — это ввел в поиске харба "расширение Google Chrome". Увидев обширный вариант статей по данной теме, я со спокойной душой ушел домой полностью уверенный в том, что завтра с утра прочитав их, к концу рабочего дня дело будет ‘в шляпе‘ (как же я тогда ошибался). Прочитав парочку их них я имел общее представление о том как это работает, но этого оказалось мало для воплащения моих идей. Что ж, приступим…

Открываем google chrome, вводим chrome://extensions, ставим галочку Developer mode, нажимаем кнопку Load unpacked extension, выбрав папку нажимаем Ok.

В начале было слово манифест. Ниже можете увидеть содержимое этого файла (manifest.json — это обязательное название файла манифеста)

manifest.json

{     "manifest_version": 2,     "name": "My application",   // тут надеюсь все понятно     "version": "0.9",      "icons": {         "16": "./16x16.png",         "32": "./32x32.png",         "48": "./48x48.png",         "128": "./128x128.png"     },      "permissions": [         "tabs",         "http://*/*",         "https://*/*"     ],      "background" : {         "page": "background.html"     },      "content_scripts":[{         "matches": [             "http://*/*",             "https://*/*"         ],         "js": [             "script_in_content.js"         ]     }],      "browser_action": {         "default_title": "Application",         "default_icon" : "./16x16.png"         //  "default_popup": "login.html"        // это имя html-страницы расширения, которая будет всплывать при нажатии на иконку, можно с помощью JS устанавливать различные html страницы     } } 

manifest_version — на данный момент значение 2 обязательное.
version — версия вашего расширения, может содержать только цифры и `.` (те. ‘2.1.12’, ‘0.59’ и тд)
icons — это список всех иконок которые будут отображатся в браузере в различных местах (16 — в адресной строке, 48 — в списке всех расширений и тд.)
permissions — здесь перечислен массив с разрешениями, мне нужно было только tabs.http и https нужен для ajax обмена с любыми сайтами, а также для того что-бы script_in_content.js мог обмениватся данными с фоновой страницей — background.html.
background — это имя фоновой страницы. Фоновая страницы — важный элемент, хотя для некоторых приложений он и не обязателен. Зачем она нужна немного по-позже.
content_scripts — здесь говорится что файл script_in_content.js, будет автоматически загружатся для страницы открытой во вкладке. Страница должны быть открыта с сайтов http://*/* те, всех сайтов с http, но не https, хотя можно было бы указать и их.
browser_action — существует 2 варанта отображения иконки расширения: browser_action и page_action

page_action говорит о том что расширение индивидуально для каждой вкладки, то есть значок будет выводиться в адресной строке. Этот значек можно спрятать/отобразить с помощью JS в зависимости от обстоятельств.

browser_action наоборот не считаются индивидуальными и отображаются не в адресной строке, а в панеле для расширений. Даную иконку никак нельзя скрыть на JS (но можно заблокировать), она отображается постоянно. У browser_action есть одно преимущество по сравнению с page_action, поверх иконки browser_action можно написать пару красивых символов (у меня влазит только 4).

Я выбрал browser_action, как мне необходимо работать не с одним сайтом, а с несколькими. И да, нанесение красивых символов на иконку.
Вот что Google говорит по этому поводу:

Do use page actions for features that make sense for only a few pages.
Don’t use page actions for features that make sense for most pages. Use browser actions instead.

И так, что будет делать наше приложение; скажу сразу, приложение, которое будет далее описано, — это малая часть того что было сделано для клиента. Когда менеджер заходит на сайт hantim.ru для просмотра информации о контракте/вакансии, приложение парсит html код страницы и находит информацию (вакансия, город и тд). При клике на иконку расширения — отображается форма логина, куда менеджер вводит свои данные, а потом может добавить выбранную вакансию/контракт в свой профиль на корпоративном сайте.

Теперь о том как все это работает. Google предоставляет нам такую картину:

1) Inspected window — это то что мы открыли во вкладке, content scripts — это наш script_in_content.js, он имеет полный доступ к DOM страницы.
2) Background page — это сердце приложения, в нашем случае — это background.html.
3) DevTools page — это то, что будет отображатся при клике на иконку расширения (login.html или find.html в нашем случае).

Единственное что меня смущает в данной картинке так это связь DevTools page и Inspected window. Я не нашел решения, что бы передать данные из одной области в другую. Но если выставить Background page как посредника, и через него передавать данный, то все заработает.

И так, настало время кода. Начнем с невидимой стороны.

background.html

<!DOCTYPE html> <html>     <head>         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">         <script type="text/javascript" src="lib.js"></script>         <script type="text/javascript" src="bg.js"></script>     </head>     <body></body> </html> 

Надеюсь с этим вопросов не должно возникнуть. Одно замечание: «background.html — загружается только один раз за все время работы браузера, а имеено при его запуске». Здесь вы можете увидеть что мы загружаем 2 js файла (lib.js — набор функций, bg.js — ‘голова’ приложения).

bg.js

/**  * OnLoad function  *   * @return void  */ window.onload = function(){      // tmp storage     window.bg = new bgObj();      // some variables  !!! important     window.bg.api_site_host = 'http://katran.by';      // get all graber hosts:   !!!once!!!     new Ajax({         url: window.bg.api_site_host+'/regexp.php',         response: 'json',         async: false,         onComplete: function(data){             if(data && data.status && (data.status === 'ok'))                 window.bg.grabber_hosts = data.data;         }     }).send();      // set handler to tabs     chrome.tabs.onActivated.addListener(function(info) {         window.bg.onActivated(info);     });      // set handler to tabs:  need for seng objects     chrome.extension.onConnect.addListener(function(port){         port.onMessage.addListener(factory);     });      // set handler to extention on icon click     chrome.browserAction.onClicked.addListener(function(tab) {         window.bg.onClicked(tab);     });      // set handler to tabs     chrome.tabs.onUpdated.addListener(function(id, info, tab) {         // if tab load         if (info && info.status && (info.status.toLowerCase() === 'complete')){             // if user open empty tab or ftp protocol and etc.             if(!id || !tab || !tab.url || (tab.url.indexOf('http:') == -1))                 return 0;              // save tab info if need             window.bg.push(tab);              // connect with new tab, and save object             var port = chrome.tabs.connect(id);             window.bg.tabs[id].port_info = port;              // run function in popup.html             chrome.tabs.executeScript(id, {code:"initialization()"});              // send id, hosts and others information into popup.js             window.bg.tabs[id].port_info.postMessage({method:'setTabId', data:id});             window.bg.tabs[id].port_info.postMessage({method:'setHosts', data:window.bg.grabber_hosts});             window.bg.tabs[id].port_info.postMessage({method:'run'});              // if user is logged into application set find.html popup             if(window.bg.user.id)                 chrome.browserAction.setPopup({popup: "find.html"});         };     });      window.bg.onAppReady(); };   /**  * Functino will be called when popup.js send some data by port interface  *   * @return void  */ function factory(obj){     if(obj && obj.method){         if(obj.data)             window.bg[obj.method](obj.data);         else             window.bg[obj.method]();     } }   /**  * Popup object  *  * @version 2013-10-11  * @return  Object  */ window.bgObj = function(){ };   /**  * Pablic methods  */ window.bgObj.prototype = {      /**      * some internal params      */     tabs: {},     user: {},     popup_dom: {},     active_tab: {},     grabber_hosts: {},     done_urls: [],      /**      * init() function      */     onAppReady: function()     {         // if user not logged into application set login.html popup         chrome.browserAction.setPopup({popup: "login.html"});     },      /**      * Function add tab into $tabs object, if need      */     push: function(tab)     {         if(tab.id && (tab.id != 0)){             if(!this.tabs[tab.id])                 this.tabs[tab.id] = {tab_obj:tab};         }     },      /**      * Function will be called from popup.js      */     mustParsed: function(data)     {         if(this.tabs[data.tab_id]){             var id = data.tab_id;             this.tabs[id].must_parsed = data.find;              // run parser in popup.js, if need             if(this.tabs[id].must_parsed && (this.tabs[id].must_parsed === true))                 this.tabs[id].port_info.postMessage({method:'parsePage'});         }     },      /**      * Function will be called from popup.js      */     matchesCount: function(data)     {         if(data.tab_id && this.tabs[data.tab_id]){             var id = data.tab_id;             this.tabs[id].matches = data.matches;             this.tabs[id].matches_count = this.tabs[id].matches.length+'';              if(this.tabs[id].matches_count && this.tabs[id].matches_count != '0'){                 chrome.browserAction.setBadgeText({text: this.tabs[id].matches_count});                 return 0;             }         }          // show default text         chrome.browserAction.setBadgeText({text:''});     },      /**      * Function will be called when user change active tab      */     onActivated: function(info)     {         // set active tab         this.active_tab = info;          var data = {};         data.matches  = [];          if(info.tabId){             data.tab_id  = info.tabId;             if(!this.tabs[data.tab_id])                 this.tabs[data.tab_id] = {};             if(!this.tabs[data.tab_id].matches)                 this.tabs[data.tab_id].matches = [];              data.matches = this.tabs[data.tab_id].matches;         }          // set actual count of matches for current tab         this.matchesCount(data);          // if user is logged into application set find.html popup         if(this.user.id)             chrome.browserAction.setPopup({popup: "find.html"});     },      /**      * Function will be called when user click on extension icon      */     onClicked: function(tab)     {         alert('Произошла ошибка. Обратитесь к разработчикам данного приложения.');         return 0;     },      /**      * Function will be called from login.js      */     loginUser: function(user_data)     {         var self = this;         var json_data = false;          // get all graber hosts:   !!!once!!!         new Ajax({             url: window.bg.api_site_host+'/login.php?user='+encodeURIComponent(JSON.stringify(user_data)),             method: 'post',             response: 'json',             async: false,             onComplete: function(data){                 if(data && data.status){                     // if login - ok                     if(data.status === 'ok')                         self.user = data.data;                      json_data = data;                 }             }         }).send();          // return value for login.js         return json_data;     },      /**      * Function will be called from login.js and others places      */     setPopup: function(popup_file)     {         chrome.browserAction.setPopup({tabId: this.active_tab.tabId, popup: popup_file});     },      /**      * Function will be called from find.js and others places      */     getMatches: function()     {         // init if need         if(!this.tabs[this.active_tab.tabId])             this.tabs[this.active_tab.tabId] = {};         if(!this.tabs[this.active_tab.tabId].matches)             this.tabs[this.active_tab.tabId].matches = [];          // if user alredy send this url - remove         for(var i = 0, cnt = this.tabs[this.active_tab.tabId].matches.length; i < cnt; i++){             for(var j = 0, len = this.done_urls.length; j < len; j++){                 if(this.tabs[this.active_tab.tabId].matches[i].url === this.done_urls[j]){                     this.tabs[this.active_tab.tabId].matches[i].url = '';                     break;                 }             }         }          return this.tabs[this.active_tab.tabId].matches;     },      /**      * Function will be called from find.js and others places      */     addUrlToGrabber: function(url)     {         // if $url == ''  -  already used         if(json_data.status && (json_data.status === 'ok')){             var matches = this.tabs[this.active_tab.tabId].matches;             for(var i = 0, cnt = matches.length; i < cnt; i++){                 if(matches[i].url && (matches[i].url === url))                     matches[i].url = '';                     this.done_urls.push(url);             }         }          // return value for login.js         return json_data;     },       /**      * Empty method      */     empty: function()     {     } } 

Первым делом мы дожидаемся window.onload, потом посылаем запрос на katran.by (получим json данные, с какого сайта и каким RegExp’пом мы дастанем необходимые данные), потом вешаем handler’ы на вкладки браузера (для этого мы и указали в манифесте permissions ~ tabs).

    chrome.tabs.onActivated.addListener(function(info) {         window.bg.onActivated(info);     }); 

onActivated — происходит тогда, когда пользователь перешел на новую вкладку (по клику или по alt+tab).

    chrome.tabs.onUpdated.addListener(function(id, info, tab) {         .....     }); 

onUpdated — происходит тогда, когда страница полностью (загрузился не только DOM, а и все картинки) загрузилась во вкладке.

    chrome.browserAction.onClicked.addListener(function(tab) {         window.bg.onClicked(tab);     }); 

onClicked — происходит тогда, когда пользователь кликает на иконке приложения. Небольшое замечание, если во время клика default_popup установлено, то обработчик onClicked — не запустится. default_popup это html страница которая будет отображаеться после нажатия на иконку расширения. default_popup можно выставить в манифесте, а так же с помощью chrome.browserAction.setPopup({popup: «find.html»}); или chrome.pageAction.setPopup({popup: «find.html»});

    chrome.extension.onConnect.addListener(function(port){         port.onMessage.addListener(factory);     }); 

Эта темная магия конструкция, нужна для приема данных, посланных от script_in_content.js с помощью port.
Обработкой данных занимается factory(obj)

function factory(obj){     if(obj && obj.method){         if(obj.data)             window.bg[obj.method](obj.data);         else             window.bg[obj.method]();     } } 

Что же происходит когда пользователь загружает вкладку, а происходит следующее:

  • Вызывается handler onUpdated
  • if (info && info.status && (info.status.toLowerCase() === 'complete')) если все загружено — продолжаем разбор полетов.
  • if(!id || !tab || !tab.url || (tab.url.indexOf('http:') == -1)) если пользователь открыл не web-сайт (проверку на https — забыл, только сейчас заметил 🙂 ), а к примеру вкладку настройки или ftp и тд., то ничего не делаем
  • window.bg.push(tab); — созраняем информацию об текущей вкладке
  • chrome.tabs.executeScript(id, {code:"initialization()"}); — сейчас мы приказываем script_in_content.js выполнить функцию initialization()
  • window.bg.tabs[id].port_info.postMessage({method:'setTabId', data:id}) — мы посылаем данные в script_in_content.js
  • chrome.browserAction.setPopup({popup: "find.html"}); — устанавливаем popup страницу, если пользователь авторизовался ранее

Есть 2 способа передать данные от background.html к script_in_content.js:

  1. сhrome.tabs.executeScript(integer tabId, InjectDetails details, function callback) — одно но, таким спосабом можно передавать данные только в виде строки (не объект, не массив)
  2. сhrome.tabs.sendMessage(integer tabId, any message, function responseCallback) — так можно передавать что угодно, правдо потребуются дополнитеные настройки

И так, мы послали данные в script_in_content.js, значит настало время рассмотреть его код.

script_in_content.js

// set handler to tabs:  need for seng objects to backgroung.js chrome.extension.onConnect.addListener(function(port){     port.onMessage.addListener(factory); });   /**  * Function remove spaces in begin and end of string  *  * @version 2012-11-05  * @param   string  str  * @return  string  */ function trim(str) {     return String(str).replace(/^\s+|\s+$/g, ''); }   /**  * Functino will be called from background.js  *   * @return void  */ function initialization(){     window.popup = new popupObj(); }   /**  * Functino will be called when background.js send some data by port interface  *   * @return void  */ function factory(obj){     if(obj && obj.method){         if(obj.data)             window.popup[obj.method](obj.data);         else             window.popup[obj.method]();     } }   /**  * Popup object  *  * @version 2013-10-11  * @return  Object  */ window.popupObj = function(){ };   /**  * Pablic methods  */ window.popupObj.prototype = {      /**      * some internal params      */     available_hosts: [],     total_host: null,     matches: [],     tab_id: null,     port: null,     cars: [],      /**      * Function will be called from bg.js      */     setHosts: function(hosts)     {         this.available_hosts = hosts;     },      /**      * Function will be called from bg.js      */     setTabId: function(id)     {         this.tab_id = id;     },      /**      * Function check total host      */     run: function()     {         // get total host         if(document.location.host && (document.location.host != ''))             this.total_host = document.location.host;         else if(document.location.hostname && (document.location.hostname != ''))             this.total_host = document.location.hostname;          if(!this.total_host || (this.total_host === ''))             return 0;          var find = false;         // if total host in array $available_hosts - parse page for finde cars         for (host in this.available_hosts) {             if(this.total_host.indexOf(host) != -1){                 this.total_host = host;                 find = true;                 break;             }         };          // create connection to backgroung.html and send request         this.port = chrome.extension.connect();         this.port.postMessage({method:'mustParsed', data:{tab_id:this.tab_id, find:find}});     },      /**      * Function will be called from bg.js      * Parse page      */     parsePage: function()     {         // reset variable before parse         this.matches = [];          if(!this.available_hosts[this.total_host])             return 0;          var html = window.document.body.innerHTML;         var reg_exp = this.available_hosts[this.total_host];         var matches = {};         var match = [];         var find = false;         for(var i = 0, len = reg_exp.length; i < len; i++) {             var exp = new RegExp(reg_exp[i].reg_exp, reg_exp[i].flag);             match = exp.exec(html);              if(match && match.length && reg_exp[i].index){                 matches[reg_exp[i].field] = trim(match[reg_exp[i].index]);                 find = true;             }             else if(match && match.length){                 matches[reg_exp[i].field] = match;                 find = true;             }         }          // this url will be send to site         if(find === true){             matches.url = document.location.href;             this.matches.push(matches);         }          // send count of matches         this.port.postMessage({method:'matchesCount', data:{tab_id:this.tab_id, matches: this.matches}});     } } 

Первое что бросается в глаза — это прием данных от background.html, как можете заметить он такой же как и в bg.js:

chrome.extension.onConnect.addListener(function(port){     port.onMessage.addListener(factory); }); 

Как помните, ранее в bg.js мы запустили initialization(), setTabId(), setHosts() и run(). Найбольший интерес представляет window.popup.run(). Там проверяется доменное имя сервера открытой страницы, и если это имя совпадает со списком сайтов которые нам интересны (данные с которых необходимо передать на корпоративный ресурс) — find = true; и отправляем запрос window.bg.mustParsed(obj) в bg.js.

    /**      * Function will be called from script_in_content.js      */     mustParsed: function(data)     {         if(this.tabs[data.tab_id]){             var id = data.tab_id;             this.tabs[id].must_parsed = data.find;              // run parser in popup.js, if need             if(this.tabs[id].must_parsed && (this.tabs[id].must_parsed === true))                 this.tabs[id].port_info.postMessage({method:'parsePage'});         }     } 

Если совпадение домена было найдено, то запускаем парсер страницы parsePage() в script_in_content.js.

    /**      * Function will be called from bg.js      * Parse page      */     parsePage: function()     {         // reset variable before parse         this.matches = [];          if(!this.available_hosts[this.total_host])             return 0;          var html = window.document.body.innerHTML;         var reg_exp = this.available_hosts[this.total_host];         var matches = {};         var match = [];         var find = false;         for(var i = 0, len = reg_exp.length; i < len; i++) {             var exp = new RegExp(reg_exp[i].reg_exp, reg_exp[i].flag);             match = exp.exec(html);              if(match && match.length && reg_exp[i].index){                 matches[reg_exp[i].field] = trim(match[reg_exp[i].index]);                 find = true;             }             else if(match && match.length){                 matches[reg_exp[i].field] = match;                 find = true;             }         }          // this url will be send to site         if(find === true){             matches.url = document.location.href;             this.matches.push(matches);         }          // send count of matches         this.port.postMessage({method:'matchesCount', data:{tab_id:this.tab_id, matches: this.matches}});     } 

Если скрипт что-то нашел на странице, то все что он нашел складывает в массивчик, добавляет к нему текущий url страницы и отправляет назад в bg.js, мол: «Смотри что я нашел…». В ответ на это, bg.js анализирует входные данные, и если RegExp’ы нашли что-то — пишет поверх иконки колличество совпадений (1, 2 и тд.) chrome.browserAction.setBadgeText({text: this.tabs[id].matches_count});.

Это вроде все основные моменты рыботы связки bg.js и script_in_content.js.
Теперь поговорим о popup. Когда пользователь кликает по иконке приложения — отображается форма login.html.
Менеджер вводит свои данные от корпоративного сайта, нажимает Login и тут происходит следующее:

login.html

<!DOCTYPE html> <html>     <head>         <meta   http-equiv="content-type" content="text/html; charset=UTF-8">         <script type="text/javascript"    src="login.js"></script>         <link   type="text/css"           rel="stylesheet" href="login.css">         <title>Grabber popup</title>     </head>     <body>         <div class="body">             <div class="emptyLogin">                 <div id="error_message"> </div>                 <form name="login_form" action="" method="get" id="popup_login_form">                     <table>                         <tbody>                             <tr>                                 <td align="right">Ваш E-mail:</td>                                 <td><input type="text" name="login" value="" tabindex="1"></td>                             </tr>                             <tr>                                 <td align="right">Пароль:</td>                                 <td><input type="password" name="pass" value="" tabindex="2"></td>                             </tr>                             <tr>                                 <td colspan="2" align="center"><input type="submit" value="Login" class="button"></td>                             </tr>                         </tbody>                     </table>                 </form>             </div>             <div id="loader"><img src="ajax-loader.gif" title="Loding" alt="Loading"></div>         </div>     </body> </html> 

login.js

/**  * OnLoad function  *   * @return void  */ window.onload = function(){      // set some events handlers     document.getElementById('popup_login_form').onsubmit = function(obj){         // fade popup         document.getElementById('loader').style.display = 'block';         document.getElementById('error_message').innerHTML = ' ';          if(obj.target.elements && obj.target.elements.length && (obj.target.elements.length === 3)){             var data = {};             data.login = obj.target.elements[0].value;             data.pass  = obj.target.elements[1].value;              setTimeout(function(){                 var bg_wnd = chrome.extension.getBackgroundPage();                 var result = bg_wnd.bg.loginUser(data);                  if(result && result.status && (result.status === 'error'))                     document.getElementById('error_message').innerHTML = result.mess;                 else{                     // set new popup html code and close popup window                     bg_wnd.bg.setPopup('find.html');                     window.close();                 }                  // hide fade on popup                 document.getElementById('loader').style.display = 'none';             }, 500);         }         return false;     }; } 

Задача login.js состоит в том, что бы повесить onsubmit на форму, и отправить логин/пароль в background.html (bg.js),
а делается это с помощью следующей конструкции (как увидите, мы пожем на прямую вызывать методы объекта bg.js):

                var bg_wnd = chrome.extension.getBackgroundPage();                 var result = bg_wnd.bg.loginUser(data); 

bg_wnd.bg.loginUser(data) отправляет данные на сервер, если все хорошо, то popup login.html сменяет find.html,
а данные о пользователе сохраняются в переменной. Смена popup происходит следующим образом:

    /**      * Function will be called from login.js and others places      */     setPopup: function(popup_file)     {         chrome.browserAction.setPopup({tabId: this.active_tab.tabId, popup: popup_file});     }, 

Небольшое замечание, если пользователь открыл popup login.html поставил курсор в поле ‘Ваш E-mail:’ и нажимает TAB (первый раз) в надежде перейти к паролю, то его ожидает разачарование, фокус не сменится. Данный баг все еще актуален.

Так, осталось совсем чуть-чуть.
После того как мы успешно авторизовались, мы поменя popup на find.html.

find.html

<!DOCTYPE html> <html>     <head>         <meta   http-equiv="content-type" content="text/html; charset=UTF-8">         <script type="text/javascript"    src="find.js"></script>         <link   type="text/css"           rel="stylesheet" href="find.css">         <title>Grabber</title>     </head>     <body>         <div class="body">             <div class="carsRows" id="popup_cars_rows">                 <h3 style="text-align: center; margin: 5px 0;">Найденные данный странице</h3>                 <form name="cars_form" action="" method="get" id="popup_cars_form">                     <table id="popup_cars_table">                         <thead>                             <tr>                                 <th class="make">Вакансия</th>                                 <th class="info">Город</th>                                 <th class="addBtn"> </th>                             </tr>                         </thead>                         <tbody>                         </tbody>                     </table>                 </form>             </div>             <div class="carsRows" id="popup_cars_rows_none" style="display: none;">                 <h3 style="text-align: center; margin: 5px 0;">Ничего не найдено на странице</h3>             </div>             <div id="loader"><img src="ajax-loader.gif" title="Loding" alt="Loading"></div>         </div>     </body> </html> 

find.js

/**  * OnLoad function  *   * @return void  */ window.onload = function(){      // set new popup html code and close popup window     window.bg_wnd = chrome.extension.getBackgroundPage();     var rows = window.bg_wnd.bg.getMatches();      // function render popup     renderPopup(rows); }   /**  * Function set cars into html  *  * @param  array  $rows  * @return void  */ function renderPopup(rows) {     if(rows.length === 0){         document.getElementById('popup_cars_rows').style.display = 'none';         document.getElementById('popup_cars_rows_none').style.display = 'block';         return 0;     }     else{         document.getElementById('popup_cars_rows').style.display = 'block';         document.getElementById('popup_cars_rows_none').style.display = 'none';     }      for (var i = 0, cnt = rows.length; i < cnt; i++)         renderRow(rows[i]); }   /**  * Function set cars into html  *  * @param  object $row  * @return void  */ function renderRow(row) {     var tbl = document.getElementById('popup_cars_table').children[1];      // add divided row     var td = tbl.insertRow(-1).insertCell(-1);     td.setAttribute('colspan', '3');     td.innerHTML = '<hr style="border: 1px solid #909090; width: 75%">';      var tr = tbl.insertRow(-1);     var td1 = tr.insertCell(-1);     var td2 = tr.insertCell(-1);     var td3 = tr.insertCell(-1);     var vacancy = [];     var city    = [];      var hash = {         vacancy: 'вакансия',         city: 'город',     }      var table_row = [];     for(key in row){         if(hash[key]){             if(key == 'vacancy')                 vacancy.push(row[key]);             if(key == 'city')                 city.push(row[key]);         }     }      td1.innerHTML = vacancy.join(' ');;     td2.innerHTML = city.join(' ');     td3.innerHTML = (row.url === '')?'<b><em>Добавлено</em></b>':'<input type="button" value="Дабавить" name="cars[]" class="button"><input type="hidden" value="'+row.url+'" name="url[]">';     td3.children[0].addEventListener('click', function(){addToGrabber(event)}, false); }   function addToGrabber(e) {     // hide fade on popup     document.getElementById('loader').getElementsByTagName('img')[0].style.marginTop = (window.innerHeight/2-10)+'px';     document.getElementById('loader').style.display = 'block';      if(e && e.srcElement){         var url = e.srcElement.parentNode.children[1].value;          setTimeout(function(){             var result = window.bg_wnd.bg.addUrlToGrabber(url);             e.srcElement.parentNode.innerHTML = '<b><em>Добавлено</em></b>';              // hide fade on popup             document.getElementById('loader').style.display = 'none';         }, 500);     } } 

Как только find.html загрузился, в работу вступает find.js. Его задача спросить bg.js: ‘Что там у тебя есть на текущую страницу’ — и отобразить то, что отдал bg.js.

/**  * OnLoad function  *   * @return void  */ window.onload = function(){      // set new popup html code and close popup window     window.bg_wnd = chrome.extension.getBackgroundPage();     var rows = window.bg_wnd.bg.getMatches();      // function render popup     renderPopup(rows); } 

Так выглядит готовое решение.

C кнопкой ‘Добавить’ я думаю сами разберетесь, как она работает. На последок хочу сказать как все это дело отлаживается.
background.html — что бы посмотреть работу скриптов bg.js и lib.js нужно кликнуть на линку background.html на странице chrome://extensions.

script_in_content.js — он выполняется в контексте страницы, поэтому можете смело инспектировать страницу и смотреть консоль с выводом ошибок в нее.
login.html и find.html — что бы вывести их Developer Tools, нужно кликнуть на иконке приложения и правым кликом мышки выбрать инспекцию страницы.

PS. Весь JavaScript должен находится в js файлах, если вы его вставите в html — chrome будет ругаться.
Также пару ссылок:
на документацию: manifest.json, Chrome’s API
на github.com: исходный код

ссылка на оригинал статьи http://habrahabr.ru/post/198778/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *