tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_profile.js (19819B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { ON_PROFILE_CHANGE_NOTIFICATION, log } = ChromeUtils.importESModule(
      7  "resource://gre/modules/FxAccountsCommon.sys.mjs"
      8 );
      9 const { FxAccountsProfileClient } = ChromeUtils.importESModule(
     10  "resource://gre/modules/FxAccountsProfileClient.sys.mjs"
     11 );
     12 const { FxAccountsProfile } = ChromeUtils.importESModule(
     13  "resource://gre/modules/FxAccountsProfile.sys.mjs"
     14 );
     15 const { setTimeout } = ChromeUtils.importESModule(
     16  "resource://gre/modules/Timer.sys.mjs"
     17 );
     18 
     19 let mockClient = function (fxa) {
     20  let options = {
     21    serverURL: "http://127.0.0.1:1111/v1",
     22    fxa,
     23  };
     24  return new FxAccountsProfileClient(options);
     25 };
     26 
     27 const ACCOUNT_UID = "abc123";
     28 const ACCOUNT_EMAIL = "foo@bar.com";
     29 const ACCOUNT_DATA = {
     30  uid: ACCOUNT_UID,
     31  email: ACCOUNT_EMAIL,
     32 };
     33 
     34 let mockFxa = function () {
     35  let fxa = {
     36    // helpers to make the tests using this mock less verbose...
     37    set _testProfileCache(profileCache) {
     38      this._internal.currentAccountState._data.profileCache = profileCache;
     39    },
     40    get _testProfileCache() {
     41      return this._internal.currentAccountState._data.profileCache;
     42    },
     43  };
     44  fxa._internal = Object.assign(
     45    {},
     46    {
     47      currentAccountState: Object.assign(
     48        {},
     49        {
     50          _data: Object.assign({}, ACCOUNT_DATA),
     51 
     52          get isCurrent() {
     53            return true;
     54          },
     55 
     56          async getUserAccountData() {
     57            return this._data;
     58          },
     59 
     60          async updateUserAccountData(data) {
     61            this._data = Object.assign(this._data, data);
     62          },
     63        }
     64      ),
     65 
     66      withCurrentAccountState(cb) {
     67        return cb(this.currentAccountState);
     68      },
     69 
     70      async _handleTokenError(err) {
     71        // handleTokenError always rethrows.
     72        throw err;
     73      },
     74    }
     75  );
     76  return fxa;
     77 };
     78 
     79 function CreateFxAccountsProfile(fxa = null, client = null) {
     80  if (!fxa) {
     81    fxa = mockFxa();
     82  }
     83  let options = {
     84    fxai: fxa._internal,
     85    profileServerUrl: "http://127.0.0.1:1111/v1",
     86  };
     87  if (client) {
     88    options.profileClient = client;
     89  }
     90  return new FxAccountsProfile(options);
     91 }
     92 
     93 add_test(function cacheProfile_change() {
     94  let setProfileCacheCalled = false;
     95  let fxa = mockFxa();
     96  fxa._internal.currentAccountState.updateUserAccountData = data => {
     97    setProfileCacheCalled = true;
     98    Assert.equal(data.profileCache.profile.avatar, "myurl");
     99    Assert.equal(data.profileCache.etag, "bogusetag");
    100    return Promise.resolve();
    101  };
    102  let profile = CreateFxAccountsProfile(fxa);
    103 
    104  makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
    105    Assert.equal(data, ACCOUNT_DATA.uid);
    106    Assert.ok(setProfileCacheCalled);
    107    run_next_test();
    108  });
    109 
    110  return profile._cacheProfile({
    111    body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myurl" },
    112    etag: "bogusetag",
    113  });
    114 });
    115 
    116 add_test(function fetchAndCacheProfile_ok() {
    117  let client = mockClient(mockFxa());
    118  client.fetchProfile = function () {
    119    return Promise.resolve({ body: { uid: ACCOUNT_UID, avatar: "myimg" } });
    120  };
    121  let profile = CreateFxAccountsProfile(null, client);
    122  profile._cachedAt = 12345;
    123 
    124  profile._cacheProfile = function (toCache) {
    125    Assert.equal(toCache.body.avatar, "myimg");
    126    return Promise.resolve(toCache.body);
    127  };
    128 
    129  return profile._fetchAndCacheProfile().then(result => {
    130    Assert.equal(result.avatar, "myimg");
    131    Assert.notEqual(profile._cachedAt, 12345, "cachedAt has been bumped");
    132    run_next_test();
    133  });
    134 });
    135 
    136 add_test(function fetchAndCacheProfile_always_bumps_cachedAt() {
    137  let client = mockClient(mockFxa());
    138  client.fetchProfile = function () {
    139    return Promise.reject(new Error("oops"));
    140  };
    141  let profile = CreateFxAccountsProfile(null, client);
    142  profile._cachedAt = 12345;
    143 
    144  return profile._fetchAndCacheProfile().then(
    145    () => {
    146      do_throw("Should not succeed");
    147    },
    148    () => {
    149      Assert.notEqual(profile._cachedAt, 12345, "cachedAt has been bumped");
    150      run_next_test();
    151    }
    152  );
    153 });
    154 
    155 add_test(function fetchAndCacheProfile_sendsETag() {
    156  let fxa = mockFxa();
    157  fxa._testProfileCache = { profile: {}, etag: "bogusETag" };
    158  let client = mockClient(fxa);
    159  client.fetchProfile = function (etag) {
    160    Assert.equal(etag, "bogusETag");
    161    return Promise.resolve({
    162      body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" },
    163    });
    164  };
    165  let profile = CreateFxAccountsProfile(fxa, client);
    166 
    167  return profile._fetchAndCacheProfile().then(() => {
    168    run_next_test();
    169  });
    170 });
    171 
    172 // Check that a second profile request when one is already in-flight reuses
    173 // the in-flight one.
    174 add_task(async function fetchAndCacheProfileOnce() {
    175  // A promise that remains unresolved while we fire off 2 requests for
    176  // a profile.
    177  let resolveProfile;
    178  let promiseProfile = new Promise(resolve => {
    179    resolveProfile = resolve;
    180  });
    181  let numFetches = 0;
    182  let client = mockClient(mockFxa());
    183  client.fetchProfile = function () {
    184    numFetches += 1;
    185    return promiseProfile;
    186  };
    187  let fxa = mockFxa();
    188  let profile = CreateFxAccountsProfile(fxa, client);
    189 
    190  let request1 = profile._fetchAndCacheProfile();
    191  profile._fetchAndCacheProfile();
    192  await new Promise(res => setTimeout(res, 0)); // Yield so fetchProfile() is called (promise)
    193 
    194  // should be one request made to fetch the profile (but the promise returned
    195  // by it remains unresolved)
    196  Assert.equal(numFetches, 1);
    197 
    198  // resolve the promise.
    199  resolveProfile({
    200    body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" },
    201  });
    202 
    203  // both requests should complete with the same data.
    204  let got1 = await request1;
    205  Assert.equal(got1.avatar, "myimg");
    206  let got2 = await request1;
    207  Assert.equal(got2.avatar, "myimg");
    208 
    209  // and still only 1 request was made.
    210  Assert.equal(numFetches, 1);
    211 });
    212 
    213 // Check that sharing a single fetch promise works correctly when the promise
    214 // is rejected.
    215 add_task(async function fetchAndCacheProfileOnce() {
    216  // A promise that remains unresolved while we fire off 2 requests for
    217  // a profile.
    218  let rejectProfile;
    219  let promiseProfile = new Promise((resolve, reject) => {
    220    rejectProfile = reject;
    221  });
    222  let numFetches = 0;
    223  let client = mockClient(mockFxa());
    224  client.fetchProfile = function () {
    225    numFetches += 1;
    226    return promiseProfile;
    227  };
    228  let fxa = mockFxa();
    229  let profile = CreateFxAccountsProfile(fxa, client);
    230 
    231  let request1 = profile._fetchAndCacheProfile();
    232  let request2 = profile._fetchAndCacheProfile();
    233  await new Promise(res => setTimeout(res, 0)); // Yield so fetchProfile() is called (promise)
    234 
    235  // should be one request made to fetch the profile (but the promise returned
    236  // by it remains unresolved)
    237  Assert.equal(numFetches, 1);
    238 
    239  // reject the promise.
    240  rejectProfile("oh noes");
    241 
    242  // both requests should reject.
    243  try {
    244    await request1;
    245    throw new Error("should have rejected");
    246  } catch (ex) {
    247    if (ex != "oh noes") {
    248      throw ex;
    249    }
    250  }
    251  try {
    252    await request2;
    253    throw new Error("should have rejected");
    254  } catch (ex) {
    255    if (ex != "oh noes") {
    256      throw ex;
    257    }
    258  }
    259 
    260  // but a new request should works.
    261  client.fetchProfile = function () {
    262    return Promise.resolve({
    263      body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" },
    264    });
    265  };
    266 
    267  let got = await profile._fetchAndCacheProfile();
    268  Assert.equal(got.avatar, "myimg");
    269 });
    270 
    271 add_test(function fetchAndCacheProfile_alreadyCached() {
    272  let cachedUrl = "cachedurl";
    273  let fxa = mockFxa();
    274  fxa._testProfileCache = {
    275    profile: { uid: ACCOUNT_UID, avatar: cachedUrl },
    276    etag: "bogusETag",
    277  };
    278  let client = mockClient(fxa);
    279  client.fetchProfile = function (etag) {
    280    Assert.equal(etag, "bogusETag");
    281    return Promise.resolve(null);
    282  };
    283 
    284  let profile = CreateFxAccountsProfile(fxa, client);
    285  profile._cacheProfile = function () {
    286    do_throw("This method should not be called.");
    287  };
    288 
    289  return profile._fetchAndCacheProfile().then(result => {
    290    Assert.equal(result, null);
    291    Assert.equal(fxa._testProfileCache.profile.avatar, cachedUrl);
    292    run_next_test();
    293  });
    294 });
    295 
    296 // Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the
    297 // last one doesn't kick off a new request to check the cached copy is fresh.
    298 add_task(async function fetchAndCacheProfileAfterThreshold() {
    299  /*
    300   * This test was observed to cause a timeout for... any timer precision reduction.
    301   * Even 1 us. Exact reason is still undiagnosed.
    302   */
    303  Services.prefs.setBoolPref("privacy.reduceTimerPrecision", false);
    304 
    305  registerCleanupFunction(async () => {
    306    Services.prefs.clearUserPref("privacy.reduceTimerPrecision");
    307  });
    308 
    309  let numFetches = 0;
    310  let client = mockClient(mockFxa());
    311  client.fetchProfile = async function () {
    312    numFetches += 1;
    313    return {
    314      body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" },
    315    };
    316  };
    317  let profile = CreateFxAccountsProfile(null, client);
    318  profile.PROFILE_FRESHNESS_THRESHOLD = 1000;
    319 
    320  // first fetch should return null as we don't have data.
    321  let p = await profile.getProfile();
    322  Assert.equal(p, null);
    323  // ensure we kicked off a fetch.
    324  Assert.notEqual(profile._currentFetchPromise, null);
    325  // wait for that fetch to finish
    326  await profile._currentFetchPromise;
    327  Assert.equal(numFetches, 1);
    328  Assert.equal(profile._currentFetchPromise, null);
    329 
    330  await profile.getProfile();
    331  Assert.equal(numFetches, 1);
    332  Assert.equal(profile._currentFetchPromise, null);
    333 
    334  await new Promise(resolve => {
    335    do_timeout(1000, resolve);
    336  });
    337 
    338  let origFetchAndCatch = profile._fetchAndCacheProfile;
    339  let backgroundFetchDone = Promise.withResolvers();
    340  profile._fetchAndCacheProfile = async () => {
    341    await origFetchAndCatch.call(profile);
    342    backgroundFetchDone.resolve();
    343  };
    344  await profile.getProfile();
    345  await backgroundFetchDone.promise;
    346  Assert.equal(numFetches, 2);
    347 });
    348 
    349 add_task(async function test_ensureProfile() {
    350  let client = new FxAccountsProfileClient({
    351    serverURL: "http://127.0.0.1:1111/v1",
    352    fxa: mockFxa(),
    353  });
    354  let profile = CreateFxAccountsProfile(null, client);
    355 
    356  const testCases = [
    357    // profile retrieval when there is no cached profile info
    358    {
    359      threshold: 1000,
    360      expectsCachedProfileReturned: false,
    361      cachedProfile: null,
    362      fetchedProfile: {
    363        uid: ACCOUNT_UID,
    364        email: ACCOUNT_EMAIL,
    365        avatar: "myimg",
    366      },
    367    },
    368    // profile retrieval when the cached profile is recent
    369    {
    370      // Note: The threshold for this test case is being set to an arbitrary value that will
    371      // be greater than Date.now() so the retrieved cached profile will be deemed recent.
    372      threshold: Date.now() + 5000,
    373      expectsCachedProfileReturned: true,
    374      cachedProfile: {
    375        uid: `${ACCOUNT_UID}2`,
    376        email: `${ACCOUNT_EMAIL}2`,
    377        avatar: "myimg2",
    378      },
    379    },
    380    // profile retrieval when the cached profile is old and a new profile is fetched
    381    {
    382      threshold: 1000,
    383      expectsCachedProfileReturned: false,
    384      cachedProfile: {
    385        uid: `${ACCOUNT_UID}3`,
    386        email: `${ACCOUNT_EMAIL}3`,
    387        avatar: "myimg3",
    388      },
    389      fetchAndCacheProfileResolves: true,
    390      fetchedProfile: {
    391        uid: `${ACCOUNT_UID}4`,
    392        email: `${ACCOUNT_EMAIL}4`,
    393        avatar: "myimg4",
    394      },
    395    },
    396    // profile retrieval when the cached profile is old and a null profile is fetched
    397    {
    398      threshold: 1000,
    399      expectsCachedProfileReturned: false,
    400      cachedProfile: {
    401        uid: `${ACCOUNT_UID}5`,
    402        email: `${ACCOUNT_EMAIL}5`,
    403        avatar: "myimg5",
    404      },
    405      fetchAndCacheProfileResolves: true,
    406      fetchedProfile: null,
    407    },
    408    // profile retrieval when the cached profile is old and fetching a new profile errors
    409    {
    410      threshold: 1000,
    411      expectsCachedProfileReturned: false,
    412      cachedProfile: {
    413        uid: `${ACCOUNT_UID}6`,
    414        email: `${ACCOUNT_EMAIL}6`,
    415        avatar: "myimg6",
    416      },
    417      fetchAndCacheProfileResolves: false,
    418    },
    419    // profile retrieval when we've cached a failure to fetch profile data
    420    {
    421      // Note: The threshold for this test case is being set to an arbitrary value that will
    422      // be greater than Date.now() so the retrieved cached profile will be deemed recent.
    423      threshold: Date.now() + 5000,
    424      expectsCachedProfileReturned: false,
    425      cachedProfile: null,
    426      fetchedProfile: {
    427        uid: `${ACCOUNT_UID}7`,
    428        email: `${ACCOUNT_EMAIL}7`,
    429        avatar: "myimg7",
    430      },
    431      fetchAndCacheProfileResolves: true,
    432    },
    433    // profile retrieval when the cached profile is old but staleOk is true.
    434    {
    435      threshold: 1000,
    436      expectsCachedProfileReturned: true,
    437      cachedProfile: {
    438        uid: `${ACCOUNT_UID}8`,
    439        email: `${ACCOUNT_EMAIL}8`,
    440        avatar: "myimg8",
    441      },
    442      fetchAndCacheProfileResolves: false,
    443      options: { staleOk: true },
    444    },
    445    // staleOk but no cached profile
    446    {
    447      threshold: 1000,
    448      expectsCachedProfileReturned: false,
    449      cachedProfile: null,
    450      fetchedProfile: {
    451        uid: `${ACCOUNT_UID}9`,
    452        email: `${ACCOUNT_EMAIL}9`,
    453        avatar: "myimg9",
    454      },
    455      options: { staleOk: true },
    456    },
    457    // fresh profile but forceFresh = true
    458    {
    459      // Note: The threshold for this test case is being set to an arbitrary value that will
    460      // be greater than Date.now() so the retrieved cached profile will be deemed recent.
    461      threshold: Date.now() + 5000,
    462      expectsCachedProfileReturned: false,
    463      fetchedProfile: {
    464        uid: `${ACCOUNT_UID}10`,
    465        email: `${ACCOUNT_EMAIL}10`,
    466        avatar: "myimg10",
    467      },
    468      options: { forceFresh: true },
    469    },
    470  ];
    471 
    472  for (const tc of testCases) {
    473    print(`test case: ${JSON.stringify(tc)}`);
    474    let mockProfile = sinon.mock(profile);
    475    mockProfile
    476      .expects("_getProfileCache")
    477      .once()
    478      .returns(
    479        tc.cachedProfile
    480          ? {
    481              profile: tc.cachedProfile,
    482            }
    483          : null
    484      );
    485    profile.PROFILE_FRESHNESS_THRESHOLD = tc.threshold;
    486 
    487    let options = tc.options || {};
    488    if (tc.expectsCachedProfileReturned) {
    489      mockProfile.expects("_fetchAndCacheProfile").never();
    490      let actualProfile = await profile.ensureProfile(options);
    491      mockProfile.verify();
    492      Assert.equal(actualProfile, tc.cachedProfile);
    493    } else if (tc.fetchAndCacheProfileResolves) {
    494      mockProfile
    495        .expects("_fetchAndCacheProfile")
    496        .once()
    497        .resolves(tc.fetchedProfile);
    498 
    499      let actualProfile = await profile.ensureProfile(options);
    500      let expectedProfile = tc.fetchedProfile
    501        ? tc.fetchedProfile
    502        : tc.cachedProfile;
    503      mockProfile.verify();
    504      Assert.equal(actualProfile, expectedProfile);
    505    } else {
    506      mockProfile.expects("_fetchAndCacheProfile").once().rejects();
    507 
    508      let actualProfile = await profile.ensureProfile(options);
    509      mockProfile.verify();
    510      Assert.equal(actualProfile, tc.cachedProfile);
    511    }
    512  }
    513 });
    514 
    515 // Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the
    516 // last one *does* kick off a new request if ON_PROFILE_CHANGE_NOTIFICATION
    517 // is sent.
    518 add_task(async function fetchAndCacheProfileBeforeThresholdOnNotification() {
    519  let numFetches = 0;
    520  let client = mockClient(mockFxa());
    521  client.fetchProfile = async function () {
    522    numFetches += 1;
    523    return {
    524      body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" },
    525    };
    526  };
    527  let profile = CreateFxAccountsProfile(null, client);
    528  profile.PROFILE_FRESHNESS_THRESHOLD = 1000;
    529 
    530  // first fetch should return null as we don't have data.
    531  let p = await profile.getProfile();
    532  Assert.equal(p, null);
    533  // ensure we kicked off a fetch.
    534  Assert.notEqual(profile._currentFetchPromise, null);
    535  // wait for that fetch to finish
    536  await profile._currentFetchPromise;
    537  Assert.equal(numFetches, 1);
    538  Assert.equal(profile._currentFetchPromise, null);
    539 
    540  Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION);
    541 
    542  let origFetchAndCatch = profile._fetchAndCacheProfile;
    543  let backgroundFetchDone = Promise.withResolvers();
    544  profile._fetchAndCacheProfile = async () => {
    545    await origFetchAndCatch.call(profile);
    546    backgroundFetchDone.resolve();
    547  };
    548  await profile.getProfile();
    549  await backgroundFetchDone.promise;
    550  Assert.equal(numFetches, 2);
    551 });
    552 
    553 add_test(function tearDown_ok() {
    554  let profile = CreateFxAccountsProfile();
    555 
    556  Assert.ok(!!profile.client);
    557  Assert.ok(!!profile.fxai);
    558 
    559  profile.tearDown();
    560  Assert.equal(null, profile.fxai);
    561  Assert.equal(null, profile.client);
    562 
    563  run_next_test();
    564 });
    565 
    566 add_task(async function getProfile_ok() {
    567  let cachedUrl = "myurl";
    568  let didFetch = false;
    569 
    570  let fxa = mockFxa();
    571  fxa._testProfileCache = { profile: { uid: ACCOUNT_UID, avatar: cachedUrl } };
    572  let profile = CreateFxAccountsProfile(fxa);
    573 
    574  profile._fetchAndCacheProfile = function () {
    575    didFetch = true;
    576    return Promise.resolve();
    577  };
    578 
    579  let result = await profile.getProfile();
    580 
    581  Assert.equal(result.avatar, cachedUrl);
    582  Assert.ok(didFetch);
    583 });
    584 
    585 add_task(async function getProfile_no_cache() {
    586  let fetchedUrl = "newUrl";
    587  let fxa = mockFxa();
    588  let profile = CreateFxAccountsProfile(fxa);
    589 
    590  profile._fetchAndCacheProfileInternal = function () {
    591    return Promise.resolve({ uid: ACCOUNT_UID, avatar: fetchedUrl });
    592  };
    593 
    594  await profile.getProfile(); // returns null.
    595  let result = await profile._currentFetchPromise;
    596  Assert.equal(result.avatar, fetchedUrl);
    597 });
    598 
    599 add_test(function getProfile_has_cached_fetch_deleted() {
    600  let cachedUrl = "myurl";
    601 
    602  let fxa = mockFxa();
    603  let client = mockClient(fxa);
    604  client.fetchProfile = function () {
    605    return Promise.resolve({
    606      body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: null },
    607    });
    608  };
    609 
    610  let profile = CreateFxAccountsProfile(fxa, client);
    611  fxa._testProfileCache = {
    612    profile: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: cachedUrl },
    613  };
    614 
    615  // instead of checking this in a mocked "save" function, just check after the
    616  // observer
    617  makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function () {
    618    profile.getProfile().then(profileData => {
    619      Assert.equal(null, profileData.avatar);
    620      run_next_test();
    621    });
    622  });
    623 
    624  return profile.getProfile().then(result => {
    625    Assert.equal(result.avatar, "myurl");
    626  });
    627 });
    628 
    629 add_test(function getProfile_fetchAndCacheProfile_throws() {
    630  let fxa = mockFxa();
    631  fxa._testProfileCache = {
    632    profile: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" },
    633  };
    634  let profile = CreateFxAccountsProfile(fxa);
    635 
    636  profile._fetchAndCacheProfile = () => Promise.reject(new Error());
    637 
    638  return profile.getProfile().then(result => {
    639    Assert.equal(result.avatar, "myimg");
    640    run_next_test();
    641  });
    642 });
    643 
    644 add_test(function getProfile_email_changed() {
    645  let fxa = mockFxa();
    646  let client = mockClient(fxa);
    647  client.fetchProfile = function () {
    648    return Promise.resolve({
    649      body: { uid: ACCOUNT_UID, email: "newemail@bar.com" },
    650    });
    651  };
    652  fxa._internal._handleEmailUpdated = email => {
    653    Assert.equal(email, "newemail@bar.com");
    654    run_next_test();
    655  };
    656 
    657  let profile = CreateFxAccountsProfile(fxa, client);
    658  return profile._fetchAndCacheProfile();
    659 });
    660 
    661 function makeObserver(aObserveTopic, aObserveFunc) {
    662  let callback = function (aSubject, aTopic, aData) {
    663    log.debug("observed " + aTopic + " " + aData);
    664    if (aTopic == aObserveTopic) {
    665      removeMe();
    666      aObserveFunc(aSubject, aTopic, aData);
    667    }
    668  };
    669 
    670  function removeMe() {
    671    log.debug("removing observer for " + aObserveTopic);
    672    Services.obs.removeObserver(callback, aObserveTopic);
    673  }
    674 
    675  Services.obs.addObserver(callback, aObserveTopic);
    676  return removeMe;
    677 }