Pass info,tab through Shortkey/command listener like with a context menu listener

What do you get if you temporarily change the line to

code: `console.log('SELECTION: ' + typeof window.getSelection().toString());`

Then I’ll get:

SELECTION: string

No matter which string I select on the page, result is always the same!

Well, that’s better than null or undefined, but not much help.

I’m guessing because the text is in a child element you need to use selectAllChildren()

eg. maybe

code: `console.log('SELECTION: ' + window.getSelection().selectAllChildren().toString());`

That doesn’t work. Console.log says that it need an argument:

Error: Not enough arguments to Selection.selectAllChildren.

Doc says:

footer = document.getElementById("footer");
window.getSelection().selectAllChildren(footer);

But I really have no idea what to do with document.getElementById

Pretty sure selectAllChildren changes the selection, especially since it says “Previous selection is lost”.

I hinted at this before, but are you making the text selection in the same frame that you call window.getSelection().toString() in? If not, the returned string is expected to be empty.

Also, console.log() acceprs variable arguments and logs them all, you shouldn’t explicitly + them, which casts everything to strings (at least if one of them is a string).

1 Like

Gosh… this was (for me) so much easier with SDK. The extension worked well without having to worry about different frames, code injection and all that.

So let me try to understand this…
A html page can have multiple frames… Top and child frames.
The shortcut and its selection works on a regular html page because it doesn’t have child frames… there is just a top frame!?

However, on my icinga page there are child frames, so making the selection on the same frame (the top frame?) would return an empty string because the actual selection is on a child frame?

How can I access those child frames?
I read that content scripts can be injected into all frames using something like:

"content_scripts": [
    {
      "js": ["my-script.js"],
      "matches": ["https://example.org/"],
      "match_about_blank": true,
      "all_frames": true
    }
  ]

My extension consist of a manifest.json and a background script icinga_addon.js.

{
  "manifest_version": 2,
  "name": "Inc Icinga Shortcuts",
  "version": "1.0",
  "description": "Icinga Shortcuts",
  "permissions": ["contextMenus","activeTab"],
  
  "commands": {
    "1 Host Connect": {
      "suggested_key": {"default": "Alt+1" } ,
      "description": "1 Host Connect"
    }
   },
  
  "background":  {
      "scripts": ["icinga_addon.js"] 
    }
}

How could I implement this?

Many thanks in advance… really appreciate it!!

Yes, that is a pretty precise description for a possible, and given the type of page you are working on I think likely, cause of what you encounter here.

How can I access those child frames?

You want to access the selected text in the background page, right?
Haven’t tested it, but this should work:

const pageSelection = (await Promise.all(
    (await browser.webNavigation.getAllFrames({ tabId, }))
    .map(({ frameId, }) => 
        browser.tabs.executeScript(tabId, { frameId, code: `window.getSelection().toString()`, })
        .catch(_ => '') //in production, avoid weird errors about uninitialized frames and stuff like that, but when debugging empty selection texts again, comment this line
    )
)).reduce((a, b) => a || b, '') || '';

You wouldn’t want static content_scripts in the manifest for this.

1 Like

Thx Niklas,

Couldn’t make it work yet. Debugger always claims that there is a missing ) in line

const pageSelection = (await Promise.all(

Don’t understand why yet. Will check it next thing tomorrow morning though…

The error message isn’t that helpful. You have to putthe code in an async function:

browser.commands.onCommand.addListener(async command => { switch (command) {
    case '1 Host Connect': {
        const { id: windowId, } = (await browser.windows.getLastFocused());
        const { id: tabId, } = (await (browser.tabs.query({ active: true, windowId, })))[0];
        const pageSelection = (await Promise.all(
            (await browser.webNavigation.getAllFrames({ tabId, }))
            .map(({ frameId, }) => 
                browser.tabs.executeScript(tabId, { frameId, code: `window.getSelection().toString()`, })
                .catch(_ => '') // in production, avoid weird errors about uninitialized frames and stuff like that, but when debugging empty selection texts again, comment this line
            )
        )).reduce((a, b) => a || b, '') || '';
        // use `pageSelection`
    } break;
    // other commands
} });

Again, net tested, but it is syntactically correct and should work.

1 Like

Hi Niklas thank you once again!! I’m not getting an syntax errors anymore… but everything after const pageSelection doesn’t seem to fire…
If I simply implement a
console.log('End host connect');
for
// use pageSelection
I’ll get nothing in the console… noerror either. Any idea?

Huh. Not really. I’d throw a bunch of console.logs in there and see what happens:

code with logging
browser.commands.onCommand.addListener(async command => { switch (command) {
    case '1 Host Connect': {
        const { id: windowId, } = (await browser.windows.getLastFocused());
        console.log(`windowId`, windowId);
        const { id: tabId, } = (await (browser.tabs.query({ active: true, windowId, })))[0];
        console.log(`tabId`, tabId);
        const pageSelection = (await Promise.all(
            (await browser.webNavigation.getAllFrames({ tabId, }))
            .map(async ({ frameId, }) => { try {
                console.log(`Checking frame ${frameId} in tab ${tabId} ...`);
                const selected = (await browser.tabs.executeScript(tabId, { frameId, code: `window.getSelection().toString()`, }));
                console.log(`Selection in frame ${frameId} in tab ${tabId}:`, selection);
                return selection;
            } catch (_) { console.error(_); return ''; } }) // in production, avoid weird errors about uninitialized frames and stuff like that, but when debugging empty selection texts again, comment this line
        )).reduce((a, b) => a || b, '') || '';
        console.info(`Selection in some frame in the currently active tab ${tabId}`: pageSelection);
        // use `pageSelection`
    } break;
    // other commands
} });
1 Like

It’s really odd…
Implementing your code I only get:

windowId 5 icinga_addon.js:672:9
tabId 2 icinga_addon.js:674:9

Thought this is some weird Icinga thing again, but even on a regular html page I can’t get more output.

Sometimes the error reporting in Firefox really sucks. Try it with a try ... catch:

code with logging and catch
browser.commands.onCommand.addListener(async command => { try { switch (command) {
    case '1 Host Connect': {
        const { id: windowId, } = (await browser.windows.getLastFocused());
        console.log(`windowId`, windowId);
        const { id: tabId, } = (await (browser.tabs.query({ active: true, windowId, })))[0];
        console.log(`tabId`, tabId);
        const pageSelection = (await Promise.all(
            (await browser.webNavigation.getAllFrames({ tabId, }))
            .map(async ({ frameId, }) => { try {
                console.log(`Checking frame ${frameId} in tab ${tabId} ...`);
                const selected = (await browser.tabs.executeScript(tabId, { frameId, code: `window.getSelection().toString()`, }));
                console.log(`Selection in frame ${frameId} in tab ${tabId}:`, selection);
                return selection;
            } catch (_) { console.error(_); return ''; } }) // in production, avoid weird errors about uninitialized frames and stuff like that, but when debugging empty selection texts again, comment this line
        )).reduce((a, b) => a || b, '') || '';
        console.info(`Selection in some frame in the currently active tab ${tabId}`: pageSelection);
        // use `pageSelection`
    } break;
    // other commands
} catch (error) { console.error(`onCommand error`, error); } });
1 Like

Well… that’s frustrating - for you probably more than for me (thx again btw)… I’ll get the following:

windowId 5 icinga_addon.js:699:9
tabId 15 icinga_addon.js:701:9
onCommand error <unavailable> icinga_addon.js:715:20 

If you aren’t using the extension debugger already (about:debugging), see if that displays more than just the <unavailable>.
If it doesn’t, set a breakpoint in the line with the console.error (you may have to put it on its own line for that to work in Firefox).

1 Like

Thx for the hint… had no idea that I need to turn on debugging specifically. Thought that the console would give me that output already -.-

I made progress however…
My manifest.json missed two permission, webNavigation and Host. Altered selection variable to selected. This is what the code looks like now:

browser.commands.onCommand.addListener(async command => { try { switch (command) {
    case '1 Host Connect': {
        const { id: windowId, } = (await browser.windows.getLastFocused());
        console.log(`windowId`, windowId);
        const { id: tabId, } = (await (browser.tabs.query({ active: true, windowId, })))[0];
        console.log(`tabId`, tabId);
        const pageSelection = (await Promise.all(
            (await browser.webNavigation.getAllFrames({ tabId, }))
            .map(async ({ frameId, }) => { try {
                console.log(`Checking frame ${frameId} in tab ${tabId} ...`);
                const selected = (await browser.tabs.executeScript(tabId, { frameId, code: `window.getSelection().toString()`, }));
                console.log(`Selection in frame ${frameId} in tab ${tabId}:`, selected);
                return selected;
            } catch (_) { console.error(_); return ''; } }) // in production, avoid weird errors about uninitialized frames and stuff like that, but when debugging empty selection texts again, comment this line
        )).reduce((a, b) => a || b, '') || '';
        console.info(`Selection in some frame in the currently active tab ${tabId}:`, pageSelection);
        // use `pageSelection`
    } break;
    // other commands
}} catch (error) { console.error(`onCommand error`, error); }
	});

This gives me the following output:

windowId 5
Webconsole context has changed
tabId 1 
Checking frame 0 in tab 1 ... 
Checking frame 8589934596 in tab 1 ... 
Checking frame 8589934597 in tab 1 ... 
Checking frame 8589934598 in tab 1 ... 
Selection in frame 0 in tab 1: Array [ "" ]
Selection in frame 8589934596 in tab 1:Array [ "" ]
Selection in frame 8589934597 in tab 1: Array [ "" ]
Selection in frame 8589934598 in tab 1: Array [ "\r\n\t\r\n\t\r\nSONE_SONEPINT" ]
Selection in some frame in the currently active tab 1: Array [ "" ]

So it seems to work now… I’m just not getting the selection of the frame I want to as it seems…

If I select a string on a regular html page, not the icinga page, it works though, since it only has 1 frame:

tabId 3 
Checking frame 0 in tab 3 ... 
Selection in frame 0 in tab 3: Array [ "wikipedia " ]
Selection in some frame in the currently active tab 3: Array [ "wikipedia " ]

:frowning:

Ok, so I forgot that tabs.executeScript() actually returns arrays with the results, because there is an allFrames option which leads to multiple results at once. Using that should also simplify the code quite a bit (but this simpler version doesn’t allow the catch per frame, which may be a problem). You wouldn’t even really need the tabId anymore:

browser.commands.onCommand.addListener(async command => { try { switch (command) {
    case '1 Host Connect': {
        const { id: windowId, } = (await browser.windows.getLastFocused());
        console.log(`windowId`, windowId);
        const { id: tabId, } = (await (browser.tabs.query({ active: true, windowId, })))[0];
        console.log(`tabId`, tabId);
        const pageSelection = (await browser.tabs.executeScript(tabId, {
            allFrames: true, code: `window.getSelection().toString()`,
        })).reduce((a, b) => a || b, '') || '';
        console.info(`Selection in some frame in the currently active tab ${tabId}:`, pageSelection);
        // use `pageSelection`
    } break;
    // other commands
}} catch (error) {
    console.error(`onCommand error`, error); }
});
1 Like

You my friend… are awesome!!!
Works perfectly now!
I’ll implement this now in the rest of my code, but that should do the trick…

Thanks a lot again!!

1 Like