summaryrefslogtreecommitdiff
path: root/firefox-sideloader/blog.md
blob: 13ab0ca7d068a8854f88b19a377aedb79c408ed3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# Firefox has deprecated "side-loading" of extensions

From [the announcement](https://blog.mozilla.org/addons/2020/03/10/support-for-extension-sideloading-has-ended/):

> Today marks the release of Firefox 74 and as we announced last fall,
> developers will no longer be able to install extensions without
> the user taking an action. This installation method was typically
> done through application installers, and is commonly referred to as
> “sideloading.”
>
> If you are the developer of an extension that installs itself via
> sideloading, please make sure that your users can install the
> extension from your own website or from addons.mozilla.org (AMO).

Of course, it is still possible to side-load. The browser itself is
installing the extension, and the browser isn't even closed source. It
can't hide what it's doing, just obfuscate and write annoying blog posts
about how taking away features is making users safer.

I spent some time yesterday figuring out how to side-load extensions
and writing code to do this for my own purposes. I got a script working
that gives an automated extension install into a new profile based on
copying out of some other profile where the extension had been installed
by firefox interactively.

What I'm doing is very coarse -- mostly just copying entire files, but
editing the paths within them to refer to the new extension directory
instead of the old one.

This works for me since I want to copy my entire list of extensions,
but you want to surgically install just one extension.  I didn't go
that far with my code but I think I can tell you how to start the
surgery based on what I found.  You just need to edit some JSON.  (BTW
[jq](https://github.com/stedolan/jq) is great.)

To get an extension running in a firefox profile, you need to edit the
following files in the profile dir:

```
./extension-settings.json
./extension-preferences.json
./extensions.json
./addonStartup.json.lz4
./prefs.js
```

* `<profile_dir>/extensions/`

  Put your `*.xpi` here.

* `extensions.json`

  Each extension needs a long entry in the `addons` array

* `extension-settings.json`

  Used to give extensions some special privileges, refers to extension by id
  (the `id` field in `extensions.json`)

* `extension-preferences.json`

  Used to give extensions privilege to run in private tab, refers to extensions
  by id

* `addonStartup.json.lz4`

  Contains one `addons` object within each of the four labels "app-builtin",
  "app-profile", "app-system-defaults", "app-system-share".
  
  I use https://github.com/jusw85/mozlz4 to decompress and compress this file.

* prefs.js

  Needed to set `extensions.webextensions.uuids` or to make an extension
  run as the homepage or new tab page without user interaction. I also
  use it to bypass confirmation/warning popups, mozilla upgrade notice,
  etc.

Here is an example of the JSON for each file for one extension. This is
generated by script and the values are edited (by jq) so that only one extension
is shown.

## addonStartup.json.lz4
```
{
  "app-profile": {
    "addons": {
      "dependencies": [],
      "enabled": true,
      "lastModifiedTime": 1592563110000,
      "loader": null,
      "path": "extension@tabliss.io.xpi",
      "rootURI": "jar:file:///home/d/.mozilla/firefox/fm0hmz3f.second_gen4/extensions/extension@tabliss.io.xpi!/",
      "runInSafeMode": false,
      "signedState": 2,
      "telemetryKey": "extension%40tabliss.io:2.0.3",
      "version": "2.0.3"
    }
  }
}
```
```
+ mozlz4 addonStartup.json.lz4
+ jq -c '.["app-profile"]["addons"]["extension@tabliss.io"] | keys'
["dependencies","enabled","lastModifiedTime","loader","path","rootURI","runInSafeMode","signedState","telemetryKey","version"]
```

## extension-preferences.json
```
{
  "extension@tabliss.io": {
    "permissions": [
      "internal:privateBrowsingAllowed"
    ],
    "origins": []
  }
}
```
```
+ jq -c '.["extension@tabliss.io"] | keys' extension-preferences.json
["origins","permissions"]
```

## extensions.json
```
{
  "addons": [
    {
      "id": "extension@tabliss.io",
      "syncGUID": "{c95f9e0d-6450-4383-87e4-2244586de7f5}",
      "version": "2.0.3",
      "type": "extension",
      "loader": null,
      "updateURL": null,
      "optionsURL": null,
      "optionsType": null,
      "optionsBrowserStyle": true,
      "aboutURL": null,
      "defaultLocale": {
        "name": "Tabliss",
        "description": "A beautiful New Tab page with many customisable backgrounds and widgets that does not require any permissions.",
        "creator": null,
        "developers": null,
        "translators": null,
        "contributors": null
      },
      "visible": true,
      "active": true,
      "userDisabled": false,
      "appDisabled": false,
      "embedderDisabled": false,
      "installDate": 1592563110000,
      "updateDate": 1592563110000,
      "applyBackgroundUpdates": 1,
      "path": "/home/d/.mozilla/firefox/fm0hmz3f.second_gen4/extensions/extension@tabliss.io.xpi",
      "skinnable": false,
      "sourceURI": "https://addons.mozilla.org/firefox/downloads/file/3549950/tabliss_new_tab-2.0.3-fx.xpi?src=search",
      "releaseNotesURI": null,
      "softDisabled": false,
      "foreignInstall": false,
      "strictCompatibility": true,
      "locales": [],
      "targetApplications": [
        {
          "id": "toolkit@mozilla.org",
          "minVersion": "54.0",
          "maxVersion": "*"
        }
      ],
      "targetPlatforms": [],
      "signedState": 2,
      "seen": true,
      "dependencies": [],
      "incognito": "spanning",
      "userPermissions": {
        "permissions": [
          "storage"
        ],
        "origins": []
      },
      "optionalPermissions": {
        "permissions": [],
        "origins": []
      },
      "icons": {
        "32": "icons/32.png",
        "48": "icons/48.png",
        "96": "icons/96.png",
        "128": "icons/128.png"
      },
      "iconURL": null,
      "blocklistState": 0,
      "blocklistURL": null,
      "startupData": null,
      "hidden": false,
      "installTelemetryInfo": {
        "source": "amo",
        "sourceURL": "https://addons.mozilla.org/en-US/firefox/addon/tabliss/?src=search",
        "method": "amWebAPI"
      },
      "recommendationState": {
        "validNotAfter": 1744746924000,
        "validNotBefore": 1586958924000,
        "states": [
          "recommended"
        ]
      },
      "rootURI": "jar:file:///home/d/.mozilla/firefox/fm0hmz3f.second_gen4/extensions/extension@tabliss.io.xpi!/",
      "location": "app-profile"
    }
  ]
}
```
```
+ jq -c '.addons[] | select(.id == "extension@tabliss.io") | keys' extensions.json
["aboutURL","active","appDisabled","applyBackgroundUpdates","blocklistState","blocklistURL","defaultLocale","dependencies","embedderDisabled","foreignInstall","hidden","iconURL","icons","id","incognito","installDate","installTelemetryInfo","loader","locales","location","optionalPermissions","optionsBrowserStyle","optionsType","optionsURL","path","recommendationState","releaseNotesURI","rootURI","seen","signedState","skinnable","softDisabled","sourceURI","startupData","strictCompatibility","syncGUID","targetApplications","targetPlatforms","type","updateDate","updateURL","userDisabled","userPermissions","version","visible"]
```

## extension-settings.json
```
{
  "url_overrides": {
    "newTabURL": {
      "precedenceList": {
        "id": "extension@tabliss.io",
        "installDate": 1592563110000,
        "value": "moz-extension://5457f46b-efee-42b2-bea9-19bcb10f7618/index.html",
        "enabled": true
      }
    }
  }
}
```
```
+ jq -c keys extension-settings.json
["commands","default_search","homepageNotification","newTabNotification","prefs","tabHideNotification","url_overrides","version"]
```
## prefs.js
```
user_pref("extensions.webextensions.uuids", "\{\"extension@tabliss.io\":\"5457f46b-efee-42b2-bea9-19bcb10f7618\"\}");
```

In total, I copy these prefs (...`.mstone` is assigned value "ignore" rather
than copied):

```
browser.newtab.extensionControlled
browser.newtab.privateAllowed
browser.reader.detectedFirstArticle
browser.rights.3.shown
browser.startup.homepage
browser.startup.homepage_override.buildID
browser.startup.homepage_override.mstone
browser.toolbarbuttons.introduced.pocket-button
datareporting.policy.dataSubmissionPolicyAcceptedVersion
datareporting.policy.dataSubmissionPolicyNotifiedTime
extensions.webextensions.uuids
```

It appears only the last value -- `extensions.webextensions.uuids` -- is
crucial. I know it's needed if you want the `browser.startup.homepage` to refer
to such a uuid (i.e., to use an extension as the homepage). If you don't include
it, mozilla will regenerate the uuids and any copied reference to the old uuid
(hiding anywhere) will fail.

This only matters if you are trying to copy configuration that refers to that
UUID, otherwise Firefox just generates a new UUID for them. But since special
preferences used by (at least) uBlock0 and tabliss.io extensions require UUID in
`extension-settings.json`, you might need the configuration even if you don't
want actual configuration.