Skip to content

Extension

Chad Shurtz edited this page Aug 30, 2016 · 1 revision

Introduction

The Web Clipper is available across all major browsers (that is, Edge, Chrome, Firefox, Safari, and Internet Explorer). Each of the different platforms may have subtle differences between their extension architectures (e.g., the different WebExtensions implementations of Edge, Chrome, and Firefox), major differences (e.g., Safari extensions versus browsers implementing WebExtensions), and in the case of the bookmarklet, an entirely different architecture. The challenges of maintaining the Web Clipper across multiple architectures brings about a couple of key questions:

  • How does the Web Clipper work on each of the platforms?
  • How does the Web Clipper maximize code-sharing across all platforms?

We hope to address these with a high-level overview in the following sections.

Architectural Overview

For contextual reasons, we will start off by looking into the WebExtensions model of extensions, before looking into Safari extensions, and finally bookmarklets.

WebExtensions

Edge, Chrome, and Firefox all implement the WebExtensions API, a browser-agnostic system for developing extensions/add-ons across browsers. Due to the identical architectural nature of each of these platforms' extension envioronments, a large portion of the Web Clipper's code can be shared across these browsers. However, while these browsers all implement the same standard, they do so to different degrees of progress, so minor differences are necessary in each of the Web Clipper's implementations to ensure they work cleanly across the different platforms.

A visual overview of the Web Clipper's architecture on browsers supporting the WebExtensions API is shown below.

WebExtensions extension architecture

In a nutshell, extension code lives in what we refer to as the background - an environment in the browser that is seperate from the code that is run in each browser tab. Extension code often starts running as soon as the browser's process starts, and continues to run until the process exits. The Web Clipper is no different, and we use the background to perform initializations such as adding the extension button to the browser, and listeners for each of our context menu items.

The are currently two primary ways the Web Clipper directly interacts with the user:

  • The user initiates the interaction, such as clicking on the extension button as the result of wanting to clip the page that they are currently on.
  • The extension initiates the interaction, such as deciding to show the user the latest changelogs as part of an automatic update.

In both of these cases, the code that lives in the extension creates a worker object responsible for performing background-side operations for that specific interaction instance such as Logging.

After performing the necessary initializations, the worker object injects code into the current tab (the html of the tab's page, to be precise). This code, which we refer to as inject code, is responsible for tasks such as grabbing the DOM of the page to be clipped, and creating the iframe element that the Web Clipper UI will live in. You can check out the inject code used by the Web Clipper at clipperInject.ts.

As mentioned, the inject code then creates an iframe containing the page (and interface) the extension uses to interact with the user. In the case where the user initiates the interaction with the Web Clipper to clip a page, this page is clipper.html. In the case where the extension initiates the interaction with the user, this page is pageNav.html.

Safari Extensions

While Safari's extensions API is different to that of WebExtensions, it offers a very similar architecture in the sense that it shares the same background/tab model. A key distinction between the two APIs is that Safari does not allow scripts to be dynamically injected into the tab's page at-will. However, developers can specify scripts to be injected automatically in every page. The injected code can then listen for a message from the extension, telling it to initialize the interaction with the user when it needs to do so. This is done in safariInject.ts and safariPageNavInject.ts.

Safari extension architecture

Bookmarklets

Things get a little more tricky in the absence of a background environment or extension infrastructure. Without the ability for the bookmarklet to update itself, we designed the bookmarklet to be responsible for querying our servers for the latest Web Clipper resources. As such, there is currently no easy way for you to test the bookmarklet on your own.

On successfully retrieving the resources from our servers, the inject code is executed directly in the page, which then creates and initializes the iframe element in the same manner that the other versions of the Web Clipper do. The bookmarklet, however, still depends on the extension code (i.e., the worker and background code) to perform duties such as logging. To solve this, the extension code is executed by clipper.tsx inside the iframe element itself as a bookmarklet-only operation.

Bookmarklet extension architecture

Another primary distinction between the bookmarklet and the extensions is that a single instance of the aforementioned background code lives in every tab where the Web Clipper is executed. This is in contrast to the extension architecture, where a single instance of the background code lives in the entire browser (i.e., across all the window's tabs).

Finally, as the bookmarklet has no way of initiating an interaction with the user on its own means, it currently has no means of performing tasks that rely on page-navigations, such as showing the latest changelogs.

Code-Sharing

In light of all the differences between the various platforms, the Web Clipper was written with the aim of maximize code-sharing between them. Since a bulk of the differences lie in each of the browser's extension architectures, most of the differences in the Web Clipper's code lives in the background (e.g., see: extensionBase.ts and extensionWorkerBase.ts).

While (just about) the entirety of the inject and iframe code is used across the browsers, certain features need to be turned off as they are not supported by the browser the Web Clipper might be running on. For example, the bookmarklet does not support the capturing of the browser's viewport as an image, so Region mode has to be disabled for that version of the Web Clipper. To accomplish this, each inject code is passed a set of options that is subsequently passed to the scripts that live in the Web Clipper's iframe element. The aforementioned example can be observed in chromeInject.ts, where enableRegionClipping is set to true, while the opposite is observed in bookmarkletInject.ts.

Finally, as the bookmarklet has no knowledge of a background environment, clipper.tsx executes some bookmarklet-specific code to initialize the extension and extension worker objects.

Home

Preface

Architecture

Clone this wiki locally