chrome extension - alternative to externally_connectable? - google-chrome-extension

It seems like the externally_connectable feature that allows a website to communicate with an extension is still in the dev channel and not yet stable. Are there any other ways to allow a specific website to communicate with my extension, while I wait for this feature to become stable? How have chrome extension developers traditionally done it?

Thanks Rob W for pointing me in the direction of HTML5 messaging. For the benefit of other chrome extension developers, I'm writing about the general problem I was trying to solve and the solution that worked in the end.
I am making a chrome extension that can control music playback on a tab via a popup player. When a user clicks on play/pause/etc on the popup player, the extension should be able to convey that message to the webpage and get back a response stating whether the action was accomplished.
My first approach was to inject a content script into the music player page. The problem is, though, that content scripts operate in a "sandbox" and cannot access native javascript on the page. Therefore, the content script was pretty useless (on its own), because while it could receive commands from the extension, it could not effect any change on the webpage itself.
One thing that worked in my favor was that the website where the music was playing belongs to me, so I could put whatever javascript I wanted there and have it be served from the server. That's exactly what I used to my advantage: I created another javascript file that would reside on the website and communicate with the content script mentioned above, via the window object of the page (i.e. HTML5 messaging). This only works because the content script and the javascript file both exist in the same webpage and can share the window object of the page. Thanks Rob W for pointing me to this capability. Here is an example of how the javascript file on the page can initiate a connection with the content script via the window object:
content_script.js (injected by extension into xyz.com):
window.addEventListener("message", function(event) {
if(event.data.secret_key &&
(event.data.secret_key === "my_secret_key") &&
event.data.source === "page"){
if(event.data.type){
switch(event.data.type) {
case 'init':
console.log("received connection request from page");
window.postMessage({source: "content_script", type: 'init',
secret_key: "my_secret_key"}, "*");
break;
}
}
}
}, false);
onpage.js (resides on server and served along with xyz.com):
window.postMessage({source: "page", type: 'init',
secret_key: "my_secret_key"}, "*");
window.addEventListener("message", function(event) {
if(event.data.secret_key &&
(event.data.secret_key === "my_secret_key") &&
event.data.source === "content_script"){
if(event.data.type){
switch(event.data.type) {
case 'init':
console.log("connection established");
break;
}
}
}
}, false);
I check the secret key just to make sure that the message originates from where I expect it to.
That's it! If anything is unclear, or if you have any questions, feel free to follow up!

You could have an extension inject a content script alongside a web page, and use that to pass messages back and forth between the website and the background page of the extension.
It's tedious, though, and externally connectable is a lot nicer.

Related

chrome.tabCapture unavailable in MV3's new offscreen API

Our extension uses tabCapture to record the sound of a tab. In MV3, chrome.tabCapture is only available in foreground pages (like popups), which makes the API useless for recording anything in the tab since it can't be kept open.
There's the current hack which opens an extension page in a new Tab while recording, to satisfy both the keep-alive and foreground constraints in the API, but that is less than ideal as it requires tabs pop-in and pop-out of existence without user interaction so it makes for a terrible user experience.
Then there's the new chrome.offscreen API which had this capturing use case as one of its main highlighted use cases in the initial draft request by Devlin Cronin here. Now, the offscreen API is enabled in 109 beta ready to be deployed in production on Jan 10th 2023 and this use case appears ignored. chrome.tabCapture is not exposed to the offscreen document.
In the extension service worker:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
chrome.offscreen.hasDocument().then(function(has) {
if (!has) {
chrome.offscreen.createDocument({
justification: 'record audio',
reasons: [chrome.offscreen.Reason.USER_MEDIA],
url: chrome.runtime.getURL('record.html'),
});
}
});
});
In the offscreen document's JS script:
window.addEventListener("load", () => {
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
console.log(chrome.tabCapture); // **this is always undefined**
});
});
So now the question. Is there another way to record the current tab audio that isn't listed above? Not talking about recording the user's mic with getUserMedia and not involving desktopCapture which brings up the nasty popup. The ideal solution would still involve tabCapture. But how?

Chrome extension - This extension may have been corrupted

I created an extension for Chrome. I need to allow the user to change a javascript file within the extension for various reasons. When the whatever.js file is changed at C:\Users\\AppData\Local\Google\Chrome\User Data\Default\Extensions\cidaekdakljdsijofjahinafcafmanb\6.0.5_0\whatever.js Chrome comes back with "This extension may have been corrupted." and I have to reinstall the extension.
How do I stop this? How do I allow the changing of javascript file? I'm just trying to set a variable. Can I include a text file and change that instead? Under what circumstances can I get Chrome to stop doing this?
Can I add a user script? whatever.user.js? I read about that somewhere.
Thanks.
You can fix it by following proper development practices. If you are making the user edit the code to charge a variable, thats a huge coding issue.
Instead it should be changeable with a user interface, for example in the extension options page. If you want full control of the option, download it from a server or read it from a web accesible extension resource json file. here is an example that reads from such file since you asked it in comments:
var url = chrome.extension.getURL("options.json"); //in manifest under web_accessible_resources
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function (e) {
if (xhr.readyState == 4 && xhr.status == 200) {
var options=null;
try {
options = JSON.parse(xhr.responseText);
}
catch (e) {
console.log("error: cant parse options.");
}
if (options) {
//got it!
}
}
};
xhr.open("GET", url);
xhr.send();
this also simplifies a lot the user experience and possible user errors. just the fact that each OS stores and caches the code differently would be a nightmare to support.
and no, you cant bypass that chrome security measure meant to protect users from i.e. viruses modifying the extension source code.

Detect My page rendering in Chrome App

I have followed this link ChromeApp for my chromeApp
I want to detect that Is my HTML page rendering on ChromeApp?
if(chromeApp){
//do this
}
else{
//do this
}
To answer the general question: Outside of the webview you can detect if you are rendering in a Chrome App by:
if (chrome && chrome.app && chrome.app.runtime)
// chrome app.
else
// open web.
(Taken from gapi-chrome-apps.js)
To answer the intent of the question, "How can detect and not show alert messages for content in a webview", you may wish to change the user agent and use that to detect. See this test code for more. Here's the idea, though:
webview.setUserAgentOverride(webview.getUserAgent() + ' in a webview');
Also, you can support alert dialogs with the change 19679002: : Implement dialog API (not quite in Chrome stable I think). The following should illustrate:
From the host:
webview.addEventListener('dialog', function(e) {
// Check e.messageType, e.g. it may be 'alert'.
// Use e.messageText
// Unblock the guest content wity e.dialog.ok();
});

Opening and writing to a new window from a Google Chrome extension sandbox page

(Cross posted here)
Hi,
I have a sandboxed page (specified in my manifest) which is loaded into an iframe in my extension's background page. From within my sandboxed page, I'd like to open a new window and write to it, i.e.:
var win = window.open(); win.document.write('<p&gtHello!</p>');
This works from my extension's background page and from regular web pages, but when invoked from either content scripts or my sandboxed page, the window opens, but I cannot access the win object (it's defined, but empty---console.log outputs "Window {}").
I assume this is due to same-origin policies (with every window being given a uinque-origin within the sandboxed environment). However, since the window opens an about:blank page, I'm confused why this would matter.
Is this a feature? Is there a parameter I can add to my manifest to avoid this? And does anyone know of work-arounds that don't involve using postMessage back to my background page? My ideal solution is to have my sandboxed script open a new window and interact with it directly, not with message passing.
I can provide a full example if necessary, but I'm hoping someone might just know off the top of their head. I'm running Chrome 24.0.1312.57 on Mac and 24.0.1312.68 on Ubuntu if that helps.
Thanks,
Hank
Content scripts, by definition, are part of external regular web pages you load so I'm not really sure how your script could work on a "regular web page" but not the content script. Do you mean the code works when you embed it in your own pages, but not in other pages via the content script?
Regardless, if the script is working properly from your background page, you could always try messaging. (more here: http://developer.chrome.com/extensions/messaging.html)
Script for your sandbox/contentscript:
//send message to background page
chrome.extension.sendMessage({todo: "newWindow"});
In background page:
//create a listener
chrome.extension.onMessage.addListener(
function(request, sender) {
if (request.todo === "newWindow") {
//do your stuff here
var win = window.open(); win.document.write('<p&gtHello!</p>');
}
});
Per the cross-post here, the issue is indeed that the opened window is given a unique origin. This was intentional as the members of the standards working group (SWG) felt that it would be more secure to not make an exception for about:blank pages where they inherit the sandbox's origin.
However, to get around this issue, at least for my purposes, I can use the following method. First, forget sandboxing. This workaround uses an iframe embedded in a background page with the src url set to data:text/html,.... This gives a unique origin to the iframe so it's no longer in extension space. That means eval can be used and chrome apis cannot be accessed. Unlike in a sandbox, windows opened from the iframe share that same origin as the iframe, allowing created windows to be accessed. For example:
In a background html page:
<html>
<head>
...
<script src="background.js"></script>
...
</head>
<body>
...
<iframe id="myframe"></iframe>
...
</body>
</html>
In background.js:
...
document.getElementById('myframe').setAttribute('src', 'data:text/html,'+
encodeURI('<html><head>'+
'<script src='+chrome.extension.getURL('jquery.js')+'></script>'+
'<script src='+chrome.extension.getURL('myscript.js')+'></script>'+
...
'</head><body></body></html>'
));
...
In myscript.js
jQuery(document).ready(function(){
...
// To receive messages from background.js.
window.addEventListener('message', function(e){ ... } );
// To send messages to background.js.
parent.postMessage({...}, '*');
// To open AND ACCESS a window.
var win = window.open();
win.document.write('Hello'); // Fails in sandbox, works here.
// Eval code, if you want. Can't do this from an extension
// page loaded normally unless you allow eval in your manifest.
// Here, it's okay!
eval( 'var x = window.open(); x.document.write("Hi!")' );
// No chrome apis.
chrome.log( chrome.extension ); // -> undefined
chrome.log( chrome.windows ); // -> undefined
// No direct access to background page (e.g., parent).
chrome.log( parent ); // -> Window with no properties.
...
});

Unable to use getBackgroundPage() api from a devtools extension

I am trying to write an extension that adds functionality to the Chrome devtools.
According to the devtools documentation, it says that the pages in devtools support very limited apis. Any API that is not supported can be access by accessing it through the background page, just as what contentscripts does.
Here is the relevant documentation snippet:
The tabId property provides the tab identifier that you can use with the chrome.tabs.* API calls. However, please note that chrome.tabs.* API is not exposed to the Developer Tools extension pages due to security considerations — you will need to pass the tab ID to the background page and invoke the chrome.tabs.* API functions from there.
Here is the source url: http://developer.chrome.com/extensions/devtools.inspectedWindow.html
However, when I try to do that, I get the following error in the console:
uncaught Error: "getBackgroundPage" can only be used in extension processes. See the content scripts documentation for more details.
Here is my code in my devtools.js script:
chrome.extension.getBackgroundPage().getLocation();
What am I doing wrong?
EDIT
I should describe my scenario first, and show how I am implementing it.
What I want to do is to display extra data in a devtools panel related to a webpage. In order to get that data, I will need to send a HTTP request in the same session as the page being debugged, because it requires authentication.
Use Case:
User browses to a particular URL. He is authenticated to the site. He then invokes devtools. The devtools panel opens up and a new panel shows up that has extra data related to the page.
Implementation:
1) DevTools script finds out the url of the page being inspected. If the url matches the site base hostname, then it opens a panel. In the callback of the panel creation, it sends a message to a background page, asking it to download a JSON payload from a debug endpoint on the same site, and then sends it to the devtools extension, wh ich then displays it.
Problems:
1) The background page gets the request, and downloads the URL. However the download is not using the same session as the user, so the download request fails.
2) From devtools window, I got the tabId of the inspected window. I send this tabId to the background page so that it can parse some stuff out of the url. However, chrome.tabs.get(tabId) does not return the tab.
To summarize, I need to
1) Get the background page to download data in the same session as the user's tab that is being debugged.
2) I need to have the background page be able to get access to the user's tab.
The APIs available to extension pages within the Developer Tools window include all devtools modules listed above and chrome.extension API. Other extension APIs are not available to the Developer Tools pages, but you may invoke them by sending a request to the background page of your extension, similarly to how it's done in the content scripts.
I guess the documentation is little ambiguous, By chrome.extension API they mean the Supported API's for content scripts.
So, you can use long lived communication for communication between inspected page and background page
Demonstration:
The following code illustrate scenario where a devtools page need some information from background page, it uses messages for communication.
manifest.json
Ensured permissions are all available in manifest file
{
"name":"Inspected Windows Demo",
"description":"This demonstrates Inspected window API",
"devtools_page":"devtools.html",
"manifest_version":2,
"version":"2",
"permissions":["experimental"],
"background":{
"scripts" : ["background.js"]
}
}
devtools.html
A trivial HTML File
<html>
<head>
<script src="devtools.js"></script>
</head>
<body>
</body>
</html>
devtools.js
Used Long lived Communication API's
var port = chrome.extension.connect({
name: "Sample Communication"
});
port.postMessage("Request Tab Data");
port.onMessage.addListener(function (msg) {
console.log("Tab Data recieved is " + msg);
});
background.js
Responded to communication request and passed trivial information using tab API()'s
chrome.extension.onConnect.addListener(function (port) {
port.onMessage.addListener(function (message) {
chrome.tabs.query({
"status": "complete",
"currentWindow": true,
"active": true
}, function (tabs) {
port.postMessage(tabs[0].id);
});
console.log("Message recived is "+message);
});
});
Sample Output received for trivial devtools.js here
Let me know if you need more information
EDIT 1)
For your question 1)
Can you make you call(s) from browser extension HTML Page\Content Script so same session is shared, i have tried both the ways in a sample and it is working form me, instead of code in background page- make the code in content script or browser action HTML Page.
Let me know if you are still facing problems.
For your question 2)
The following code always fetches current window user is browsing
manifest.json
Ensure you have tabs permission in your manifest.
{
"name":"Inspected Windows Demo",
"description":"This demonstrates Inspected window API",
"manifest_version":2,
"version":"2",
"permissions":["tabs"],
"background":{
"scripts" : ["background.js"]
}
}
background.js
chrome.tabs.query({
"status": "complete", // Window load is completed
"currentWindow": true, // It is in current window
"active": true //Window user is browsing
}, function (tabs) {
for (tab in tabs) { // It returns array so used a loop to iterate over items
console.log(tabs[tab].id); // Catch tab id
}
});
Let me know if you are still unable to get tab id of current window.

Resources