1. Preface
After developing Chrome plug-in for the first time last weekgithub-star-trend
Since then, I have been wondering what practical problems can be solved by plug-ins? Just as I was searching the browser for inspiration, the numerous tab tabs that I opened gave me a flash of light.
Why not make a plug-in to manage tab? Every time too many tab tabs are opened at the same time, the squeezed title always makes me unable to distinguish which is which, which is very inconvenient to view. As a result, after a weekend afternoon’s ordeal, I made this thing available (gif may be a little big, please wait patiently …):
2. Preparations
According to convention, let’s get some preliminary knowledge before entering the topic formally. Open Chrome Plug-in in SilenceOfficial documents, straight to usTabs
. It can be seen that it provides us with many methods, and even there areexecuteScript
, this can be said that the authority is very large, but it has nothing to do with our needs this time. . .
2.1 query
Since our requirement is to manage tab, we must first obtain all tab information. Sweep it againMethods
, the most relevant is the methodquery
:
Gets all tabs that have the specified properties, or all tabs if no properties are specified.
As officially introduced, this method can return the corresponding tabs; according to the specified conditions; And when no attribute is specified, all tabs can be obtained. This just meets our needs. According to API instructions, I tried to print out the tabs object I got in callback:
chrome.tabs.query({}, tabs => console.log(tabs));
[
{
"active": true,
"audible": false,
"autoDiscardable": true,
"discarded": false,
"favIconUrl": "https://static.clewm.net/static/images/favicon.ico",
"height": 916,
"highlighted": true,
"id": 25,
"incognito": false,
"index": 0,
"mutedInfo": {"muted":false},
"pinned": true,
"selected": true,
"status": "complete",
"title": "草料文本二维码生成器",
"url": "https://cli.im/text?bb032d49e2b5fec215701da8be6326bb",
"width": 1629,
"windowId": 23
},
...
{
"active": true,
"audible": false,
"autoDiscardable": true,
"discarded": false,
"favIconUrl": "https://www.google.com/images/icons/product/chrome-32.png",
"height": 948,
"highlighted": true,
"id": 417,
"incognito": false,
"index": 0,
"mutedInfo": {"muted": false},
"pinned": false,
"selected": true,
"status": "complete",
"title": "chrome.tabs - Google Chrome",
"url": "https://developers.chrome.com/extensions/tabs#method-query",
"width": 1629,
"windowId": 812
}
]
If you look closely, it is not difficult to find that the two tab’swindowId
Different. This is because I opened two Chrome windows locally at the same time, and these two TABs are just in two different windows, so they are exactly in line with the expectation.
In additionid
,index
,highlighted
,favIconUrl
,title
Such field information also plays a very important role in the following text, and relevant definitions can be found inHereCheck.
When designing the Chrome plug-in UI, in order to highlight the current tab in the current window, we must find this tab from the above data. Since there is a tab in each windowhighlighted
, so we cannot directly determine which tab is in the current window. However, we can do this:
chrome.tabs.query(
{active: true, currentWindow: true},
tabs => console.log(tabs[0])
);
According to the document, by specifyingactive
AndcurrentWindow
If these two properties are true, we can get the current tab of the current window smoothly. And then according to tab’swindowId
Andhighlighted
By matching, we can locate which tab is the real current tab from the tabs array.
2.2 highlight
According to the above, we can already get all tabs information and determine which tab is the current tab of the current window, so we can build a list based on the data. The next thing to do is, when the user clicks on one of the items, the browser can switch to the corresponding tab tab. With this requirement, I went through the documents again and found it.highlight
:
Highlights the given tabs and focuses on the first of group. Will appear to do nothing if the specified tab is currently active.
chrome.tabs.highlight({windowId, tabs});
According to the instructions of the API, what it needs iswindowId
And tab’sindex
And this information is available in every tab entity. However, there is one pit to note: if the tab of the other window is switched when the current window is switched to the tab of the other window, the Chrome window is still focused on the current window. Therefore, the following methods are needed to focus the other window:
chrome.windows.update(windowId, {focused: true});
2.3 remove
In order to enhance the practicability of the plug-in, we can add the function of deleting the specified tab in the tabs list. After browsing the documents, it can be determined thatremove
We can meet our needs.
Closes one or more tabs.
chrome.tabs.remove(tabId);
TabId is theid
Property, there is no problem in implementing the function of closing tabs.
3. Commencement
Unlike plug-insgithub-star-trend
This time it is more complicated and involves more interactions. To this end, we introducereact
,antd
Andwebpack
However, the overall development is still relatively easy, and more likely still lies in the proficiency of API provided by Chrome plug-in.
3.1 manifest.json
{
"permissions": [
"tabs"
],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": {
"16": "./icons/logo_16.png",
"32": "./icons/logo_32.png",
"48": "./icons/logo_48.png"
},
"default_title": "Tab Killer",
"default_popup": "./popup.html"
}
}
- Since the plug-in developed this time is related to tabs, we need to use the
permissions
Application in fieldPermission.
- Since eval is used for webpack packaging in dev mode, Chrome browser will report an error due to security policy, so it needs to be set up
content_security_policy
Make it ignore (if it is a prod mode packet, it does not need to be set). - This plug-in interaction is to click the button to pop up a floating layer, so it needs to be set up
browser_action
Property, whichdefault_popup
The fields are exactly the pages we will develop next.
3.2 App.js
This document is one of our core documents and is mainly responsible for the acquisition and processing of tabs data and other maintenance work.
According to the API document, obtaining tabs data is an asynchronous operation, which we can only obtain in its callback function. This also means that our application should be in aLOADING
The state of, after get the data becomeOK
State, in addition to considering the abnormal situation (such as no data or error), we can define it asEXCEPTION
Status.
class App extends React.PureComponent {
state = {
tabsData: [],
status: STATUS.LOADING
}
componentDidMount() {
this.getTabsData();
}
getTabsData() {
Promise.all([
this.getAllTabs(),
this.getCurrentTab(),
Helper.waitFor(300),
]).then(([allTabs, currentTab]) => {
const tabsData = Helper.convertTabsData(allTabs, currentTab);
if(tabsData.length > 0) {
this.setState({tabsData, status: STATUS.OK});
} else {
this.setState({tabsData: [], status: STATUS.EXCEPTION});
}
}).catch(err => {
this.setState({tabsData: [], status: STATUS.EXCEPTION});
console.log('get tabs data failed, the error is:', err.message);
});
}
getAllTabs = () => new Promise(resolve => chrome.tabs.query({}, tabs => resolve(tabs)))
getCurrentTab = () => new Promise(resolve => chrome.tabs.query({active: true, currentWindow: true}, tabs => resolve(tabs[0])))
render() {
const {status, tabsData} = this.state;
return (
<div className="app-container">
<TabsList data={tabsData} status={status}/>
</div>
);
}
}
const Helper = {
waitFor(timeout) {
return new Promise(resolve => {
setTimeout(resolve, timeout);
});
},
convertTabsData() {}
}
The idea is very simple, is indidMount
When we get tabs data, but we use it here.Promise.all
To control asynchronous operation.
Because the operation of acquiring tabs data is asynchronous, the time consumption of the operation may be different for different computers, different states and different tab numbers, so in order to better user experience, we can use antd’s at the beginning.Spin component
To act as placeholders. It should be noted that if tabs data is acquired very quickly, the Loading animation will have a flash feeling and is not very friendly. So we use a 300ms promise to matchPromise.all
Use, can guarantee at least 300ms Loading animation.
The next step is to get the tabs dataconvert
Work.
The data obtained by the API provided by Chrome is a flat array, and the TABs in different windows are also mixed in the same array. We prefer to group by window, which makes browsing and searching more intuitive to users, easier to operate and better user experience. So we need to do a conversion of tabsData:
convertTabsData(allTabs = [], currentTab = {}) {
// 过滤非法数据
if(!(allTabs.length > 0 && currentTab.windowId !== undefined)) {
return [];
}
// 按windowId进行分组归类
const hash = Object.create(null);
for(const tab of allTabs) {
if(!hash[tab.windowId]) {
hash[tab.windowId] = [];
}
hash[tab.windowId].push(tab);
}
// 将obj转成array
const data = [];
Object.keys(hash).forEach(key => data.push({
tabs: hash[key],
windowId: Number(key),
isCurWindow: Number(key) === currentTab.windowId
}));
// 进行排序,将当前窗口的顺序往上提,保证更好的体验
data.sort((winA, winB) => {
if(winA.isCurWindow) {
return -1;
} else if(winB.isCurWindow) {
return 1;
} else {
return 0;
}
});
return data;
}
3.3 TabList.js
According toApp.js
In the design of, we can first put up the skeleton of the code:
export class TabsList extends React.PureComponent {
renderLoading() {
return (
<div className={'loading-container'}>
<Spin size="large"/>
</div>
);
}
renderOK() {
// TODO...
}
renderException() {
return (
<div className={'no-result-container'}>
<Empty description={'没有数据哎~'}/>
</div>
);
}
render() {
const {status} = this.props;
switch(status) {
case STATUS.LOADING:
return this.renderLoading();
case STATUS.OK:
return this.renderOK();
case STATUS.EXCEPTION:
default:
return this.renderException();
}
}
}
The next step isrenderOK
Because there is no fixed design draft, we can fully exert our imagination. Here with the help ofantd
Roughly implemented a version of interaction (adding tab switching, search and delete operations, etc.), the specific code is not posted considering the length, interested can enterHereCheck.
4. Closure
The whole plug-in production process has been finished here. If you have a better idea or design, you can mentionPROh ~ through this study, I am familiar with the operation of Tabs, and at the same time I have a further understanding of the production process of Chrome plug-in.
5. Reference
All code in this article is managed inHere, think it’s okstarFor a moment, you can alsoAttentionMy Blog.