JS variable from DOM chrome extension - javascript

I would like to take some js variable using a injected script into the DOM. To do so I have two files the one that inject the script into the DOM and the one that send over the value.
getPageSource.js
var s = document.createElement('script');
s.src = chrome.extension.getURL('script.js');
(document.head||document.documentElement).appendChild(s);
s.onload = function() {
s.parentNode.removeChild(s);
};
function getTag(){
document.addEventListener('ITAG_connectExtension', function(e) {
return e.detail;
});}
chrome.extension.sendMessage({
action: "getSource",
source: getTag()
});
script.js
var tagType = {};
tagType = itsClickPI;
setTimeout(function() {
document.dispatchEvent(new CustomEvent('ITAG_connectExtension', {
detail: tagType
}));
}, 0);
However the request.source in the popup.js is undefined.
popup.js
chrome.extension.onMessage.addListener(function(request, sender) {
if (request.action == "getSource") {
message.innerText = request.source;
}
});
Could you give me some lights on here?
Thanks in advance.

Your problem is with getTag() function - it is asynchronous and cannot possibly return e.detail.
Even then the logic is suspect - you're adding an event listener, but you're triggering the event before it.
So what's the intended flow?
Get ready to receive a reply.
Inject a script that sends the data (that we are ready to receive).
Send that data somewhere else.
The correct chain of events would be this:
function sendTag(tag) {
// chrome.extension.sendMessage / onMessage are deprecated!
chrome.runtime.sendMessage({
action: "getSource",
source: tag
});
}
// 1. Get ready to listen
document.addEventListener('ITAG_connectExtension', function(e) {
// This code executes asynchronously only when the event is received, so:
// 3. Send the data
sendTag(e.detail);
});
// 2. Inject the script
var s = document.createElement('script');
s.src = chrome.extension.getURL('script.js');
(document.head||document.documentElement).appendChild(s);
s.onload = function() {
s.parentNode.removeChild(s);
};
If you need to do it several times, you only need to inject the script again (as the listener is ready).
Again, use chrome.runtime.onMessage on the receiving side.

Related

How to call one function after another in Chrome Extension content script

I have two functions injectChat and firstTimeTrigger in my content script. Both of them attach a script to the body of the DOM.
I need injectChat to run first and after it's fully complete then load firstTimeTrigger. firstTimeTrigger doesn't work unless injectChat run and is fully loaded.
These are the two functions -
function injectChat(){
console.log("Injecting Chat");
const script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.setAttribute("src", `${host}/api/botpress-platform-webchat/inject.js/`);
script.setAttribute("id", 'botpress-script');
document.body.appendChild(script);
script.addEventListener('load', function(){
const botpress_settings = `window.botpressWebChat.init({host: '${host}'})`;
const settings = document.createElement('script');
settings.setAttribute("id", "botpress-settings");
settings.innerHTML = botpress_settings;
document.body.appendChild(settings);
});
};
function firstTimeTrigger(){
console.log("First Time Trigger");
chrome.runtime.sendMessage({type: "isFirstTime"}, function(response) {
if(response == true){
const botpress_trigger_1 = "window.botpressWebChat.sendEvent({ type: 'show' })";
const botpress_trigger_2 = `window.botpressWebChat.sendEvent({ type: 'proactive-trigger', platform: 'web', text: '${JSON.stringify(config)}' })`;
const trigger = document.createElement('script');
trigger.innerHTML = botpress_trigger_1 + '\n' + botpress_trigger_2;
document.body.appendChild(trigger);
}
});
};
Currently, I've been doing it like this
injectChat();
setTimeout(function(){
firstTimeTrigger();
}, 3000);
But it's very unreliable because of the various page load times due to this being inside a content script.
How do I make this happen? Promises don't work in here.
You can pass firstTimeTrigger inside of injectChat as a parameter and call it at the end of the function, like this:
function injectChat(firstTimeTrigger) {
// logic of the injectChat function...
firstTimeTrigger();
}
Welcome to Javascript's callback hell :)
In order to run a function after the previous script has finished, you have to call it at the end of the script's load event. Remember to set up the load listener before actually adding the script element to the page. For example:
function injectChat(){
console.log('Injecting Chat');
const script = document.createElement('script');
script.setAttribute('type','text/javascript');
script.setAttribute('src', `${host}/api/botpress-platform-webchat/inject.js/`);
script.setAttribute('id', 'botpress-script');
script.addEventListener('load', injectSettings); //set up the load listener ...
document.body.appendChild(script); //...before adding the script element to the page
}
function injectSettings(){
console.log('Injecting settings');
const settings = document.createElement('script');
settings.setAttribute('id', 'botpress-settings');
settings.innerHTML = `window.botpressWebChat.init({host: '${host}'})`;
settings.addEventListener('load', firstTimeTrigger); //set up listener...
document.body.appendChild(settings); //...before injecting code
}
function firstTimeTrigger(){
console.log('First Time Trigger');
chrome.runtime.sendMessage({type: 'isFirstTime'}, function(response) {
if(response == true){
const trigger = document.createElement('script');
trigger.innerHTML = `
window.botpressWebChat.sendEvent({ type: 'show' });
window.botpressWebChat.sendEvent({ type: 'proactive-trigger', platform: 'web', text: ${JSON.stringify(config)} });
`;
document.body.appendChild(trigger);
}
});
}
injectChat();

Is it possible to read the HTML body before the DOM-ready event?

I need to read the HTML body content (either as DOM or as a raw string) as soon as possible to optimize some loading of further scripts (load script A if content is X, or B if content is Y). Is it possible to avoid waiting for all sync scripts to load (which is a condition for DOM-ready) in order to read the body?
Note: changing scripts to be async is not an option.
Update: I do not control the server, so the optimization cannot be done there.
Perhaps look at the state changes by subscribing to that. Inline comments for each.
// self envoking
(function() {
console.log("self envoking in script");
}());
console.log(document.readyState); // logs "loading" first
window.onload = function() {
console.log('onload'); // I fire later
}
document.addEventListener("DOMContentLoaded", function() {
console.log('DOMContentLoaded');
});
document.addEventListener('readystatechange', function() {
if (document.readyState == "loading") {
//document is loading, does not fire here since no change from "loading" to "loading"
}
if (document.readyState == "interactive") {
//document fully read. fires before DOMContentLoaded
}
if (document.readyState == "complete") {
//document fully read and all resources (like images) loaded. fires after DOMContentLoaded
}
console.log(document.readyState)
});
<script id="myscript">
// self envoking
(function() {
console.log("self envoking with id");
window.customevent = new Event('customload');
// Listen for the event.
document.addEventListener('customload', function(e) {
console.log("customload fired");
}, false);
// another way
var scriptNode = document.createElement('script');
scriptNode.innerHTML = 'console.log("happy happy day inject");';
document.head.appendChild(scriptNode);
}());
</script>
<script id="another">
var thisScriptElement = document.getElementById("another");
// self envoking
(function() {
console.log("Another self envoking with id");
// Dispatch the event.
document.dispatchEvent(customevent);
}());
</script>

Unit test to assert that a boolean has been changed when a scripts load event fires

I have a script that loads ckeditor.js and once loaded it disables auto inline ..
var script = window.document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', 'ckeditor.js');
window.document.body.appendChild(script);
script.addEventListener('load', function() {
CKEDITOR.disableAutoInline = true;
});
Is there a way I can assert that CKEDITOR.disableAutoInline is set to true once the script's load event handler is triggered?
it('should ensure disableAutoInline is set to true when the ckeditor.js file is loaded in the browser', function() {
window.CKEDITOR = {
disableAutoInline: false
};
var ckeditorjs = window.document.querySelectorAll('[src=\'/ckeditor.js\']');
expect(ckeditorjs.length).to.equal(1);
});
Need to actually trigger the load event on the script ..
var ckeditorjs = window.document.querySelectorAll('[src=\'/ckeditor.js\']');
window.CKEDITOR = {
disableAutoInline: null
};
var event = new Event('load');
ckeditorjs[0].dispatchEvent(event);
expect(window.CKEDITOR.disableAutoInline).to.equal(true);

Loading external JavaScript file

I have a cross platform app built using PhoneGap/Cordova.
I am trying to implement a function that runs an external JavaScript file when a controller loads. I am following a solution from HERE. And similarly HERE. But I want the JavaScript to execute without the window.open event, i.e. I want to run executeScript as soon as the device is ready.
How do I call the executeScript() without defining the var ref first though?
var navigation = angular.module("navigation", []);
navigation.controller("Navigation", function ($scope) {
var init = function () {
document.addEventListener("deviceready", onDeviceReady, false);
};
init();
function onDeviceReady() {
// LOAD EXTERNAL SCRIPT
var ref = window.open('http://www.haruair.com/', '_blank', 'location=yes, toolbar=yes, EnableViewPortScale=yes');
ref.addEventListener("loadstop", function () {
ref.executeScript(
{ file: 'http://haruair.com/externaljavascriptfile.js' },
function () {
ref.executeScript(
{ code: 'getSomething()' },
function (values) {
var data = values[0];
alert("Name: " + data.name + "\nAge: " + data.age);
});
}
);
});
});
You could try to add the script in the index.html file and do whatever you want from JS. Also, you must add to your whitelist this endpoint.
<!-- index.html -->
<script>
function onCustomLoad() {
//do stuff
}
</script>
<script src="your-custom-script" onload="onCustomLoad"></script>
you could use
var script = document.createElement("script");
script.type = "text/javascript";
script.id = "some_id";
script.src = 'http://haruair.com/externaljavascriptfile.js';
document.head.appendChild(script);
then call the function once finished

Load script on event and trigger the same event

I am trying to add certain javascript files on an event, say click. I am trying to use the Javascript to be used on same event, only if it is triggered. This is because the scripts are slowing down the page load and there is no need for the scripts otherwise.
Can I just move the scripts to the footer and be all set, or do this pro grammatically via loading them only when needed - via event triggering instead? Below is what I have so far:
HTML:
<a id="customId" href="#myLink"></a>
Javascript:
$(document).ready(function() {
//The async addition
var myJS = {
lazyload : function(scriptSrc) {
if(!this.isPresent(scriptSrc)){
var scriptTag = document.createElement('script');
scriptTag.src = scriptSrc;
scriptTag.async = true;
document.getElementsByTagName('head')[0].appendChild(scriptTag);
}
return false;
}
};
//The event trigger needs to do something using the said script
if($('#customId')){
//Approach 1:
var mapEl = document.getElementById("customId");
mapEl.addEventListener("click", customEventHandler, false);
//mapEl.dispatchEvent(event);
//*where
customEventHandler : function(e){
e.preventDefault;
myJS.lazyload('/jsfile.js');
// Update or use link relative #href (not complete path) and use the javascript without navigating out of page.
//e.currentTarget.dispatchEvent(?);
}
//2nd attempt: Adds the script, but not able to trigger event to use JS
$('#customId').click(function(event) {
event.preventDefault();
myJS.lazyload('/jsfile.js');
//Either approach:
//Trigger the custom event to do an actual click after doing the lazy load, using the JS file
(click); $('#customId').trigger('click'); //Is this correct on same element ID
});
}
}
Try using onload event of script element, defining a custom event to prevent recursively calling native click event handler on element
$(document).ready(function() {
//The async addition
var myJS = {
lazyload: function(scriptSrc, id, type) {
// use `.is()` to check if `script` element has `src`
// equal to `scriptSrc`
if (!$("script[src='"+ scriptSrc +"']").is("*")) {
var scriptTag = document.createElement("script");
scriptTag.src = scriptSrc;
scriptTag.async = true;
// use `script` `onload` to trigger custom event `customClick`
scriptTag.onload = function() {
$(id).trigger(type)
};
document.getElementsByTagName("head")[0].appendChild(scriptTag);
}
return false;
}
};
$("#customId").on("click", function() {
myJS.lazyload("jsfile.js", "#" + this.id, "customClick");
})
// do stuff at `customClick` event
.one("customClick", function(e) {
customClick(e.type)
});
});
plnkr http://plnkr.co/edit/Rw4BRAfSYlXXe5c6IUml?p=preview

Categories