How to pass data from content-script to page-level? - javascript

I'm injecting all my js code to front page, but it needs pictures for ui and stuff, that can be imported only with the help of chrome.extension.getUrl and can be called only from content-script, so I've found tons of advices how to pass data to content page, and nothing of about how pass data back, is it possible at all?
My code now looks like this:
my js code, that will be injected with other code:
var Content = {};
$(document).contentReady = function(content) {
Content = content;
$(document).ready(function () {/*cool stuff here, that require content*/});
}
var event = new CustomEvent('LoadContent');
window.dispatchEvent(event);
content-script:
document.querySelector('head').appendChild(jsCode);
window.addEventListener("LoadContent", function(evt) {
var content =
{
data: "url(" + chrome.extension.getURL('content.smth') + ")"
};
document.contentReady(content);
}, false);
And, obviously, I get document.contentReady is not a function
But declaring function in document was the only(!) advice of about how to pass data back from content-script after about 2 hours of googling.

Nothing stops you from making the CustomEvent-based communication bi-directional, and it can pass data with detail property:
// Page script
window.addEventListener('RecieveContent', function(evt) {
// do something cool with evt.detail
});
var event = new CustomEvent('LoadContent');
window.dispatchEvent(event);
// Content script
window.addEventListener('LoadContent', function(evt) {
content = /* ... */
var event = new CustomEvent('RecieveContent', {detail: content});
window.dispatchEvent(event);
});
A more in-depth answer can be found here.
However, you should ask yourself whether you even need the page-level script to query for data, since you fully control when it's injected. You can use uni-directional approach after you make sure the code has executed:
// Page script
window.addEventListener('RecieveContent', function(evt) {
// do something cool with evt.detail
});
// Content script
jsCode.onload = function() {
// This fires after the page script finishes executing
content = /* ... */
var event = new CustomEvent('RecieveContent', {detail: content});
window.dispatchEvent(event);
}
document.querySelector('head').appendChild(jsCode);

You can pass JS data to the page by creating a new script tag. For example:
function injectScript(code) {
var body = document.getElementsByTagName('body')[0];
var s = document.createElement('script');
s.innerHTML = code;
body.appendChild(s);
}
injectScript('var foo = 2;');
So for your particular example, you should be able to do:
injectScript('document.contentReady({data: url(' + blahblah + '})');
Not pretty (what is when you're working with overwriting content scripts?) but it works.

Content Scripts do not share window object with normal scripts on the page. Both of them work on different context.
In your case, you are registering an event listener on window and listening for the event on other context (window). Hence, your event listener will never be called.
However, there is one alternative approach I can see to communicate between content script and normal script is by using MutationObserver.
Idea
Define a node with some Id under which you will create subnodes corresponding to an event.
Register Mustation Observer in your script.
From content script, add the nodes with data as data-* api.
Implementation Example
Content Script
var submitEvent = function(category, action, label) {
var eventObserverPlaceholder = document.getElementById('events-observer-placeholder'),
$eventEl = $('<span></span>').attr({
'data-category': category,
'data-action': action,
'data-label': label
});
eventObserverPlaceholder.appendChild($eventEl.get(0));
};
Normal Script for registering Mutation Observer:
RQ.Methods.addObserverForEvents = function(targetNode) {
var observer = new MutationObserver(RQ.Methods.handleMutationList);
// Notify me when a new child is added
var observerConfig = {
attributes: false,
childList: true,
characterData: false
};
observer.observe(targetNode, observerConfig);
return observer;
};
RQ.mutationObserver = RQ.Methods.addObserverForEvents(document.getElementById('events-observer-placeholder'));
Links
https://davidwalsh.name/mutationobserver-api
https://developer.mozilla.org/en/docs/Web/API/MutationObserver
Working Example:
I have used the same approach in Requestly Chrome Extension for submitting events to Google Analytics.
Content Script: https://github.com/requestly/chrome-extension/blob/master/src/Shared/utils.js#L26
Normal Script: https://github.com/requestly/web/blob/gh-pages/js/scripts/tracker.js#L35

Related

How to fireEvent outside of ConsumeEvent function in icCube web-reporting

i would like do a simple fireEvent("Refresh","")
from javascript outside of consumeEvent function.
as i want to be able to do a setinterval that would fireEvent "Refresh"
and put the event name inside a table 'do refresh query' in the web reporting
so eventually the table will refresh itself every 1 minute for example.
(i want to be able to refresh every table i have in the dashboard separately with different time interval)
the problem is that i'm able to do fireEvent only from the consumeEvent function
and then use context.fireEvent("Refresh","") but this can happen every time i have a different event occurring from the dashboard and it's not good enough
Event could be thrown anywhere with context's event manager instance:
<script type="text/javascript">
context.eventMgr().fireExternalEvent("eventName", eventValue)
</script>
Also you can fire events if you have access to ic3Reporting instance:
for example:
var ic3Application = ic3.startReport(options);
In that case you can fire app events in such way :
<script type="text/javascript">
//get ic3application instance
var ic3Application = ic3.startReport(options);
setInterval(function(){
ic3Application.fireEvent('table1-refresh', {})
},60000)
setInterval(function(){
ic3Application.fireEvent('table2-refresh', {})
},120000)
</script>
Then just set event names to "do Refresh Query" tables' event.
UPDATE
Version of script inside ic3report.html
<script type="text/javascript">
var ic3root = "../"
var ic3rootLocal = "../"
var options = {
root: ic3root,
rootLocal: ic3rootLocal,
callback: function () {
$('#intro').remove();
var options = {
<!-- ic3-start-report-options (DO NOT REMOVE - USED TO GENERATE FILES) -->
};
var ic3Application = ic3.startReport(options);
setInterval(function () {
ic3Application.fireEvent('ic3-table', {})
},20000)
};
ic3ready(options);
</script>
UPDATE
Here is a report with an example.

Handling State changes jQuery and History.js

Ok, so I need some insight into working with History.js and jQuery.
I have it set up and working (just not quite as you'd expect).
What I have is as follows:
$(function() {
var History = window.History;
if ( !History.enabled ) {
return false;
}
// Capture all the links to push their url to the history stack and trigger the StateChange Event
$('.ajax-link').click(function(e) {
e.preventDefault();
var url = this.href; //Tells us which page to load
var id = $(this).data('passid'); //Pass ID -- the ID in which to save in our state object
e.preventDefault();
console.log('url: '+url+' id:'+id);
History.pushState({ 'passid' : id }, $(this).text(), url);
});
History.Adapter.bind(window, 'statechange', function() {
console.log('state changed');
var State = History.getState(),
id = State.data.editid; //the ID passed, if available
$.get(State.url,
{ id: State.data.passid },
function(response) {
$('#subContent').fadeOut(200, function(){
var newContent = $(response).find('#subContent').html();
$('#subContent').html(newContent);
var scripts = $('script');
scripts.each(function(i) {
jQuery.globalEval($(this).text());
});
$('#subContent').fadeIn(200);
});
});
});
}); //end dom ready
It works as you'd expect as far as changing the url, passing the ID, changing the content. My question is this:
If I press back/forward on my browser a couple times the subContent section will basically fadeIn/fadeOut multiple times.
Any insight is appreciated. Thanks
===================================================
Edit: The problem was in my calling all of my <script> and Eval them on each statechange. By adding a class="no-reload" to the history controlling script tag I was able to do:
var scripts = $('script').not('.no-reload');
This got rid of the problem and it now works as intended. Figure I will leave this here in case anyone else runs into the same issue as I did.
The problem was in my calling of all of my <script> and Eval them on each statechange. By adding a class="no-reload" to the history controlling script tag I was able to do:
var scripts = $('script').not('.no-reload');
This got rid of the problem and it now works as intended. Figure I will leave this here in case anyone else runs into the same issue as I did.

Dynamically adding script to a div does not execute the script

PROBLEM:
Why does this not show the alert? And how can I make it so?
<script>
function onSuccess() {
var response= "<script> alert(1);</\script>";
document.getElementById("xxx").innerHTML = response;
}
</script>
<div id="xxx">existing text</div>
<button id="click" onclick="onSuccess();">click</button>
http://jsfiddle.net/7hWRR/1/
This is just a simplified version of my problem. In our application (in one very old module in particular) we use an ancient home-grown AJAX class which just innerHTMLs all AJAX responses.Traditionally we have only sent back HTML as AJAX response but I would like to execute JS in the success handler.I do not have access to the JS file so cannot modify the way the response is handled. I can only work with the fact that the success handler calls div.innerHTML='<my response>'
So stupid as it may be, I'm hoping for some help using these constraints!
SIMILAR LINKS:
Dynamically adding script element to a div does not execute the script
Dynamically added script will not execute
Caveat: Here I'm assuming the <div> on which the results are inserted is known.
A possible solution is to use a MutationObserver (and the DOMNodeInserted event, to support IE 9 and 10) to watch said <div> for changes on its contents, and execute the code on any inserted <script> tags.
Example built upon your jsFiddle:
watchNodeForScripts(document.getElementById("xxx"));
function watchNodeForScripts(scriptRecipient) {
if ('MutationObserver' in window) {
// Prefer MutationObserver: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
watchUsingMutationObserver();
} else {
// Fallback to Mutation Events: https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Events/Mutation_events
watchUsingDeprecatedMutationEvents();
}
function watchUsingMutationObserver() {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
var i, addedNodes = mutation.addedNodes;
for (i = 0; i < addedNodes.length; i++) {
handleAddedNode(addedNodes[i]);
}
});
});
observer.observe(scriptRecipient, {
childList: true
});
}
function watchUsingDeprecatedMutationEvents() {
scriptRecipient.addEventListener("DOMNodeInserted", function (event) {
handleAddedNode(event.target);
});
}
function handleAddedNode(node) {
// Don't try to execute non-script elements
if (!(node instanceof HTMLScriptElement)) return;
// Don't try to execute linked scripts
if (node.src !== "") return;
// Use 'new Function' instead of eval to avoid
// the creation of a (undesired) closure
fn = new Function(node.textContent);
fn.call(window);
}
}
Updated fiddle: http://jsfiddle.net/7hWRR/13/
Edit: Changed innerText to the more cross-compatible textContent.
Edit2: Don't execute code that isn't inside a <script> element.
Edit3: Don't execute scripts with the src attribute, and add mutation events fallback

Javascript static variables and using in different pages

I have a jQuery plugin in my layout page header:
<script src="#Url.Content("~/Scripts/js/kendo.web.min.js")"></script>
<script src="#Url.Content("~/Scripts/app/jsCommon.js")"></script>
<script src="#Url.Content("~/Scripts/app/Layout.js")"></script>
and my layout.js:
(function ($) {
var Layout = function (node, options) {
this.node = node;
this.options = $.extend({
url: ""
}, options);
$(this.node).find('.HButton').bind('click', $.proxy(this.HButtonClicked, this));
};
Layout.prototype = {
constructor: Layout,
_loadBackground: function () {
debugger;
//load second now 'Common.currentTarget' have been lost
$(Common.currentTarget).removeClass();
$(Common.currentTarget).addClass(".HButton_Selected");
},
HButtonClicked: function (e) {
debugger;
//load first
Common.currentTarget = e.currentTarget;
}
}
$.fn.Layout = function (options) {
return this.each(function () {
$(this).data('Layout', new Layout(this, options));
});
};
}(jQuery));
in the other side I have a share repository javascript object like this :
function common() {
}
common.currentTarget = null;
var Common = new common();
then in the other page I've triggered an event like following :
<script>
$(document).ready(function () {
var layout = $("#layout").data("Layout");
$(layout).trigger("_loadBackground")
});
</script>
when the HButton element click happened at the first I'm writing the object inside the "Common.currentTarget" and it saved successfully when I've watched variable but when another page loads completely and then trigger the event "_loadBackground" the value of "Common.currentTarget" have been lost, my question is how I can define a static variable like this to be permanent in whole of my pages?
You can set a cookie from javascript to store the data, and then access the cookie from another page. Cookies can persist just during the browser session, or you can give them an expiration. For HTML5, there is local storage.
All JavaScript data is unloaded when the page is changed or refreshed. There is no way around this in JavaScript itself. You will have to send data to the server instead. Probably the easiest way to do this is to store your data in a hidden field:
<input type="hidden" id="storable" />
....
document.getElementById("storable").value = // whatever value you want to store
Then on the server side you can transfer that data to the new page.
If you are redirecting client side, use a query parameter instead.

How to determine if content in popup window is loaded

I need to set some contextData for a popup window from its parent. I try this:
var some contextData = {};
$(function() {
$('#clickme').click(function() {
var w = window.open('http://jsfiddle.net');
w.contextData = contextData
//w.context data is null in the popup after the page loads - seems to get overwritten/deleted
});
});
It doesn't work, so my next thought, wait until content is loaded
var some contextData = {};
$(function() {
$('#clickme').click(function() {
var w = window.open('http://jsfiddle.net');
w.onload = function() {
//Never Fires
w.contextData = contextData;
}
});
});
See this fiddle. My onload method never fires.
This works:
var some contextData = {};
$(function() {
$('#clickme').click(function() {
var w = window.open('http://jsfiddle.net');
setTimeout(function(){
if(w.someVariableSetByThePageBeingLoaded) {
w.contextData = contextData;
}
else{
setTimeout(arguments.callee, 1);
}
}, 1);
});
});
But has obvious elegance problems (but is the current work around).
I know you can go the other way (have the popup call back to a method on the opener/parent, but this forces me to maintain some way of looking up context (and I have to pass the key to the context to the popup in the query string). The current method lets me capture the context in a closure, making my popup a much more reusable piece of code.
I am not trying to do this cross domain - both the parent and popup are in the same domain, although the parent is an iframe (hard to test with jsfiddle).
Suggestions?
If you are doing this with an iframe try it this way
HTML
<button id="clickme">Click Me</button>
<iframe id="framer"></iframe>
​
Javascript
$(function() {
$('#clickme').click(function() {
$("#framer").attr("src","http://jsfiddle.net");
$("#framer")[0].onload = function(){
alert('loaded');
};
});
});​
I updated your jsfiddle http://jsfiddle.net/HNvn3/2/
EDIT
Since the above is completely wrong this might point you in the right direction but it needs to be tried in the real environment to see if it works.
The global variable frames should be set and if you
window.open("http://jsfiddle.net","child_window");
frames["child_window"] might refer to the other window
I got javascript access errors when trying it in jsfiddle - so this might be the right track
EDIT2
Trying out on my local dev box I was able to make this work
var w = window.open("http://localhost");
w.window.onload = function(){
alert("here");
};
the alert() happened in the parent window

Categories

Resources