Pass data or modify extension html in a new tab/window
Although you can directly access DOM of your own extension's page opened in a new tab/window by using chrome.extension.getViews (or even simpler if window.open was used), but it's the approach from the times when UI was primitive so it won't work if your opened page uses a presentation framework. Moreover, when used from the popup you would have to open the tab in background first (active:false
in the parameters of chrome.tabs.create), otherwise the popup will auto-close so no further code would run, which is unfortunately still unreliable because another extension may force-activate the tab.
The reliable/proper approach is to pass the data to the other page and let it handle the data inside its script that's loaded in that page html via the standard <script src="other-page.js"></script>
.
1. MV2: HTML5 localStorage + synchronous access
Use if you need to access the data during loading inside the other page before the first painted frame e.g. to choose a light/dark theme.
Disadvantage: big amounts of data could noticeably slow down a budget device and you'll have to JSON'ify the non-string types such as objects or arrays.
popup.js:
localStorage.sharedData = JSON.stringify({foo: 123, bar: [1, 2, 3], theme: 'dark'});
chrome.tabs.create({url: 'other-page.html'});
other-page.js:
let sharedData;
try {
sharedData = JSON.parse(localStorage.sharedData);
if (sharedData.theme === 'dark') {
document.documentElement.style = 'background: #000; color: #aaa;';
}
} catch (e) {}
delete localStorage.sharedData;
2. MV2/MV3: URL parameters + synchronous access
Use if you need to access the data during loading inside the other page before the first painted frame e.g. to choose a light/dark theme.
Disadvantage: a long url in the address bar and you'll have to JSON'ify the non-string types such as objects or arrays.
popup.js:
chrome.tabs.create({
url: 'other-page.html?data=' + encodeURIComponent(JSON.stringify({foo: [1, 2, 3]})),
});
other-page.js:
let sharedData;
try {
sharedData = JSON.parse(new URLSearchParams(location.search).get('data'));
} catch (e) {}
// simplify the displayed URL in the address bar
history.replace({}, document.title, location.origin + location.pathname);
3. MV2: Background script's global variable + synchronous access
Use if you need to access the data during loading inside the other page before the first painted frame e.g. to choose a light/dark theme.
Disadvantage 1: the need for a background page.
Disadvantage 2: the need to deep-clone the objects by using JSON.parse(JSON.stringify(data)) or a custom deepClone that works properly for cross-window contexts because none of the popular deepClone implementations do it AFAIK: specifically it should use the target window
's reference of the object constructor.
manifest.json:
"background": {
"scripts": ["bg.js"],
"persistent": false
}
popup.js:
// ensure the non-persistent background page is loaded
chrome.runtime.getBackgroundPage(bg => {
// using JSON'ification to avoid dead cross-window references.
bg.sharedData = JSON.stringify({foo: 123, bar: [1, 2, 3], theme: 'dark'});
chrome.tabs.create({url: 'other-page.html'});
});
other-page.js:
// if this tab was reloaded the background page may be unloaded and the variable is lost
// but we were saving a copy in HTML5 sessionStorage!
let sharedData = sessionStorage.sharedData;
if (!sharedData) {
const bg = chrome.extension.getBackgroundPage();
sharedData = bg && bg.sharedData;
if (sharedData) {
sessionStorage.sharedData = sharedData;
}
}
// using JSON'ification to avoid dead cross-window references.
try {
sharedData = JSON.parse(sharedData);
} catch (e) {}
4. MV2/MV3: Background script relay messaging in two hops
Use if you need to perform a sequence of actions in the background page of which opening the tab is just the first step. For example we need to pass the data in the second step.
The background page is needed because the popup would be closed and its scripts won't run anymore when an active tab opens in the same window where the popup is displayed. One might think that creating a tab with active: false
would solve the problem but only until a user decides to install another extension that overrides tab opening behavior. You would think you could open a new window but again there's no guarantee some other extension doesn't re-attach the new window's tab into the existing window thus closing your popup.
Disadvantage 1: in Chrome the data is JSON'ified internally so it nukes all the nonstandard types such as WeakMap, TypedArray, Blob, etc. In Firefox they seem to be using structured cloning so more types can be shared.
Disadvantage 2: we're sending the same data message twice.
Note: I'm using Mozilla's WebExtension polyfill.
manifest.json:
"background": {
"scripts": [
"browser-polyfill.min.js",
"bg.js"
],
"persistent": false
}
popup.js:
chrome.runtime.sendMessage({
action: 'openTab',
url: '/other-page.html',
data: {foo: 123, bar: [1, 2, 3], theme: 'dark'},
});
bg.js:
function onTabLoaded(tabId) {
return new Promise(resolve => {
browser.tabs.onUpdated.addListener(function onUpdated(id, change) {
if (id === tabId && change.status === 'complete') {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve();
}
});
});
}
browser.runtime.onMessage.addListener(async (msg = {}, sender) => {
if (msg.action === 'openTab') {
const tab = await browser.tabs.create({url: msg.url});
await onTabLoaded(tab.id);
await browser.tabs.sendMessage(tab.id, {
action: 'setData',
data: msg.data,
});
}
});
other-page.html:
<!doctype html>
<p id="text"></p>
<!-- scripts at the end of the page run when DOM is ready -->
<script src="other-page.js"></script>
other-page.js:
chrome.runtime.onMessage.addListener((msg, sender) => {
if (msg.action === 'setData') {
console.log(msg.data);
document.getElementById('text').textContent = JSON.stringify(msg.data, null, ' ');
// you can use msg.data only inside this callback
// and you can save it in a global variable to use in the code
// that's guaranteed to run at a later point in time
}
});
5. MV2/MV3: chrome.storage.local
See the example for chrome.storage.local in this answer.
Open and pass data to a popup (new tab) from content script
Ok, I found two solutions to my problem.
1) Add a background page, which opens a popup with chrome.tabs.create()
. Then send a message from a content script to a background page, which re-sends it to a corresponding tab via chrome.tabs.sendMessage()
. It looks a little ugly, but works.
2) A better one, w/out background page. Extension (popup) page creates a listener for long-lived connection. Then content script sends a message to this connection. A problem here is that a listener is not created right after the page is opened, so there should be a mechanism for a content script to wait until popup is loaded. It can be a simple setTimeout
or a notification from popup via same long-lived connection.
If anyone has a better solution I'd gladly check it out as well.
Simple chrome extension opening new tab and showing data from page
You would need 2 scripts for this - Content Script, Background Script, apart from manifest.json
Process would be :-
1)Fetch the data from current page using content script.
2)Notify Background with the your data and pass as a message from content script.
3)Receive a new message in background script and create new tab with the message data.
For e.g. - Pass title from current page, and set in new Tab.
Content Script -
//send current window title to background js.
chrome.runtime.sendMessage({'title': window.title})
Background Script -
//receive message
chrome.runtime.onMessage((message, sender) => {
chrome.tabs.create({url: 'NEW URL'}, (tab) => {
setTimeout(() => {
//use your message data here.
chrome.tabs.executeScript(tab.id, {code: "document.title = message.title"})
}, 3000);
})
});
chrome extension - passing data from background to custom html page
The callback of chrome.tabs.create
returns as soon as the tab is created, not after it fully loads.
As such, you have a race condition: your message is potentially sent before the listener is initialized. Since it's a race condition, it can sometimes work.
The correct logic here would be to send a message requesting data from the newly opened tab, and use sendResponse
to pass that data from the background.
// analytics.js
chrome.runtime.sendMessage({"action" : "getTxns"}, function(txns) {
// Process the data
});
// background.js
// Register this before opening the tab
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.action == "getTxns") {
sendResponse(txnDataJSON); // No need to serialize yourself!
}
});
Related Topics
How to Get the Day of Week and the Month of the Year
What Is "Export Default" in JavaScript
Filtering Array of Objects with Arrays Based on Nested Value
Turning Off Eslint Rule for a Specific Line
Three.Js Generate Uv Coordinate
Making Custom Right-Click Context Menus for My Web-App
How to Parse JSON to Receive a Date Object in JavaScript
How to Find the Height of Text on an HTML Canvas
JavaScript - Populate Drop Down List with Array
React Component Initialize State from Props
JavaScript Thousand Separator/String Format
How to Extract the Hostname Portion of a Url in JavaScript
How to Detect Online/Offline Event Cross-Browser
How to Get Evaluated Attributes Inside a Custom Directive
Are Es6 Classes Just Syntactic Sugar for the Prototypal Pattern in JavaScript