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

So the page debugger shows 'SELECTION: ' but not the text you selected in the top frame of that tab? That would be odd …

1 Like

Exactly!! Output is:

HOTKEY 
1 Host Connect 
SELECTION:

Ok… So it actually does works just not where I need it to work!
If I execute the code via shortcut on a regular html page it works fine, the string is shown.
But I’ll be using the extension with an icinga2 (https://github.com/Icinga/icinga2) webpage. That’s where I was testing it before and there it doesn’t work!! Don’t know why though…
Maybe you guys have an idea???

When I inspect the page element (again, don’t know anything about html whatsoever…) I see:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

When I inspect the text that I’m selecting and trying to get through my extension I see:

Does that information somehow explain why it doesn’t work?

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