--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE bindings [
+<!ENTITY % tabBrowserDTD SYSTEM "chrome://global/locale/tabbrowser.dtd" >
+%tabBrowserDTD;
+<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+%globalDTD;
+]>
+
+<bindings id="vertical-tabbar-bindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+
+
+ <binding id="tabbrowser">
+ <content>
+ <xul:stringbundle anonid="tbstringbundle" src="chrome://global/locale/tabbrowser.properties"/>
+ <xul:tabbox anonid="tabbox" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
+ onselect="if (!('updateCurrentBrowser' in this.parentNode) || event.target.localName != 'tabpanels') return; this.parentNode.updateCurrentBrowser();">
+ <xul:vbox class="tabbrowser-strip" collapsed="true" tooltip="_child" context="_child"
+ anonid="strip" width="102" orient="vertical"
+ ondraggesture="nsDragAndDrop.startDrag(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondragover="nsDragAndDrop.dragOver(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondragdrop="nsDragAndDrop.drop(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondragexit="nsDragAndDrop.dragExit(event, this.parentNode.parentNode); event.stopPropagation();">
+ <xul:tooltip onpopupshowing="return this.parentNode.parentNode.parentNode.createTooltip(event);"/>
+ <xul:menupopup anonid="tabContextMenu" onpopupshowing="this.parentNode.parentNode.parentNode.updatePopupMenu(this);">
+ <xul:menuitem label="&newTab.label;" accesskey="&newTab.accesskey;"
+ xbl:inherits="oncommand=onnewtab"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.reloadTab(tabbrowser.mContextTab);"/>
+ <xul:menuitem label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
+ tbattr="tabbrowser-multiple"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.reloadAllTabs(tabbrowser.mContextTab);"/>
+ <xul:menuitem label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
+ tbattr="tabbrowser-multiple"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.removeAllTabsBut(tabbrowser.mContextTab);"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&closeTab.label;" accesskey="&closeTab.accesskey;"
+ tbattr="tabbrowser-multiple"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.removeTab(tabbrowser.mContextTab);"/>
+ </xul:menupopup>
+
+ <xul:tabs class="tabbrowser-tabs" flex="1"
+ anonid="tabcontainer"
+ setfocus="false"
+ onclick="this.parentNode.parentNode.parentNode.onTabClick(event);"
+ xbl:inherits="onnewtab"
+ ondblclick="this.parentNode.parentNode.parentNode.onTabBarDblClick(event);"
+ onclosetab="var node = this.parentNode;
+ while (node.localName != 'tabbrowser')
+ node = node.parentNode;
+ node.removeCurrentTab();">
+ <xul:tab selected="true" validate="never"
+ onerror="this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image'));
+ this.removeAttribute('image');"
+ flex="100"
+ class="tabbrowser-tab" label="&untitledTab;" crop="end"/>
+ </xul:tabs>
+ </xul:vbox>
+ <xul:splitter class="tabbrowser-splitter" collapse="before" resizeafter="closest" resizebefore="closest" ondblclick="this.previousSibling.width=102">
+ <xul:grippy />
+ </xul:splitter>
+ <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
+ <xul:notificationbox flex="1">
+ <xul:browser flex="1" type="content-primary" message="true" disablehistory="true"
+ xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
+ </xul:notificationbox>
+ </xul:tabpanels>
+ </xul:tabbox>
+ <children/>
+ </content>
+ <implementation>
+ <field name="mTabstrip">
+ document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
+ </field>
+ <field name="mPrefs" readonly="true">
+ Components.classes['@mozilla.org/preferences-service;1']
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch(null);
+ </field>
+ <field name="mURIFixup" readonly="true">
+ Components.classes["@mozilla.org/docshell/urifixup;1"]
+ .getService(Components.interfaces.nsIURIFixup);
+ </field>
+ <field name="mTabBox" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
+ </field>
+ <field name="mTabDropIndicatorBar">
+ this.mTabBox.childNodes[0]
+ </field>
+ <field name="mStrip" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "strip");
+ </field>
+ <field name="mTabContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
+ </field>
+ <field name="mPanelContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
+ </field>
+ <field name="mTabs" readonly="true">
+ this.mTabContainer.childNodes
+ </field>
+ <field name="mStringBundle">
+ document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
+ </field>
+ <field name="mCurrentTab">
+ null
+ </field>
+ <field name="mCurrentBrowser">
+ null
+ </field>
+ <field name="mProgressListeners">
+ []
+ </field>
+ <field name="mTabListeners">
+ new Array()
+ </field>
+ <field name="mTabFilters">
+ new Array()
+ </field>
+ <field name="mTabbedMode">
+ false
+ </field>
+ <field name="mIsBusy">
+ false
+ </field>
+ <field name="mMissedIconCache">
+ null
+ </field>
+ <field name="mContextTab">
+ null
+ </field>
+ <field name="mModalDialogShowing">
+ false
+ </field>
+ <field name="arrowKeysShouldWrap" readonly="true">
+ false
+ </field>
+ <field name="mAddProgressListenerWasCalled">
+ false
+ </field>
+ <field name="_browsers">
+ null
+ </field>
+
+ <field name="_blockDblClick">
+ false
+ </field>
+
+ <method name="getBrowserAtIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.mTabContainer.childNodes[aIndex].linkedBrowser;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserIndexForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ for (var i = 0; i < this.mPanelContainer.childNodes.length; i++) {
+ if (this.getBrowserAtIndex(i).contentDocument == aDocument) {
+ return i;
+ }
+ }
+ return -1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var index = this.getBrowserIndexForDocument(aDocument);
+ if (index < 0)
+ return null;
+ return this.getBrowserAtIndex(index);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getNotificationBox">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ if (aBrowser)
+ return aBrowser.parentNode;
+ else if (this.mCurrentBrowser)
+ return this.mCurrentBrowser.parentNode;
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <!-- A web progress listener object definition for a given tab. -->
+ <method name="mTabProgressListener">
+ <parameter name="aTab"/>
+ <parameter name="aBrowser"/>
+ <parameter name="aStartsBlank"/>
+ <body>
+ <![CDATA[
+ return ({
+ mTabBrowser: this,
+ mTab: aTab,
+ mBrowser: aBrowser,
+ mBlank: aStartsBlank,
+ mLastURI: null,
+
+ // cache flags for correct status bar update after tab switching
+ mStateFlags: 0,
+ mStatus: 0,
+ mMessage: "",
+ mTotalProgress: 0,
+
+ // count of open requests (should always be 0 or 1)
+ mRequestCount: 0,
+
+ onProgressChange : function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress)
+ {
+ if (!this.mBlank && this.mTabBrowser.mCurrentTab == this.mTab) {
+ for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
+ var p = this.mTabBrowser.mProgressListeners[i];
+ if (p)
+ p.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress);
+ }
+ }
+
+ this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (!aRequest)
+ return;
+
+ var oldBlank = this.mBlank;
+
+ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ const nsIChannel = Components.interfaces.nsIChannel;
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START) {
+ this.mRequestCount++;
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ const NS_ERROR_UNKNOWN_HOST = 2152398878;
+ if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
+ // to prevent bug 235825: wait for the request handled
+ // by the automatic keyword resolver
+ return;
+ }
+ // since we (try to) only handle STATE_STOP of the last request,
+ // the count of open requests should now be 0
+ this.mRequestCount = 0;
+ }
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+ // It's okay to clear what the user typed when we start
+ // loading a document. If the user types, this counter gets
+ // set to zero, if the document load ends without an
+ // onLocationChange, this counter gets decremented
+ // (so we keep it while switching tabs after failed loads)
+ if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
+ this.mBrowser.userTypedClear++;
+
+ if (!this.mBlank) {
+ this.mTab.setAttribute("busy", "true");
+ this.mTabBrowser.updateIcon(this.mTab);
+ this.mTabBrowser.setTabTitleLoading(this.mTab);
+
+ if (this.mTabBrowser.mCurrentTab == this.mTab)
+ this.mTabBrowser.mIsBusy = true;
+ }
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+ if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
+ // The document is done loading, we no longer want the
+ // value cleared.
+ if (this.mBrowser.userTypedClear > 0)
+ this.mBrowser.userTypedClear--;
+
+ if (!this.mBrowser.mIconURL)
+ this.mTabBrowser.useDefaultIcon(this.mTab);
+ }
+
+ if (this.mBlank)
+ this.mBlank = false;
+
+ this.mTab.removeAttribute("busy");
+ this.mTabBrowser.updateIcon(this.mTab);
+
+ var location = aRequest.QueryInterface(nsIChannel).URI;
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs
+ if (location.scheme == "keyword")
+ this.mBrowser.userTypedValue = null;
+
+ if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
+ this.mTabBrowser.setTabTitle(this.mTab);
+
+ if (this.mTabBrowser.mCurrentTab == this.mTab)
+ this.mTabBrowser.mIsBusy = false;
+ }
+
+ if (this.mTabBrowser.mCurrentTab == this.mTab) {
+ for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
+ var p = this.mTabBrowser.mProgressListeners[i];
+ if (p && !oldBlank)
+ p.onStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+ // make sure that the visible status of new blank tabs is correctly set
+ else if (p && "onUpdateCurrentBrowser" in p)
+ p.onUpdateCurrentBrowser(aStateFlags, aStatus, "", 0);
+ }
+ }
+
+ if (aStateFlags & (nsIWebProgressListener.STATE_START |
+ nsIWebProgressListener.STATE_STOP)) {
+ // reset cached temporary values at beginning and end
+ this.mMessage = "";
+ this.mTotalProgress = 0;
+ }
+ this.mStateFlags = aStateFlags;
+ this.mStatus = aStatus;
+ },
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation)
+ {
+ // The document loaded correctly, clear the value if we should
+ if (this.mBrowser.userTypedClear > 0 && aRequest)
+ this.mBrowser.userTypedValue = null;
+
+ if (aWebProgress.DOMWindow == this.mBrowser.contentWindow &&
+ aWebProgress.isLoadingDocument)
+ this.mTabBrowser.setIcon(this.mTab, null);
+
+ // changing location, clear out the missing plugins list
+ this.mTab.missingPlugins = null;
+
+ if (!this.mBlank && this.mTabBrowser.mCurrentTab == this.mTab) {
+ for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
+ var p = this.mTabBrowser.mProgressListeners[i];
+ if (p)
+ p.onLocationChange(aWebProgress, aRequest, aLocation);
+ }
+ }
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ if (this.mBlank)
+ return;
+
+ if (this.mTabBrowser.mCurrentTab == this.mTab) {
+ for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
+ var p = this.mTabBrowser.mProgressListeners[i];
+ if (p)
+ p.onStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+ }
+
+ this.mMessage = aMessage;
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState)
+ {
+ if (this.mTabBrowser.mCurrentTab == this.mTab) {
+ for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
+ var p = this.mTabBrowser.mProgressListeners[i];
+ if (p)
+ p.onSecurityChange(aWebProgress, aRequest, aState);
+ }
+ }
+ },
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="setIcon">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ browser.mIconURL = aURI;
+
+ this.updateIcon(aTab);
+
+ for (var i = 0; i < this.mProgressListeners.length; i++) {
+ var p = this.mProgressListeners[i];
+ if ('onLinkIconAvailable' in p)
+ p.onLinkIconAvailable(browser);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ if (!aTab.hasAttribute("busy") && browser.mIconURL)
+ aTab.setAttribute("image", browser.mIconURL);
+ else
+ aTab.removeAttribute("image");
+ ]]>
+ </body>
+ </method>
+
+ <method name="shouldLoadFavIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ return (aURI && this.mPrefs.getBoolPref("browser.chrome.site_icons") &&
+ this.mPrefs.getBoolPref("browser.chrome.favicons") &&
+ ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
+ ]]>
+ </body>
+ </method>
+
+ <method name="useDefaultIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ if (browser.contentDocument instanceof ImageDocument) {
+ if (this.mPrefs.getBoolPref("browser.chrome.site_icons")) {
+ try {
+ var sz = this.mPrefs.getIntPref("browser.chrome.image_icons.max_size");
+ if (!sz)
+ return;
+
+ var req = browser.contentDocument.imageRequest;
+ if (!req || !req.image ||
+ req.image.width > sz ||
+ req.image.height > sz)
+ return;
+
+ this.setIcon(aTab, browser.currentURI.spec);
+ } catch (e) { }
+ }
+ }
+ else if (this.shouldLoadFavIcon(browser.currentURI)) {
+ var url = browser.currentURI.prePath + "/favicon.ico";
+ if (!this.isIconKnownMissing(url))
+ this.setIcon(aTab, url);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="addToMissedIconCache">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ var entry = this.openCacheEntry(aURI, Components.interfaces.nsICache.ACCESS_READ_WRITE);
+ if (!entry)
+ return;
+
+ if (entry.accessGranted == Components.interfaces.nsICache.ACCESS_WRITE)
+ // It's a new entry. Just write a bit of metadata in to the entry.
+ entry.setMetaDataElement("Icon", "Missed");
+ entry.markValid();
+ entry.close();
+ ]]>
+ </body>
+ </method>
+
+ <method name="openCacheEntry">
+ <parameter name="key"/>
+ <parameter name="access"/>
+ <body>
+ <![CDATA[
+ try {
+ if (!this.mMissedIconCache) {
+ var cacheService = Components.classes['@mozilla.org/network/cache-service;1'].getService(Components.interfaces.nsICacheService);
+ this.mMissedIconCache = cacheService.createSession("MissedIconCache", Components.interfaces.nsICache.STORE_ANYWHERE, true);
+ if (!this.mMissedIconCache)
+ return null;
+ }
+ return this.mMissedIconCache.openCacheEntry(key, access, true);
+ }
+ catch (e) {
+ return null;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="isIconKnownMissing">
+ <parameter name="key"/>
+ <body>
+ <![CDATA[
+ var e = this.openCacheEntry(key, Components.interfaces.nsICache.ACCESS_READ);
+ if (e) {
+ e.close();
+ return true;
+ }
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateTitlebar">
+ <body>
+ <![CDATA[
+ var newTitle = "";
+ var docTitle;
+ var docElement = this.ownerDocument.documentElement;
+ var sep = docElement.getAttribute("titlemenuseparator");
+
+ if (this.docShell.contentViewer)
+ docTitle = this.contentTitle;
+
+ if (!docTitle)
+ docTitle = docElement.getAttribute("titledefault");
+
+ var modifier = docElement.getAttribute("titlemodifier");
+ if (docTitle) {
+ newTitle += docElement.getAttribute("titlepreface");
+ newTitle += docTitle;
+ if (modifier)
+ newTitle += sep;
+ }
+ newTitle += modifier;
+
+ // If location bar is hidden and the URL type supports a host,
+ // add the scheme and host to the title to prevent spoofing.
+ // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
+ // (only for schemes that support a host)
+ try {
+ if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
+ var uri = this.mURIFixup.createExposableURI(
+ this.mCurrentBrowser.currentURI);
+ if (uri.host)
+ newTitle = uri.prePath + sep + newTitle;
+ }
+ } catch (e) {}
+
+ this.ownerDocument.title = newTitle;
+ ]]>
+ </body>
+ </method>
+
+ <method name="updatePopupMenu">
+ <parameter name="aPopupMenu"/>
+ <body>
+ <![CDATA[
+ this.mContextTab = document.popupNode;
+ var disabled = this.mPanelContainer.childNodes.length == 1;
+ var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
+ for (var i = 0; i < menuItems.length; i++)
+ menuItems[i].disabled = disabled;
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateCurrentBrowser">
+ <body>
+ <![CDATA[
+ var newBrowser = this.getBrowserAtIndex(this.mTabContainer.selectedIndex);
+ if (this.mCurrentBrowser == newBrowser)
+ return;
+
+ if (this.mCurrentBrowser) {
+ // Only save the focused element if it is in our content window
+ // or in an ancestor window.
+ var focusedWindow = document.commandDispatcher.focusedWindow;
+ var saveFocus = false;
+
+ if (focusedWindow && focusedWindow.top == window.content) {
+ saveFocus = true;
+ } else {
+ var contentWindow = window;
+
+ while (contentWindow) {
+ if (contentWindow == focusedWindow) {
+ saveFocus = true;
+ break;
+ }
+
+ if (contentWindow.parent == contentWindow) {
+ break;
+ }
+
+ contentWindow = contentWindow.parent;
+ }
+ }
+
+ if (saveFocus) {
+ // Preserve the currently-focused element or DOM window for
+ // this tab.
+
+ this.mCurrentBrowser.focusedWindow = focusedWindow;
+ this.mCurrentBrowser.focusedElement = document.commandDispatcher.focusedElement;
+ }
+
+ if (this.mCurrentBrowser.focusedElement instanceof NSHTMLElement ||
+ this.mCurrentBrowser.focusedElement instanceof XULElement) {
+ // Clear focus outline before we draw on top of it
+ this.mCurrentBrowser.focusedElement.blur();
+ }
+ else {
+ // non-HTML/XUL elements have no blur method, see bug 323805
+ this.mCurrentBrowser.focusedElement = null;
+ }
+ this.mCurrentBrowser.setAttribute("type", "content-targetable");
+ }
+
+ var updatePageReport = false;
+ if ((this.mCurrentBrowser.pageReport && !newBrowser.pageReport) ||
+ (!this.mCurrentBrowser.pageReport && newBrowser.pageReport))
+ updatePageReport = true;
+
+ newBrowser.setAttribute("type", "content-primary");
+ this.mCurrentBrowser = newBrowser;
+ this.mCurrentTab = this.selectedTab;
+
+ if (updatePageReport)
+ this.mCurrentBrowser.updatePageReport();
+
+ // Update the URL bar.
+ var loc = this.mCurrentBrowser.currentURI;
+ if (!loc)
+ loc = ({ spec: "" });
+
+ var webProgress = this.mCurrentBrowser.webProgress;
+ var securityUI = this.mCurrentBrowser.securityUI;
+
+ var i, p;
+ for (i = 0; i < this.mProgressListeners.length; i++) {
+ p = this.mProgressListeners[i];
+ if (p) {
+ p.onLocationChange(webProgress, null, loc);
+ if (securityUI)
+ p.onSecurityChange(webProgress, null, securityUI.state);
+
+ // make sure that all status indicators are properly updated
+ if ("onUpdateCurrentBrowser" in p) {
+ var listener = this.mTabListeners[this.mTabContainer.selectedIndex] || null;
+ if (listener && listener.mStateFlags)
+ p.onUpdateCurrentBrowser(listener.mStateFlags, listener.mStatus,
+ listener.mMessage, listener.mTotalProgress);
+ }
+ }
+ }
+
+ this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
+
+ // Update the window title.
+ this.updateTitlebar();
+
+ // If the new tab is busy, and our current state is not busy, then
+ // we need to fire a start to all progress listeners.
+ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
+ this.mIsBusy = true;
+ webProgress = this.mCurrentBrowser.webProgress;
+ for (i = 0; i < this.mProgressListeners.length; i++) {
+ p = this.mProgressListeners[i];
+ if (p)
+ p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_IS_NETWORK, 0);
+ }
+ }
+
+ // If the new tab is not busy, and our current state is busy, then
+ // we need to fire a stop to all progress listeners.
+ if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
+ this.mIsBusy = false;
+ webProgress = this.mCurrentBrowser.webProgress;
+ for (i = 0; i < this.mProgressListeners.length; i++) {
+ p = this.mProgressListeners[i];
+ if (p)
+ p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_STOP | nsIWebProgressListener.STATE_IS_NETWORK, 0);
+ }
+ }
+
+ // We've selected the new tab, so go ahead and notify listeners.
+ var event = document.createEvent("Events");
+ event.initEvent("TabSelect", true, false);
+ this.mCurrentTab.dispatchEvent(event);
+
+ if (document.commandDispatcher.focusedElement &&
+ document.commandDispatcher.focusedElement.parentNode ==
+ this.mCurrentTab.parentNode) {
+ // The focus is on a tab in the same tab panel
+ return; // If focus was on a tab, switching tabs focuses the new tab
+ }
+
+ var whatToFocus = window.content;
+
+ // Focus the previously focused element or window
+ if (newBrowser.focusedElement) {
+ if (newBrowser.focusedElement.parentNode !=
+ this.mCurrentTab.parentNode) {
+ // Focus the remembered element unless it's in the current tab panel
+ whatToFocus = newBrowser.focusedElement;
+ }
+ }
+ else if (newBrowser.focusedWindow) {
+ whatToFocus = newBrowser.focusedWindow;
+ }
+
+ function setFocus(element) {
+ document.commandDispatcher.suppressFocusScroll = true;
+
+ if (element instanceof Window ||
+ element instanceof NSHTMLElement ||
+ element instanceof XULElement) {
+ try {
+ element.focus();
+ }
+ catch(ex) {
+ dump("XXXfocus() failed, see bug #348183: ex = " + ex + "\n");
+ }
+ }
+ document.commandDispatcher.suppressFocusScroll = false;
+ }
+
+ // Use setTimeout to avoid focus outline ghosting.
+ setTimeout(setFocus, 0, whatToFocus);
+ ]]>
+ </body>
+ </method>
+
+ <method name="onTabClick">
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ if (event.button != 1 || event.target.localName != 'tab')
+ return;
+
+ this.removeTab(event.target);
+ event.stopPropagation();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onLinkAdded">
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ if (!this.mPrefs.getBoolPref("browser.chrome.site_icons"))
+ return;
+
+ if (!event.originalTarget.rel.match((/(?:^|\s)icon(?:\s|$)/i)))
+ return;
+
+ // We have an icon.
+ var href = event.originalTarget.href;
+ if (!href)
+ return;
+
+ const nsIContentPolicy = Components.interfaces.nsIContentPolicy;
+ try {
+ var contentPolicy =
+ Components.classes['@mozilla.org/layout/content-policy;1']
+ .getService(nsIContentPolicy);
+ } catch(e) {
+ return; // Refuse to load if we can't do a security check.
+ }
+
+ // Verify that the load of this icon is legal.
+ // We check first with the security manager
+ const secMan =
+ Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Components.interfaces.nsIScriptSecurityManager);
+
+ // Get the IOService so we can make URIs
+ const ioService =
+ Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ const targetDoc = event.target.ownerDocument;
+ // Make a URI out of our href.
+ var uri = ioService.newURI(href, targetDoc.characterSet, null);
+
+ var origURI = ioService.newURI(targetDoc.documentURI, targetDoc.characterSet, null);
+
+ const nsIScriptSecMan =
+ Components.interfaces.nsIScriptSecurityManager;
+
+ try {
+ // error pages can load their favicon
+ // to be on the safe side, only allow chrome:// favicons
+ const aboutNeterr = "about:neterror?";
+ if (origURI.spec.substr(0, aboutNeterr.length) != aboutNeterr ||
+ !uri.schemeIs("chrome"))
+ secMan.checkLoadURI(origURI, uri,
+ nsIScriptSecMan.DISALLOW_SCRIPT);
+ } catch(e) {
+ return;
+ }
+
+ // Security says okay, now ask content policy
+ if (contentPolicy.shouldLoad(nsIContentPolicy.TYPE_IMAGE,
+ uri, origURI, event.target,
+ event.target.type,
+ null) != nsIContentPolicy.ACCEPT)
+ return;
+
+ var browserIndex = this.getBrowserIndexForDocument(targetDoc);
+ // no browser? no favicon.
+ if (browserIndex == -1)
+ return;
+
+ var tab = this.mTabContainer.childNodes[browserIndex];
+ this.setIcon(tab, href);
+ ]]>
+ </body>
+ </method>
+
+ <method name="onTitleChanged">
+ <parameter name="evt"/>
+ <body>
+ <![CDATA[
+ if (evt.target != this.contentDocument)
+ return;
+
+ var i = 0;
+ for ( ; i < this.parentNode.parentNode.childNodes.length; i++) {
+ if (this.parentNode.parentNode.childNodes[i].firstChild == this)
+ break;
+ }
+
+ var tabBrowser = this.parentNode.parentNode.parentNode.parentNode;
+
+ var tab = document.getAnonymousElementByAttribute(tabBrowser, "linkedpanel", this.parentNode.id);
+ tabBrowser.setTabTitle(tab);
+
+ if (tab == tabBrowser.mCurrentTab)
+ tabBrowser.updateTitlebar();
+ ]]>
+ </body>
+ </method>
+
+ <method name="setTabTitleLoading">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ aTab.label = this.mStringBundle.getString("tabs.loading");
+ aTab.setAttribute("crop", "end");
+ ]]>
+ </body>
+ </method>
+
+ <method name="setTabTitle">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ var title = browser.contentDocument.title;
+
+ if (!title) {
+ if (browser.currentURI.spec) {
+ try {
+ title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
+ } catch(ex) {
+ title = browser.currentURI.spec;
+ }
+ }
+
+ if (title && title != "about:blank") {
+ // At this point, we now have a URI.
+ // Let's try to unescape it using a character set
+ // in case the URI is not ASCII.
+ try {
+ var characterSet = browser.contentDocument.characterSet;
+ const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
+ .getService(Components.interfaces.nsITextToSubURI);
+ title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
+ } catch(ex) { /* Do nothing. */ }
+
+
+ } else // Still no title? Fall back to our untitled string.
+ title = '\\{0_0}/';
+ }
+
+ aTab.label = title;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setStripVisibilityTo">
+ <parameter name="aShow"/>
+ <body>
+ <![CDATA[
+ this.mStrip.collapsed = !aShow;
+ if (aShow) {
+ // XXXdwh temporary unclean dependency on specific menu items in navigator.xul
+ document.getElementById("menu_closeWindow").hidden = false;
+ document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.closeTab"));
+ if (!this.mTabbedMode)
+ this.enterTabbedMode();
+ }
+ else {
+ // XXXdwh temporary unclean dependency on specific menu items in navigator.xul
+ document.getElementById("menu_closeWindow").hidden = true;
+ document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.close"));
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getStripVisibility">
+ <body>
+ return !this.mStrip.collapsed;
+ </body>
+ </method>
+
+ <method name="enterTabbedMode">
+ <body>
+ <![CDATA[
+ this.mTabbedMode = true; // Welcome to multi-tabbed mode.
+
+ // Get the first tab all hooked up with a title listener and popup blocking listener.
+ this.mCurrentBrowser.addEventListener("DOMTitleChanged", this.onTitleChanged, true);
+
+ var throbberElement = document.getElementById("navigator-throbber");
+ if (throbberElement && throbberElement.hasAttribute("busy")) {
+ this.mCurrentTab.setAttribute("busy", "true");
+ this.mIsBusy = true;
+ this.setTabTitleLoading(this.mCurrentTab);
+ this.updateIcon(this.mCurrentTab);
+ } else {
+ this.setTabTitle(this.mCurrentTab);
+ this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
+ }
+
+ var filter;
+ if (this.mTabFilters.length > 0) {
+ // Use the filter hooked up in our addProgressListener
+ filter = this.mTabFilters[0];
+ } else {
+ // create a filter and hook it up to our first browser
+ filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Components.interfaces.nsIWebProgress);
+ this.mTabFilters[0] = filter;
+ this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ }
+
+ // Remove all our progress listeners from the active browser's filter.
+ for (var i = 0; i < this.mProgressListeners.length; i++) {
+ var p = this.mProgressListeners[i];
+ if (p)
+ filter.removeProgressListener(p);
+ }
+
+ // Wire up a progress listener to our filter.
+ const listener = this.mTabProgressListener(this.mCurrentTab,
+ this.mCurrentBrowser,
+ false);
+ filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[0] = listener;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadOneTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
+ this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
+ var owner = bgLoad ? null : this.selectedTab;
+ var tab = this.addTab(aURI, aReferrerURI, aCharset, aPostData, owner,
+ aAllowThirdPartyFixup);
+ // Set newly selected tab after quick timeout, otherwise hideous focus problems
+ // can occur when "browser.tabs.loadInBackground" is false and presshell is not ready
+ if (!bgLoad) {
+ function selectNewForegroundTab(browser, tab) {
+ browser.selectedTab = tab;
+ }
+ setTimeout(selectNewForegroundTab, 0, getBrowser(), tab);
+ }
+ if (!bgLoad)
+ this.selectedTab = tab;
+
+ return tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadTabs">
+ <parameter name="aURIs"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aReplace"/>
+ <body><![CDATA[
+ // The tab selected after this new tab is closed (i.e. the new tab's
+ // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
+ // when several urls are opened here (i.e. closing the first should select
+ // the next of many URLs opened) or if the pref to have UI links opened in
+ // the background is set (i.e. the link is not being opened modally)
+ //
+ // i.e.
+ // Number of URLs Load UI Links in BG Focus Last Viewed?
+ // == 1 false YES
+ // == 1 true NO
+ // > 1 false/true NO
+ var owner = (aURIs.length > 1) || aLoadInBackground ? null : gBrowser.selectedTab;
+ var firstTabAdded = null;
+ if (aReplace)
+ this.loadURI(aURIs[0], null, null);
+ else
+ firstTabAdded = gBrowser.addTab(aURIs[0], null, null, null, owner, false);
+
+ var tabNum = this.mTabContainer.selectedIndex;
+ for (var i = 1; i < aURIs.length; ++i) {
+ var tab = gBrowser.addTab(aURIs[i]);
+ if (aReplace)
+ this.moveTabTo(tab, ++tabNum);
+ }
+
+ if (!aLoadInBackground) {
+ if (firstTabAdded) {
+ // .selectedTab setter focuses the content area
+ this.selectedTab = firstTabAdded;
+ }
+ else
+ window.content.focus();
+ }
+ ]]></body>
+ </method>
+
+ <method name="addTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aOwner"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ this._browsers = null; // invalidate cache
+
+ if (!this.mTabbedMode)
+ this.enterTabbedMode();
+
+ // if we're adding tabs, we're past interrupt mode, ditch the owner
+ if (this.mCurrentTab.owner)
+ this.mCurrentTab.owner = null;
+
+ var t = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "tab");
+
+ var blank = (aURI == "about:blank");
+
+ if (blank)
+ t.setAttribute("label", this.mStringBundle.getString("tabs.untitled"));
+ else
+ t.setAttribute("label", aURI);
+
+ t.setAttribute("crop", "end");
+ t.minWidth = this.mTabContainer.mTabMinWidth;
+ t.setAttribute("flex", "100");
+ t.setAttribute("validate", "never");
+ t.setAttribute("onerror", "this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image')); this.removeAttribute('image');");
+ t.className = "tabbrowser-tab";
+
+ this.mTabContainer.appendChild(t);
+
+ if (document.defaultView
+ .getComputedStyle(this.mTabContainer, "")
+ .direction == "rtl") {
+ /* In RTL UI, the tab is visually added to the left side of the
+ * tabstrip. This means the tabstip has to be scrolled back in
+ * order to make sure the same set of tabs is visible before and
+ * after the new tab is added */
+
+ }
+
+ // invalidate cache, because mTabContainer is about to change
+ this._browsers = null;
+
+ // If this new tab is owned by another, assert that relationship
+ if (aOwner !== undefined && aOwner !== null) {
+ t.owner = aOwner;
+
+ var self = this;
+ function attrChanged(event) {
+ if (event.attrName == "selectedIndex" &&
+ event.prevValue != event.newValue)
+ self.resetOwner(parseInt(event.prevValue));
+ }
+ if (!this.mTabChangedListenerAdded) {
+ this.mTabBox.addEventListener("DOMAttrModified", attrChanged, false);
+ this.mTabChangedListenerAdded = true;
+ }
+ }
+
+ var b = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "browser");
+ b.setAttribute("type", "content-targetable");
+ b.setAttribute("message", "true");
+ b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
+ b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
+ if (this.hasAttribute("autocompletepopup"))
+ b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
+
+ // Add the Message and the Browser to the box
+ var notificationbox = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "notificationbox");
+ notificationbox.setAttribute("flex", "1");
+ notificationbox.appendChild(b);
+ b.setAttribute("flex", "1");
+ this.mPanelContainer.appendChild(notificationbox);
+
+ b.addEventListener("DOMTitleChanged", this.onTitleChanged, true);
+
+ if (this.mStrip.collapsed)
+ this.setStripVisibilityTo(true);
+
+ this.mPrefs.setBoolPref("browser.tabs.forceHide", false);
+
+ // wire up a progress listener for the new browser object.
+ var position = this.mTabContainer.childNodes.length-1;
+ var tabListener = this.mTabProgressListener(t, b, blank);
+ const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Components.interfaces.nsIWebProgress);
+ filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[position] = tabListener;
+ this.mTabFilters[position] = filter;
+
+ b._fastFind = this.fastFind;
+
+ var uniqueId = "panel" + Date.now() + position;
+ this.mPanelContainer.lastChild.id = uniqueId;
+ t.linkedPanel = uniqueId;
+ t.linkedBrowser = b;
+ t._tPos = position;
+ if (t.previousSibling.selected)
+ t.setAttribute("afterselected", true);
+
+ if (!blank) {
+ // pretend the user typed this so it'll be available till
+ // the document successfully loads
+ b.userTypedValue = aURI;
+
+ if (aPostData === undefined)
+ aPostData = null;
+ const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
+ var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup) {
+ flags = nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+ b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
+ }
+
+
+ // Dispatch a new tab notification. We do this once we're
+ // entirely done, so that things are in a consistent state
+ // even if the event listener opens or closes tabs.
+ var evt = document.createEvent("Events");
+ evt.initEvent("TabOpen", true, false);
+ t.dispatchEvent(evt);
+
+ return t;
+ ]]>
+ </body>
+ </method>
+
+ <method name="warnAboutClosingTabs">
+ <parameter name="aAll"/>
+ <body>
+ <![CDATA[
+ var numTabs = this.mTabContainer.childNodes.length;
+ var reallyClose = true;
+ if (numTabs <= 1)
+ return reallyClose;
+
+ const pref = "browser.tabs.warnOnClose";
+ var shouldPrompt = this.mPrefs.getBoolPref(pref);
+
+ if (shouldPrompt) {
+ var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+
+ //default to true: if it were false, we wouldn't get this far
+ var warnOnClose = { value:true };
+ var bundle = this.mStringBundle;
+ var tabsToClose = numTabs; //number of tabs to be removed
+ if (!aAll)
+ --tabsToClose;
+
+ var messageKey = (tabsToClose == 1) ? "tabs.closeWarningOneTab" : "tabs.closeWarningMultipleTabs";
+ var closeKey = (tabsToClose == 1) ? "tabs.closeButtonOne" : "tabs.closeButtonMultiple";
+ // focus the window before prompting.
+ // this will raise any minimized window, which will
+ // make it obvious which window the prompt is for and will
+ // solve the problem of windows "obscuring" the prompt.
+ // see bug #350299 for more details
+ window.focus();
+ var buttonPressed = promptService.confirmEx(window,
+ bundle.getString("tabs.closeWarningTitle"),
+ bundle.getFormattedString(messageKey, [tabsToClose]),
+ (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
+ + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
+ bundle.getString(closeKey),
+ null, null,
+ bundle.getString("tabs.closeWarningPromptMe"),
+ warnOnClose);
+ reallyClose = (buttonPressed == 0);
+ // don't set the pref unless they press OK and it's false
+ if (reallyClose && !warnOnClose.value)
+ this.mPrefs.setBoolPref(pref, false);
+ }
+ return reallyClose;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeAllTabsBut">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (this.warnAboutClosingTabs(false)) {
+ if (aTab.localName != "tab")
+ aTab = this.mCurrentTab;
+ else
+ this.mTabContainer.selectedItem = aTab;
+
+ var childNodes = this.mTabContainer.childNodes;
+
+ for (var i = childNodes.length - 1; i >= 0; --i) {
+ if (childNodes[i] != aTab)
+ this.removeTab(childNodes[i]);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeCurrentTab">
+ <body>
+ <![CDATA[
+ return this.removeTab(this.mCurrentTab);
+ ]]>
+ </body>
+ </method>
+
+ <method name="resetOwner">
+ <parameter name="oldIndex"/>
+ <body>
+ <![CDATA[
+ // Reset the owner property, since we're leaving the modally opened
+ // tab for another.
+ if (oldIndex < this.mTabContainer.childNodes.length) {
+ var tab = this.mTabContainer.childNodes[oldIndex];
+ tab.owner = null;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ this._browsers = null; // invalidate cache
+ if (aTab.localName != "tab")
+ aTab = this.mCurrentTab;
+
+ var l = this.mTabContainer.childNodes.length;
+ if (l == 1 && this.mPrefs.getBoolPref("browser.tabs.autoHide")) {
+ // hide the tab bar
+ this.mPrefs.setBoolPref("browser.tabs.forceHide", true);
+ this.setStripVisibilityTo(false);
+ return;
+ }
+
+ var ds = this.getBrowserForTab(aTab).docShell;
+ if (ds.contentViewer && !ds.contentViewer.permitUnload())
+ return;
+
+ // see notes in addTab
+ var _delayedUpdate = function(aTabContainer) {
+ }
+ setTimeout(_delayedUpdate, 0, this.mTabContainer);
+
+ if (l == 1) {
+ // add a new blank tab to replace the one we're about to close
+ // (this ensures that the remaining tab is as good as new)
+ this.addTab("about:blank");
+ l++;
+ };
+ // We're committed to closing the tab now.
+ // Dispatch a notification.
+ // We dispatch it before any teardown so that event listeners can
+ // inspect the tab that's about to close.
+ var evt = document.createEvent("Events");
+ evt.initEvent("TabClose", true, false);
+ aTab.dispatchEvent(evt);
+
+ var index = -1;
+ if (this.mCurrentTab == aTab)
+ index = this.mTabContainer.selectedIndex;
+ else {
+ // Find and locate the tab in our list.
+ for (var i = 0; i < l; i++)
+ if (this.mTabContainer.childNodes[i] == aTab)
+ index = i;
+ }
+
+ // Remove the tab's filter and progress listener.
+ const filter = this.mTabFilters[index];
+ var oldBrowser = this.getBrowserAtIndex(index);
+ oldBrowser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(this.mTabListeners[index]);
+ this.mTabFilters.splice(index, 1);
+ this.mTabListeners.splice(index, 1);
+
+ // Remove our title change and blocking listeners
+ oldBrowser.removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
+
+ // We are no longer the primary content area.
+ oldBrowser.setAttribute("type", "content-targetable");
+
+ // Get the index of the tab we're removing before unselecting it
+ var currentIndex = this.mTabContainer.selectedIndex;
+
+ var oldTab = aTab;
+
+ // clean up the before/afterselected attributes before removing the tab
+ oldTab.selected = false;
+
+ // Remove this tab as the owner of any other tabs, since it's going away.
+ for (i = 0; i < this.mTabContainer.childNodes.length; ++i) {
+ var tab = this.mTabContainer.childNodes[i];
+ if ("owner" in tab && tab.owner == oldTab)
+ // |tab| is a child of the tab we're removing, make it an orphan
+ tab.owner = null;
+ }
+
+ // Because of the way XBL works (fields just set JS
+ // properties on the element) and the code we have in place
+ // to preserve the JS objects for any elements that have
+ // JS properties set on them, the browser element won't be
+ // destroyed until the document goes away. So we force a
+ // cleanup ourselves.
+ // This has to happen before we remove the child so that the
+ // XBL implementation of nsIObserver still works. But
+ // clearing focusedWindow happens below because it gets
+ // reset by updateCurrentBrowser.
+ oldBrowser.destroy();
+
+ // Remove the tab
+ this.mTabContainer.removeChild(oldTab);
+ // invalidate cache, because mTabContainer is about to change
+ this._browsers = null;
+ this.mPanelContainer.removeChild(oldBrowser.parentNode);
+
+ // Find the tab to select
+ var newIndex = -1;
+ if (currentIndex > index)
+ newIndex = currentIndex-1;
+ else if (currentIndex < index)
+ newIndex = currentIndex;
+ else {
+ if ("owner" in oldTab && oldTab.owner &&
+ this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
+ for (i = 0; i < this.mTabContainer.childNodes.length; ++i) {
+ tab = this.mTabContainer.childNodes[i];
+ if (tab == oldTab.owner) {
+ newIndex = i;
+ break;
+ }
+ }
+ }
+ if (newIndex == -1)
+ newIndex = (index == l - 1) ? index - 1 : index;
+ }
+
+ // Select the new tab
+ this.selectedTab = this.mTabContainer.childNodes[newIndex];
+
+ for (i = oldTab._tPos; i < this.mTabContainer.childNodes.length; i++) {
+ this.mTabContainer.childNodes[i]._tPos = i;
+ }
+ this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;
+ this.mCurrentTab.selected = true;
+
+ this.updateCurrentBrowser();
+
+ // see comment above destroy above
+ oldBrowser.focusedWindow = null;
+ oldBrowser.focusedElement = null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadAllTabs">
+ <body>
+ <![CDATA[
+ var l = this.mPanelContainer.childNodes.length;
+ for (var i = 0; i < l; i++) {
+ try {
+ this.getBrowserAtIndex(i).reload();
+ } catch (e) {
+ // ignore failure to reload so others will be reloaded
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (aTab.localName != "tab")
+ aTab = this.mCurrentTab;
+
+ this.getBrowserForTab(aTab).reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onTabBarDblClick">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ // See hack note in the tabbrowser-close-button binding
+ if (!this._blockDblClick && aEvent.button == 0 &&
+ aEvent.originalTarget.localName == "box") {
+ // xxx this needs to check that we're in the empty area of the tabstrip
+ var e = document.createEvent("Events");
+ e.initEvent("NewTab", true, true);
+ this.dispatchEvent(e);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="addProgressListener">
+ <parameter name="aListener"/>
+ <parameter name="aMask"/>
+ <body>
+ <![CDATA[
+ if (!this.mAddProgressListenerWasCalled) {
+ this.mAddProgressListenerWasCalled = true;
+ var autoHide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
+ var forceHide = this.mPrefs.getBoolPref("browser.tabs.forceHide");
+ var tabStripHide = !window.toolbar.visible;
+ if (!autoHide && !forceHide && !tabStripHide)
+ this.setStripVisibilityTo(true);
+ }
+
+ if (!this.mTabbedMode && this.mProgressListeners.length == 1) {
+ // If we are adding a 2nd progress listener, we need to enter tabbed mode
+ // because the browser status filter can only handle one progress listener.
+ // In tabbed mode, mTabProgressListener is used which will iterate over all listeners.
+ this.enterTabbedMode();
+ }
+
+ this.mProgressListeners.push(aListener);
+
+ if (!this.mTabbedMode) {
+ // If someone does this:
+ // addProgressListener, removeProgressListener, addProgressListener
+ // don't create a new filter; reuse the existing filter.
+ if (this.mTabFilters.length == 0) {
+ // hook a filter up to our first browser
+ const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Components.interfaces.nsIWebProgress);
+ this.mTabFilters[0] = filter;
+ this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ }
+
+ // Directly hook the listener up to the filter for better performance
+ this.mTabFilters[0].addProgressListener(aListener, aMask);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ for (var i = 0; i < this.mProgressListeners.length; i++) {
+ if (this.mProgressListeners[i] == aListener) {
+ this.mProgressListeners.splice(i, 1);
+ break;
+ }
+ }
+
+ if (!this.mTabbedMode)
+ // Don't forget to remove it from the filter we hooked it up to
+ this.mTabFilters[0].removeProgressListener(aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return aTab.linkedBrowser;
+ ]]>
+ </body>
+ </method>
+
+ <property name="tabContainer">
+ <getter>
+ return this.mTabContainer;
+ </getter>
+ </property>
+
+ <property name="selectedTab">
+ <getter>
+ return this.mTabBox.selectedTab;
+ </getter>
+ <setter>
+ <![CDATA[
+ // Update the tab
+ this.mTabBox.selectedTab = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedBrowser"
+ onget="return this.mCurrentBrowser;"
+ readonly="true"/>
+
+ <property name="browsers" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._browsers) {
+ var browsers = [];
+ var i;
+ browsers.item = function(i) {return this[i];}
+ for (i = 0; i < this.mTabContainer.childNodes.length; i++)
+ browsers.push(this.mTabContainer.childNodes[i].linkedBrowser);
+ this._browsers = browsers;
+ }
+ return this._browsers;
+ ]]>
+ </getter>
+ </property>
+
+ <!-- Drag and drop observer API -->
+ <method name="onDragStart">
+ <parameter name="aEvent"/>
+ <parameter name="aXferData"/>
+ <parameter name="aDragAction"/>
+ <body>
+ <![CDATA[
+ if (aEvent.target.localName == "tab" &&
+ aEvent.originalTarget.localName != "toolbarbutton") {
+ aXferData.data = new TransferData();
+
+ var URI = this.getBrowserForTab(aEvent.target).currentURI;
+ if (URI) {
+ aXferData.data.addDataForFlavour("text/x-moz-url", URI.spec + "\n" + aEvent.target.label);
+ aXferData.data.addDataForFlavour("text/unicode", URI.spec);
+ aXferData.data.addDataForFlavour("text/html", '<a href="' + URI.spec + '">' + aEvent.target.label + '</a>');
+ } else {
+ aXferData.data.addDataForFlavour("text/unicode", "about:blank");
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="canDrop">
+ <parameter name="aEvent"/>
+ <parameter name="aDragSession"/>
+ <body>
+ <![CDATA[
+ /*if (aDragSession.sourceNode &&
+ aDragSession.sourceNode.parentNode == this.mTabContainer &&
+ (aEvent.screenX >= aDragSession.sourceNode.boxObject.screenX &&
+ aEvent.screenX <= (aDragSession.sourceNode.boxObject.screenX +
+ aDragSession.sourceNode.boxObject.width)))
+ return false;*/
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDragOver">
+ <parameter name="aEvent"/>
+ <parameter name="aFlavour"/>
+ <parameter name="aDragSession"/>
+ <body>
+ <![CDATA[
+
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDrop">
+ <parameter name="aEvent"/>
+ <parameter name="aXferData"/>
+ <parameter name="aDragSession"/>
+ <body>
+ <![CDATA[
+ if (aDragSession.sourceNode && aDragSession.sourceNode.parentNode == this.mTabContainer) {
+ var newIndex = this.getNewIndex(aEvent);
+ var oldIndex = aDragSession.sourceNode._tPos;
+
+ /*if (newIndex > oldIndex)
+ newIndex--;*/
+ if (newIndex != oldIndex)
+ this.moveTabTo(this.mTabs[oldIndex], newIndex);
+ var tn=this.mTabs[newIndex].label;
+ this.mTabs[newIndex].label= '';
+ this.mTabs[newIndex].label= tn;
+ } else {
+ var url = transferUtils.retrieveURLFromData(aXferData.data, aXferData.flavour.contentType);
+
+ // valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
+ // Also disallow dropping javascript: or data: urls--bail out
+ if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
+ /^\s*(javascript|data):/.test(url))
+ return;
+
+ this.dragDropSecurityCheck(aEvent, aDragSession, url);
+
+ var bgLoad = true;
+ try {
+ bgLoad = this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
+ }
+ catch (e) { }
+
+ if (aEvent.shiftKey)
+ bgLoad = !bgLoad;
+
+ if (document.getBindingParent(aEvent.originalTarget).localName != "tab") {
+ // We're adding a new tab.
+ this.loadOneTab(getShortcutOrURI(url), null, null, null, bgLoad, false);
+ }
+ else {
+ // Load in an existing tab.
+ var tab = aEvent.target;
+ try {
+ this.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
+ if (this.mCurrentTab != tab && !bgLoad)
+ this.selectedTab = tab;
+ } catch(ex) {
+ // Just ignore invalid urls
+ }
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onDragExit">
+ <parameter name="aEvent"/>
+ <parameter name="aDragSession"/>
+ <body>
+ <![CDATA[
+ if (aDragSession.sourceNode &&
+ aDragSession.sourceNode.parentNode == this.mTabContainer &&
+ aDragSession.canDrop) {
+ var target = aEvent.relatedTarget;
+ while (target && target != this.mStrip)
+ target = target.parentNode;
+ if (target)
+ return;
+ }
+ this.mTabDropIndicatorBar.setAttribute('dragging','false');
+ ]]>
+ </body>
+ </method>
+
+ <method name="getSupportedFlavours">
+ <body>
+ <![CDATA[
+ var flavourSet = new FlavourSet();
+ flavourSet.appendFlavour("text/x-moz-url");
+ flavourSet.appendFlavour("text/unicode");
+ flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
+ return flavourSet;
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabTo">
+ <parameter name="aTab"/>
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ this._browsers = null; // invalidate cache
+ this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
+ this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
+
+ var oldPosition = aTab._tPos;
+
+ aIndex = aIndex <= aTab._tPos ? aIndex: aIndex+1;
+
+ this.mCurrentTab.selected = false;
+ this.mTabContainer.insertBefore(aTab, this.mTabContainer.childNodes[aIndex]);
+ // invalidate cache, because mTabContainer is about to change
+ this._browsers = null;
+
+ var i;
+ for (i = 0; i < this.mTabContainer.childNodes.length; i++) {
+ this.mTabContainer.childNodes[i]._tPos = i;
+ this.mTabContainer.childNodes[i].selected = false;
+ }
+ this.mCurrentTab.selected = true;
+
+ var evt = document.createEvent("UIEvents");
+ evt.initUIEvent("TabMove", true, false, window, oldPosition);
+ aTab.dispatchEvent(evt);
+
+ return aTab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getNewIndex">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var i;
+ for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
+ if (aEvent.screenY < this.mTabs[i].boxObject.screenY + this.mTabs[i].boxObject.height)
+ return i;
+/* var i;
+ if (window.getComputedStyle(this.parentNode, null).direction == "ltr") {
+ for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
+ if (aEvent.screenX < this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
+ return i;
+ } else {
+ for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
+ if (aEvent.screenX > this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
+ return i;
+ }*/
+
+ return this.mTabs.length;
+ ]]>
+ </body>
+ </method>
+
+
+ <method name="moveTabForward">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos < this.browsers.length - 1) {
+ this.moveTabTo(this.mCurrentTab, tabPos + 1);
+ this.mCurrentTab.focus();
+ }
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToStart();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabBackward">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos > 0) {
+ this.moveTabTo(this.mCurrentTab, tabPos - 1);
+ this.mCurrentTab.focus();
+ }
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToEnd();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToStart">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos > 0) {
+ this.moveTabTo(this.mCurrentTab, 0);
+ this.mCurrentTab.focus();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToEnd">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos < this.browsers.length - 1) {
+ this.moveTabTo(this.mCurrentTab,
+ this.browsers.length - 1);
+ this.mCurrentTab.focus();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabOver">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var direction = window.getComputedStyle(this.parentNode, null).direction;
+ if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
+ (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
+ this.moveTabForward();
+ else
+ this.moveTabBackward();
+ ]]>
+ </body>
+ </method>
+
+ <!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
+ MAKE SURE TO ADD IT HERE AS WELL. -->
+ <property name="canGoBack"
+ onget="return this.mCurrentBrowser.canGoBack;"
+ readonly="true"/>
+
+ <property name="canGoForward"
+ onget="return this.mCurrentBrowser.canGoForward;"
+ readonly="true"/>
+
+ <method name="goBack">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goBack();
+ ]]>
+ </body>
+ </method>
+
+ <method name="goForward">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goForward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reload">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadWithFlags">
+ <parameter name="aFlags"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reloadWithFlags(aFlags);
+ ]]>
+ </body>
+ </method>
+
+ <method name="stop">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.stop();
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURI">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURIWithFlags">
+ <parameter name="aURI"/>
+ <parameter name="aFlags"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset);
+ ]]>
+ </body>
+ </method>
+
+ <method name="goHome">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goHome();
+ ]]>
+ </body>
+ </method>
+
+ <property name="homePage">
+ <getter>
+ <![CDATA[
+ return this.mCurrentBrowser.homePage;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.mCurrentBrowser.homePage = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="gotoIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.gotoIndex(aIndex);
+ ]]>
+ </body>
+ </method>
+
+ <method name="attachFormFill">
+ <body><![CDATA[
+ for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
+ var cb = this.getBrowserAtIndex(i);
+ cb.attachFormFill();
+ }
+ ]]></body>
+ </method>
+
+ <method name="detachFormFill">
+ <body><![CDATA[
+ for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
+ var cb = this.getBrowserAtIndex(i);
+ cb.detachFormFill();
+ }
+ ]]></body>
+ </method>
+
+ <property name="pageReport"
+ onget="return this.mCurrentBrowser.pageReport;"
+ readonly="true"/>
+
+ <property name="currentURI"
+ onget="return this.mCurrentBrowser.currentURI;"
+ readonly="true"/>
+
+ <field name="_fastFind">null</field>
+ <property name="fastFind"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._fastFind) {
+ this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
+ .createInstance(Components.interfaces.nsITypeAheadFind_MOZILLA_1_8_BRANCH);
+ this._fastFind.init(this.docShell);
+ }
+ return this._fastFind;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="findString"
+ onget="return this.mCurrentBrowser.findString;"
+ readonly="true"/>
+
+ <property name="docShell"
+ onget="return this.mCurrentBrowser.docShell"
+ readonly="true"/>
+
+ <property name="webNavigation"
+ onget="return this.mCurrentBrowser.webNavigation"
+ readonly="true"/>
+
+ <property name="webBrowserFind"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webBrowserFind"/>
+
+ <property name="webProgress"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webProgress"/>
+
+ <property name="contentWindow"
+ readonly="true"
+ onget="return this.mCurrentBrowser.contentWindow"/>
+
+ <property name="sessionHistory"
+ onget="return this.mCurrentBrowser.sessionHistory;"
+ readonly="true"/>
+
+ <property name="markupDocumentViewer"
+ onget="return this.mCurrentBrowser.markupDocumentViewer;"
+ readonly="true"/>
+
+ <property name="contentViewerEdit"
+ onget="return this.mCurrentBrowser.contentViewerEdit;"
+ readonly="true"/>
+
+ <property name="contentViewerFile"
+ onget="return this.mCurrentBrowser.contentViewerFile;"
+ readonly="true"/>
+
+ <property name="documentCharsetInfo"
+ onget="return this.mCurrentBrowser.documentCharsetInfo;"
+ readonly="true"/>
+
+ <property name="contentDocument"
+ onget="return this.mCurrentBrowser.contentDocument;"
+ readonly="true"/>
+
+ <property name="contentTitle"
+ onget="return this.mCurrentBrowser.contentTitle;"
+ readonly="true"/>
+
+ <property name="securityUI"
+ onget="return this.mCurrentBrowser.securityUI;"
+ readonly="true"/>
+
+ <method name="find">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.find();
+ ]]>
+ </body>
+ </method>
+
+ <method name="findAgain">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.findAgain();
+ ]]>
+ </body>
+ </method>
+
+ <method name="findPrevious">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.findPrevious();
+ ]]>
+ </body>
+ </method>
+
+ <method name="dragDropSecurityCheck">
+ <parameter name="aEvent"/>
+ <parameter name="aDragSession"/>
+ <parameter name="aUri"/>
+ <body>
+ <![CDATA[
+ // Do a security check for drag n' drop. Make sure the
+ // source document can load the dragged link.
+ var sourceDoc = aDragSession.sourceDocument;
+
+ if (sourceDoc) {
+ // Strip leading and trailing whitespace, then try to
+ // create a URI from the dropped string. If that
+ // succeeds, we're dropping a URI and we need to do a
+ // security check to make sure the source document can
+ // load the dropped URI. We don't so much care about
+ // creating the real URI here (i.e. encoding differences
+ // etc don't matter), we just want to know if aUri
+ // really is a URI.
+
+ var uriStr = aUri.replace(/^\s*|\s*$/g, '');
+ var uri = null;
+
+ try {
+ uri = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(uriStr, null, null);
+ } catch (e) {
+ }
+
+ if (uri) {
+ // aUri is a URI, do the security check.
+ var sourceURI = sourceDoc.documentURI;
+
+ const nsIScriptSecurityManager =
+ Components.interfaces.nsIScriptSecurityManager;
+ var secMan =
+ Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(nsIScriptSecurityManager);
+
+ try {
+ secMan.checkLoadURIStr(sourceURI, uriStr,
+ nsIScriptSecurityManager.STANDARD);
+ } catch (e) {
+ // Stop event propagation right here.
+ aEvent.stopPropagation();
+
+ throw "Drop of " + aUri + " denied.";
+ }
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="_keyEventHandler" readonly="true">
+ <![CDATA[({
+ tabbrowser: this,
+ handleEvent: function handleEvent(aEvent) {
+ if (!aEvent.isTrusted) {
+ // Don't let untrusted events mess with tabs.
+ return;
+ }
+
+ if (('shiftKey' in aEvent && aEvent.shiftKey) ||
+ ('altKey' in aEvent && aEvent.altKey))
+ return;
+ if (('ctrlKey' in aEvent && aEvent.ctrlKey) &&
+ !('metaKey' in aEvent && aEvent.metaKey)) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
+ this.tabbrowser.mTabBox.handleCtrlPageUpDown) {
+ this.tabbrowser.removeCurrentTab();
+
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ return;
+ }
+ if (aEvent.target.localName == "tabbrowser") {
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_UP:
+ this.tabbrowser.moveTabBackward();
+ break;
+ case KeyEvent.DOM_VK_DOWN:
+ this.tabbrowser.moveTabForward();
+ break;
+ case KeyEvent.DOM_VK_RIGHT:
+ case KeyEvent.DOM_VK_LEFT:
+ this.tabbrowser.moveTabOver(aEvent);
+ break;
+ case KeyEvent.DOM_VK_HOME:
+ this.tabbrowser.moveTabToStart();
+ break;
+ case KeyEvent.DOM_VK_END:
+ this.tabbrowser.moveTabToEnd();
+ break;
+ default:
+ // Stop the keypress event for the above keyboard
+ // shortcuts only.
+ return;
+ }
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+ }
+ }
+ })]]>
+ </field>
+
+ <property name="canFindAgain"
+ onget="return this.mCurrentBrowser.canFindAgain;"
+ readonly="true"/>
+
+ <property name="userTypedClear"
+ onget="return this.mCurrentBrowser.userTypedClear;"
+ onset="return this.mCurrentBrowser.userTypedClear = val;"/>
+
+ <property name="userTypedValue"
+ onget="return this.mCurrentBrowser.userTypedValue;"
+ onset="return this.mCurrentBrowser.userTypedValue = val;"/>
+
+ <property name="forceSyncURLBarUpdate"
+ onget="return this.mModalDialogShowing;"/>
+
+ <method name="createTooltip">
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ event.stopPropagation();
+ var tn = document.tooltipNode;
+ if (tn.localName != "tab")
+ return false; // Not a tab, so cancel the tooltip
+ if ("mOverCloseButton" in tn && tn.mOverCloseButton) {
+ event.target.setAttribute("label", tn.getAttribute("closetabtext"));
+ return true;
+ }
+ if (tn.hasAttribute("label")) {
+ event.target.setAttribute("label", tn.getAttribute("label"));
+ return true;
+ }
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ try{this.mStrip.width= this.mPrefs.getIntPref("browser.tabs.width");}catch(ex){};
+
+ this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild;
+ this.mCurrentTab = this.mTabContainer.firstChild;
+ document.addEventListener("keypress", this._keyEventHandler, false);
+
+ var uniqueId = "panel" + Date.now();
+ this.mPanelContainer.childNodes[0].id = uniqueId;
+ this.mTabContainer.childNodes[0].linkedPanel = uniqueId;
+ this.mTabContainer.childNodes[0]._tPos = 0;
+ this.mTabContainer.childNodes[0].linkedBrowser = this.mPanelContainer.childNodes[0].firstChild;
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ for (var i = 0; i < this.mTabListeners.length; ++i) {
+ this.getBrowserAtIndex(i).webProgress.removeProgressListener(this.mTabFilters[i]);
+ this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
+ this.mTabFilters[i] = null;
+ this.mTabListeners[i] = null;
+ this.getBrowserAtIndex(i).removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
+ }
+ document.removeEventListener("keypress", this._keyEventHandler, false);
+ ]]>
+ </destructor>
+ </implementation>
+
+ <handlers>
+ <handler event="DOMLinkAdded" phase="capturing" action="this.onLinkAdded(event);"/>
+
+ <handler event="DOMWindowClose" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ const browsers = this.mPanelContainer.childNodes;
+ if (browsers.length == 1) {
+ // There's only one browser left. If a window is being
+ // closed and the window is *not* the window in the
+ // browser that's still around, prevent the event's default
+ // action to prevent closing a window that's being closed
+ // already.
+ if (this.getBrowserAtIndex(0).contentWindow != event.target)
+ event.preventDefault();
+
+ return;
+ }
+
+ var i = 0;
+ for (; i < browsers.length; ++i) {
+ if (this.getBrowserAtIndex(i).contentWindow == event.target) {
+ this.removeTab(this.mTabContainer.childNodes[i]);
+ event.preventDefault();
+
+ break;
+ }
+ }
+ ]]>
+ </handler>
+ <handler event="DOMWillOpenModalDialog" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ // We're about to open a modal dialog, make sure the opening
+ // tab is brought to the front.
+
+ var targetTop = event.target.top;
+
+ for (var i = 0; i < browsers.length; ++i) {
+ if (this.getBrowserAtIndex(i).contentWindow == targetTop) {
+ this.mModalDialogShowing = true;
+ this.selectedTab = this.mTabContainer.childNodes[i];
+
+ break;
+ }
+ }
+ ]]>
+ </handler>
+ <handler event="DOMModalDialogClosed" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ this.mModalDialogShowing = false;
+ ]]>
+ </handler>
+ <handler event="resize">
+ <![CDATA[
+ this.mPrefs.setIntPref("browser.tabs.width", this.mStrip.width);
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tabs"
+ extends="chrome://global/content/bindings/tabbox.xml#tabs" >
+ <content>
+ <xul:vbox style="overflow-y: auto; overflow-x: hidden;" anonid="arrowscrollbox" flex="1">
+ <children includes="tab"/>
+ </xul:vbox>
+ </content>
+ <handlers>
+ <!--<handler event="dblclick">
+ BrowserOpenTab();
+ </handler>
+ <handler event="click" button="1">
+ if (event.target==this) undoCloseTab();
+ </handler>-->
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tab" display="xul:box" extends="chrome://global/content/bindings/tabbox.xml#tab">
+ <content chromedir="&locale.dir;">
+ <xul:hbox class="tab-mid box-inherit" xbl:inherits="align,dir,pack,orient,selected" flex="1">
+ <xul:stack flex="1">
+ <xul:hbox><xul:vbox><xul:image class="tab-icon" xbl:inherits="validate,src=image" /></xul:vbox></xul:hbox>
+ <xul:description class="tab-text" anonid="tabtext" xbl:inherits="accesskey,crop,disabled" flex="1" >\(0_0)/</xul:description>
+ </xul:stack>
+ <!--<xul:label class="tab-text" xbl:inherits="value=label,accesskey,crop,disabled" flex="1"/>-->
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <field name="mCorrespondingMenuitem">null</field>
+ <constructor>
+
+ </constructor>
+ <method name="textinit">
+ <body>
+ document.getAnonymousElementByAttribute(this,'anonid','tabtext').textContent= this.label;
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="DOMAttrModified"><![CDATA[ if (event.attrName=='label') this.textinit(); ]]></handler>
+ </handlers>
+
+ </binding>
+
+</bindings>