get 'this' tab of content script (not selected, not active tab)? - javascript

There are quite some similar question but they all comes down to chrome.tabs.getSelected or chrome.tabs.query API which is not suitable in my case.
Basically what I need to do is to get an id of the tab where the script is running from - so it's not necessarily an active or selected tab.
As per the doc:
getCurrent chrome.tabs.getCurrent(function callback)
Gets the tab that this script call is being made from. May be
undefined if called from a non-tab context (for example: a background
page or popup view).
Which implies that it should work from content script but chrome.tabs is undefined in content script. Why is it so? Is there any way to know this tab data (from where the content script is running and not from selected or active tab)?
Even though the doc says the tabs permission is not mandatory for the most APIs I've anyway added it to the manifest with no luck:
{
"manifest_version": 2,
"name": ...
"permissions": [
...
"tabs",
...
}
Any ideas are much appreciated
The use case for get current/this tab is that when the extension does its work it needs to reload a page where it's running from as part of the working flow and user can be on different tab or in different window. The extension's script still needs to get the correct tabId to keep working as expected.

Effectively, you can only use chrome.tabs.getCurrent on a page from the chrome-extension:// scheme (opened as your extension's Options page or via chrome.tabs.create or chrome.windows.create), or if you're using chrome_url_overrides. The background, popup, and embedded options pages don't have a current tab, and the API doesn't exist in a content script.

Well, seems like I've figured out an answer to my question.
The trick is to send a message to background script and extract sender data from there. Sender will include tab object where the script is running from.
I'm using ports so this is what I'll describe as example below:
On content script side:
var port = chrome.extension.connect({
name: "some name"
});
port.postMessage({"key":"some json here"})
On Background side:
chrome.extension.onConnect.addListener(function (port) {
console.log(port.sender.tab)
})
port.sender is a MessageSender object that will contain tabId (and tabUrl if "tabs" permission is added to the manifest)
In my case I'm just sending tabId back from background to the content script:
port.postMessage({"tabId":port.sender.tab.id})
More on this can be found in messaging doc and in this api doc

Related

Chrome Extension Development open extension window from background service

Using: Manifest V3
How do I open my extension same way Metamask does it?
Currently what I have tried is that from my background service I am using chrome.runtime.sendMessage to send message to my content.html and .js of my extension where I have chrome.runtime.onMessage.addListener and listen for the open window message then I tried with chrome.extension.getViews({ type: 'popup' }).forEach(v => v.open()); to display my extension window, but instead it sometimes opens a new empty tab or sometimes I get error Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
What I want to accomplish is that the service worker which is constantly running in background, can open my extension window same way like when I click on extension icon.
Metamask case example: https://youtu.be/vhUjCLYlnMM?t=633
This:
From [ (Service worker) background.js ] open extension window programmatically, same way
as when clicked in the Chrome taskbar on the icon of extension.
You can open it with a new tab on the url in the css and js parts.
<a target="_blank" href="file.url"></a>
I had a similar situation but used a different tactic. Rather then relying on the content script, I am using the options page as the dispatcher.
I am not sure whether this helps in your situation, as I am not sure whether you want to open a new tab or a popup. This works for a new tab, but not for a popup.
Looking at chrome.runtime API, I found only one method that opens a new tab directly: chrome.runtime.openOptionsPage(). For this to work, you need to define an option page in your manifest:
{
"manifest_version": 3,
...
"options_ui": {
"open_in_tab": true,
"page": "options.html"
},
}
Note that the options page must be bundled with your extension. But if you open it in a tab, you can perform a redirect to wherever you want to be, for instance:
window.open("https://www.whereever.com","_self")
If you need to configure the destination, chrome.runtime.openOptionsPage() can take a callback function as an argument, which you can use on the options page to figure out the final destination page(s).

How to open a Firefox WebExtension options page as a tab, separate from about:addons

So, I've looked through the WebExtensions API, but I haven't been able to figure out how to open an HTML page separate from about:addons for options. In the Add-on SDK you could have resource://ext-id-/path/to/file.html. I've tried making a directory web accessible and putting an HTML file in there, but that didn't seem to work.
Does anyone know how I can open the options HTML file in it's own tab with WebExtensions?
Opening a tab
Options page always in a tab:
If you want your options page to always open in a tab, you can add the property open_in_tab with a value of true to the options_ui key in your manifest.json:
"options_ui" : {
"page": "options.html",
"open_in_tab":true
}
This will result in your options page always opening in a tab. Both clicking on your extension's "Options" from within about:addons and using runtime.openOptionsPage() will result in your options page opening in a tab.
Thanks to BigBlutHat for reminding me of this option.
In a tab when normally your options page is within about:addons:
You can open a new tab with whatever URL from within your extension you desire, including your options page, using tabs.create and runtime.getURL. Specifically for an options.html file located in the same directory as your manifest.json, the following works:
chrome.tabs.create({
url: chrome.runtime.getURL('/options.html')
});
Does not need to be web accessible and loading JavaScript:
You do not need the files to be declared as web accessible. The page runs in the background context so JavaScript is loaded by directly including the files in the src of a <script> tag (e.g. <script src="/options.js">). This is the same as you would do for a popup. This answer has an extension that uses the same HTML and JavaScript as both a popup and an options page. It does not, however, actually show opening that page as a tab, but it could be done with the above code.
Resolving Relative URLs:
Both Chrome and Firefox state:
Relative URLs will be relative to the current page within the extension.
Note: For all the different chrome.* API calls, Chrome and Firefox do not always resolve relative URLs in the same way. For example, it is different in each browser for chrome.executeScript().

Click on page from chrome extension

How can a chrome extension click on a button on active page?
There is banal page on the Web. There is simple element of button type with specific ID on the page. Also there is Chrome extension with a button. I'd like to click to extension button and the button in turn clicks to page button.
var someElement = document.getElementById('someElement');
someElement.addEventListener('click', function () {
// here I'd like to click on a button with specific ID.
});
The very first thing you want to do is to read the Overview page, especially the Architecture part. Read it thoroughly, and it will answer many questions you have.
Your problem can be split into two parts.
How to trigger something with a click on your extension.
How to click something inside the active tab.
Before I proceed, I'll reiterate what wOxxOm said: there's a great small example in the docs that does nearly what you want. But if you want to be someone taught to fish, not given a fish, read on.
How to trigger something with a click on your extension
It depends on what kind of UI you're using; the simplest is a Browser Action button.
Simplest button:
If you add a browser action to the manifest without specifying a popup:
"browser_action": {
"default_icon": { "38": "icon38.png" }
},
then clicking on it will raise chrome.browserAction.onClicked event to your extension's pages. The only page open at any time you need it is a background page, the role of which is usually the central dispatch for extension events. So, you need a background page that listens to that event:
"background": {
"scripts": ["background.js"]
},
and
// background.js
chrome.browserAction.onClicked.addListener(function(tab) {
// Okay, the actual action should go here
// And look, we already have the required Tab object for free!
});
Variations (exercises for the reader):
If you do specify a "default_popup" in your manifest, then chrome.browserAction.onClicked will not trigger. Instead, a small popup page will open with the HTML file you specify; you can add UI/logic there as you wish, the principle will be the same as normal webpages but with Chrome API access, except:
You'll need to query for the current tab yourself.
You'll need to be mindful of Chrome's extension CSP.
In case you run into problems, you need to know how to debug them.
If your extension targets only a few specific pages, consider using Page Actions instead of Browser Actions.
As noted in the documentation, Event pages are preferable to Background pages. In this case, you can use an Event page easily without any side effects, but in general this may require some thinking.
You could inject your own UI into the page itself with Content scripts; this is an advanced topic and will not be covered here.
How to click something inside the active tab
Since you've read the Architecture overview, you already know that the only part of the extension that can interact with the DOM of an open page is a Content script.
Content scripts can either be specified in the manifest (and then they will automatically be injected and ready for you when a matching page is opened), or they can be manually injected into the page.
In your case, you want to do something simple, and only when clicked. This is a perfect job for programmatic injection, so we'll stick with that.
Assuming the solution from the previous section, you are in a context of a background page and already have the current tab as the tab variable:
// background.js
chrome.browserAction.onClicked.addListener(function(tab) {
// Do something with tab
});
Programmatic injection is done with chrome.tabs.executeScript method. At a minimum, you need to specify the tab you want to inject to, and the code that will be run:
// background.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(tab.id, {
code: "document.getElementById('#specificId').click()"
});
});
That's not all yet though. The extension must have permissions to execute code in an open tab.
You could use "host permissions" that are defined by a match pattern to give access to specific pages, but remember that we are only triggering it when the user clicks the extension.
For that specific case, there's a special permission "activeTab". It is sufficient to do a lot of things with the currently active tab when the extension is explicitly invoked, and clicking its button is explicit enough.
So, add to manifest:
"permissions": ["activeTab"],
And that should be all you need for this to work.
Extra credit:
While not necessary for this simple purpose, you may want more complicated code than a single line to be executed in the tab. Then it makes sense to use a separate file and invoke executeScript with "file" instead of "code".
Just triggering a click on a button does not require you to directly interact with JavaScript running in the page itself, as DOM events are shared. However, it's important to understand that normally, content scripts can't interact with the page's own scripts, which is called "isolated world". There are ways to bypass it if you really need it, but it's an advanced topic better explained elsewhere.
Sometimes you need the content script to persist, answer some commands and maybe send its own queries to the extension's pages. In that case, it's probably better to auto-inject through the manifest and use Messaging instead of executeScript.

How to use console.log when debugging tabs.executeScript

I have been looking for the answer to this question, but it only took me to the solution for debugging the extension itself, while I want to debug the webpage.
I want to create an extension that allows me to modify a particular web page (obviously only on my computer).
I have created a very simple script following the "activeTab" permission tutorial on the chrome developer's site, then I have made the following:
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
// No tabs or host permissions needed!
chrome.tabs.executeScript({
code:'
var oSwitchContainer = document.getElementById("norm");
console.log(oSwitchContainer.childNodes);
'
});
});
When I inspect the page I cannot see any console messages, however, I can change the content of that oSwitchContainer easily by modifying its innerHTML.
Is there any way to see the console logs of the page after I enable the extension?
You say that you found solutions to debug "the extension itself".
Take a look at the Architecture Overview.
What you refer to as "extension itself" is its background page, and that's where your browserAction.onClicked listener executes. If you execute console.log() statements from that code, it will go to the background page's console.
However, using chrome.tabs.executeScript, you pass code to be executed in the context1 of an open tab instead of executing it in the background page. All console.log() calls from that context go to the open page's own console - you should look there.
console.log("This will show in the background page console");
chrome.tabs.executeScript(
{ code: 'console.log("This will show in the current tab console");' }
);
1 To be precise, the extension creates an isolated context, but it still belongs to the open page. See Content Scripts documentation for more details.

How can I get opened URL in browser-action panel

I am creating a browser-action button on the right side of toolbar so that I can show deals on my extension depending upon the opened URL in tab.
Upon going through SDK documentation I found this: https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/ui_button_toggle#Attaching_panels_to_buttons but it says that it's supported only for Firefox 30 onwards which is an issue.
To achieve this thing, I used browser-action lib by Rob-W (source: https://github.com/Rob--W/browser-action-jplib)
Here is how it works:
Let's say, I have opened www.example.com, and it has 20 deals on my server. If I click on my browser-action button, it would open a panel showing deals depending upon the opened URL (with the help of an AJAX request)
Now the problem is:
The browser-action button (upon click) opens popup.html, and in my popup.html I have included popup.js. This is the file where I want access to the opened tab URL, so that I can perform the AJAX request. I do not get how can I pass the opened tab URL from main.js to popup.js.
sdk/tabs give you the access to current tab and from there you can get the location.
https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/tabs
I do not get how can I pass the opened tab URL from main.js to
popup.js.
Have you tried to use the port system.
to emit a message from a content script:
self.port.emit("myContentScriptMessage", myContentScriptMessagePayload);
To receive a message from the add-on code:
self.port.on("myAddonMessage", function(myAddonMessagePayload) {
// Handle the message
});
You can also use widget instead of the new UI module if you really want to target browsers before version 30, however it is depreciated and will probably be removed within the next few releases.

Categories

Resources