diff --git a/platform/firefox/install.rdf b/platform/firefox/install.rdf
index 38337c2ee..56cac0fc0 100644
--- a/platform/firefox/install.rdf
+++ b/platform/firefox/install.rdf
@@ -23,6 +23,15 @@
+
+
+
+ {{aa3c5121-dab2-40e2-81ca-7ea25febc110}}
+ 24.0
+ 38.0
+
+
+
diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js
index 92a3cd8fe..e07b753ee 100644
--- a/platform/firefox/vapi-background.js
+++ b/platform/firefox/vapi-background.js
@@ -41,6 +41,10 @@ var vAPI = self.vAPI = self.vAPI || {};
vAPI.firefox = true;
+vAPI.fennec = Components.classes["@mozilla.org/xre/app-info;1"]
+ .getService(Components.interfaces.nsIXULAppInfo)
+ .ID == "{aa3c5121-dab2-40e2-81ca-7ea25febc110}";
+
/******************************************************************************/
vAPI.app = {
@@ -262,16 +266,23 @@ var windowWatcher = {
return;
}
- if ( !this.gBrowser || !this.gBrowser.tabContainer ) {
- return;
+ if ( this.gBrowser && this.gBrowser.tabContainer ) {
+ // desktop Firefox
+ var tC = this.gBrowser.tabContainer;
+
+ this.gBrowser.addTabsProgressListener(tabWatcher);
+ tC.addEventListener('TabClose', tabWatcher.onTabClose);
+ tC.addEventListener('TabSelect', tabWatcher.onTabSelect);
+
+ } else if ( this.BrowserApp && this.BrowserApp.deck ) {
+ // Fennec
+ var deck = this.BrowserApp.deck;
+
+ deck.addEventListener('DOMTitleChanged', tabWatcher.onFennecLocationChange);
+ deck.addEventListener('TabClose', tabWatcher.onTabClose);
+ deck.addEventListener('TabSelect', tabWatcher.onTabSelect);
}
- var tC = this.gBrowser.tabContainer;
-
- this.gBrowser.addTabsProgressListener(tabWatcher);
- tC.addEventListener('TabClose', tabWatcher.onTabClose);
- tC.addEventListener('TabSelect', tabWatcher.onTabSelect);
-
vAPI.contextMenu.register(this.document);
// when new window is opened TabSelect doesn't run on the selected tab?
@@ -294,7 +305,14 @@ var tabWatcher = {
},
onTabSelect: function({target: tab}) {
- var URI = tab.linkedBrowser.currentURI;
+ var URI = null;
+ if ( tab.currentURI ) {
+ // on Fennec the target is actually the linked browser
+ URI = tab.currentURI;
+ } else {
+ // desktop Firefox
+ URI = tab.linkedBrowser.currentURI;
+ }
var aboutPath = URI.schemeIs('about') && URI.path;
var tabId = vAPI.tabs.getTabId(tab);
@@ -334,7 +352,25 @@ var tabWatcher = {
tabId: tabId,
url: location.asciiSpec
});
- }
+ },
+
+ onFennecLocationChange: function(e) {
+ // Fennec "equivalent" to onLocationChange
+ // note that DOMTitleChanged is selected as it fires very early
+ // (before DOMContentLoaded), and it does fire even if there is no title
+
+ var tabId = vAPI.tabs.getTabId(e.target);
+ if ( tabId === -1 ) {
+ // probably not top level
+ return;
+ }
+
+ vAPI.tabs.onNavigation({
+ frameId: 0,
+ tabId: tabId,
+ url: e.target.location.href
+ });
+ },
};
/******************************************************************************/
@@ -367,20 +403,39 @@ vAPI.tabs.registerListeners = function() {
for ( var win of vAPI.tabs.getWindows() ) {
vAPI.contextMenu.unregister(win.document);
-
win.removeEventListener('DOMContentLoaded', windowWatcher.onReady);
- win.gBrowser.removeTabsProgressListener(tabWatcher);
- var tC = win.gBrowser.tabContainer;
- tC.removeEventListener('TabClose', tabWatcher.onTabClose);
- tC.removeEventListener('TabSelect', tabWatcher.onTabSelect);
+ if ( win.gBrowser && win.gBrowser.tabContainer ) {
+ // desktop Firefox
+ var tC = win.gBrowser.tabContainer;
- // close extension tabs
- for ( var tab of win.gBrowser.tabs ) {
- var URI = tab.linkedBrowser.currentURI;
+ win.gBrowser.removeTabsProgressListener(tabWatcher);
+ tC.removeEventListener('TabClose', tabWatcher.onTabClose);
+ tC.removeEventListener('TabSelect', tabWatcher.onTabSelect);
- if ( URI.schemeIs('chrome') && URI.host === location.host ) {
- win.gBrowser.removeTab(tab);
+ // close extension tabs
+ for ( var tab of win.gBrowser.tabs ) {
+ var URI = tab.linkedBrowser.currentURI;
+
+ if ( URI.schemeIs('chrome') && URI.host === location.host ) {
+ win.gBrowser.removeTab(tab);
+ }
+ }
+ } else if ( win.BrowserApp && win.BrowserApp.deck ) {
+ // Fennec
+ var deck = win.BrowserApp.deck;
+
+ deck.removeEventListener('DOMTitleChanged', tabWatcher.onFennecLocationChange);
+ deck.removeEventListener('TabClose', tabWatcher.onTabClose);
+ deck.removeEventListener('TabSelect', tabWatcher.onTabSelect);
+
+ // close extension tabs
+ for ( var tab of win.BrowserApp.tabs ) {
+ var URI = tab.browser.currentURI;
+
+ if ( URI.schemeIs('chrome') && URI.host === location.host ) {
+ win.BrowserApp.closeTab(tab);
+ }
}
}
}
@@ -390,53 +445,97 @@ vAPI.tabs.registerListeners = function() {
/******************************************************************************/
vAPI.tabs.getTabId = function(target) {
- if ( target.linkedPanel ) {
- return target.linkedPanel;
- }
+ if ( vAPI.fennec ) {
+ // Fennec
- var i, gBrowser = target.ownerDocument.defaultView.gBrowser;
+ if ( target.browser ) {
+ // target is a tab
+ return target.id;
+ }
- if ( !gBrowser ) {
+ // target is a browser or contentDocument
+ for ( var win of vAPI.tabs.getWindows() ) {
+ for ( var tab of win.BrowserApp.tabs ) {
+ if ( target === tab.browser ||
+ target === tab.window.document ) {
+ return tab.id;
+ }
+ }
+ }
return -1;
+
+ } else {
+ // desktop Firefox
+
+ if ( target.linkedPanel ) {
+ // target is a tab
+ return target.linkedPanel;
+ }
+
+ // target is a browser
+ var i, gBrowser = target.ownerDocument.defaultView.gBrowser;
+
+ if ( !gBrowser ) {
+ return -1;
+ }
+
+ // This should be more efficient from version 35
+ if ( gBrowser.getTabForBrowser ) {
+ i = gBrowser.getTabForBrowser(target);
+ return i ? i.linkedPanel : -1;
+ }
+
+ if ( !gBrowser.browsers ) {
+ return -1;
+ }
+
+ i = gBrowser.browsers.indexOf(target);
+
+ if ( i !== -1 ) {
+ i = gBrowser.tabs[i].linkedPanel;
+ }
+
+ return i;
}
-
- // This should be more efficient from version 35
- if ( gBrowser.getTabForBrowser ) {
- i = gBrowser.getTabForBrowser(target);
- return i ? i.linkedPanel : -1;
- }
-
- if ( !gBrowser.browsers ) {
- return -1;
- }
-
- i = gBrowser.browsers.indexOf(target);
-
- if ( i !== -1 ) {
- i = gBrowser.tabs[i].linkedPanel;
- }
-
- return i;
};
/******************************************************************************/
vAPI.tabs.get = function(tabId, callback) {
- var tab, windows;
+ var tab, windows, win;
if ( tabId === null ) {
- tab = Services.wm.getMostRecentWindow('navigator:browser').gBrowser.selectedTab;
+ win = Services.wm.getMostRecentWindow('navigator:browser');
+ if ( win.gBrowser ) {
+ // desktop Firefox
+ tab = win.gBrowser.selectedTab;
+ } else if ( win.BrowserApp ) {
+ // Fennec
+ tab = win.BrowserApp.selectedTab;
+ }
tabId = vAPI.tabs.getTabId(tab);
} else {
windows = this.getWindows();
- for ( var win of windows ) {
- tab = win.gBrowser.tabContainer.querySelector(
- 'tab[linkedpanel="' + tabId + '"]'
- );
+ if ( vAPI.fennec ) {
+ // Fennec
+ for ( win of windows ) {
+ tab = win.BrowserApp.getTabForId(tabId);
- if ( tab ) {
- break;
+ if ( tab ) {
+ break;
+ }
+ }
+ } else {
+ // desktop Firefox
+ for ( win of windows ) {
+ tab = win.gBrowser.tabContainer.querySelector(
+ 'tab[linkedpanel="' + tabId + '"]'
+ );
+
+ if ( tab ) {
+ break;
+ }
}
}
}
@@ -451,21 +550,41 @@ vAPI.tabs.get = function(tabId, callback) {
return;
}
- var browser = tab.linkedBrowser;
- var gBrowser = browser.ownerDocument.defaultView.gBrowser;
+ if ( tab.linkedBrowser ) {
+ // desktop Firefox
+ var browser = tab.linkedBrowser;
+ var gBrowser = win.gBrowser;
- if ( !windows ) {
- windows = this.getWindows();
+ if ( !windows ) {
+ windows = this.getWindows();
+ }
+
+ callback({
+ id: tabId,
+ index: gBrowser.browsers.indexOf(browser),
+ windowId: windows.indexOf(win),
+ active: tab === gBrowser.selectedTab,
+ url: browser.currentURI.asciiSpec,
+ title: tab.label
+ });
+ } else if ( tab.browser ) {
+ // Fennec
+ var browser = tab.browser;
+ var BrowserApp = win.BrowserApp;
+
+ if ( !windows ) {
+ windows = this.getWindows();
+ }
+
+ callback({
+ id: tabId,
+ index: BrowserApp.tabs.indexOf(tab),
+ windowId: windows.indexOf(win),
+ active: tab === BrowserApp.selectedTab,
+ url: browser.currentURI.asciiSpec,
+ title: browser.contentDocument.title
+ });
}
-
- callback({
- id: tabId,
- index: gBrowser.browsers.indexOf(browser),
- windowId: windows.indexOf(browser.ownerDocument.defaultView),
- active: tab === gBrowser.selectedTab,
- url: browser.currentURI.asciiSpec,
- title: tab.label
- });
};
/******************************************************************************/
@@ -478,7 +597,16 @@ vAPI.tabs.getAll = function(window) {
continue;
}
- for ( tab of win.gBrowser.tabs ) {
+ var tabList;
+ if ( win.gBrowser ) {
+ // desktop Firefox
+ tabList = win.gBrowser.tabs;
+ } else {
+ // Fennec
+ tabList = win.BrowserApp.tabs;
+ }
+
+ for ( tab of tabList ) {
tabs.push(tab);
}
}
@@ -528,11 +656,25 @@ vAPI.tabs.open = function(details) {
tabs = this.getAll();
for ( tab of tabs ) {
- var browser = tab.linkedBrowser;
+ var browser;
+ if ( tab.linkedBrowser ) {
+ // desktop Firefox
+ browser = tab.linkedBrowser;
+ } else {
+ // Fennec
+ browser = tab.browser;
+ }
// Or simply .equals if we care about the fragment
if ( URI.equalsExceptRef(browser.currentURI) ) {
- browser.ownerDocument.defaultView.gBrowser.selectedTab = tab;
+ var win = browser.ownerDocument.defaultView;
+ if ( win.gBrowser ) {
+ // desktop Firefox
+ win.gBrowser.selectedTab = tab;
+ } else {
+ // Fennec
+ win.BrowserApp.selectTab(tab);
+ }
return;
}
}
@@ -542,27 +684,52 @@ vAPI.tabs.open = function(details) {
details.active = true;
}
- var gBrowser = Services.wm.getMostRecentWindow('navigator:browser').gBrowser;
+ var win = Services.wm.getMostRecentWindow('navigator:browser');
+ if ( win.gBrowser ) {
+ // desktop Firefox
+ var gBrowser = win.gBrowser;
- if ( details.index === -1 ) {
- details.index = gBrowser.browsers.indexOf(gBrowser.selectedBrowser) + 1;
- }
+ if ( details.index === -1 ) {
+ details.index = gBrowser.browsers.indexOf(gBrowser.selectedBrowser) + 1;
+ }
- if ( details.tabId ) {
- tabs = tabs || this.getAll();
+ if ( details.tabId ) {
+ tabs = tabs || this.getAll();
- for ( tab of tabs ) {
- if ( vAPI.tabs.getTabId(tab) === details.tabId ) {
- tab.linkedBrowser.loadURI(details.url);
+ for ( tab of tabs ) {
+ if ( vAPI.tabs.getTabId(tab) === details.tabId ) {
+ tab.linkedBrowser.loadURI(details.url);
+ return;
+ }
+ }
+ }
+
+ tab = gBrowser.loadOneTab(details.url, {inBackground: !details.active});
+
+ if ( details.index !== undefined ) {
+ gBrowser.moveTabTo(tab, details.index);
+ }
+ } else {
+ // Fennec
+ var BrowserApp = win.BrowserApp;
+
+ if ( details.index === -1 ) {
+ details.index = BrowserApp.tabs.indexOf(gBrowser.selectedTab) + 1;
+ }
+
+ if ( details.tabId ) {
+ tabs = tabs || this.getAll();
+
+ tab = BrowserApp.getTabForId(details.tabId);
+ if ( tab ) {
+ tab.browser.loadURI(details.url);
return;
}
}
- }
- tab = gBrowser.loadOneTab(details.url, {inBackground: !details.active});
+ tab = BrowserApp.addTab(details.url, {selected: details.active});
- if ( details.index !== undefined ) {
- gBrowser.moveTabTo(tab, details.index);
+ // note that it's impossible to move tabs on Fennec, so don't bother
}
};
@@ -573,19 +740,35 @@ vAPI.tabs.remove = function(tabIds) {
tabIds = [tabIds];
}
- tabIds = tabIds.map(function(tabId) {
- return 'tab[linkedpanel="' + tabId + '"]';
- }).join(',');
+ if ( vAPI.fennec ) {
+ // Fennec
+ for ( var win of this.getWindows() ) {
+ var tabs = win.BrowserApp.tabs;
+ if ( !tabs ) {
+ continue;
+ }
- for ( var win of this.getWindows() ) {
- var tabs = win.gBrowser.tabContainer.querySelectorAll(tabIds);
-
- if ( !tabs ) {
- continue;
+ for ( var tab of tabs ) {
+ if ( tab.id in tabIds ) {
+ win.BrowserApp.closeTab(tab);
+ }
+ }
}
+ } else {
+ // desktop Firefox
+ tabIds = tabIds.map(function(tabId) {
+ return 'tab[linkedpanel="' + tabId + '"]';
+ }).join(',');
- for ( var tab of tabs ) {
- win.gBrowser.removeTab(tab);
+ for ( var win of this.getWindows() ) {
+ var tabs = win.gBrowser.tabContainer.querySelectorAll(tabIds);
+ if ( !tabs ) {
+ continue;
+ }
+
+ for ( var tab of tabs ) {
+ win.gBrowser.removeTab(tab);
+ }
}
}
};
@@ -596,7 +779,13 @@ vAPI.tabs.reload = function(tabId) {
var tab = this.get(tabId);
if ( tab ) {
- tab.ownerDocument.defaultView.gBrowser.reloadTab(tab);
+ if ( tab.browser ) {
+ // Fennec
+ tab.browser.reload();
+ } else {
+ // desktop Firefox
+ tab.ownerDocument.defaultView.gBrowser.reloadTab(tab);
+ }
}
};
@@ -638,8 +827,17 @@ vAPI.setIcon = function(tabId, iconStatus, badge) {
var win = badge === undefined
? iconStatus
: Services.wm.getMostRecentWindow('navigator:browser');
- var curTabId = vAPI.tabs.getTabId(win.gBrowser.selectedTab);
var tb = vAPI.toolbarButton;
+ var selectedTab, curTabId;
+
+ if ( win.gBrowser ) {
+ // desktop Firefox
+ selectedTab = win.gBrowser.selectedTab;
+ } else {
+ // Fennec
+ selectedTab = win.BrowserApp.selectedTab;
+ }
+ curTabId = vAPI.tabs.getTabId(selectedTab);
// from 'TabSelect' event
if ( tabId === undefined ) {
@@ -1503,15 +1701,29 @@ vAPI.contextMenu.register = function(doc) {
return;
}
- var contextMenu = doc.getElementById('contentAreaContextMenu');
- var menuitem = doc.createElement('menuitem');
- menuitem.setAttribute('id', this.menuItemId);
- menuitem.setAttribute('label', this.menuLabel);
- menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon16.svg'));
- menuitem.setAttribute('class', 'menuitem-iconic');
- menuitem.addEventListener('command', this.onCommand);
- contextMenu.addEventListener('popupshowing', this.displayMenuItem);
- contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator'));
+ if ( vAPI.fennec ) {
+ // Fennec
+ // TODO
+ /*
+ var nativeWindow = doc.defaultView.NativeWindow;
+ contextId = nativeWindow.contextmenus.add(
+ this.menuLabel,
+ nativeWindow.contextmenus.linkOpenableContext, // TODO https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/NativeWindow/contextmenus/add
+ this.onCommand);
+ */
+ } else {
+ // desktop Firefox
+
+ var contextMenu = doc.getElementById('contentAreaContextMenu');
+ var menuitem = doc.createElement('menuitem');
+ menuitem.setAttribute('id', this.menuItemId);
+ menuitem.setAttribute('label', this.menuLabel);
+ menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon16.svg'));
+ menuitem.setAttribute('class', 'menuitem-iconic');
+ menuitem.addEventListener('command', this.onCommand);
+ contextMenu.addEventListener('popupshowing', this.displayMenuItem);
+ contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator'));
+ }
};
/******************************************************************************/
@@ -1521,11 +1733,18 @@ vAPI.contextMenu.unregister = function(doc) {
return;
}
- var menuitem = doc.getElementById(this.menuItemId);
- var contextMenu = menuitem.parentNode;
- menuitem.removeEventListener('command', this.onCommand);
- contextMenu.removeEventListener('popupshowing', this.displayMenuItem);
- contextMenu.removeChild(menuitem);
+ if ( vAPI.fennec ) {
+ // Fennec
+ // TODO
+ } else {
+ // desktop Firefox
+
+ var menuitem = doc.getElementById(this.menuItemId);
+ var contextMenu = menuitem.parentNode;
+ menuitem.removeEventListener('command', this.onCommand);
+ contextMenu.removeEventListener('popupshowing', this.displayMenuItem);
+ contextMenu.removeChild(menuitem);
+ }
};
/******************************************************************************/