From ba96d73fc1f8337d4b2aa68a3387f8e5810254ff Mon Sep 17 00:00:00 2001
From: uBlock <35694050+uBlockAdmin@users.noreply.github.com>
Date: Thu, 31 May 2018 13:35:09 +0530
Subject: [PATCH] Added Firefox Embedded WebExtension code.
---
.travis.yml | 1 +
platform/chromium/vapi-background.js | 19 ++-
platform/webext/background.html | 35 +++++
platform/webext/bootstrap.js | 196 +++++++++++++++++++++++++++
platform/webext/chrome.manifest | 1 +
platform/webext/from-legacy.js | 62 +++++++++
platform/webext/install.rdf | 26 ++++
platform/webext/manifest.json | 70 ++++++++++
src/js/messaging.js | 14 +-
src/js/settings.js | 22 ++-
src/js/start.js | 58 +++++---
src/js/storage.js | 6 +-
tools/make-embed-webext-meta.py | 69 ++++++++++
tools/make-embed-webext.sh | 45 ++++++
14 files changed, 590 insertions(+), 34 deletions(-)
create mode 100644 platform/webext/background.html
create mode 100644 platform/webext/bootstrap.js
create mode 100644 platform/webext/chrome.manifest
create mode 100644 platform/webext/from-legacy.js
create mode 100644 platform/webext/install.rdf
create mode 100644 platform/webext/manifest.json
create mode 100644 tools/make-embed-webext-meta.py
create mode 100755 tools/make-embed-webext.sh
diff --git a/.travis.yml b/.travis.yml
index d9a2d15ee..f2b1830cd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ env:
matrix:
- BROWSER=chromium EXT=zip
- BROWSER=firefox EXT=xpi
+ - BROWSER=embed-webext EXT=xpi
script: ./tools/make-${BROWSER}.sh all
deploy:
provider: releases
diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js
index 88678c113..9199a9090 100644
--- a/platform/chromium/vapi-background.js
+++ b/platform/chromium/vapi-background.js
@@ -38,6 +38,16 @@ var manifest = chrome.runtime.getManifest();
vAPI.chrome = true;
+vAPI.webextFlavor = '';
+if (
+ self.browser instanceof Object &&
+ typeof self.browser.runtime.getBrowserInfo === 'function'
+) {
+ self.browser.runtime.getBrowserInfo().then(function(info) {
+ vAPI.webextFlavor = info.vendor + '-' + info.name + '-' + info.version;
+ });
+}
+
var noopFunc = function(){};
/******************************************************************************/
@@ -219,7 +229,9 @@ vAPI.tabs.registerListeners = function() {
onClosedClient(tabId);
};
- chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget);
+ if ( chrome.webNavigation.onCreatedNavigationTarget instanceof Object ) {
+ chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget);
+ }
chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate);
chrome.webNavigation.onCommitted.addListener(onCommitted);
chrome.tabs.onUpdated.addListener(onUpdated);
@@ -337,6 +349,11 @@ vAPI.tabs.open = function(details) {
wrapper();
return;
}
+
+ if ( /^Mozilla-Firefox-5[2-5]\./.test(vAPI.webextFlavor) ) {
+ wrapper();
+ return;
+ }
chrome.tabs.query({ url: targetURL }, function(tabs) {
var tab = tabs[0];
diff --git a/platform/webext/background.html b/platform/webext/background.html
new file mode 100644
index 000000000..61005fc90
--- /dev/null
+++ b/platform/webext/background.html
@@ -0,0 +1,35 @@
+
+
+
+
+uBlock
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/platform/webext/bootstrap.js b/platform/webext/bootstrap.js
new file mode 100644
index 000000000..4128c8f97
--- /dev/null
+++ b/platform/webext/bootstrap.js
@@ -0,0 +1,196 @@
+/*******************************************************************************
+
+ µBlock - a browser extension to block requests.
+ Copyright (C) 2014 The µBlock authors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/uBlockAdmin/uBlock
+*/
+
+'use strict';
+
+const hostName = 'ublock';
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const {Services} = Cu.import('resource://gre/modules/Services.jsm', null);
+
+function startup({ webExtension }) {
+ webExtension.startup().then(api => {
+ let { browser } = api,
+ dataMigrator;
+ let onMessage = function(message, sender, callback) {
+ if ( message.what === 'getNextMigrateItem' ) {
+ dataMigrator = dataMigrator || getDataMigrate();
+ dataMigrator.sendNextItemData((key, value) => {
+ if ( key === undefined ) {
+ dataMigrator.closeDbConn();
+ dataMigrator = undefined;
+ browser.runtime.onMessage.removeListener(onMessage);
+ }
+ callback({ key: key, value: JSON.stringify(value) });
+ });
+ return true;
+ }
+ if ( message.what === 'dataMigrateDone' ) {
+ browser.runtime.onMessage.removeListener(onMessage);
+ }
+ if ( typeof callback === 'function' ) {
+ callback();
+ }
+ };
+ browser.runtime.onMessage.addListener(onMessage);
+ });
+}
+
+function shutdown() {
+}
+
+function install() {
+}
+
+function uninstall() {
+}
+
+var SQLite = {
+
+ open: function() {
+
+ var path = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ path.append('extension-data');
+ path.append(hostName + '.sqlite');
+ if ( !path.exists() || !path.isFile() ) {
+ return null;
+ }
+ this.db = Services.storage.openDatabase(path);
+ return this.db;
+ },
+
+ close: function() {
+ SQLite.db.asyncClose();
+ },
+
+ run: function(query, values, callback) {
+
+ if ( !this.db ) {
+ if ( this.open() === null ) {
+ callback({});
+ return;
+ }
+ }
+
+ var result = {};
+ query = this.db.createAsyncStatement(query);
+
+ if ( Array.isArray(values) && values.length ) {
+ var i = values.length;
+
+ while ( i-- ) {
+ query.bindByIndex(i, values[i]);
+ }
+ }
+
+ query.executeAsync({
+ handleResult: function(rows) {
+ if ( !rows || typeof callback !== 'function' ) {
+ return;
+ }
+
+ var row;
+
+ while ( row = rows.getNextRow() ) {
+ result[row.getResultByIndex(0)] = row.getResultByIndex(1);
+ }
+ },
+ handleCompletion: function(reason) {
+ if ( typeof callback === 'function' && reason === 0 ) {
+ callback(result);
+ }
+ },
+ handleError: function(error) {
+ if ( typeof callback === 'function' && reason === 0 ) {
+ callback();
+ }
+ result = null;
+ if ( error.result.toString() === '11' ) {
+ close();
+ }
+ }
+ });
+ }
+};
+
+var getDataMigrate = function() {
+
+ var legacyData = null;
+ var legacyDataKeys = null;
+
+ var fetchLegacyData = function(cb) {
+ var values = [];
+
+ var prepareResult = function(result) {
+
+ if ( result === undefined ) {
+ cb();
+ return;
+ }
+
+ var key;
+ for ( key in result ) {
+ result[key] = JSON.parse(result[key]);
+ }
+
+ legacyData = result;
+ legacyDataKeys = Object.keys(result);
+
+ var key = legacyDataKeys.pop();
+ var value = legacyData[key];
+ cb({ key: key, value: value });
+ };
+
+ SQLite.run(
+ 'SELECT * FROM settings',
+ values,
+ prepareResult
+ );
+ };
+
+ var sendNextItemData = function(callback) {
+
+ if(!legacyData) {
+ fetchLegacyData( bin => {
+ callback(bin.key, bin.value);
+ });
+ return;
+ }
+ else {
+ var key = legacyDataKeys.pop();
+ var value = legacyData[key];
+ callback(key,value);
+ return;
+ }
+ };
+
+ var closeDbConn = function() {
+ SQLite.close();
+ };
+
+ return {
+ sendNextItemData: sendNextItemData,
+ closeDbConn: closeDbConn
+ };
+}
+
+
+
+/******************************************************************************/
diff --git a/platform/webext/chrome.manifest b/platform/webext/chrome.manifest
new file mode 100644
index 000000000..5433b6dca
--- /dev/null
+++ b/platform/webext/chrome.manifest
@@ -0,0 +1 @@
+content ublock ./
diff --git a/platform/webext/from-legacy.js b/platform/webext/from-legacy.js
new file mode 100644
index 000000000..b5584fa21
--- /dev/null
+++ b/platform/webext/from-legacy.js
@@ -0,0 +1,62 @@
+/*******************************************************************************
+
+ µBlock - a browser extension to block requests.
+ Copyright (C) 2014 The µBlock authors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/uBlockAdmin/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+µBlock.migrateLegacyData = (function() {
+
+ let µb = µBlock;
+
+ let migrateLegacyData = function(callback) {
+
+ let storeKeyValue = function(details, callback) {
+ let bin = {};
+ bin[details.key] = JSON.parse(details.value);
+ vAPI.storage.set(bin, callback);
+ };
+
+ let migrateNextDataItem = function() {
+ self.browser.runtime.sendMessage({ what: 'getNextMigrateItem' }, response => {
+ if ( response.key === undefined ) {
+ return callback();
+ }
+ storeKeyValue(response, migrateNextDataItem);
+ });
+ };
+
+ self.browser.storage.local.get('dataMigrateDone', bin => {
+ if ( bin && bin.dataMigrateDone ) {
+ self.browser.runtime.sendMessage({ what: 'dataMigrateDone' });
+ return callback();
+ }
+ self.browser.storage.local.set({ dataMigrateDone: true });
+ migrateNextDataItem();
+ });
+ };
+
+ return migrateLegacyData;
+
+})();
+
+
+/******************************************************************************/
diff --git a/platform/webext/install.rdf b/platform/webext/install.rdf
new file mode 100644
index 000000000..676ba1692
--- /dev/null
+++ b/platform/webext/install.rdf
@@ -0,0 +1,26 @@
+
+
+
+ {{2b10c1c8-a11f-4bad-fe9c-1c11e82cac42}}
+ {version}
+ {name}
+ {description}
+ {homepage}
+ {author}
+ 2
+ true
+ true
+ true
+{localized}
+
+
+
+
+ {{ec8030f7-c20a-464f-9b0e-13a3a9e97384}}
+ 52.0a1
+ 56.*
+
+
+
+
+
diff --git a/platform/webext/manifest.json b/platform/webext/manifest.json
new file mode 100644
index 000000000..4cffd9cdc
--- /dev/null
+++ b/platform/webext/manifest.json
@@ -0,0 +1,70 @@
+{
+ "manifest_version": 2,
+
+ "name": "uBlock",
+ "version": "0.9.5.5",
+
+ "applications": {
+ "gecko": {
+ "id": "{2b10c1c8-a11f-4bad-fe9c-1c11e82cac42}",
+ "strict_min_version": "52.0a1"
+ }
+ },
+
+ "default_locale": "en",
+ "description": "__MSG_extShortDesc__",
+ "icons": {
+ "16": "img/icon_16.png",
+ "128": "img/icon_128.png"
+ },
+
+ "browser_action": {
+ "default_icon": "img/browsericons/icon19-off.png",
+ "default_title": "uBlock",
+ "default_popup": "popup.html"
+ },
+
+ "author": "The uBlock Development Team",
+ "background": {
+ "page": "background.html"
+ },
+ "content_scripts": [
+ {
+ "matches": ["http://*/*", "https://*/*"],
+ "js": ["js/vapi-client.js", "js/contentscript-start.js"],
+ "run_at": "document_start",
+ "all_frames": true
+ },
+ {
+ "matches": ["http://*/*", "https://*/*"],
+ "js": ["js/contentscript-end.js"],
+ "run_at": "document_end",
+ "all_frames": true
+ },
+ {
+ "matches": [
+ "https://*.adblockplus.org/*",
+ "https://*.adblockplus.me/*",
+ "https://www.fanboy.co.nz/*"
+ ],
+ "js": ["js/subscriber.js"],
+ "run_at": "document_idle"
+ }
+ ],
+ "minimum_chrome_version": "22.0",
+ "options_ui": {
+ "page": "options_ui.html"
+ },
+ "permissions": [
+ "contextMenus",
+ "storage",
+ "tabs",
+ "unlimitedStorage",
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking",
+ "http://*/*",
+ "https://*/*"
+ ],
+ "short_name": "uBlock"
+}
diff --git a/src/js/messaging.js b/src/js/messaging.js
index cc4b26840..4ad932041 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -1075,17 +1075,13 @@ var backupUserData = function(callback) {
var filename = vAPI.i18n('aboutBackupFilename')
.replace('{{datetime}}', now.toLocaleString())
.replace(/ +/g, '_');
-
- vAPI.download({
- 'url': 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(userData, null, ' ')),
- 'filename': filename
- });
-
µb.restoreBackupSettings.lastBackupFile = filename;
µb.restoreBackupSettings.lastBackupTime = Date.now();
vAPI.storage.preferences.set(µb.restoreBackupSettings);
-
- getLocalData(callback);
+
+ getLocalData(function(localData) {
+ callback({ localData: localData, userData: userData });
+ });
};
var onUserFiltersReady = function(details) {
@@ -1100,7 +1096,7 @@ var backupUserData = function(callback) {
var restoreUserData = function(request) {
var userData = request.userData;
- var countdown = 7;
+ var countdown = 6;
var onCountdown = function() {
countdown -= 1;
if ( countdown === 0 ) {
diff --git a/src/js/settings.js b/src/js/settings.js
index 972c4f00e..48372fa84 100644
--- a/src/js/settings.js
+++ b/src/js/settings.js
@@ -99,14 +99,32 @@ var startImportFilePicker = function() {
/******************************************************************************/
var exportToFile = function() {
- messager.send({ what: 'backupUserData' }, onLocalDataReceived);
+ //messager.send({ what: 'backupUserData' }, onLocalDataReceived);
+ messager.send({ what: 'backupUserData' }, function(response) {
+ if (
+ response instanceof Object === false ||
+ response.userData instanceof Object === false
+ ) {
+ return;
+ }
+ vAPI.download({
+ 'url': 'data:text/plain;charset=utf-8,' +
+ encodeURIComponent(JSON.stringify(response.userData, null, ' ')),
+ 'filename': response.localData.lastBackupFile
+ });
+ onLocalDataReceived(response.localData);
+ });
};
/******************************************************************************/
var onLocalDataReceived = function(details) {
uDom('#localData > ul > li:nth-of-type(1)').text(
- vAPI.i18n('settingsStorageUsed').replace('{{value}}', details.storageUsed.toLocaleString())
+ vAPI.i18n('settingsStorageUsed')
+ .replace(
+ '{{value}}',
+ typeof details.storageUsed === 'number' ? details.storageUsed.toLocaleString() : '?'
+ )
);
var elem, dt;
diff --git a/src/js/start.js b/src/js/start.js
index 1eeea6931..50e0262a4 100644
--- a/src/js/start.js
+++ b/src/js/start.js
@@ -182,7 +182,7 @@ var onUserFiltersReady = function(userFilters) {
var onFirstFetchReady = function(fetched) {
// Order is important -- do not change:
- onInstalled(fetched.version);
+ onInstalled();
onSystemSettingsReady(fetched);
fromFetch(µb.localSettings, fetched);
onUserSettingsReady(fetched);
@@ -199,21 +199,30 @@ var onFirstFetchReady = function(fetched) {
µb.loadPublicSuffixList(onPSLReady);
};
-var onInstalled = function(version) {
- var firstInstall = version === '0.0.0.0';
- if(!firstInstall) {
- return;
- }
- var onDataReceived = function(data) {
- entries = data.stats || {userId: µBlock.stats.generateUserId(),totalPings: 0 };
- vAPI.storage.set({ 'stats': entries });
- vAPI.tabs.open({
- url: µBlock.donationUrl+"?u=" + entries.userId + "&lg=" + navigator.language,
- select: true,
- index: -1
- });
- }
- vAPI.storage.get('stats',onDataReceived);
+
+var onInstalled = function() {
+
+ var onVersionRead = function(store) {
+
+ var lastVersion = store.extensionLastVersion || '0.0.0.0';
+
+ var firstInstall = lastVersion === '0.0.0.0';
+
+ if(!firstInstall) {
+ return;
+ }
+ var onDataReceived = function(data) {
+ entries = data.stats || {userId: µBlock.stats.generateUserId(),totalPings: 0 };
+ vAPI.storage.set({ 'stats': entries });
+ vAPI.tabs.open({
+ url: µBlock.donationUrl+"?u=" + entries.userId + "&lg=" + navigator.language,
+ select: true,
+ index: -1
+ });
+ }
+ vAPI.storage.get('stats',onDataReceived);
+ };
+ vAPI.storage.get('extensionLastVersion', onVersionRead);
}
/******************************************************************************/
@@ -249,10 +258,7 @@ var fromFetch = function(to, fetched) {
}
};
-/******************************************************************************/
-
-return function() {
- // Forbid remote fetching of assets
+var onSelectedFilterListsLoaded = function() {
µb.assets.remoteFetchBarrier += 1;
var fetchableProps = {
@@ -265,6 +271,7 @@ return function() {
'netWhitelist': '',
'userFilters': '',
'cached_asset_content://assets/user/filters.txt': '',
+ 'selfie': null,
'selfieMagic': '',
'version': '0.0.0.0'
};
@@ -274,7 +281,16 @@ return function() {
vAPI.storage.preferences.get(fetchableProps, onPrefFetchReady);
};
-/******************************************************************************/
+return function() {
+ if ( typeof µb.migrateLegacyData === 'function' ) {
+ µb.migrateLegacyData(function() {
+ onSelectedFilterListsLoaded();
+ });
+ }
+ else {
+ onSelectedFilterListsLoaded();
+ }
+};
})();
diff --git a/src/js/storage.js b/src/js/storage.js
index 8271608e5..92ecb9db5 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -33,7 +33,11 @@
µBlock.storageUsed = bytesInUse;
callback(bytesInUse);
};
- vAPI.storage.getBytesInUse(null, getBytesInUseHandler);
+ if ( vAPI.storage.getBytesInUse instanceof Function ) {
+ vAPI.storage.getBytesInUse(null, getBytesInUseHandler);
+ } else {
+ callback();
+ }
};
/******************************************************************************/
diff --git a/tools/make-embed-webext-meta.py b/tools/make-embed-webext-meta.py
new file mode 100644
index 000000000..bac916f38
--- /dev/null
+++ b/tools/make-embed-webext-meta.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+import os
+import json
+import re
+import sys
+from io import open as uopen
+from collections import OrderedDict
+
+if len(sys.argv) == 1 or not sys.argv[1]:
+ raise SystemExit('Build dir missing.')
+
+proj_dir = os.path.join(os.path.split(os.path.abspath(__file__))[0], '..')
+build_dir = os.path.abspath(sys.argv[1])
+
+chromium_manifest = {}
+webext_manifest = {}
+
+chromium_manifest_file = os.path.join(proj_dir, 'platform', 'chromium', 'manifest.json')
+with open(chromium_manifest_file) as f1:
+ chromium_manifest = json.load(f1)
+
+webext_manifest_file = os.path.join(build_dir, 'webextension', 'manifest.json')
+with open(webext_manifest_file) as f2:
+ webext_manifest = json.load(f2)
+
+webext_manifest['version'] = chromium_manifest['version']
+
+with open(webext_manifest_file, 'w') as f2:
+ json.dump(webext_manifest, f2, indent=2, separators=(',', ': '), sort_keys=True)
+ f2.write('\n')
+
+descriptions = OrderedDict({})
+source_locale_dir = os.path.join(build_dir, 'webextension', '_locales')
+for alpha2 in sorted(os.listdir(source_locale_dir)):
+ locale_path = os.path.join(source_locale_dir, alpha2, 'messages.json')
+ with uopen(locale_path, encoding='utf-8') as f:
+ strings = json.load(f, object_pairs_hook=OrderedDict)
+ alpha2 = alpha2.replace('_', '-')
+ descriptions[alpha2] = strings['extShortDesc']['message']
+
+webext_manifest['author'] = chromium_manifest['author'];
+webext_manifest['name'] = chromium_manifest['name'] + '/embed-webext';
+webext_manifest['homepage'] = 'https://github.com/uBlockAdmin/uBlock'
+webext_manifest['description'] = descriptions['en']
+del descriptions['en']
+webext_manifest['localized'] = []
+t = ' '
+t3 = 3 * t
+for alpha2 in descriptions:
+ if alpha2 == 'en':
+ continue
+ webext_manifest['localized'].append(
+ '\n' + t*2 + '\n' +
+ t3 + '' + alpha2 + '\n' +
+ t3 + '' + webext_manifest['name'] + '\n' +
+ t3 + '' + descriptions[alpha2] + '\n' +
+ t3 + '' + webext_manifest['author'] + '\n' +
+ t3 + '' + webext_manifest['homepage'] + '\n' +
+ t*2 + ''
+ )
+webext_manifest['localized'] = '\n'.join(webext_manifest['localized'])
+
+install_rdf = os.path.join(build_dir, 'install.rdf')
+with uopen(install_rdf, 'r+t', encoding='utf-8', newline='\n') as f:
+ install_rdf = f.read()
+ f.seek(0)
+ f.write(install_rdf.format(**webext_manifest))
+ f.truncate()
diff --git a/tools/make-embed-webext.sh b/tools/make-embed-webext.sh
new file mode 100755
index 000000000..ad2939103
--- /dev/null
+++ b/tools/make-embed-webext.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+#
+# This script assumes a linux environment
+
+echo "*** uBlock.embed-webext: Creating web store package"
+echo "*** uBlock.embed-webext: Copying files"
+
+DES=dist/build/uBlock.embed-webext
+rm -rf $DES
+mkdir -p $DES/webextension
+
+cp -R assets $DES/webextension/
+rm $DES/webextension/assets/*.sh
+cp -R src/css $DES/webextension/
+cp -R src/img $DES/webextension/
+cp -R src/js $DES/webextension/
+cp -R src/lib $DES/webextension/
+cp -R src/_locales $DES/webextension/
+cp -R $DES/webextension/_locales/nb $DES/webextension/_locales/no
+cp src/*.html $DES/webextension/
+cp platform/chromium/*.js $DES/webextension/js/
+cp -R platform/chromium/img $DES/webextension/
+cp platform/chromium/*.html $DES/webextension/
+cp platform/chromium/*.json $DES/webextension/
+cp LICENSE.txt $DES/webextension/
+
+cp platform/webext/background.html $DES/webextension/
+cp platform/webext/from-legacy.js $DES/webextension/js/
+cp platform/webext/manifest.json $DES/webextension/
+cp platform/webext/bootstrap.js $DES/
+cp platform/webext/chrome.manifest $DES/
+cp platform/webext/install.rdf $DES/
+mv $DES/webextension/img/icon_128.png $DES/icon.png
+
+echo "*** uBlock.embed-webext: Generating meta..."
+python tools/make-embed-webext-meta.py $DES/
+
+if [ "$1" = all ]; then
+ echo "*** uBlock.embed-webext: Creating package..."
+ pushd $DES > /dev/null
+ zip ../$(basename $DES).xpi -qr *
+ popd > /dev/null
+fi
+
+echo "*** uBlock.embed-webext: Package done."