How to move a tab after the last pinned tab? - google-chrome-extension

The extension is designed so that when you click a pinned tab, it moves it to the rightmost position out of all the pinned tabs. It works a few times before I keep getting the issue:
Error during tabs.move: Tabs cannot be edited right now (user may be
dragging a tab).
chromeHidden.handleResponse
When I use the debugger though, it works every single time.
Code:
chrome.tabs.onActivated.addListener(function(tab) {
chrome.windows.getAll({"populate":true}, function(windows) {
var tabs = [];
for (var i = 0; i < windows.length; i++) {
var win = windows[i];
if (win.id == tab.windowId) {
tabs = win.tabs;
for (var k = 0; k < tabs.length; k++) {
var tempTab = tabs[k];
if (tempTab.id == tab.tabId && tempTab.pinned == true) {
for (var j = k; tabs[j+1] && tabs[j+1].pinned; j++) {
chrome.tabs.move(tab.tabId, {"index":j+1});
}
break;
}
}
}
}
});
});

Your current code has some flaws:
Inefficiency: You select all windows and tabs, while only the pinned tabs in the current window are needed.
Incorrect use of the asynchronous chrome.tabs.move method: You're moving the single tab after the last pinned tab by repeatedly moving the tab to the right. As I said, chrome.tabs.move is asynchronous, so when you call chrome.tabs.move again, the previous "move request" might not have finished yet. Hence your error.
To get more efficient, use the chrome.tabs.query method with the relevant filters:
chrome.tabs.onActivated.addListener(function(activeInfo) {
var tabId = activeInfo.tabId;
chrome.tabs.query({
currentWindow: true,
pinned: true
}, function(tabs) {
// Only move the tab if at least one tab is pinned
if (tabs.length > 0) {
var lastTab = tabs[ tabs.length - 1 ];
var tabIndex = lastTab.index + 1;
for (var i=0; i<tabs.length; ++i) {
if (tabs[i].id == tabId) {
// Current tab is pinned, so decrement the tabIndex by one.
--tabIndex;
break;
}
}
chrome.tabs.move(tabId, {index: tabIndex});
}
}); // End of chrome.tabs.query
}); // End of chrome.tabs.onActivated.addListener
If you want to do something else after moving the tab, pass a callback to chrome.tabs.move:
chrome.tabs.move(tabId, {index: tabIndex}, function() {
console.log('Moved tab!');
});
Note: The tab won't move if you keep the mouse pressed. Also, always moving the tab when a tab is focused is a bit unfriendly in terms of user experience. You'd better use a browser action button to activate this feature. Yes, it's one additional click to move the tab, but at least the user will have a choice to not move tabs if they want to.
chrome.browserAction.onClicked.addListener(function(tab) {
var tabId = tab.id;
chrome.tabs.query({currentWindow: true, pinned: true}, ...);
});

Related

Setting badge in chrome extension works in dev mode but not in prod (chrome store)

I have problems with changing badge value on every content load (page change). Weird thing is that everyting works on dev mode and badge value is chaning but when I developed app to the store badge doesnt't change on every content load. Why?
Here's a little bit code from content.js
function hideComments(comments, users) {
let deletedComments = 0;
for (let i = 0; i < comments.length; i++)
for (let j = 0; j < users.length; j++)
//user is ALWAYS at [2] index in the array
if(comments[i].children[0].innerHTML.toLowerCase().split(',')[2].trim() === users[j]) {
comments[i].style.display = 'none';
deletedComments++;
}
//send number of deleted comments to the background
if(deletedComments !== 0)
return chrome.runtime.sendMessage({data: `${deletedComments}`});
chrome.runtime.sendMessage({data: ''});
}
if(blockedUsers === null)
localStorage.setItem('blockedUsers', '');
blockedUsers = localStorage.getItem('blockedUsers');
if(blockedUsers.length) {
//function expects an array as 2nd parameter
hideComments(commentsNodes, blockedUsers.split(','));
}
Like I said before. If I have.. lets say 2 users added, the badge value is changing to 5 if there are 5 comments exist or badge is set to empty string (to hide it) if there's none. It works perfectly on dev mode but not when app is added to the store.
background.js
chrome.runtime.onInstalled.addListener(function() {
chrome.runtime.onMessage.addListener(function (message, sender) {
chrome.browserAction.setBadgeBackgroundColor({ color: [238, 50, 78, 255] });
chrome.browserAction.setBadgeText({text: message.data});
});
});

Cant get chrome.tabs.executeScripts to work

I am trying to modify an existing chrome extention.
What i want is a chrome extention where i populate a popup window full of urls and then when i click OK each url opens up in a new tab 1 at a time, certain data on that tab is then copied somewhere, the tab closes and the next url in the list is called and so on untill the list is complete. the extention finally outputs all the copied data from all the tabs into a CSV file (which i can then manipulate into Excel.
I have looked everywhere but so far i have only got as far as to open the tabs up HOWEVER they all open at the same time. this i sas far as i have got.
I have tried using chrome.tabs.execute script and that seems to block the loop,
Any ideas?
EDIT - HERE IS THE FULL CODE
Many Thanks
function loadSites(e) {
var urlschemes = ['http', 'https', 'file', 'view-source'];
var urls = document.getElementById('urls')
.value.split('\n');
var lazyloading = document.getElementsByName('lazyloading')[0].checked;
for (var i = 0; i < urls.length; i++) {
theurl = urls[i].trim();
if (theurl != '') {
if (urlschemes.indexOf(theurl.split(':')[0]) == -1) {
theurl = 'http://' + theurl;
}
if (lazyloading && theurl.split(':')[0] != 'view-source' && theurl.split(':')[0] != 'file') {
chrome.tabs.create({
url: chrome.extension.getURL('lazyloading.html#') + theurl,
selected: false
});
} else {
chrome.tabs.create({
url: theurl,
selected: true,
activ
function(tab) {
chrome.tabs.executeScript(tab.id, { file: "contentscript.js" });
});
}
}
}
}

Multiple Tab Chrome Extension Issue

I've created a basic extension that searches Google if the URL/HTML content fulfill certain requirements. It works for the most part, but fails miserably when there are multiple instances of the extension. For example, if I load tab A and then tab B, but click on the page action for tab A, I will be directed to a search of tab B's content.
I don't know how to silo the script to each tab, so that clicking tab A's page action will always result in a search for tab A stuff. How can that be done? I'd appreciate your suggestions!
background.js
title = "";
luckySearchURL = "http://www.google.com/search?btnI=I%27m+Feeling+Lucky&ie=UTF-8&oe=UTF-8&q=";
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.title != "") {
title = request.title;
sendResponse({confirm: "WE GOT IT."});
}
});
chrome.tabs.onUpdated.addListener(function(tabId, change, tab) {
if (change.status === "complete" && title !== "") {
chrome.pageAction.show(tabId);
}
});
chrome.pageAction.onClicked.addListener(function(tab) {
chrome.tabs.create({url: luckySearchURL + title})
})
contentscript.js
function getSearchContent() {
url = document.URL;
if (url.indexOf("example.com/") > -1)
return "example";
}
if (window === top) {
content = getSearchContent();
if (content !== null) {
chrome.runtime.sendMessage({title: content}, function(response) {
console.log(response.confirm); })
};
}
You could do something like store the title with its associated tabId, that way when you click on the pageAction it uses the correct title. The changes would be just these:
background.js
title= [];
[...]
chrome.runtime.onMessage.addListener(function(request,sender,sendResponse){
if (request.title != "") {
title.push({tabId:sender.tab.id, title:request.title});
sendResponse({confirm: "WE GOT IT."});
}
});
[...]
chrome.pageAction.onClicked.addListener(function(tab) {
title.forEach(function(v,i,a){
if(v.tabId == tab.id){
chrome.tabs.create({url: luckySearchURL + v.title});
// Here I am going to remove it from the array because otherwise the
// array would grow without bounds, but it would be better to remove
// it when the tab is closed so that you can use the pageAction more
// than once.
a.splice(i,1);
}
});
});
You're facing this issue because of window === top. So your title variable gets its value from the last opened tab. So if B is opened after A, title gets its value from B. Try this: Detect Tab Id which called the script, fetch the url of that tab, which then becomes your title variable. As below:
chrome.pageAction.onClicked.addListener(function(tab) {
chrome.tabs.query({active:true},function(tabs){
//this function gets tabs details of the active tab, the tab that clicked the pageAction
var urltab = tabs[0].url;
//get the url of the tab that called this script - in your case, tab A or B.
chrome.tabs.create({url: urltab + title});
});
});

Preventing tab to cycle through address bar

I realize this is probably an accessibility issue that may best be left alone, but I'd like to figure out if it possible to prevent the tab from visiting the address bar in the tabbing cycle.
My application has another method of cycling through input areas, but many new users instinctively try to use the tab, and it doesn't work as expected.
Here's a generic jquery implementation where you don't have to find the max tab index. Note that this code will also work if you add or remove elements in your DOM.
$('body').on('keydown', function (e) {
var jqTarget = $(e.target);
if (e.keyCode == 9) {
var jqVisibleInputs = $(':input:visible');
var jqFirst = jqVisibleInputs.first();
var jqLast = jqVisibleInputs.last();
if (!e.shiftKey && jqTarget.is(jqLast)) {
e.preventDefault();
jqFirst.focus();
} else if (e.shiftKey && jqTarget.is(jqFirst)) {
e.preventDefault();
jqLast.focus();
}
}
});
However, you should note that the code above will work only with visible inputs. Some elements may become the document's activeElement even if they're not input so if it's your case, you should consider adding them to the $(':input:visible') selector.
I didn't add code to scroll to the focus element as this may not be the wanted behavior for everyone... if you need it, just add it after the call to focus()
You can control the tabbing order (and which elements should be able to get focus at all) with the global tabindex attribute.
However, you can't prevent users to tab into another context not under control of the page (e.g. the browser's address bar) with this attribute. (It might be possible in combination with JavaScript, though.)
For such a (evil!) use case, you'd have to look into keyboard traps.
WCAG 2.0 has the guideline: 2.1.2 No Keyboard Trap. In Understanding SC 2.1.2 you can find "Techniques and Failures" for this guideline:
F10: Failure of Success Criterion 2.1.2 and Conformance Requirement 5 due to combining multiple content formats in a way that traps users inside one format type
FLASH17: Providing keyboard access to a Flash object and avoiding a keyboard trap
G21: Ensuring that users are not trapped in content
So maybe you get some ideas by that how such a trap would be possible.
I used to add two tiny, invisible elements on tabindex 1 and on the last tabindex. Add a onFocus for these two: The element with tabindex 1 should focus the last real element, the one with the max tabindex should focus the first real element. Make sure that you focus the first real element on Dom:loaded.
You could use Javascript and capture the "keydown" event on the element with the highest "tabindex". If the user presses the "TAB" key (event.keyCode==9) without the "Shift" key (event.shiftKey == false) then simply set the focus on the element with the lowest tabindex.
You would then also need to do the same thing in reverse for the element with the lowest tabindex. Capture the "keydown" event for this element. If the user presses the "TAB" key (event.keyCode==9) WITH the "Shift" key (event.shiftKey == true) then set the focus on the element with the highest tabindex.
This would effectively prevent the address bar from ever being focused using the TAB key. I am using this technique in my current project.
Dont forget to cancel the keydown event if the proper key-combination is pressed! With JQuery it's "event.preventDefault()". In standard Javascript, I believe you simply "return false".
Here's a JQuery-laden snippet I'm using...
$('#dos-area [tabindex=' + maxTabIndex + ']').on('keydown', function (e) {
if (e.keyCode == 9 && e.shiftKey == false) {
e.preventDefault();
$('#dos-area [tabindex=1]').focus();
}
});
$('#dos-area [tabindex=1]').on('keydown', function (e) {
if (e.keyCode == 9 && e.shiftKey == true) {
e.preventDefault();
$('#dos-area [tabindex=' + maxTabIndex + ']').focus();
}
});
Also keep in mind that setting tabindex=0 has undesirable results on the order in which things are focused. I always remember (for my purposes) that tabindex is a 1-based index.
Hi i have an easy solution. just place an empty span on the end of the page. Give it an id and tabindex = 0, give this span an onfocus event, when triggered let your focus jump to the first element on your page you want to cycle trough. This way you won't lose focus on the document, because if you do your events don't work anymore.
I used m-albert solution and it works. But in my case I do not control the tabindex properties. My intention is set the focus on a toolbar at the top of the page (first control) when user leaves the last control on the page.
$(':input:visible').last().on('keydown', function (e) {
if (e.keyCode == 9 && e.shiftKey == false) {
e.preventDefault();
$('html, body').animate({
scrollTop: 0
}, 500);
$(':input:visible', context).first().focus();
}
});
Where context can be any Jquery object, selector, or even document, or you can omit it.
The scrolling animation, of course, is optional.
Not sure if this is still a issue, but I implemented my own solution that does not use jQuery, can be used in the case when all the elements have tabindex="0", and when the DOM is subject to change. I added an extra argument for a context if you want to limit the tabcycling to a specific element containing the tabindex elements.
Some brief notes on the arguments:
min must be less than or equal to max, and contextSelector is an optional string that if used should be a valid selector. If contextSelector is an invalid selector or a selector that doesn't match with any elements, then the document object is used as the context.
function PreventAddressBarTabCyle(min, max, contextSelector) {
if( isNaN(min) ) throw new Error('Invalid argument: first argument needs to be a number type.')
if( isNaN(max) ) throw new Error('Invalid argument: second argument needs to be a number type.')
if( max < min ) throw new Error('Invalid arguments: first argument needs to be less than or equal to the second argument.')
min = min |0;
max = max |0;
var isDocumentContext = typeof(contextSelector) != 'string' || contextSelector == '';
if( min == max ) {
var tabCycleListener = function(e) {
if( e.keyCode != 9 ) return;
var context = isDocumentContext ? document : document.querySelector(contextSelector);
if( !context && !('querySelectorAll' in context) ) {
context = document;
}
var tabindexElements = context.querySelectorAll('[tabindex]');
if( tabindexElements.length <= 0 ) return;
var targetIndex = -1;
for(var i = 0; i < tabindexElements.length; i++) {
if( e.target == tabindexElements[i] ) {
targetIndex = i;
break;
}
}
// Check if tabbing backward and reached first element
if( e.shiftKey == true && targetIndex == 0 ) {
e.preventDefault();
tabindexElements[tabindexElements.length-1].focus();
}
// Check if tabbing forward and reached last element
if( e.shiftKey == false && targetIndex == tabindexElements.length-1 ) {
e.preventDefault();
tabindexElements[0].focus();
}
};
} else {
var tabCycleListener = function(e) {
if( e.keyCode != 9 ) return;
var context = isDocumentContext ? document : document.querySelector(contextSelector);
if( !context && !('querySelectorAll' in context) ) {
context = document;
}
var tabindex = parseInt(e.target.getAttribute('tabindex'));
if( isNaN(tabindex) ) return;
// Check if tabbing backward and reached first element
if (e.shiftKey == true && tabindex == min) {
e.preventDefault();
context.querySelector('[tabindex="' + max + '"]').focus();
}
// Check if tabbing forward and reached last element
else if (e.shiftKey == false && tabindex == max) {
e.preventDefault();
context.querySelector('[tabindex="' + min + '"]').focus();
}
};
}
document.body.addEventListener('keydown', tabCycleListener, true);
}
More notes:
If min is equal to max, then tab cycling will occur naturally until the last element in the context is reached. If min is strictly less than max, then tabbing will cycle naturally until either the min or the max is reached. If tabbing backwards and the min is reached, or tabbing forward and the max is reached, then the tabbing will cycle to the min element or max element respectively.
Example usage:
// For all tabindex elements inside the document
PreventAddressBarTabCyle(0,0);
// Same as above
PreventAddressBarTabCyle(1,1);
// NOTE: if min == max, the tabindex value doesn't matter
// it matches all elements with the tabindex attribute
// For all tabindex elements between 1 and 15
PreventAddressBarTabCyle(1,15);
// For tabindex elements between 1 and 15 inside
// the first element that matches the selector: .some-form
PreventAddressBarTabCyle(1,15, '.some-form');
// For all tabindex elements inside the element
// that matches the selector: .some-form2
PreventAddressBarTabCyle(1,1, '.some-form2');
Salinan solution worked for me
Put this in the start of your html page:
<span tabindex="0" id="prevent-outside-tab"></span>
and this at the end of your html page.:
<span tabindex="0" onfocus="foucsFirstElement()"></span>
foucsFirstElement() :
foucsFirstElement() {
document.getElementById("prevent-outside-tab").focus();
},

When I close a window and then open a different one, the selected tab is recreated by Chrome

I am writing a Chrome extension that saves/restores your browsers window state - So, I save the state of a given window:
var properties = [ "top",
"left",
"width",
"height",
"incognito",
"focused",
"type"
];
var json = {};
var cache = chrome_window_object;
// copy only the keys we care about:
_.each(properties,function(key,value) {
json[key] = cache[key];
});
// then copy the URLs of the tabs, if they exist:
if(cache.tabs) {
json.url = [];
_.each(cache.tabs,function(tab) {
json.url.push(tab.url);
});
}
return json;
At some point in the future, I remove all windows:
closeAllWindows: function(done_callback) {
function got_all(windows) {
var index = 0;
// use a closure to only close one window at a time:
function close_next() {
if(windows.length <= index) return;
var window = windows[index++];
chrome.windows.remove(window,close_next);
}
// start closing windows:
close_next();
}
chrome.windows.getAll(got_all);
}
and then I restore the window using:
chrome.windows.create(json_from_before);
The window that is created has an extra tab in it, whatever was in the window that I just closed... I am completely floored, and I assume the problem is something that I am doing in the code that I haven't posted (it's a big extension). I've spent a few hours checking code line by line and making sure I'm not explicitly asking for this tab to be created. So - has anybody seen anything like this before?

Resources