Firefox Add-On SDK: Run content script in new tab only once - javascript

I am trying to develop a simple add-on for Firefox which should work something like this:
User clicks item in context menu.
New tab is opened.
Content (innerHTML) of the new tab is overridden using content script.
Also, the content script should only be executed once, so that if the user would enter a website in the new tab the script should not be executed.
I've got it working with editing the new tabs content, but my only problem is to have the content script run only once when the tab is opened. In the code I have at the moment the script run every time a page has been loaded in the tab:
var contextMenu = require("sdk/context-menu");
var tabs = require("sdk/tabs");
var menuItem = contextMenu.Item({
label: "Test",
contentScript: 'self.on("click", function () { self.postMessage(); })',
onMessage: function (data) {
newTab();
}
});
function newTab () {
tabs.open("about:blank");
tabs.activeTab.on('ready', function (tab) {
tab.attach({
contentScript: 'document.body.innerHTML = "testing";'
});
});
}
I'm guessing there's a way to have this run only the first time the tab is "ready". Seems like a simple task but I can't figure out how to do this. Anyone got any tips?

Use tabs.activeTab.once(); instead of tabs.activeTab.on();.
This way the listener gets detached once it intercepts the first message.

Related

Close window with JavaScript background script? (Firefox Extension)

I am currently writing a Firefox extension that opens a new window in my background script. When a user clicks a button on a page the background script executes and opens the window.
So far I have:
// content script
downloadMod(linkToOpen); //button clicked
function downloadMod(url) {
// var test = window.open(url, '_blank');
var myPort = browser.runtime.connect({ name: "cac410c4672fff93bf0d3186636d8876de3dfeb6#temporary-addon"});
myPort.postMessage({ greeting: url });
}
In my background script (the script the above code connects too) I have:
//background script
portFromCS.onMessage.addListener(function (m) {
var test = browser.windows.create({
url: m.greeting,
allowScriptsToClose: true,
});
NOTE: all the code works as expected. However, the trouble comes when closing the window.
I have tried variations of self.close(), window.close(). I even created a function to wait 5 seconds to load the newly created window and then close the page to no avail. All of my attempts come back with the error:
Scripts may not close windows that were not opened by script.
I thought this error was supposed to be removed with the allowScriptsToClose flag?
To close a window you created with browser.windows.create, you need to use browser.windows.remove.
So:
let windowId;
browser.windows.create({url: "http://google.be"}).then((data) => {
windowId = data.id;
});
// Sometime later, use windowId to close the window
setTimeout(function(){
browser.windows.remove(windowId);
}, 5000);

PhantomJS: open (via simulated user click) a popup and get source code of new window

I'm using PhantomJS to retrieve the source code of a website after some AJAX manipulations of the DOM. Then using jQuery, I'm simulating a click on a button element (which has it's link hidden in an obscure JavaScript script). This button's onClick effect is to open a new window.
What I'd like to do is retrieve the source code of this new window.
What I cannot do is use the window.open("url") method in JS because I cannot retrieve the url of the new window since it's obfuscated in a script.
Some workaround I've been looking at but haven't succeeded to implement:
Return the new window in any way with the click() function.
Switch the focus to the new window in JS.
Retrieve the url of the button's onClick whitout having to parse the JS script.
Here's what I've got at the moment:
var page = require("webpage").create();
page.open("http://www.example.com", function() {
page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() {
var page = page.evaluate(function() {
$("#myButton").click(function() {
// ???
});
});
phantom.exit();
});
});
I'm open to other frameworks/technologies if there is a solution!

Programmatically (or optionally) override Chrome's New Tab page

I've written a Chrome extension that overrides the New Tab page:
manifest.json:
"chrome_url_overrides": {
"newtab": "new-tab.html"
},
Is there a way to make this override optional? That is, I'd like to enable the user to uncheck a checkbox in the options page and disable the New Tab override. This must be possible because when I open a new tab for the first time, there's a popup informing of an extension changing the New Tab settings and asking whether to keep changes or restore settings:
I couldn't find any API for controlling overrides. The New Tab Redirect project doesn't have an option to display the native New Tab.
Google made a Star Wars new tab replacement which allows you to view the default new tab page. The url it uses is chrome-search://local-ntp/local-ntp.html.
Example:
options.html:
<input type="checkbox"> Use default new tab page
options.js:
var checkbox = document.querySelector("input[type=checkbox]")
checkbox.addEventListener("click", function() {
chrome.storage.sync.set({ defaultnewtab: checkbox.checked })
})
newtab.js:
chrome.storage.sync.get("defaultnewtab", function(storage) {
if(storage.defaultnewtab) {
chrome.tabs.update({ url: "chrome-search://local-ntp/local-ntp.html" })
}
})
Instead of using the chrome_url_override you could write a listener that listens for when tabs update using the chrome.tabs.onUpdated.addListener(), then check if the url is chrome://newtab/ and if it is and the check box is ticked, then using chrome.tabs.update() relocate them to another page.
Using the Star Wars method as described #Daniel Herr, I did this, which is working well. Although feels a little hack-y.
I have an option being set in the popup.html whether the Extension is "on" or not.
First off, set the default new tab page using the Chrome defined method:
manifest.json
"chrome_url_overrides": {
"newtab": "newtab.html"
},
Then in your Extension's newtab.html call a new JavaScript file, newtab.js (or whatever).
I am also using jQuery, so my code uses that, but you can do this natively using DOMContentLoaded.
newtab.js
$(document).ready(function(){
// It takes a moment for the Chrome query/update so sometimes there is a flash of content
// Hiding the Body makes it look blank/white until either redirected or shown
$('body').hide();
var background = chrome.extension.getBackgroundPage();
var _app = background._app;
// App is OFF, show Default New Tab
if(!_app._on){
// Get the current Tab
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
var active = tabs[0].id;
// Set the URL to the Local-NTP (New Tab Page)
chrome.tabs.update(active, { url: "chrome-search://local-ntp/local-ntp.html" }, function() { });
});
// App is ON, show custom content
} else {
$('body').show();
}
});
Basically, the methodology is to update the Tab so that it is redirected to chrome-search://local-ntp/local-ntp.html which is the hard URL to the default Chrome NTP.
Since this is a Chrome internal URL -- the URL field still appears blank.

Initial tab doesn't fire open, ready events

I'm build an SDK Firefox Add-on that is supposed to read a tab's URL and parse it. To this end, I'm listening to the 'ready' event in lib/main.js,
var tabs = require('sdk/tabs');
tabs.on('open', function(tab){
tab.on('ready', function(tab){
console.log(tab.url);
});
});
as described in Mozilla's documentation.
When debugging with cfx run, this appears to work well for new tabs. The tab that's already open on cfx run, however does not fire the open and ready events.
What's the reason for this and how to fix it?
To list all tabs that were open at the moment of loading the addon you could just use the tabs object you got after requiring sdk/tabs
var tabs = require('sdk/tabs');
for (var tab of tabs) {
console.log(tab.url);
}
So the code from your example could be transformed to something like this:
var tabs = require('sdk/tabs');
for (let tab of tabs) {
processTab(tab);
}
tabs.on('open', function(tab){
tab.on('ready', processTab);
});
function processTab(tab) {
console.log(tab.url);
}

how to resume script when new window loads

I am writing an page action extension for Google Chrome. The extension injects the following script into a search page after it loads. After the script finds all the occurrences of class "f_foto" (typically 10 items), it finds the first link in each of them, puts these hrefs in an array and then iterates thru the array opening a new window for each link and examining the result. That's what it is supposed to do.
Everything works ok in this code except the last part. The new window opens in a new tab (I have tabs permission) but it only finishes loading after the script finishes. Each new window overwrites the previous one in the same tab which would be ok if I had a chance to examine the contents first. So if I run it without using the debugger when the script finishes the new tab contains the last item in the array and focus is on the new tab. As far as I can see, handleResponse is never called.
If I run it in the DOM inspector and stop it at window.open, I can see that the new tab opens with "About Blank" in the title and the tab shows a spinning thingy showing that it is loading. Stepping thru the code, detailWin remains undefined even after detailWin=window.open(profileLinks[i], "Detail Window"); is executed. I've tried replacing window.onload = handleResponse; with detailWin.onload =handleResponse; but in this case detailWin is undefined.
It seems to me I need to add an event listener that fires when the new window is loaded and executes handleResponse. Yes? No?
//PEEK.JS//
var req;
var detailWin;
var profileLinks = new Array();
function handleResponse()
{
// var contentDetail = document.getElementsByClassName("content");
alert("Examine Detail Page Here");
};
//drag off the f_foto class
var searchResult = document.getElementsByClassName("f_foto");
alert("Found Class f_foto "+searchResult.length+" times.");
//collect profile links
for (var i = 0; i<searchResult.length; ++i)
{
var profileLink=searchResult[i].getElementsByTagName("a");
profileLinks[i]=profileLink[0].href;
// alert(i+1+" of "+searchResult.length+" "+profileLinks[i]+" length of "+profileLinks[i].length);
}
for (var i = 0; i<searchResult.length; ++i)
{
//DYSFUNCTIONAL CODE: New window finishes loading only after script completes, how to execute handleResponse?
detailWin=window.open(profileLinks[i], "Detail Window");
window.onload = handleResponse;
}
Option #1: make two separated content scripts - one for the search page only, one for the profile page only. Search script would only open profile link, profile script would only process it (contains code inside your handleResponse())
Option #2 If for some reasons you don't want to inject profile script to all profile pages, only to those you opened yourself from the search page, then instead of opening windows from a content script you should send a message to a background page asking it to open a profile link in a new tab and inject your profile script.
You still will have two content scripts.
search.js (injected to search pages only):
//PEEK.JS//
var req;
var detailWin;
//drag off the f_foto class
var searchResult = document.getElementsByClassName("f_foto");
alert("Found Class f_foto "+searchResult.length+" times.");
//collect profile links
for (var i = 0; i<searchResult.length; ++i)
{
var profileLink=searchResult[i].getElementsByTagName("a");
profileLinks[i]=profileLink[0].href;
// alert(i+1+" of "+searchResult.length+" "+profileLinks[i]+" length of "+profileLinks[i].length);
}
for (var i = 0; i<searchResult.length; ++i)
{
//tell bkgd page to open link
chrome.extension.sendRequest({cmd: "openProfile", url: profileLinks[i]});
}
profile.js (will be injected to profile pages you opened)
var contentDetail = document.getElementsByClassName("content");
alert("Examine Detail Page Here");
background.html:
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request.cmd == "openProfile") {
chrome.tabs.create({url: request.url}, function(tab){
//profile tab is created, inject profile script
chrome.tabs.executeScript(tab.id, {file: "profile.js"});
});
}
});
Option #3: Maybe you don't need to create profile window at all? If all you need is to find something in the page source, then you can just load that page through ajax and parse it (you would need to do it in a background page).

Categories