I'm currently building an extension for chrome (I'm a beginner) and looking for some help to some one issue. The flow of the extension is the following:
User activate the extension
User click an icon in the extension panel to start the capture
When the mouse cursor is over a DOM element it highlight it
When the user click it gets the "selector" (unique identifier/path to the element)
After step 2 I attach a new Debugger instance to the tab. it seems like you can do this action either in background.js or content-script.js. Both work so my question is which one makes more sense. I'd say content-script because it doesn't interact directly with the browser but only with my extension. Am I right?
Second question is when using the DebuggerAPI I need to send command using the DevTools Protocol Viewer. I guess the command I must send to interact with my DOM element sit under this category (https://chromedevtools.github.io/devtools-protocol/tot/DOM). Most of the command requires a NodeId parameter. My question is how would I get this NodeId when the mouse cursor is over it. I have the following event in my content-script
chrome.runtime.onMessage.addListener(function(msg, sender){
if(msg == "togglePanel"){
togglePanel();
} else if (msg == "startCaptureElement") {
console.log("- Content-Script.js: Add Mouse Listener");
document.addEventListener('mouseover', captureEvent);
} else if (msg == "stopCaptureElement") {
console.log("- Content-Script.js: Remove Mouse Listener");
document.removeEventListener('mouseover', captureEvent);
}
});
function captureEvent(el) {
//console.log("- Content-Script.js: It's moving");
console.log(el);
chrome.runtime.sendMessage("highlightElement");
}
In my background.js script
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender);
if (request == "startCaptureElement") {
console.log("- Background.js: Attach the debugger");
chrome.debugger.attach({tabId: sender.tab.id}, "1.0");
chrome.tabs.sendMessage(sender.tab.id, "startCaptureElement");
} else if (request == "stopCaptureElement") {
console.log("- Background.js: Detach the debugger");
chrome.debugger.detach({tabId: sender.tab.id});
chrome.tabs.sendMessage(sender.tab.id, "stopCaptureElement");
} else if (request == "highlightElement") {
console.log("- Background.js: Highlight Element");
chrome.debugger.sendCommand({tabId: sender.tab.id}, "DOM.enable", {});
chrome.debugger.sendCommand({tabId: sender.tab.id}, "Overlay.inspectNodeRequested", {}, function(result) {
console.log(result);
});
}
}
);
I found the similar question here How to highlight elements in a Chrome Extension similar to how DevTools does it? but the code provided confused me a little bit.
Thanks for your help
"Overlay.inspectNodeRequested" is an event that should be listened to.
you can call "Overlay.setInspectMode" to select a node.
background.js:
var version = "1.0";
//show popup page while click icon
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.debugger.attach({tabId:tab.id}, version,
onAttach.bind(null, tab.id));
});
function onAttach(tabId) {
if (chrome.runtime.lastError) {
alert(chrome.runtime.lastError.message);
return;
}
chrome.windows.create(
{url: "headers.html?" + tabId, type: "popup", width: 800, height: 600});
}
headers.html:
<html>
<head>
<meta charset="utf-8">
<script src="headers.js"></script>
</head>
<body>
<div id="container">
<button id="btn_inspect">select node</button>
</div>
</body>
</html>
headers.js:
var tabId = parseInt(window.location.search.substring(1));
var hightCfg = {
'showInfo': true, 'showStyles':true, 'contentColor':{r: 155, g: 11, b: 239, a: 0.7}
}
//listen events when page is loaded
window.addEventListener("load", function() {
chrome.debugger.sendCommand({tabId:tabId}, "DOM.enable");
chrome.debugger.sendCommand({tabId:tabId}, "Overlay.enable");
chrome.debugger.onEvent.addListener(onEvent);
document.getElementById('btn_inspect').addEventListener('click', function(){
chrome.debugger.sendCommand({tabId:tabId}, "Overlay.setInspectMode",
{'mode':'searchForNode', 'highlightConfig':hightCfg});
});
});
window.addEventListener("unload", function() {
chrome.debugger.detach({tabId:tabId});
});
var requests = {};
function onEvent(debuggeeId, message, params) {
console.log('onEvent ...'+message);
if (tabId != debuggeeId.tabId)
return;
if (message == "Network.inspectNodeRequested") {
//do something..
}
}
Related
I'm working on a browser extension that needs to be constantly running, even after an automatic refresh in the background. The problem is, that the page randomly automatically unloads and the script just shuts off. I need to find a way to keep the content script on at all times. Here's part of the code:
// content.js:
function run(fn) {
if(typeof(Worker) !== "undefined") {
if(typeof(w) == "undefined") {
w = new Worker(URL.createObjectURL(new Blob(['('+fn+')()'])));
}
w.onmessage = function(event) {
if (isNaN(grabbedmin) && ID) {
bump() // Note: the bump function refreshes in the page.
w.terminate();
}
if ($("[href='/server/bump/" + ID + "']").text().includes("Bump")) {
bump()
w.terminate();
}
document.getElementById("bumpcount").innerHTML = "Autobumper Enabled: " + getCurrentTimestamp();
if (numberwow == grabbedmin) {
bump()
w.terminate();
}
};
}
}
The code above basically gets run every minute by this worker:
// content.js:
const worker = run(function() {
var i = 0;
function timedCount() {
i = i + 1;
postMessage(i);
setTimeout(function(){timedCount()},1000);
}
timedCount();
});
Is there a way for background.js to detect that content.js is not running (or that the page is unloaded) when it should be and then reload it?
Note: You can find the script here: https://github.com/Theblockbuster1/disboard-auto-bump
After looking through the docs and looking at examples, I put this together:
chrome.tabs.query({
url: ["*://disboard.org/*dashboard/servers", "*://disboard.org/*dashboard/servers/"] // finds matching tabs
}, function(tabs) {
tabs.forEach(tab => {
chrome.tabs.update(tab.id,{autoDiscardable:false});
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
// checks if the browser automatically re-enabled autoDiscarding and disables it
if (changeInfo.autoDiscardable == true) chrome.tabs.update(tabId,{autoDiscardable:false});
});
i try create simple Chrome Extension, after click button in pop-up, i need send function setInput() to page, function change value and i need use trigger('keyup'), if i try use this function in Chrome Console - trigger work. But if i send this function after click in pop-up - trigger not work(
Chrome Extension - Trigger not Work
Console - Trigger Work
popup.html
<head>
<script src="popup.js"></script>
</head>
<body>
<div class="btn">Click</div>
</body>
popup.js
function sendMessage() {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {"message": "start"});
});
}
function onWindowLoad() {
chrome.tabs.executeScript(null, { file: "PageReader.js" });
}
document.addEventListener("DOMContentLoaded", function() {
var btn = document.querySelector('.btn');
btn.addEventListener('click', function() {
sendMessage();
});
});
window.onload = onWindowLoad;
PageReader.js
- in file top i include Jquery
function setInput() {
var input = $('.text input');
input.val('1111').trigger('keyup');
}
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "start" ) {
setInput();
}
}
);
thanks all for help, i find answer, i delete jQuery, and create event "keyup"
Old:
var input = $('.text input');
input.val('1111').trigger('keyup');
New:
var evt = document.createEvent('KeyboardEvent');
evt.initEvent('keyup', true, true);
var input = document.querySelector('.text input');
input.value = '1111';
input.dispatchEvent(evt);
Please add debugger after btn.addEventListener('click', function() { too see what is going on. if this event handler is attached.
Second thing - you may want to wrap you initialisation code into setTimeout call with let's say 100ms of delay, to check if this page you are dealing with is not only working with some framework that generates this HTML and this is done after DOMContentLoaded. This means basically wrap everything inside
document.addEventListener("DOMContentLoaded", function() {
with setTimeout(function() {/*everything inside goes here*/}, 100)
I've been struggling with my idea of google extension, and you as always are the last hope of mine ! :))
Well, I want to click the button on my chrome extension that will cause keydown simulation on the page extension is running.
I think chrome has some safety issues on my idea, that blocks the keyboard simulation (makes event isTrusted:false) and deletes which property.
The function I wrote works fine on jsfiddle , but it appears that chrome extension does it in a different manner.
Here is the content script file :
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if(request.action == "scrollToTop"){
}
else if(request.action == "scrollToBottom"){
}
else if(request.action == "enter"){
triggerKeyboardEvent(document,13);
}
function triggerKeyboardEvent(el, keyCode){
var event = new Event("keydown", {"bubbles":true, "cancelable":true});
event.which = keyCode;
el.dispatchEvent(event);
}
});
chrome.runtime.sendMessage({action : "show"});
The log on jsFiddle writes:
Event {isTrusted: false, which: 13}
The log on website:
document.addEventListener('keydown',function (e) {
console.log(e)
}
writes just:
Event {isTrusted: false}
Thanks to #BG101 and #Rob W I found out that solution is script injection!
the only thing was that according to MDN KeyboardEvent.initKeyboardEvent() is depricated, so I replaced the code with:
var event = new Event(event, {"bubbles":true, "cancelable":true});
Also, as I wanted the trigger to run on document, I removed the element selector things. Here is what I got:
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if(request.action == "scrollToTop"){
triggerKeyboardEventOnDocument("keydown",38);
}
else if(request.action == "scrollToBottom"){
triggerKeyboardEventOnDocument("keydown",40);
}
else if(request.action == "enter"){
triggerKeyboardEventOnDocument("keydown",13);
}
function triggerKeyboardEventOnDocument(event, keyCode){
var script = document.createElement('script');
script.textContent = '(' + function(event, charCode) {
//Create Event
var event = new Event(event, {"bubbles":true, "cancelable":true});
// Define custom values
// This part requires the script to be run in the page's context
var getterCode = {get: function() {return charCode}};
var getterChar = {get: function() {return String.fromCharCode(charCode)}};
Object.defineProperties(event, {
charCode: getterCode,
which: getterCode,
keyCode: getterCode, // Not fully correct
key: getterChar, // Not fully correct
char: getterChar
});
document.dispatchEvent(event);
} + ')(' + '\"' + event + '\", '+ keyCode + ')';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
}
});
chrome.runtime.sendMessage({action : "show"});
I'm writing a firefox plugin and keeping track of each page's workers in an array. Apart from a bit of fancy footwork required to manage this array (as described here https://bugzilla.mozilla.org/show_bug.cgi?id=686035 and here Addon SDK - context-menu and page-mod workers) everything is working properly. One issue I'm having is that when listening to the tabs pageshow event (or the worker's own pageshow event for that matter), the callback seems to fire before the worker is actually ready. When retrieving the page's corresponding worker in the callback and using it to try to send a message to the content script, I'm receiving the error The page is currently hidden and can no longer be used until it is visible again. Normally, I'd just use a setTimeout and grit my teeth, but this isn't available for add-ons. What's a suitable workaround? The code for the main part of the add-on is below:
var { ToggleButton } = require('sdk/ui/button/toggle');
var panels = require('sdk/panel');
var tabs = require('sdk/tabs');
var self = require('sdk/self');
var pageMods = require('sdk/page-mod');
var ss = require('sdk/simple-storage');
var workers = [];
ss.storage.isPluginActive = ss.storage.isPluginActive || false;
var button = ToggleButton({
id: 'tomorrowww',
label: 'Tomorowww',
icon: {
'16': './icon-16.png',
'32': './icon-32.png',
'64': './icon-64.png'
},
onChange: handleButtonChange
});
var panel = panels.Panel({
contentURL: self.data.url('panel.html'),
contentScriptFile: self.data.url('panel-script.js'),
onHide: handlePanelHide,
width: 342,
height: 270
});
panel.port.on('panel-ready', handlePanelReady);
panel.port.on('plugin-toggled', handlePluginToggled);
panel.port.on('link-clicked', handleLinkClicked);
pageMods.PageMod({
include: ['*'],
contentScriptFile: [self.data.url('CancerDOMManager.js'), self.data.url('content-script.js')],
contentStyleFile: self.data.url('content-style.css'),
onAttach: function (worker) {
addWorker(worker);
sendActiveState(ss.storage.isPluginActive);
}
});
// move between tabs
tabs.on('activate', function () {
sendActiveState();
});
// this actually fires before the worker's pageshow event so isn't useful as the workers array will be out of sync
//tabs.on('pageshow', function () {
// sendActiveState();
//});
function addWorker (worker) {
if(workers.indexOf(worker) > -1) {
return;
}
worker.on('detach', handleWorkerDetach);
worker.on('pageshow', handleWorkerShown);
worker.on('pagehide', handleWorkerHidden);
workers.push(worker);
}
function handleWorkerDetach () {
removeWorker(this, true);
}
function handleWorkerShown () {
addWorker(this);
// back / forward page history
// trying to send the state here will trigger the page hidden error
sendActiveState();
}
function handleWorkerHidden () {
removeWorker(this);
}
function removeWorker (worker, removeEvents) {
var index = workers.indexOf(worker);
removeEvents = removeEvents || false;
if(index > -1) {
if(removeEvents) {
worker.removeListener('detach', handleWorkerDetach);
worker.removeListener('pageshow', handleWorkerShown);
worker.removeListener('pagehide', handleWorkerHidden);
}
workers.splice(index, 1);
}
}
function getWorkersForCurrentTab () {
var i;
var tabWorkers = [];
i = workers.length;
while(--i > -1) {
if(workers[i].tab.id === tabs.activeTab.id) {
tabWorkers.push(workers[i]);
}
}
return tabWorkers;
}
function handlePanelReady () {
setActive(ss.storage.isPluginActive);
}
function setActive (bool) {
ss.storage.isPluginActive = bool;
panel.port.emit('active-changed', bool);
sendActiveState();
}
function sendActiveState () {
var tabWorkers = getWorkersForCurrentTab();
var i = tabWorkers.length;
while(--i > -1) {
tabWorkers[i].port.emit('toggle-plugin', ss.storage.isPluginActive);
}
}
function handleButtonChange (state) {
if(state.checked) {
panel.show({
position: button
});
}
}
function handlePanelHide () {
button.state('window', {checked: false});
}
function handleLinkClicked (url) {
if(panel.isShowing) {
panel.hide();
}
tabs.open(url);
}
function handlePluginToggled (bool) {
if(panel.isShowing) {
panel.hide();
}
setActive(bool);
}
try using contentScriptWhen: "start" in the page-mod
I was dealing with a similar problem. I think I have it working the way I want now by putting the listener in the content script instead of the addon script. I listen for the event on the window, I then emit a message from my content script to my addon script, my addon script then sends a message back to the content script with the information needed from the addon script.
In my code, I am working on update the preferences in the content script to ensure that the tab always has the most up to date settings when they are changed, only the addon script can listen to the prefs change event.
This particular snippet will listen for when the page is navigated to from history (i.e., back or forward button), will inform the addon script, the addon script will get the most up to date preferences, and then send them back to a port listening in the content script.
Content script:
window.onpageshow = function(){
console.log("onpageshow event fired (content script)");
self.port.emit("triggerPrefChange", '');
};
Addon Script (e.g., main.js:
worker.port.on("triggerPrefChange", function() {
console.log("Received request to triggerPrefChange in the addon script");
worker.port.emit("setPrefs", prefSet.prefs);
});
Since the event is being fired from the DOM event, the page must be shown. I am not sure if listening to the pageshow event in the addon script is doing what we think.
I'm the maker of an addon called BeautifyTumblr which changes the apperance of Tumblr.
I wish for my Chrome extension to automatically detect when it has been updated and display changelog to the user. I use an event page with the chrome.runtime.onInstalled.addListener hook to detect when an update has occured, retrieve the changelog from a text file in the extension.. this all works fine, then when I want to forward it to my content script via chrome.tabs.sendmessage it just wont work, nothing ever happens, no errors no nothing. I'm stumped.
Any help would be much appreciated!
Event Page:
chrome.runtime.onInstalled.addListener(function (details) {
"use strict";
if (details.reason === "install") {
} else if (details.reason === "update") {
var thisVersion = chrome.runtime.getManifest().version, xmlDom, xmlhttp;
xmlDom = null;
xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", chrome.extension.getURL("changelog.txt"), false);
xmlhttp.send(null);
xmlDom = xmlhttp.responseText;
chrome.tabs.query({'url' : 'http://www.tumblr.com/*'}, function (tabs) {
if (tabs.length > 0) {
var mTab = tabs[0].id;
chrome.tabs.update(mTab, {active: true});
setTimeout(chrome.tabs.sendMessage(mTab, {beautifyTumblrUpdate: xmlDom}), 500);
} else {
chrome.tabs.create({'url' : 'http://www.tumblr.com/dashboard'}, function (tab) {
setTimeout(chrome.tabs.sendMessage(tab.id, {beautifyTumblrUpdate: xmlDom}), 500);
});
}
});
}
});
Relevant code in Content Script:
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
"use strict";
window.alert('test');
if (request.beautifyTumblrUpdate) {
window.alert(request.beautifyTumblrUpdate);
} else if (request.beautifyTumblrInstall) {
window.alert(request.beautifyTumblrInstall);
}
}
);
I am also seeing the same thing. I am not a 100% sure but I think this happens because chrome shuts off connection between background page and "old" content scripts the moment the extension is updated. There's more info here in this bug : https://code.google.com/p/chromium/issues/detail?id=168263
simple, use the following code in background,
chrome.runtime.onInstalled.addListener(function(details){
if(details.reason == "install"){
chrome.tabs.create({ url: chrome.extension.getURL('welcome.html')});
}
});