Tabbed browser

您將在此尋獲協助您運用Firefox的多頁籤瀏覽器的一些程式碼片段,註解將提示您應該在什麼地方插入您自己的程式碼。

 

一般來說這些片段會包含其初始化的程式碼。基本假設是這些程式碼片段將於瀏覽器視窗內容中執行,最佳做法是以 load listener 來運行。若您要從瀏覽器視窗以外的地方來操作頁籤,您必須先取得任意一個瀏覽器視窗,詳見: Working with windows in chrome code

多世界詮釋算什麼,多 browser 詮釋才夠暈

"browser" 有很多意思。把整個 Firefox 稱為 browser 再為理所當然不過,但是 Firefox 有頁籤,每個頁籤本身也都是 browser。無論是一般所指的 web page browser,還是 XUL 裡的 browser element,通通叫做 browser。在本文件和其它某些 Firefox 文件裡, browser 還有另一個意思,就是 Firefox XUL window 裡的 tabbrowser element。

如何存取 browser

從主視窗

從 global ChromeWindow 中運行的程式碼,例如套疊 (overlay) 於 browser.xul 的 extension,可以用全域變數 gBrowser 來存取 tabbrowser element

// gBrowser 只存在於 browser window (browser.xul)
gBrowser.addTab(...);

如果 gBrowser 未定義,代表您的程式碼要不是太早運行,就是並非從 browser window 中運行。在 browser window 還沒完全載入之前 gBrowser 根本還不存在。若要在視窗一打開時存取 gBrowser ,請在 event listener 中使用它,並 listen 'load' 事件。

若您的程式是 sidebar 或 dialog 而無法存取主視窗,那就要先獲取主視窗才能使用 gBrowser ,詳見: Working with windows in chrome code.

從腮巴 (sidebar) 存取 browser

若您的附加元件基本上是一坨屎 (sidebar) ,那就:

var mainWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                       .getInterface(Components.interfaces.nsIWebNavigation)
                       .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                       .rootTreeItem
                       .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                       .getInterface(Components.interfaces.nsIDOMWindow);

mainWindow.gBrowser.addTab(...);

從呆鴨肉 (dialog) 存取 browser

若您的程式基本上運行於某種呆鴨肉 (dialog) ,而且是從 browser window 直接甩出來的精選優質肥美呆鴨肉,那就:

window.opener.gBrowser.addTab(...);

如果 window.opener 搞不定,那就用以下的程式碼先取得最近打開的 browser window :

var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                   .getService(Components.interfaces.nsIWindowMediator); 

var mainWindow = wm.getMostRecentWindow("navigator:browser"); 
mainWindow.gBrowser.addTab(...);

如何以 URL 開啟新 tab

// Add tab  
gBrowser.addTab("http://www.google.com/");

// Add tab, then make active
gBrowser.selectedTab = gBrowser.addTab("http://www.google.com/");

操作新頁籤的內容

若要操作新開啟頁籤的內容,就要等它載入完。

// 醬子不行啊 .. 人家還沒準備好  
var newTabBrowser = gBrowser.getBrowserForTab(gBrowser.addTab("http://www.google.com/"));
alert(newTabBrowser.contentDocument.body.innerHTML);

// BETTER WAY
var newTabBrowser = gBrowser.getBrowserForTab(gBrowser.addTab("http://www.google.com/"));
newTabBrowser.addEventListener("load", function () {
  newTabBrowser.contentDocument.body.innerHTML = "<div>hello world</div>";
}, true);

onLoad handler 中的 event target 是個 'tab' XUL element。 關於 getBrowserForTab(),參見: tabbrowser

如何精準地在 window 或 tab 開啟 URL

chrome://browser/content/utilityOverlay.js 中有許多方法例如 openUILinkInopenUILink, 讓 tab 開啟 URL 變簡單。

openUILinkIn( url, where, allowThirdPartyFixup, postData, referrerUrl )
where 可以是:
  • "current" 目前 tab (要是 browser window 連個鬼都沒的話就開新視窗)
  • "tab" 新的 tab (一樣,鬼都沒有就開新視窗)
  • "tabshifted" 也是新 tab,但若預設選定新 tab 則會在背景開啟,若預設相反則行為相反 (這就是為什麼叫 shifted)
  • "window" 新視窗
  • "save" 直接存檔 (不會提示檔名喔!)
openUILink( url, e, ignoreButton, ignoreAlt, allowKeywordFixup, postData, referrerUrl )
 

下列程式碼將開啟 URL 並依照什麼鬼滑鼠鍵和什麼鬼熱鍵(例:Ctrl)被按下來開新視窗、開新 tab 或是開在現有的 tab 。這是用於 menuitem 的程式碼,而在其它的 XUL elements 上也沒什麼兩樣,但只限 browser.xul 的 overlay 。

XUL:

<menuitem oncommand="myExtension.foo(event)" onclick="checkForMiddleClick(this, event)" label="Click me"/>

JS:

var myExtension = {
  foo: function(event) {
    openUILink("http://www.example.com", event, false, true);
  }
}

如何優柔寡斷地開啟 URL

var gSessionStore = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);

// 生一個新 tab 但是沒載入內容
var url = "https://developer.mozilla.org";
var tab = gBrowser.addTab(null);
gSessionStore.setTabState(tab,
  '{\
    "entries":[\
      {\
        "url":"' + url + '",\
        "title":"' + url + '"\
      }\
    ],\
    "lastAccessed":0,\
    "index":1,\
    "hidden":false,\
    "attributes":{},\
    "image":null\
  }'
);

tab 再利用

與其每次需要就再開新的 tab,若已有 tab 開啟了欲前往的 URL,最佳做法就是試著重用它。遵循這個做法可使您的附加元件開啟最少的 tab。

依 URL/URI 來重用

附加元件常見的一項功能是,當 user 按下某個按鈕或連結就從browser window 中將 user 導向至某個 chrome:// 開頭的 URI (像是 help 或 about 這種) 或者一個外部的 HTML (on-line http(s)://) 。接下來的程式碼展示如何重用已顯示所欲前往 URL/URI 的頁籤,如果沒有現存的,則會開個新的。

function openAndReuseOneTabPerURL(url) {
  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                     .getService(Components.interfaces.nsIWindowMediator);
  var browserEnumerator = wm.getEnumerator("navigator:browser");

  // Check each browser instance for our URL
  var found = false;
  while (!found && browserEnumerator.hasMoreElements()) {
    var browserWin = browserEnumerator.getNext();
    var tabbrowser = browserWin.gBrowser;

    // Check each tab of this browser instance
    var numTabs = tabbrowser.browsers.length;
    for (var index = 0; index < numTabs; index++) {
      var currentBrowser = tabbrowser.getBrowserAtIndex(index);
      if (url == currentBrowser.currentURI.spec) {

        // The URL is already opened. Select this tab.
        tabbrowser.selectedTab = tabbrowser.tabContainer.childNodes[index];

        // Focus *this* browser-window
        browserWin.focus();

        found = true;
        break;
      }
    }
  }

  // Our URL isn't open. Open it now.
  if (!found) {
    var recentWindow = wm.getMostRecentWindow("navigator:browser");
    if (recentWindow) {
      // Use an existing browser window
      recentWindow.delayedOpenTab(url, null, null, null, null);
    }
    else {
      // No browser windows are open, so open a new one.
      window.open(url);
    }
  }
}
依其它準則來重用

有時候您可能想重用某個先前已由您的附加元件開啟的頁籤,管它目前 URL/URI 是什麼,只要不是其他的元件開啟的。您的確可以為所欲為,只要在第一次開啟時給它一個自訂屬性。之後要重用它的時候,就從所有已開啟的頁籤找出這個有自訂屬性的頁籤,變更其 URL/URI 並 focus 或 select 該頁籤。要是連個屁都沒有 (可能被 user 關了或是根本沒開過),那就開個新的然後給它這個自訂屬性。

function openAndReuseOneTabPerAttribute(attrName, url) {
  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                     .getService(Components.interfaces.nsIWindowMediator);
  for (var found = false, index = 0, tabbrowser = wm.getEnumerator('navigator:browser').getNext().gBrowser;
       index < tabbrowser.tabContainer.childNodes.length && !found;
       index++) {

    // Get the next tab
    var currentTab = tabbrowser.tabContainer.childNodes[index];
  
    // Does this tab contain our custom attribute?
    if (currentTab.hasAttribute(attrName)) {

      // Yes--select and focus it.
      tabbrowser.selectedTab = currentTab;

      // Focus *this* browser window in case another one is currently focused
      tabbrowser.ownerDocument.defaultView.focus();
      found = true;
    }
  }

  if (!found) {
    // Our tab isn't open. Open it now.
    var browserEnumerator = wm.getEnumerator("navigator:browser");
    var tabbrowser = browserEnumerator.getNext().gBrowser;
  
    // Create tab
    var newTab = tabbrowser.addTab(url);
    newTab.setAttribute(attrName, "xyz");
  
    // Focus tab
    tabbrowser.selectedTab = newTab;
    
    // Focus *this* browser window in case another one is currently focused
    tabbrowser.ownerDocument.defaultView.focus();
  }
}

該函式應如此這般調用:

openAndReuseOneTabPerAttribute("myextension-myattribute", "http://developer.mozilla.org/").

如何砍掉 tab

下列程式碼將砍掉目前選擇的 tab。

gBrowser.removeCurrentTab();

另有一個更廣用的 removeTab 方法,以 XUL tab element 為唯一的參數。

如何改變 active tab

左三圈 .. ok,並沒有三圈 .. 選定向左一個 tab:

gBrowser.tabContainer.advanceSelectedTab(-1, true);

右三圈 (喔 .. 抱歉)

gBrowser.tabContainer.advanceSelectedTab(1, true);

如何偵測 page load

另見: Code snippets:On page load

function examplePageLoad(event) {
  if (event.originalTarget instanceof HTMLDocument) {
    var win = event.originalTarget.defaultView;
    if (win.frameElement) {
      // Frame within a tab was loaded. win should be the top window of
      // the frameset. If you don't want do anything when frames/iframes
      // are loaded in this web page, uncomment the following line:
      // return;
      // Find the root document:
      win = win.top;
    }
  }
}

// do not try to add a callback until the browser window has
// been initialised. We add a callback to the tabbed browser
// when the browser's window gets loaded.
window.addEventListener("load", function () {
  // Add a callback to be run every time a document loads.
  // note that this includes frames/iframes within the document
  gBrowser.addEventListener("load", examplePageLoad, true);
}, false);

...
// When no longer needed
gBrowser.removeEventListener("load", examplePageLoad, true);
...

tab 增減通知

function exampleTabAdded(event) {
  var browser = gBrowser.getBrowserForTab(event.target);
  // browser is the XUL element of the browser that's been added
}

function exampleTabMoved(event) {
  var browser = gBrowser.getBrowserForTab(event.target);
  // browser is the XUL element of the browser that's been moved
}

function exampleTabRemoved(event) {
  var browser = gBrowser.getBrowserForTab(event.target);
  // browser is the XUL element of the browser that's been removed
}

// During initialization
var container = gBrowser.tabContainer;
container.addEventListener("TabOpen", exampleTabAdded, false);
container.addEventListener("TabMove", exampleTabMoved, false);
container.addEventListener("TabClose", exampleTabRemoved, false);

// When no longer needed
container.removeEventListener("TabOpen", exampleTabAdded, false);
container.removeEventListener("TabMove", exampleTabMoved, false);
container.removeEventListener("TabClose", exampleTabRemoved, false);

Gecko 1.9.1 備註
(Firefox 3.5 / Thunderbird 3.0 / SeaMonkey 2.0)

Starting in Gecko 1.9.1 (Firefox 3.5 / Thunderbird 3.0 / SeaMonkey 2.0), there's an easy way to listen on progress events on all tabs.

(Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1)

Notification when a tab's attributes change

自 Gecko 2.0 起,您可以 listen TabAttrModified event 來得知 tab 之 attribute 異動。event listener 可能收到 tab 異動的 attribute 如下:

function exampleTabAttrModified(event) {
  var tab = event.target;
  // Now you can check what's changed on the tab
}

// During initialization
var container = gBrowser.tabContainer;
container.addEventListener("TabAttrModified", exampleTabAttrModified, false);

// When no longer needed
container.removeEventListener("TabAttrModified", exampleTabAttrModified, false);

(Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1)

Notification when a tab is pinned or unpinned

自 Gecko 2.0 起,tab 可以被 "釘選";也就是說這類 tab 變成一種特殊的 application tabs ("app tabs"),可以被釘在 tab 列的開頭而且只顯示為 favicon。透過 TabPinnedTabUnpinned 事件可以得知 tab 被釘了或被拔了。

function exampleTabPinned(event) {
  var browser = gBrowser.getBrowserForTab(event.target);
  // browser is the XUL element of the browser that's been pinned
}

function exampleTabUnpinned(event) {
  var browser = gBrowser.getBrowserForTab(event.target);
  // browser is the XUL element of the browser that's been pinned
}

// Initialization

var container = gBrowser.tabContainer;
container.addEventListener("TabPinned", exampleTabPinned, false);
container.addEventListener("TabUnpinned", exampleTabUnpinned, false);

// When no longer needed

container.removeEventListener("TabPinned", exampleTabPinned, false);
container.removeEventListener("TabUnpinned", exampleTabUnpinned, false);

如何偵測 tab 選定

下列程式碼用來得知 browser 中的 tab 被選定:

function exampleTabSelected(event) {
  var browser = gBrowser.selectedBrowser;
  // browser is the XUL element of the browser that's just been selected
}

// During initialisation
var container = gBrowser.tabContainer;
container.addEventListener("TabSelect", exampleTabSelected, false);

// When no longer needed
container.removeEventListener("TabSelect", exampleTabSelected, false);

如何取得已選定 tab 裡的內文(document)

下列程式碼用來獲取選定 tab 中的內文(document),於 browser window scope 中運作,例如從 browser window 的 overlay 。

gBrowser.contentDocument;

content.document

若程式碼是從由 browser window 所開啟的 window 或 dialog,可用以下程式碼獲取已選定 tab 裡所顯示的內文:

window.opener.content.document

若程式從並非自 browser window 所開啟的 window 或 dialog 中運行,則可用 nsIWindowMediator 獲取最近所使用的 browser window 中已選定 tab 裡的內文。

var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                   .getService(Components.interfaces.nsIWindowMediator);
var recentWindow = wm.getMostRecentWindow("navigator:browser");
return recentWindow ? recentWindow.content.document.location : null;

另見: Working with windows in chrome code.

如何遍歷 browser

要遍歷 tabbrowser 中所有的 browser,要先取得任意一個 browser 的 window。如果程式碼是從 browser.xul overlay 執行 (譬如 toolbar button 或者 menu click handler),可以用預定義的 window 變數。但若是從獨自的 window 執行 (例如 settings/options dialog) 則要用 nsIWindowMediator 來取得任意一個 browser 的window。

有了一個 window 後再用 win.gBrowser 取得其 <tabbrowser/> element,其中 win 就是上個步驟中所得到的 browser window 。 如果程式是從 browser.xul overlay 執行的話,可以不必寫 window.gBrowser 直接用 gBrowser 就好。很奇怪沒錯,在所有的 IDE 當中也沒有 gBrowser 這個東西,但是別忘了您是在開發 Firefox 的 extension , 這是它本身的 object model 。

最後一步,利用 gBrowser.browsers.length 得知 browser 的數量,並用 gBrowser.getBrowserAtIndex() 取得 <browser/> element。範例:

var num = gBrowser.browsers.length;
for (var i = 0; i < num; i++) {
  var b = gBrowser.getBrowserAtIndex(i);
  try {
    dump(b.currentURI.spec); // dump URLs of all open tabs to console
  } catch(e) {
    Components.utils.reportError(e);
  }
}

透過 DOM Inspector 或參見 browser.xml 得知對應的 XBL bindings ( 不然參見 browsertabbrowser 也行),您將可了解 <browser/><tabbrowser/> elements 有哪些方法可用。

如何取得觸發 http-on-modify-request notification 的 browser

關於http-on-* notification 請參見 Observer notifications

請注意,部份 HTTP requests 與 tab 無關;例如 RSS feed updates、extension manager requests 以及 XPCOM components 所發出的 XHR requests 等。在這些情況中,下列程式碼將返回 null 。

注意:該程式碼使用之 getInterface(Components.interfaces.nsIDOMWindow) 應更新為 nsILoadContext參見範例
observe: function (subject, topic, data) {
  if (topic == "http-on-modify-request") {
    subject.QueryInterface(Components.interfaces.nsIHttpChannel);
    var url = subject.URI.spec; /* url being requested. you might want this for something else */
    var browser = this.getBrowserFromChannel(subject);
    if (browser != null) {
      /* do something */
    }
  }
},

getBrowserFromChannel: function (aChannel) {
  try {
    var notificationCallbacks = 
      aChannel.notificationCallbacks ? aChannel.notificationCallbacks : aChannel.loadGroup.notificationCallbacks;

    if (!notificationCallbacks)
      return null;

    var domWin = notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow);
    return gBrowser.getBrowserForDocument(domWin.top.document);
  }
  catch (e) {
    dump(e + "\n");
    return null;
  }
}

文件標籤與貢獻者

 此頁面的貢獻者: b9136057
 最近更新: b9136057,