1. Introduction

After developing the Chrome plugin Github-star-Trend for the first time last week, I’ve been wondering what real-world problems can be solved with the plugin? Just as I was searching for ideas in my browser, I had an Epiphany from the numerous TAB tabs that opened up.

Gee, why not make a plugin to manage tabs? Every time I had too many tabs open at the same time, the squeezed titles kept me confused about which was which, making it difficult to look at. So, after a weekend afternoon, HERE’s what I found. :

2. Preparation

As usual, let’s start with a bit of preparation before we get into the subject. Silently open the official documentation of the Chrome plugin and go straight to Tabs. As you can see, it provides many options for us, as well as executeScript, which can be very powerful, but it’s not relevant to our needs this time…

2.1 the query

Since our requirement is to manage the TAB TAB, we must first get all the TAB information. The most relevant method is query:

Gets all tabs that have the specified properties, or all tabs if no properties are specified.

As officially described, this method returns the appropriate tabs based on the specified criteria; And when no attribute is specified, all tabs can be obtained. This is exactly what we need. Following the API instructions, I try to print the tabs object in callback:

chrome.tabs.query({}, tabs => console.log(tabs));
Copy the code
[{"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": "test title1"."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}]Copy the code

On closer inspection, the two tabs’ windowId differ. This is because I have two Chrome Windows open locally at the same time, and the tabs happen to be in two different Windows, so it works exactly as expected.

In addition, ID, index, highlighted, favIconUrl, title and other field information also plays a very important role in the text, the relevant definition can be viewed here.

In order to highlight the current TAB in the current window when designing the Chrome plugin UI, we had to find the TAB from the above data. As there is a TAB highlighted in each window, we cannot directly determine which TAB is the current window. But here’s what we can do:

chrome.tabs.query(
  {active: true.currentWindow: true},
  tabs => console.log(tabs[0]));Copy the code

According to the documentation, by specifying the active and currentWindow properties to true, we can get the current TAB for the currentWindow. Then match TAB windowId and highlighted to see which TAB is the real one in the tabs array.

2.2 highlight

Now that we have all the tabs information and determined which TAB is the current TAB of the current window, we can build a list based on that data. Then, when the user clicks on one of these items, the browser switches to the corresponding TAB. With this requirement in mind, scroll through the document again to find 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});
Copy the code

According to the API’s instructions, all it needs is the windowId and TAB indexes, which are available in each TAB entity. However, there is a catch: if you switch the current window to a TAB in another window, the Chrome window will still focus on the current window, even though the TAB in the other window is switched. So you need to use the following method to focus the other window:

chrome.windows.update(windowId, {focused: true});
Copy the code

2.3 remove

To enhance the utility of the plug-in, you can add the ability to remove the specified TAB in the Tabs list. After reviewing the documentation, we can confirm that Remove can fulfill our requirements.

Closes one or more tabs.

chrome.tabs.remove(tabId);
Copy the code

TabId is the ID attribute in TAB data, so the ability to close tabs works fine.

To start construction 3.

Unlike github-star-Trend, this one is more complex and involves more interactions. We introduced React, ANTD, and WebPack, but the overall development is relatively easy, probably due to the API proficiency provided by the Chrome plugin.

3.1 the 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"}}Copy the code
  1. Since the plug-in developed this time is related to Tabs, we need to use thepermissionsApplication in the fieldtabsPermissions.
  2. Since WebPack uses eval when packaged in dev mode, Chrome will report an error for security reasons, so it needs to be setcontent_security_policyMake it ignored (this is not necessary if packets are in PROD mode).
  3. This plug-in interaction is to click the button to pop up a floating layer, so it needs to be setbrowser_actionProperty, and itsdefault_popupThe fields are the pages we will develop next.

3.2 App. Js

This file is one of our core files and is mainly responsible for the maintenance of tabs data acquisition and processing.

According to the API documentation, getting the Tabs data is an asynchronous operation and is available only in its callback function. This also means that our application should be in a LOADING state at the beginning, which becomes OK after receiving data. In addition, considering abnormal conditions (such as no data or errors), we can define it as EXCEPTION state.

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 = (a)= > new Promise(resolve= > chrome.tabs.query({}, tabs => resolve(tabs)))

  getCurrentTab = (a)= > 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() {}
}
Copy the code

The idea is simple: get the Tabs data on didMount, but we use promise.all to control the asynchronous operation.

Since the operation of obtaining Tabs data is asynchronous, the operation may take different time according to different computers, different states and different number of tabs. Therefore, for better user experience, we can use antD’s Spin component as a placeholder at the beginning. Note that if you get the Tabs data very quickly, the Loading animation will flash by and not be very friendly. Therefore, we use a promise of 300ms with promise. all to ensure the Loading animation of at least 300ms.

The next step is to convert after getting the Tabs data.

Chrome provides an API that fetches data as a flat array, with tabs in different Windows mixed into the same array. We prefer to group by window, so that browsing and searching will be more intuitive for users, easier to operate and better user experience. So we need to do a conversion to tabsData:

convertTabsData(allTabs = [], currentTab = {}) {

  // Filter illegal data
  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); }// Convert obj to array
  const data = [];
  Object.keys(hash).forEach(key= > data.push({
    tabs: hash[key],
    windowId: Number(key),
    isCurWindow: Number(key) === currentTab.windowId
  }));

  // Sort the current window order up to ensure a better experience
  data.sort((winA, winB) = > {
    if(winA.isCurWindow) {
      return - 1;
    } else if(winB.isCurWindow) {
      return 1;
    } else {
      return 0; }});return data;
}
Copy the code

3.3 TabList. Js

Based on the design in app.js, we can start by laying down 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={' no data ~'}/> </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(); }}}Copy the code

The next step was to implement renderOK, and since there was no set design, we could do whatever we wanted. Here with the aid of ANTD a rough implementation of a version of the interaction (added TAB switching, search and delete operations), the specific code for space is not posted, interested can enter here to view.

Completion of 4.

This is the end of the plugin process. If you have a better idea or design, you can mention PR. Through this study, you are familiar with the operation of Tabs, and also have a further understanding of the process of making Chrome plug-in.

Reference 5.

  • Chrome extensions dev guide
  • Chrome extensions browserAction
  • Chrome extensions tabs

All the code for this article is hosted here, and you can star it or follow my Blog.