<
  • Главная
Статьи

Створення своїх власних розширень для браузера: Частина 4. Перехід до розширень, які залежать від браузера

  1. Серія контенту:
  2. Цей контент є частиною серії: Створення своїх власних розширень для браузера
  3. Чи не вирішив чи вже це завдання будь-хто інший?
  4. Перед початком роботи
  5. анатомія Gawkblocker
  6. Малюнок 1. Сторінки спливаючого вікна і властивостей Chrome
  7. Малюнок 2. Комбінована версія сторінки спливаючого вікна і властивостей для Firefox
  8. Малюнок 3. Комбінована сторінка спливаючого вікна і властивостей для Safari
  9. Абстрагування коду, специфічного для браузера
  10. Малюнок 4. Сторінки спливаючого вікна / властивостей розширень для Firefox і Safari
  11. Лістинг 1. Код JavaScript сторінки спливаючого вікна
  12. Лістинг 2. Код об'єкта BA
  13. Лістинг 3. Перевірка наявності API Safari, Chrome або Firefox
  14. Лістинг 4. Сторінка JavaScript background.html
  15. Адаптація існуючих модулів
  16. Лістинг 5. Імпорт модуля SM
  17. Лістинг 6. Обхідний шлях для Firefox
  18. Отримання коду, що залежить від браузера
  19. Лістинг 7. Сторінка спливаючого вікна Safari
  20. Лістинг 8. Сторінка спливаючого вікна Firefox
  21. Лістинг 9. Додавання процесу прослуховування подій beforeNavigate в Safari.
  22. Лістинг 10. Додавання процесів прослуховування для вкладок Chrome
  23. Лістинг 11. Створення панелі і віджета для Firefox
  24. Лістинг 12. Оброблювач intercept для Safari
  25. Пастка
  26. Лістинг 13. Додавання обробника подій click в Firefox
  27. Лістинг 14. Додавання методу removeSite
  28. Малюнок 5. Два набору модулів - недобре!
  29. висновок
  30. Ресурси для скачування

Створення своїх власних розширень для браузера

Виняток перевантаження процесора і надмірності в розширенні для Chrome, Firefox і Safari

Серія контенту:

Цей контент є частиною # з серії # статей: Створення своїх власних розширень для браузера

http://www.ibm.com/search/csass/search/?sn=dw&lang=ru&cc=RU&en=utf&hpp=20&dws=rudw&lo=ru&q=%D0%A1%D0%BE%D0%B7%D0%B4% D0% B0% D0% BD% D0% B8% D0% B5 +% D1% 81% D0% B2% D0% BE% D0% B8% D1% 85 +% D1% 81% D0% BE% D0% B1% D1 % 81% D1% 82% D0% B2% D0% B5% D0% BD% D0% BD% D1% 8B% D1% 85 +% D1% 80% D0% B0% D1% 81% D1% 88% D0% B8% D1% 80% D0% B5% D0% BD% D0% B8% D0% B9 +% D0% B4% D0% BB% D1% 8F +% D0% B1% D1% 80% D0% B0% D1% 83% D0% B7% D0% B5% D1% 80% D0% B0 & Search =% D0% 9F% D0% BE% D0% B8% D1% 81% D0% BA

Слідкуйте за виходом нових статей цієї серії.

Цей контент є частиною серії: Створення своїх власних розширень для браузера

Слідкуйте за виходом нових статей цієї серії.

Чи не вирішив чи вже це завдання будь-хто інший?

Crossrider і Kango Framework (див. Розділ ресурси ) Допомагають створювати крос-браузерні розширення, але кожен з цих інструментів має свої обмеження. Crossrider підтримує Safari в меншій мірі, ніж Firefox і Chrome. Kango Framework, як стверджується, створює розширення для всіх основних браузерів (включаючи Internet Explorer і Opera), але якщо проект не відноситься до розробки ПЗ з відкритим вихідним кодом, то потрібно заплатити за ліцензію. І обидва інструменту страждають однією і тією ж проблемою: ви працюєте з браузерами не безпосередньо, так що ваші можливості обмежені API, наданими середовищем, що додає рівень, яким не можна або майже не можна керувати. У майбутньому кожен з цих інструментів може виявитися дієвим рішенням для ваших завдань, але спочатку корисно вирішити таке завдання самостійно. Вважайте це навчальною практикою.

В цьому циклі статей ми вже написали розширення Gawkblocker для браузерів Chrome, Firefox і Safari. Нагадаємо, що Gawkblocker дозволяє блокувати певні домени, які користувач вважає за краще не відвідувати, такі як блоги, віднімають багато часу. Ми написали розширення Gawkblocker для Chrome, потім адаптували його для Firefox і для Safari. Але наявність трьох аналогічних наборів файлів розширень для всіх трьох браузерів веде до додаткової роботи і надлишкового коду.

У цій останній статті ми усунемо надмірність і додаткову роботу, підійшовши якомога ближче до створення загальної бази коду Gawkblocker. (Повний вихідний код наведено в розділі завантаження ). Деякі речі легко піддаються об'єднанню, наприклад, основні файли JavaScript і шаблони HTML. З попередніх статей ви знаєте, що механізми зберігання даних від браузера до браузеру різниться, і у кожного браузера свій унікальний API для відстеження змін в URL. Прочитавши статтю, ви зможете відповісти на основні питання:

  • Яку частину розширення можна зробити не залежить від браузера?
  • Чи не виявиться таке розширення невиправдано складним?

Перед початком роботи

Про це циклі статей

В цьому циклі з чотирьох статей розглядається процес створення розширення Gawkblocker для трьох браузерів: Chrome, Firefox і Safari.

  • Перша частина присвячена створенню розширення для Google Chrome - від початку і до розміщення в App Store.
  • під другій частині ми побудували доповнення (Або розширення) для Mozilla Firefox.
  • В третій частині адаптували його для браузера Safari.
  • А тепер спробуємо зробити код розширення взагалі не залежних від браузера.

Але перш ніж ви взятися за це, прочитайте перші три статті циклу . Ця стаття написана з розрахунком на Chrome 23 (збірка Canary), Firefox 16 (beta channel) і Safari 6.0 (див. Розділ ресурси ). Для роботи з браузером Safari знадобиться Mac, оскільки у нього немає версій для інших операційних систем. Вам також знадобиться інструмент для редагування HTML, CSS і JavaScript. І якщо ви все ж вирішите взятися за цю статтю без попереднього прочитання попередніх, то прочитайте хоча б опис установок для створення розширень для кожного браузера.

Довідковими документами можуть служити документація по Chrome Extension, Firefox Add-on SDK і Safari Extensions Reference (див. Розділ ресурси ). Завдяки вже набутим знанням, вам навряд чи доведеться звертатися до цих документів більше, ніж пару раз. Але про всяк випадок відкрийте їх.

анатомія Gawkblocker

У Gawkblocker для Chrome використовуються:

  • фонова сторінка для управління додатком;
  • файл JavaScript, що містить велику частину логіки;
  • спливаюча сторінка для відображення користувачеві того, що заблоковано в даний час;
  • сторінка властивостей для управління заблокованими сайтами;
  • цільова сторінка, на яку перенаправляються користувачі.

На малюнку 1 показані сторінки спливаючого вікна і властивостей.

Малюнок 1. Сторінки спливаючого вікна і властивостей Chrome
Створення своїх власних розширень для браузера   Виняток перевантаження процесора і надмірності в розширенні для Chrome, Firefox і Safari   Серія контенту:   Цей контент є частиною # з серії # статей: Створення своїх власних розширень для браузера   http://www

У Firefox ми скомбінували сторінки властивостей і спливаючого вікна в одну, змінивши файл JavaScript для використання API зберігання даних Firefox, і управляли додатком за допомогою файлу main.js. Переадресуемие користувачі направляються прямо на YouTube. Комбінована версія сторінки спливаючого вікна і властивостей в Firefox показана на малюнку 2.

Малюнок 2. Комбінована версія сторінки спливаючого вікна і властивостей для Firefox

У браузері Safari використовується комбінована сторінка спливаючого вікна і властивостей, фонова сторінка для управління додатком і той же файл JavaScript, що і в Chrome. Комбінована сторінка спливаючого вікна і властивостей Safari показана на малюнку 3.

Малюнок 3. Комбінована сторінка спливаючого вікна і властивостей для Safari

Тут ми також будемо використовувати комбіновану сторінку спливаючого вікна і властивостей. Досить скоро ви дізнаєтеся, чи повинні main.js і фонова сторінка залишатися різними для різних версій.

Абстрагування коду, специфічного для браузера

Абстрагування коду, що залежить від браузера, можна почати з двох різних місць: коду спливаючого вікна і основного файлу JavaScript. Почнемо з коду спливаючого вікна.

На малюнку 4 показані пліч-о-пліч сторінки спливаючого вікна / властивостей розширень для Firefox і Safari.

Малюнок 4. Сторінки спливаючого вікна / властивостей розширень для Firefox і Safari

Основні важливі відмінності між ними ставляться до способу звернення до об'єкту GB з фонової сторінки. У Safari (і в Chrome, не показаний) код спливаючого вікна може звертатися безпосередньо до фонової сторінці. У Firefox він повинен відправити повідомлення в файл main.js. Крім того, кожен браузер виконує свою процедуру ініціалізації при відкритті сторінки спливаючого вікна. Якщо екстерналізіровать процес ініціалізації і звернень до об'єкта GB, то можна буде використовувати один і той же код спливаючого вікна.

Створимо об'єкт BA для обробки дій, що залежать від браузера, і помістимо його в BA.js. В цьому об'єкті поставимо у відповідність кожному браузеру те, що в ньому потрібно робити. Крім того, перенесемо об'єкт StorageManager (SM) в окремий файл. Код JavaScript сторінки спливаючого вікна представлений в лістингу 1.

Лістинг 1. Код JavaScript сторінки спливаючого вікна

<Script src = "BA.js"> </ script> <script src = "SM.js"> </ script> <script src = "GB.js"> </ script> <script> $ (document). ready (function () {BA.handle.popup (); ... showBlockList (GB.getBlockedSites ());}); </ Script>

В об'єкті BA визначимо таблицю відповідності браузерів та належних до них дій. Ми знаємо напевно, що об'єкт BA повинен налаштовувати спливаючу сторінку, фонову сторінку і перехоплювати запити браузера, як це робиться в лістингу 2.

Лістинг 2. Код об'єкта BA

actionMap = { 'safari': { 'popup': function () {}, 'background': function (callback) {}, 'intercept': function (candidate, site, watchthis) {}}, 'chrome': { ...}

Потрібно також, щоб об'єкт BA визначав, в якому браузері він працює. Виконаємо тест на наявність API -safari для Safari, chrome для Chrome і require або addon для Firefox (в залежності від того, чи знаходиться він в main.js або в коді спливаючого вікна). Тест виконується при створенні модуля, тому його результат доступний в будь-який момент. Код, наведений у лістингу 3, перевіряє наявність API.

Лістинг 3. Перевірка наявності API Safari, Chrome або Firefox

if (typeof safari! == 'undefined') {console.log ( "Safari"); // Звернення до глобальної сторінці за допомогою // safari.extension.globalPage.contentWindow my.handle = actionMap.safari; } Else if (typeof chrome! == 'undefined') {console.log ( "Chrome"); // Звернення до глобальної сторінці за допомогою chrome.extension.getBackgroundPage () my.handle = actionMap.chrome; } Else if (typeof require! == 'undefined') {console.log ( "Firefox - background"); // Прослуховування повідомлень за допомогою addon.port.on (messagename, callback); // Відправка повідомлення за допомогою addon.port.emit (messagename, message); my.handle = actionMap.firefox; } Else if (typeof addon! == 'undefined') {console.log ( "Firefox - popup"); // Прослуховування повідомлень за допомогою addon.port.on (messagename, callback); // Відправка повідомлення за допомогою addon.port.emit (messagename, message); my.handle = actionMap.firefox; }

При виклику BA.handle.popup () в Safari BA.handle вже налаштований на об'єкт з actionMap.safari, що містить функцію установки popup () для цього браузера.

Щось подібне робиться на сторінках background.html і в файлі main.js, який використовується для розширень. Ми викликаємо BA.handle.background () для настройки фонової сторінки (приєднання прослуховуючих процесів і т.п.) і передаємо його функції, яка визначає, чи слід блокувати сайт. Сторінки JavaScript background.html нагадують лістинг 4.

Лістинг 4. Сторінка JavaScript background.html

<Script src = "BA.js"> </ script> <script src = "SM.js"> </ script> <script src = "GB.js"> </ script> <script> $ (document). ready (function () {... function shouldIBlockThis (candidate) {var site, blockedSites = GB.getBlockedSites (); for (site in blockedSites) {if (blockedSites.hasOwnProperty (site)) {BA.handle.intercept (candidate , site, GB.getWatchThisInstead ());}}} BA.handle.background (shouldIBlockThis);}); </ Script>

Код з файлу main.js виглядає так само; тільки для вилучення модулів BA і GB використовується require. Потім модуль GB імпортує модуль SM, так що в main.js це робити необов'язково.

Адаптація існуючих модулів

Ми злегка змінимо модулі BA, SM і GB, так щоб вони не залежали від браузера. Зокрема, Firefox потрібно, щоб модулі експортувалися і запрошувалися. Відкоригуємо exports і require і будемо використовувати їх умовно. На початку модуля GB помістимо код, наведений у лістингу 5, щоб імпортувати модуль SM.

Лістинг 5. Імпорт модуля SM

if (typeof require! == 'undefined') {var SM = require ( "SM"). SM; } // І переходимо до експорту модуля GB if (typeof exports! == 'undefined') {exports.GB = GB; }

Цей модуль SM створює проблему. Нагадаємо, що Firefox не має доступу до localStorage з розширення, тому в частини 2 ми використовували simple-storage. Для цього до модуля BA можна додати обробник. Також можна додати перевірку в об'єкті SM. Це дозволить підтримувати об'єкт localStorage в новій версії Firefox, якщо він стане доступним, і зберегти підтримку старих версій. Цей обхідний шлях показаний в лістингу 6.

Лістинг 6. Обхідний шлях для Firefox

if (typeof localStorage! == 'undefined') {// В даний час підтримується в Chrome і Safari my.get = function (key) {return localStorage.getItem (key); }; my.put = function (key, value) {return localStorage.setItem (key, value); }; my.remove = function (key) {return localStorage.removeItem (key); }; } Else if (typeof require! == 'undefined') {// Це для Firefox. Звернення і використання simple-storage SS = require ( "simple-storage"); console.log ( "SimpleStorage"); my.get = function (key) {return SS.storage [key]; }; ...}

Цей обхідний шлях також дозволяє використовувати модуль SM для іншого проекту. За допомогою цієї структури можна підключати код, що залежить від браузера.

Отримання коду, що залежить від браузера

Сторінки спливаючого вікна для кожного браузера трохи відрізняються. Для Safari потрібно знати, коли вона відкривається, щоб очистити зображення, як показано в лістингу 7.

Лістинг 7. Сторінка спливаючого вікна Safari

'Popup': function () {// Сторінки спливаючого вікна Safari зберігають стан. При його закриття // обов'язково скидайте параметри і приводите елементи div в початковий стан. safari.application.addEventListener ( "popover", function () {$ ( "# onestep"). show (); $ ( "# options"). hide ();}, true); }

Chrome не вимагає ніякої додаткової роботи - приємно!

Для Firefox потрібно налаштувати повідомлення port і скинути зображення, як показано в лістингу 8.

Лістинг 8. Сторінка спливаючого вікна Firefox

'Popup': function () {// Сторінки спливаючого вікна Firefox зберігають стан. При його закриття // обов'язково скидайте параметри і приводите елементи div в початковий стан. addon.port.on ( "popshow", function () {$ ( "# onestep"). show (); $ ( "# options"). hide ();}); // Сторінки спливаючого вікна Firefox не можуть звертатися до об'єкта GB безпосередньо, // так що доводиться надсилати повідомлення. addon.port.emit ( "pop"); $ ( "# Watchthis"). Click (function () {addon.port.emit ( "watchthis");}); $ ( "# Makethathappen"). Click (function () {addon.port.emit ( "makethathappen", $ ( "# watchthatinstead"). Val ());}); $ ( "# Blockthistoo"). Click (function () {addon.port.emit ( "dontgothere", $ ( "# dontgothere"). Val ());}); addon.port.on ( "blocklist", function (blocklist) {showBlockList (blocklist);}); addon.port.on ( "watchthatinstead", function (instead) {$ ( "# watchthatinstead"). val (instead);}); }

Щось подібне потрібно зробити для фонових сторінок і main.js. У Safari додамо процес прослуховування подій beforeNavigate, як показано в лістингу 9.

Лістинг 9. Додавання процесу прослуховування подій beforeNavigate в Safari.

'Background': function (callback) {safari.application.addEventListener ( "beforeNavigate", callback, true); }

Для Chrome додамо процеси прослуховування для вкладок, як показано в лістингу 10.

Лістинг 10. Додавання процесів прослуховування для вкладок Chrome

'Background': function (callback) {chrome.tabs.onUpdated.addListener (function (tabId, changedInfo, tab) {callback (tab);}); chrome.tabs.onCreated.addListener (function (tab) {callback (tab);}); }

Для Firefox створимо панель і віджет, а потім налаштуємо іншу половину сеансу зв'язку port. Зберемо відповідні модулі, як показано в лістингу 11.

Лістинг 11. Створення панелі і віджета для Firefox

'Background': function (callback) {var data = require ( "self"). Data, tabs = require ( "tabs"), GB = require ( "GB"). GB, popupPanel = require ( "panel"). Panel ({height: 500, contentURL: data.url ( "popup.html"), onShow: function () {this.port.emit ( "popshow", true);}}); tabs.on ( "ready", function (tab) {callback (tab);}); require ( "widget"). Widget ({id: "GBBrowserAction", label: "Gawkblocker", contentURL: data.url ( "GB-19.png"), panel: popupPanel}); popupPanel.port.on ( "pop", function () {popupPanel.port.emit ( "blocklist", GB.getBlockedSites ()); popupPanel.port.emit ( "watchthatinstead", GB.getWatchThisInstead ());}) ; ...}

Тепер нам знадобиться обробник intercept- код, який запускається, щоб перевірити, чи потрібно блокувати сайт. Кожен обробник працює трохи по-різному, але структура у них одна і та ж: об'єкт вкладки або подія передається обробнику intercept разом з підприємством, що перевіряється сайтом і сторінкою переадресації користувача, якщо сайт заблокований. Оброблювач intercept виконує необхідні дії для даного браузера. Для Safari він виглядає, як в лістингу 12.

Лістинг 12. Оброблювач intercept для Safari

'Intercept': function (candidate, site, watchthis) {if (candidate.url && candidate.url.match (site)) {candidate.preventDefault (); candidate.target.url = watchthis; }}

Отже, для Safari і Chrome все готово! Можна завантажувати універсальні файли, і вони будуть працювати в обох браузерах.

Пастка

Однак для Firefox це ще не все. Chrome і Safari в цьому випадку працюють легко, тому що сторінка спливаючого вікна і фонова сторінка можуть звертатися в один і той же місце в межах localStorage. У Firefox, перебуваючи всередині коду спливаючого вікна, можна отримати доступ до об'єкта simple-storage або об'єктам з контексту main.js. Ось чому все робиться за допомогою передачі повідомлень. При створенні списку заблокованих сайтів на сторінці спливаючого вікна в Firefox кожному сайту є потреба у додатковому обробник click, щоб передати повідомлення в main.js. Цей додатковий обробник можна додати в блок, де створюються обробники click, як показано в лістингу 13.

Лістинг 13. Додавання обробника подій click в Firefox

$ ( "# Unblock-" + i) .click (function () {GB.removeBlockedSite (index); BA.handle.removeSite (index); showBlockList (GB.getBlockedSites ());});

Потім додамо метод removeSite в об'єкт actionMap.firefox модуля BA, як показано в лістингу 14.

Лістинг 14. Додавання методу removeSite

'RemoveSite': function (index) {addon.port.emit ( "unblock", index); }

Якщо це викликає проблеми, потрібно шукати кращі способи. Залежно від вимог розширення код може виявитися проблематичним. В цьому випадку потрібно повернутися на крок назад і переглянути те, що зроблено.

Ще гірше, що в разі Firefox ви потрапляєте в зачароване коло. Сторінка спливаючого вікна включає в себе модулі BA, SM і GB. Але все використовувані фактичні дані надходять з файлу main.js, якому також потрібні модулі BA, SM і GB. А так як вони не можуть працювати на два фронти, то в розширення для Firefox доводиться включати по дві копії кожного модуля, як показано на малюнку 5.

Малюнок 5. Два набору модулів - недобре!

Дубльовані модулі - не найкращий варіант. Їх можна було б розділяти або передавати туди-назад, але це вже занадто. З'являються сумніви, чи варта гра свічок.

висновок

Тепер можна відповісти на питання, поставлені на початку статті.

  • Якою мірою розширення можна зробити не залежних від браузера? Як з'ясовується, в досить значною. Основний клас JavaScript і код спливаючого вікна не залежать від браузера. Код JavaScript фонової сторінки майже ідентичний файлу main.js, а інші модулі працюють в будь-якому з браузерів.
  • Чи не виявиться таке розширення невиправдано складним? Можливо. Модуль GB досить простий. Модуль BA надає єдине місце, де можна виконати більшу частину роботи браузера. Однак залишається подолати відмінності між розширенням для Firefox і для Chrome або Safari.

Дуже хотілося б зробити так, щоб код спливаючого вікна і файл main.js розширення для Firefox спільно використовували об'єкти або принаймні отримували доступ до однієї і тієї ж області зберігання. У цьому випадку вдалося б майже повністю виключити передачу повідомлень. Але чи не занадто зациклюйтеся на цю проблему. Можна встановити паритет між усіма трьома браузерами, якщо використовувати щось на зразок RequireJS або іншого завантажувача в стилі CommonJS. А модуль BA можна замінити трьома модулями, по одному для кожного браузера, залишивши свій продукт більш дискретним.

Ресурси для скачування

Схожі теми

  • Оригінал статті: Create your own browser extensions, Part 4: Move toward browser-agnostic extensions .
  • Розширення можливостей Chrome: Створення своїх власних розширень для браузера Chrome (Дуейн О'Брайен, developerWorks, серпень 2012 року): перша частина циклу статей про розширення для браузерів присвячена створенню розширення для Google Chrome - від початку і до розміщення в App Store.
  • Розширення можливостей Firefox: Створення своїх власних розширень для браузера Firefox (Дуейн О'Брайен, developerWorks, березень 2013): друга частина цього циклу статей про розширення для браузерів вчить створювати додатки для Firefox - від початку і до дистрибутива.
  • Розширення можливостей Safari: Створення своїх власних розширень для браузера Safari (Дуейн О'Брайен, developerWorks, квітень 2013): третя частина циклу статей про розширення для браузерів присвячена створенню, тестування і поширенню розширень для Safari.
  • Розширення для Google Chrome : Докладніше про розширення для Chrome.
  • Chrome Web Store : Розширення і теми в інтернет-магазині Chrome.
  • Chrome Developer Tools: Heap Profiling : Це керівництво вчить користуватися інструментом Heap Profiler для виявлення витоків пам'яті в додатках.
  • Firefox Add-ons : На цьому сайті доступні тисячі доповнень.
  • Mozilla Add-on Developer Hub : Інформаційний центр по Add-on Builder і Add-on SDK.
  • Mozilla Development Network : Відвідайте центр розробників Mozilla і знайдіть керівництво по створенню розширень .
  • Mozilla Add-on SDK Developer Guide : в розділі Communicating using port керівництва пояснюється, як сценарії add-on scripts і content scripts повідомляються один з одним за допомогою об'єкта port.
  • Mozilla Firefox : Завантажте версію Firefox для своєї платформи.
  • Firefox Add-on SDK : Завантажте SDK.
  • Firefox Add-on Builder : Тут знаходиться Add-on Builder.
  • Gawkblocker : Завантажте Gawkblocker для Firefox з профілю Add-on Builder автора.
  • Safari Extensions Gallery : На цьому сайті можна знайти безліч розширень для Safari.
  • Safari Extensions Reference : Документація по розширень для Safari.
  • Safari Dev Center : Центр базування Safari-розробників.
  • Apple Developer Program : Зареєструйтеся безкоштовно і приступайте до створення власних розширень для Safari.
  • Chrome Developer Tools : Використовуйте версію Google Chrome з Developer Channel, щоб отримати новітні інструменти розробки.
  • Kango Framework : Познайомтеся з цим середовищем для створення JavaScript-розширень.
  • Crossrider : Створюйте крос-браузерні розширення за допомогою цієї платформи.

Підпишіть мене на повідомлення до коментарів

Com/search/csass/search/?
Чи не вирішив чи вже це завдання будь-хто інший?
Чи не виявиться таке розширення невиправдано складним?
Якою мірою розширення можна зробити не залежних від браузера?
Чи не виявиться таке розширення невиправдано складним?


Новости
  • Виртуальный хостинг

    Виртуальный хостинг. Возможности сервера распределяются в равной мере между всеми... 
    Читать полностью

  • Редизайн сайта

    Редизайн сайта – это полное либо частичное обновление дизайна существующего сайта.... 
    Читать полностью

  • Консалтинг, услуги контент-менеджера

    Сопровождение любых интернет ресурсов;- Знание HTML и CSS- Поиск и обновление контента;-... 
    Читать полностью

  • Трафик из соцсетей

    Сравнительно дешевый способ по сравнению с поисковым и контекстным видами раскрутки... 
    Читать полностью

  • Поисковая оптимизация

    Поисковая оптимизация (англ. search engine optimization, SEO) — поднятие позиций сайта в результатах... 
    Читать полностью