postMessage Function runs repeatedly without a message being sent - javascript

I have an iframe with a form in it, and in that iframe I have the following:
// Send a message to the parent window
window.parent.postMessage({
event: 'submit'
}, '*');
The above is supposed to send a message to the parent window when the form is submitted.
In the parent window I have the following:
function receiveMessage(event) {
var origin = event.origin;
if (origin !== 'https://iframe.domain') {
return;
} else {
console.log('Submitted!');
}
}
window.addEventListener('message', receiveMessage, false);
The problem I seem to be having is that the code on the parent window is executing immediately without a message being sent from the iframe form being submitted. It is also executing over and over again. It logs "Submitted!" in the console over and over again for as long as I let it run.
How can this function run without the form being submitted to send the function, and why is it running over and over again?

In my iframe I moved the postMessage() to the footer, and checked for a div that is only available after my form is submitted. If the div exists I send a message to the parent window. This is the exact code in my iframe now.
// if the form has submitted and the confirmation message
// div exists send a messge to the parent window
if (jQuery('#gform_confirmation_wrapper_1').length) {
// Send a message to the parent window
parent.postMessage({
event: 'formSubmit'
}, '*');
}
On my parent window I created the function, checked the domain the message was coming from, and checked for the exact message being sent with if (event.data.event === 'formSubmit'). If that message, which was only sent from my iframe if the form's confirmation div existed, matched exactly formSubmitted then I pushed the event to the datalayer of Google Tag Manager. This is the exact code that is working on my dev site now.
// create function to push event to GTM if the iframe form has been submitted
function awReceiveMessage(event) {
// set variable to url that the message is coming from
var origin = event.origin;
// check if the message is coming from Polk
if (origin !== 'https://iframe.domain') {
//stop function if it is not coming from Polk
return;
} else {
// instantiating the GTM datalayer variable
var dataLayer = window.dataLayer || (window.dataLayer = []);
// if the message is formSubmit push the formSubmit event to the GTM datalayer
if (event.data.event === 'formSubmit') {
dataLayer.push({
'event': 'formSubmit'
});
}
}
}
// run awReceiveMessage() if a message is sent from the iframe to the parent
window.addEventListener('message', awReceiveMessage, false);
The above code is working and firing a GTM tag correctly on the parent page when the form is submitted in the iframe.

You need to check you have received the correct message.
function receiveMessage(event) {
if (event.origin !== 'https://iframe.domain') {
return;
} else if (event.data.event && event.data.event === 'submit') {
console.log('Submitted!');
}
}
window.addEventListener('message', receiveMessage, false);
I find it odd you are getting so many messages and I would suggest adding some code to dump them to the console to see what they are.
window.addEventListener('message', (event) => console.log(event), false);

Related

Why does JavaScript "window.postMessage" create duplicate messages?

I'm trying to open a popup window, do some stuff, and send a message back to the opening window so I can do some more stuff with the data it sends.
Essentially, I'm adapting the process outlined here.
This is my code on the "opening window". It runs when a social connect button is clicked. The code opens a popup window and assigns a listener event on the opening window to receive a message from the popup:
//Do the operation
let windowObjectReference = null;
let previousUrl = null;
const openSignInWindow = (url, name) => {
// remove any existing event listeners
window.removeEventListener('message', receiveMessage);
// window features
const strWindowFeatures =
'toolbar=no, menubar=no, width=600, height=700, top=100, left=100';
if (windowObjectReference === null || windowObjectReference.closed) {
/* if the pointer to the window object in memory does not exist
or if such pointer exists but the window was closed */
windowObjectReference = window.open(url, name, strWindowFeatures);
} else if (previousUrl !== url) {
/* if the resource to load is different,
then we load it in the already opened secondary window and then
we bring such window back on top/in front of its parent window. */
windowObjectReference = window.open(url, name, strWindowFeatures);
windowObjectReference.focus();
} else {
/* else the window reference must exist and the window
is not closed; therefore, we can bring it back on top of any other
window with the focus() method. There would be no need to re-create
the window or to reload the referenced resource. */
windowObjectReference.focus();
}
// add the listener for receiving a message from the popup
window.addEventListener('message', event => receiveMessage(event), false);
// assign the previous URL
previousUrl = url;
};
const receiveMessage = event => {
// Do we trust the sender of this message? (might be different from what we originally opened, for example).
if (event.origin !== websiteHomeUrlNoSlash) {
return;
}
const { data } = event;
console.log(data); //<--- THIS WHERE I'm SEEING DUPLICATES
};
//Invoke the function
openSignInWindow(url, name);
In the popup users login to their social account and then get redirected to a page on my app where the below code is run. The code posts a message back to the opening window and then closes the popup:
// Get the message data
const messageObj = {
pluginReason: pluginReasonVar,
displayName: displayNameVar,
provider: providerVar,
};
if (window.opener) {
// send them to the opening window
window.opener.postMessage(messageObj, websiteHomeUrlNoSlash);
// close the popup
if (closePopup) {
window.close();
}
}
Everything almost works as expected. Users can login to their social accounts and all the redirections and opening and closing of the popup works fine.
The Problem:
If users go through the Social Connect process multiple times without refreshing the page, then the message data that is printed to the console is duplicated more and more each run.
For example:
On the 1st run console.log(data) is printed once. So far this works as expected.
On the 2nd run console.log(data) prints twice. It should only be printed once.
On the 3rd run console.log(data) prints three times. It should only be printed once.
Each time the Social Connect process is run it should only print once. But somehow it's adding a duplicate copy on each subsequent run.
This duplication keeps growing until the users refreshes the page, which starts the count back at one.
I want to do more data manipulation at the point of the console.log(data) but I can't do that while it's creating duplicates copies on each subsequent run.
How do I stop that from happening?
Maybe it's because the listener event is not detaching? If so, how do I fix that?
You have created an anonymous method (event) => { } as a wrapper and attached it to the addEventListener method.
window.addEventListener('message', event => receiveMessage(event), false);
It can't be removed by
window.removeEventListener('message', receiveMessage);
To fix it, make changes like this:
window.addEventListener('message', receiveMessage, false);
Meanwhile, if the method receiveMessage gets lost every time the window has been closed, it's better to move the removeEventListener part inside the receiveMessage.
const receiveMessage = (event)=> {
window.removeEventListener('message', receiveMessage);
// do something else
}

How to warn user before leaving page but not on redirect

I have some pages on my site that allow users to create and edit posts. When a user is on these pages, I'd like to warn them before leaving the page. I can do that like this:
//Only warn if user is on a New or Edit page
if(window.location.href.indexOf("/new") !== -1 || window.location.href.indexOf("/edit") !== -1 {
window.addEventListener('beforeunload', function (e) {
e.preventDefault();
e.returnValue = '';
});
//Doing this again because I don't know which version is compataible with all browsers
window.onbeforeunload = function (e) {
e.preventDefault();
e.returnValue = ''
};
};
On a New or Edit page, the information in a form gets submitted to the server using JQuery ajax. The server returns a URL which the user gets redirected to to see the results of their post/update like this window.location.href = result; with result being the URL sent back from the server.
When that code runs to do the redirect, the user is getting the warning that they are about to leave the page they are on. I don't want it to do this on any redirects/navigation that the user has not performed. How could I stop/remove the warning in this instances?
UPDATE: This is not a duplicate. This question asks about preventing a beforeunloadevent happening on a redirect where the user has not requested to move away from the page himself.
Because you may want the event bound in some circumstances but not others within the same window, you'll have to not only add the event handler to the window, but you'll have to remove it as well (under the right circumstance) because even though you are changing the URL of the document loaded in the window, you are not changing the window itself:
function handleBeforeUnload (e) {
e.preventDefault();
e.returnValue = '';
}
//Only warn if user is on a New or Edit page
if(location.href.indexOf("/new") !== -1 || location.href.indexOf("/edit") !== -1 {
window.addEventListener('beforeunload', handleBeforeUnload);
} else {
// Remove the previously registered event handler (if any)
window.removeEventListener('beforeunload', handleBeforeUnload);
}
If you want to force a navigation with window.location.href, you should disable the beforeunload event listener before you navigate.
Something like this, for example:
function unloadHandler(e) {
e.preventDefault();
e.returnValue = '';
}
window.addEventListener('beforeunload', unloadHandler);
function forceNavigation(url) {
// Remove the "are you sure you want to leave?" message, then navigate
window.removeEventListener('beforeunload', unloadHandler);
window.location.href = url;
}
Call forceNavigation('https://example.com') to navigate without warning the user.
JS fiddle here: https://jsfiddle.net/o918wsam/1/

Porting Chrome extension to Edge

I have created a chrome extension to enable clipboard data access. The solution is explained in details here Implementing 'Paste' in custom context menu. Now the problem is how to port this extension to Edge. There is a tool for that I know I used it, and maybe it is working, but my problem is how to "consume" this extension, what is equivalent to chrome.runtime.sendMessage in Edge? In Chrome I used this https://developer.chrome.com/apps/messaging#external-webpage - the part 'Sending messages from webpages', but in Edge I just can't find anything similar. Thanks for your time and help.
There is runtime.sendMessage() in Edge too.
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/sendMessage
The thing to keep in mind is that the runtime object is defined on the browser object, not chrome.
Sends a single message to event listeners within your extension or a different extension.
If sending to your extension, omit the extensionId argument. The runtime.onMessage event will be fired in each page in your extension, except for the frame that called runtime.sendMessage.
If sending to a different extension, include the extensionId argument set to the other extension's ID. runtime.onMessageExternal will be fired in the other extension.
Extensions cannot send messages to content scripts using this method. To send messages to content scripts, use tabs.sendMessage.
This is an asynchronous function that returns a Promise.
I managed to solve this. There is no way (at least I couldn't find it) to communicate from web page with extension background script (and only the background script can get data from the clipboard and has the 'browser' object defined). So what I did, I communicated with content script and content script communicated with background script. Here is the code.
PAGE CODE:
contextMenuPaste: function () {
if (getBrowserName() == 'EDGE') {
window.postMessage({
direction: "from-page-script"
}, "*");
}
},
window.addEventListener("message", function (event) {
if (event.source == window &&
event.data.direction &&
event.data.direction == "from-content-script") {
console.log('Data in page script', event.data.message);
}
});
CONTENT SCRIPT CODE
window.addEventListener("message", (event) => {
// If message came from page-script send request to background script to get clipboard data
if (event.source == window &&
event.data &&
event.data.direction == "from-page-script") {
browser.runtime.sendMessage({
message: "getClipboardData"
},
function(clipboardData) {
messagePageScript(clipboardData);
}
);
}
});
// Send clipboard data to page script
function messagePageScript(clipboardData) {
window.postMessage({
direction: "from-content-script",
message: clipboardData
}, "*");
}
BACKGROUND SCRIPT CODE
browser.runtime.onMessage.addListener(
function(req, sender, callback) {
if (req) {
if (req.message) {
if (req.message == "installed") {
console.log('Checking is extension is installed!');
callback(true);
}
else if(req.message = "getClipboardData") {
console.log('Get clipboard data');
callback(getDataFromClipboard());
}
}
}
return true;
}
);
function getDataFromClipboard() {
var bg = browser.extension.getBackgroundPage();
var helperTextArea = bg.document.getElementById('sandbox');
if (helperTextArea == null) {
helperTextArea = bg.document.createElement("textarea");
document.body.appendChild(helperTextArea);
}
helperTextArea.value = '';
helperTextArea.select();
// Clipboard data
var clipboardData = '';
bg.document.execCommand("Paste");
clipboardData = helperTextArea.value;
helperTextArea.value = '';
return clipboardData;
}
But there is one tiny issue. This code works if I have a break-point set on line
bg.document.execCommand("Paste");
and it doesn't if I don't have that break-point. I thought it is a trimming issue, added pauses, delayed executions but nothing helped. I will start a new question for that issues and will copy solution here (if I find one).

JavaScript: calling function of parent window from child window

I am opening a popup window from a page. The process goes like this:
var newwindow = window.open("mydomain.com/a.html", "Testing", 'height=600,width=800');
if (window.focus) { newwindow.focus() }
mydomain.com/a.html opens a popup mydomain.com/b.html
mydomain.com/b.html redirects the user to another site(say payment gateway)
paymentgateway.com/authenticate.html
paymentgateway.com/authenticate.html redirects the user to mydomain.com/success.html
From mydomain.com/success.html I want to execute a function written on mydomain.com/a.html
I have written
window.parent.LaunchFunction();
window.close();
but it's not working. What can be the issue? Is it possible to achieve?
I'm offering another way - use the local storage for communication between apps at the same origin (same protocol+domain+port).
You can add listener, that is fired when somebody change any property in localstorage.
First instance is listening
window.addEventListener('storage', onStorageChange, false);
function onStorageChange (e) {
if (e.key === 'messageText') {
var messageText = localStorage.getItem('messageText');
if (messageText) { // Prevent to call when fired by `removeItem()` below
console.log('New message from another instance has come:', messageText);
localStorage.removeItem('messageText'); // this will fires the `onStorageChange` again.
}
}
}
Second instance is sending the messages
localStorage.setItem('messageText', 'Hello from second app instance');

Safari extension send message to a specific tab

Is there a way to send a message from the global page to a specific tab?
What I'm currently doing is, on tab creation the injected script creates a unique id and sends a message with this number to the global page and the global page saves this number.
If the global page needs to send some data to a tab (i.e: tab #3) then the global page will "broadcast" a message to all tabs with the number #3 as part of the data passed to the tabs (iterate over all tabs and send a message to each tab).
Is there something like Chrome: (i.e: chrome.tabs.sendRequest(tabID, {action: 'respond', params:[channel,msg,async]});)?
Right now what I'm doing is that on the injected script side, each script has a listener that will catch this message. If a content script unique number is equal to the number sent by the global page then this message is for it, else doNothing.
Is there an easier more elegant way to do this in Safari?
Within the global page's message event handler, event.target refers to the tab from which a message was received. For example:
function handleMessage(e) {
if (e.name === 'whatIsMyUrl?') {
e.target.page.dispatchMessage('yourUrlIs', e.target.url);
}
}
safari.application.addEventListener("message", handleMessage, false);
The Safari extension API doesn't have tab IDs, but you could just keep each tab in an associative array and use its index to address it later. For example:
function handleMessage(e) {
if (e.name === 'hereIsMyId') {
myTabs[e.message] = e.target;
}
}
safari.application.addEventListener("message", handleMessage, false);
// later...
myTabs[someId].page.dispatchMessage('haveSomeCake');
Safari Answer
In your global page save directly to the tab.. so for instance on message from injected script
// global page
safari.application.addEventListener("message", function(event){
switch(event.name){
case "saveData":
event.target.page.tabData = { data: myData }
break;
case "getData":
event.target.page.dispatchMessage("tabData", myData);
break;
}
}, false);
-
// injected page
// first save data
safari.self.tab.dispatchMessage("saveData", {firstname:"mike", age: 25} );
// setup listner to recevie data
safari.self.addEventListener("message", function(event){
switch(event.name){
case "tabData":
// get data for page
console.debug(event.message);
// { firstname: "mike", age: 25 }
break;
}
}, false);
// send message to trigger response
safari.self.tab.dispatchMessage("getData", {} );

Categories

Resources