exteranto / framework

The Exteranto browser extension framework core packages.
https://exteranto.github.io
MIT License
5 stars 4 forks source link

Generic API-Centric with Browser-specific cases #146

Open haydnba opened 4 years ago

haydnba commented 4 years ago

Relates #143 & #144

Following some discussion on these issues, I propose the following approach to resolve both:

We maintain a 'browser-centric' approach at-the or up-to-the API provider level. Therefore, at application boot, the program still established the specific 'current browser' context and binds this data on the container before the API providers are registered.

For each wrapped browser API (e.g. Tabs, Cookies etc.), we always provide the two generic namespaced concrete classes, browser and chrome under the names 'WebExtension' and 'Chromium' respectively. For each API, we follow the same pattern of binding to container based on the 'current browser' param:

this.container.bind<ApiName>(ChromiumApiName).to(ApiName).for(Browser.CHROME)
this.container.bind<ApiName>(ChromiumApiName).to(ApiName).for(Browser.EDGE)
this.container.bind<ApiName>(WebExtensionApiName).to(ApiName).for(Browser.FIREFOX)
this.container.bind<ApiName>(WebExtensionApiName).to(ApiName).for(Browser.SAFARI)

Here, both Chrome and Edge implement generic Chromium API while both Firefox and Safari implement generic WebExtension API - Repetition is handled at the provider level.

As we have become more aware, while many browsers use the same API namespace (Chrome, Opera, Edge -> Chromium; Firefox, Safari -> WebExtension) they all do so in more or less peculiar ways, with some methods or classes not implemented or implemented differently etc. Therefore, in order to allow for this divergence, while adhering as much as possible to a generic API-centric approach, where there is a special case, we can implement that browser-specific special case and provide it as follows:

this.container.bind<ApiName>(ChromiumApiName).to(ApiName).for(Browser.CHROME)
this.container.bind<ApiName>(OperaApiName).to(ApiName).for(Browser.OPERA)
this.container.bind<ApiName>(WebExtensionApiName).to(ApiName).for(Browser.FIREFOX)
this.container.bind<ApiName>(SafariApiName).to(ApiName).for(Browser.SAFARI)

Here, both Opera and Safari required their own specific wrappers on the given API, since they implemented them differently from the generic case. The folder structure for this API would look as follows:

ApiName/
  chromium/
    ...
  webextension/
    ...
  opera/
    ...
  safari/
    ...
  ApiName.ts
  ApiNameProvider.ts
  events.ts
  index.ts

This is a flexible approach that can accomodate the often significant divergences in API class/method implementation at the browser vendor level while maintaining a generic API-centric approach...

haydnba commented 4 years ago

P.S. See here https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs for details on divergences between browser API implementations, which are still significant

kouks commented 4 years ago

I like this, it's even simpler than what we discussed - doesn't require any changes in the IOC logic.

userhooke commented 4 years ago

This will give us the best of both worlds (Browser-specific vs API). Great! 🚀

kouks commented 4 years ago

Yes you retain the flexibility to create specific implementations without duplication where it's not needed.

haydnba commented 4 years ago

OK great 🎉

Well I suggest the first thing is to get currentBrowser Util working properly and then we start the updates to APIs

I will create a ticket for Util