Как стать автором
Обновить

Как сделать FF удобней — пишем свои кнопки

Время на прочтение14 мин
Количество просмотров7.1K

Преамбула


Я перешел на FF с IE очень давно — мне нужен был браузер с удобными средствами разработки и отладки, позволяющий полностью настраиваться «под себя» так как это был мой основной рабочий инструмент — инструмент должен быть удобным.

И FF полностью справился со своими обязанностями — возможно, я кому-то покажусь странным — но я потратил около 2-х месяцев на чтение сайта с дополнениями и нахождения для меня лучшего их набора, настройки и т.д.
В результате я получил мощную хорошо настроенную и удобную программу для своих нужд.

image

Не смотря на то, что я знаю JS, мне ни разу не пришло в голову писать какие-либо расширения под ff по одной простой причине — чтобы я не захотел — это уже было в депозитории расширений. Так продолжалось до вчера :)


Вчера мне подумалось вот о чем — во время работы мне все время нужно переключаться и заходить на localhost, где крутится мой апач с текущим проектом, и на адрес где текущий релиз проекты выложен в интернете — вполне стандартная ситуация. Вариантов решения масса — можно ( и как сделано у меня ) повесить это дело на жесты мыши, но проблема в том что я одновременно веду не один проект а несколько десятков.

Можно использовать избранное… но как-то много у меня там всякого не относящегося к работе… папки там — тоже вариант — но вот не нравится мне оно как-то.

Поиски...


И я стал искать как можно это все сделать красиво и удобно.
Для начала перерыв сайт с расширениями ничего конкретно подходящего я не обнаружил и был в несколько шоковом состоянии x_X, но по скольку борцы на просторах всемирной паутины никогда не унывают, несколько удивившись я понял что момент создания своего расширения под ff наконец-то настал, но как всегда подвел двигатель прогресса — а именно лень.

Было абсолютно не охота разбираться с тем как его упаковывать как устанавливать и писать локализации к нему — однако выход нашелся быстро — есть такое замечательное расширение, которое наверняка стоит у многих программеров Custom Buttons, есть еще более навороченный вариант Custom Buttons 2 — кому что нравится, я же выбрал первый, так как руководствуюсь принципом если несколько сущностей одинаково удовлетворяют ваши потребности — используйте простейшую — ну да это я — а Вы можете выбрать исходя из своих принципов :)

Далее все просто — данные расширения — это некоторая обвязка, позволяющая создавать свои собственные кнопки на тулбаре и реализовывать в них собственный функционал. Он пишется на JS, расширенном внутренними объектами FF.

Т.е. особо пытливым хабралюдям все-таки придется лезть и читать документацию :)

Алгоритм добавления кнопки прост: правый клик по тулбару -> добавить кнопку, позднее после заполнения всех нужных полей и сохранения кнопки надо будет еще вытащить ее в нужное место на нужный тулбар, используя правый клик по тулбару -> настроить, а дальше найдя свою кнопку перетащить ее в нужное место.

image

Что касается полей:
Имя — имя вашей кнопки
Картинка — путь до картинки, которая и будет кнопкой ( вы можете указав путь нажать base64 — тогда за место пути до картинки у вас образуется dataURL, и картинка будет хранится в самой кнопке )

Далее вкладки
Код — тот код, что выполняется по нажатию
Инициализация — код, выполняемый один раз при загрузке браузера

Соотвественно вся мощь FF у вас в руках, делать можно все что хочешь :)
Например для того, чтобы сделать отдельную кнопку для вызова избранного достаточно добавить вот такое:
Код:
if ( !this.lastChild ) {
  var mc = document.getElementById("bookmarksMenuPopup");
  var mcc = mc.cloneNode( true );
  this.appendChild( mcc );
}

var bo = document.getBoxObjectFor( this );
this.lastChild.showPopup ( this, -1, -1, "popup", "bottomleft", "topleft" );


* This source code was highlighted with Source Code Highlighter.


Инициализация:
пусто
Здесь я обошелся без отдельного кода инициализации, засунув его и проверку в основной код.
Естественно можно разделить это как должно быть правильно — переписав все содержимое первого if в код инициализации и убрав if из кода, но зато так кароче :)

Собственно щелкнув по правой клавишей по нашей кнопке мы получим меню Custom Buttons с помощью которого можно редактировать код кнопки, копировать код кнопки — вобщем работать и изменять наше маленькое расширение, левый же клик откроет нам избранное, выполнив наш код.

image

Копирование кода кнопки позволит создать эту кнопку в любом другом FF, причем там сохранятся все поля вашей кнопки ( и естественно картинки, если они были dataURL ).

Вот оно!


Возвращаясь к своей персональной задачке и пока я писал для нее код, у меня родился еще небольшой функционал:
  1. возможность открыть определенный URL
  2. хотелось иметь возможность быстро запускать нужные приложения ( например хранитель паролей )
  3. удобно было бы иметь возможность запускать некий стандартный код JS на любой странице ( конечно можно поглядеть в FireBug-е, но всеже иногда удобней кликнуть два раза чем бегать по его закладкам )
  4. ну и некое подобие roboform, только с более простым функционалом — а именно — открыть нужный URL и заполнить определнные поля определнными значениями

п.3 — использую например чтобы поглядеть список iframe-ов или timer-ов на странице и много еще для чего. Очень часто мне такое бывает нужно.

п.4 — Например при отладке страниц с формами — можно конечно заполнить форму сохранить и юзать — но как-то не срослась у меня дружба с форм-филлерами для FF :) кроме того — закончил проект — все просто — удалил запись и мусора нет — все поля хранятся в самой записи. Можно тоже поспорить о целесообразности, но опять-же для меня это было бы удобно.

Почитав доки и подглядев в установленных расширения куски кода родилась вот такая штука ( над оптимизацией кода не работал — ибо не до того было — с удовольствием приму замечания :) ):
Код:
var bo = document.getBoxObjectFor( this );
this.lastChild.showPopup ( this, -1, -1, "popup", "bottomleft", "topleft" );


Инициализация:
var mitems = [
    {
      "label"    : "localhost",
      "tip"    : "localhost",
      "image"    : "",
      "type"    : "url",
      "val"    : "localhost"
    },

    "separator",
    {
      "label"    : "Google",
      "tip"    : "Google",
      "image"    : "",
      "type"    : "url",
      "val"    : "http://www.google.ru"
    },
    "separator",

    {
      "label"    : "Notepad",
      "tip"    : "Notepad",
      "image"    : "",
      "type"    : "exec",
      "val"    : "c:/windows/notepad.exe"
    },

    "separator",

    {
      "label"    : "other",
      "tip"    : "other",
      "image"    : "",
      "type"    : "submenu",
      "val"    :
          [
                    {
                      "label"    : "JS test",
                      "tip"    : "JS test",
                      "image"    : "",
                      "type"    : "js",
                      "val"    : "alert( 'js testing...' );"
                    },

                    {
                      "label"    : "Выход из FF",
                      "tip"    : "Выход из FF",
                      "image"    : "",
                      "type"    : "js",
                      "val"    : "goQuitApplication();"
                    }
          ]
    }
];

function createMenu( label, tip, image ) {
      var m = document.createElement( "menu" );
      m.setAttribute( "label", label );
      if ( image ) {
        m.setAttribute( "class", "menu-iconic" );
        m.setAttribute( "image", image );
      }
      if ( tip ) m.tooltipText = tip;

  return m;
}

function createMenuItem( label, tip, image ) {
      var mi = document.createElement( "menuitem" );
      mi.setAttribute( "label", label );
      if ( image ) {
        mi.setAttribute( "class", "menuitem-iconic" ); // "menuitem-iconic bookmark-item"
        mi.setAttribute( "image", image );
      }
      if ( tip ) mi.tooltipText = tip;

  return mi;
}

function executeFile( progPath, arg ) {
  progPath = progPath.replace( /\//gi, "\\" );
  try {
    var argArray = arg ? arg.split(/\s+/) : [];
    var nsILocalFile = Components.classes["@mozilla.org/file/local;1"].getService(Components.interfaces.nsILocalFile);
    var nsIProcess = Components.classes["@mozilla.org/process/util;1"].getService(Components.interfaces.nsIProcess);
    nsILocalFile.initWithPath(progPath);
    nsIProcess.init(nsILocalFile);
    nsIProcess.run(false, argArray, argArray.length);
//    nsIProcess.close();
    return nsILocalFile;
  }
  catch( ex ) {
    alert( ex.toString() );
    return null;
  }
}

function buildMenu( items ) {
  var menu, i, t, mi, item;
  var self = this;

  menu = document.createElement( "menupopup" );
  menu.type = "menu";
  menu.orient = "horizontal";
//  menu.id = "myCustomMenu";

  for ( i in items ) {
    item = items[i];
    if ( typeof( item ) == "string" ) {
      switch( item ) {
        case "separator":
          mi = document.createElement( "menuseparator" );
        break;

        default:
          mi = null;
        break;
      }
    }
    else {
      switch( item.type ) {
        case "url":
          mi = createMenuItem( item.label, item.tip, item.image );
          mi.onclick = function() {
              var b = getBrowser();
              b.selectedTab = b.addTab( this.onclick.addr );
              b.selectedTab.onload = function() {}
          };
          mi.onclick.addr = item.val;
        break;

        case "urlForm":
          mi = createMenuItem( item.label, item.tip, item.image );
          mi.onclick = function() {
              var item = this.onclick.item;
              var b = getBrowser();
              var tab = gBrowser.addTab( item.val.addr );
              var newTabBrowser = b.getBrowserForTab( tab );
              b.selectedTab = tab;
                  var lf = function( event ) {
                    var fields = item.val.fields;
                    newTabBrowser.removeEventListener( 'load', lf, true );
                    setTimeout(
                      function() {
                        var doc = newTabBrowser.contentDocument;
                        var inputs = doc.getElementsByTagName( "input" );
                        for ( var i in fields ) {
                          for ( var j = 0; j < inputs.length; j++ ) if ( inputs[j].name == i ) inputs[j].value = fields[i];
                        }
                      },
                      100
                    );
                  };
              newTabBrowser.addEventListener( "load", lf, true );
          };
          mi.onclick.item = item;
        break;

        case "js":
          mi = createMenuItem( item.label, item.tip, item.image );
          mi.onclick = new Function( item.val );
        break;

        case "submenu":
          mi = createMenu( item.label, item.tip, item.image );
              mi.appendChild( buildMenu( item.val ) );
        break;

        case "exec":
          mi = createMenuItem( item.label, item.tip, item.image );
          mi.onclick = new Function( "this.onclick.executeFile('" + item.val + "')" );
          mi.onclick.executeFile = executeFile;
        break;          

        default:
          mi = null;
        break;
      }
    }

    if ( mi ) menu.appendChild( mi );
  }

  return menu;
}

this.appendChild( buildMenu( mitems, false ) );


* This source code was highlighted with Source Code Highlighter.



mitems собственно массив наших пунктов меню и самих меню
задатеся JS объектами, поля:
  • label — название пункта меню
  • tip — всплывающая подсказка на пункте ( может быть пустое )
  • image — либо адрес либо dataURL для картинки ( может быть пустое )
  • type — тип пункта:
    • url — открыть страничку, в поле val — адрес странички
    • urlForm — открыть страничку и заполнить нужные поля нужными значениями, поле val:
      {
        "addr": "адрес страницы",
        "fields": {
            "поле1": "значение поля1",
            "поле2": "значение поля2",      
            и т.д.
        }
      }


      * This source code was highlighted with Source Code Highlighter.
    • js — запустить JS код, в поле val — JS код
    • exec — запустить приложение, в поле val — полный путь до приложение и его имя — типа «c:/a/b/c.exe», слэши используются обратные
    • submenu — создать подменю, в поле val — массив из наших пунктов-объектов

  • val — зависит от type


image

Ну вот собственно наше расширение и готово, не буду говорить за всех — а мне стало удобней.
И, я надеюсь, пусть на чуток я увеличу количество пользователей такого замечательного браузера ( а может быть чего доброго — и количество разработчиков расширений под него :) )

Очень надеюсь, что моя статья кому-нибудь окажется полезной…

P.S. Полный код кнопки: тут
Теги:
Хабы:
Всего голосов 133: ↑125 и ↓8+117
Комментарии74

Публикации

Истории

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань