Is there any way to post messages directly from web pages to global page like Chrome's
chrome.runtime.sendMessage, chrome.runtime.onMessageExternal
API functions in Safari?
I've used AngularJS in my web app, and can't think of any clean way integrating it with injected scripts to pass messages to background. My web app is going to initiate some commands to background and get results basically. Currently, trying to convert my Chrome extension to Safari.
Or is there any way my Angular controller speak with injected script if above approach is not possible so that Angular be responsible for updating DOM not injected script?
Content scripts injected into page are isolated from page's javascript. However they share DOM. Event if you can't call javascript function from content script directly you can use window.postMessage to send some message to content script and listen for message inside CS:
(according to extensions documentation):
contentscript.js
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
var passedDataObject = event.data;
//...
}
}, false);
And on the web page:
window.postMessage({ /* Object to pass */ }, "*");
This is regular message passing on the web sites. postMessage takes two arguments: 1st is a data to pass and second is a target origin (see https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage for explanations).
Onmessage event have "source" attribute which tells you from which window/frame message come from. In your case you want to listen only for events from page where CS was injected to: if (event.source != window) return;
Edited:
Documentation for content scripts and message passing you'll find at https://developer.chrome.com/extensions/content_scripts
I'm developing a chrome extension to verify page content of current tab from devtools when request finishes. I'm only interested in following types of requests: main_frame, sub_frame, and xmlhttprequest.
It can be done like this:
listen for chrome.webRequest.onCompleted event in background.js and filter out non-interested requests
communicate with contentScript.js to get the page content
send page content as a message to devtools
But will such situation happen?
In background.js, request A completes and send a message M_A to contentScript.js to get page content.
Request B completes and page content changed.
In contentScript.js, M_A is received, then ...
So I want to find a better way, like put all the logic in devtools:
var requestFinishedHandler = function(request) {
// do something here ...
}
chrome.devtools.network.onRequestFinished.addListener(requestFinishedHandler);
Any advice?
There is a chrome.devtools.inspectedWindow.onResourceAdded. I use it in DevTools Autosave. It doesn’t specify request type such as main_frame, sub_frame, and xmlhttprequest.
I want to send message from the console of the random web page to my chrome extension.
chrome.extension.sendMessage doesn't seem to work.
According to the official docs you should use postMessage in the sender and message event listener in the receiver.
Here is an example:
Your website's page.html
var data = { type: "FROM_PAGE", text: "Hello from the webpage!" };
window.postMessage(data, "*");
Content script: (injected using chrome.tabs.executeScript(tabid, {code:...)
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received message: " + event.data.text);
}
});
Here page.html (which is not a part of the extension) posts messages to itself, which are intercepted and inspected by the content script. The reverse is possible through similar means.
To pass from content script to extension, you will have to use one of the available message-passing techniques.
It looks complicated and it is somewhat complicated but all this mumbo-jumbo is very secure.
Here's a quote from the latest https://developer.chrome.com/docs/extensions/mv3/messaging/, It's much simpler to support this kind of feature now, here's how:
###Sending messages from web pages
Similar to cross-extension messaging, your app or extension can
receive and respond to messages from regular web pages. To use this
feature, you must first specify in your manifest.json which web
sites you want to communicate with. For example:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
This will expose the messaging API to any page which matches the URL
patterns you specify. The URL pattern must contain at least a
second-level domain - that is, hostname patterns like "", ".com",
".co.uk", and ".appspot.com" and <all_urls> are prohibited.
From the web page, use the runtime.sendMessage or runtime.connect APIs
to send a message to a specific app or extension. For example:
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
From your app or extension, you may listen to messages from web pages
via the runtime.onMessageExternal or runtime.onConnectExternal APIs,
similar to cross-extension messaging. Only the web page can initiate a
connection. Here is an example:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url == blacklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
So to elaborate, a more concreate example: An issue with chrome.runtime.sendMessage(...) style is that you have to specify the pag you're on as externally_connectable which doesn't take a global wildcard like "https:///". So if you want that ability, you have to use the postMessage style communication. Capture the message from the window into the contentscript, then from the contentscript, you can send it elsewhere (if desired, like to the background.js etc.)
So in the normal web page, or in injected source that you embed into the normal page, from your contentscript.js, send a message like this:
window.postMessage({ type: "FROM_PAGE_TO_CONTENT_SCRIPT",
text: "Hello from the webpage!" }, "*");
ex, you could add it to a button like this:
document.getElementById("theButton").addEventListener("click",
function() {
window.postMessage({ type: "FROM_PAGE_TO_CONTENT_SCRIPT",
text: "Hello from the webpage!" }, "*");
}, false);
Then to capture it in the contentscript.js and "send it on" to the rest of the extension, the only caveat is that you only want to "select" messages that seem to be ones that you care about:
window.addEventListener("message", function(event) {
// We only accept messages from this window to itself [i.e. not from any iframes]
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE_TO_CONTENT_SCRIPT")) {
chrome.runtime.sendMessage(event.data); // broadcasts it to rest of extension, or could just broadcast event.data.payload...
} // else ignore messages seemingly not sent to yourself
}, false);
You can switch to the JS execution context of your content script using the <page context> menu at the bottom of a page’s developer JS console, then use chrome.runtime.sendMessage and other chrome.* APIs just as you would within the content script.
In addition to #hewigovens , I don't have enough points to comment...
I'm explaining #renatoargh and #sbichenko
If sending message from a default web page -
1) the webpage needs to be quoted in the manifest. e.g.:
"externally_connectable": {
"matches": ["http://abcde/abcde/main.aspx*"]
}
2) the background.js (background page)
excepts the call with onMessageExternal e.g. (calling an extension):
var host_name = "com.my_chrome_extension.com";
chrome.runtime.onMessageExternal.addListener(function(message, sender, sendResponse) {
chrome.runtime.sendNativeMessage(host_name, {"run":message});
sendResponse({"success": "success"});
return true;
});
Here it shows how to send messages from chrome extension to web page https://developer.chrome.com/docs/extensions/mv3/messaging/#external-webpage
Add externally connectible resources in manifest.json that matches your web page URL or localhost, if you testing locally.
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
From the web page, use the runtime.sendMessage or runtime.connect APIs to send a message to a specific app or extension. For example:
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
From your extension, you may listen to messages from web pages via the runtime.onMessageExternal or runtime.onConnectExternal APIs, similar to cross-extension messaging. Only the web page can initiate a connection. Here is an example:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
I am trying to create a simple javascript based extension for Google Chrome that takes data from one specific iframe and sends it as part of a POST request to a webpage.
The web page that sends the data submitted by POST request, to my email address.
I tried running the extension, it looks to be running fine, but I am not getting any email.
The servlet which receives form data is very simple, I dont think there is any error in it.
What I want is some way to check if the javascript based extension works or not.
The javascript code is given below-
var mypostrequest=new ajaxRequest()
mypostrequest.onreadystatechange=function(){
if (mypostrequest.readyState==4){
if (mypostrequest.status==200 || window.location.href.indexOf("http")==-1){
document.getElementById("result").innerHTML=mypostrequest.responseText
}
else{
alert("An error has occured making the request")
}
}
}
var namevalue=encodeURIComponent("Arvind")
var descvalue=encodeURIComponent(window.frames['test_iframe'].document.body.innerHTML)
var emailvalue=encodeURIComponent("arvindikchari#yahoo.com")
var parameters="name="+namevalue+"&description="+descvalue &email="+emailvalue
mypostrequest.open("POST", "http://taurusarticlesubmitter.appspot.com/sampleform", true)
mypostrequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
mypostrequest.send(parameters)
UPDATE
I made changes so that the content in js file is invoked by background page, but even now the extension is not working.
I put the following code in background.html:
<script>
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript( null, {file: "content.js"});
});
chrome.browserAction.setBadgeBackgroundColor({color:[0, 200, 0, 100]});
</script>
Looking at your code looks like you are trying to send cross domain ajax request from a content script. This is not allowed, you can do that only from background pages and after corresponding domains are declared in the manifest. More info here.
To check if your extension works, you can open dev tools and check if there any errors in the console. Open "Network" tab and see if request was sent to your URL. Place console.log in various places in your code for debugging, or use full featured built in javascript debugger for step-by-step debugging.
I have injected a content script file to all pages and inner iframes using "all_frames": true but when I send a request from popup only top window is receiving it. Any idea how can send it to all inner frames or at least last focused window/document?
Popup.html (page action):
===========
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {...}, function(response) {...});
});
content_script.js
=================
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
sendResponse({...});
});
I have checked that content script is working in inner frames.
I'm trying to retrieve text selection which may be in inner frames.
(By the way I prefer not to open connection by every and all content script, that's a big overhead.)
The problem is that one tab can have only one onRequest listener. So if you want to send messages to only iframe script, and there is only one iframe on the page, then you should create a listener inside iframe only:
//listen to requests only inside iframe
if(window!=window.top) {
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
...
});
}
Now if you want to send requests to both parent page and iframe, or there are many iframes on the page that need to listen to requests - you need to communicate through ports.
Ports still have a similar limitation - only one port listener will be called, but you can open many ports. If there is a parent page with only one iframe - you just open two connections on two different ports. But if there are many iframes on the page, and each iframe has subframes - you would need to enumerate all iframes first to create unique ports for each of them.
PS. I am not sure what you meant by
I prefer not to open connection by every and all content script
but if you want to inject content script on demand, there is chrome.tabs.executeScript().