0

The question: In Firefox WebExtensions, from arbitrary background origins, how can I open an arbitrary page in a tab, uniquely?

Requirements:

1) Arbitrary background origins. My initial use case is from browser_action contextMenus. However, the technique ought to work from popup, options, or any custom background script. For the moment, not concerned with content pages. Although if the technique works for them too, great.

2) Open an arbitrary page in a tab. My initial use is for the options page, but the technique should work for arbitrary background pages (options, popup, or custom pages).

3) Open uniquely. If it's the first time, open in a new tab. If the tab was previously opened, focus to the existing tab, don't create duplicate tabs.

4) If I close a tab, I need to make sure I remove my reference to the previously opened tab ID.

I've answered my own question with one possible solution.

If the solution could be improved upon, let me know. Thank you.

If the question is a duplicate, I'll remove and post solution elsewhere if appropriate.

  • You are asking multiple questions: 1) Pass a parameter to a tab you are opening (there is a way to do this which is, in my opinion, easier than what you have described and can work for both pages in the background context and content scripts); 2) Determine if a tab containing the desired URL is open, if so focus, if not, open it; and 3) [Generally send messages between pages in the background context](http://stackoverflow.com/a/41420772/3773011). These three things are separate Questions. Please separate them out into [different Questions](http://stackoverflow.com/questions/ask). – Makyen Jan 04 '17 at 21:25
  • When I wrote this question, I suppose I was just collecting my thoughts, and what I thought was one question turned into a few. Will edit and create other questions. –  Jan 10 '17 at 18:30
  • No problem, I know how that can go. Officially, I should have voted to close this question as too broad (one issue per question). I believe that this can be good question(s), if more tightly focused. I do have ideas on how to do 1 & 2. For 1, I have an idea about how I would do it. Your outlined method should also work, and may be most appropriate, depending on what else you are doing. For 2, I have partial code which I have used for other things. For 3, obviously, I have already supplied the linked answer. Please ping me when you update the question and/or write up additional ones. – Makyen Jan 10 '17 at 19:45
  • This is my attempt to answer the first and (to me, easiest) part of my question. Opening a unique tab, switching to it, closing it. This technique should supersede using `browser.runtime.openOptionsPage()` as it is more general and more powerful. Goodbye training wheels. :D –  Jan 10 '17 at 19:51
  • Ah, I did neglect to handle the case for multiple windows, and the previously opened tab may not be in the top most visible window, so I need to ind window of other tab, pull that window to focus, then change active tab. According to docs, simply changing active tab may not get window focus. I need a break and will get back to that feature later. :p I think the method of direct-sending message to other page will work best for my simple use case to pass a single integer or perhaps a JSON style object . It should even work after the `await browser.tabs.create` ... will try that later. –  Jan 10 '17 at 20:04

1 Answers1

0

This is an attempt to answer my own question, with comments inline.

This simple example assumes all pagers live in the top level directory, but this is arbitrary and easily changed.

Essentially it consists of four parts:

1) A global object tabIDs to hold the page names (without '.html'). You could change this to be full path including extension, or keep the page name as a short name and modify technique to use another option like path for the full path name, etc.

2) An async function (to make use of the new await feature) named tabCreate to determine if one is already open and switch to it or create a new tab.

3) An onRemoved event handler, tabRemove to handle cleanup of tabIDs after a tab is closed (if it was one of interest).

4) A usage example, from a context menu item, passing some a page and a panel option, which have no use in this question, but are part of another question, and just here for demonstration purposes.

background.js:

// tabID contexts
//     global var to keep track of different tabs,
//     i.e. options.html, popup.html and so on.

var tabIDs = {
    'options': null,
    'popup': null,
}

// Requires Firefox 52.0+ to use async/await
//     opts correspond to contexts above in tabIDs
//     of the form { 'page': 'options' } or { 'page': 'popup' }
//     note if using Node.js, this may require v7+ and --harmony_async_await
async function tabCreate ( opts ) {
    var tab;

    if ( tabIDs[ opts.page ] !== null ) {
        // should probably bring window to front first... oops
        // ..
        // switch to tab
        tab = await browser.tabs.update( tabIDs[ opts.page ], { active: true } );
    } else {
        tab = await browser.tabs.create( {
            'url': opts.page + '.html'
        } );

        tabIDs[ opts.page ] = tab.id;
    }

    console.log( '**** opts.page = ' + opts.page + ', opts.tab = ' + opts.tab + ', tab.id = ' + tab.id );
}

// When tabs are closed, see if the tabID is in tabIDs,
//     and if so, set it to null
function tabRemove ( tabID, removeInfo ) {
console.log( 'Closed TAB ' + tabID );

    Object.keys( tabIDs ).forEach( function( key, index ) {
        if ( tabIDs[ key ] === tabID ) {
            tabIDs[ key ] = null;
            return;
        }
    } );
}
browser.tabs.onRemoved.addListener( tabRemove );

/*
 * Context Menus
 */

browser.contextMenus.removeAll( );

browser.contextMenus.create( {
    title: 'My Web Extension',
    contexts: [ 'browser_action' ],
} );

browser.contextMenus.create( {
    title: 'Options',
    contexts: [ 'browser_action' ],
    onclick: myWebExt_Options
} );

function myWebExt_Options ( ) {
    tabCreate( {
        'page': 'options',
        'panel': 1,
    } );
}

Another approach might be to add an event listener to each page, and when closed, send a message back to background.js, but that seems much more complicated with little or no benefit.

  • What if the user has bookmarked the page and previously opened it independent of your `tabCreate()`? – Makyen Jan 10 '17 at 20:09
  • Hmm, I hadn't considered that possibility, as my use case was restricted to internal page of my add-on, specifically to open pages from context menu. I could adjust answer to match the solution. Hmm, not sure how I'd handle the case of externally opened pages, or bookmarks to add-on. If no tabIDs found to match, then maybe look at tab.url, maybe expand my global object to keep track of the short name tab.id, tab.url. Hmm, oh, or handle the record creations in a tab.onCreated event handler ... but I don't want to keep track of everyone else's tabs, just my own extension's. ^_^ –  Jan 10 '17 at 20:24
  • You don't need to track any tabs. You can directly obtain a list of the window objects for pages (tabs, panels, the background page, options pages, etc.) that are currently open in the background context of your extension from [`extension.getViews()`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/extension/getViews). From the Window object you can determine to which URL the page is open. – Makyen Jan 10 '17 at 20:40
  • Oh, didn't know about getViews. A user can sit there all day opening the same bookmark on all tabs, not sure how I can prevent that from happening for just my add-on. But after 1+ of such pages are created, I could use getViews, and getURL on my page, prevent any of my add-on menu items (etc) from adding more. But then how to I switch to the existing tab? That requires tabs.update, and that requires a tab.id, which does not exist in the Windows objects returned by getViews. I must keep track of tab ids and thus must track pages myself, meaning getViews is useless to me. I misunderstood? –  Jan 11 '17 at 13:59
  • Neither can I use browser.tabs.query as the url uses a match pattern which specifically disallows URIs with the moz-extension:// scheme. A bit confused how I am meant to retrieve a tab ID. Window.focus() won't focus a tab in a single window, nor even raise a background window if multiple browser windows are open. getViews can only help me find any of my add-on pages opened externally, by manually entering, or bookmark to choose not to open a new page. Then I must rely on my tab ID state data to focus the tab. –  Jan 12 '17 at 13:25
  • Created a [question](http://stackoverflow.com/questions/41635692/given-a-moz-extension-url-opened-by-bookmark-how-can-i-switch-to-tab-using-e) to handle this specific bookmark case more elegantly. The solution lies in using `browser.windows.getAll()` and populating the object with tabs. –  Jan 13 '17 at 15:47