Popup.html closing when attempting Text Selection on my chrome extension [duplicate] - javascript

This question already has answers here:
Getting selected text in a Chrome extension
(1 answer)
How to access the webpage DOM/HTML from an extension popup or background script?
(2 answers)
Closed 11 days ago.
This post was edited and submitted for review 10 days ago.
I am creating a chrome extension that allows a user to click and select multiple texts on a web page. The selected text will be displayed on the extension window as the user is selecting the text. The extension will then allow the user to download the list of text selected as an excel file
Here is my code:
Manifest.json
{
"manifest_version": 2,
"name": "Text Selector",
"version": "1.0",
"description": "This extension allows you to select text from web pages.",
"permissions": [
"activeTab",
"https://*/*"
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
}
popup.html
<!DOCTYPE html>
<html>
<head>
<style>
#selected-text {
height: 200px;
overflow-y: scroll;
border: 1px solid #ccc;
padding: 10px;
}
</style>
</head>
<body>
<h2>Selected Text:</h2>
<div id="selected-text"></div>
<button id="download-button">Download as Excel</button>
</body>
<script src="popup.js"></script>
</html>
popup.js
// Initialize the state of text selection
var isTextSelectionEnabled = false;
// Listen for the user clicking the "Enable Text Selection" button
document.getElementById('enable-text-selection').addEventListener('click', function(event) {
// Toggle the state of text selection
isTextSelectionEnabled = !isTextSelectionEnabled;
// Change the button text based on the state of text selection
if (isTextSelectionEnabled) {
document.getElementById('enable-text-selection').innerText = 'Disable Text Selection';
} else {
document.getElementById('enable-text-selection').innerText = 'Enable Text Selection';
}
});
// Listen for the user clicking on the page
document.addEventListener('mouseup', function(event) {
// Only add the selected text to the list if text selection is enabled
if (isTextSelectionEnabled) {
// Get the selected text
var selectedText = window.getSelection().toString();
if (selectedText) {
// Add the selected text to the list
var selectedTextList = document.getElementById('selected-text');
var newItem = document.createElement('div');
newItem.innerText = selectedText;
selectedTextList.appendChild(newItem);
}
}
});
// Listen for the user clicking the download button
document.getElementById('download-button').addEventListener('click', function(event) {
// Get the list of selected text
var selectedTextList = document.getElementById('selected-text');
var textArray = [];
for (var i = 0; i < selectedTextList.children.length; i++) {
textArray.push(selectedTextList.children[i].innerText);
}
// Convert the text array to a CSV string
var csvString = textArray.join(',');
// Download the CSV string as an Excel file
var link = document.createElement('a');
link.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvString);
link.download = 'selected-text.csv';
link.click();
});
When clicking on any text in a webpage, the popup.html is closing and there is no selected text displayed on it. When downloading the excel it is empty

Related

Chrome extention - How to save changes that were made into storage? [duplicate]

This question already has answers here:
How to store objects in HTML5 localStorage/sessionStorage
(24 answers)
saving and retrieving from chrome.storage.sync
(6 answers)
Closed 1 year ago.
I am new to coding and currently I am trying to build an chrome extension that acts as a little notebook and can store things inside. I have already finished a very raw example but it works totally fine. Right now the biggest obstacle is how to save the changes after closing the popup and the browser. I did some research and found that I can use localStorage to store the data. Yet I am kinda lost and not sure where to start and how to use it.
Here is pretty much everything I have
manifest.json
{
"name": "1st extension",
"description": "Try to build an extention",
"version": "0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_title": "This is my first chrome extention",
},
"permissions": ["storage"],
"options_page": "options.html"
}
popup.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="button.css">
</head>
<body>
<p>Type in something and save it below!</p>
<textarea id="mytext" style="width: 300px;"></textarea>
<button id="submit">Submit</button>
<button id="clear">Clear</button>
<ul id="items"></ul>
<script src="popup.js"></script>
</body>
</html>
popup.js
var myButton1 = document.getElementById("submit");
var myButton2 = document.getElementById("clear");
var itemList = document.getElementById('items');
itemList.addEventListener('click', removeItem);
//add new element into the list
function addItem(input) {
// new element
var li = document.createElement("li");
// new delete button
var deletebutton = document.createElement("button");
//add classes to btn
deletebutton.className = "delete";
// text in the delete buttion
deletebutton.appendChild(document.createTextNode('Delete'))
// text in the new element
li.appendChild(document.createTextNode(input));
// combine text and the delete button
li.appendChild(deletebutton);
// conbine the element to the list
itemList.appendChild(li);
}
//remove item from the list
function removeItem(element){
itemList.removeChild(element.target.parentElement);
}
//clear the textarea
function clear(){
document.getElementById('mytext').value = "";
};
myButton1.onclick = function() {
var myText = document.getElementById('mytext').value;
addItem(myText);
clear();
}
myButton2.onclick = function() {
clear();
}
Using storage is easy...
localStorage.setItem('Phone','1234-5678');
And reading it back...
var Phone=localStorage.getItem('Phone');
But I hope you're aware that the data won't be there in incognito windows and also... some browsers (at least mine) blow all storages away regardless, once you exit the browser.
So it's a very volatile solution and not suitable if you were looking for permanent save to the hard disk.
Let me check the Google APIs to see what else they've got for saving to the HD.
Here's one method, but dealing with blobs is not for novices...
https://developers.google.com/web/updates/2011/08/Saving-generated-files-on-the-client-side

Edit - Selecting an image on Instagram is not working on my chrome extension

EDIT #2
I've just tried selecting every div element on the website as each image is contained in a div. So, I used querySelectorAll followed with an if statement. The if statement checks the div and makes sure the image (class: FFVAD) is actually inside of the div before proceeding. But it's not working, it's now throwing a getElementsByClassName is not a function error.
My content script:
console.log('injected')
window.onload = function() {
var imagediv = document.querySelectorAll('div')
console.log('selected elements')
var i;
for (i = 0; i < imagediv.length; i++) {
imagediv[i].addEventListener('mouseover', function(el){
if (el.getElementsByClassName('FFVAD').length > 0) { //if the image with the class FFVAD is in the div
el.target.style.border = "7px solid red" //change the certain div's color
}
})
}
}
PREVIOUS QUESTION
I am developing a chrome extension which injects a javascript-written script into Instagram. I am trying to get the src of each image displayed on the users profile.
I have tried using document.querySelectorAll('img') to get every image element, but that isn't working. So, I took a peek in the developer console and noticed each image has a class called FFVAD. So, I used document.getElementsByClassName('FFVAD') (reference here). I also noticed each image has a div element as their parent. So, I tried doing this with my code:
My content script:
console.log('injected')
var image = document.getElementsByClassName('FFVAD') // instagram sets all
their images as the class FFVAD, as I looked in the inspect element window
console.log('selected elements')
while(image.length > 0){ //for every image
image[0].parentNode.addEventListener("mouseover", function (event) { //when the image's parent is hovered
event.style.border = "2px solid red";
});
image[0].parentNode.addEventListener("mouseout", function (event) { //when the image's parent isn't hovered
event.style.border = "none";
});
image[0].parentNode.addEventListener("click", function(event){ //when the image's parent is clicked
var child = event.childNodes; //get the image in the image's div
console.log(child[1].src) //log the src of the image
});
}
My exensions manifest:
{
"manifest_version": 2,
"name": "Testing",
"description": "Test extension",
"version": "0.0.1",
"author": "tester123",
"icons":{
"16": "icon.png",
"48": "icon2.png",
"128": "icon3.png"
},
"content_scripts": [{
"all_frames": false,
"js": ["imgselect.js"],
"matches": [ "https://*.instagram.com/*"],
"run_at": "document_end"
}],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"tabs"
]
}
I used parentNode to get the parent of the image. I expect to see a red border around the image's div parent when it's hovered and seeing the src of the image in the console but I'm not seeing results. Any help will be appreciated, thank you!
This worked for me!
I looked deeper into the developer console and realized every image on Instagram is inside of multiple divs, so I put together this code that highlights the main div that contains the image, and it works perfectly!
console.log('injected')
function show(e) {
console.log("post found")
var img = this.getElementsByTagName('img')
for (i = 0; i <img.length; i++){
if (img[i].className == "FFVAD"){
console.log(img[i].src)
this.style.border = "5px solid red"
}
}
}
function hide(e){
var img = this.getElementsByTagName('img')
for (i = 0; i <img.length; i++){
if (img[i].className == "FFVAD"){
console.log(img[i].src)
this.style.border = "none"
}
}
}
setInterval(function(){
var imagediv = document.getElementsByClassName('KL4Bh')
var i;
for (i = 0; i < imagediv.length; i++) {
var parent = imagediv[i].parentNode
var secondparent = parent.parentNode
var thirdparent = secondparent.parentNode
var children = parent.children
if (children.length > 1){
children[1].remove()
thirdparent.addEventListener('mouseover', show)
thirdparent.addEventListener('mouseout', hide)
}
}
},1000)

Download a text file from textarea on Button Click

I am using Jquery UI Dialog. Within the dialog there is textarea that have some text. And I need to save that text as textfile like data.txt when I click the button in the dialog.
<div id = 'metaDataDialog' title='Meta Data' >
<textarea id = 'metaText'>
Some Text
</textarea>
</div>
and this is the jquery ui dialog
$("#metaDataDialog").dialog({ //Jquery UI Dialog Intialization
autoOpen: false,
modal: true,
width: 400,
height: 300,
buttons: {
Save: function() {},
Cancel: function() { $(this).dialog( "close" ); }
},
});
and I need to save/download the text in the local machine, when the save button is clicked
<html>
<head>
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script>
$(document).ready(function () {
function saveTextAsFile() {
// grab the content of the form field and place it into a variable
var textToWrite = document.getElementById("content").value;
// create a new Blob (html5 magic) that conatins the data from your form feild
var textFileAsBlob = new Blob([textToWrite], { type: 'text/plain' });
// Specify the name of the file to be saved
var fileNameToSaveAs = "myNewFile.txt";
// Optionally allow the user to choose a file name by providing
// an imput field in the HTML and using the collected data here
// var fileNameToSaveAs = txtFileName.text;
// create a link for our script to 'click'
var downloadLink = document.createElement("a");
// supply the name of the file (from the var above).
// you could create the name here but using a var
// allows more flexability later.
downloadLink.download = fileNameToSaveAs;
// provide text for the link. This will be hidden so you
// can actually use anything you want.
downloadLink.innerHTML = "My Hidden Link";
// allow our code to work in webkit & Gecko based browsers
// without the need for a if / else block.
window.URL = window.URL || window.webkitURL;
// Create the link Object.
downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
// when link is clicked call a function to remove it from
// the DOM in case user wants to save a second file.
downloadLink.onclick = destroyClickedElement;
// make sure the link is hidden.
downloadLink.style.display = "none";
// add the link to the DOM
document.body.appendChild(downloadLink);
// click the new link
downloadLink.click();
}
function destroyClickedElement(event) {
// remove the link from the DOM
document.body.removeChild(event.target);
}
$("#download").click(function (e) {
e.preventDefault();
saveTextAsFile();
});
});
</script>
</head>
<body>
<input type="button" id="download" value="Download" />
<textarea id="content">In trying to keep this plugin as simple as possible, all four states are always assumed to be present. You should prepare your button image as a single image the width you want your button, and four times the height of the button. All four states should then live in that one image in the same order as the previous list from top to bottom.</textarea>
</body>
</html>

How to get element data to debugger script?

I'm trying to write my first chrome extension for a specific website, automatically downloading streams that are loaded by the site. I've come as far as downloading the stream using the chrome.debugger api, but now i want to auto name the stream using data on the website.
First, let me give you the relevant scripts:
manifest.json
---------------------------------------
{
"name": "Downloader script",
"description": "Downloads stuff",
"version": "0.1",
"permissions": [
"debugger",
"downloads"
],
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": "icon.png",
"default_title": "Downloader"
},
"manifest_version": 2
}
here is the background.js, it displays a window stating all the files that have been downloaded.
background.js
--------------------------
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.debugger.attach({tabId:tab.id}, version,
onAttach.bind(null, tab.id));
});
var version = "1.0";
function onAttach(tabId) {
if (chrome.runtime.lastError) {
alert(chrome.runtime.lastError.message);
return;
}
chrome.windows.create(
{url: "headers.html?" + tabId, type: "popup", width: 400, height: 600});
}
Here is the (very simple) headers.html that is called:
<html>
<head>
<style>
body {
font-family: monospace;
word-wrap: break-word;
}
#container {
white-space: pre;
}
.request {
border-top: 1px solid black;
margin-bottom: 10px;
}
</style>
<script src="headers.js"></script>
</head>
<body>
<div id="container"></div>
</body>
</html>
Now here comes the hard part, the headers.js
var tabId = parseInt(window.location.search.substring(1));
window.addEventListener("load", function() {
chrome.debugger.sendCommand({tabId:tabId}, "Network.enable");
chrome.debugger.onEvent.addListener(onEvent);
});
window.addEventListener("unload", function() {
chrome.debugger.detach({tabId:tabId});
});
var requests = {};
function onEvent(debuggeeId, message, params) {
if (tabId != debuggeeId.tabId)
return;
if (message == "Network.requestWillBeSent") {
var requestDiv = requests[params.requestId];
if (!requestDiv && params.redirectResponse) {
var requestDiv = document.createElement("div");
requestDiv.className = "request";
requests[params.requestId] = requestDiv;
downloadSong(params.requestId, params.redirectResponse);
}
document.getElementById("container").appendChild(requestDiv);
}
}
function downloadSong(requestId, response) {
var requestDiv = requests[requestId];
var newSong = document.createElement("div");
newSong.textContent = "New song is being downloaded...";
var songLink = document.createElement('a');
var songLinkText = document.createTextNode("Direct link to song");
songLink.appendChild(songLinkText);
songLink.title = "Right click -> save as...";
songLink.href = response.headers.location;
if (songLink.href.indexOf("//audio") > -1) {
requestDiv.appendChild(newSong);
requestDiv.appendChild(songLink);
chrome.downloads.download({url: response.headers.location},function(id){});
}
}
Now the website i'm taking the streams from has the following structure:
<div id="trackInfo" class="unselectable">
<div class="contents" style="display: block;">
<div class="info">
<div class="trackData">
<div>This is the song title i want to grab</div>
<div><i class="byText">by</i> Artist name i want to grab
</div>
<div><i class="onText">on</i> <a class="albumTitle" address="true" href="http://www.albumlink" style="">Album title i want to grab</a></div>
</div>
</div>
</div>
</div>
My question is: how can I grab the element data from the page (the DOM i believe it is called) and use it with the chrome.downloads api? Since the headers.js file is (i think) not in contact with the webpage itself, just the network part.
PS: My first time posting here, constructive criticism on my post would also be welcome.
I have a feeling you're hunting squirrel with a howitzer here, employing "debugger" to intercept network requests and inject DOM into a page.
For intercepting network requests, there's a dedicated API for that, chrome.webRequest.
You can attach a listener to chrome.webRequest.onBeforeRequest, and you will have the same kind of information as you are using in your chrome.debugger.onEvent listener.
For injecting your DIV and reading data from DOM, you (only) need a content script.
The following code will assume assume you'll also use jQuery (no reason not to, it's easier to manipulate DOM with it). And it's only skeleton code.
You can assign a content script to always load on the music service, and listen to messages from background with chrome.runtime.onMessage:
chrome.runtime.onMessage.addListener(function(message) {
if(message.songUrl){
var newSong = $('<div>').text(New song is being downloaded...);
/* fill the <div> and append it to the page */
}
});
When your background detects music stream download, it has tabId from the request event, and can message the required tab:
chrome.webRequest.onBeforeRequest.addListener(
function(details){
if (details.url.indexOf("//audio") > -1) {
chrome.tabs.sendMessage(tabId, {songUrl: details.url});
}
return {}; // Do not modify the request
},
/* filter details */
);
As for extracting song details, jQuery is your friend. You need to come up with a selector which will pick the element you need:
// Inside content script
var songName = $("#trackInfo .songTitle").text();
// etc.

Chrome Extension Oddity with Tab API

I'm writing a Chrome Extension and am having some difficulty getting more than 5 tabs open. here is the source code.
manifset.json
{
"manifest_version": 2,
"name": "Test",
"description": "testing this",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"bookmarks"
]
}
popup.html
<!doctype html>
<html>
<head>
<title>Testing</title>
<style>
body {
min-width: 357px;
overflow-x: hidden;
}
</style>
<script src="popup.js"></script>
</head>
<body>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function () {
chrome.bookmarks.getTree(function (stuff){
traverseBookmarks(stuff[0].children[0].children);
});
});
function traverseBookmarks(bookmarkTreeNodes) {
for(var i=0;i<bookmarkTreeNodes.length;i++) {
var bookmark = document.createElement('a');
if(bookmarkTreeNodes[i].url){
bookmark.href = bookmarkTreeNodes[i].url;
bookmark.target = "_blank";
}
else{
(function(num) {
bookmark.addEventListener("click", function() {
addChildren(bookmarkTreeNodes[num].children, false );
})})(i);
document.body.appendChild(document.createElement("br"));
}
bookmark.innerHTML = bookmarkTreeNodes[i].title;
document.body.appendChild(bookmark);
document.body.appendChild(document.createElement("br"));
if(bookmarkTreeNodes[i].children) {
traverseBookmarks(bookmarkTreeNodes[i].children);
}
}
}
function addChildren(children) {
for(var i = 0; i < children.length; i++){
// will open each link in the current window
chrome.tabs.create({
url: children[i].url
});
}
}
The goal is to be able to click the folder(now just a link symbolizing the folder) and open all links inside that folder. Currently what happens in I click on the link to the folder and it opens the first 5. For the sake of getting some form of logging I added:
var bookmark = document.createElement('a');
bookmark.innerHTML = children[i].title;
document.body.appendChild(bookmark);
document.body.appendChild(document.createElement("br"));
to the addChildren() function. It prints out every child. The issue I'm running into is that when I click on the folder it only opens up the first 5 tabs then I'm guessing the focus leaves the popup so it doesn't finish. I can't really find anything else online to help
Any help is appreciated. Let me know if I need to clarify anything.
I suspect the problem is just as you said, when you create a new tab it is active by default so the popup loses focus and unloads. You could fix this in one of two ways. You can send the list of url's to a background page and have that open them all. This will work because the background page stays loaded when opening tabs.
Another, probably better, way to fix it would be to create the tabs with the active property set to false and then maybe switch to the first tab in the group after they are all open. Code:
function addChildren(children) {
for(var i = 0; i < children.length; i++){
chrome.tabs.create({
url: children[i].url,
active: false
});
}
// switch focus to first opened tab
chrome.tabs.query({url:children[0].url},function(tabs){
chrome.tabs.update(tabs[tabs.length-1].id,{active:true});
});
}

Categories