This article appeared in Issue 9, 10, and 11 of Programmer magazine in 2017. The following version has been further revised.

about

  • Making: IHeader
  • My blog: Louis Blog
  • SF column: Louis Front end In-depth class
  • Customize the HTTP request response header field

The passage contains 15K words and will take 15 minutes to read.

takeaway

Search is the soul of a programmer. In order to improve the efficiency of search and query information faster, I tried to search four websites at the same time, which are Baidu, Google, Wikipedia and Bing. One possible approach is to embed four iframes in the web page, splicing the Search URLS of the previous four Search engines through JS and loading them in the iframe in turn. There’s nothing wrong with the idea. However, even such a simple function, can not be implemented. Since Google adds an X-frame-options field to the HTML response header to prevent the page from being framed (this setting is often used to prevent Click Cheats), I couldn’t add Google Search to the iframe. So, would I give up Google?

Nginx reverse proxy Google

Obviously not, since the problem is x-frame-options, I will remove it. Nginx is a good choice for request or response header field customization, and its third-party ngX_Headers_more module is particularly good at this. Since nginx cannot dynamically load third-party modules, I dynamically compiled Nginx to add the ngX_Headers_more module. At this point, the first step is complete. The following is a partial configuration of NGINx.

location / {
  more_clear_headers 'X-Frame-Options';
}Copy the code

To make www.google.com accessible, I need to use another domain name like louis.google.com. Via nginx, ask louis.google.com to forward to www.google.com and remove the X-frame-options field from the response header. So the nginx configuration looks like this:

server {
  listen 80;
  server_name louis.google.com;
  location / {
    proxy_pass https://www.google.com/;
    more_clear_headers 'X-Frame-Options'; }}Copy the code

Is there anything wrong with the above configuration? Not to mention HTTP directly to HTTPS, even if we can forward, actually due to Google’s security policy restrictions, we can not access the Google home page!

Ngx_http_google_filter_module). Nginx is configured as follows:

server { listen 80; server_name louis.google.com; Resolver 192.168.1.1.# Needs to be set to the gateway of the current route
    location / {
        google on;
        google_robots_allow on;
        more_clear_headers 'X-Frame-Options'; }}Copy the code

Above, by implementing a reverse proxy for The Google web site, the proxy removes the X-frame-options field from the response header field. This is the end of the Nginx solution.

One obvious disadvantage of the Nginx solution is that the configuration of the resolver gateway IP192.168.1.1 changes with the router. Home and business are two different gateways (let alone the starbucks office), so it is often necessary to manually modify the gateway and restart nginx.

IHeader origin

This is a bit of a problem with the Nginx solution. It happens that headers can be customized for Chrome Extension. To solve this problem, I tried to develop a Chrome Extension. (I’ve downloaded and tried countless Chrome Extensions since I started using Chrome. Every time I see an excellent Extension, I feel excited for a long time, and I always feel that I met you very late. Extension has conquered millions of developers with its customization power and amazing operation mechanism, and I am no exception. However, no matter how much learning and imitation, the ultimate purpose is to use, so it is imperative to develop a customized request Extension. Because of Chrome’s natural connection to web pages, removing the response header field with Chrome Extension is simpler and more efficient than other solutions.

Want to know, Chrome Extension provides apis in Chrome. WebRequest. OnHeadersReceived. It adds the ability to listen to the response header and synchronously changes the response header field. Removing x-frame-options seems to be a small case.

So I’m going to create a new project and I’m going to call it IHeader. The directory structure is as follows:

The directory structure

Where _locales is the internationalization configuration, IHeader currently supports both Chinese and English languages.

Res is the resource directory, index.html is the home page of Extension, and options. HTML is the options page.

Manifest.json is the declaration configuration for Extension (the master entry), where you configure the name, version number, icon, shortcut keys, resource path, and permissions for extension.

Manifest.json is posted as follows:

{" name ":" IHeader ", / / extension name "version" : "1.1.0, / / extension version number" ICONS ": {// Upload the chrome WebStore to 32px, 64px, 128px square icon "128": "res/images/lightning_green128.png", "32": "res/images/lightning_green.png", "64": "res/images/lightning_green64.png" }, "page_action": {// A type of extension, indicating that this is the page level extension "default_title": "IHeader", // Default name "default_icon": "Res /images/lightning_default. PNG ", // default icon "default_popup": "res/index.html", "background": {// if the extension runs in the background "persistent": true, // if the extension runs in the background "persistent": true, // if the extension runs in the background "persistent": true, // if the extension is not active, the browser may close "scripts": ["res/js/message.js", "res/js/background.js"] // specify the scripts to run. Chrome actually enables an anonymous HTML reference to these JS scripts. }, "commands": {// specify the shortcut "toggLE_status ": {/ / the name of the shortcut command "suggested_key" : {/ / quick command hotkey "default" : "Alt + H", "Windows" : "Alt + H", "MAC" : "Alt + H", "jump" : "Alt+H", "Linux ": "Alt+H"}, "description": "Toggle IHeader"}}, "content_scripts": [// With each page loading the content script, through which you can access the page's DOM {"all_frames": false, // don't load "matches" in the frame: ["\u003Call_urls>"], // match all urls "js": ["res/js/message.js", "res/js/content.js"] // Path of the content script}], "default_locale": "en", // Default language "description": "__MSG_description__", // Expand descriptor "Manifest_version ": Minimum_chrome_version (minimum_chrome_version) : // Minimum_chrome_version (minimum_chrome_version) : Runtime API "options_page": "res/options. HTML ", // option page path "permissions": [ "tabs" , "webRequest", "webRequestBlocking", "http://*/*", "https://*/*", "contextMenus", "Notifications "] // Extend required permissions}Copy the code

Chrome Extension profile

Before we start development, let’s brush up on the basics.

The differences between plugins, extensions, and apps are clear:

  • A Plugin is a component that extends kernel functionality by calling the Webkit kernel NPAPI. It works at the kernel level and can theoretically be developed in any language that generates native binaries, such as C/C++, Java, etc. Plug-ins focus on accessing the browser base, have more permissions, and can call system apis, so plug-ins generally cannot cross systems. Adobe’s recently abandoned Flash, Xunlei for downloading resources, and E-banking for online payments, for example, all offer Chrome plugins to enable and run in specific scenarios, enabling rich functionality.
  • Extension is a component that extends the browser functions by calling the Chrome API provided by Chrome. It is a developable Extension technology provided by Chrome, which is completely based on Chrome browser and realizes functions with the help of HTML, CSS, JS and other Web technologies. For example, this year’s micro channel small program, it is a micro channel to provide an extension technology. Compared to plug-ins, extensions have limited permissions and apis and are unaware of the underlying system, making them good cross-platform features. Note that both plugins and extensions only work when Chrome is up.
  • Applications are also used to expand the features of Chrome. It differs from an extension in that it has a standalone user interface and can be called when Chrome is not up, just like a standalone App.

When we talk about Chrome plug-ins, we tend to mean one of these three things. In order to avoid misunderstandings, this article will keep the concepts strictly separate and avoid the ambiguity of using plug-ins.

How to Install extensions

To develop an extension, you must first install it. Starting with Chrome 21, Chrome has added restrictions on installing extensions. By default, you can only install extensions and applications from the Chrome Web Store. This means that users can generally only install extensions and apps in the Chrome Web Store.

If you drag a CRX installation file to any normal Web page in Chrome, the following prompt will appear.

Extended features, applications, and theme backgrounds may harm your computer

Click the Continue button, and the following warning pops up in the upper-left corner of your browser.

Unable to add applications, extensions, and scripts from this site

If you happen to find a great Chrome extension on Github that’s not in the Chrome Web Store. Is there no way to install it? Of course not. Chrome has three other ways to load extensions.

  • For the extensions source directory, click the Load unzipped extensions button on the Chrome :// Extensions/page to install it directly.

  • If it is a CRX installation file, drag it directly to the Chrome :// Extensions/page to install it. The installation process is as follows:

    1) Drag-and-drop installation

Drag and drop to install

2) Click Add Extender

Add extensions

3) The added extension is shown below.

Drag and drop after installation

  • Add parameters when starting Chrome--enable-easy-off-store-extension-installTo open a simple extension installation mode, and you can then drag the CRX file to the browser page as before.

Speaking of installation, some people naturally ask, after installing an extension, how to view the source code of that extension? Mac users please remember this directory ~ / Library/Application Support/Google/Chrome/Default/Extensions/no) (the expansion of the Windows directory.

Extension packaging and updates

In addition, the package extension button in the middle is used to package a locally developed extension into a CRX package. The first package also generates a secret key file (such as iheaders. Pem), as shown below.

Package extensions

Extensions are packaged

The extensions are packaged and can be sent to others to install, or published to the Chrome Web Store ($5 developer registration).

The Update extension now button on the right is used to update the extension.

The basic components of an extension

Usually a Chrome extension contains the following resources or directories:

  • Manifest.json entry configuration file (1, in the root directory)
  • Js file (at least 1, in the root or child directory)
  • 1 square icon for 32px, 64px, 128px (in root or subdirectory)
  • _locales directory for internationalization support (optional at the root)
  • Popup.html popup page (optional in root or subdirectory)
  • HTML background running page, mainly used to introduce multiple background running JS (optional root or sub-directory)
  • Options.html options page for setting extensions (optional at root or child level)

In order to facilitate management, I prefer to unify HTML, JS, CSS, ICON and other resources into the same directory.

Extended classification

In terms of usage scenarios, Chrome extensions fall into the following three categories:

1) The Browser Action, the Browser extension, can be set via the manifest.json browser_action property, as shown below.

"browser_action": { "default_title": "Qrcode", "default_icon": "images/icon.png", "default_popup": "Index.html" // Optional},Copy the code

The Browser Action extension for URL generated QR codes is shown below:

Browser Action demonstration

This type of extension features: global extension, icon occupies the upper right corner of the browser toolbar for a long time, available on every page.

2) Page Action, a page-level extension, can be set via the page_action property in manifest.json, as shown below.

"page_action": { "default_title": "IHeader", "default_icon": "res/images/lightning_default.png", "default_popup": "Res /index.html" // Optional},Copy the code

This is the IHeader extension to the Page Action that will be covered in this article. It is specified to be visible to all pages, and its icon status is toggle as shown below.

Page Action demonstration

This type of extension features: different pages can have different status and different icon, icon is visible in the specified page, visible in the upper right corner of the browser toolbar.

As you can see above, the Browser Action is very similar to the Page Action in functionality, and the internal properties of each Action are the same in configuration. The Browser Action not only configures the Page that pops up when a click occurs, but also binds to the click event, as shown below.

// The following event bindings are normally run in background.js
// Browser Action
chrome.browserAction.onClicked.addListener(function(tab) {
  console.log(tab.id, tab.url);
  chrome.tabs.executeScript(tab.id, {file: 'content.js'});
});
// Page Action
chrome.pageAction.onClicked.addListener(function(tab) {
  console.log(tab.id, tab.url);
});Copy the code

If anything, the difference between the two is that the former does not need to maintain icon states, while the latter needs to manage different icon states for each enabled page.

3) Omnibox, the Omnibox toolbar, can be set by the Omnibox property in manifest.json, as shown below.

"Omnibox ": {"keyword":" MDN -";Copy the code

The above is the MDN site quick query Omnibox extension, run as follows:

The Omnibox demo

Obviously, you can customize the various inputs in the URL bar, and the OmniBox is a huge part of what makes Chrome’s URL bar so powerful.

This type of extension features: run in the URL bar, no pop-up interface, user input, the extension can display suggestions or automatically complete some work.

These three categories determine how the extension runs in the browser. In addition, each extension can optionally carry the following pages or scripts.

  • The Background Page can be set by using the Background property in manifest.json, which is subdivided into script or Page to represent the script and Page respectively, as shown below.

    "Background ": {"persistent": true, // default is false, specifying true will continue to run "scripts" in the background: ["res/js/background.js"] // specify the js to run in the background // "page": ["res/background. HTML "] // specify the HTML to be run in the background, the HTML needs to introduce several JS, there is no user interface, the actual equivalent of introducing multiple JS scripts},Copy the code

    Background Page is important in extensions because it uses all of Chrome.* apis. It allows popup.js and content.js to communicate with each other on the fly and call apis that they otherwise couldn’t.

    According to whether the persistent value is true, Background Pages can be divided into two categories: ① Persistent Background Pages, ② Event Pages. The former runs continuously and is accessible at any time; The latter is only accessible when the event is triggered.

    The features of this page are as follows: It runs in the background of the browser and has no user interface. The background page can be used for message communication between pages and background monitoring. Once the browser starts, the background page will run automatically.

  • The Content Script can be set using the content_scripts property in manifest.json, as shown below.

    "Content_scripts ": [{"all_frames": true, // Default to false, specifying true means that frame will also load content script "matches": ["\u003Call_urls>"], // matches all urls, meaning that any page will load "js": ["res/js/content.js"], // specifies the content script "run_at" to run: "Document_end" // execute after page loading}],Copy the code

    In addition to configuration, content scripts can also be loaded dynamically through JS.

    // Load the js file dynamically
    chrome.tabs.executeScript(tabId, {file: 'res/js/content.js'});
    // Load js statements dynamically
    chrome.tabs.executeScript(tabId, {code: 'alert("Hello Extension!" ) '});Copy the code

    The script features: Each page is loaded with a content script, which can be specified as document_start, IDEL, or end (when the page DOM starts loading, when it is idle, and when it finishes loading). Content scripts are the only scripts that have access to the PAGE’S DOM, allowing them to manipulate the PAGE’s DOM nodes, thereby affecting visual presentation; For security reasons, the content scripts are designed to exist in two different sandboxes from the rest of the page’s JS and therefore cannot access each other’s global variables.

  • Option Html, the setup page, can be set via the options_page property in manifest.json, as shown below.

    "options_page": "res/options.html",Copy the code

    Features of this page: Click the [Options] button on the right menu of the extension icon to enter the Settings page, which is generally used for the option Settings of the extension.

  • Override Html, which replaces the blank page of the new TAB, can be set via the Chrome_url_overrides property in manifest.json, as shown below.

    "chrome_url_overrides":{
      "newtab": "blank.html"
    },Copy the code

    This page is usually used to replace the default blank TAB content of the browser. It is usually used in the wallpaper application when a new TAB is opened. You can create your own blank page based on this page.

  • Devtool Page, the developer Page, can be set via the devTools_page property in manifest.json, as shown below.

    "devtools_page": "debug.html",Copy the code

    This page features: it starts as the console opens and can be used to output messages received by the extension to the current console.

For Chrome extensions, the Browser Action, Page Action, or Omnibox are mutually exclusive. Otherwise, there is no limit to what pages or scripts you need to add, and you can combine them as you wish.

How does the extension run debugging

As long as you know how to write JS, you’re ready to develop Chrome extensions. Debugging is inevitable when it comes to development, and debugging Chrome extensions is very simple. You can view all of your Chrome extensions in the // Extensions/page of the Chrome browser. In addition, the Load decompressed Extensions button under the page allows you to directly load your locally developed extensions, as shown below.

Load the decompressed extension

Note: You need to check developer mode for the load unzipped extension button to appear.

The successfully loaded extension is no different than a normally installed extension, and we are ready to debug using Web technologies.

  • Click aboveoptionsorBackground pageButton will open the options page and background page, respectively. The options page is a normal HTML page, press⌃ + ⌘ + JPress open console to debug. The background page has no interface and opens the console. Both pages can be debugged at breakpoints.
  • The Browser Action or Page Action extension usually displays an Icon in the upper right corner of Chrome. Right-click on the Icon and click on the right menuReview pop-up contentButton will open the pop-up page at the same time it opens its console. The console can also debug directly.

Chrome Extension API

Chrome continues to open up a number of apis to developers. Using these apis, we can listen to or proxy network requests, store data, manage tabs and cookies, bind shortcuts, set right-click menus, add notifications and alarms, get INFORMATION about CPU, battery, memory, monitor, and more (and many more are not listed). Please read the Chrome API documentation for details. Note that to use the corresponding API, you often need to apply for the corresponding permissions, such as the permissions applied for IHeader are shown below.

"permissions": [ "tabs" , "webRequest", "webRequestBlocking", "http://*/*", "https://*/*", "contextMenus", "notifications"]Copy the code

Above, IHeader applies for TAB, request, request breakpoint, HTTP site, HTTPS site, right click menu, and desktop notification.

WebRequest API

In the Chrome Extension API, only Chrome. WebRequest can modify the request. WebRequest has the ability to add event listeners to the different phases of the request, which can collect the details of the request and even modify or cancel the request.

Event listeners are fired only at certain stages, and they are fired in the order shown below. (Image from MDN)

The meanings of event listeners are described as follows.

  • OnBeforeRequest, triggered before the request is sent (the first event in the request, the request has not yet been created, at which point the request can be cancelled or redirected).
  • OnBeforeSendHeaders, the second event in the request, when the request header can be customized, Some cache-related request headers (Authorization, cache-control, Connection, Content-) Length, Host, if-modified-since, if-none-match, if-range, Partial Data, Pragma, Proxy- Authorization, proxy-connection, and Transfer-encoding) do not appear in the request information and can be overridden by adding keys with the same name, but cannot be deleted.
  • OnSendHeaders, triggered before the request hair is sent (the third event of the request, at which time only the request information can be viewed to confirm which request headers have been modified in onBeforeSendHeaders).
  • OnHeadersReceived, triggered after the response header is received (the fourth event of the request. At this time, the response header can be customized, and only the non-cache related fields can be modified or deleted or added. Since the response header allows multiple fields with the same name to exist at the same time, the modified cache related fields cannot be overwritten).
  • OnResponseStarted, which is triggered after the transmission of the response content has started (the fifth event of the request, at which point only the response information can be viewed to confirm which response headers have been changed in onHeadersReceived).
  • OnCompleted, fired when response acceptance has completed (the sixth event of the request, at which point only the response information can be viewed).
  • OnBeforeRedirect, onHeadersReceived events are triggered before the request is redirected (only response headers can be viewed at this time).
  • OnAuthRequired, onHeadersReceived events are triggered when a 401 or 407 status code is received (at which point the request can be cancelled, credentials can be provided synchronously, or credentials can be provided asynchronously).

Above, any event listener that can modify a request can specify that its extraInfoSpec argument array contains a blocking string (meaning it can block the request and modify it), and vice versa.

Also note that Chrome has very specific rules about the presentation of request and response headers, meaning that only fields that have been sent or just received are displayed in the console. Therefore, the edited request field can be displayed in the network bar of the console. The edited response field does not belong to the field that was just received, so the edit will not be visible from the console, as if it had not been modified, in fact, the edit is still valid.

Event listeners have different meanings, but the syntax is the same. Let’s take a closer look at onHeadersReceived as an example.

How do I bind header listeners

Remember our goal? Want to remove the X-frame-options field from the HTML response header of the Google web site. Look at the following code:

// Listen callback
var callback = function(details) {
  var headers = details.responseHeaders;
  for (var i = 0; i < headers.length; ++i) {
    // Remove the x-frame-options field
    if (headers[i].name === 'X-Frame-Options') {
      headers.splice(i, 1);
      break; }}// Return the modified headers list
  return { responseHeaders: headers };
};
// What to listen for
var filter = {
  urls: ["<all_urls>"]};// Additional information specification is optional
var extraInfoSpec = ["blocking"."responseHeaders"];
/* Listen for response headers to receive events */
chrome.webRequest.onHeadersReceived.addListener(callback, filter, extraInfoSpec);Copy the code

Chrome. WebRequest. OnHeadersReceived. AddListener said adding a receiving the response header listening in. The key parameters or properties in the above code are described below.

  • By default, the callback passes a parameter (Details), which is the details of the request.
  • Filter, Object type, limits the filter that the event callback can trigger. Filter has four properties you can specify: ①urls (containing an array of specified urls), ②types (type of request), ③tabId (tabId), and ④windowId (windowId).
  • ExtraInfoSpec, the array type, refers to the list of additional options. For headersReceived events, including “blocking” means that the request is required to be synchronized, so the response header can be modified accordingly. Including “responseHeaders” means that the event callback’s default parameter details will contain the responseHeaders field, which points to the list of responseHeaders.

Now that there’s a way to add listeners, there’s also a way to remove them.

chrome.webRequest.onHeadersReceived.removeListener(listener);Copy the code

In addition, to avoid repeated listens, you can also determine whether the listens already exist.

var bool = chrome.webRequest.onHeadersReceived.hasListener(listener);Copy the code

In order to better clarify the logical relationship of the above attributes, methods or parameters, please see the following brain map:

HeadersReceived event

Extended state management

Status management of listeners

Knowing how to bind listeners is just the first step. Listeners need to be bound at the right time and unbound at the right time. In order not to affect Chrome’s access speed, we only create new listeners on the required TAB, so listeners need to rely on the Filter to distinguish between different tabids. Considering that users may only need to listen for a certain number of request types, the differences in types are inevitable. And because different pages may be loaded at different times on a Tab, it is also necessary for a listener to work properly on different pages (so you do not need to specify urls in the listener filter).

A few words may not be enough to describe listener state management in its original form, but see the following figure for further understanding.

Page listener

Each of the five event callbacks (①, ②, ③, ④, ⑤) will be triggered by a request. Each event callback corresponds to a listener. These listeners are divided into two categories (as indicated by the color).

  • ②③⑤ The main function of the listener is to record and listen to the Request header and response header of each Request on the page, as well as the response time of the Request.
  • ①④ The main function of the listener is to update. It is used to add, delete, or modify the Request header and response header of the specified Request.

If the IHeader extension is enabled for the specified TAB in Chrome, the ②③⑤ listener logs information about subsequent requests of the specified type for the current TAB. If the user updates the Request header or response header of the Request in a TAB with the IHeader extension enabled, the ① or ④ listener is enabled. Instead of worrying about an infinite number of listeners being turned on, I have a collection mechanism in place where all listeners for a single TAB are released when the TAB is closed or the IHeader extension is deactivated.

First, encapsulate the listener code for easy administration.

/* Independent listener */
var Listener = (function(){
  var webRequest = chrome.webRequest;

  function Listener(type, filter, extraInfoSpec, callback){
    this.type = type; // Event name
    this.filter = filter; / / filter
    this.extraInfoSpec = extraInfoSpec; // Additional parameters
    this.callback = callback; // Event callback
    this.init();
  }
  Listener.prototype.init = function(){
    webRequest[this.type].addListener( // Add a listener
      this.callback,
      this.filter,
      this.extraInfoSpec
    );
    return this;
  };
  Listener.prototype.remove = function(){
    webRequest[this.type].removeListener(this.callback); // Remove the listener
    return this;
  };
  Listener.prototype.reload = function(){ // Restart listeners (used to restart all enabled listeners after the options page updates the request type)
    this.remove().init();
    return this;
  };
  returnListener; }) ();Copy the code

The listener controller manages all the listeners on the TAB based on the dimension of the TAB. The code is as follows.

/* Listener controller */
var ListenerControler = (function(){
  var allListeners = {}; /* List of all listener controllers */
  function ListenerControler(tabId){
    if(allListeners[tabId]){ /* Return the existing instance */
      return allListeners[tabId];
    }
    if(! (this instanceof ListenerControler)){ /* Force a constructor call to */
      return new ListenerControler(tabId);
    }

    /* Initialize the variable */
    var _this = this;
    var filter = getFilter(tabId); // Get the filter Settings that are currently being monitored
    /* Capture requestHeaders */
    var l1 = new Listener('onSendHeaders', filter, ['requestHeaders'].function(details){
      _this.saveMesage('request', details); // Record the header field information of the request
    });
    /* Capture responseHeaders */
    var l2 = new Listener('onResponseStarted', filter, ['responseHeaders'].function(details){
      _this.saveMesage('response', details); // Record the header information of the response
    });
    /* Capture Completed Details */
    var l3 = new Listener('onCompleted', filter, ['responseHeaders'].function(details){
      _this.saveMesage('complete', details); // Record the time when the request was completed
    });

    allListeners[tabId] = this; // Record the current TAB controller
    this.tabId = tabId;
    this.listeners = {  // Record the enabled listeners
      'onSendHeaders': l1,
      'onResponseStarted': l2,
      'onCompleted': l3
    };
    this.messages = {}; // The set of requested information for the current TAB
    console.log('tabId=' + tabId + ' listener on');
  }
  ListenerControler.has = function(tabId){... }// Check whether the controller for the specified TAB is included
  ListenerControler.get = function(tabId){... }// Return the controller for the specified TAB
  ListenerControler.getAll = function(){... }// Get all the TAB controllers
  ListenerControler.remove = function(tabId){... }// Remove all listeners under the specified TAB
  ListenerControler.prototype.remove = function(){... }// Remove all listeners from the current controller
  ListenerControler.prototype.saveMesage = function(type, message){... }// Record the request information
  returnListenerControler; }) ();Copy the code

Through the uniform scheduling of the listener controller, multiple listeners in the TAB can work efficiently.

In fact, there is still a lot of work to be done, and the above code has not yet been shown. For example, if the user updates the Request header or response header on a tag that has enabled the IHeader extension, how does the beforeSendHeaders or headersReceived listener work? Read this in conjunction with the “How to bind a Header listener” node.

Page Action Icon status management

The status of the TAB controller needs to be visually represented, so the management of the Page Action icon is inevitable. In general, the default icon can be specified in manifest.json.

"Page_action ": {"default_icon": "res/images/lightning_default. PNG ", // default icon},Copy the code

The icon has the following three states (the latter two states can be switched between each other).

  • Default state, showing default icon.
  • Initial state, showing the icon after the extension is initialized.
  • Activation status, showing the icon after the extension activation.

Chrome provides the Chrome. PageAction API for Page Actions. Chrome. PageAction currently has the following methods.

  • Show, displays the Page Action under the specified TAB.
  • Hide, hides the Page Action under the specified TAB.
  • SetTitle, set the title of the Page Action (when the mouse moves over the Page Action, the title is displayed)
  • GetTitle, gets the title of the Page Action.
  • SetIcon sets the icon for the Page Action.
  • SetPopup, which sets the URL of the page that pops up when clicked.
  • GetPopup gets the URL of the page that pops up when clicked.

Above, the setTitle, setIcon, and show methods are commonly used. The show method has two functions: (1) display the icon, (2) update the icon. Therefore, you usually set the title and path of the icon first, and then call show to display (or update). Note that the Page Action does not respond to clicks until the show method is called, so you need to call the show method before the initialization is complete. A thousand words is better than the code, as follows.

/* Declare the three icon states */
var UNINIT = 0.// The extension is not initialized
    INITED = 1.// The extension was initialized, but not activated
    ACTIVE = 2; // The extension is activated
/* Handle the extended icon state */
var PageActionIcon = (function(){
  var pageAction = chrome.pageAction, icons = {}, tips = {};
  icons[INITED] = 'res/images/lightning_green.png'; // Set the icon path in different states (relative to the extended root)
  icons[ACTIVE] = 'res/images/lightning_red.png';

  tips[INITED] = Text('iconTips'); // Elsewhere, the Text is pointed to chrome.i18n.getMessage, which reads the Text for the corresponding field in the specified language in _locales
  tips[ACTIVE] = Text('iconHideTips');

  function PageActionIcon(tabId){ / / the constructor
    this.tabId  = tabId;
    this.status = UNINIT; // The default state is uninitialized
    pageAction.show(tabId); // Show the Page Action
  }
  PageActionIcon.prototype.init = function(){... }// Initialize icon
  PageActionIcon.prototype.active = function(){... }// Icon switch to the active state
  PageActionIcon.prototype.hide = function(){... }/ / hide icon
  PageActionIcon.prototype.setIcon = function(){ / / Settings icon
    pageAction.setIcon({ // Set the path of icon
      tabId : this.tabId,
      path  : icons[this.status]
    });
    pageAction.setTitle({ // Set the icon title
      tabId : this.tabId,
      title : tips[this.status]
    });
    return this;
  };
  PageActionIcon.prototype.restore = function(){// After the page is refreshed, the state before the icon will be lost, which needs to be manually restored
    this.setIcon();
    pageAction.show(this.tabId);
    return this;
  };
  returnPageActionIcon; }) ();Copy the code

Icon management preparation is ok, the rest is to use, as follows.

new PageActionIcon(this.tabId).init();Copy the code

Status management of tabs

For the IHeader extension, a TAB contains changes to both the listener state and icon state. Therefore, a TAB page controller needs to be abstracted to manage the two in a unified way, so as to be invoked externally. The code is as follows.

/* Handle TAB status */
var TabControler = (function(){
  var tabs = {}; // A list of all TAB controllers
  function TabControler(tabId, url){
    if(tabs[tabId]){ /* Return the existing instance */
      return tabs[tabId];
    }
    if(! (this instanceof TabControler)){ /* Force a constructor call to */
      return new TabControler(tabId);
    }
    /* Initialize properties */
    tabs[tabId] = this;
    this.tabId = tabId;
    this.url    = url;
    this.init();
  }
  TabControler.get = function(tabId){... }// Get the specified TAB controller
  TabControler.remove = function(tabId){
    if(tabs[tabId]){
      delete tabs[tabId]; // Remove the specified TAB controller
      ListenerControler.remove(tabId); // Remove the specified listener controller}}; TabControler.prototype.init =function(){... }// Initialize the TAB controller
  TabControler.prototype.switchActive = function(){ // Current TAB status switch
    var icon = this.icon;
    if(icon){
      var status = icon.status;
      var tabId = this.tabId;
      switch(status){
        case ACTIVE: // If it is active, restore the original state and remove the listener controller
          icon.init(); 
          ListenerControler.remove(tabId);
          Message.send(tabId, 'ListeningCancel'); // The notification content script to output the cancellation prompt in the console (more on message communication later)
          break;
        default: // If it is not active, activate it and add the listener controller
          icon.active();
          ListenerControler(tabId);
          Message.send(tabId, 'Listening'); // and notifies the content script to output the listening prompt in the console}}return this;
  };
  TabControler.prototype.restore = function(){... }// Restore the status of the TAB controller (for page refresh scenarios)
  TabControler.prototype.remove = function(){... }// Remove the TAB controller
  returnTabControler; }) ();Copy the code

The abstraction of the TAB controller helps encapsulate the internal operation details of the extension, facilitating the subsequent management of the extension in various scenarios.

Tabs closed or updated properly handled

When tabs are closed or updated, some data needs to be released or synchronized to avoid memory leaks and stable operation. The just-wrapped TAB controller can be used to do just that.

First, Tab closing requires the release of the controller and listener objects for the current Tab.

/* Listen for TAB closing events */
chrome.tabs.onRemoved.addListener(function(tabId, removeInfo){
  TabControler.remove(tabId); // Free the memory and remove the TAB controller and listener
});Copy the code

Second, every time the Tab performs a jump or refresh Action, the icon of the Page Action will return to the initial state and be non-clickable. In this case, the state before the icon should be restored.

/* Listen for TAB update events, including jump or refresh actions */
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo){
  if(changeInfo.status === 'loading') {// The page is in loading
    TabControler(tabId).restore(); // Restore the icon state}});Copy the code

When a page is skipped or refreshed, changeInfo will go through loading and complete (some pages will contain favIconUrl or title information), as shown below.

changeInfo

With state management getting better, it’s time for Message communication (did you notice the Message object in the above code? It is the object of message processing).

Message communication

Extend internal message communication

There are four ways to communicate messages between pages in the Chrome extension (the following interface omits the Chrome prefix).

type Message is sent The message received Support version
One-time message extension.sendRequest extension.onRequest V33 scrap (early plan)
One-time message extension.sendMessage extension.onMessage V20 + (not recommended)
One-time message runtime.sendMessage runtime.onMessage V26 + (now mainstream, recommended)
Connection for a long time runtime.connect runtime.onConnect v26+

All four of these options are currently available. The message sent by extension.sendRequest can only be received by extension.onRequest (deprecated and not recommended, Issue 9965005 can be read). OnMessage can be received with extension.onMessage or runtime.onMessage, but the Runtime API is triggered first. If multiple listeners exist at the same time, only the first response will trigger the sendResponse callback for the message and the other responses will be ignored, as described below.

If multiple pages are listening for onMessage events, only the first to call sendResponse() for a particular event will succeed in sending the response. All other responses to that event will be ignored.

Let’s first look at one-time message communication, whose basic laws are as follows.

One-time message communication diagram

Figure in a new way of news communication, namely chrome. The extension. GetBackgroundPage, through which can obtain background. The js script (the background) of the window object, thus call under the window of any global method. It is not technically message communication, but it is perfectly capable of message communication, and it appears in the diagram because it is the dominant way messages are communicated from popup.html to background.js. So, you might ask, why doesn’t content.js have the same API?

This is because they are used differently and have different permissions. The chrome. Extension object in popup. HTML or background.js is printed as follows:

Chrome. The extension object

The chrome.extension object in content.js is printed as follows:

Chrome. Extension object under content.js

As you can see, the former contains the full number of properties, while the latter retains only a few. Content. js and there is no chrome. The extension. GetBackgroundPage method, therefore the js can’t direct call background. The global methods in js.

Returning to message communication, take a look at a simple example of message sending and listening, as follows:

// Message flow: popover page, options page, or background.js --> content.js
// Since each TAB may load a content script, you need to specify TAB
chrome.tabs.query( / / query TAB
  { active: true.currentWindow: true }, // Get the active TAB page of the current window, i.e. the current TAB
  function(tabs) { // The list is an array of TAB objects
    chrome.tabs.sendMessage( // Send a message to TAB
      tabs[0].id, // Specify the ID of the TAB
      { message: 'Hello content.js' }, // The message content can be any object
      function(response) { // Callback when the response is received
        console.log(response); }); });/* Message flow: * 1. Popover page or options page --> background.js * 2. background.js --> Popover page or options page * 3. content.js --> Popover page, options page or background.js */
chrome.runtime.sendMessage({ message: 'runtime-message' }, function(response) {
  console.log(response);
});

// You can use the onMessage method of extension or Runtime to listen for messages
chrome.runtime.onMessage.addListener( // Add a message listener
  function(request, sender, sendResponse) { // The three parameters are ① the message content, ② the message sender, ③ the method of sending the response
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.message === 'Hello content.js'){
      sendResponse({ answer: 'goodbye' }); // Send the response content
    }
    // return true; // To call sendResponse asynchronously, return true explicitly});Copy the code
One-time messaging API

The API syntax involved above is as follows:

  • chrome.tabs.query(object queryInfo, function callback)To query the TAB that meets the condition. Callback is the callback of the query result, and the default parameter is a tabs list. QueryInfo is the description of the TAB page, including the following attributes.
attribute type supportive describe
active boolean TAB Enabled or not
audible boolean v45+ TAB Indicates whether to allow sound playback
autoDiscardable boolean v54+ TAB Indicates whether the TAB can be discarded
currentWindow boolean v19+ TAB is in the current window
discarded boolean v54+ Whether the TAB is discarded
highlighted boolean TAB is highlighted
index Number v18+ TAB number in the window
muted boolean v45+ TAB Mute Or not
lastFocusedWindow boolean v19+ TAB is in the last selected window
pinned boolean Is TAB fixed?
status String TAB status. The optional value isloadingorcomplete
title String The title of the page in TAB (need to apply for tabs permission)
url String or Array Link to the page in TAB
windowId Number Id of the window where TAB is located
windowType String The type of the window in which TAB is locatednormal,popup,panel,appordevtools

Note: A discarded TAB means that the TAB contents have been removed from memory, but the TAB is not closed.

  • Chrome.tabs. SendMessage (INTEGER tabId, any Request, Object Options, function responseCallback) sends a single message to content.js under the specified TAB. The options parameter is supported from V41. It specifies the frameId value for sending a message to the specified frame, and the responseCallback is the callback when the response is received.
  • Chrome. Runtime. SendMessage (string extensionId, any message, object options, function responseCallback), to extend the messages or specify other extensions. The options parameter, which is supported from v32, allows you to specify the includeTlsChannelId (Boolean), which is the value of the includeTlsChannelId (Boolean). To determine whether the TLS channel ID is passed to the onMessageExternal event listener callback, responseCallback is the callback when the response is received.
  • Chrome. Runtime. OnMessage. AddListener (function callback), add a single message communication listening in. Function (any message, MessageSender sender, function sendResponse) {… } This is a function where message is the content of the message, sender is the sender, sendResponse is used to reply the response to the message sender, and if the response needs to be sent asynchronously, Return true in the callback (this will keep the message channel open until the sendResponse method is called).

In summary, we use chrome. Runtime API can be perfect for message communication, for V25, or even v20 version, please refer to the following compatible code.

var callback = function(message, sender, sendResponse) {
  // Do something
});
var message = { message: 'hello' }; // message
if (chrome.extension.sendMessage) { // chrome20+
  var runtimeOrExtension = chrome.runtime && chrome.runtime.sendMessage ? 'runtime' : 'extension';
  chrome[runtimeOrExtension].onMessage.addListener(callback); // bind event
  chrome[runtimeOrExtension].sendMessage(message); // send message
} else { // chrome19-
  chrome.extension.onRequest.addListener(callback); // bind event
  chrome.extension.sendRequest(message); // send message
}Copy the code
Long term connection message communication

Presumably, you’ve become adept at one-off messaging. What about frequent communication? At this point, one-time message communication becomes a little more complicated. To meet this need for frequent communication, The Chrome browser provides the Chrome.Runtime.Connect API. Based on this, the two communicating parties can establish a long-term connection.

The basic rules of long-term connection are as follows:

One-time message communication diagram

Above, as with the one-time message communication above, long-term connections can be made between two of popup.html, background.js, and content.js (note: you need to specify a tabId whenever you actively make a connection to Content.js). The following is an example of establishing a long-term connection between popup.html and content.js 🌰.

// popup.html initiates a long-term connection
chrome.tabs.query(
  {active: true.currentWindow: true}, // Get the active TAB of the current window
  function(tabs) {
    // If you want to connect to background. Js, you should use the Chrome.runtime.connect API
    var port = chrome.tabs.connect( // Return the Port object
      tabs[0].id, / / specified tabId
      {name: 'call2content.js'} // Connection name
    );
    port.postMessage({ greeting: 'Hello' }); // Send the message
    port.onMessage.addListener(function(msg) { // Listen for messages
      if (msg.say == 'Hello, who\'s there? ') {
        port.postMessage({ say: 'Louis' });
      } else if (msg.say == "Oh, Louis, how\'s it going?") {
        port.postMessage({ say: 'It\'s going well, thanks. How about you? ' });
      } else if (msg.say == "Not good, can you lend me five bucks?") {
        port.postMessage({ say: 'What did you say? Inaudible? The signal was terrible' });
        port.disconnect(); // Disconnect the long-term connection}}); });// Content.js listens and responds to long connections
chrome.runtime.onConnect.addListener(function(port) { // Listen for long-term connections. By default, the Port object is passed
  console.assert(port.name == "call2content.js"); // Filter connection names
  console.group('Long-lived connection is established, sender:' + JSON.stringify(port.sender));
  port.onMessage.addListener(function(msg) {
    var word;
    if (msg.greeting == 'Hello') {
      word = 'Hello, who\'s there? ';
      port.postMessage({ say: word });
    } else if (msg.say == 'Louis') {
      word = 'Oh, Louis, how\'s it going? ';
      port.postMessage({ say: word });
    } else if (msg.say == 'It\'s going well, thanks. How about you? ') {
      word = 'Not good, can you lend me five bucks? ';
      port.postMessage({ say: word });
    } else if (msg.say == 'What did you say? Inaudible? The signal was terrible') {
      word = 'Don\'t hang up! ';
      port.postMessage({ say: word });
    }
    console.log(msg);
    console.log(word);
  });
  port.onDisconnect.addListener(function(port) { // Listen for long term connection disconnection events
    console.groupEnd();
    console.warn(port.name + ': The phone went dead');
  });
});Copy the code

The console output is as follows:

Extended long – term connection message output

The API syntax involved in establishing a long-term connection is as follows:

  • chrome.tabs.connect(integer tabId, object connectInfo)To establish a long-term connection with content.js. TabId is the tabId and connectInfo is the connection configuration information. You can specify two properties, name and frameId. The name property specifies the name of the connection, and the frameId property specifies the unique frame in the TAB to establish the connection.
  • chrome.runtime.connect(string extensionId, object connectInfo)To initiate a long-term connection. Where extensionId is the extensionId, connectInfo is the connection configuration information, currently you can specify two attributes, name and includeTlsChannelId. The name attribute specifies the name of the connection. The includeTlsChannelId attribute, supported from v32, indicates whether the TLS channel ID is passed to the onConnectExternal listener.
  • chrome.runtime.onConnect.addListener(function callback)To monitor the establishment of long-term connections. The callback is the event callback after the connection is established. By default, the callback is passed to the Port object through which two-way communication between pages can be carried out. The structure of the Port object is as follows:
attribute type describe
name String Name of connection
disconnect Function Disconnect immediately (re-invocation of a disconnected connection has no effect, no new messages will be received after the connection is disconnected)
onDisconnect Object Fired on disconnection (listeners can be added)
onMessage Object Fired when a message is received (listeners can be added)
postMessage Function Send a message
sender MessageSender The initiator of the connection (this property will only appear in connection listeners, that is, onConnect or onConnectExternal)

Message communication between extenders

Message communication between extensions is simpler than message communication within extensions. For one-time message communication, there are two apis involved:

  • Chrome. Runtime. SendMessage, talked about before, need to be specially designated the first parameter extensionId, other unchanged.
  • Chrome. Runtime. OnMessageExternal, listening to other extensions, usage and chrome. Runtime. OnMessage consistent.

For long-term connection message communication, there are two apis involved:

  • Chrome.runtime. connect, as described earlier, requires that the first parameter extensionId be specified, and the rest remain unchanged.
  • Chrome. Runtime. OnConnectExternal, listening to other extensions, usage and chrome. Runtime. OnConnect.

To send a message, refer to the following code:

var extensionId = "oknhphbdjjokdjbgnlaikjmfpnhnoend"; // The target extension ID
// Initiate a one-time message communication
chrome.runtime.sendMessage(extensionId, { message: 'hello' }, function(response) {
  console.log(response);
});
// Initiate long term connection message communication
var port = chrome.runtime.connect(extensionId, {name: 'web-page-messages'});
port.postMessage({ greeting: 'Hello' });
port.onMessage.addListener(function(msg) {
  // See the popup.html example code for the communication logic of "long-term connection message communication"
});Copy the code

Listen for messages by referring to the following code:

// Listen for one-time messages
chrome.runtime.onMessageExternal.addListener( function(request, sender, sendResponse) {
  console.group('simple request arrived');
  console.log(JSON.stringify(request));
  console.log(JSON.stringify(sender));
  sendResponse('bye');
});
// Listen for long-term connections
chrome.runtime.onConnect.addListener(function(port) {
  console.assert(port.name == "web-page-messages");
  console.group('Long-lived connection is established, sender:' + JSON.stringify(port.sender));
  port.onMessage.addListener(function(msg) {
    // The communication logic is shown in the "long connection message communication" content.js sample code
  });
  port.onDisconnect.addListener(function(port) {
    console.groupEnd();
    console.warn(port.name + ': The phone went dead');
  });
});Copy the code

The console output is as follows:

Message communication output between extensions

Message communication between Web pages and extensions

In addition to communication within and between extensions, Web Pages can also have message communication with extensions (one-way). This communication is very similar to communication between extensions and requires the following three steps to communicate.

First, manifest.json specifies the URL rules for the pages that can be received.

"externally_connectable": {
  "matches": ["https://developer.chrome.com/*"]
}Copy the code

Second, Web pages to send information, such as in developer.chrome.com/extensions/… The page console executes the message sending statement in the “Message Communication between Extenders” section above.

Finally, to extend the listening message, the code is the same as the message listening section in the “Message Communication between extenders” section above.

At this point, the extension’s messaging conversation is over. With that in mind, you can wrap your own message.js to simplify messaging. In effect, the read-mode extension wraps a message.js, on which the message communication in the IHeader extension is based.

Setting shortcuts

Generally involving state switching, shortcut keys can effectively improve the use experience. I’ve also added shortcuts to iheaders for this purpose.

There are two steps to setting up a shortcut for the extension.

  1. Add commands declarations to manifest.json (you can specify multiple commands).

    Suggested_key ": {// commandname "suggested_key": {"default": "Alt+H", "Windows ": "Alt + H", "MAC" : "Alt + H", "jump" : "Alt + H", "Linux" : "Alt + H}", "description" : "Toggle IHeader" / / the description of the command}},Copy the code
  2. Add a command listener to background.js.

    /* Listen shortcuts */
    chrome.commands.onCommand.addListener(function(command) {
      if (command == "toggle_status") { // Match the command name
        chrome.tabs.query({active: true.currentWindow: true}, function(tabs) { // Query the current active TAB
          var tab = tabs[0];
          tab && TabControler(tab.id, tab.url).switchActive(); // Switch the TAB controller status}); }});Copy the code

To switch the listening status of the IHeader extension, press Alt+H.

Note the differences between the Mac and Windows and Linux. The Mac has both the Ctrl and Command keys. In addition, if you set a shortcut that conflicts with Chrome’s default shortcut, the setting will fail silently, so please remember to bypass the following Chrome shortcuts (KeyCue is the application for viewing the shortcut, please ignore it).

Chrome shortcuts

Add right-click menu

In addition to shortcuts, you can add right-click menus for extensions, such as the one for IHeader.

Demonstration of IHeader right-click menu

There are three steps to adding a right-click menu to the extension.

  1. To apply for menu permissions, add the “contextMenus” permission in the manifest.json property on permissions.

    "permissions": ["contextMenus"]Copy the code
  2. The menu is created manually in background.js.

    chrome.contextMenus.removeAll(); // It is recommended to clear the menu before creating it
    chrome.contextMenus.create({ // Create a right-click menu
      title: 'Switch Header listening mode'.// Specify the menu name
      id: 'contextMenu-0'.// Specify the menu ID
      contexts: ['all'] // Visible everywhere
    });Copy the code

    Because chrome. ContextMenus. Create (object createProperties, function callback) default () method returns the id of the new menu, so it through callbacks (the second parameter callback) to inform whether to create success, The first parameter createProperties specifies the configuration information for the menu item.

  3. Bind the right click menu function.

    chrome.contextMenus.onClicked.addListener(function (menu, tab){ // Bind the click event
      TabControler(tab.id, tab.url).switchActive(); // Switch the extended state
    });Copy the code

Install or update

Chrome provides rich apis for extensions. For example, you can listen for extension installation or update events, do some initialization, or give friendly hints, as shown below.

/* Install prompt */
chrome.runtime.onInstalled.addListener(function(data){
  if(data.reason == 'install' || data.reason == 'update'){
    chrome.tabs.query({}, function(tabs){
      tabs.forEach(function(tab){
        TabControler(tab.id).restore(); // Restore the state of all tabs
      });
    });
    // Restart the global listener during initialization...
    // Dynamically load the Notification JS file
    setTimeout(function(){
      var partMessage = data.reason == 'install' ? 'Installation successful' : 'Update successful';
      chrome.tabs.query({active: true.currentWindow: true}, function(tabs) {
        var tab = tabs[0];
        if (!/chrome:\/\//.test(tab.url)){ // You can only inject content scripts on pages whose URL does not start with "Chrome:// URL"
          chrome.tabs.executeScript(tab.id, {file: 'res/js/notification.js'}, function(){
            chrome.tabs.executeScript(tab.id, {code: 'notification("IHeader'+ partMessage +'"'}, function(log){
              log[0] && console.log('[Notification]: Successful pop-up Notification ');
            });
          });
        } else {
          console.log('[Notification]: Cannot access a chrome:// URL'); }}); },1000); // The delay of 1s is to enable debugging to switch to another TAB in time to pop up the Notification.
    console.log('[expansion] :', data.reason); }});Copy the code

Above, chrome. Tabs. ExecuteScript (integer tabId, object details) interface, used for dynamic injection content scripts, and only in the url is not “chrome: / / url” into the beginning of the page. The tabId parameter specifies the id of the target TAB, the Details parameter specifies the path or statement of the content script, the file attribute specifies the script path, and the code attribute specifies the dynamic statement. If multiple scripts or statements are injected into the same TAB, the injected scripts or statements reside in the same sandbox, that is, global variables can be shared.

Notification.js is shown below.

function notification(message) {
  if(! ('Notification' in window)) { // Check whether the browser supports the Notification function
    console.log('This browser does not support desktop notification');
  } else if (Notification.permission === "granted") { // Determines whether to grant notification permission
    new Notification(message); // Create a notification
    return true;
  } else if(Notification.permission ! = ='denied') { // Apply for permission from the user for the first time
    Notification.requestPermission(function (permission) { // Apply for permission
      if (permission === "granted") { // After the user grants the permission, a notification is displayed
        new Notification(message); // Create a notification
        return true; }}); }}Copy the code

The final pop-up notification is as follows.

Notification

internationalization

Internationalization is a must in order for your extensions to be used globally. From the perspective of software engineering, internationalization is to store all the visible strings in the user interface of a product in a resource file, and then display the visual information of the corresponding language according to the different language environment of the user. Chrome has provided a standard internationalisation API, Chrome.i18n, since the V17 release. I18n means internationalization. Because there are 18 characters between I and n, it is referred to as i18n for short.

The Chrome extension reserves the _locales directory for multiple language versions of the resource file message.json. The directory structure is “_locales/locales_code/message.json”, as follows:

_locales
|-- en
    |-- message.json
|-- zh_CN
    |-- message.jsonCopy the code

Locales_code includes not only en (English) and zh_CN (simplified Chinese), but also many other languages. For details, see Choosing Locales to Support. Chrome automatically ignores unsupported locales.

The message.json resource file looks like this, where key is the keyword, the message property specifies its corresponding value, and the description property describes the key.

{
  "key": {
    "message": "the value for the key",
    "description": "the description for the key"
  },
  ...
}Copy the code

According to the i18N website documentation

Important: If an extension has a _locales directory, the manifest must define “default_locale”.

Once you have the _locales directory in the extension, you must specify “default_locale” in manifest.json, as shown below.

"default_locale": "en",Copy the code

How do I reference an internationalized string

  • To reference a string named “key” in the manifest.json or CSS file, look like this:

    __MSG_key__Copy the code
  • If you need to reference a string corresponding to a key in the extension’s JS, use the Chrome.i18n.getMessage (String messageName, any substitutions) API. MessageName refers to the key of information. Substitutions array is used to store the value of the character to be substituted in the string. This parameter is optional and can contain a maximum of nine substitution values. Use the following:

    chrome.i18n.getMessage("key");Copy the code

    Chrome. I18n. getMessage returns an empty string “” if the key value is not obtained; If messageName is not a string or substitutions array length greater than 9, then this method will return undefined.

    So, how do you add a string with a placeholder to message.json? Here’s a test using the message.json code in the IHeader:

    "IconTips" : {" message ":" in the Header to monitor mode $$$b $", "placeholders" : {" a ": {" content" : "$1"}, "b" : {" content ": "$2"}}},Copy the code

    So, for a placeholder, $key$is a string that needs to be injected. Key is the name of the injection point, and it needs to specify a value for a placeholder in a placeholder configuration. As mentioned above, the contents of injection point A are specified as $1, that is, the value of the first replacement is injected at a, the contents of injection point B are specified as $2, that is, the value of the second replacement is injected at B, and so on.

    In fact, there are two ways to do this.

    // Replace the injection point a with "apple", or pass in an array or string to replace only one occupancy point
    chrome.i18n.getMessage('iconTips'.'apple'); 
    chrome.i18n.getMessage('iconTips'['apple']);
    
    // If the injection point a is "apple" and the substitution point B is "oranges", then the substitutions type should only be array
    chrome.i18n.getMessage('iconTips'['apple'.'oranges']);Copy the code

The actual effect is shown as follows:

Inject content through a placeholder

The above reference process is shown below (image from MDN) :

Internationalized string reference diagram

Predefined message

Above, it is not enough to provide these apis, the internationalization system also provides some predefined messages, which are as follows.

Message Name Description
@@extension_id Extension ID, which can be used to concatenate links, even if there are no internationalized extensions. Note that it cannot be used in manifest.json files.
@@ui_locale Current language that can be used to concatenate localized links.
@@bidi_dir Text direction of the current language, includingltr,rtlFrom left to right and from right to left.
@@bidi_reversed_dir If @ @ bidi_dir value isltr, its value isrtl, or forltr
@@bidi_start_edge If @ @ bidi_dir value isrtl, its value isleft, or forright
@@bidi_end_edge If @ @ bidi_dir value isltr, its value isright, or forleft

Predefined messages are available in the Chrome extension’s JavaScript and CSS, as shown below.

var extensionId = chrome.i18n.getMessage('@@extension_id');
location.href = 'chrome-extension://' + extensionId + '/res/options.html';Copy the code
body {
  direction: __MSG_@@bidi_dir__;
  background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}
div {
  padding-__MSG_@@bidi_start_edge__: 5px;
  padding-__MSG_@@bidi_end_edge__: 10px;
}Copy the code

Other Internationalization apis

In addition to Chrome.i18n.getMessage, there are three other apis.

  • GetAcceptLanguages gets a list of languages accepted by the browser.

    chrome.i18n.getAcceptLanguages(function(languageArray){
        console.log(languageArray);
    });
    / / since IHeader only support Chinese and English, so the output [" useful - CN ", "useful", "en", "useful - TW"]Copy the code
  • GetUILanguage, get the language for the browser’s user interface (supported from Chrome V35).

    chrome.i18n.getUILanguage(); // "zh-CN"Copy the code
  • DetectLanguage: Uses CLD to detect the language corresponding to the text.

    chrome.i18n.detectLanguage('Nihao Chi chi how are you'.function(result){
      console.log(result);
    });Copy the code

    The output is as follows:

    chrome.i18n.detectLanguage

Chrome extension development experience

IHeader is by far the longest Chrome extension I’ve spent working on in my spare time. From May 8th this year to June 14th, the first version was completed, and then after nearly two months of July and August, the final V1.1.0 version was formed, which reached my original intention of development.

There are a lot of extension development tutorials on the Internet, and even API translation websites. As far as I know, there are at least these:

  • 360– Extend development documentation
  • JavaScript API – Baidu browser application development documentation
  • Chrome Extension and Application Development (First edition)
  • Chrome extension development geek

By looking at these resources, you can basically get started on Chrome extension development quickly.

Of course, the tutorial is not as good as the official document, the development process, the most sad is the Chrome developer website connection is unstable, often can not access (even if the ladder), so it is difficult to view the official website information, which affects the development progress, so this article intentionally introduced some Chrome API usage. In addition, there are two important points to note during the release of a developed extension:

  1. Chrome developer registration needs 5$, pro test PUDONG Development visa credit card can pay, not so complex online.
  2. The release of the extension, for easy user viewing, requires complete documentation. Since Chrome WebStore extensions are available to global users, documentation should be available in at least two languages: Chinese and English.

In short, The Chrome extension, no matter how magic and powerful, is ultimately through HTML, CSS, JS to achieve functionality, not out of the world of the Web. So, in theory, as long as you know how to write JS, you can fully develop Chrome extensions. Chrome has even written the first Demo for you. Download and install a random extension source from the Google Chrome website, the Sample Extensions-google Chrome website, and tweak it to get your own Extensions running.

Of course, a good extension should be helpful for work or life. Developing a powerful extension in your spare time is not a problem as long as you focus on the pain points and focus on the functionality.

summary

Now that we’re done with Chrome extensions, let’s take a look at what IHeader looks like. With the help of the IHeader extension, I removed the X-frame-options field from the www.google.com website response, finally solving the puzzle at the beginning of the article, as shown below.

Customize the response headers

Can poke the link after you installed IHeader louiszhai. Making. IO/res/search /… , try the IHeader.

Not only that, the IHeader can add, delete, or edit request and response headers at any given URL, and the global listener remains valid even after the browser restarts. It is suitable for HTTP caching research, HTTP interface field debugging, and even provides a temporary solution to cross-domain problems in interface debugging (I have done a lot of cross-domain interface debugging based on this). So iHeaders can make things easier whenever you’re doing things based on HTTP request and response headers. As for how to use it, here’s an iheader-guide. (it may not be updated in time on the Chrome webstore due to web reasons, so it’s recommended to install the IHeader source on Github.)

If you are interested in Chrome extensions, please come to Github to learn and share your experience in extension development.

In this article, the IHeader extension program is cited as a step-by-step explanation of the Development of Chrome extension, involving more content, inevitably some omission, welcome to criticize the corrections, thanks.


Copyright notice: the author and the source should be indicated for reprinting.

Louis

This paper links: louiszhai. Making. IO / 2017/11/14 /…

Related articles

  • JavaScript APIs – Google Chrome
  • Message Passing – Google Chrome
  • chromium – chrome.runtime.sendMessage not working as expected
  • Issue 9965005: Deprecate chrome.extension.sendRequest in favor of sendMessage, with a safer – Code Review
  • Chrome extension development series 13: Message delivery