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).
Related
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
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().
I am trying to write a JavaScript function that will open my extension like when the extension icon is clicked. I know how to open my extension in a new tab:
var url = "chrome-extension://kelodmiboakdjlbcdfoceeiafckgojel/login.html";
window.open(url);
But I want to open a pop-up in the upper right corner of the browser, like when the extension icon is clicked.
The Chromium dev team has explicitly said they will not enable this functionality. See Feature request: open extension popup bubble programmatically :
The philosophy for browser and page action popups is that they must be triggered by user action. Our suggestion is to use the new html notifications feature...
Desktop notifications can be used progammatically to present the user with a small HTML page much like your popup. It's not a perfect substitution, but it might provide the type of functionality you need.
Chrome team did create a method to open the popup programmatically, but it's only enabled as a private API, and plans to make it generally available have stalled due to security concerns.
So, as of March 2018 as of now, you still can't do it.
Short answer is that you cannot open browserAction programmatically. But you can create a dialog with your content script which emulates your browserAction and display that isntead (programmatically). However you won't be able to access your extension's background page from this popup directly as you can from your popup.html. You will have to pass message instead to your extension.
As mentioned there is no public API for this.
One workaround I have come up with is launching the extension as an iframe inside a content script with a button click. Whereby the background script emits the extension URL to the content script to be set as the iframe's src, something like below.
background.js
browser.runtime.onMessage.addListener((request) => {
if (request.open) {
return new Promise(resolve => {
chrome.browserAction.getPopup({}, (popup) => {
return resolve(popup)
})
})
}
})
content-scipt.js
const i = document.createElement('iframe')
const b = document.createElement('button')
const p = document.getElementById('some-id')
b.innerHTML = 'Open'
b.addEventListener('click', (evt) => {
evt.preventDefault()
chrome.runtime.sendMessage({ open: true }, (response) => {
i.src = response
p.appendChild(i)
})
})
p.appendChild(b)
This opens the extension in the DOM of the page the script is running on. You will also need to add the below to the manifest.
manifest.json
....
"web_accessible_resources": [
"popup.html"
]
....
You could emulate the popup by displaying a fixed html element on the page in the same location the popup would be and style it to look like the popup.
I had the same requirement: When the user clicks on the extension icon a small popup should open. In my case, I was writing an extension which will give updates on selective stocks whenever the icon is clicked. This is how my popup looked.
If you were having the same requirement then please read the answer below.
This is how my manifest.json file looked.
All the heavy lifting was handled by manifest.json file only. There is a section browser_action inside which there is a key called default_popup, just put the name of the HTML file that you want the popup to display.
I wanted my extension to work on all the pages that's why I added the attribute matches under content_scripts. I really didn't need to put the jquery file jquery-3.2.1.js inside the js array but the extension manager was not allowing me to keep that array empty.
Hope this helps, do comment if you have any doubt regarding the answer.
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.
Is there a way to open a Google Chrome plugin's options.html page via Javascript in background.html?
There is a new method that is enabled beginning with Chrome 42:
chrome.runtime.openOptionsPage(function callback)
Open your Extension's options page, if possible.
The precise behavior may depend on your manifest's options_ui or options_page key, or what Chrome happens to support at the time. For example, the page may be opened in a new tab, within chrome://extensions, within an App, or it may just focus an open options page. It will never cause the caller page to reload.
If your Extension does not declare an options page, or Chrome failed to create one for some other reason, the callback will set lastError.
chrome.tabs.create({ url: "options.html" });
Update
Starting with version 40, Chrome now uses a new popup options dialog from the extension management page instead of dedicated options pages (which are being deprecated). You can still achieve the same effect with a modification to the URL.
chrome.tabs.create({ 'url': 'chrome://extensions/?options=' + chrome.runtime.id });
Open or switch to already opened options page (instead of opening a duplicate):
var optionsUrl = chrome.extension.getURL('options.html');
chrome.tabs.query({url: optionsUrl}, function(tabs) {
if (tabs.length) {
chrome.tabs.update(tabs[0].id, {active: true});
} else {
chrome.tabs.create({url: optionsUrl});
}
});
Without using the Chrome API, only the standard Web APIs, the following is possible:
window.open("chrome-extension://ghipmampnddcpdlppkkamoankmkmcbmh/options.html")
Or, to navigate from a visible page to an extension page:
location.href = "chrome-extension://ghipmampnddcpdlppkkamoankmkmcbmh/options.html"
This requires hardcoding the extension ID.
Probably the only time this is preferable over using the Chrome API is when it's called from a non-extension context (and not the original "from background page" scenario). However, note that a web context cannot navigate to a chrome-extension://* page (it will result in about:blank) unless it's declared as web-accessible.
In such a scenario, one should also consider communicating with the webpage either through a content script or external messaging instead.