So, I have an input in popup.html. If you enter data, popup.js sends the data to content.js and it already changes something on tab. But after updating tab changes on the tab is gone. How can you make it so that they would stay and change only if new data were entered into the input?
popup.html и popup.js
var input = document.querySelector('#inp');
var text = document.querySelector("#text");
var button = document.querySelector("#btn");
btn.addEventListener('click', function() {
var inp = input.value;
chrome.tabs.query({active: true, currentWindow: true}, function(foundTabs) {
const activeTab = foundTabs[0];
chrome.tabs.sendMessage(activeTab.id, {text: inp});//sending value of input
})
});
<input type="text" id="inp">
<button id="btn">Send</button>
content.js:
chrome.runtime.onMessage.addListener(function(request) {
const txt = request.text; //get input value
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
//insert in html
});
});
In order to keep the changes made, I would use localStorage (permanent) or sessionStorage (last as long as the window). With that, you can keep track of the changes made and re-make them when the webpage is updated.
For example:
content.js
//whenever the page is loaded/updated, check if there was a saved value
var my_text = sessionStorage.getItem("my_text");
if (my_text) { // if there was, update the page accordingly.
document.querySelector("input").value = my_text;
}
chrome.runtime.onMessage.addListener(function(message){ //when a message is received...
my_text = message.text;
document.querySelector("input").value = my_text; //...update the webpage ...
sessionStorage.setItem("my_text", my_text); // ... and save the value
});
This way your changes will persist upon page reloads, until the window is closed. To make them persist even if the window is closed, substitute sessionStorage by localStorage.
To save changes permanently you need to save the state somewhere.
For this purpose I recommend looking at the Chrome developer background page.
You can send a message from a background page to your content script, which is content.js.
Related
I am trying to get this chrome storage sync set to run when the person closes the popup window in my chrome extension but cannot seem to get it working. That way if they abruptly close the extension window say by clicking in their browser or whatever their data still stores. Anyone have any ideas?
window.addEventListener("beforeunload", function load(unloadEvent) {
let currentTimeGet = document.getElementById("currentTimeInfo").innerHTML;
chrome.storage.sync.set({ ct: currentTimeGet }, function() {
console.log("Value is set to " + currentTimeGet);
});
});
beforeunload event is ignored for the popup shown by browser_action or page_action.
unload won't wait for the asynchronous chrome.storage to complete so the data won't be stored
Based on this several solutions are possible.
Autosave on any change.
This is the preferable and modern user-friendly solution. You can give the elements an id that's equal to their name in chrome.storage like <input type="text" id="userName" class="storage">.
const STORAGE_SELECTOR = '.storage[id]';
let debounceTimer;
document.addEventListener('change', saveOnChange);
document.addEventListener('input', saveOnChange);
function saveOnChange(e) {
if (e.target.closest(STORAGE_SELECTOR)) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(doSave, 100);
}
}
function collectData() {
const data = {};
for (const el of document.querySelectorAll(STORAGE_SELECTOR))
data[el.id] = el.type === 'checkbox' ? el.checked : el.value;
return data;
}
As for doSave() function, either simply overwrite the current options data in storage
function doSave() {
chrome.storage.sync.set(collectData());
}
or save under a separate autoSave and check it the next time the popup is shown:
function doSave() {
chrome.storage.sync.set({autoSave: collectData()});
}
function loadFromStorage() {
chrome.storage.sync.get(data => {
if (data.autoSave) data = data.autoSave;
for (const [id, value] of Object.entries(data)) {
const el = document.getElementById(id);
if (el) el[el.type === 'checkbox' ? 'checked' : 'value'] = value;
}
});
}
loadFromStorage();
Save the data in unload event directly in the backround script's JavaScript window object e.g. chrome.extension.getBackgroundPage().popupData = collectData().
It's bad because it requires a persistent background page (or a temporarily persistent page) and because Chrome moves away from unload events in general. The data may be easily lost on a crash of the extension process or of the browser itself, it might be lost if the browser was closed by the user.
Following OctoberCMS guidelines I need to redirect users when they attempt to reach a certain path (ex: www.foo.com/bar) to the home page with php variables (ex: www.foo.com/?bar=1) and have a modal open if the variable is found.
I've created bar.htm which redirects with the proper variable:
title = "bar"
url = "/bar"
layout = "default"
meta_title = ""
meta_description = ""
is_hidden = 0
==
<?php
function onStart() {
header('Location:/?bar=1');
}
?>
==
and then in my JS (with #submitBtn being the button on the modal to send the form)
$(document).ready(function(){
if(window.location.href.indexOf('1') > -1) {
$('#myModal').modal('show');
$('#submitBtn').click(function() {
window.location.href='/';
})
}
});
So this is not working, it is just showing the modal in a constant loop and never moving to the success modal or changing the href. This form is working properly in all other situations, so I do not believe the issue lies there.
Shortened example modal code:
$('#myModal').on('show.bs.modal', function (event) {
var newForm = $('#newFakeForm');
var config = {
submitBtn: $('#submitBtn'),
contactModal: $('#myModal'),
successModal: $('#successModal'),
};
initContactForm(newFakeForm, config);
});
and my HTML button to dismiss the modal:
Thank you
What if you do the redirect on JS?
Just listen for the hidden.bs.modal event and do the redirect there.
I have a settings page on my WebExtension, but I dont know how to acces the values of the settings with javascript.
Current .xul-File:
<?xml version="1.0"?>
<!DOCTYPE mydialog SYSTEM "chrome://myaddon/locale/mydialog.dtd">
<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<setting type="string" pref="extensions.check.email" title="email" desc="please insert your email here" />
</vbox>
How do I acces the "email" value? Can I just write something like "getPreferences('email')"?
Don't use XUL in a WebExtension add-on:
If you are using XUL from within a WebExtension, then something is probably wrong. If you are writing a WebExtension, and you start to do XUL: take a step back. Make sure that is really what you are supposed to be doing. One of the points of WebExtensions is that it insulates the add-on from the internals of Firefox (i.e. from XUL).
Options panels and pages:
In general, options should be stored in storage.local (or storage.sync, when supported). If you are attempting to communicate from an options page, or a panel back to your main background script there are, at least, 4 somewhat different ways to do so:
Options are stored to storage.local in the options.js code. The background code listens for events from storage.onChanged. No need to pass messages back and forth. No need for your options/panel code to specifically notify the background page that changes have occurred.
Options are stored to storage.local in the options.js code. Then, the options.js directly invokes the a function in your background script to have the background script re-read the options. In the code below, the getOptions() function in the background.js file is directly invoked by the options.js code.
Options are stored to storage.local in the options.js code. Use chrome.runtime.sendMessage() to send a message to your background page that the options have changed. In the code below: After storing the options in storage.local, the options.js sends an optionsUpdated message to the background script that the options have been updated. The background script then re-reads the options. [The message can be whatever you want that indicates this. optionsUpdated is merely what I chose as the message in the code below.]
Use chrome.runtime.sendMessage() to send a message with all options data to the background page. In the code below: An optionsData message is sent from the options.js code to the background page when the options are change which contains a data payload with all of the options. The options are then stored to storage.local in the background script. Once the options are stored, the background script sends a optionsStored message back to the options.js code. The options.js code then indicates to the user that the options have been saved. [The message can be whatever you want that indicates this. optionsData and optionsStored is merely what I chose as the message in the code below.]
Below is a WebExtension that demonstrates those four different methods of getting the changed option information back to the background script.
Note: The code below uses a browser_action button to bring up a panel with the exact same options as are used for options_ui. This is done for the purpose of demonstration. If you are going to have a button that opens your options, it may be better to directly open your options_ui page with runtime.openOptionsPage(). Which you do depends on the user interface you want to present to the user.
background.js:
var useDirect=0; //Holds the state of how we communicate with options.js
var emailAddress=''; //The email address from options.
const useDirectTypes=[ 'Listen to chrome.storage changes'
,'Directly invoke functions in the background script from'
+ ' the options/panel code'
,'Send a message that data in storage.local was updated'
,'Send a message with all options data' ];
//Register the message listener
chrome.runtime.onMessage.addListener(receiveMessage);
function receiveMessage(message,sender,sendResponse){
//Receives a message that must be an object with a property 'type'.
// This format is imposed because in a larger extension we may
// be using messages for multiple purposes. Having the 'type'
// provides a defined way for other parts of the extension to
// both indicate the purpose of the message and send arbitrary
// data (other properties in the object).
console.log('Received message: ',message);
if(typeof message !== 'object' || !message.hasOwnProperty('type')){
//Message does not have the format we have imposed for our use.
//Message is not one we understand.
return;
}
if(message.type === "optionsUpdated"){
//The options have been updated and stored by options.js.
//Re-read all options.
getOptions();
}
if(message.type === "optionsData"){
saveOptionsSentAsData(message.data,function(){
//Callback function executed once data is stored in storage.local
console.log('Sending response back to options page/panel');
//Send a message back to options.js that the data has been stored.
sendResponse({type:'optionsStored'});
//Re-read all options.
getOptions();
});
//Return true to leave the message channel open so we can
// asynchronously send a message back to options.js that the
// data has actually been stored.
return true;
}
}
function detectStorageChange(change){
//Ignore the change information. Just re-get all options
console.log('Background.js: Detected storage change');
getOptions();
}
function listenToStorageChanges(){
chrome.storage.onChanged.addListener(detectStorageChange);
}
function stopListeningToStorageChanges(){
chrome.storage.onChanged.removeListener(detectStorageChange);
}
function getOptions(){
//Options are normally in storage.sync (sync'ed across the profile).
//This example is using storage.local.
//Firefox does not currently support storage.sync.
chrome.storage.local.get({
useDirect: 0,
emailAddress: ''
}, function(items) {
if(typeof items.useDirect !== 'number' || items.useDirect <0
|| items.useDirect >= useDirectTypes.length) {
items.useDirect=0;
}
useDirect = items.useDirect;
emailAddress = items.emailAddress;
console.log('useDirect=' + useDirectTypes[useDirect]);
console.log('email address=' + emailAddress);
});
}
function saveOptionsSentAsData(data,callback) {
//Options data received as a message from options.js is
// stored in storeage.local.
chrome.storage.local.set(data, function() {
//Invoke a callback function if we were passed one.
if(typeof callback === 'function'){
callback();
}
});
}
//Read the options stored from prior runs of the extension.
getOptions();
//On Firefox, open the Browser Console:
//To determine if this is Chrome, multiple methods which are not implemented
// in Firefox are checked. Multiple ones are used as Firefox will eventually
// support more APIs.
var isChrome = !! window.InstallTrigger
|| (!!chrome.extension.setUpdateUrlData
&& !!chrome.runtime.reload
&& !!chrome.runtime.restart);
if(!isChrome) {
//In Firefox cause the Browser Console to open by using alert()
window.alert('Open the console. isChrome=' + isChrome);
}
options.js:
// Saves options to chrome.storage.local.
// It is recommended by Google that options be saved to chrome.storage.sync.
// Firefox does not yet support storage.sync.
function saveOptions(data, callback) {
chrome.storage.local.set(data, function() {
if(typeof callback === 'function'){
callback();
}
// Update status to let user know options were saved.
notifyOptionsSaved();
});
}
function optionsChanged() {
//Get the selected option values from the DOM
let useDirectValue = document.getElementById('useDirect').value;
let email = document.getElementById('email').value;
useDirectValue = +useDirectValue; //Force to number, not string
//Put all the option data in a single object
let optionData = {
useDirect: useDirectValue,
emailAddress: email
}
setBackgroundPageNotListeningToStorageChanges();
if(useDirectValue == 0 ) {
//Use listening to storage changes
//console.log('Going to listen for storage changes');
setBackgroundPageListeningToStorageChanges();
saveOptions(optionData);
} else if (useDirectValue == 1) {
//We save the options in the options page, or popup
saveOptions(optionData, function(){
//After the save is complete:
//The getOptions() functon already exists to retrieve options from
// storage.local upon startup of the extension. It is easiest to use that.
// We could remove and add the listener here, but that code already
// exists in background.js. There is no reason to duplicate the code here.
let backgroundPage = chrome.extension.getBackgroundPage();
backgroundPage.getOptions();
});
} else if (useDirectValue == 2) {
//We save the options in the options page, or popup
saveOptions(optionData, function(){
//Send a message to background.js that options in storage.local were updated.
chrome.runtime.sendMessage({type:'optionsUpdated'});
});
} else {
//Send all the options data to background.js and let it be dealt with there.
chrome.runtime.sendMessage({
type:'optionsData',
data: optionData
}, function(message){
//Get a message back that may indicate we have stored the data.
if(typeof message === 'object' && message.hasOwnProperty('type')){
if(message.type === 'optionsStored') {
//The message received back indicated the option data has
// been stored by background.js.
//Notify the user that the options have been saved.
notifyOptionsSaved();
}
}
});
}
}
function setBackgroundPageListeningToStorageChanges(){
//Normally the listener would be set up once, and only once, within the
// background page script. We are doing it here to demonstrate switing
// between the different methods of notification.
let backgroundPage = chrome.extension.getBackgroundPage();
//either add the listener directly:
chrome.storage.onChanged.addListener(backgroundPage.detectStorageChange);
//or let the background page add it:
//backgroundPage.listenToStorageChanges();
}
function setBackgroundPageNotListeningToStorageChanges(){
//Normally the listener would be set up once, and only once, within the
// background page script. We are doing it here to demonstrate switing
// between the different methods of notification.
let backgroundPage = chrome.extension.getBackgroundPage();
//either remove the listener directly:
chrome.storage.onChanged.removeListener(backgroundPage.detectStorageChange);
//or let the background page add it:
//backgroundPage.stopListeningToStorageChanges();
}
// Restores select box and input using the preferences
// stored in chrome.storage.
function useStoredOptionsForDisplayInDOM() {
chrome.storage.local.get({
useDirect: 0,
emailAddress: ''
}, function(items) {
//Store retrieved options as the selected values in the DOM
document.getElementById('useDirect').value = items.useDirect;
document.getElementById('email').value = items.emailAddress;
});
//notifyStatusChange('Option read');
}
function notifyOptionsSaved(callback){
//Notify the user that the options have been saved
notifyStatusChange('Options saved.',callback);
}
function notifyStatusChange(newStatus,callback){
let status = document.getElementById('status');
status.textContent = newStatus;
//Clear the notification after a second
setTimeout(function() {
status.textContent = '';
if(typeof callback === 'function'){
callback();
}
}, 1000);
}
document.addEventListener('DOMContentLoaded', useStoredOptionsForDisplayInDOM);
document.getElementById('optionsArea').addEventListener('change',optionsChanged);
//In order to track the change if this is open in _both_ the browser_action panel
// and the add-on options page, we need to listen for changes to storage.
// There is already a function which reads all of the options, so don't
// use the information as to what changed, just that a change occurred.
chrome.storage.onChanged.addListener(useStoredOptionsForDisplayInDOM);
options.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebRequest Logging Options</title>
<style>
body: { padding: 10px; }
</style>
</head>
<body>
<div id="optionsArea">
Communication with background page:
<select id="useDirect">
<option value="0">Listen for storage changes</option>
<option value="1">Directly call background page functions</option>
<option value="2">Send a Message Updated storage.local</option>
<option value="3">Send a Message with all Data</option>
</select>
<div>Email:
<input id="email"></input>
</div>
</div>
<div id="status" style="top:0px;display:inline-block;"></div>
<script src="options.js"></script>
</body>
</html>
manifest.json:
{
"description": "Demonstrate an email field in options with various ways of communicating with the background script.",
"manifest_version": 2,
"name": "email-in-options-demo",
"version": "0.1",
"applications": {
"gecko": {
//Firefox: must define id to use option_ui:
"id": "email-in-options-demo#example.example",
"strict_min_version": "48.0"
}
},
"permissions": [
"storage"
],
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {
"default_icon": {
"48": "myIcon.png"
},
"default_title": "Show panel",
"browser_style": true,
"default_popup": "options.html"
},
"options_ui": {
"page": "options.html",
"chrome_style": true
}
}
The code above is based on code in my answer to Update WebExtension webRequest.onBeforeRequest listener URL settings from separate script.
I need to prevent a user from downloading a file (PDF) until they have entered some simple details into a form. We need to capture the details so that we can see who is downloading the file.
See a jsFiddle here http://jsfiddle.net/ctn7N/1/
The steps I need it to follow are:
User opens page. If they have already filled out the capture form, store that state in a variable.
They click a download link. Store the link so that it can be used later.
If they've already entered details, i.e. check the variable, open the link in a new tab as normal (default behaviour).
If they haven't entered details, show the capture form.
Once they click submit on the form, show the downloads section again, store the state and open the original download that they clicked on in a new tab.
On subsequent loads of the page they should not have to enter their details again and downloads should just open.
The current code I'm using fails at the last part of step 5, when it tries to open the download link in a new tab. Although it works in the fiddle, it breaks in Chrome v35.0 because the link is blocked by a popup blocker.
Is there any way around this that would allow it to open in all browsers?
Thanks for looking,
Adam
Code to accompany fiddle:
HTML Code
<div id="capture-section" class="hide">
<form id="capture-form">
<label for="name">Enter your name to download the file:</label>
<input id="name" name="name" type="text" />
<button id="submit-btn" type="submit">Submit</button>
</form>
</div>
<div id="download-section">
<!-- Download links replaced with placeholder links for jsFiddle, would normally be PDFs -->
<a target="_blank" class="js-download" href="http://example.com">Document 1</a>
<a target="_blank" class="js-download" href="http://www.google.com">Document 2</a>
<a target="_blank" class="js-download" href="http://www.bing.com">Document 3</a>
</div>
JavaScript
$(document).ready(function() {
var detailsEntered = '',
downloadLink = '';
// Would normally access localStorage on load of page to see if user has already entered details
// Removed to allow multiple jsFiddle runs for a user
//
// detailsEntered = accessStorage('retrieve', 'detailsEntered');
$('.js-download').click(function(event) {
var self = $(this);
downloadLink = self.attr('href'); // Store clicked download link
if (detailsEntered != 'true') {
// If the user hasn't entered details yet, show the form
$('#download-section').addClass('hide');
$('#capture-section').removeClass('hide');
event.preventDefault();
return false;
} // Otherwise allow standard link behviour
});
$("#submit-btn").click(function(event) {
var name = $('input[name=name]').val(),
proceed = true;
if(name==""){
$('input[name=name]').addClass("error");
proceed = false;
}
if(proceed) {
// If form validates, show downloads again and store value for return visits
$('#capture-form input').val('');
$('#capture-section').addClass('hide');
$('#download-section').removeClass('hide');
detailsEntered = 'true';
accessStorage('store', 'detailsEntered', 'true');
// Now open previously clicked download link in new tab
// DOES NOT WORK - Blocked by popup blocker
window.open(downloadLink, '_blank');
}
event.preventDefault();
return false;
});
//reset previously set border colors and hide all message on .keyup()
$("input, textarea").keyup(function() {
$(this).removeClass("error");
});
function accessStorage(action, dataKey, dataValue) {
if(typeof(Storage) === "undefined") {
// No support for localStorage/sessionStorage.
return false;
}
if (action == 'store') {
localStorage.setItem(dataKey, dataValue);
} else if (action == 'retrieve') {
return localStorage.getItem(dataKey);
}
}
});
A solution, if you don't need to open a new page, would be to simply change the location of the current page (means no popup issue) :
if(proceed) {
// If form validates, show downloads again and store value for return visits
$('#capture-form input').val('');
$('#capture-section').addClass('hide');
$('#download-section').removeClass('hide');
detailsEntered = 'true';
accessStorage('store', 'detailsEntered', 'true');
// Now open previously clicked download link in new tab
window.location.href = window.location.protocol + "//" + window.location.host + downloadLink; // if downloadLink is a relative URI
// window.location.href = downloadLink; // if downloadLink is an absolute URI
}
I have made a solution for my website which includes using ajax to present the general information on the website. In doing this, I am changing the URL every time a user loads some specific content with the window.history.pushState method. However, when I press backspace or press back, the content of the old url is not loaded (however the URL is loaded).
I have tried several solutions presented on SO without any luck.
Here is an example of one of the ajax functions:
$(document).ready(function(){
$(document).on("click",".priceDeckLink",function(){
$("#hideGraphStuff").hide();
$("#giantWrapper").show();
$("#loadDeck").fadeIn("fast");
var name = $(this).text();
$.post("pages/getPriceDeckData.php",{data : name},function(data){
var $response=$(data);
var name = $response.filter('#titleDeck').text();
var data = data.split("%%%%%%%");
$("#deckInfo").html(data[0]);
$("#textContainer").html(data[1]);
$("#realTitleDeck").html(name);
$("#loadDeck").hide();
$("#hideGraphStuff").fadeIn("fast");
loadGraph();
window.history.pushState("Price Deck", "Price Deck", "?p=priceDeck&dN="+ name);
});
});
Hope you guys can help :)
pushState alone will not make your page function with back/forward. What you'd need to do is listen to onpopstate and load the contents yourself similar to what would happen on click.
var load = function (name, skipPushState) {
$("#hideGraphStuff").hide();
// pre-load, etc ...
$.post("pages/getPriceDeckData.php",{data : name}, function(data){
// on-load, etc ...
// we don't want to push the state on popstate (e.g. 'Back'), so `skipPushState`
// can be passed to prevent it
if (!skipPushState) {
// build a state for this name
var state = {name: name, page: 'Price Deck'};
window.history.pushState(state, "Price Deck", "?p=priceDeck&dN="+ name);
}
});
}
$(document).on("click", ".priceDeckLink", function() {
var name = $(this).text();
load(name);
});
$(window).on("popstate", function () {
// if the state is the page you expect, pull the name and load it.
if (history.state && "Price Deck" === history.state.page) {
load(history.state.name, true);
}
});
Note that history.state is a somewhat less supported part of the history API. If you wanted to support all pushState browsers you'd have to have another way to pull the current state on popstate, probably by parsing the URL.
It would be trivial and probably a good idea here to cache the results of the priceCheck for the name as well and pull them from the cache on back/forward instead of making more php requests.
This works for me. Very simple.
$(window).bind("popstate", function() {
window.location = location.href
});
Have same issue and the solution not working for neither
const [loadBackBtn, setLoadBackBtn] = useState(false);
useEffect(() => {
if (loadBackBtn) {
setLoadBackBtn(false);
return;
} else {
const stateQuery = router.query;
const { asPath } = router;
window.history.pushState(stateQuery, "", asPath);
},[router.query?.page]