Assume frame.html inside data folder in Firefox SDK addon,
how to append an iframe and define frame.html as its source?
Additional info:
Because of CPS, its not possible to use inline source, so I can't use data.load('frame.html'), I need to use URL of the file:
lib/main.js
tabs.activeTab.attach({
contentScriptFile: [self.data.url("js/appender.js") ],
attachTo: 'top',
contentScriptOptions: {
scriptUrl: self.data.url("js/sandbox.js"),
imageUrl: self.data.url("image.jpg")
frameUrl: self.data.url("sandbox.html")
}
});
data/appender.js
var scriptTag = document.createElement("script");
scriptTag.src = self.options.scriptUrl;
document.body.appendChild(scriptTag); // worked fine
var imageTag = document.createElement("image");
imageTag.src = self.options.imageUrl;
document.body.appendChild(imageTag); // worked fine
var iframeTag = document.createElement("iframe");
iframeTag.src = self.options.frameUrl;
document.body.appendChild(iframeTag); // the html tag of iframe is empty
It's blank because of the firefox security policy which doens't allow content scripts to load resources URLs as iframes. The solution could be to set it directly from the background script, for this you'll need to use low level sdk.
var { viewFor } = require("sdk/view/core");
var tab_utils = require("sdk/tabs/utils");
var iframe = viewFor(tab).linkedBrowser._contentWindow.document.querySelector('iframe[src="' + self.data.url("sandbox.html") + '"]')
iframe.contentWindow.location.href = iframe.getAttribute("src")
so the completely working code would look like:
data/appender.js
var iframeTag = document.createElement("iframe");
iframeTag.src = self.options.frameUrl;
document.body.appendChild(iframeTag); // the html tag of iframe is empty
// note that the iframe is attached after the content ready event, so you have to inform the background when it can start working with the iframe
self.port.emit("attached", true);
main.js
tabs = require("sdk/tabs")
self = require("sdk/self")
var { viewFor } = require("sdk/view/core");
tab_utils = require("sdk/tabs/utils");
tabs.on("ready", function(tab) {
var worker = tab.attach({
contentScriptFile: [self.data.url("js/appender.js") ],
attachTo: 'top',
contentScriptOptions: {
frameUrl: self.data.url("sandbox.html")
}
});
worker.port.on('attached', function() {
reinitIframe(tab)
});
function reinitIframe(tab) {
var iframe = viewFor(tab).linkedBrowser._contentWindow.document.querySelector('iframe[src="' + self.data.url("sandbox.html") + '"]')
iframe.contentWindow.location.href = iframe.getAttribute("src")
}
})
You'll probably need a cross-process wrapper in order to make it work in future version of Firefox with electrolysis
Found a better way. You add a blank <iframe> and attach an event listener for the load event. Then you can easily append whatever elements you want to the iframe as usual.
Include this in your content-script:
var iframe = document.createElement('iframe'),
div = document.createElement('div');
div.textContent('Hello World');
document.body.appendChild(iframe);
iframe.addEventListener('load', function(){
this.contentWindow.document.body.appendChild(div)
})
Related
I have an iframe loaded within a Cshtml page. The Iframe contains an html form. i need to add an image in front of each text area of the form when the main page(cshtml page) is loaded. Have tried a lot of things but nothing has worked as such. Below is the code i am trying:
$("#iframe").ready(function() {
$.each($('input[type=text],textarea'), function() {
var idOfCurrentElement = $(this).attr('id');
var classNameOfCurrentElement = $(this).attr('class');
var visibilityOfCurrentElement = $(this).css('visibility');
if(classNameOfCurrentElement != "hidden" && visibilityOfCurrentElement == "visible")
$("<img src='~/images/pic' alt='Pic'>");
});
});
You must access iframe content, not your page.
$("#iframe").ready(function() {
$.each($('#iframe').contents().find('input[type=text],textarea'), function() {
var $thatButton = $(this);
var idOfCurrentElement = thatButton .attr('id');
var classNameOfCurrentElement = thatButton ).attr('class');
var visibilityOfCurrentElement = thatButton .css('visibility');
if(classNameOfCurrentElement != "hidden" && visibilityOfCurrentElement == "visible")
//do what you want.
});
});
I am creating a chrome extension that downloads all the images on the current webpage and this is what I have
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('checkPage');
checkPageButton.addEventListener('click', function() {
downloadImages()
}, false);
}, false);
function downloadImages() {
var images = document.getElementsByTagName('img');
var srcList = [];
var i = 0;
setInterval(function(){
if(images.length > i){
srcList.push(images[i].src);
var link = document.createElement("a");
link.id=i;
link.download = images[i].src;
link.href = images[i].src;
link.click();
i++;
}
},1500);
}
This adds a click listener that should download all images when you click the button on the other section. I know this code works because I replaced the code that downloads the images with the code for an alert and when I pressed the button it indeed made an alert, so why is it that when I add the image downloading code it doesn't do anything? Also I have tested the image downloading code, it works.
I am using the addon-sdk. I have a widget and upon clicking the widget I want to do something with the website (be it modifying the page or reading the deep DOM).
So my thought after reading https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/page-mod#Communicating_With_Content_Scripts would be:
pagemod activated on matched url (in this case ANY)
click on widget which satisfies left-click event. It then emits widget-click.
Pagemod receives widget-click event and fires back an event called from-pagemod.
from-pagemod does something to the webpage.
I see the following output in stdout:
console.log: project: about to emit widget-click
console.log: project: after emitting widget-click
So pagemod didn't receive that event or it was never set up. I am not sure what is missing from this simple test case. Any help is appreciated.
Here is lib/main.js.
var widgets = require('sdk/widget');
var pageMod = require("sdk/page-mod");
var data = require("sdk/self").data;
var tabs = require("sdk/tabs");
exports.main = function() {
var widget = widgets.Widget({
label: "widget label",
id: "widget-id",
contentURL: data.url("off.png"),
contentScriptFile: [data.url("widget.js"), data.url("page.js")]
});
widget.port.on("left-click", function() {
console.log("about to emit widget-click");
widget.port.emit("widget-click", "foo");
console.log("after emitting widget-click");
});
var page = pageMod.PageMod({
include: "*",
contentScriptWhen: "end",
contentScriptFile: data.url("page.js"),
onAttach: function(worker) {
worker.port.on("widget-click", function(msg) {
console.log("on widget-click, ready to emit from-pagemod event");
worker.port.emit("from-pagemod", "foo");
});
}
});
};
Here is page.js
self.port.on("from-pagemod", function(msg) {
console.log("inside from-pagemod listener");
// read DOM or modify the DOM
});
Here is widget.js
this.addEventListener('click', function(event) {
if(event.button == 0 && event.shiftKey == false)
self.port.emit('left-click');
}, true);
Edit 2, Answering the actual question:
If you want to do something to the page on widget click when the content script is already attached, register your widget listener somewhere you have access to the page-worker. You could do this by putting your widget.port.on code inside the pageMod's onAttach(), but then it would only work for the most recently attached page. The best way to make it functional would be to store all workers then check if the current tab has a worker when the widget is clicked, like so:
main.js partial
var workers = [];
widget.port.on("left-click", function() {
console.log("about to emit widget-click");
var worker = getWorker(tabs.activeTab);
if (worker) {
worker.port.emit("widget-click", "foo");
console.log("after emitting widget-click");
}
});
var page = pageMod.PageMod({
include: "*", // TODO: make more specific
contentScriptWhen: "end",
contentScriptFile: data.url("page.js"),
onAttach: function(worker) {
workers.push(worker);
worker.on('detach', function() {
detachWorker(worker);
});
// could be written as
// worker.on('detach', detachWorker.bind(null, worker);
}
});
function detachWorker(worker) {
var index = workers.indexOf(worker);
if(index!==-1) workerArray.splice(index, 1);
}
function getWorker(workers, tab) {
for (var i = workers.length - 1; i >= 0; i--) {
if (workers[i].tab===tab) return worker;
}
}
page.js
self.port.on("widget-click", function(msg) {
console.log("inside widget-click listener");
// read DOM or modify the DOM
});
The reason that your solution wasn't working is that you assumed that events were somehow linked to the file rather than an object.
Old answer: You're making it way too complicated.
Just attach the content script on click (as opposed to adding a content script to every single page that does nothing unless it receives an event)
main.js
var widgets = require('sdk/widget');
var data = require("sdk/self").data;
var tabs = require("sdk/tabs");
exports.main = function() {
var widget = widgets.Widget({
label: "widget label",
id: "widget-id",
contentURL: data.url("off.png"),
contentScriptFile: data.url("widget.js")
});
widget.port.on("left-click", function() {
console.log("about to attach script");
var worker = tabs.activeTab.attach({
contentScriptFile: data.url("page.js");
});
worker.port.on('message from content script', function(someVariable){
//Now I can do something with someVariable in main.js
});
});
};
page.js
// read DOM or modify the DOM
//I'm done, I'll send info back to the main script. This is optional
self.port.emit('message from content script', someVariable);
Edit: Read Modifying the Page Hosted by a Tab for more info. Also, the Tutorials page is a good place to start when you're trying to do something you haven't done before with the SDK. It's a good way to step back and think of alternatives for trying to achieve your goal.
a port is a communication channel between a content script and an add-on component: your Widget may interact with its content via widget.js, and your pagemod with the matched webpage content via page.js. When you do widget.port.emit("widget-click", "foo"); this message can only be listened by widget.js using self.port.on('widget-click') not by the pagemod instance. Since you widget and pagemod are objects that share the main.js scope they can talk each other by just accessing its properties and methods.
On my site I use one core/frame PHP file. If user hit one of my link (like contact, our about..etc..) the content loaded via ajax. I use the following snippet to achieve this:
var AjaxContent = function(){
var container_div = '';
var content_div = '';
return {
getContent : function(url){
$(container_div).animate({opacity:0},
function(){ // the callback, loads the content with ajax
$(container_div).load(url, //only loads the selected portion
function(){
$(container_div).animate({opacity:1});
}
);
});
},
ajaxify_links: function(elements){
$(elements).click(function(){
AjaxContent.getContent(this.href);
return false;
});
},
init: function(params){
container_div = params.containerDiv;
content_div = params.contentDiv;
return this;
}
}
}();
I need help how to integrate a preloading, so if visitors hit one of my link (for example the gallery menu) will see a little loading image, because now they see the big white nothing for long - long seconds.
Add loading image beforing ajax call and after you get response from server simply replace that image with data like the one below
function(){ // the callback, loads the content with ajax
$(container_div).html("<img src='loading.gif' />");//add image before ajax call
$(container_div).load(url, //only loads the selected portion
function(){
$(container_div).html(data);//replace image with server response data
$(container_div).animate({opacity:1});
}
Try this
var AjaxContent = function(){
var container_div = '';
var content_div = '';
return {
getContent : function(url){
$(container_div).html('Loading...'); //replace with your loading img html code
$(container_div).load(url, //only loads the selected portion
function(){
$(container_div).css({opacity:0});
$(container_div).animate({opacity:1});
});
},
ajaxify_links: function(elements){
$(elements).click(function(){
AjaxContent.getContent(this.href);
return false;
});
},
init: function(params){
container_div = params.containerDiv;
content_div = params.contentDiv;
return this;
}
}
}();
In my application i added Marionette.sync plugin and override these methods:
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function (templateId, callback) {
var tmpId = templateId.replace("#", ""),
url = "/app/templates/" + tmpId + ".html";
$.get(url, function (templateHtml) {
compiledTemplate = Handlebars.compile($(templateHtml).html())
callback.call(this, compiledTemplate);
});
};
Backbone.Marionette.Renderer.renderTemplate = function (template, data) {
template(data);
};
But this not work, any ideas?
I assume you're running v0.9 of Marionette since you mention the Marionette.Async plugin.
The Renderer change is slightly off in the method name, and nothing is calling your TemplateCache object anymore.
If you're intending to use pre-compiled Handlebars functions, then you only need to do this:
Backbone.Marionette.Renderer.render = function(template, data){
return template(data);
};
If you intend to have the template loaded asynchronously and then compiled, using the TemplateLoader, your code would need to look like this:
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function (templateId, callback) {
var tmpId = templateId.replace("#", ""),
url = "/app/templates/" + tmpId + ".html";
$.get(url, function (templateHtml) {
compiledTemplate = Handlebars.compile($(templateHtml).html())
callback.call(this, compiledTemplate);
});
};
Backbone.Marionette.Renderer.renderTemplate = function (templateId, data) {
var renderer = $.Deferred();
Backbone.Marionette.TemplateCache.get(templateId, function(template){
var html = template(data);
renderer.resolve(html);
});
return renderer.promise();
};
The Renderer is responsible for calling the TemplateCache.
Side note: what article / blog post / wiki page / documentation were you using to get your code from? I have probably missed some pages that need to be updated.