0
votes

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.

1
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. These three things are separate Questions. Please separate them out into different Questions.Makyen
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.user2895783
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
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. :Duser2895783
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.user2895783

1 Answers

0
votes

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.