Skip to content
This repository has been archived by the owner on Dec 8, 2021. It is now read-only.

Documentation of LaSuli code and its structure

triinuerik edited this page Mar 14, 2019 · 29 revisions

Introduction

In this section, the structure of LaSuli web extension will be documented and explained. LaSuli was developed according to the Mozilla Browser Extension standard. The complete documentation for Mozilla Browser Extensions can be found here.

Structure

A web extension is essentially a collection of files that are packaged for easy distribution and installation. The files and directories usually follow a certain structure, which components will be described below.

manifest.json

This is the only file that is mandatory for every extension. In this file, the basic metadata of the extension will be described.

In LaSuli's project, this file can be found in the extension directory. The following metadata is described in the manifest.json file of LaSuli's project:

  • Name
  • Description
  • Version
  • Manifest version
  • Permissions

The manifest.json file can also contain pointers to several other types of files. In the case of LaSuli:

  • Applications
  • Default settings for both browser and sidebar (title, icons, etc)
  • Background scripts

Background scripts

Web extensions often need to perform long-term operations, that should not depend on the particular web page or browser window. To help with that, we have background scripts. They are loaded as soon as the web extension is opened and will stay until the extension is disabled/uninstalled. It is also possible to load background pages by specifying a HTML-file. The script has to be specified in the manifest.json file (as mentioned above).

In LaSuli, the background scripts can be found in src/backgroundScripts directory. The specification in the manifest.json file is done in the following manner:

"background": {
    "scripts" : [
      "/dist/background.js"
    ]
  },

Another bonus of background scripts is the ability to use any of the WebExtension APIs in the script, provided you have specified the permissions in the manifest.json file.

In LaSuli, the APIs are accessed using the browser namespace. For example: tabs = browser.tabs;

Sidebar

A web extension can also include various user interface components, whose content is defined as a HTML-document. These can be for example:

  • Sidebars
  • Popups
  • Option pages

In the case of LaSuli, we have a sidebar. A sidebar is a pane on the left-hand side of the webpage. In LaSuli, this is where the user can see the results of their analysis. In the project, the HTML-document can be found in extension/page directory.

Similarly to background scripts, the sidebar has to be specified in the manifest.json file. It is done in the following manner:

"sidebar_action": {
    "default_panel": "/page/sidebar.html"
  },

All of the aforementioned user interface components are a type of Extension pages. It means that it is also possible to access the WebExtensions API, just like with background scripts. This is done also with the browser namespace.

Content scripts

Content scripts are scripts that are loaded into the web page and run within the context of that page (as opposed to background scripts which are part of the extension, or scripts which are part of the web site itself). Background scripts can't directly access the content of web pages. So if your extension needs to do that, you need content scripts. In the case of LaSuli, we need to highlight words on the web page, so that is why content scripts are useful to this project.

Content scripts are able to use a small subset of the WebExtension APIs and they can exchange messages with the background scripts, which will be more thoroughly described below.

In LaSuli project, we can find the content scripts in src/contentScripts directory.

There are 3 ways to load a content script into a web page:

  1. At install time, into pages that match URL patterns using the content_scripts key in your manifest.json
  2. At runtime, into pages that match URL patterns using the contentScripts API
  3. At runtime, into specific tabs using tabs.executeScript()

An example from LaSuli, where a background script uses the 3rd method to load a content script:

async () => {
	await tabs.executeScript(this.props.tabId, {
		file: '/dist/content.js'
	});
}

This function is asynchronous (notice the async/await keywords), which is described in more detail below.

Web accessible resources

Web accessible resources are images, HTML, CSS and JavaScript that is included in the extension. In the case of LaSuli, there are some buttons and icons that are displayed in the extension. These are located in the extension/button and extension/icons directories.

Some important principles

In this section, some more important principles of development in LaSuli will be described. Understanding these concepts and how they work will provide better understanding of this web extension and its mechanisms.

Asynchronous operations

As it was also mentioned before, the background and content scripts utilise the JavaScript/WebExtension APIs in their work. To do this, we use the browser namespace. Some of these APIs will return a Promise, which is an object that represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. In the example below, the functionality of a Promise will be demonstrated:

var promise1 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve('foo');
  }, 300);
});

promise1.then(function(value) {
  console.log(value);
  // expected output: "foo"
});

console.log(promise1);
// expected output: [object Promise]

As it is seen in the example, the Promise itself is just an object and calling on it will not return any value. But by applying the .then() function on it, we can access the value when the Promise will get fulfilled (or rejected). It is also possible to chain multiple Promises together, which is called chaining. It is very useful in cases when the later Promises depend on the completion of the first ones. It looks like this:

doSomething()
.then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

Sometimes these chains can get very long and complicated. This is where the async/await functionality becomes very useful. It helps to simplify and clean your code by helping to write asynchronous code in a way that looks synchronous. Here are the steps to follow for using async/await in React:

  1. Put the async keyword in front of your functions
  2. Use await in the function’s body
  3. Catch the errors

Let's look at some LaSuli code examples where this method is used.

browser.tabs.onActivated.addListener(async (activeInfo) => {
	try {
		let tab = await tabs.get(activeInfo.tabId);
		await updateHighlightNumber(tab.id, tab.url, false);
	} catch (e) {
		errorHandler(e, activeInfo.tabId);
	}
});

As we can see, indeed the code resembles synchronous code, it looks simple and clean. The Promise returned from the API (browser namespace) will be handled nicely.

NB! It has to be remembered that async/await is not supported by all browsers (ex. IE) and behave accordingly.

Messages

In order to communicate with the background scripts, the content scripts utilise a messaging system. There are two basic patterns for communicating between the background scripts and content scripts:

  1. You can send one-off messages, with an optional response
  2. You can set up a longer-lived connection between the two sides, and use that connection to exchange messages.

LaSuli uses the first option, so this one will be described below.

In content script In background script
Send a message browser.runtime.sendMessage() browser.tabs.sendMessage()
Receive a message browser.runtime.onMessage browser.runtime.onMessage

As it is seen, there are 3 different APIs to communicate, depending on the type of script and if it is needed to send or receive a message. The API for receiving a message stays the same for both content and background scripts, but it is important to pay attention to the differences in sending a message.

Below are some examples from LaSuli to illustrate this concept.

Let's say that we have a background script file that wishes to send a message to a content script. It will utilise the browser.tabs.sendMessage API to send a message. Notice the aim attribute of the message.

browser.tabs.sendMessage(this.props.tabId, {
	aim: 'highlight',
	labels,
	fragments
})

This message will be received by the content script using the browser.runtime.onMessage API.

browser.runtime.onMessage.addListener(messageHandler);

This will call the function messageHandler, whose aim is to decipher the message and act accordingly. The cases are recognised by the aim attribute (remember from before).

messageHandler = async (message) => {
	switch (message.aim) {
	case 'highlight':
		erase();
		highlight(message.fragments, message.labels);
		return true;

Conclusion

These main structural components presented make up the project tree of LaSuli. With this documentation, it should be more clear how LaSuli web extension is built up and give some insight into its workings.

Clone this wiki locally