So, I've got a situation where I want a background and content script to be run everytime the browser extension icon is clicked. The ideal behaviour is that extension icon is clicked, the script runs, and the popup will open, displaying the data that was grabbed by the script (but this should happen quickly, the script runs and gets the data very fast). Since chrome.pageAction.onClicked will not work if there is a default_popup defined in manifest.json, I think this leaves me with two options:
Use default_popup and figure out that the extension icon has been clicked some other way. I found this solution in another stack overflow post, but the workaround is to use default_popup: "popup.html", and then the popup.js that is defined in popup.html will send a message saying that the icon has been clicked, then when background.js receives this message, it executes the script. I implemented this idea and it worked... kinda. The trouble is, the popup will always come up before the script is fully executed, so you can't actually display the data grabbed by the script in the popup until the next click. I'm not sure there's any way to get the behaviour I desire using this method, so on to the next:
The other solution I can possible think of is to use onClicked, and then make the popup come up some other way, besides using default_popup in manifest.json. I'm also not sure if this is possible, I have looked on stackoverflow and haven't found anything similar.
Is the second method possible? Can the first method work somehow?
Your option #1 is correct, I think all that is needed is a loading screen when the user first sees the popup, and add some code that updates the popup as soon as it hears from the backend. Might need to see some code to better help there.
Option #2 will not really work, unless you opened the popup in a new tab (or just made it a whole new HTML page). I say this because there is a note here from the Chrome Dev team they will not support opening a popup unless it is from a user gesture -- https://stackoverflow.com/a/10484764/4875295.
If you wanted to go that route it would probably look something like:
Delete from your manifest.json browser_action.default_popup
In your background script add something like:
chrome.browserAction.onClicked.addListener(() => {
const data = dataMaker();
chrome.tabs.create({
url: `${chrome.runtime.getURL("app.html")}?data=${data}`
});
});
Then in your html file have some JS that reads the query string and updates the page accordingly.
Though, that's a different approach than you asked for and I think your original route may still be the best bet (with some added JS around loading).
Related
Posting without a target so that a web page reloads seems useful behaviour for some things - such as writing a login page. I have implemented a calendar in PHP which takes advantage of this. It reloads an object from the session (or creates a new one if not present), applying any changes that result from the post then saves the object back to the session. The problem is this. If I hit the back button I don't want to go back through every click of the calendar button but would rather jump back to the page before arriving at the calendar page. Not only that, if I do go back one calendar page after another I get an annoying "confirm form resubmission". I have implemented an incrementing value after the # for each post so that I might be able to use window.onhashchange. The problem is that window.onhashchange never fires so I am unable to intercept the back button and pop the history stack. Any ideas? Am I better off coding on the server side with javascript?
Well I solved one problem. My form subclass in PHP defaults to using POST as I understand this is more secure. This causes the annoying resubmission problem when using the back button. I now use GET in my calendar page which solves this issue. I am still bemused by JS debugging in Netbeans. I have never got script to stop on a breakpoint within a single document. I have previously had it working with an external javascript source but this no longer works. If I can output to console but there is no window in which to see the output. I am told window.alert no longer works for some events in Chrome. I am completely blind! To add to the irritation, it took me a while to realize was that the javascript file was cached and changes would not be reflected in behaviour. I have put a random number into the script tag which fixes this issue. As I am debugging using netbeans connector in Chrome I have no idea why this does not force the js file to refresh. All in all, this appears to be a pretty shambolic toolchain.
I'm updating a very old web app (webforms) that only works properly in IE 6-9. It was not originally written by me. Also, I'm not an expert at web development, so the solution to this might be very simple.
One issue is extensive use of window.showModalDialog, which is an IE specific call that opens a new browser window that disables the browser window that opened it, like a popup message. This has been replaced with a jquery modal dialog, however there's sometimes an issue with the 'url' that gets passed to it.
Here's a simplified reproduction of the issue. There's a javascript function that takes a url and an id.
function openEdit(url, id) { ...
This function existed in the original version, except it had code to open a modal popup window. I replaced it with the necessary jquery. However, the url value that gets passed in sometimes doesn't have enough information. Also, assume I have no control over the value that gets passed here.
Let's say the main page is at localhost/TestSite/Main.aspx. There are a number of frames within this page which display other pages, like localhost/TestSite/Products/ProductList.aspx - clicking an item on this page might open a window to localhost/TestSite/Products/ProductDetails.aspx. There are hundreds of pages that follow this general format.
Sometimes the url has a value of '/TestSite/Products/ProductDetails.aspx'. The jquery dialog correctly navigates to localhost/TestSite/Products/ProductDetails.aspx
However, other pages just pass in the name of the page, 'ProductDetails.aspx', which jquery tries to find at localhost/TestSite/ProductDetails.aspx. This works on IE using window.showModalDialog and the browser is able to get the expected directory of 'Product' because it's the same directory the open window call was made from. Jquery doesn't seem make this leap.
Now, I have a possible solution using window.location to get the current url, parse it a bit, and generate a valid url. I'm worried about what fringe cases this may create, though, and it also seemed like the improper way to do it.
Is there a way to have jquery open a dialog using the corrent directory, or is there a way to generate a current directory to use that doesn't involve window.location? Or is that my best choice.
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.
I'm having a situation in which I want to allow the user to download a PDF. The generation of this PDF can take a couple of seconds, and I don't want a webserver thread to wait for the generation of the PDF, since that means the thread isn't available to handle other incoming requests.
So, what I would like to do is introduce the following URL patterns:
/request_download which invokes a background job to generate the PDF;
/document.pdf which will serve the PDF once it is generated
The use case is as follows.
A user clicks on 'Download PDF'. This invokes a piece of Javascript that'll show a spinner, make a request to /request_download and receive a HTTP 202 Accepted, indicating the request was accepted and a background job was created. The JS should then poll the /request_download url periodically until it gets HTTP 201 Created, indicating that the PDF has been created. A Location header is included that is used by the JS to forward the client to /document.pdf. This has to be in a new window (or tab, at least it shouldn't replace the current page). The level of expertise of our users is very low, so when I forward to the url, they might not know how to get back to the website.
The problem
Now, window.open works fine if it is invoked by the user via a click event. However, I want to invoke window.open in a callback function through setInterval as soon as I see that 201 Created response. However, browsers won't like that and will interpret it as a popup, causing it to get caught by popup blockers on IE8-10 as well as Chrome. Which makes sense.
I also can't open a new window and invoke the Javascript over there. In that case, users would see a blank page with a spinner. If that page then forwards to the /document.pdf, IE will show this yellow warning bar telling that it prevented files from being downloaded. Chrome and Firefox will do this without any problems, but a large percentage of our users is on IE.
What I've tried, and didn't work
window.open(location)
Setting the src of an iframe to the retrieve location
Manually adding an <a> tag to the document body and trying to click it using javascript
Adding an <a> tag to the document body and invoking a MouseEvent on it
Partially works: opening a new window on user click and storing a reference to it, then perform the asynchronous requests and upon completion, set the location of the earlier opened window. But: IE will block this and say it prevented files from being downloaded. So still not a fully working solution.
I'm straight out of ideas on how I can make this work and decided and decided to ask The Internet for help. I'm hoping you guys can help me with this, or recognise the problem. Thanks in advance!
This question is a follow-on to another question which needed asking and warranted a new post, so excuse me if I refer to things which may not be clear without reading the other question.
When using the utility waitForKeyElements() I'm facing an issue in which a div is included inside a small popup contained within the same URL. My extension is currently running on the Twitter site, and my intention is that a div contained on the profile pages (e.g. http://twitter.com/todayshow) gets moved above another div on the page. I'm doing this via waitForKeyElements() because of some loading issues which are resolved by using this utility.
However, on a profile page you can click a link to another users name which pops up a small window (inside the same window/tab, on the same URL) showing some info about them and a few previous tweets. The issue here is that the same div appears on this popup and is then moved to the main page behind the popup window, where it shouldn't be. On a profile page, this can be stopped by plugging in the false parameter to waitForKeyElements(), however on a non-profile page it is still possible to activate this popup which is then moving onto the main page, as the div I wish to move it above on a profile page still exists here, causing clear issues.
I'm wondering if there's a way around this, as bugs in Chrome have stopped me from excluding these pages. So far (just brainstorming) I'm thinking:
on a page where the div doesn't exist to begin with, create an empty one meaning false will handle the issue.
somehow stop the script from firing on a given URL, although due to the way Twitter works this would have to monitor OnClick() and the page URL (I think) which I'm unsure how to do.
stop running when the popup appears, but I have almost no idea where to start with that.
Any help is appreciated. Any necessary code related to this question can be found in the first two links, and the issue I'm facing can be seen by a quick visit to Twitter.
EDIT: When plugging in the false param it works when going directly to profiles via the URL bar, if you're on a profile and use a link to get to a profile, the script isn't inserted and my extension fails. So this would need resolving too, or an alternative method altogether.
I had a brainwave that I could use insertAfter() to insert the <div> I was originally moving in front of, after the <div> I was originally moving. This <div> is not present on the popup, which means that nothing is moved onto the back page when it shouldn't be.
In regards to the previous question, my code is now simply:
waitForKeyElements (
"jQuery selector for div(s) you want to move", // Opposite to what it was.
moveSelectDivs
);
function moveSelectDivs (jNode) {
jNode.insertAfter ("APPROPRIATE JQUERY SELECTOR"); // Again, the opposite.
}
This solves the issue I was having and my extension is now working just fine, however I will leave this question posted in case anybody comes back to it in future.