My question is: is there a way to communicate directly between my custom Chrome DevTools panel and my Chrome Extension's content script? Right now, it seems like I need to use the background script as a mediator between the content script and the devtools panel.
I tried using a custom window event:
// in the dev tools panel
let event = new Event('suman-dev-tools', {
value: 'foo'
} as any);
window.dispatchEvent(event);
// in the content script
window.addEventListener('suman-dev-tools', function(ev){
console.log('my devtools panel has spoken:', ev);
});
but that doesn't seem to work - it looks like I can't use window events to communicate between the two things. Must I use the background page to communicate between devtools page and content script?
As you mentioned the best practice is to use background script as a message broker.
But there is one more way to run code in content-script directly from devtool panel: devtools.inspectedWindow.eval with useContentScriptContext: true allows you to evaluate code in the content-script scope.
chrome.devtools.inspectedWindow.eval('<your code>', {
useContentScriptContext: true
}, function(result) {
// result of the execution
});
Related
Use case
Existing web page / URL is opened from a 3rd party app. Upon completion of the work on the web page, it is expected to close
itself.
Javascript does not allow it using window.close() or alike, ref.
window.close and self.close do not close the window in Chrome
Browser limitation (Firefox/Chrome) is well documented and explored over the past years. For the given use case, per my understanding closing of a browser tab can only be achieved from a background script, calling chrome.tabs.remove() API.
Browser extension
The approach that seemed logical to me is using the following artifacts.
content.js
document.addEventListener("myCloseTabEvent", function(e){
console.log("[content] originating domain: " + this.domain);
// Prepare message for background script
var myMsgContent = {
type: "myAction",
value: "CloseBrowserTab"}
browser.runtime.sendMessage(myMsgContent);
}, false);
background.js
chrome.runtime.onMessage.addListener(
function(msgData, sender, sendResponse) {
if (msgData.type == "myAction" && msgData.value == "CloseBrowserTab") {
chrome.tabs.remove(sender.tab.id);
} else {
console.log("[background] No action because !(myAction && CloseBrowserTab)");
}
});
Changes to web page to raise the new event
function mySendEvent() {
const myEvent = new Event("myCloseTabEvent"); // OR CustomEvent()
// Dispatch the event
document.dispatchEvent(myEvent);
}
In summary, the following happens:
The web page loads
Content script adds the event listener for a custom event myCloseTabEvent
Background script adds an onMessage listener using chrome.runtime.onMessage.addListener()
Once all work is done on the page, existing Javascript code dispatches the custom event by calling mySendEvent().
Content script runs its listener function(e), sending a message to the background script.
Background script onMessage listener function(msgData, sender, sendResponse) uses the sender object to determine the tabId, and closes the browser tab using chrome.tabs.remove(sender.tab.id);
Questions
In concept, is this approach the best option we have to achieve the goal of closing the browser tab? Would there be any better ways of achieving the same?
A background script is require to be able to close the tab (correct me if I'm wrong here). Therefore the browser extension cannot be restricted to be active only on a specific set of domains using content_scripts -> matches. What best practices exists to restrict the functionality of the browser extension like this to specific domains? (domain names are known to the users of the extension, but not while packaging the artifacts). This is especially is of interest, to prevent other (malicious) web pages from closing them selves by sending the same message to the background script of the extension.
I've a HTML page which has some DOM configured with Angular. Now I'm building a chrome extension to modifying value in a text box. element.value= newValue will not work as the text box is designed with Angular. After reading some resources, I came to know that I need to do the changes to the scope of the element. I tried doing this in the extension.
var eleScope = window.angular.element(document.querySelector('.textbox')).scope();
eleScope.$apply(function() {
eleScope.item.request.box = newValue;
});
This doesn't seem to work as intended. When I dug into the problem more deeply, I came to know that the angular in the scope of window was not getting the right scope.
I also tried to inject angular.js from my extension and using angular.element directly which also doesn't seem to solve the problem.
Am I missing something. Appreciate your replies.
Hmm.. Actually you should be just able to change the value using vanilla Javascript or JQuery and should not need to update the scope as it is two-way binding ie. Updating the view updates the model and vice-versa.
The only reason why the model does not update is because the watchers to update the model trigger only on particular events like "click" or "input".
So the solution here would be to manually trigger these events.
This can be do by:
// the selector used is based on your example.
var elementTOUpdate = $('.textbox');
input.val('new-value');
input.trigger('input');
The last statement will trigger all the handlers on the element for the "input" event ( which will fire the angular watchers ) and update the scope.
More info on the trigger function:
http://api.jquery.com/trigger/
Although your web page and your Chrome extension's content script share the same DOM, they have separate JS execution contexts. In other words, despite grabbing the element via angular.element().scope() in your Chrome extension, you're still not accessing the correct scope object. (See the Chrome dev docs for more on this.)
In order to get the modified data from your Chrome extension on to the page, you can either manipulate the DOM via your content script, or take some extra steps to get the modified data into the web page's execution context (and subsequently, into your controller's scope).
For the latter, the solution is to use the Chrome's Message Passing API; specifically, the API designed to communicate between the web page and the Chrome extension (see here).
Here's an example from the documentation:
First, enable the API for the appropriate website(s) in your manifest.json file:
"externally_connectable": {
"matches": ["http://*.foo.com/*"]
}
From the web page, make a request to connect to the Chrome extension. In your callback, you'd update your model:
// Chrome extension's ID
var extensionId = "abcdefghijklmnoabcdefhijklmnoabc";
chrome.runtime.sendMessage(extensionId, {foo: 'bar'},
function(response) {
if (response.success) {
$scope.myData.foo = response.data;
$scope.$apply();
}
});
In your Chrome extension's background.js file, add a listener for the request:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request.foo) {
sendResponse({ data: 'modified data goes here' });
}
});
This process will allow your Chrome extension to send data into the correct execution environment, allowing your AngularJS app to work with it.
The main problem is well explained in Jonathan Guerrera's answer: you can't just load Angular in your extension and expect it to work because it's a different instance of Angular; the real one is in another JS context.
Another possible (though hacky) solution is to inject code into the page's context. That way, you're actually manipulating the page's own Angular.
It can be combined with other methods of communicating between the page-level script and the content script.
I need to do some actions when new tab in Firefox is opened. I'm using addon-sdk and I'm totally new to firefox extensions development.
I downloaded some new tab extensions in store, unpack them and mostly of them use such code:
var newtab = {
init: function ()
{
gBrowser.addEventListener("NewTab", newtab.opentab, false);
},
opentab: function (aEvent)
{
// action here
}
}
window.addEventListener( "load", newtab.init, false);
They are subscribing on load event of window, then they guaranteed have gBrowser to subscribe on new tab opened event.
When I'm trying to do this, I'm getting:
Message: ReferenceError: window is not defined
As far as I understood, there is no window object in that context.
Firefox Addon-sdk. On page load
StackOverflow – Firefos Addon: eventlistener: windows is not defined
According to sdk and SO answer, It's possible to do it described way, the only difference between my extension and downloaded extensions from the store (and above topics), that I haven't any xul files, because I haven't any UI.
I also tried to use code from SO answer Firefox Add-On window.addEventListener error: window not defined, but the Firefox is crashing.
How to do it right way? Should I have any xul files if I have no UI?
Thar code you're quoting is regular XUL overlay code, and does not apply to the SDK (well, it could be made to work in the SDK by jumping through a lot of hoops, but that's beside the point).
The SDK provides APIs for dealing with tabs in the sdk/tabs module. You should use that.
Try this:
var data = require('sdk/self').data;
require('sdk/page-mod').PageMod({
include: ["about:newtab"],
//contentScriptFile: [data.url('cs.js')], // <<< you dont need this unless you want to run stuff inside the about:newtab page
attachTo: ["existing", "top"], // <<<< im not sure what this does
onAttach: function(worker) {
worker.port.emit('attached', true);
}
});
self.port.on('attached', function() {
console.log('new tab page loaded!!!');
});
disclaimer: im not an sdk guy, this might fail, so you might have to tweak it after implementing, but im pretty sure its in the right direction
maybe this question is a bit noob style but I don't understand this JavaScript stuff.
My question: how do I debug injected code of the following
chrome extension example?
The file popup.js executes send_links.js (this is the injected file, if I understand this correct). I would like to debug send_links.js. I cannot set any breakpoint because I cannot see send_links.js in the Developer Tools of Chrome. I tried the command "debugger;" in send_links.js but it does not work. "console.log("blah");" commands are ignored too.
Thank you!
The debugger keyword will work if you open the Developer Tools for the current tab before you press the action button.
Also, if you want the script to display with its name, add the following line anywhere in send_links.js:
//# sourceURL=send_links.js
Then the script will show up in the 'Content Scripts' tab of the Developer Tools of the current tab. There you can set breakpoints and such. But you need always to open the Developer Tools for the tab before pressing the button for this to work.
All Injected Files or Content Scripts are Exposed to Page Developer Tools, You can set breakpoints and all regular stuff you do on regular Java Script Pages.
(source: google.com)
All your console log(s) appear in the page they are injected.
Ex:
If i inject console.log(document.getElementsByTagName('body')[0].style); to http://www.google.co.in/, then i need to open devtools of http://www.google.co.in/ page and look in its console.
The Output appeared is similar to regular debugging result.
EDIT 1
They are exposed through chrome.tabs.executeScript() but indirectly, when you set debugger; command you can inspect code.
Demonstration
If some sample code injects
chrome.tabs.executeScript(35, {
"code": "console.log('hi');debugger;//# sourceURL=send_links.js"
});
debugger of page shows the script being injected.
I guess it's because you open the debugger tool on the tab and not on the extension. Right click on the icon of your extension and choose the Inspect popup menu item. You can find more informations on this page http://developer.chrome.com/extensions/tut_debugging.html
In this case the script is not injected until you open the popup. Once the popup window loads, it injects send_links.js, which in turn sends a message as soon as it is done getting the links. You could reverse this messaging and inject the file in the manifest:
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["send_links.js"]
}],
add an on message handler to send_links.js with support to send a response
chrome.extension.onMessage.addListener(function(message,sender,sendResponse){
[...]
sendResponse(links);
});
and then replace the onMessage handler and executeScript in the popup with a sendMessage callback
chrome.windows.getCurrent(function (currentWindow) {
chrome.tabs.query({active: true, windowId: currentWindow.id},function(tab) {
chrome.tabs.sendMessage(tab[0].id, {method: "getlinks"},function(links){
for (var index in links) {
allLinks.push(links[index]);
}
allLinks.sort();
visibleLinks = allLinks;
showLinks();
});
});
});
This arrangement will put send_links.js into every page so that you can debug it more easily. Once it is bug free you can switch back to programmatic injection because in cases like these it is more efficient.
You can find the script under Sources > Content Scripts > One of them (plfdheimenpnchlahmhicnkejgmhjhom for example).
Edited source files
There is an excellent extension called Blipshot which takes page screenshots. I need to invoke the extension with page level javascript, instead of clicking its icon. Is this possible?
You cannot invoke any methods of an extension from within a web page. However, it's possible to inject a content script into the web page, and use sendMessage and onMessage, or onConnect and connect.
To edit an extension: Visit chrome://extensions page, and enable the Developer mode. Unpack an extension and/or visit the extension's directory. Edit the manifest.json file, and add the necessary lines (see here).
Add an event event listener at the background page. Add a poller in the content script, eg:
// Content script
var poller = window.setInterval(function() {
if (document.documentElement.getAttribute('extensionCalled')) {
chrome.extension.sendMessage({"anyname": "anything"}, function() {
/*optional callback function.*/alert("Something happened")
});
clearInterval(poller);
}
}, 200);
// Background
chrome.extension.onMessage.addListener(function(request, sender, callback) {
if (request.anyname == "anything") {
function_logic_here();
//Optionally, callback:
callback();
}
});
See also
Chrome extension - retrieving Gmail's original message - Using DOM events to communicate between a page and extension (recommended)
MDN: postMessage - It can be used to communicate between a page and extension (this method may cause conflicts when the page itself is also using the message events).
References:
Extension messaging
Content scripts
Content scripts in extensions
It would only be possible if the extension provides an interface to do it. Extensions run in an isolated environment, so you don't have direct access to any of their functions.
The closest they get is content scripts, which have access to the DOM. Because of that, you can communicate using events, but obviously the extension would need to set up event handlers for them, so it completely depends on the extension.