diff options
Diffstat (limited to 'build')
-rw-r--r-- | build/BUSYBOX_LICENSE | 16 | ||||
-rw-r--r-- | build/applications-data.js | 243 | ||||
-rw-r--r-- | build/busybox-armv6l | bin | 0 -> 1085140 bytes | |||
-rw-r--r-- | build/install-gaia.py | 176 | ||||
-rw-r--r-- | build/multilocale.py | 248 | ||||
-rw-r--r-- | build/offline-cache.js | 159 | ||||
-rw-r--r-- | build/optimize-clean.js | 28 | ||||
-rwxr-xr-x | build/otoro-install-busybox.sh | 17 | ||||
-rw-r--r-- | build/payment-prefs.js | 5 | ||||
-rw-r--r-- | build/preferences.js | 100 | ||||
-rw-r--r-- | build/settings.py | 236 | ||||
-rw-r--r-- | build/ua-override-prefs.js | 102 | ||||
-rw-r--r-- | build/utils.js | 222 | ||||
-rw-r--r-- | build/wallpaper.jpg | bin | 0 -> 33533 bytes | |||
-rw-r--r-- | build/webapp-manifests.js | 179 | ||||
-rw-r--r-- | build/webapp-optimize.js | 386 | ||||
-rw-r--r-- | build/webapp-zip.js | 293 |
17 files changed, 2410 insertions, 0 deletions
diff --git a/build/BUSYBOX_LICENSE b/build/BUSYBOX_LICENSE new file mode 100644 index 0000000..e5b619c --- /dev/null +++ b/build/BUSYBOX_LICENSE @@ -0,0 +1,16 @@ +About BusyBox + +BusyBox is licensed under the GNU General Public License version 2 + +Check busybox' site for more information: + http://busybox.net + http://busybox.net/license.html + +Distributed busybox-armv6l binary was obtained from: + http://www.busybox.net/downloads/binaries/1.19.0/busybox-armv6l + +You can check this by comparing the sha1 signature of the binary: + b7d3edcd61956f4dfcccad7c5ee4d92bcbbcb172 + +The sources are available at: + http://www.busybox.net/downloads/busybox-1.19.0.tar.bz2 diff --git a/build/applications-data.js b/build/applications-data.js new file mode 100644 index 0000000..69ef24b --- /dev/null +++ b/build/applications-data.js @@ -0,0 +1,243 @@ +'use strict'; + +const PREFERRED_ICON_SIZE = 60; +const GAIA_CORE_APP_SRCDIR = 'apps'; +const GAIA_EXTERNAL_APP_SRCDIR = 'external-apps'; +const INSTALL_TIME = 132333986000; // Match this to value in webapp-manifests.js + +// Initial Homescreen icon descriptors. + +// c.f. the corresponding implementation in the Homescreen app. +function bestMatchingIcon(preferred_size, manifest, origin) { + var icons = manifest.icons; + if (!icons) { + return undefined; + } + + var preferredSize = Number.MAX_VALUE; + var max = 0; + + for (var size in icons) { + size = parseInt(size, 10); + if (size > max) + max = size; + + if (size >= PREFERRED_ICON_SIZE && size < preferredSize) + preferredSize = size; + } + // If there is an icon matching the preferred size, we return the result, + // if there isn't, we will return the maximum available size. + if (preferredSize === Number.MAX_VALUE) + preferredSize = max; + + var url = icons[preferredSize]; + if (!url) { + return undefined; + } + + // If the icon path is not an absolute URL, prepend the app's origin. + if (url.indexOf('data:') == 0 || + url.indexOf('app://') == 0 || + url.indexOf('http://') == 0 || + url.indexOf('https://') == 0) + return url; + + return origin + url; +} + +function iconDescriptor(directory, app_name, entry_point) { + let origin = gaiaOriginURL(app_name); + let manifestURL = gaiaManifestURL(app_name); + + // For external/3rd party apps that don't use the Gaia domain, we have an + // 'origin' file that specifies the URL. + let dir = getFile(GAIA_DIR, directory, app_name); + let originFile = dir.clone(); + originFile.append("origin"); + if (originFile.exists()) { + origin = getFileContent(originFile).replace(/^\s+|\s+$/, ''); + if (origin.slice(-1) == "/") { + manifestURL = origin + "manifest.webapp"; + } else { + manifestURL = origin + "/manifest.webapp"; + } + } + + let manifestFile = dir.clone(); + manifestFile.append("manifest.webapp"); + let manifest = getJSON(manifestFile); + + if (entry_point && + manifest.entry_points && + manifest.entry_points[entry_point]) { + manifest = manifest.entry_points[entry_point]; + } + let icon = bestMatchingIcon(PREFERRED_ICON_SIZE, manifest, origin); + + //TODO set localizedName once we know the default locale + return { + manifestURL: manifestURL, + entry_point: entry_point, + updateTime: INSTALL_TIME, + name: manifest.name, + icon: icon + }; +} + +// zeroth grid page is the dock +let customize = {"homescreens": [ + [ + ["apps", "communications", "dialer"], + ["apps", "sms"], + ["apps", "communications", "contacts"], + ["apps", "browser"] + ], [ + ["apps", "camera"], + ["apps", "gallery"], + ["apps", "fm"], + ["apps", "settings"], + ["external-apps", "marketplace"] + ], [ + ["apps", "calendar"], + ["apps", "clock"], + ["apps", "costcontrol"], + ["apps", "email"], + ["apps", "music"], + ["apps", "video"] + ] +]}; + +if (DOGFOOD == 1) { + customize.homescreens[0].push(["dogfood_apps", "feedback"]); +} + +let init = getFile(GAIA_DIR, 'customize.json'); +if (init.exists()) { + customize = getJSON(init); +} + +let content = { + search_page: { + provider: 'EverythingME', + enabled: true + }, + + grid: customize.homescreens.map( + function map_homescreens(applist) { + var output = []; + for (var i = 0; i < applist.length; i++) { + if (applist[i] !== null) { + output.push(iconDescriptor.apply(null, applist[i])); + } + } + return output; + } + ) +}; + +init = getFile(GAIA_DIR, GAIA_CORE_APP_SRCDIR, 'homescreen', 'js', 'init.json'); +writeContent(init, JSON.stringify(content)); + +// Apps that should never appear in settings > app permissions +// bug 830659: We want homescreen to appear in order to remove e.me geolocation permission +let hidden_apps = [ + gaiaManifestURL('keyboard'), + gaiaManifestURL('wallpaper'), + gaiaManifestURL('bluetooth'), + gaiaManifestURL('pdfjs') +]; + +init = getFile(GAIA_DIR, GAIA_CORE_APP_SRCDIR, 'settings', 'js', 'hiddenapps.js'); +writeContent(init, "var HIDDEN_APPS = " + JSON.stringify(hidden_apps)); + +// Apps that should never appear as icons in the homescreen grid or dock +hidden_apps = hidden_apps.concat([ + gaiaManifestURL('homescreen'), + gaiaManifestURL('system') +]); + +init = getFile(GAIA_DIR, GAIA_CORE_APP_SRCDIR, 'homescreen', 'js', 'hiddenapps.js'); +writeContent(init, "var HIDDEN_APPS = " + JSON.stringify(hidden_apps)); + +// Cost Control +init = getFile(GAIA_DIR, 'apps', 'costcontrol', 'js', 'config.json'); + +content = { + provider: 'Vivo', + enable_on: { 724: [6, 10, 11, 23] }, // { MCC: [ MNC1, MNC2, ...] } + is_free: true, + is_roaming_free: true, + credit: { currency : 'R$' }, + balance: { + destination: '8000', + text: 'SALDO', + senders: ['1515'], + regexp: 'Saldo Recarga: R\\$\\s*([0-9]+)(?:[,\\.]([0-9]+))?' + }, + topup: { + destination: '7000', + ussd_destination: '*321#', + text: '&code', + senders: ['1515', '7000'], + confirmation_regexp: 'Voce recarregou R\\$\\s*([0-9]+)(?:[,\\.]([0-9]+))?', + incorrect_code_regexp: '(Favor enviar|envie novamente|Verifique) o codigo de recarga' + }, + default_low_limit_threshold: 3 +}; + +writeContent(init, JSON.stringify(content)); + +// SMS +init = getFile(GAIA_DIR, 'apps', 'sms', 'js', 'blacklist.json'); +content = ["1515", "7000"]; +writeContent(init, JSON.stringify(content)); + +// Browser +init = getFile(GAIA_DIR, 'apps', 'browser', 'js', 'init.json'); + +content = { + "bookmarks": [ + { "title": "Vivo Busca", + "uri": "http://www.google.com.br/m/search?client=ms-hms-tef-br", + "iconUri": "data:image/png;base64,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" + }, + { "title": "Serviços e Downloads", + "uri": "http://vds.vivo.com.br", + "iconUri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABOWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjarZGxSsNQFIa/G0XFoVYI4uBwJ1FQbNXBjElbiiBYq0OSrUlDldIk3NyqfQhHtw4u7j6Bk6PgoPgEvoHi1MEhSHASwW/6zs/hcOAHo2LXnYZRhkGsVbvpSNfz5ewTM0wBQCfMUrvVOgCIkzjiJwI+XxEAz5t23WnwN+bDVGlgAmx3oywEUQH6FzrVIMaAGfRTDeIOMNVJuwbiASj1cn8BSkHub0BJuZ4P4gMwe67ngzEHmEHuK4Cpo0sNUEvSkTrrnWpZtSxL2t0kiOTxKNPRIJP7cZioNFEdHXWB/D8AFvPFdtORa1XL2lvnn3E9X+b2foQAxNJjkRWEQ3X+3YWx8/tc3Bgvw+EtTE+KbPcKbjZg4brIVqtQ3oL78RfCs0/+HAmzJwAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAAAd0SU1FB9wMDg8FJ18m6tUAAAjBSURBVFjDrVd7cFXFGf/tnnPuM++bhBSD8rQq+MCkZVoQaEc7aivRqY8RLVroyB/BUmuSiqMdWxGQURkBR7AkPnCKVUSxVmYqRQTCACUE0YgPCCDP5L5C7r3nsbtnv/5xEyDVDsykO7MzZ3Z/+51vf99zgQscH6Pixl0/qan/fOEDhf8L8+XihqKOeQ/Ut44YeQcA/P0C5PLzAbo6NkZbh47+KlBVsSE0fORyp+3L3g8Bawsqz2Bahw7HRoBnWtsSVklsuVle/mb7vb9wfrxne/H55JvnAxz/w5LrKx+6d0xo1EiQlFCpNGXXdkybjO63+zETTxzG1srq6wqvnWCFho/G0Ecug0zFg+nNH84AsGxQChx5/wM99qe18E4d619iZqxk7cdJ32CwNC8NYFL6GMyS4s1GZRFEsisPYmD7//i8P2gT8HDZpkMvrIHqSUCl4lCpOEpn/wwE/dBkdGNS+hi2hCpvKrzxh1DJ/L5KJ9D5/OvQPtYPWoE6J5lzvum+L7PvK8h0CjKdgi9tWBXlz2wxqsIAYA4pXUchUP++e+wE5TpPPlHnpI6fTz47H+C9aDmm5RJ4v7SKqn85AaQJAKAzLjJv7XqcwYxHb7lyhVHRFxyM4cT6f0PlvCDjTEzLJQbnhP0CVM4bf3pvZ3v0klgfdxxmRckjAGyEGfzTvQAAN5GFyrp1dW5KXEh4s6ZhjQBDMfk0I+2kfeL0t+ZES/I3FbOwKt48ALw+Ejv4vUmjRiJPAnROAkqDFwfPYE62HsxahaHCm7vOsm8fWo3IiF/BO7EupqHv0na3QaTXFYyZc5wBQONFDU71j0YFw2UR1rG5HZlETyPAlrSkXh7gxevDsXCwJGQXX1JKoD7zMQAEgIGceA6ZE70VdU4yee45op2wD31dT35muerZCxgBsoouY9HR9Yw1Dmu8w4pYbw6bNBpaaRAIbsamAzv3M+F4U61QYMvKEyvpHBbeKasquZUbA/2XtEbyZE9HnZ0cd+766c9WTADUDj/zGcjPAoyDAbDKxoEZ4Vnc0qgMWCaE40IKD9L1wE2DXTppLKqvuGSzEvKbWbGZFQCwoGQK6uzkbb3dvTalBem0B532QGlBmXjWr7OT47bfMhEAEN/5dDjZ9uxB2fP5Du/UR9B2CloIaOFBS4Hc4U+R+bI9zJ4e0lQYlKxX15TDL7IAIpAmEJ2d3Z0nqberZ2tzomVKHwsPRmAsDVHehwU0skyuqbOT0wGge9ufN5FMTiX7EGOMAX2TcQaAQbsepfccZEZhYREDgNcwj2uDplghY9OpawvgFDGgX4m8EUl5knUfOOlKV85vTrQ89V6kvAPAFQBAjA7V5ZIjj/+z6UFobxFzD0ZAksAYY2AAZwDnIOkjte8wtOPdTtp/9+rFR3wOANyEvt9f9NGInDYu3nr6ntjODCjjQSoJKQSEEMyHRmzMkGC0vGD+rPKZrdPsxFhiVA6GqrpccuThd+es9bNHl+r03rB2c9BCMi0EtC8hMzbiezpxctv+6a7tGVct6nybmZb/rUT0qjUP98mFAIDFpY2PZGL8MRkzorAIRABR3hdTX8cBILIq3uwAwBev3sOtUNA3nf0A5+inXXk+cl2ZbK47s9CMWEtqF3zttM8bjfELD3x3IuqoUsDR/HcijLSGtknoKBGdMcl35nPLgLJtcCkBzs4ooIUPJRUUAO3IfKL6r9zHAaBpWGNfLIE3XdQ4vWF4A8lKf4UsVhWKJJSQkFJBSUVujw0i2r4q3uzs2nd5+a59lw+5dPpr2ndy64SMQAtBvhDQngDnGqVDowVVY8qeMgtC9s4nxt+djXdxAPji5bqztaBpWCMHYQpx2iQKFXxDD4gGEBH5xNRp6Wpfz29OtDzV2n7ZNgKbCDAw6PjE8V9U7nvuhgcZiUVBIxXhnPJOyPpqM+MgxiFYDEZB8e3gePfS6Wt91jSsoZCAXifqQTO/74cYEIbkaNKe3tKcaJkKABt3jJ3TIyqWZWQMACFsZFEePr7m+gkd0wGg7clJ75iGe2skmMmHYF/45RVh4MEIhcdcx4KFRUU8rNkMAxrCF3l7SQUlJJRQ8G0F3eMf054e0pxomTr//p/ns5sbXHC8N4KM6yDjuujKmdSdLbwTADasrUHN49tuEx6PpJPRTifDQZ4EybPTz/YyQ+VghaMzzLIc7xaccIoLcN13cw0yJGeM2GQwbGuOtxAAPPbKP/DGxqtf7UyXFiqtBhS1o16B8ca/rt6f1epyAPjBn3Y5AEZtf3jshNxpY0dhsYBlAsQYCICXTkHZHQ4DgL8ajzq7iuPBhOWwgLbAiTcSaElzomVAMXplwzWRrAjkuuwoMQwsRgRQcdBlZSH3qpk3t3967rk1E4GLrhxRb5r+8uIYgRsB4qEq9v2mVsaXhx5FsU9VN6Qq51Z50XoGXr4q0fwMY+xb/VzO41uOZsLwlGau0rCFQtaWcJWGpzTrzgVge3zri+tr+LNv1Zw5V33lcExecegFkVHliSOqXsTVXPfIN9UX1BH1j2ffvPaqXhn4JCMt6j/n2h6I6Ei4IHwxiBgABAwfsYB7d+Nde97A/+NdMK+lNn97wXekPBNKa6a0hvQ1lPQfU1I3CSGZ0hpKa9iSISeMNU+urgk/+nItBt0T5pWoudcha7Wks3DfVSCfIivn7nZmL6t1zUgg0M8CB6iAiyULZ7Y9PGgG6l+sMRzJV3vEoYlBEwMRg/Z1w8q5u518M0K3aalZ/74CY7bkv//dippRg1ZAerjJY8bZHgEE3xUAsHL20lrMXlqLl+a2bdBCAejDaILgJjzJbhz000z4MCJE0Izl+woiIqKGl+a2ZQf2fXQN+XovDA4iwASRD+KDZiBp8w9CzD8woliiIqwQJMWUj2Wzlw50sJf+EvyEKZUuDSiMKpEoMpUnfbw+aAVGVEA+98DuMaZBN4UMPcfkqG5+qE2u/O3ugcDPtsMy2PAgp/qAiTsX/Hp3eNzFPH0++f8BWpzorRJD7KcAAAAASUVORK5CYII=" + }, + { + "title": "Site Vivo", + "uri": "http://www.vivo.com.br/conteudosmartphone", + "iconUri": "data:image/jpg;base64,/9j/4AAQSkZJRgABAQEAlgCWAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAgACADASIAAhEBAxEB/8QAGgAAAgIDAAAAAAAAAAAAAAAABQYBAwQHCP/EAC4QAAEDAwIEBQIHAAAAAAAAAAECAwQFBhEAIQcSMUETFFFhcSKBMkKCkqGxwf/EABkBAAIDAQAAAAAAAAAAAAAAAAMGAAECBP/EACQRAAIBAwQCAgMAAAAAAAAAAAECEQMEEgAFITEiQVFxMmGh/9oADAMBAAIRAxEAPwDrG7rkg25AEiVlx1eQywk/U4f8A7nSXFrXEW4k+ZpMJiFEV+BxYSAfgqyT8gasmQBcPEQpmDnjNuFPIenI2OnwVf3ozOnSKnJ8rDeUhnPIyyyoJKwO59Btn2Gky53F7lncswQMVVVMFiOzPxpko0KVsiqEDORJLcgA9CPnQGbVOJdvtGXUIrE+IjdxTaUr5R6kJwoD3wQNN9lXVCuaEXGR4UlsAuMk52PRST3ToLBRNgywtp59pxDwbcaWvmC9wMehznY++sOi01qlcRZSYIDbHmikIT0AW0lah8cxOpZX9ak6sC2JYKysZImYIPB4jkau4pUbimwKgMBIKiAY7BHXvgjQldRXa/F+WmouLEOWQ6ypXQJUCDj9RP7dTSLHrsC6mpER1C6c49zpmMPAEtE53Gc5x6ZGn29LUpd1QEx56VNvNEqYkN7LaJ649QcDIOxwO4B0jxba4kW2S1RqjGqEXP0guBKvulY5QfcHJ0W82rB/KmXTLIY/kCYkR7BgaNbbgtWl4OEfEKQ3RA6IPo/erJ9PumJeTtRapbstIfUtsBWW1D8pOD22ODo9Zdt1RmsP1+vPDzTpJbjpVlKCeqj2zjYY6DQZUHilUh4cmQ1BQepEhKMfdsc3862HSWpLFMjMzHQ7IbbSlxYJIUoDc7763tW3UzcM5RwAchnAGR98cn9Trm3C7dKIQMhJGJxkmB/PvX//2Q==" + } + ] +} + +writeContent(init, JSON.stringify(content)); + +// Support +init = getFile(GAIA_DIR, 'apps', 'settings', 'resources', 'support.json'); +content = { + "onlinesupport": { + "href": "http://www.vivo.com.br/portalweb/appmanager/env/web?_nfls=false&_nfpb=true&_pageLabel=vcAtendMovelBook&WT.ac=portal.atendimento.movel", + "title": "Vivo" + }, + "callsupport": [ + { + "href": "tel:*8486", + "title": "*8486" + }, + { + "href": "tel:1058", + "title": "1058" + } + ] +} +writeContent(init, JSON.stringify(content)); + +// ICC / STK +init = getFile(GAIA_DIR, 'apps', 'settings', 'resources', 'icc.json'); +content = { + "defaultURL": "http://www.mozilla.org/en-US/firefoxos/" +} +writeContent(init, JSON.stringify(content)); diff --git a/build/busybox-armv6l b/build/busybox-armv6l Binary files differnew file mode 100644 index 0000000..5f34bbc --- /dev/null +++ b/build/busybox-armv6l diff --git a/build/install-gaia.py b/build/install-gaia.py new file mode 100644 index 0000000..9ff2ae0 --- /dev/null +++ b/build/install-gaia.py @@ -0,0 +1,176 @@ +"""Usage: python %prog [ADB_PATH] [REMOTE_PATH] + +ADB_PATH is the path to the |adb| executable we should run. +REMOTE_PATH is the path to push the gaia webapps directory to. + +Used by |make install-gaia| to push files to a device. You shouldn't run +this file directly. + +""" + +import sys +import os +import hashlib +import subprocess +from tempfile import mkstemp + +def compute_local_hash(filename, hashes): + h = hashlib.sha1() + with open(filename,'rb') as f: + for chunk in iter(lambda: f.read(256 * h.block_size), b''): + h.update(chunk) + hashes[filename] = h.hexdigest() + +def compute_local_hashes_in_dir(dir, hashes): + def visit(arg, dirname, names): + for filename in [os.path.join(dirname, name) for name in names]: + if not os.path.isfile(filename): + continue + compute_local_hash(filename, hashes) + + os.path.walk(dir, visit, None) + +def compute_local_hashes(): + hashes = {} + compute_local_hashes_in_dir('webapps', hashes) + compute_local_hash('user.js', hashes) + return hashes + +def adb_push(local, remote): + global adb_cmd + subprocess.check_call([adb_cmd, 'push', local, remote]) + +def adb_shell(cmd, ignore_error=False): + global adb_cmd + + # Output the return code so we can check whether the command executed + # successfully. + new_cmd = cmd + '; echo "RETURN CODE: $?"' + + # universal_newlines=True because adb shell returns CRLF separators. + proc = subprocess.Popen([adb_cmd, 'shell', new_cmd], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + (stdout, stderr) = proc.communicate() + if stderr.strip(): + raise Exception('adb shell "%s" returned the following unexpected error: "%s"' % + (cmd, stderr.strip())) + if proc.returncode != 0: + raise Exception('adb shell "%s" exited with error %d' % (cmd, proc.returncode)) + + split = [line for line in stdout.split('\n') if line.strip()] + if not ignore_error and not split[-1].startswith('RETURN CODE: 0'): + raise Exception('adb shell "%s" did not complete successfully. Output:\n%s' % (cmd, stdout)) + + # Don't return the "RETURN CODE: 0" line! + return split[0:-1] + + +def compute_remote_hashes(): + hashes = {} + adb_out = adb_shell('cd /data/local && find . -type f | xargs sha1sum') + for (hash, filename) in [line.split() for line in adb_out]: + # Strip off './' from the filename. + if filename.startswith('./'): + filename = filename[2:] + else: + raise Exception('Unexpected filename %s' % filename) + + hashes[filename] = hash + return hashes + +INDEXED_DB_FOLDER = 'indexedDB/' + +def remove_from_remote(local_hashes, remote_hashes): + """Remove any files from the remote device which don't appear in + local_hashes. + + """ + + # Keep indexedDB content + to_keep = set() + for path in remote_hashes: + if path[:len(INDEXED_DB_FOLDER)] == INDEXED_DB_FOLDER: + to_keep.add(path) + + to_remove = list(set(remote_hashes.keys()) - set(local_hashes.keys()) - to_keep) + + if not to_remove: + return + + print 'Removing from device:\n%s\n' % '\n'.join(to_remove) + # Chunk to_remove into 25 files at a time so we don't send too much over + # adb_shell at once. + for files in [to_remove[pos:pos + 25] for pos in xrange(0, len(to_remove), 25)]: + adb_shell('cd /data/local && rm -f %s' % ' '.join(files)) + +def push_to_remote(local_hashes, remote_hashes): + global adb_cmd + + to_push = set() + for (k, v) in local_hashes.items(): + if k not in remote_hashes or remote_hashes[k] != local_hashes[k]: + to_push.add(k) + + if not to_push: + return + + print 'Pushing to device:\n%s' % '\n'.join(list(to_push)) + + tmpfile, tmpfilename = mkstemp() + try: + subprocess.check_call(['tar', '-czf', tmpfilename] + list(to_push)) + adb_push(tmpfilename, '/data/local') + basename = os.path.basename(tmpfilename) + adb_shell('cd /data/local && tar -xzf %s && rm %s' % (basename, basename)) + finally: + os.remove(tmpfilename) + +def install_gaia_fast(): + os.chdir('profile') + try: + local_hashes = compute_local_hashes() + remote_hashes = compute_remote_hashes() + remove_from_remote(local_hashes, remote_hashes) + push_to_remote(local_hashes, remote_hashes) + finally: + os.chdir('..') + +def install_gaia_slow(): + global adb_cmd, remote_path + webapps_path = remote_path + '/webapps' + adb_shell("rm -r " + webapps_path, ignore_error=True) + adb_shell("rm /data/local/user.js", ignore_error=True) + adb_push('profile/webapps', webapps_path) + adb_push('profile/user.js', '/data/local') + +def install_gaia(): + global remote_path + try: + if remote_path == "/system/b2g": + # XXX Force slow method until we fix the fast one to support + # files in both /system/b2g and /data/local + # install_gaia_fast() + install_gaia_slow() + else: + install_gaia_fast() + except: + # If anything goes wrong, fall back to the slow method. + install_gaia_slow() + +if __name__ == '__main__': + if len(sys.argv) > 3: + print >>sys.stderr, 'Too many arguments!\n' + print >>sys.stderr, \ + 'Usage: python %s [ADB_PATH] [REMOTE_PATH]\n' % __FILE__ + sys.exit(1) + + adb_cmd = 'adb' + remote_path = '/data/local/webapps' + if len(sys.argv) >= 2: + adb_cmd = sys.argv[1] + if len(sys.argv) >= 3: + remote_path = sys.argv[2] + + install_gaia() diff --git a/build/multilocale.py b/build/multilocale.py new file mode 100644 index 0000000..60387b3 --- /dev/null +++ b/build/multilocale.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python + +import os +import shutil +import fnmatch +import json +import re +from optparse import OptionParser +import logging + +section_line = re.compile('\[(?P<section>.*)\]') +import_line = re.compile('@import url\((?P<filename>.*)\)') +property_line = re.compile('(?P<id>.*)\s*[:=]\s*(?P<value>.*)') + +def _get_locales(filename): + locales_list = json.load(open(filename), encoding="utf-8") + return locales_list.keys() + + +def find_files(dirs, pattern): + matches = [] + for dir in dirs: + for current, dirnames, filenames in os.walk(dir): + for filename in fnmatch.filter(filenames, pattern): + matches.append(os.path.join(current, filename)) + return matches + + +def parse_manifest_properties(filename): + with open(filename) as f: + data = f.readlines() + strings = { + "default": {}, + "entry_points": {}, + } + for line in data: + m = property_line.search(line) + if not m or line.strip().startswith('#'): + continue + value = m.group('value').strip() + if '.' in m.group('id'): + entry_point, key = m.group('id').split('.',1) + if entry_point not in strings["entry_points"]: + strings["entry_points"][entry_point] = {} + strings["entry_points"][entry_point][key.strip()] = value + else: + key = m.group('id') + strings["default"][key.strip()] = value + return strings + + +def parse_ini(filename): + log = logging.getLogger(__name__) + with open(filename) as f: + data = f.readlines() + section = 'default' + imports = { section: [] } + for line in data: + if line.strip() == "" or line.startswith('!') or line.startswith('#'): + continue + elif line.strip().startswith('['): # Section header + section = section_line.search(line).group('section') + imports[section] = [] + elif '@import' in line: # Import lines + property_file = import_line.search(line).group('filename') + imports[section].append(property_file) + else: + log.warn('parse_ini - found a line with contents ' + 'unaccounted for "%s"', line.strip()) + return imports + + +def serialize_ini(outfile, imports): + def _section(locale): + return "[%s]" % locale + def _import(path): + return "@import url(%s)" % path + output = [] + for locale, paths in imports.items(): + if locale == "default": + for path in paths: + output.insert(0, _import(path)) + continue + output.append(_section(locale)) + for path in paths: + output.append(_import(path)) + with open(outfile, 'w') as o: + o.write("\n".join(output)) + + +def add_locale_imports(locales, ini_file): + """Recreate an ini file with all locales sections""" + log = logging.getLogger(__name__) + imports = { + "default": parse_ini(ini_file)["default"] + } + for locale in locales: + log.info("adding %s to %s" % (locale, ini_file)) + imports[locale] = [] + for path in imports["default"]: + locale_path = path.replace("en-US", locale) + imports[locale].append(locale_path) + log.debug("added %s" % locale_path) + serialize_ini(ini_file, imports) + log.info("updated %s saved" % ini_file) + + +def copy_properties(source, locales, ini_file): + log = logging.getLogger(__name__) + ini_dirname = os.path.dirname(ini_file) + imports = parse_ini(ini_file) + for locale in locales: + log.info("copying %s files as per %s" % (locale, ini_file)) + for path in imports[locale]: + target_path = os.path.join(ini_dirname, path) + # apps/browser/locales/browser.fr.properties becomes + # apps/browser/browser.properties + source_path = target_path.replace('/locales', '') \ + .replace('.%s' % locale, '') + source_path = os.path.join(source, locale, source_path) + if not os.path.exists(source_path): + log.warn('%s does not exist' % source_path) + continue + shutil.copy(source_path, target_path) + log.debug("copied %s to %s" % (source_path, target_path)) + + +def add_locale_manifest(source, locales, manifest_file): + log = logging.getLogger(__name__) + with open(manifest_file) as f: + manifest = json.load(f, encoding="utf-8") + for locale in locales: + log.info("adding %s to %s" % (locale, manifest_file)) + manifest_properties = os.path.join(source, locale, + os.path.dirname(manifest_file), + 'manifest.properties') + log.debug("getting strings from %s" % manifest_properties) + if not os.path.exists(manifest_properties): + log.warn("%s does not exist" % manifest_properties) + continue + strings = parse_manifest_properties(manifest_properties) + if "entry_points" in manifest: + for name, ep in manifest["entry_points"].items(): + if "locales" not in ep: + continue + log.debug("adding to entry_points.%s.locales" % name) + if name not in strings["entry_points"]: + log.warn("%s.* strings are missing from %s" % + (name, manifest_properties)) + continue + ep["locales"][locale] = {} + ep["locales"][locale].update(strings["entry_points"][name]) + if "locales" in manifest: + log.debug("adding to locales") + manifest["locales"][locale] = {} + manifest["locales"][locale].update(strings["default"]) + f.close() + with open(manifest_file, 'w') as o: + json.dump(manifest, o, encoding="utf-8", indent=2) + log.debug("updated %s saved" % manifest_file) + + +def setup_logging(volume=1, console=True, filename=None): + logger = logging.getLogger(__name__) + levels = [logging.DEBUG, + logging.INFO, + logging.WARNING, + logging.ERROR, + logging.CRITICAL][::1] + if volume > len(levels): + volume = len(levels) - 1 + elif volume < 0: + volume = 0 + logger.setLevel(levels[len(levels)-volume]) + if console: + console_handler = logging.StreamHandler() + console_formatter = logging.Formatter('%(levelname)s: %(message)s') + console_handler.setFormatter(console_formatter) + logger.addHandler(console_handler) + if filename: + file_handler = logging.FileHandler(filename) + file_formatter = logging.Formatter('%(asctime) - %(levelname)s: %(message)s') + file_handler.addFormatter(file_formatter) + logger.addHandler(file_handler) + + +def main(): + parser = OptionParser("%prog [OPTIONS] [LOCALES...] - create multilocale Gaia") + parser.add_option("-v", "--verbose", + action="count", dest="verbose", default=2, + help="use more to make louder") + parser.add_option("-i", "--ini", + action="store_true", dest="onlyini", default=False, + help=("just edit the ini files and exit; " + "use this with DEBUG=1 make profile")) + parser.add_option("--target", + action="append", dest="target", + help=("path to directory to make changes in " + "(more than one is fine)")) + parser.add_option("--source", + action="store", dest="source", + help="path to the l10n basedir") + parser.add_option("--config", + action="store", dest="config_file", + help=("path to the languages.json config file; " + "will be used instead of LOCALES")) + + options, locales = parser.parse_args() + + setup_logging(volume=options.verbose) + log = logging.getLogger(__name__) + + if options.config_file is not None: + locales = _get_locales(options.config_file) + log.debug("config file specified; ignoring any locales passed as args") + elif len(locales) == 0: + parser.error("You need to specify --config or pass the list of locales") + if options.target is None: + parser.error("You need to specify at least one --target") + if options.source is None and not options.onlyini: + parser.error("You need to specify --source (unless you meant --ini)") + + if "en-US" in locales: + locales.remove("en-US") + ini_files = find_files(options.target, "*.ini") + + # 1. link properties files from the inis + for ini_file in ini_files: + log.info("########## adding locale import rules to %s" % ini_file) + add_locale_imports(locales, ini_file) + + if options.onlyini: + parser.exit(1) + + # 2. copy properties files as per the inis + for ini_file in ini_files: + log.info("########## copying locale files as per %s" % ini_file) + copy_properties(options.source, locales, ini_file) + + # 3. edit manifests + manifest_files = find_files(options.target, 'manifest.webapp') + for manifest_file in manifest_files: + log.info("########## adding localized names to %s" % manifest_file) + add_locale_manifest(options.source, locales, manifest_file) + + +if __name__ == "__main__": + main() diff --git a/build/offline-cache.js b/build/offline-cache.js new file mode 100644 index 0000000..abec495 --- /dev/null +++ b/build/offline-cache.js @@ -0,0 +1,159 @@ +let Namespace = CC('@mozilla.org/network/application-cache-namespace;1', + 'nsIApplicationCacheNamespace', + 'init'); +const nsICache = Ci.nsICache; +const nsIApplicationCache = Ci.nsIApplicationCache; +const applicationCacheService = Cc['@mozilla.org/network/application-cache-service;1'] + .getService(Ci.nsIApplicationCacheService); + +function log(str) { + dump(' +-+ OfflineCache: ' + str + '\n'); +} + +/* + * Compile Gaia into an offline cache sqlite database. + */ +function storeCache(applicationCache, url, file, itemType) { + let session = Services.cache.createSession(applicationCache.clientID, + nsICache.STORE_OFFLINE, true); + session.asyncOpenCacheEntry(url, nsICache.ACCESS_WRITE, { + onCacheEntryAvailable: function (cacheEntry, accessGranted, status) { + cacheEntry.setMetaDataElement('request-method', 'GET'); + cacheEntry.setMetaDataElement('response-head', 'HTTP/1.1 200 OK\r\n'); + // Force an update. the default expiration time is way too far in the future: + //cacheEntry.setExpirationTime(0); + + let outputStream = cacheEntry.openOutputStream(0); + + // Input-Output stream machinery in order to push nsIFile content into cache + let inputStream = Cc['@mozilla.org/network/file-input-stream;1'] + .createInstance(Ci.nsIFileInputStream); + inputStream.init(file, 1, -1, null); + let bufferedOutputStream = Cc['@mozilla.org/network/buffered-output-stream;1'] + .createInstance(Ci.nsIBufferedOutputStream); + bufferedOutputStream.init(outputStream, 1024); + bufferedOutputStream.writeFrom(inputStream, inputStream.available()); + bufferedOutputStream.flush(); + bufferedOutputStream.close(); + outputStream.close(); + inputStream.close(); + + cacheEntry.markValid(); + log (file.path + ' -> ' + url + ' (' + itemType + ')'); + applicationCache.markEntry(url, itemType); + cacheEntry.close(); + } + }); +} + +function getCachedURLs(origin, appcacheFile) { + let urls = []; + let lines = getFileContent(appcacheFile).split(/\r?\n/); + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + if (/^#/.test(line) || !line.length) + continue; + if (line == 'CACHE MANIFEST') + continue; + if (line == 'CACHE:') + continue; + if (line == 'NETWORK:') + break; + // Prepend webapp origin in case of absolute path + if (line[0] == '/') + urls.push(origin + line.substring(1)); + // Just pass along the url, if we have one + else if (line.substr(0, 4) == 'http') + urls.push(line); + else + throw new Error('Invalid line in appcache manifest:\n' + line + + '\nFrom: ' + appcacheFile.path); + } + return urls; +} + +let webapps = getJSON(getFile(PROFILE_DIR, 'webapps', 'webapps.json')); + +Gaia.externalWebapps.forEach(function (webapp) { + // Process only webapp with a `appcache_path` field in their manifest. + if (!('appcache_path' in webapp.manifest)) + return; + + // Get the nsIFile for the appcache file by using `origin` file and + // `appcache_path` field of the manifest in order to find it in `cache/`. + let originDomain = webapp.origin.replace(/^https?:\/\//, ''); + let appcachePath = 'cache/' + originDomain + webapp.manifest.appcache_path; + let appcacheURL = webapp.origin + + webapp.manifest.appcache_path.replace(/^\//, ''); + let appcacheFile = webapp.sourceDirectoryFile.clone(); + appcachePath.split('/').forEach(function (name) { + appcacheFile.append(name); + }); + if (!appcacheFile.exists()) + throw new Error('Unable to find application cache manifest: ' + + appcacheFile.path); + + // Retrieve generated webapp id from platform profile file build by + // webapp-manifest.js; in order to allow the webapp to use offline cache. + let appId = webapps[webapp.sourceDirectoryName].localId; + let principal = Services.scriptSecurityManager.getAppCodebasePrincipal( + Services.io.newURI(webapp.origin, null, null), + appId, false); + Services.perms.addFromPrincipal(principal, 'offline-app', + Ci.nsIPermissionManager.ALLOW_ACTION); + + // Get the url for the manifest. At some points the root + // domain should be extracted from manifest.webapp. + // See netwerk/cache/nsDiskCacheDeviceSQL.cpp : AppendJARIdentifier + // The group ID contains application id and 'f' for not being hosted in + // a browser element, but a mozbrowser iframe. + let groupID = appcacheURL + '#' + appId + '+f'; + let applicationCache = applicationCacheService.createApplicationCache(groupID); + applicationCache.activate(); + + log ('Compiling (' + webapp.domain + ')'); + + let urls = getCachedURLs(webapp.origin, appcacheFile); + urls.forEach(function appendFile(url) { + // Get this nsIFile out of its relative path + let path = url.replace(/https?:\/\//, ''); + let file = webapp.sourceDirectoryFile.clone(); + file.append('cache'); + let paths = path.split('/'); + for (let i = 0; i < paths.length; i++) { + file.append(paths[i]); + } + + if (!file.exists()) { + let msg = 'File ' + file.path + ' exists in the manifest but does not ' + + 'points to a real file.'; + throw new Error(msg); + } + + // TODO: use ITEM_IMPLICIT for launch_path, if it occurs to be important. + let itemType = nsIApplicationCache.ITEM_EXPLICIT; + storeCache(applicationCache, url, file, itemType); + }); + + // Store the appcache file + storeCache(applicationCache, appcacheURL, appcacheFile, + nsIApplicationCache.ITEM_MANIFEST); + + // NETWORK: + // http://* + // https://* + let array = Cc['@mozilla.org/array;1'].createInstance(Ci.nsIMutableArray); + let bypass = Ci.nsIApplicationCacheNamespace.NAMESPACE_BYPASS; + array.appendElement(new Namespace(bypass, 'http://*/', ''), false); + array.appendElement(new Namespace(bypass, 'https://*/', ''), false); + applicationCache.addNamespaces(array); +}); + + +// Wait for cache to be filled before quitting +if (Gaia.engine === 'xpcshell') { + let thread = Services.tm.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } +} diff --git a/build/optimize-clean.js b/build/optimize-clean.js new file mode 100644 index 0000000..14dbcf0 --- /dev/null +++ b/build/optimize-clean.js @@ -0,0 +1,28 @@ + +function debug(str) { + //dump(' -*- l10n-clean.js: ' + str + '\n'); +} + +debug('Begin'); + +Gaia.webapps.forEach(function(webapp) { + // if BUILD_APP_NAME isn't `*`, we only accept one webapp + if (BUILD_APP_NAME != '*' && webapp.sourceDirectoryName != BUILD_APP_NAME) + return; + + debug(webapp.sourceDirectoryName); + + let re = new RegExp('\\.html\\.' + GAIA_DEFAULT_LOCALE + '$'); + let files = ls(webapp.sourceDirectoryFile, true); + files.forEach(function(file) { + if ( + re.test(file.leafName) || + file.leafName.indexOf(Gaia.aggregatePrefix) === 0 + ) { + file.remove(false); + } + }); +}); + +debug('End'); + diff --git a/build/otoro-install-busybox.sh b/build/otoro-install-busybox.sh new file mode 100755 index 0000000..8fa138b --- /dev/null +++ b/build/otoro-install-busybox.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# This shell script installs busybox on the device. +# This lets us take the fast path in install-gaia.py. + +# Remount file system with read/write permissions +adb shell "mount -o rw,remount -t rootfs /" +adb shell "mkdir -p /system/vendor/bin" +adb push busybox-armv6l /vendor/bin/busybox +adb shell "chmod 555 /vendor/bin/busybox" + +# Perform the symbolic links +adb shell "for x in \`busybox --list\`; do ln -s /vendor/bin/busybox /vendor/bin/\$x; done" + +# Remount file system with read-only permissions +adb shell "mount -o ro,remount -t rootfs /" + diff --git a/build/payment-prefs.js b/build/payment-prefs.js new file mode 100644 index 0000000..214fd4e --- /dev/null +++ b/build/payment-prefs.js @@ -0,0 +1,5 @@ +pref("dom.payment.provider.0.name", "firefoxmarket"); +pref("dom.payment.provider.0.description", "marketplace.firefox.com"); +pref("dom.payment.provider.0.uri", "https://marketplace.firefox.com/mozpay/?req="); +pref("dom.payment.provider.0.type", "mozilla/payments/pay/v1"); +pref("dom.payment.provider.0.requestMethod", "GET"); diff --git a/build/preferences.js b/build/preferences.js new file mode 100644 index 0000000..8787a3b --- /dev/null +++ b/build/preferences.js @@ -0,0 +1,100 @@ + +'use strict'; + +function debug(msg) { + //dump('-*- preferences.js ' + msg + '\n'); +} + +const prefs = []; + +let homescreen = HOMESCREEN + (GAIA_PORT ? GAIA_PORT : ''); +prefs.push(["browser.manifestURL", homescreen + "/manifest.webapp"]); +if (homescreen.substring(0,6) == "app://") { // B2G bug 773884 + homescreen += "/index.html"; +} +prefs.push(["browser.homescreenURL", homescreen]); + +let domains = []; +domains.push(GAIA_DOMAIN); + +Gaia.webapps.forEach(function (webapp) { + domains.push(webapp.domain); +}); + +prefs.push(["network.http.max-connections-per-server", 15]); + +// for https://bugzilla.mozilla.org/show_bug.cgi?id=811605 to let user know what prefs is for ril debugging +prefs.push(["ril.debugging.enabled", false]); + +if (LOCAL_DOMAINS) { + prefs.push(["network.dns.localDomains", domains.join(",")]); +} + +if (DEBUG) { + prefs.push(["marionette.defaultPrefs.enabled", true]); + prefs.push(["b2g.remote-js.enabled", true]); + prefs.push(["b2g.remote-js.port", 4242]); + prefs.push(["javascript.options.showInConsole", true]); + prefs.push(["nglayout.debug.disable_xul_cache", true]); + prefs.push(["browser.dom.window.dump.enabled", true]); + prefs.push(["javascript.options.strict", true]); + prefs.push(["dom.report_all_js_exceptions", true]); + prefs.push(["nglayout.debug.disable_xul_fastload", true]); + prefs.push(["extensions.autoDisableScopes", 0]); + prefs.push(["browser.startup.homepage", homescreen]); + + prefs.push(["dom.mozBrowserFramesEnabled", true]); + prefs.push(["b2g.ignoreXFrameOptions", true]); + prefs.push(["dom.sms.enabled", true]); + prefs.push(["dom.mozContacts.enabled", true]); + prefs.push(["dom.mozSettings.enabled", true]); + prefs.push(["device.storage.enabled", true]); + prefs.push(["devtools.chrome.enabled", true]); + prefs.push(["webgl.verbose", true]); + + // Preferences for httpd + // (Use JSON.stringify in order to avoid taking care of `\` escaping) + prefs.push(["extensions.gaia.dir", GAIA_DIR]); + prefs.push(["extensions.gaia.domain", GAIA_DOMAIN]); + prefs.push(["extensions.gaia.port", parseInt(GAIA_PORT.replace(/:/g, ""))]); + prefs.push(["extensions.gaia.app_src_dirs", GAIA_APP_SRCDIRS]); + prefs.push(["extensions.gaia.locales_debug_path", GAIA_LOCALES_PATH]); + let appPathList = []; + Gaia.webapps.forEach(function (webapp) { + appPathList.push(webapp.sourceAppDirectoryName + '/' + + webapp.sourceDirectoryName); + }); + prefs.push(["extensions.gaia.app_relative_path", appPathList.join(' ')]); + + // Identity debug messages + prefs.push(["toolkit.identity.debug", true]); +} + +function writePrefs() { + let userJs = getFile(GAIA_DIR, 'profile', 'user.js'); + let content = prefs.map(function (entry) { + return 'user_pref("' + entry[0] + '", ' + JSON.stringify(entry[1]) + ');'; + }).join('\n'); + writeContent(userJs, content + "\n"); + debug("\n" + content); +} + +function setPrefs() { + prefs.forEach(function(entry) { + if (typeof entry[1] == "string") { + Services.prefs.setCharPref(entry[0], entry[1]); + } else if (typeof entry[1] == "boolean") { + Services.prefs.setBoolPref(entry[0], entry[1]); + } else if (typeof entry[1] == "number") { + Services.prefs.setIntPref(entry[0], entry[1]); + } else { + throw new Error("Unsupported pref type: " + typeof entry[1]); + } + }); +} + +if (Gaia.engine === "xpcshell") { + writePrefs(); +} else if (Gaia.engine === "b2g") { + setPrefs(); +} diff --git a/build/settings.py b/build/settings.py new file mode 100644 index 0000000..dd8c1b6 --- /dev/null +++ b/build/settings.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# +# This script generates the settings.json file which is stored into the b2g profile + +import base64 +import json +import optparse +import os +import sys + +settings = { + "accessibility.invert": False, + "accessibility.screenreader": False, + "alarm.enabled": False, + "alert-sound.enabled": True, + "alert-vibration.enabled": True, + "app.reportCrashes": "ask", + "app.update.interval": 86400, + "audio.volume.alarm": 15, + "audio.volume.bt_sco": 15, + "audio.volume.dtmf": 15, + "audio.volume.content": 15, + "audio.volume.notification": 15, + "audio.volume.tts": 15, + "audio.volume.telephony": 5, + "bluetooth.enabled": False, + "bluetooth.debugging.enabled": False, + "camera.shutter.enabled": True, + "clear.remote-windows.data": False, + "debug.grid.enabled": False, + "debug.oop.disabled": False, + "debug.fps.enabled": False, + "debug.ttl.enabled": False, + "debug.log-animations.enabled": False, + "debug.paint-flashing.enabled": False, + "debug.peformancedata.shared": False, + "deviceinfo.firmware_revision": "", + "deviceinfo.hardware": "", + "deviceinfo.os": "", + "deviceinfo.platform_build_id": "", + "deviceinfo.platform_version": "", + "deviceinfo.software": "", + "deviceinfo.update_channel": "", + "gaia.system.checkForUpdates": False, + "geolocation.enabled": True, + "keyboard.layouts.english": True, + "keyboard.layouts.dvorak": False, + "keyboard.layouts.otherlatins": False, + "keyboard.layouts.cyrillic": False, + "keyboard.layouts.arabic": False, + "keyboard.layouts.hebrew": False, + "keyboard.layouts.zhuyin": False, + "keyboard.layouts.pinyin": False, + "keyboard.layouts.greek": False, + "keyboard.layouts.japanese": False, + "keyboard.layouts.portuguese": False, + "keyboard.layouts.spanish": False, + "keyboard.vibration": False, + "keyboard.clicksound": False, + "keyboard.wordsuggestion": False, + "keyboard.current": "en", + "language.current": "en-US", + "lockscreen.passcode-lock.code": "0000", + "lockscreen.passcode-lock.timeout": 0, + "lockscreen.passcode-lock.enabled": False, + "lockscreen.notifications-preview.enabled": True, + "lockscreen.enabled": True, + "lockscreen.locked": True, + "lockscreen.unlock-sound.enabled": False, + "mail.sent-sound.enabled": True, + "operatorvariant.mcc": 0, + "operatorvariant.mnc": 0, + "ril.iccInfo.mbdn":"", + "ril.sms.strict7BitEncoding.enabled": False, + "ril.cellbroadcast.searchlist": "", + "debug.console.enabled": False, + "phone.ring.keypad": True, + "powersave.enabled": False, + "powersave.threshold": 0, + "privacy.donottrackheader.enabled": False, + "ril.callwaiting.enabled": None, + "ril.cf.enabled": False, + "ril.data.enabled": False, + "ril.data.apn": "", + "ril.data.carrier": "", + "ril.data.passwd": "", + "ril.data.httpProxyHost": "", + "ril.data.httpProxyPort": 0, + "ril.data.mmsc": "", + "ril.data.mmsproxy": "", + "ril.data.mmsport": 0, + "ril.data.roaming_enabled": False, + "ril.data.user": "", + "ril.mms.apn": "", + "ril.mms.carrier": "", + "ril.mms.httpProxyHost": "", + "ril.mms.httpProxyPort": "", + "ril.mms.mmsc": "", + "ril.mms.mmsport": "", + "ril.mms.mmsproxy": "", + "ril.mms.passwd": "", + "ril.mms.user": "", + "ril.radio.preferredNetworkType": "", + "ril.radio.disabled": False, + "ril.supl.apn": "", + "ril.supl.carrier": "", + "ril.supl.httpProxyHost": "", + "ril.supl.httpProxyPort": "", + "ril.supl.passwd": "", + "ril.supl.user": "", + "ril.sms.strict7BitEncoding.enabled": False, + "ring.enabled": True, + "screen.automatic-brightness": True, + "screen.brightness": 1, + "screen.timeout": 60, + "tethering.usb.enabled": False, + "tethering.usb.ip": "192.168.0.1", + "tethering.usb.prefix": "24", + "tethering.usb.dhcpserver.startip": "192.168.0.10", + "tethering.usb.dhcpserver.endip": "192.168.0.30", + "tethering.wifi.enabled": False, + "tethering.wifi.ip": "192.168.1.1", + "tethering.wifi.prefix": "24", + "tethering.wifi.dhcpserver.startip": "192.168.1.10", + "tethering.wifi.dhcpserver.endip": "192.168.1.30", + "tethering.wifi.ssid": "FirefoxHotspot", + "tethering.wifi.security.type": "open", + "tethering.wifi.security.password": "1234567890", + "tethering.wifi.connectedClients": 0, + "tethering.usb.connectedClients": 0, + "time.nitz.automatic-update.enabled": True, + "time.timezone": None, + "ums.enabled": False, + "ums.mode": 0, + "vibration.enabled": True, + "wifi.enabled": True, + "wifi.disabled_by_wakelock": False, + "wifi.notification": False, + "icc.displayTextTimeout": 40000, + "icc.inputTextTimeout": 40000 +} + +def main(): + parser = optparse.OptionParser(description="Generate initial settings.json file") + parser.add_option( "--override", help="JSON files for custom settings overrides") + parser.add_option( "--homescreen", help="specify the homescreen URL") + parser.add_option( "--ftu", help="specify the ftu manifest URL") + parser.add_option("-c", "--console", help="indicate if the console should be enabled", action="store_true") + parser.add_option("-o", "--output", help="specify the name of the output file") + parser.add_option("-w", "--wallpaper", help="specify the name of the wallpaper file") + parser.add_option("-v", "--verbose", help="increase output verbosity", action="store_true") + parser.add_option( "--noftu", help="bypass the ftu app", action="store_true") + parser.add_option( "--locale", help="specify the default locale to use") + parser.add_option( "--enable-debugger", help="enable remote debugger (and ADB for VARIANT=user builds)", action="store_true") + (options, args) = parser.parse_args(sys.argv[1:]) + + verbose = options.verbose + + if options.homescreen: + homescreen_url = options.homescreen + else: + homescreen_url = "app://homescreen.gaiamobile.org/manifest.webapp" + + if options.ftu: + ftu_url = options.ftu + else: + ftu_url = "app://communications.gaiamobile.org/manifest.webapp" + + if options.output: + settings_filename = options.output + else: + settings_filename = "profile/settings.json" + + if options.wallpaper: + wallpaper_filename = options.wallpaper + else: + wallpaper_filename = "build/wallpaper.jpg" + + enable_debugger = (options.enable_debugger == True) + + if verbose: + print "Console:", options.console + print "Homescreen URL:", homescreen_url + print "Ftu URL:", ftu_url + print "Setting Filename:",settings_filename + print "Wallpaper Filename:", wallpaper_filename + print "Enable Debugger:", enable_debugger + + # Set the default console output + if options.console: + settings["debug.console.enabled"] = options.console + + # Set the homescreen URL + settings["homescreen.manifestURL"] = homescreen_url + + # Set the ftu manifest URL + if not options.noftu: + settings["ftu.manifestURL"] = ftu_url + + # Set the default locale + if options.locale: + settings["language.current"] = options.locale + + settings["devtools.debugger.remote-enabled"] = enable_debugger; + + # Grab wallpaper.jpg and convert it into a base64 string + wallpaper_file = open(wallpaper_filename, "rb") + wallpaper_base64 = base64.b64encode(wallpaper_file.read()) + settings["wallpaper.image"] = "data:image/jpeg;base64," + wallpaper_base64.decode("utf-8") + + # Grab ringer_classic_courier.opus and convert it into a base64 string + ringtone_name = "shared/resources/media/ringtones/ringer_classic_courier.opus" + ringtone_file = open(ringtone_name, "rb"); + ringtone_base64 = base64.b64encode(ringtone_file.read()) + settings["dialer.ringtone"] = "data:audio/ogg;base64," + ringtone_base64.decode("utf-8") + settings["dialer.ringtone.name"] = "ringer_classic_courier.opus" + + # Grab notifier_bell.opus and convert it into a base64 string + notification_name = "shared/resources/media/notifications/notifier_bell.opus" + notification_file = open(notification_name, "rb"); + notification_base64 = base64.b64encode(notification_file.read()) + settings["notification.ringtone"] = "data:audio/ogg;base64," + notification_base64.decode("utf-8") + settings["notification.ringtone.name"] = "notifier_bell.opus" + + if options.override and os.path.exists(options.override): + try: + overrides = json.load(open(options.override)) + for key, val in overrides.items(): + settings[key] = val + except Exception, e: + print "Error while applying override setting file: %s\n%s" % (options.override, e) + + json.dump(settings, open(settings_filename, "wb")) + +if __name__ == "__main__": + main() diff --git a/build/ua-override-prefs.js b/build/ua-override-prefs.js new file mode 100644 index 0000000..af5c354 --- /dev/null +++ b/build/ua-override-prefs.js @@ -0,0 +1,102 @@ + +// Send these sites a custom user-agent. Bugs to remove each override after +// evangelism are included. +pref("general.useragent.override.facebook.com", "\(Mobile#(Android; Mobile"); // bug 827635 +pref("general.useragent.override.youtube.com", "\(Mobile#(Android; Mobile"); // bug 827636 +pref("general.useragent.override.yelp.com", "\(Mobile#(Android; Mobile"); // bug 799884 +pref("general.useragent.override.dailymotion.com", "\(Mobile#(Android; Mobile"); // bug 827638 +pref("general.useragent.override.accounts.google.com", "\(Mobile#(Android; Mobile"); // bug 805164 +pref("general.useragent.override.maps.google.com", "\(Mobile#(Android; Mobile"); // bug 802981 +pref("general.useragent.override.uol.com.br", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 826330 +pref("general.useragent.override.live.com", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 826332 +pref("general.useragent.override.globo.com", "\(Mobile#(Android; Mobile"); // bug 826335 +pref("general.useragent.override.yahoo.com", "\(Mobile#(Android; Mobile"); // bug 826338 +pref("general.useragent.override.mercadolivre.com.br", "\(Mobile#(Android; Mobile"); // bug 826342 +pref("general.useragent.override.ig.com.br", "\(Mobile#(Android; Mobile"); // bug 826343 +pref("general.useragent.override.abril.com.br", "\(Mobile#(Android; Mobile"); // bug 826344 +pref("general.useragent.override.msn.com", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 826347 +pref("general.useragent.override.linkedin.com", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 826348 +pref("general.useragent.override.itau.com.br", "\(Mobile#(Android; Mobile"); // bug 826353 +pref("general.useragent.override.tumblr.com", "\(Mobile#(Android; Mobile"); // bug 826361 +pref("general.useragent.override.4shared.com", "\(Mobile#(Android; Mobile"); // bug 826502 +pref("general.useragent.override.orkut.com.br", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 826504 +pref("general.useragent.override.r7.com", "\(Mobile#(Android; Mobile"); // bug 826510 +pref("general.useragent.override.amazon.com", "\(Mobile#(Android; Mobile"); // bug 826512 +pref("general.useragent.override.estadao.com.br", "\(Mobile#(Android; Mobile"); // bug 826514 +pref("general.useragent.override.letras.mus.br", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 826517 +pref("general.useragent.override.bb.com.br", "\(Mobile#(Android; Mobile"); // bug 826711 +pref("general.useragent.override.orkut.com", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 826712 +pref("general.useragent.override.noticias.uol.com.br", "\(Mobile#(Android; Mobile"); // bug 826715 +pref("general.useragent.override.olx.com.br", "\(Mobile#(Android; Mobile"); // bug 826720 +pref("general.useragent.override.bancobrasil.com.br", "\(Mobile#(Android; Mobile"); // bug 826736 +pref("general.useragent.override.techtudo.com.br", "\(Mobile#(Android; Mobile"); // bug 826845 +pref("general.useragent.override.clickjogos.uol.com.br", "\(Mobile#(Android; Mobile"); // bug 826949 +pref("general.useragent.override.ebay.com", "\(Mobile#(Android; Mobile");// bug 826958 +pref("general.useragent.override.bing.com", "\(Mobile#(Android; Mobile"); // bug 827622 +pref("general.useragent.override.tam.com.br", "\(Mobile#(Android; Mobile"); // bug 827623 +pref("general.useragent.override.pontofrio.com.br", "\(Mobile#(Android; Mobile"); // bug 827624 +pref("general.useragent.override.pagseguro.uol.com.br", "\(Mobile#(Android; Mobile"); // bug 827625 +pref("general.useragent.override.magazineluiza.com.br", "\(Mobile#(Android; Mobile"); // bug 827626 +pref("general.useragent.override.bol.uol.com.br", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 827627 +pref("general.useragent.override.groupon.com.br", "\(Mobile#(Android; Mobile"); // bug 827628 +pref("general.useragent.override.vagalume.com.br", "\(Mobile#(Android; Mobile"); // bug 827630 +pref("general.useragent.override.climatempo.com.br", "\(Mobile#(Android; Mobile"); // bug 827631 +pref("general.useragent.override.tecmundo.com.br", "\(Mobile#(Android; Mobile"); // bug 827632 +pref("general.useragent.override.hao123.com", "\(Mobile#(Android; Mobile"); // bug 827633 +pref("general.useragent.override.imdb.com", "\(Mobile#(Android; Mobile"); // bug 827634 +pref("general.useragent.override.lancenet.com.br", "\(Mobile#(Android; Mobile"); // bug 827576 +pref("general.useragent.override.webmotors.com.br", "\(Mobile#(Android; Mobile"); // bug 827573 +pref("general.useragent.override.mercadolibre.com.co", "\(Mobile#(Android; Mobile"); // bug 827661 +pref("general.useragent.override.elespectador.com", "\(Mobile#(Android; Mobile"); // bug 827664 +pref("general.useragent.override.slideshare.net", "\(Mobile#(Android; Mobile"); // bug 827666 +pref("general.useragent.override.scribd.com", "\(Mobile#(Android; Mobile"); // bug 827668 +pref("general.useragent.override.elpais.com.co", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 827670 +pref("general.useragent.override.olx.com.co", "\(Mobile#(Android; Mobile"); // bug 827672 +pref("general.useragent.override.avianca.com", "\(Mobile#(Android; Mobile"); // bug 827674 +pref("general.useragent.override.dropbox.com", "\(Mobile#(Android; Mobile"); // bug 827676 +pref("general.useragent.override.marca.com", "\(Mobile#(Android; Mobile"); // bug 827678 +pref("general.useragent.override.wp.pl", "\(Mobile#(Android; Mobile"); // bug 828351 +pref("general.useragent.override.gazeta.pl", "\(Mobile#(Android; Mobile"); // bug 828354 +pref("general.useragent.override.o2.pl", "\(Mobile#(Android; Mobile"); // bug 828356 +pref("general.useragent.override.ceneo.pl", "\(Mobile#(Android; Mobile"); // bug 828358 +pref("general.useragent.override.sport.pl", "\(Mobile#(Android; Mobile"); // bug 828360 +pref("general.useragent.override.tvn24.pl", "\(Mobile#(Android; Mobile"); // bug 828362 +pref("general.useragent.override.nk.pl", "\(Mobile#(Android; Mobile"); // bug 828364 +pref("general.useragent.override.wyborcza.biz", "\(Mobile#(Android; Mobile"); // bug 828366 +pref("general.useragent.override.money.pl", "\(Mobile#(Android; Mobile"); // bug 828369 +pref("general.useragent.override.ingbank.pl", "\(Mobile#(Android; Mobile"); // bug 828371 +pref("general.useragent.override.tablica.pl", "\(Mobile#(Android; Mobile"); // bug 828374 +pref("general.useragent.override.plotek.pl", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 828376 +pref("general.useragent.override.wyborcza.pl", "\(Mobile#(Android; Mobile"); // bug 828378 +pref("general.useragent.override.deser.pl", "\(Mobile#(Android; Mobile"); // bug 828380 +pref("general.useragent.override.as.com", "\(Mobile#(Android; Mobile"); // bug 828383 +pref("general.useragent.override.ebay.es", "\(Mobile#(Android; Mobile"); // bug 828386 +pref("general.useragent.override.amazon.es", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 828388 +pref("general.useragent.override.20minutos.es", "\(Mobile#(Android; Mobile"); // bug 828390 +pref("general.useragent.override.infojobs.net", "\(Mobile#(Android; Mobile"); // bug 828392 +pref("general.useragent.override.vimeo.com", "\(Mobile#(Android; Mobile"); // bug 828394 +pref("general.useragent.override.elconfidencial.com", "\(Mobile#(Android; Mobile"); // bug 828397 +pref("general.useragent.override.antena3.com", "\(Mobile#(Android; Mobile"); // bug 828399 +pref("general.useragent.override.ingdirect.es", "\(Mobile#(Android; Mobile"); // bug 828401 +pref("general.useragent.override.fotocasa.es", "\(Mobile#(Android; Mobile"); // bug 828403 +pref("general.useragent.override.orange.es", "\(Mobile#(Android; Mobile"); // bug 828406 +pref("general.useragent.override.stackoverflow.com", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 828408 +pref("general.useragent.override.amazon.co.uk", "\(Mobile#(Android; Mobile"); // bug 828412 +pref("general.useragent.override.paginasamarillas.es", "\(Mobile#(Android; Mobile"); // bug 828414 +pref("general.useragent.override.loteriasyapuestas.es", "\(Mobile#(Android; Mobile"); // bug 828416 +pref("general.useragent.override.bbva.es", "\(Mobile#(Android; Mobile"); // bug 828418 +pref("general.useragent.override.booking.com", "\(Mobile#(Android; Mobile"); // bug 828420 +pref("general.useragent.override.publico.es", "\(Mobile#(Android; Mobile"); // bug 828422 +pref("general.useragent.override.mercadolibre.com.ve", "\(Mobile#(Android; Mobile"); // bug 828425 +pref("general.useragent.override.lapatilla.com", "\(Mobile#(Android; Mobile"); // bug 828428 +pref("general.useragent.override.meridiano.com.ve", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); // bug 828430 +pref("general.useragent.override.espn.go.com", "\(Mobile#(Android; Mobile"); // bug 828431 +pref("general.useragent.override.olx.com.ve", "\(Mobile#(Android; Mobile"); // bug 828433 +pref("general.useragent.override.rincondelvago.com", "\(Mobile#(Android; Mobile"); // bug 828435 +pref("general.useragent.override.avn.info.ve", "\(Mobile#(Android; Mobile"); // bug 828437 +pref("general.useragent.override.movistar.com.ve", "\(Mobile#(Android; Mobile"); // bug 828439 +pref("general.useragent.override.laverdad.com", "\(Mobile#(Android; Mobile"); // bug 828441 +pref("general.useragent.override.despegar.com.ve", "\(Mobile#(Android; Mobile"); // bug 828443 +pref("general.useragent.override.bumeran.com.ve", "\(Mobile#(Android; Mobile"); // bug 828445 +pref("general.useragent.override.petardas.com", "\(Mobile#(Android; Mobile"); // bug 828448 +pref("general.useragent.override.mail.google.com", "\(Mobile#(Android; Mobile"); // bug 827869 diff --git a/build/utils.js b/build/utils.js new file mode 100644 index 0000000..5b7a58b --- /dev/null +++ b/build/utils.js @@ -0,0 +1,222 @@ +const { 'classes': Cc, 'interfaces': Ci, 'results': Cr, 'utils': Cu, + 'Constructor': CC } = Components; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/FileUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +function isSubjectToBranding(path) { + return /shared[\/\\][a-zA-Z]+[\/\\]branding$/.test(path) || + /branding[\/\\]initlogo.png/.test(path); +} + +function getSubDirectories(directory) { + let appsDir = new FileUtils.File(GAIA_DIR); + appsDir.append(directory); + + let dirs = []; + let files = appsDir.directoryEntries; + while (files.hasMoreElements()) { + let file = files.getNext().QueryInterface(Ci.nsILocalFile); + if (file.isDirectory()) { + dirs.push(file.leafName); + } + } + return dirs; +} + +/** + * Returns an array of nsIFile's for a given directory + * + * @param {nsIFile} dir directory to read. + * @param {boolean} recursive set to true in order to walk recursively. + * @param {RegExp} exclude optional filter to exclude file/directories. + * + * @return {Array} list of nsIFile's. + */ +function ls(dir, recursive, exclude) { + let results = []; + let files = dir.directoryEntries; + while (files.hasMoreElements()) { + let file = files.getNext().QueryInterface(Ci.nsILocalFile); + if (!exclude || !exclude.test(file.leafName)) { + results.push(file); + if (recursive && file.isDirectory()) { + results = results.concat(ls(file, true, exclude)); + } + } + } + return results; +} + +function getFileContent(file) { + try { + let fileStream = Cc['@mozilla.org/network/file-input-stream;1'] + .createInstance(Ci.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + + let converterStream = Cc['@mozilla.org/intl/converter-input-stream;1'] + .createInstance(Ci.nsIConverterInputStream); + converterStream.init(fileStream, 'utf-8', fileStream.available(), + Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + let out = {}; + let count = fileStream.available(); + converterStream.readString(count, out); + + var content = out.value; + converterStream.close(); + fileStream.close(); + } catch (e) { + let msg = (file && file.path) ? '\nfile not found: ' + file.path : ''; + throw new Error(' -*- build/utils.js: ' + e + msg + '\n'); + } + return content; +} + +function writeContent(file, content) { + var fileStream = Cc['@mozilla.org/network/file-output-stream;1'] + .createInstance(Ci.nsIFileOutputStream); + fileStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0); + + let converterStream = Cc['@mozilla.org/intl/converter-output-stream;1'] + .createInstance(Ci.nsIConverterOutputStream); + + converterStream.init(fileStream, 'utf-8', 0, 0); + converterStream.writeString(content); + converterStream.close(); +} + +// Return an nsIFile by joining paths given as arguments +// First path has to be an absolute one +function getFile() { + try { + let file = new FileUtils.File(arguments[0]); + if (arguments.length > 1) { + for (let i = 1; i < arguments.length; i++) { + file.append(arguments[i]); + } + } + return file; + } catch(e) { + throw new Error(' -*- build/utils.js: Invalid file path (' + + Array.slice(arguments).join(', ') + ')\n' + e + '\n'); + } +} + +function ensureFolderExists(file) { + if (!file.exists()) { + try { + file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0755', 8)); + } catch (e if e.result == Cr.NS_ERROR_FILE_ALREADY_EXISTS) { + // Bug 808513: Ignore races between `if exists() then create()`. + return; + } + } +} + +function getJSON(file) { + try { + let content = getFileContent(file); + return JSON.parse(content); + } catch (e) { + dump('Invalid JSON file : ' + file.path + '\n'); + throw e; + } +} + +function makeWebappsObject(dirs) { + return { + forEach: function(fun) { + let appSrcDirs = dirs.split(' '); + appSrcDirs.forEach(function parseDirectory(directoryName) { + let directories = getSubDirectories(directoryName); + directories.forEach(function readManifests(dir) { + let manifestFile = getFile(GAIA_DIR, directoryName, dir, + 'manifest.webapp'); + let updateFile = getFile(GAIA_DIR, directoryName, dir, + 'update.webapp'); + // Ignore directories without manifest + if (!manifestFile.exists() && !updateFile.exists()) { + return; + } + + let manifest = manifestFile.exists() ? manifestFile : updateFile; + let domain = dir + '.' + GAIA_DOMAIN; + + let webapp = { + manifest: getJSON(manifest), + manifestFile: manifest, + url: GAIA_SCHEME + domain + (GAIA_PORT ? GAIA_PORT : ''), + domain: domain, + sourceDirectoryFile: manifestFile.parent, + sourceDirectoryName: dir, + sourceAppDirectoryName: directoryName + }; + + // External webapps have a `metadata.json` file + let metaData = webapp.sourceDirectoryFile.clone(); + metaData.append('metadata.json'); + if (metaData.exists()) { + webapp.metaData = getJSON(metaData); + } + + fun(webapp); + }); + }); + } + }; +} + +let externalAppsDirs = ['external-apps']; + +// External apps are built differently from other apps by webapp-manifests.js, +// and we need apps that are both external and dogfood to be treated like +// external apps (to properly test external apps on dogfood devices), so we +// segregate them into their own directory that we add to the list of external +// apps dirs here when building a dogfood profile. +if (DOGFOOD === '1') { + externalAppsDirs.push('external-dogfood-apps'); +} + +const Gaia = { + engine: GAIA_ENGINE, + sharedFolder: getFile(GAIA_DIR, 'shared'), + webapps: makeWebappsObject(GAIA_APP_SRCDIRS), + externalWebapps: makeWebappsObject(externalAppsDirs.join(' ')), + aggregatePrefix: 'gaia_build_' +}; + +function registerProfileDirectory() { + let directoryProvider = { + getFile: function provider_getFile(prop, persistent) { + persistent.value = true; + if (prop != 'ProfD' && prop != 'ProfLDS') { + throw Cr.NS_ERROR_FAILURE; + } + + return new FileUtils.File(PROFILE_DIR); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider, + Ci.nsISupports]) + }; + + Cc['@mozilla.org/file/directory_service;1'] + .getService(Ci.nsIProperties) + .QueryInterface(Ci.nsIDirectoryService) + .registerProvider(directoryProvider); +} + +if (Gaia.engine === 'xpcshell') { + registerProfileDirectory(); +} + +function gaiaOriginURL(name) { + return GAIA_SCHEME + name + '.' + GAIA_DOMAIN + (GAIA_PORT ? GAIA_PORT : ''); +} + +function gaiaManifestURL(name) { + return gaiaOriginURL(name) + '/manifest.webapp'; +} + diff --git a/build/wallpaper.jpg b/build/wallpaper.jpg Binary files differnew file mode 100644 index 0000000..d60fc2e --- /dev/null +++ b/build/wallpaper.jpg diff --git a/build/webapp-manifests.js b/build/webapp-manifests.js new file mode 100644 index 0000000..6bd79b9 --- /dev/null +++ b/build/webapp-manifests.js @@ -0,0 +1,179 @@ +const INSTALL_TIME = 132333986000; // Match this to value in applications-data.js + +function debug(msg) { + //dump('-*- webapp-manifest.js: ' + msg + '\n'); +} + +let webappsTargetDir = Cc['@mozilla.org/file/local;1'] + .createInstance(Ci.nsILocalFile); +webappsTargetDir.initWithPath(PROFILE_DIR); +// Create profile folder if doesn't exists +if (!webappsTargetDir.exists()) + webappsTargetDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0755', 8)); +// Create webapps folder if doesn't exists +webappsTargetDir.append('webapps'); +if (!webappsTargetDir.exists()) + webappsTargetDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0755', 8)); + +let manifests = {}; +let id = 1; + +function copyRec(source, target) { + let results = []; + let files = source.directoryEntries; + if (!target.exists()) + target.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0755', 8)); + + while (files.hasMoreElements()) { + let file = files.getNext().QueryInterface(Ci.nsILocalFile); + if (file.isDirectory()) { + let subFolder = target.clone(); + subFolder.append(file.leafName); + copyRec(file, subFolder); + } else { + file.copyTo(target, file.leafName); + } + } +} + +// Returns the nsIPrincipal compliant integer +// from the "type" property in manifests. +function getAppStatus(status) { + let appStatus = 1; // By default, apps are installed + switch (status) { + case "certified": + appStatus = 3; + break; + case "privileged": + appStatus = 2; + break; + case "web": + default: + appStatus = 1; + break; + } + return appStatus; +} + +Gaia.webapps.forEach(function (webapp) { + // If BUILD_APP_NAME isn't `*`, we only accept one webapp + if (BUILD_APP_NAME != '*' && webapp.sourceDirectoryName != BUILD_APP_NAME) + return; + + // Compute webapp folder name in profile + let webappTargetDirName = webapp.domain; + + // Copy webapp's manifest to the profile + let webappTargetDir = webappsTargetDir.clone(); + webappTargetDir.append(webappTargetDirName); + webapp.manifestFile.copyTo(webappTargetDir, 'manifest.webapp'); + + // Add webapp's entry to the webapps global manifest. + // appStatus == 3 means this is a certified app. + // appStatus == 2 means this is a privileged app. + // appStatus == 1 means this is an installed (unprivileged) app + + let url = webapp.url; + manifests[webappTargetDirName] = { + origin: url, + installOrigin: url, + receipt: null, + installTime: INSTALL_TIME, + manifestURL: url + '/manifest.webapp', + appStatus: getAppStatus(webapp.manifest.type), + localId: id++ + }; + +}); + +// Process external webapps from /gaia/external-app/ folder +Gaia.externalWebapps.forEach(function (webapp) { + // If BUILD_APP_NAME isn't `*`, we only accept one webapp + if (BUILD_APP_NAME != '*' && webapp.sourceDirectoryName != BUILD_APP_NAME) + return; + + if (!webapp.metaData) { + return; + } + + // Compute webapp folder name in profile + let webappTargetDirName = webapp.sourceDirectoryName; + + // Copy webapp's manifest to the profile + let webappTargetDir = webappsTargetDir.clone(); + webappTargetDir.append(webappTargetDirName); + + let origin; + let installOrigin; + let manifestURL; + + let removable; + + // In case of packaged app, just copy `application.zip` and `update.webapp` + let appPackage = webapp.sourceDirectoryFile.clone(); + appPackage.append('application.zip'); + if (appPackage.exists()) { + let updateManifest = webapp.sourceDirectoryFile.clone(); + updateManifest.append('update.webapp'); + if (!updateManifest.exists()) { + throw new Error('External packaged webapp `' + webapp.domain + ' is ' + + 'missing an `update.webapp` file. This JSON file ' + + 'contains a `package_path` attribute specifying where ' + + 'to download the application zip package from the origin ' + + 'specified in `metadata.json` file.'); + } + appPackage.copyTo(webappTargetDir, 'application.zip'); + updateManifest.copyTo(webappTargetDir, 'update.webapp'); + removable = true; + origin = webapp.metaData.origin; + installOrigin = webapp.metaData.installOrigin; + manifestURL = webapp.metaData.manifestURL; + } else { + webapp.manifestFile.copyTo(webappTargetDir, 'manifest.webapp'); + origin = webapp.metaData.origin; + installOrigin = webapp.metaData.origin; + manifestURL = webapp.metaData.origin + 'manifest.webapp'; + + // This is an hosted app. Check if there is an offline cache. + let srcCacheFolder = webapp.sourceDirectoryFile.clone(); + srcCacheFolder.append('cache'); + if (srcCacheFolder.exists()) { + let cacheManifest = srcCacheFolder.clone(); + cacheManifest.append('manifest.appcache'); + if (!cacheManifest.exists()) + throw new Error('External webapp `' + webapp.domain + '` has a cache ' + + 'directory without `manifest.appcache` file.'); + + // Copy recursively the whole cache folder to webapp folder + let targetCacheFolder = webappTargetDir.clone(); + targetCacheFolder.append('cache'); + copyRec(srcCacheFolder, targetCacheFolder); + } + } + + let etag = webapp.metaData.etag || null; + let packageEtag = webapp.metaData.packageEtag || null; + + // Add webapp's entry to the webapps global manifest + manifests[webappTargetDirName] = { + origin: origin, + installOrigin: installOrigin, + receipt: null, + installTime: 132333986000, + manifestURL: manifestURL, + removable: removable, + localId: id++, + etag: etag, + packageEtag: packageEtag, + appStatus: getAppStatus(webapp.metaData.type || "web"), + }; + +}); + +// Write webapps global manifest +let manifestFile = webappsTargetDir.clone(); +manifestFile.append('webapps.json'); + +// stringify json with 2 spaces indentation +writeContent(manifestFile, JSON.stringify(manifests, null, 2) + '\n'); + diff --git a/build/webapp-optimize.js b/build/webapp-optimize.js new file mode 100644 index 0000000..8258e3d --- /dev/null +++ b/build/webapp-optimize.js @@ -0,0 +1,386 @@ + +function debug(str) { + //dump(' -*- webapp-optimize.js: ' + str + '\n'); +} + + +/** + * Expose a global `win' object and load `l10n.js' in it -- + * note: the `?reload' trick ensures we don't load a cached `l10njs' library. + */ + +var win = { navigator: {} }; +Services.scriptloader.loadSubScript('file:///' + GAIA_DIR + + '/shared/js/l10n.js?reload=' + new Date().getTime(), win); + + +/** + * Locale list -- by default, only the default one + */ + +var l10nLocales = [GAIA_DEFAULT_LOCALE]; +var l10nDictionary = { + locales: {}, + default_locale: GAIA_DEFAULT_LOCALE +}; +l10nDictionary.locales[GAIA_DEFAULT_LOCALE] = {}; + +/** + * whitelist by app name for javascript asset aggregation. + */ +const JS_AGGREGATION_WHITELIST = [ + 'calendar' +]; + +/** + * Helpers + */ + +function optimize_getFileContent(webapp, htmlFile, relativePath) { + let paths = relativePath.split('/'); + let file; + + // get starting directory: webapp root, HTML file or /shared/ + if (/^\//.test(relativePath)) { + paths.shift(); + file = webapp.sourceDirectoryFile.clone(); + } else { + file = htmlFile.parent.clone(); + } + if (paths[0] == 'shared') { + file = getFile(GAIA_DIR); + } + + paths.forEach(function appendPath(name) { + file.append(name); + if (isSubjectToBranding(file.path)) { + file.append((OFFICIAL == 1) ? 'official' : 'unofficial'); + } + }); + + try { + return getFileContent(file); + } catch (e) { + dump(file.path + ' could not be found.\n'); + return ''; + } +} + +/** + * Aggregates javascript files by type to reduce the IO overhead. + * Depending on the script tags there are two files made: + * + * - defered scripts (<script defer src= ...) : + * $(Gaia.aggregatePrefix)defer_$(html_filename).js + * + * - normal scripts (<script src=...) : + * $(Gaia.aggregatePrefix)$(html_filename).js + * + * + * Also it is possible to skip aggregation on a per script basis: + * + * <script src="..." data-skip-optimize defer></script> + * + * + * This function is somewhat conservative about what it will aggregate and will + * only group scripts found the documents <head> section. + * + * @param {HTMLDocument} doc DOM document of the file. + * @param {Object} webapp details of current web app. + * @param {NSFile} htmlFile filename/path of the document. + */ +function optimize_aggregateJsResources(doc, webapp, htmlFile) { + // Everyone should be putting their scripts in head with defer. + // The best case is that only l10n.js is put into a normal. + let scripts = Array.slice( + doc.head.querySelectorAll('script[src]') + ); + + let deferred = { + prefix: 'defer_', + content: '', + lastNode: null + }; + + let normal = { + prefix: '', + content: '', + lastNode: null + }; + + scripts.forEach(function(script, idx) { + let html = script.outerHTML; + + // per-script out see comment in function header. + if ('skipOptimize' in script.dataset) { + // remove from scripts so it will not be commented out... + debug( + '[optimize ' + webapp.sourceDirectoryName + '] ' + + 'skipping script "' + html + '"' + ); + scripts.splice(idx, 1); + return; + } + + // we inject the whole outerHTML into the comment for debugging so + // if there is something valuable in the html that effects the script + // that broke the app it should be fairly easy to tell what happened. + let content = '; /* "' + html + ' "*/\n\n'; + + // fetch the whole file append it to the comment. + content += optimize_getFileContent(webapp, htmlFile, script.src); + + let config = normal; + + if (script.defer) + config = deferred; + + config.content += content; + config.lastNode = script; + + // some apps (email) use version in the script types + // (text/javascript;version=x). + // + // If we don't have the same version in the aggregate the + // app will not load correctly. + if (script.type.indexOf('version') !== -1) { + config.type = script.type; + } + }); + + // root name like index or oncall, etc... + let baseName = htmlFile.path.split('/').pop().split('.')[0]; + + // used as basis for aggregated scripts... + let rootDirectory = htmlFile.parent; + + // find the absolute root of the app's html file. + let rootUrl = htmlFile.parent.path; + rootUrl = rootUrl.replace(webapp.manifestFile.parent.path, ''); + // the above will yield something like: '', '/facebook/', '/contacts/', etc... + + function writeAggregatedScript(config) { + // skip if we don't have any content to write. + if (!config.content) + return; + + // prefix the file we are about to write content to. + let scriptBaseName = + Gaia.aggregatePrefix + config.prefix + baseName + '.js'; + + let target = rootDirectory.clone(); + target.append(scriptBaseName); + + debug('writing aggregated source file: ' + target.path); + + // write the contents of the aggregated script + writeContent(target, config.content); + + let script = doc.createElement('script'); + let lastScript = config.lastNode; + + script.src = rootUrl + '/' + scriptBaseName; + script.defer = lastScript.defer; + // use the config's type if given (for text/javascript;version=x) + script.type = config.type || lastScript.type; + + debug('writing to path="' + target.path + '" src="' + script.src + '"'); + + // insert after the last script node of this type... + let parent = lastScript.parentNode; + parent.insertBefore(script, lastScript.nextSibling); + } + + writeAggregatedScript(deferred); + writeAggregatedScript(normal); + + function commentScript(script) { + script.outerHTML = '<!-- ' + script.outerHTML + ' -->'; + } + + // comment out all scripts + scripts.forEach(commentScript); +} + +function optimize_embedl10nResources(doc, dictionary) { + // remove all external l10n resource nodes + var resources = doc.querySelectorAll('link[type="application/l10n"]'); + for (let i = 0; i < resources.length; i++) { + let res = resources[i].outerHTML; + resources[i].outerHTML = '<!-- ' + res + ' -->'; + } + + // put the current dictionary in an inline JSON script + let script = doc.createElement('script'); + script.type = 'application/l10n'; + script.innerHTML = '\n ' + JSON.stringify(dictionary) + '\n'; + doc.documentElement.appendChild(script); +} + +function optimize_serializeHTMLDocument(doc, file) { + debug('saving: ' + file.path); + + // the doctype string should always be '<!DOCTYPE html>' but just in case... + let doctypeStr = ''; + let dt = doc.doctype; + if (dt && dt.name) { + doctypeStr = '<!DOCTYPE ' + dt.name; + if (dt.publicId) { + doctypeStr += ' PUBLIC ' + dt.publicId; + } + if (dt.systemId) { + doctypeStr += ' ' + dt.systemId; + } + doctypeStr += '>\n'; + } + + // outerHTML breaks the formating, so let's use innerHTML instead + let htmlStr = '<html'; + let docElt = doc.documentElement; + let attrs = docElt.attributes; + for (let i = 0; i < attrs.length; i++) { + htmlStr += ' ' + attrs[i].nodeName.toLowerCase() + + '="' + attrs[i].nodeValue + '"'; + } + let innerHTML = docElt.innerHTML.replace(/ \n*<\/body>\n*/, ' </body>\n'); + htmlStr += '>\n ' + innerHTML + '\n</html>\n'; + + writeContent(file, doctypeStr + htmlStr); +} + +function optimize_compile(webapp, file) { + let mozL10n = win.navigator.mozL10n; + + let processedLocales = 0; + let dictionary = l10nDictionary; + + // catch console.[log|warn|info] calls and redirect them to `dump()' + // XXX for some reason, this won't work if gDEBUG >= 2 in l10n.js + function optimize_dump(str) { + dump(file.path.replace(GAIA_DIR, '') + ': ' + str + '\n'); + } + + win.console = { + log: optimize_dump, + warn: optimize_dump, + info: optimize_dump + }; + + // catch the XHR in `loadResource' and use a local file reader instead + win.XMLHttpRequest = function() { + debug('loadResource'); + + function open(type, url, async) { + this.readyState = 4; + this.status = 200; + this.responseText = optimize_getFileContent(webapp, file, url); + } + + function send() { + this.onreadystatechange(); + } + + return { + open: open, + send: send, + onreadystatechange: null + }; + }; + + // catch the `localized' event dispatched by `fireL10nReadyEvent()' + win.dispatchEvent = function() { + processedLocales++; + debug('fireL10nReadyEvent - ' + + processedLocales + '/' + l10nLocales.length); + + let docElt = win.document.documentElement; + dictionary.locales[mozL10n.language.code] = mozL10n.dictionary; + + if (processedLocales < l10nLocales.length) { + // load next locale + mozL10n.language.code = l10nLocales[processedLocales]; + } else { + // we expect the last locale to be the default one: + // set the lang/dir attributes of the current document + docElt.dir = mozL10n.language.direction; + docElt.lang = mozL10n.language.code; + + // save localized document + let newPath = file.path + '.' + GAIA_DEFAULT_LOCALE; + let newFile = new FileUtils.File(newPath); + optimize_embedl10nResources(win.document, dictionary); + + if (JS_AGGREGATION_WHITELIST.indexOf(webapp.sourceDirectoryName) !== -1) { + optimize_aggregateJsResources(win.document, webapp, newFile); + dump( + '[optimize] aggregating javascript for : "' + + webapp.sourceDirectoryName + '" \n' + ); + } + + optimize_serializeHTMLDocument(win.document, newFile); + } + }; + + // load and parse the HTML document + let DOMParser = CC('@mozilla.org/xmlextras/domparser;1', 'nsIDOMParser'); + win.document = (new DOMParser()). + parseFromString(getFileContent(file), 'text/html'); + + // if this HTML document uses l10n.js, pre-localize it -- + // selecting a language triggers `XMLHttpRequest' and `dispatchEvent' above + if (win.document.querySelector('script[src$="l10n.js"]')) { + debug('localizing: ' + file.path); + mozL10n.language.code = l10nLocales[processedLocales]; + } +} + + +/** + * Pre-translate all HTML files for the default locale + */ + +debug('Begin'); + +if (GAIA_INLINE_LOCALES === '1') { + l10nLocales = []; + l10nDictionary.locales = {}; + + // LOCALES_FILE is a relative path by default: shared/resources/languages.json + // -- but it can be an absolute path when doing a multilocale build. + // LOCALES_FILE is using unix separator, ensure working fine on win32 + let abs_path_chunks = [GAIA_DIR].concat(LOCALES_FILE.split('/')); + let file = getFile.apply(null, abs_path_chunks); + if (!file.exists()) { + file = getFile(LOCALES_FILE); + } + let locales = JSON.parse(getFileContent(file)); + + // we keep the default locale order for `l10nDictionary.locales', + // but we ensure the default locale comes last in `l10nLocales'. + for (let lang in locales) { + if (lang != GAIA_DEFAULT_LOCALE) { + l10nLocales.push(lang); + } + l10nDictionary.locales[lang] = {}; + } + l10nLocales.push(GAIA_DEFAULT_LOCALE); +} + +Gaia.webapps.forEach(function(webapp) { + // if BUILD_APP_NAME isn't `*`, we only accept one webapp + if (BUILD_APP_NAME != '*' && webapp.sourceDirectoryName != BUILD_APP_NAME) + return; + + debug(webapp.sourceDirectoryName); + + let files = ls(webapp.sourceDirectoryFile, true, /^(shared|tests?)$/); + files.forEach(function(file) { + if (/\.html$/.test(file.leafName)) { + optimize_compile(webapp, file); + } + }); +}); + +debug('End'); + diff --git a/build/webapp-zip.js b/build/webapp-zip.js new file mode 100644 index 0000000..1c6df44 --- /dev/null +++ b/build/webapp-zip.js @@ -0,0 +1,293 @@ + +function debug(msg) { + //dump('-*- webapps-zip.js ' + msg + '\n'); +} + +// Header values usefull for zip xpcom component +const PR_RDONLY = 0x01; +const PR_WRONLY = 0x02; +const PR_RDWR = 0x04; +const PR_CREATE_FILE = 0x08; +const PR_APPEND = 0x10; +const PR_TRUNCATE = 0x20; +const PR_SYNC = 0x40; +const PR_EXCL = 0x80; + +/** + * Add a file or a directory, recursively, to a zip file + * + * @param {nsIZipWriter} zip zip xpcom instance. + * @param {String} pathInZip relative path to use in zip. + * @param {nsIFile} file file xpcom to add. + */ +function addToZip(zip, pathInZip, file) { + if (isSubjectToBranding(file.path)) { + file.append((OFFICIAL == 1) ? 'official' : 'unofficial'); + } + + if (!file.exists()) + throw new Error('Can\'t add inexistent file to zip : ' + file.path); + + // nsIZipWriter should not receive any path starting with `/`, + // it would put files in a folder with empty name... + pathInZip = pathInZip.replace(/^\/+/, ''); + + // Case 1/ Regular file + if (file.isFile()) { + try { + debug(' +file to zip ' + pathInZip); + + if (/\.html$/.test(file.leafName)) { + // this file might have been pre-translated for the default locale + let l10nFile = file.parent.clone(); + l10nFile.append(file.leafName + '.' + GAIA_DEFAULT_LOCALE); + if (l10nFile.exists()) { + zip.addEntryFile(pathInZip, + Ci.nsIZipWriter.COMPRESSION_DEFAULT, + l10nFile, + false); + return; + } + } + + let re = new RegExp('\\.html\\.' + GAIA_DEFAULT_LOCALE); + if (!zip.hasEntry(pathInZip) && !re.test(file.leafName)) { + zip.addEntryFile(pathInZip, + Ci.nsIZipWriter.COMPRESSION_DEFAULT, + file, + false); + } + } catch (e) { + throw new Error('Unable to add following file in zip: ' + + file.path + '\n' + e); + } + } + // Case 2/ Directory + else if (file.isDirectory()) { + debug(' +directory to zip ' + pathInZip); + + if (!zip.hasEntry(pathInZip)) + zip.addEntryDirectory(pathInZip, file.lastModifiedTime, false); + + // Append a `/` at end of relative path if it isn't already here + if (pathInZip.substr(-1) !== '/') + pathInZip += '/'; + + let files = ls(file); + files.forEach(function(subFile) { + let subPath = pathInZip + subFile.leafName; + addToZip(zip, subPath, subFile); + }); + } +} + +/** + * Copy a "Building Block" (i.e. shared style resource) + * + * @param {nsIZipWriter} zip zip xpcom instance. + * @param {String} blockName name of the building block to copy. + * @param {String} dirName name of the shared directory to use. + */ +function copyBuildingBlock(zip, blockName, dirName) { + let dirPath = '/shared/' + dirName + '/'; + + // Compute the nsIFile for this shared style + let styleFolder = Gaia.sharedFolder.clone(); + styleFolder.append(dirName); + let cssFile = styleFolder.clone(); + if (!styleFolder.exists()) { + throw new Error('Using inexistent shared style: ' + blockName); + } + + cssFile.append(blockName + '.css'); + addToZip(zip, dirPath + blockName + '.css', cssFile); + + // Copy everything but index.html and any other HTML page into the + // style/<block> folder. + let subFolder = styleFolder.clone(); + subFolder.append(blockName); + ls(subFolder, true).forEach(function(file) { + let relativePath = file.getRelativeDescriptor(styleFolder); + // Ignore HTML files at style root folder + if (relativePath.match(/^[^\/]+\.html$/)) + return; + // Do not process directory as `addToZip` will add files recursively + if (file.isDirectory()) + return; + addToZip(zip, dirPath + relativePath, file); + }); +} + +let webappsTargetDir = Cc['@mozilla.org/file/local;1'] + .createInstance(Ci.nsILocalFile); +webappsTargetDir.initWithPath(PROFILE_DIR); + +// Create profile folder if doesn't exists +ensureFolderExists(webappsTargetDir); + +// Create webapps folder if doesn't exists +webappsTargetDir.append('webapps'); +ensureFolderExists(webappsTargetDir); + +Gaia.webapps.forEach(function(webapp) { + // If BUILD_APP_NAME isn't `*`, we only accept one webapp + if (BUILD_APP_NAME != '*' && webapp.sourceDirectoryName != BUILD_APP_NAME) + return; + + // Compute webapp folder name in profile + let webappTargetDir = webappsTargetDir.clone(); + webappTargetDir.append(webapp.domain); + ensureFolderExists(webappTargetDir); + + let zip = Cc['@mozilla.org/zipwriter;1'].createInstance(Ci.nsIZipWriter); + + let mode = PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE; + + let zipFile = webappTargetDir.clone(); + zipFile.append('application.zip'); + zip.open(zipFile, mode); + + // Add webapp folder to the zip + debug('# Create zip for: ' + webapp.domain); + let files = ls(webapp.sourceDirectoryFile); + files.forEach(function(file) { + // Ignore l10n files if they have been inlined + if (GAIA_INLINE_LOCALES && + (file.leafName === 'locales' || file.leafName === 'locales.ini')) + return; + // Ignore files from /shared directory (these files were created by + // Makefile code). Also ignore files in the /test directory. + if (file.leafName !== 'shared' && file.leafName !== 'test') + addToZip(zip, '/' + file.leafName, file); + }); + + // Put shared files, but copy only files actually used by the webapp. + // We search for shared file usage by parsing webapp source code. + let EXTENSIONS_WHITELIST = ['html']; + let SHARED_USAGE = + /<(?:script|link).+=['"]\.?\.?\/?shared\/([^\/]+)\/([^''\s]+)("|')/g; + + let used = { + js: [], // List of JS file paths to copy + locales: [], // List of locale names to copy + resources: [], // List of resources to copy + styles: [], // List of stable style names to copy + unstable_styles: [] // List of unstable style names to copy + }; + + let files = ls(webapp.sourceDirectoryFile, true); + files.filter(function(file) { + // Process only files that may require a shared file + let extension = file.leafName + .substr(file.leafName.lastIndexOf('.') + 1) + .toLowerCase(); + return file.isFile() && EXTENSIONS_WHITELIST.indexOf(extension) != -1; + }). + forEach(function(file) { + // Grep files to find shared/* usages + let content = getFileContent(file); + while ((matches = SHARED_USAGE.exec(content)) !== null) { + let kind = matches[1]; // js | locales | resources | style + let path = matches[2]; + switch (kind) { + case 'js': + if (used.js.indexOf(path) == -1) + used.js.push(path); + break; + case 'locales': + if (!GAIA_INLINE_LOCALES) { + let localeName = path.substr(0, path.lastIndexOf('.')); + if (used.locales.indexOf(localeName) == -1) { + used.locales.push(localeName); + } + } + break; + case 'resources': + if (used.resources.indexOf(path) == -1) { + used.resources.push(path); + } + break; + case 'style': + let styleName = path.substr(0, path.lastIndexOf('.')); + if (used.styles.indexOf(styleName) == -1) + used.styles.push(styleName); + break; + case 'style_unstable': + let unstableStyleName = path.substr(0, path.lastIndexOf('.')); + if (used.unstable_styles.indexOf(unstableStyleName) == -1) + used.unstable_styles.push(unstableStyleName); + break; + } + } + }); + + used.js.forEach(function(path) { + // Compute the nsIFile for this shared JS file + let file = Gaia.sharedFolder.clone(); + file.append('js'); + path.split('/').forEach(function(segment) { + file.append(segment); + }); + if (!file.exists()) { + throw new Error('Using inexistent shared JS file: ' + path + ' from: ' + + webapp.domain); + } + addToZip(zip, '/shared/js/' + path, file); + }); + + used.locales.forEach(function(name) { + // Compute the nsIFile for this shared locale + let localeFolder = Gaia.sharedFolder.clone(); + localeFolder.append('locales'); + let ini = localeFolder.clone(); + localeFolder.append(name); + if (!localeFolder.exists()) { + throw new Error('Using inexistent shared locale: ' + name + ' from: ' + + webapp.domain); + } + ini.append(name + '.ini'); + if (!ini.exists()) + throw new Error(name + ' locale doesn`t have `.ini` file.'); + + // Add the .ini file + addToZip(zip, '/shared/locales/' + name + '.ini', ini); + // And the locale folder itself + addToZip(zip, '/shared/locales/' + name, localeFolder); + }); + + used.resources.forEach(function(path) { + // Compute the nsIFile for this shared resource file + let file = Gaia.sharedFolder.clone(); + file.append('resources'); + path.split('/').forEach(function(segment) { + file.append(segment); + if (isSubjectToBranding(file.path)) { + file.append((OFFICIAL == 1) ? 'official' : 'unofficial'); + } + }); + if (!file.exists()) { + throw new Error('Using inexistent shared resource: ' + path + + ' from: ' + webapp.domain + '\n'); + return; + } + addToZip(zip, '/shared/resources/' + path, file); + }); + + used.styles.forEach(function(name) { + try { + copyBuildingBlock(zip, name, 'style'); + } catch (e) { + throw new Error(e + ' from: ' + webapp.domain); + } + }); + + used.unstable_styles.forEach(function(name) { + try { + copyBuildingBlock(zip, name, 'style_unstable'); + } catch (e) { + throw new Error(e + ' from: ' + webapp.domain); + } + }); + + zip.close(); +}); |