How to share an object between content script and a window script? - javascript

I needed to get deeper into the page so my content script is really just an injector, like this
function injectScript(file) {
var th = document.getElementsByTagName('body')[0];
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('src', file);
th.appendChild(s);
}
injectScript( chrome.extension.getURL('/cs.js') );
Now I need to transport settings (stored in the content scripts local storage) into there as well during runtime. I can do it once no problem by creating another script node and adding the variable declaration and value in its innerHTML. Updating it this way doesn't work as well though.
Currently I have a hidden input added to the page and I write the value there and have the script on the other side periodically check and update the local one. This is just "ugh" and "eww" to me though. Is there any other way I can share the settings object between the two?

Related

Dynamically add javascript script to page with code not src

I am trying to figure out how to inject this script into a site using Tampermonkey. Here is what I have so far but the onBrazeSdkLoaded event is never being called so I don't think its being inserted correctly. I have tried a few iterations but no luck. To reference the exact script I am trying to include here is the link to the public docs.
https://www.braze.com/docs/developer_guide/platform_integration_guides/web/initial_sdk_setup/#install-gtm
const script = document.createElement("script");
var code = document.createTextNode(`+function(a,p,P,b,y){a.braze={};a.brazeQueue=[];for(var s="BrazeSdkMetadata DeviceProperties Card Card.prototype.dismissCard Card.prototype.removeAllSubscriptions Card.prototype.removeSubscription Card.prototype.subscribeToClickedEvent Card.prototype.subscribeToDismissedEvent Card.fromContentCardsJson Banner CaptionedImage ClassicCard ControlCard ContentCards ContentCards.prototype.getUnviewedCardCount Feed Feed.prototype.getUnreadCardCount ControlMessage InAppMessage InAppMessage.SlideFrom InAppMessage.ClickAction InAppMessage.DismissType InAppMessage.OpenTarget InAppMessage.ImageStyle InAppMessage.Orientation InAppMessage.TextAlignment InAppMessage.CropType InAppMessage.prototype.closeMessage InAppMessage.prototype.removeAllSubscriptions InAppMessage.prototype.removeSubscription InAppMessage.prototype.subscribeToClickedEvent InAppMessage.prototype.subscribeToDismissedEvent InAppMessage.fromJson FullScreenMessage ModalMessage HtmlMessage SlideUpMessage User User.Genders User.NotificationSubscriptionTypes User.prototype.addAlias User.prototype.addToCustomAttributeArray User.prototype.addToSubscriptionGroup User.prototype.getUserId User.prototype.incrementCustomUserAttribute User.prototype.removeFromCustomAttributeArray User.prototype.removeFromSubscriptionGroup User.prototype.setCountry User.prototype.setCustomLocationAttribute User.prototype.setCustomUserAttribute User.prototype.setDateOfBirth User.prototype.setEmail User.prototype.setEmailNotificationSubscriptionType User.prototype.setFirstName User.prototype.setGender User.prototype.setHomeCity User.prototype.setLanguage User.prototype.setLastKnownLocation User.prototype.setLastName User.prototype.setPhoneNumber User.prototype.setPushNotificationSubscriptionType InAppMessageButton InAppMessageButton.prototype.removeAllSubscriptions InAppMessageButton.prototype.removeSubscription InAppMessageButton.prototype.subscribeToClickedEvent automaticallyShowInAppMessages destroyFeed hideContentCards showContentCards showFeed showInAppMessage toggleContentCards toggleFeed changeUser destroy getDeviceId initialize isPushBlocked isPushPermissionGranted isPushSupported logCardClick logCardDismissal logCardImpressions logContentCardImpressions logContentCardsDisplayed logCustomEvent logFeedDisplayed logInAppMessageButtonClick logInAppMessageClick logInAppMessageHtmlClick logInAppMessageImpression logPurchase openSession requestPushPermission removeAllSubscriptions removeSubscription requestContentCardsRefresh requestFeedRefresh requestImmediateDataFlush enableSDK isDisabled setLogger setSdkAuthenticationSignature addSdkMetadata disableSDK subscribeToContentCardsUpdates subscribeToFeedUpdates subscribeToInAppMessage subscribeToSdkAuthenticationFailures toggleLogging unregisterPush wipeData handleBrazeAction".split(" "),i=0;i<s.length;i++){for(var m=s[i],k=a.braze,l=m.split("."),j=0;j<l.length-1;j++)k=k[l[j]];k[l[j]]=(new Function("return function "+m.replace(/\./g,"_")+"(){window.brazeQueue.push(arguments); return true}"))()}window.braze.getCachedContentCards=function(){return new window.braze.ContentCards};window.braze.getCachedFeed=function(){return new window.braze.Feed};window.braze.getUser=function(){return new window.braze.User};(y=p.createElement(P)).type='text/javascript';
y.src='https://js.appboycdn.com/web-sdk/4.2/braze.min.js';
y.async=1;(b=p.getElementsByTagName(P)[0]).parentNode.insertBefore(y,b)`);
script.text = code;
document.head.appendChild(script);
script.onload = onBrazeSdkLoaded;
A load event fires when a resource has been loaded from an external source.
Since your script is getting its program from a text node inside the script element, instead of from a URL assigned to the src attribute, there is no external source.
Consequently, there is no load event.
If you want onBrazeSdkLoaded to fire when the script you create on line 1 has loaded, then just include onBrazeSdkLoaded() in the string you assign to code.
If you want it to fire when https://js.appboycdn.com/web-sdk/4.2/braze.min.js loads, then you need to:
Ensure it is in the global scope
Write code that assigns it to y.onload inside the string you assign to code
Fix the syntax error(s) in the JS in that string

Create an embedded JavaScript in a Cross Domain Host Page which is not affected by the Host Page CSS?

Most javascript widget which can be embedded into a website use the following structure. First you embed a code snipped like this:
<script type="text/javascript">
window.$zopim||(function(d,s){var z=$zopim=function(c){
z._.push(c)},
$=z.s=d.createElement(s),
e=d.getElementsByTagName(s)[0];
z.set=function(o){
z.set._.push(o)
};
z._=[];
z.set._=[];
$.async=!0;
$.setAttribute('charset','utf-8');
$.src='//v2.zopim.com/?2342323423434234234';
z.t=+new Date;
$.type='text/javascript';
e.parentNode.insertBefore($,e)})(document,'script');
</script>
Then, when load your page this script creates a html structure like this:
<div class="widget-class">
<iframe src="about:blank">
// the content of the widget
</iframe>
</div
I see this same structure in many chat services like:
https://en.zopim.com/
http://banckle.com/
https://www.livechatinc.com/
All have in common that their iframe does not have a src, i.e., an URL attached.
Update: Here is the script I use to load my widget code into a third party website:
<script type="text/javascript">
(function(d){
var f = d.getElementsByTagName('SCRIPT')[0], p = d.createElement('SCRIPT');
window.WidgetId = "1234";
p.type = 'text/javascript';
p.setAttribute('charset','utf-8');
p.async = true;
p.src = "//www.example.com/assets/clientwidget/chatwidget.nocache.js";
f.parentNode.insertBefore(p, f);
}(document));
</script>
I want that the CSS of the site where the GWT widget is integrated should not influence the CSS of the GWT widget. I will prevent that the CSS of the host page influence the CSS of my GWT widget.
Note: I want to have access to tho host website from my GWT widget too.
The domain of the host page is www.example.com and the domain of the iframe is www.widget.com. I also want to set cookies of the host domain from the iframe.
What is the procedure of building a widget running on such a structure? How is the content of the iframe being set? Is there a pattern for that? How can I do that with GWT
I don't know GWT, but you can easily achieve this in plain JavaScript.
Let's assume you're creating an online-count widget. At first, create an iframe:
<script id="your-widget">
// Select the script tag used to load the widget.
var scriptElement = document.querySelector("your-widget");
// Create an iframe.
var iframe = document.createElement("iframe");
// Insert iframe before script's next sibling, i.e. after the script.
scriptElement.parentNode.insertBefore(iframe, scriptElement.nextSibling);
// rest of the code
</script>
Then fetch the online count using JSONP (see What is JSONP all about?), for example:
// The URL of your API, without JSONP callback parameter.
var url = "your-api-url";
// Callback function used for JSONP.
// Executed as soon as server response is received.
function callback(count) {
// rest of code
}
// Create a script.
var script = document.createElement("script");
// Set script's src attribute to API URL + JSONP callback parameter.
// It makes browser send HTTP request to the API.
script.src = url + "?callback=callback";
Then handle server response (inside the callback() function):
// Create a div element
var div = document.createElement("div");
// Insert online count to this element.
// I assume that server response is plain-text number, for example 5.
div.innerHTML = count;
// Append div to iframe's body.
iframe.contentWindow.document.body.appendChild(div);
That's all. Your whole code could look like this:
Snippet to insert into third party website:
<script type="text/javascript">
(function(d){
var f = d.getElementsByTagName('SCRIPT')[0], p = d.createElement('SCRIPT');
window.WidgetId = "1234";
p.type = 'text/javascript';
p.setAttribute('charset','utf-8');
p.async = true;
p.id = "your-widget";
p.src = "//www.example.com/assets/clientwidget/chatwidget.nocache.js";
f.parentNode.insertBefore(p, f);
}(document));
</script>
JavaScript file on your server:
// Select the script tag used to load the widget.
var scriptElement = document.querySelector("#your-widget");
// Create an iframe.
var iframe = document.createElement("iframe");
// Insert iframe before script's next sibling, i.e. after the script.
scriptElement.parentNode.insertBefore(iframe, scriptElement.nextSibling);
// The URL of your API, without JSONP callback parameter.
var url = "your-api-url";
// Callback function used for JSONP.
// Executed as soon as server response is received.
function callback(count) {
// Create a div element
var div = document.createElement("div");
// Insert online count to this element.
// I assume that server response is plain-text number, for example 5.
div.innerHTML = count;
// Append div to iframe's body.
iframe.contentWindow.document.body.appendChild(div);
}
// Create a script.
var script = document.createElement("script");
// Set script's src attribute to API URL + JSONP callback parameter.
// It makes browser send HTTP request to the API.
script.src = url + "?callback=callback";
EDIT:
if you want your widget to not be influenced by any css from the "outside" you have to load into an iframe.
code to add to your website to load any gwt project/widget:
<iframe id="1234" src="//www.example.com/assets/Chatwidget.html" style="border: 1px solid black;" tabindex="-1"></iframe>
notice: that im NOT loading the nocache.js but the yourwidget.html file.
like this all your clases insde the frame wont be affected by any class from the outside.
to access anything outside ofthis iframe you can use jsni methods. this will only work if the domain of your iframe and the thirdpartysite are the same. otherwise youve to use window.postMessage:
public native static void yourMethod() /*-{
$wnd.parent.someMethodFromOutsideTheIframe();
}-*/;
EDIT2:
by using the snippet from above you make sure that your widget is not influened by any css from the hostpage.
to get the hostpage url from inside the widget simply add this function:
private native static String getHostPageUrl() /*-{
return $wnd.parent.location.hostname;
}-*/;
EDIT3:
since you are on 2 different domains, you have to use window.postMessage.
here one little example to get you going:
besides the iframe you have to add a event listener to the window of your example.com, that listens for the messages from your iframe. you also check if the messages comes form the correct origin.
<script>
// Create IE + others compatible event handler
var eventMethod = window.addEventListener ? "addEventListener"
: "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage"
: "message";
// Listen to message from child window
eventer(messageEvent, function(e) {
//check for the correct origin, if wanted
//if ( e.origin !== "http://www.widget.com" )
// return
console.log('parent received message!: ', e.data);
//here you can set your cookie
document.cookie = 'cookie=widget; expires=Fri, 1 Feb 2016 18:00:00 UTC; path=/'
}, false);
</script>
From inside your widget you call this method:
public native static void postMessageToParent(String message) /*-{
//message to sent, the host that is supposed to receive it
$wnd.parent.postMessage(message, "http://www.example.com");
}-*/;
i put a working example on pastebin:
javascript to insert into your page: http://pastebin.com/Y0iDTntw
gwt class with onmoduleload: http://pastebin.com/QjDRuPmg
Here's a full functional simple widget expample project I wrote in cloud9 (online IDE) with javascript, please feel free to request an access if you want to edit it, viewing is publicly available (for registered users - registration is free).
sources:
https://ide.c9.io/nmlc/widget-example,
result:
https://widget-example-nmlc.c9users.io/index.html
As for the question about how do they do it:
It seems that zopim builds their widgets gradually on the client side, defining and requiring basic modules (like these __$$__meshim_widget_components_mobileChatWindow_MainScreen), which are consist from submodules and then process everything with __$$__jx_ui_HTMLElement builder which creates HTML elements and appends them to provided parent nodes. All that compiles to the resulting HTML of the chatbox. Btw, judging by the names of some components, it seems, they build their widgets with some "meshim" library, but I have never heard of this library.
this.dom.src='about:blank'
this.appendToParent(!0)
var H=this.iwin=this.dom.contentWindow
var I=this.idoc=r.extend(H.document)
I.write(G)
I.close()
This, I guess, is the place where zopim service creates an iframe for their widgets. I'm not sure why they are using document.write instead of appendChild (document.write drops event bindings), but I have implemented both versions - they are pretty much the same except setIframeContents and addHtmlElement functions.
Hope someone will find this useful :).
1) There are many different ways to load content to iframe. Iframe have isolated content. iframe that you put in host page, does not have src, because of browser secure politic, you can't simply load content from other domains. But you can load js from other domain.For this porpuse you need usw JSONP
2) to share cookies with host page and widget iframe, you need use postMessage api like in this post

Passing a variable before injecting a content script

I am working on a Chrome Extension that works mainly within a pop-up.
I would like the user to enter some text (a string) into an input field in the pop-up, and this string will serve as a "variable" in a script I would like to inject and run on a specific page.
I have tried achieving this by making a content script that will execute the script, using the following well documented way:
var s = document.createElement('script');
s.src = chrome.runtime.getURL('pageSearch.js');
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
Basically, I would like to pass the user's input all the way to the code in pageScript.js before executing the script on the page.
What would be the best way to approach this? I will not be getting any information back to the extension.
Thanks.
To pass a variable from the popup to the dynamically inserted content script, see Pass a parameter to a content script injected using chrome.tabs.executeScript().
After getting a variable in the content script, there are plenty of ways to get the variable to the script in the page.
E.g. by setting attributes on the script tag, and accessing this <script> tag using document.currentScript. Note: document.currentScript only refers to the script tag right after inserting the tag in the document. If you want to refer to the original script tag later (e.g. within a timer or an event handler), you have to save a reference to the script tag in a local variable.
Content script:
var s = document.createElement('script');
s.dataset.variable = 'some string variable';
s.dataset.not_a_string = JSON.stringify({some: 'object'});
s.src = chrome.runtime.getURL('pageSearch.js');
s.onload = function() {
this.remove();
};
(document.head||document.documentElement).appendChild(s);
pageSearch.js:
(function() {
var variable = document.currentScript.dataset.variable;
var not_a_string = JSON.parse(document.currentScript.dataset.not_a_string);
// TODO: Use variable or not_a_string.
})();

Is there a way to refresh just the javascript include while doing development?

While doing development on a .js file I'd like to just refresh that file instead of the entire page to save time. Anyone know of any techniques for this?
Here is a function to create a new script element. It appends an incremented integer to make the URL of the script unique (as Kon suggested) in order to force a download.
var index = 0;
function refreshScript (src) {
var scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
scriptElement.src = src + '?' + index++;
document.getElementsByTagName('head')[0].appendChild(scriptElement);
}
Then in the Firebug console, you can call it as:
refreshScript('my_script.js');
You'll need to make sure that the index itself is not part of the script being reloaded!
The Firebug Net panel will help you see whether the script is being downloaded. The response status should be "200 OK" and not "304 Not Modified. Also, you should see the index appended in the query string.
The Firebug HTML panel will help you see whether the script element was appended to the head element.
UPDATE:
Here is a version that uses a timestamp instead of an index variable. As #davyM suggests, it is a more flexible approach:
function refreshScript (src) {
var scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
scriptElement.src = src + '?' + (new Date).getTime();
document.getElementsByTagName('head')[0].appendChild(scriptElement);
}
Alexei's points are also well-stated.
I suggest you to use Firebug for this purpose.
See this video, it helped me a lot.
http://encosia.com/2009/09/21/updated-see-how-i-used-firebug-to-learn-jquery/
If you're talking about the unfortunate case of client-side/browser caching of your .js file, then you can simply version your .js file. You can:
Rename the .js file itself (not preferred)
Update the include line to reference yourfile.js?1, yourfile.js?2, etc.. Thus forcing the browser to request the latest version from the server. (preferred)
Unfortunately, you have to refresh the web page to see edits to your JavaScript take place. There is no way that I know of to edit JavaScript in "real-time" and see those edits effect without a refresh.
You can use Firebug to insert new JavaScript, and make real-time changes to DOM objects; but you cannot edit JavaScript that has already been run.
If you just fed up refilling the forms while developing just use form recover extensions like this one https://addons.mozilla.org/ru/firefox/addon/lazarus-form-recovery/

showing several JS variables value in chrome extensions

is it possible to get my website 2,3 js variables in an extensions i build so i will be able to see the info behind the site i build
the extension will help me develop my sites
Seeing the variables of a given website (using Content Scripts) is possible. Just inject your own content script, and create an script tag that reads your variables. You cannot use those variables, or modify them on your extension due to some limitations of what Content script can do. You can read the following docs on Communication with the embedding page.
For example the following will read a JS variable in the webpage and transfer its contents to the background page so we can let our extension deal with it. You will notice in the background page inspector, that the variable is successfully passed:
content_script.js
// JS script injection, so that we can read the JS class 'InternalJSVariable'
var postFriendMap = function() {
var textarea = document.getElementById('transfer-dom-area');
textarea.value = JSON.stringify(InternalJSVariable);
};
// Create a dummy textarea DOM.
var textarea = document.createElement('textarea');
textarea.setAttribute('id', 'transfer-dom-area');
textarea.style.display = 'none';
document.body.appendChild(textarea);
// Start injecting the JS script.
var script = document.createElement('script');
script.appendChild(document.createTextNode('(' + postFriendMap + ')();'));
document.body.appendChild(script);
// Inform our world that we have received the friend map data.
chrome.extension.sendRequest({internalVariable: textarea.value});
// Clean up since we no longer need this.
document.body.removeChild(textarea);
background.html
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.internalVariable) {
var internal_object = JSON.parse(request.internalVariable);
console.log(internal_object );
}
});

Categories

Resources