Chrome Extension Oddity with Tab API - javascript

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});
});
}

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

Getting all the hyperlinks title in a webpage using a chrome extension

I'm working on an application that needs to get all the URL title (one which we see when we hover the mouse on the hyperlink) present in the current tab. I am trying to make a chrome extension for the same. Find relevant files below:
manifest.json
{
"manifest_version": 2,
"name": "Sentiment Analyzer",
"description": "This extension analyzes sentiments of tweets, facebook and instagram posts",
"version": "1.0",
"icons": {
"128": "icon128.png",
"48": "icon48.png",
"16": "icon16.png"
},
"browser_action": {
"default_icon": "icon16.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab"
]
}
popup.html
<!DOCTYPE html>
<html>
<head>
<title>Sentiment Analyzer</title>
</head>
<body>
<script type="text/javascript" src="popup.js"></script>
<button id="idanalyze"> Analyze </button>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function(){
document.getElementById('idanalyze').addEventListener('click', function(){
temp = "";
for(i = 0; i < document.links.length; i++)
{
temp += " "+document.links[i].title;
}
alert(temp);
}, false);
}, false);
When I tried the below code in console then it produced expected result i.e. gave title for each hyperlink but when I tried to get the same result using chrome extension it did not work. Any kind of help/suggestion will be appreciated.
temp = "";
for(i = 0; i < document.links.length; i++)
{
temp += " "+document.links[i].title;
}
console.log(temp)
Only content script files are able to manipulate the page DOM, you are trying to access the site DOM from you popup page, so it won't work.
To solve it:
1. create a file and add it to your manifest.
"content_scripts": [
{
"matches": [THE_URLS_YOU_WANT_TO_INJECT_FILE], //can be regex for any url
"js": [YOUR_FILE]
}
]
send message using from you popup file to the content script file using chrome.tab.sendMessage. the message will initialize the function that check the DOM.
listen to message using addEventListener. and run your function the manipulate the DOM.
this is a high level explenation, every thing written in the documnation.
Content script - https://developer.chrome.com/extensions/content_scripts
chrome tabs message - https://developer.chrome.com/extensions/tabs

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)

How to open a link in the first open new browser tab

I know that this might not be possible.
Lets say I have a list of say 50 hyperlinks. The default behavior is to open the links in a new tab when clicked. But I want to prevent the user from opening 50 tabs if he/she clicks on all 50.
So is there any way to create a hyperlink which when clicked,
Opens the first link in a new tab
Subsequent links are opened in that same tab(instead of a new tab)
If it helps, Chrome will be the browser that we would use for this.
Is there any HTML.JS, Chrome trick that we can use? Thanks.
Add this snippet to your code, and check.
var a = document.getElementsByTagName("a");
for (i=0;i<a.length;i++) {
if (a[i].target="_blank") {
a[i].target="_self"
}
}
This works because it changes the URL of one window if it is opened to the link that the user clicked.
<html>
<head>
<title>
Search Engines
</title>
<script>
opened = false ;
function openMyLink(linkToOpen) {
if (opened === false) {
openWindow = window.open(linkToOpen,"_blank") ;
opened = true ;
openWindow.addEventListener("beforeunload",function () {opened = false ;}) ;
}
else {
openWindow.location.href = linkToOpen ;
}
}
</script>
<style>
a {
cursor : pointer ;
color : blue ;
text-decoration : underline ;
}
</style>
</head>
<body>
<h1>
Search Engines
</h1>
<br><a onclick="openMyLink('http://www.google.com/') ;">Google</a>
<br><a onclick="openMyLink('http://www.bing.com/') ;">Bing</a>
<br><a onclick="openMyLink('http://www.yahoo.com/') ;">Yahoo</a>
</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.

Categories