In main.js I am opening a tab and attaching a script with page-mod. The html file that is being opened it has a bunch of regular includes.
Sort of have two issues.
The script from page mod does not get attached until after those other scripts are loaded, and also
the regular scripts can't access variables defined in the script that is attached with page mod.
You have to send messages.
Directly
In your page-mod, send a message:
page-mod.js
window.postMessage(projectUniqueId + '|' + message, domain);
If your page needs to work with all domains (being a plug-in), you might need '*' as a domain.
tab-attach.js
window.addEventListener('message',function(event){
var words = event.data.split('|');
if (words[0] == projectUniqueId){
handle(words[1]);
}
});
The script from page mod does not get attached until after those other scripts are loaded,
Specifying contentScriptWhen: 'ready' in the page-mod constructor should "[l]oad content scripts once DOM content has been loaded, corresponding to the DOMContentLoaded event"
the regular scripts can't access variables defined in the script that is attached with page mod.
Have a look at Expose objects to page scripts. You need to use
var contentScriptObject = {"greeting" : "hello from add-on"};
unsafeWindow.clonedContentScriptObject = cloneInto(contentScriptObject, unsafeWindow);
in the content script to make the object accessible.
Related
I've never tried to create WebExtensions before. I'm trying to create a Helper for one web-site. I map content script on the appropriate page, but the web-site use only asynchronous calls to navigate between pages. In ff's console I see that all calls are marked as xhr. So my script is invoked only on reloading the particular page manually, but not while navigating.
Is it possible to map xhr calls and content scripts?
Should I use a kind of interceptor to do so? Is there a best practice to resolve this?
The best solution I came out with is to extend the content script's visibility in manifest to the whole web-site and to check constantly path in the script:
var enabledPages = [
"/page1",
"/page2"
];
setInterval(function() {
if(enabledPages.indexOf(window.location.pathname) > -1){
// logic
}
}, 1000);
I'm creating a Firefox add-on with the Firefox Add-on SDK. This add-on does two things:
Inject a content script into every page with sdk/page-mod.
Add a context menu item using sdk/context-menu.
I want that when user clicks the context menu item, the add-on will call functions in the content script which was loaded by PageMod().
Unless your page-mod script is doing other things, it sounds like it might be more appropriate to load it using the context-menu contentScript or contentScriptFile properties. Alternately, load the portions of it that are needed by the context menu using this methodology. How best to split the script you are using depends on what you are actually doing. Without more information from you it is difficult to provide specific recommendations.
Communicating between content scripts loaded at different times or by different methods:
There is no method of directly doing what you desire. Content scripts that are not loaded at the same time by the same methodology are loaded into different contexts. They are unable to directly call functions between them. Multiple content scripts which are loaded at the same time and the same methodology share the same context/scope and can directly call functions between them.
However, you can communicate between content scripts. If they are not loaded into the same page, then you will need to communicate from one content script to another by using your main add-on script to first receive a message from one content script. Then, your main add-on script will need to send a second message (potentially containing exactly the same data) to the second content script. In other words, your main add-on code would need to relay the message between the two content scripts.
For content scripts that are loaded into the same page via different methods (e.g. one with page-mod and another as a context menu item – the situation in which you are interested), you can communicate directly between them using the DOM postMessage() API or a CustomEvent. Either can be used to send whatever JSON serializable data you desire between the two scripts. The DOM postMessage() API provides for more security, but is a bit more complex. With it you must also filter out any other "message" events that are sent on it by random code. It should probably be used if you are going to have code in a released add-on execute functions based on the content of the messages. This is a security issue which will depend on exactly what you are doing with the messages.
Example:
The following code will load a page-mod script into every page that matches "*.mozilla.org". It also creates a context menu item in those same pages which is displayed on links. Clicking on the context menu item will send an event from the context-menu content script with data containing the URL for which the context menu was displayed. The custom event will be received by the page-mod script. The page-mod script will then issue an alert with the URL for the link.
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "*.mozilla.org",
contentScript: 'function contextMenuAlert(href) {'
+ ' window.alert("The context menu click on a link with URL:\\n" + href);'
+ '};'
+ 'window.addEventListener("myAddonId-contextMenu-clicked",'
+ ' function(event){contextMenuAlert(event.detail);});'
});
//Context menu
let cm = require("sdk/context-menu");
cm.Item({
label: "Alert link URL",
context: [
cm.URLContext(["*.mozilla.org"]),
cm.SelectorContext("a[href]")
],
contentScript: 'self.on("click", function (node, data) {'
+ ' var event = new CustomEvent("myAddonId-contextMenu-clicked",'
+ ' {detail:node.href});'
+ ' window.dispatchEvent(event);'
+ '});'
});
The above code produces a context menu that looks like:
When clicked on, the page-mod added content script initiates the following alert:
Using the message sent to choose from multiple different functions:
The information passed through the event can be expanded to allow multiple different functions to be called depending on the content. One method of doing this is to send an object as the message. One property of the object can be the function desired and another can be data to use in that function. My answers to the following questions contain examples of doing this:
Add menu item created with the sdk/context-menu API to the top of the context menu: This answer has code which uses the same passed message to indicate that either a click was made on a context menu item and pass the URL on which the context menu item was clicked, or to tell the main script that the context menu is about to be displayed so it can be modified.
How to console.log from ChromeWorker (alternative to dump): This answer shows sending a message that will result in a call to one of a variety of different functions and pass data to the function which was called. It was implemented as a way of using console methods from a worker with just console.log("message"). I'd code this one a bit differently were I doing it today, but it works and demonstrates the concept.
Because the documentation on MDN (here and here) was not very clear on content script to content script communication, I have updated the pages I found on which it was discussed. I have also added the above code as an example.
I'm converting a standard browser based app that's working fine to a chrome-app.
Once the page loads up, it has already hit an error - Uncaught TypeError: Cannot call method 'appendChild' of null. This occurs after several hundred lines of JS have done their job but its the first time the code makes a reference to the document object, specifically document.getElementById('mainDiv').appendChild(...).
I can clearly see the div with the id="mainDiv" in the debuggers elements tab. Yet, document.getElementById('mainDiv') must be returning a null. Any attempt at putting in breakpoints fails as they are ignored. I've added them to the line that fails as well as to lines that lead up to it and breakpoints are never triggered. I've read some of the threads on SO and I'm certain the breakpoints issue is just a bug in the debugger, but not recognizing an id when I can clearly see it and the code when run in the browser works fine leaves me wondering what's going on. Is document in the browser different from document in the app version?
Any ideas?
If I choose "inspect background page", the breakpoints work but it still fails but in a different way. The elements tab does NOT show my html page, but the pseudo generated background one and I can't get the debugger to show my page at all.
Any enlightenment would be appreciated. I've searched and read what I could find, but much of the docs are clearly out of date.
You seem to be accessing the document object of the background page, instead of that of your POS.html file.
Try this:
chrome.app.window.create('POS.html',{
'bounds': {
'width': screen.availWidth,
'height': screen.availHeight
}
}, function(appWin) {
var pageWindow = appWin.contentWindow;
var pageDocument = pageWindow.document;
pageWindow.addEventListener('load',function() {
// now use
pageDocument.getElementById('yourid');
// instead of
document.getElementById('yourid');
},false);
});
Also to inspect elements in your page right-click anywhere in the app window and select Inspect Element (this works only when the app was loaded as an 'unpacked extension')
Alternatively you can navigate to chrome://extensions and click the page link next to your app entry.
As lostsource mentioned, you're probably accessing the wrong DOM's document. You should think about the javascript in your app running in different global contexts, one for each page. There is (at a minimum) a page for the background page, and a page for each window.
Each of these pages runs in its own global context. This means global variables like document and window are different.
In the background page will be scripts which you load via the background manifest tag. When you open a window, it can also load its own script via script tags (make sure you do not use inline or block script tags, but use script src="foo.js". See http://developer.chrome.com/apps/contentSecurityPolicy.html).
The code that runs in the callback to chrome.app.window.create runs in the background page's context, so its document variable is for the background page's DOM, which is usually empty. Instead you can make it refer to the window's DOM using win.contentWindow as lostsource suggested, or add a page.js file with the script in it, and include it from the page via a script src='page.js' tag.
Is your call occurring after the load event, e.g. the JS called in a function set on window.onload?
I have an image in the data folder: data\img\myimage.jpg. I want to reference it in a content script. More over, I want to alter the DOM of the host page (the page where the content script is injected to) by putting that image there.
I tried to follow what Jeff says here: http://blog.mozilla.org/addons/2012/01/11/sdk-1-4-known-issue-with-hard-coding-resource-uris/ (because I didn't find any other references to that issue), but nothing worked.
What is the URL I need to use in the page in order to reference an image from the add-on's folders?
The data folder is available in main.js and you can pass that url as a content script option.
To preview the url in the console from main.js:
var data = require("sdk/self").data;
console.log(data.url('img/myimage.jpg'));
To pass the url to the content script in main.js:
var image_url = data.url('img/myimage.jpg');
...
contentScriptFile: data.url('myScript.js'),
contentScriptOptions: {"image_url" : image_url}
...
To view the url from the content script:
alert(self.options.image_url);
Like Jeff mentioned in that post, you can use the self module to get a URL to your images in the data directory. To get that information in a content script, you can either pass it in via messages(page-mod example here, but similar to all content scripts) to communicate with the script, or if you're inlining your content script, can just 'bake' it in. The self module won't be available in the content script, but passing in a string is fine.
let url = self.data.url('img/myimage.png');
pageMod({
contentScript: 'var url = ' + url + ';' + 'console.log(url);',
include: '*.mozilla.org'
})
I'm writing a Firefox extension which takes a custom switch from the command line and sets a variable inside my cmdline.js in the components directory, we'll call the variable switchDetected which is a boolean. Now based upon this variable I want actions to be carried out in my overlay.js file in the chrome/content directory.
The problem I'm having is I can't seem to be able to access the variable switchDetected that is declared in components/cmdline.js from within chrome/contents/overlay.js.
I've tried numerous ways of doing this but nothing seems to work. So I'm just wondering if anyone knows how this can be achieved.
A script loaded in an overlay runs in the context of the browser window - its global variables are stored as properties of the window object corresponding with the browser. If you open a second browser window the same script will load a second time and run in the context of the new browser window - it will have different global variables. The scripts containing XPCOM components on the other hand only load once and they have their independent context that isn't bound to a window. So their global variables cannot be accessed from a browser window directly, just like two browser windows cannot access each others global variables directly.
Instead the browser window should communicate with the XPCOM component using the usual approach: get a component instance and call its method. If you don't want to define your own interface for that (you probably don't) you can use a trick, something like this:
CommandLineHandler.prototype = {
handle: function(commandLine) {...},
get helpInfo() {...},
isSwitchDetected: function()
{
return switchDetected;
},
get wrappedJSObject()
{
return this;
},
QueryInterface: XPCOMUtils.generateQI(["nsICommandLineHandler"]);
};
The wrappedJSObject property makes sure that your component can be unwrapped - all its methods and properties will become accessible then and not just the ones defined in the interface. So your overlay script needs to do the following:
var cmdLineHandler = Components.classes["#myself.com/my-command-line-handler;1"]
.getService()
.wrappedJSObject;
var switchDetected = cmdLineHandler.isSwitchDetected();