~ubuntu-branches/ubuntu/lucid/ubufox/lucid-proposed

« back to all changes in this revision

Viewing changes to modules/uAddonInstaller.jsm

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2011-12-21 02:10:09 UTC
  • mfrom: (1.1.23)
  • Revision ID: package-import@ubuntu.com-20111221021009-wiou9irl64fx525o
Tags: 0.9.3-0ubuntu0.10.04.1
* New upstream release v0.9.3
  - Support Firefox 9 and 10 (LP: #904594)
* New upstream release v0.9.2
  - Bump maxVersion to 8.*
* New upstream release v0.9.1
  - Rename AboutHome to AboutStartpage, and have it handle about:startpage now
    (to not conflict with the new about:home handler in Firefox)
  - Set default home page to about:startpage
  - Add support for the XPCOM changes in Gecko 2.0
  - In the restart notifier, don't hardcode a list of filenames for different
    browser versions, but just use MOZ_APP_LAUNCHER instead. This will
    indirectly fix LP: #511250 and should prevent it from happening again
  - Merge the plugin finder code from Firefox 4. This now uses AddonManager,
    so the MinVersion needs to be bumped
  - Drop the FF2.0 specific code
  - Drop the AddonsOverlay and associated code, this hasn't worked for
    several releases since we started using software-center, and nobody
    really noticed so far. We can reimplement this at a later date if wanted,
    but it throws JS exceptions when loading in FF4.0 now
  - Specify for the extension to be unpacked by the installer. Without this,
    the prefs aren't used
  - Get the distributionID field for the pfs URL from the preferences, rather
    than hard-coding a value
  - Fix LP: #579091 - ubufox interferes with toolbar buttons for other add-ons
    Don't initialize or assign any value to gBrowser in alternatePlugins.js.
    It runs in the context of browser.xul, so gBrowser will always exist
    once the browser window has loaded
  - Fix LP: #333799 - set a default dictionary language
  - Don't duplicate preferences that we're already setting in Firefox
  - Update Japanese(ja) translations (LP: #746538)
  - Fix LP: #750305 - Don't hard-code links and configs for each release.
    Rather than doing this manually for every release, we add a dist.js with
    distro information (populated at build time with lsb_release) and just
    do all this automatically.
  - Fix LP: #752364 - Plugin Finder Service never finds anything. Use the
    release info in dist.js to construct the URL, now that this got dropped
    from Firefox
  - Fix LP: #728826 - Update icons to new versions.
  - Drop the pluginGlue.js module. This didn't work, as we can no longer
    use a modules registerSelf method to use nsICategoryManager at startup,
    due to the XPCOM registration changes in Firefox 4. Instead, add
    category entries with the chrome.manifest
  - Fix LP: #709125 - User agent doesn't include Ubuntu in it so
    apt.ubuntu.com doesn't work. We add an extra X-Ubuntu HTTP header in
    requests to apt.ubuntu.com, rather than send this information out in
    every request in the UA string

* Revert an upstream commit to look in the new location for the restart
  required trigger
  - add debian/patches/use_old_restart_notifier_file.patch
  - add debian/patches/series
* Drop debian/patches/02_fix_about_home.patch - not relevant now
* Backport a change from trunk to support installing system-wide extensions
  in to the users profile, where they will get updates directly from upstream
  - add debian/patches/addon_installer.patch
  - update debian/patches/series
* Fix LP: #809601 - do the ubufox => xul-ext-ubufox transition. I'd already
  uploaded a backport of this previously to the firefox-stable PPA, which
  causes upgrades to versions without this transition to fail. In addition
  to this, it is required for lucid -> maverick upgrades to work
* Move ubufox in to the Firefox application directory
* Improve the addon installer module

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
# ***** BEGIN LICENSE BLOCK *****
 
3
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
4
#
 
5
# The contents of this file are subject to the Mozilla Public License Version
 
6
# 1.1 (the "License"); you may not use this file except in compliance with
 
7
# the License. You may obtain a copy of the License at
 
8
# http://www.mozilla.org/MPL/
 
9
#
 
10
# Software distributed under the License is distributed on an "AS IS" basis,
 
11
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
12
# for the specific language governing rights and limitations under the
 
13
# License.
 
14
#
 
15
# The Original Code is the Extension Manager.
 
16
#
 
17
# The Initial Developer of the Original Code is
 
18
# the Mozilla Foundation.
 
19
# Portions created by the Initial Developer are Copyright (C) 2009
 
20
# the Initial Developer. All Rights Reserved.
 
21
#
 
22
# Contributor(s):
 
23
#   Dave Townsend <dtownsend@oxymoronical.com>
 
24
#   Chris Coulson <chris.coulson@canonical.com>
 
25
#
 
26
# Alternatively, the contents of this file may be used under the terms of
 
27
# either the GNU General Public License Version 2 or later (the "GPL"), or
 
28
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 
29
# in which case the provisions of the GPL or the LGPL are applicable instead
 
30
# of those above. If you wish to allow use of your version of this file only
 
31
# under the terms of either the GPL or the LGPL, and not to allow others to
 
32
# use your version of this file under the terms of the MPL, indicate your
 
33
# decision by deleting the provisions above and replace them with the notice
 
34
# and other provisions required by the GPL or the LGPL. If you do not delete
 
35
# the provisions above, a recipient may use your version of this file under
 
36
# the terms of any one of the MPL, the GPL or the LGPL.
 
37
#
 
38
# ***** END LICENSE BLOCK *****
 
39
*/
 
40
 
 
41
const Cc = Components.classes;
 
42
const Ci = Components.interfaces;
 
43
const Cu = Components.utils;
 
44
const Cr = Components.results;
 
45
 
 
46
const nsILocalFile            = Ci.nsILocalFile;
 
47
const nsIFile                 = Ci.nsIFile;
 
48
const nsIRDFService           = Ci.nsIRDFService;
 
49
const nsIRDFLiteral           = Ci.nsIRDFLiteral;
 
50
const nsIRDFResource          = Ci.nsIRDFResource;
 
51
const nsIRDFInt               = Ci.nsIRDFInt;
 
52
const nsIRDFXMLParser         = Ci.nsIRDFXMLParser;
 
53
const nsIRDFDataSource        = Ci.nsIRDFDataSource;
 
54
const nsIInputStreamChannel   = Ci.nsIInputStreamChannel;
 
55
const nsIChannel              = Ci.nsIChannel;
 
56
const nsIFileInputStream      = Ci.nsIFileInputStream;
 
57
const nsIBufferedInputStream  = Ci.nsIBufferedInputStream;
 
58
const nsIZipReader            = Ci.nsIZipReader;
 
59
 
 
60
Cu.import("resource://gre/modules/Services.jsm");
 
61
Cu.import("resource://gre/modules/AddonManager.jsm");
 
62
Cu.import("resource://gre/modules/NetUtil.jsm");
 
63
 
 
64
const PREF_AI_INSTALL_CACHE         = "extensions.ubufox@ubuntu.com.installCache";
 
65
const PREF_AI_INSTALL_DIRS          = "extensions.ubufox@ubuntu.com.installDirs";
 
66
 
 
67
const FILE_INSTALL_MANIFEST         = "install.rdf";
 
68
 
 
69
const PREFIX_NS_EM                  = "http://www.mozilla.org/2004/em-rdf#";
 
70
const RDFURI_INSTALL_MANIFEST_ROOT  = "urn:mozilla:install-manifest";
 
71
 
 
72
const REGEXP_XPI_FILE = /^.+\.xpi$/;
 
73
const REGEXP_VALID_ID = /^(\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/;
 
74
 
 
75
const PROP_METADATA = [ "id", "version" ];
 
76
 
 
77
var EXPORTED_SYMBOLS = [];
 
78
 
 
79
/**
 
80
 * A helpful wrapper around the prefs service that allows for default values
 
81
 * when requested values aren't set.
 
82
 */
 
83
var Prefs = {
 
84
  getCharPref: function(aName, aDefaultValue) {
 
85
    try {
 
86
      return Services.prefs.getCharPref(aName);
 
87
    }
 
88
    catch (e) {
 
89
    }
 
90
    return aDefaultValue;
 
91
  },
 
92
 
 
93
  getBoolPref: function(aName, aDefaultValue) {
 
94
    try {
 
95
      return Services.prefs.getBoolPref(aName);
 
96
    }
 
97
    catch (e) {
 
98
    }
 
99
    return aDefaultValue;
 
100
  }
 
101
}
 
102
 
 
103
function AddonInternal() {
 
104
}
 
105
 
 
106
this.__defineGetter__("gRDF", function() {
 
107
  delete this.gRDF;
 
108
  return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
 
109
                     getService(nsIRDFService);
 
110
});
 
111
 
 
112
function EM_R(aProperty) {
 
113
  return gRDF.GetResource(PREFIX_NS_EM + aProperty);
 
114
}
 
115
 
 
116
function getRDFValue(aLiteral) {
 
117
  if (aLiteral instanceof nsIRDFLiteral)
 
118
    return aLiteral.Value;
 
119
  if (aLiteral instanceof nsIRDFResource)
 
120
    return aLiteral.Value;
 
121
  if (aLiteral instanceof nsIRDFInt)
 
122
    return aLiteral.Value;
 
123
  return null;
 
124
}
 
125
 
 
126
function getRDFProperty(aDs, aResource, aProperty) {
 
127
  return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
 
128
}
 
129
 
 
130
function loadManifestFromRDF(aUri, aStream) {
 
131
  let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
 
132
                  createInstance(nsIRDFXMLParser)
 
133
  let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
 
134
           createInstance(nsIRDFDataSource);
 
135
  let listener = rdfParser.parseAsync(ds, aUri);
 
136
  let channel = Cc["@mozilla.org/network/input-stream-channel;1"].
 
137
                createInstance(nsIInputStreamChannel);
 
138
  channel.setURI(aUri);
 
139
  channel.contentStream = aStream;
 
140
  channel.QueryInterface(nsIChannel);
 
141
  channel.contentType = "text/xml";
 
142
 
 
143
  listener.onStartRequest(channel, null);
 
144
 
 
145
  try {
 
146
    let pos = 0;
 
147
    let count = aStream.available();
 
148
    while (count > 0) {
 
149
      listener.onDataAvailable(channel, null, aStream, pos, count);
 
150
      pos += count;
 
151
      count = aStream.available();
 
152
    }
 
153
    listener.onStopRequest(channel, null, Cr.NS_OK);
 
154
  }
 
155
  catch (e) {
 
156
    listener.onStopRequest(channel, null, e.result);
 
157
    throw e;
 
158
  }
 
159
 
 
160
  let root = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
 
161
  let addon = new AddonInternal();
 
162
  PROP_METADATA.forEach(function(aProp) {
 
163
    addon[aProp] = getRDFProperty(ds, root, aProp);
 
164
  });
 
165
 
 
166
  return addon;
 
167
}
 
168
 
 
169
function loadManifestFromDir(aDir) {
 
170
  let file = aDir.clone();
 
171
  file.append(FILE_INSTALL_MANIFEST);
 
172
  if (!file.exists() || !file.isFile())
 
173
    throw new Error("Directory " + aDir.path + " does not contain a valid " +
 
174
                    "install manifest");
 
175
 
 
176
  let fis = Cc["@mozilla.org/network/file-input-stream;1"].
 
177
            createInstance(nsIFileInputStream);
 
178
  fis.init(file, -1, -1, false);
 
179
  let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
 
180
            createInstance(nsIBufferedInputStream);
 
181
  bis.init(fis, 4096);
 
182
 
 
183
  try {
 
184
    let addon = loadManifestFromRDF(Services.io.newFileURI(file), bis);
 
185
    return addon;
 
186
  }
 
187
  finally {
 
188
    bis.close();
 
189
    fis.close();
 
190
  }
 
191
}
 
192
 
 
193
function loadManifestFromZipReader(aZipReader) {
 
194
  let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST);
 
195
  let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
 
196
            createInstance(nsIBufferedInputStream);
 
197
  bis.init(zis, 4096);
 
198
 
 
199
  try {
 
200
    let uri = buildJarURI(aZipReader.file, FILE_INSTALL_MANIFEST);
 
201
    let addon = loadManifestFromRDF(uri, bis);
 
202
 
 
203
    return addon;
 
204
  }
 
205
  finally {
 
206
    bis.close();
 
207
    zis.close();
 
208
  }
 
209
}
 
210
 
 
211
function loadManifestFromZipFile(aXPIFile) {
 
212
  let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
 
213
                  createInstance(nsIZipReader);
 
214
  try {
 
215
    zipReader.open(aXPIFile);
 
216
 
 
217
    return loadManifestFromZipReader(zipReader);
 
218
  }
 
219
  finally {
 
220
    zipReader.close();
 
221
  }
 
222
}
 
223
 
 
224
function loadManifestFromFile(aFile) {
 
225
  if (aFile.isFile())
 
226
    return loadManifestFromZipFile(aFile);
 
227
  else
 
228
    return loadManifestFromDir(aFile);
 
229
}
 
230
 
 
231
function buildJarURI(aJarfile, aPath) {
 
232
  let uri = Services.io.newFileURI(aJarfile);
 
233
  uri = "jar:" + uri.spec + "!/" + aPath;
 
234
  return NetUtil.newURI(uri);
 
235
}
 
236
 
 
237
function DirectoryEntry(file, dirCache) {
 
238
  this.file = file;
 
239
  this.dirCache = dirCache;
 
240
}
 
241
 
 
242
var uAddonInstaller = {
 
243
  installCache: null,
 
244
 
 
245
  startup: function() {
 
246
    try {
 
247
      this.installCache = JSON.parse(Prefs.getCharPref(PREF_AI_INSTALL_CACHE,
 
248
                                                       null));
 
249
    } catch(e) {
 
250
      Cu.reportError("installCache failed to load: " + e);
 
251
      return;
 
252
    }
 
253
 
 
254
    if (this.installCache == null)
 
255
      this.installCache = [];
 
256
 
 
257
    try {
 
258
      let dirs = Prefs.getCharPref(PREF_AI_INSTALL_DIRS,
 
259
                                   "/usr/share/ubufox/extensions").split(",");
 
260
      dirs.forEach(function(aDir) {
 
261
        try {
 
262
          let dirFile = Cc["@mozilla.org/file/local;1"].createInstance(nsILocalFile);
 
263
          dirFile.initWithPath(aDir);
 
264
 
 
265
          var cache = null;
 
266
          uAddonInstaller.installCache.forEach(function(aCache) {
 
267
            try {
 
268
              if (aCache.location == aDir)
 
269
                cache = aCache;
 
270
            } catch(e) {
 
271
              Cu.reportError("Entry in installCache missing location");
 
272
              uAddonInstaller.installCache.splice(uAddonInstaller.installCache.indexOf(aCache), 1);
 
273
            }
 
274
          });
 
275
 
 
276
          if (cache && !("mtime" in cache && "addons" in cache)) {
 
277
            Cu.reportError("Entry in installCache for " + aDir + " is missing mtime or addons array");
 
278
            uAddonInstaller.installCache.splice(uAddonInstaller.installCache.indexOf(cache), 1);
 
279
            cache = null;
 
280
          }
 
281
 
 
282
          if (dirFile.exists() && dirFile.isDirectory() &&
 
283
              (!cache || dirFile.lastModifiedTime != cache.mtime)) {
 
284
            if (!cache) {
 
285
              cache = JSON.parse("{\"location\": \"" + aDir + "\", \"addons\": []}");
 
286
              uAddonInstaller.installCache.push(cache);
 
287
            }
 
288
            cache.mtime = dirFile.lastModifiedTime;
 
289
            uAddonInstaller.processDir(dirFile, cache);
 
290
          }
 
291
        } catch(e) {
 
292
          Cu.reportError("Failed to process directory " + aDir);
 
293
        }
 
294
      });
 
295
    } catch(e) {
 
296
      Cu.reportError(e);
 
297
      return;
 
298
    }
 
299
 
 
300
    Services.prefs.setCharPref(PREF_AI_INSTALL_CACHE,
 
301
                               JSON.stringify(this.installCache));
 
302
  },
 
303
 
 
304
  processDir: function(aDir, aCache) {
 
305
    let entries = aDir.directoryEntries;
 
306
    while (entries.hasMoreElements())
 
307
      this.processFile(entries.getNext().QueryInterface(nsIFile), aCache);
 
308
  },
 
309
 
 
310
  processFile: function(aFile, aCache) {
 
311
    var id = aFile.leafName;
 
312
 
 
313
    if (aFile.isFile()) {
 
314
      if (REGEXP_XPI_FILE.test(id))
 
315
        id = id.substring(0, (id.length - 4));
 
316
      else
 
317
        return;
 
318
    } else if (!aFile.isDirectory()) {
 
319
      return;
 
320
    }
 
321
 
 
322
    if (!REGEXP_VALID_ID.test(id))
 
323
      return;
 
324
 
 
325
    var cachedAddon = null;
 
326
    var seenBefore = false;
 
327
    aCache.addons.forEach(function(aAddon) {
 
328
      try {
 
329
        if (aAddon.id == id)
 
330
          cachedAddon = aAddon;
 
331
      } catch(e) {
 
332
        Cu.reportError("Addon in cache without ID");
 
333
        aCache.addons.splice(aCache.addons.indexOf(aAddon), 1);
 
334
      }
 
335
    });
 
336
 
 
337
    if (cachedAddon)
 
338
      seenBefore = true;
 
339
 
 
340
    if (cachedAddon && !("mtime" in cachedAddon)) {
 
341
      Cu.reportError("Addon entry for " + id + " in installCache is missing mtime");
 
342
      aCache.addons.splice(aCache.addons.indexOf(cachedAddon), 1);
 
343
      cachedAddon = null;
 
344
    }
 
345
 
 
346
    if (cachedAddon && aFile.lastModifiedTime == cachedAddon.mtime)
 
347
      return;
 
348
 
 
349
    if (!cachedAddon) {
 
350
      cachedAddon = JSON.parse("{\"id\": \"" + id + "\"}");
 
351
      aCache.addons.push(cachedAddon);
 
352
    }
 
353
 
 
354
    cachedAddon.mtime = aFile.lastModifiedTime;
 
355
 
 
356
    var addon = null;
 
357
    try {
 
358
      addon = loadManifestFromFile(aFile);
 
359
    } catch(e) {
 
360
      Cu.reportError("Failed to load extension manifest: " + e);
 
361
      return;
 
362
    }
 
363
 
 
364
    if (addon.id != id)
 
365
      return;
 
366
 
 
367
    AddonManager.getAddonByID(id, function(aAddon) {
 
368
      let shouldInstall = false;
 
369
      if (aAddon) {
 
370
        if ((Services.vc.compare(aAddon.version, addon.version) < 0) &&
 
371
            (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE)) {
 
372
          shouldInstall = true;
 
373
        }
 
374
      } else if (!seenBefore) {
 
375
        shouldInstall = true;
 
376
      }
 
377
 
 
378
      if (!shouldInstall)
 
379
        return;
 
380
 
 
381
      AddonManager.getInstallForFile(aFile, function(aInstall) {
 
382
        if (aInstall) {
 
383
          aInstall.addListener(uAddonInstaller);
 
384
          aInstall.install();
 
385
        }
 
386
      });
 
387
    });
 
388
  },
 
389
 
 
390
  onInstallEnded: function(aInstall, aAddon) {
 
391
    aInstall.removeListener(this);
 
392
    aAddon.findUpdates({
 
393
      onUpdateAvailable: function(aAddon, aInstall) {
 
394
        if (aInstall)
 
395
          aInstall.install();
 
396
      }
 
397
    }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
 
398
  },
 
399
 
 
400
  onInstallCancelled: function(aInstall) {
 
401
    aInstall.removeListener(this);
 
402
  },
 
403
 
 
404
  onInstallFailed: function(aInstall) {
 
405
    aInstall.removeListener(this);
 
406
  }
 
407
};
 
408
 
 
409
uAddonInstaller.startup();