I am new to chrome-extension development. I followed some tutorials and later I was playing with the extension . there is something bugging me this extension.
steps to reproduce:
open the devtools in two pages side by side
follow the steps mentioned in github page.
when you click on(any one of the page) 'Insert button to send a message from page to devtools' button in panel, you can see that Html page changes.
then click on the button displayed in page. You can see that in both the panels, button/text is changed.
Is there any filter to restrict this and only change the button/text on clicked page only? I know this is happening because of port.postMessage(message); in background.js page.
I found this but I was not able to make it work.
Any help would be appreciated :)
The communication scheme is simple:
your devtools panel opens a port to the background page
the background page listens on that port and stores a lookup table of tabId-to-port mappings
the background page also listens for messages from content scripts and uses the above lookup table to route the message to a corresponding port channel
devtools-panel.js
var port = chrome.runtime.connect();
port.onMessage.addListener(message => {
$id('insertmessagebutton').innerHTML = message.content;
});
$id('executescript').onclick = () =>
runContentScript({code: "console.log('Content script executed')"});
$id('insertscript').onclick = () =>
runContentScript({file: "inserted-script.js"});
$id('insertmessagebutton').onclick = () => {
runContentScript({code: "document.body.innerHTML='<button>Send to panel</button>'"});
runContentScript({file: "messageback-script.js"});
};
function runContentScript(params) {
port.postMessage(
Object.assign({
tabId: chrome.devtools.inspectedWindow.tabId,
}, params)
);
}
function $id(id) {
return document.getElementById(id);
}
background.js
var tabPorts = {};
chrome.runtime.onConnect.addListener(port => {
let tabId;
port.onMessage.addListener(message => {
if (!tabId) {
// this is a first message from devtools so let's set the tabId-port mapping
tabId = message.tabId;
tabPorts[tabId] = port;
}
if (message.code || message.file) {
delete message.tabId;
chrome.tabs.executeScript(tabId, message);
}
});
port.onDisconnect.addListener(() => {
delete tabPorts[tabId];
});
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
const port = sender.tab && tabPorts[sender.tab.id];
if (port) {
port.postMessage(message);
}
});
chrome.tabs.onRemoved.addListener(tabId => {
delete tabPorts[tabId];
});
chrome.tabs.onReplaced.addListener((newTabId, oldTabId) => {
delete tabPorts[oldTabId];
});
Related
I'm building an extension where when the extension first starts (browser is started/extension is updated) a window is opened with a html file containing a form asking for a master password. When this master password does not match a certain string, a message is sent through chrome.runtime.sendMessage. A message is also sent the same way when the modal window is closed through the chrome.windows.onRemoved listener.
Here is my service worker:
/// <reference types="chrome-types"/>
(async () => {
console.log('Extension started. Modal opened...');
const window = await chrome.windows.create({
url: chrome.runtime.getURL("html/index.html"),
type: "popup",
width: 400,
height: 600,
});
chrome.windows.onRemoved.addListener((windowId) => {
if (windowId === window?.id) chrome.runtime.sendMessage({ monitoringEnabled: true, reason: 'tab closed' }).catch(console.log);
});
chrome.runtime.onMessage.addListener((message) => {
if (Object.hasOwn(message, 'monitoringEnabled')) {
console.log(`Monitoring ${message.monitoringEnabled ? 'enabled' : 'disabled'}. ${message.reason ? `Reason: ${message.reason}` : ''}`)
chrome.storage.local.set({ monitoringEnabled: message.monitoringEnabled });
if (window?.id) chrome.windows.remove(window.id);
}
return true;
});
})();
The html file just has a form with a button which when clicked triggers a script:
const MASTER_PASSWORD = 'some_thing_here';
document.getElementById('submit-button').addEventListener("click", (e) => {
const password = document.getElementById('master-password-text-field').value;
if (password !== MASTER_PASSWORD) return chrome.runtime.sendMessage({ monitoringEnabled: true, reason: 'invalid password' })
return chrome.runtime.sendMessage({ monitoringEnabled: false })
})
These are some logs:
The first error is when the modal tab is closed, notice that nothing happens after this (i.e onMessage listener is not triggered). However, in the second case, when a message is sent from the modal script, the onMessage listener is triggered, but the connection error still appears after the code in the listener has processed.
Not sure why this happens. I have checked multiple other threads on the same topic but none of them seem to help me. If you have a better idea on what I can do to achieve what I want right now, please suggest.
In my code, I'm sending a message to the service worker in the server worker itself. I've re-wrote my code by just making a function which is called when the windows.onRemoved event is triggered and also when a message is sent from the modal tab. The seems to have fixed my issue. This is my service worker code for reference:
/// <reference types="chrome-types"/>
console.log('Extension started. Modal opened...');
let windowId: number | null = null;
chrome.windows
.create({
url: chrome.runtime.getURL('html/index.html'),
type: 'popup',
width: 400,
height: 600
})
.then((created) => (windowId = created?.id ?? null));
chrome.windows.onRemoved.addListener((id) => {
if (id === windowId) enableMonitoring('window closed')
});
chrome.runtime.onMessage.addListener((message) => {
if (message.monitoringEnabled) {
enableMonitoring(message.reason);
}
return undefined
})
function enableMonitoring(reason: any) {
console.log('monitoring enabled', reason);
}
Here is the relevant part of the code.
I am trying to capture network calls using chrome.debugger API here. Everything is ok but once the page loads or I click a link then after the first action it closes automatically
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.play) {
if (message.captureNtwrk) {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tab = tabs[0];
tabId = tab.id;
chrome.debugger.attach({ tabId: tabId }, version, onAttach.bind(null, tabId));
});
}
if (message.captureScreen) {
}
if (message.captureEvent) {
}
}
if (message.stop) {
chrome.debugger.detach({ tabId: tabId });
}
});
// Function to run while debugging
const onAttach = (tabId) => {
if (chrome.runtime.lastError) console.log(chrome.runtime.lastError.message);
chrome.debugger.sendCommand({ tabId: tabId }, 'Network.enable');
chrome.debugger.onEvent.addListener(onEvent);
chrome.debugger.onDetach.addListener((source, reason) => {
console.log('Detached: ' + reason);
});
});
You can try disabling other extensions, especially 3rd party extensions added by desktop applications. One particular extension observed, which causes debugger to detach after page navigation is Adobe Acrobat.
I built a website that uses the Chrome web midi interface (based on navigator.requestMidiAccess) that works fine in a local development server, but when pushed to a cloud server fails, saying that navigator.requestMidiAccess is not a function. The same code, the same browser. I'll try to include the relevant code:
function initializeMidi() {
navigator.requestMIDIAccess()
.then(
(midi) => midiReady(midi),
(err) => console.log('Something went wrong', err));
}
window.onload = (event) => {
initializeMidi();
};
// this next function builds a list of radio buttons to select the MIDI device
function midiReady(midi) {
globalMidi = midi.outputs
parentElement = document.getElementById('midi-devices-div')
parentElement.innerHTML = ''
var lastMidiPortName = null
midi.outputs.forEach(function (port, key) {
addRadioButton(parentElement, port)
lastMidiPortName = port.name
})
var n = window.localStorage.getItem('selectedMidiPortName')
if (n)
{
var e = document.getElementById(n)
e.checked = true
}
}
The Web MIDI interface is only exposed to SecureContexts, you must serve your document using https://.
I have created a background script which runs on short keys and click on the extension icon but, now I want to run it on every web page load.
The background script already loads on every page request. If your looking to update the data between the background script and content script in your background process you need to create two listeners, the second is usefull when multiple tabs are open. Remember data in your popup can be retrieved by simply calling chrome.extension.getBackgroundPage()
background.js
chrome.tabs.onUpdated.addListener(() => {
chrome.tabs.sendMessage(info.tabId, {
message: 'DO_SOMETHING_MESSAGE'
});
})
chrome.tabs.onActivated.addListener(() => {
chrome.tabs.sendMessage(info.tabId, {
message: 'DO_SOMETHING_MESSAGE'
});
})
content.js should function similar to this.
const processContent = () => {
// do whatever here.
let data = {message: UPDATE_BACKGROUND_DATA}
chrome.runtime.sendMessage(data);
}
// run when messages sent from background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.message === 'DO_SOMETHING_MESSAGE') {
processContent();
}
});
// run onload
processContent();
And finally, back in your background script create a listener that listens for any updated data.
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.message === UPDATE_BACKGROUND_DATA) {
// update background vars
}
})
I'm using a listener in the background page to know when a tab is loaded:
chrome.tabs.onUpdated.addListener(function (tabId) { })
But the listener is fired twice: when the page has started loading, and when the page has finished.Is there a way to differentiate the two cases?
Luckily have found the solution.
There is an additional parameter that holds the status value:
chrome.tabs.onUpdated.addListener(function (tabId , info) {
if (info.status === 'complete') {
// your code ...
}
});
Status can be either loading or complete.
I wanted a easier way to do this after opening a tab
function createTab (url) {
return new Promise(resolve => {
chrome.tabs.create({url}, async tab => {
chrome.tabs.onUpdated.addListener(function listener (tabId, info) {
if (info.status === 'complete' && tabId === tab.id) {
chrome.tabs.onUpdated.removeListener(listener);
resolve(tab);
}
});
});
});
}
so it would be
let tab = await createTab('http://google.com');