forked from mirrors/gecko-dev
		
	This patch changes ext-history.js and ext-browsingData.js to ensure that these API modules are importing PlacesUtils in the same way as ext-bookmarks.js, because mixing CU.defineModuleGetter and CU.import would raise an error and it would break those two WebExtensions APIs if the ext-bookmarks.js gets loaded first. Differential Revision: https://phabricator.services.mozilla.com/D18683 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			315 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			315 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* vim: set sts=2 sw=2 et tw=80: */
 | 
						|
"use strict";
 | 
						|
 | 
						|
 | 
						|
var {PlacesUtils} = ChromeUtils.import("resource://gre/modules/PlacesUtils.jsm");
 | 
						|
 | 
						|
ChromeUtils.defineModuleGetter(this, "Preferences",
 | 
						|
                               "resource://gre/modules/Preferences.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(this, "Sanitizer",
 | 
						|
                               "resource:///modules/Sanitizer.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(this, "Services",
 | 
						|
                               "resource://gre/modules/Services.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(this, "setTimeout",
 | 
						|
                               "resource://gre/modules/Timer.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(this, "ServiceWorkerCleanUp",
 | 
						|
                               "resource://gre/modules/ServiceWorkerCleanUp.jsm");
 | 
						|
 | 
						|
XPCOMUtils.defineLazyServiceGetter(this, "quotaManagerService",
 | 
						|
                                   "@mozilla.org/dom/quota-manager-service;1",
 | 
						|
                                   "nsIQuotaManagerService");
 | 
						|
 | 
						|
/**
 | 
						|
* A number of iterations after which to yield time back
 | 
						|
* to the system.
 | 
						|
*/
 | 
						|
const YIELD_PERIOD = 10;
 | 
						|
 | 
						|
const makeRange = options => {
 | 
						|
  return (options.since == null) ?
 | 
						|
    null :
 | 
						|
    [PlacesUtils.toPRTime(options.since), PlacesUtils.toPRTime(Date.now())];
 | 
						|
};
 | 
						|
 | 
						|
const clearCache = () => {
 | 
						|
  // Clearing the cache does not support timestamps.
 | 
						|
  return Sanitizer.items.cache.clear();
 | 
						|
};
 | 
						|
 | 
						|
const clearCookies = async function(options) {
 | 
						|
  let cookieMgr = Services.cookies;
 | 
						|
  // This code has been borrowed from Sanitizer.jsm.
 | 
						|
  let yieldCounter = 0;
 | 
						|
 | 
						|
  if (options.since || options.hostnames) {
 | 
						|
    // Iterate through the cookies and delete any created after our cutoff.
 | 
						|
    for (const cookie of cookieMgr.enumerator) {
 | 
						|
      if ((!options.since || cookie.creationTime >= PlacesUtils.toPRTime(options.since)) &&
 | 
						|
          (!options.hostnames || options.hostnames.includes(cookie.host.replace(/^\./, "")))) {
 | 
						|
        // This cookie was created after our cutoff, clear it.
 | 
						|
        cookieMgr.remove(cookie.host, cookie.name, cookie.path,
 | 
						|
                         false, cookie.originAttributes);
 | 
						|
 | 
						|
        if (++yieldCounter % YIELD_PERIOD == 0) {
 | 
						|
          await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // Remove everything.
 | 
						|
    cookieMgr.removeAll();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const clearDownloads = options => {
 | 
						|
  return Sanitizer.items.downloads.clear(makeRange(options));
 | 
						|
};
 | 
						|
 | 
						|
const clearFormData = options => {
 | 
						|
  return Sanitizer.items.formdata.clear(makeRange(options));
 | 
						|
};
 | 
						|
 | 
						|
const clearHistory = options => {
 | 
						|
  return Sanitizer.items.history.clear(makeRange(options));
 | 
						|
};
 | 
						|
 | 
						|
const clearIndexedDB = async function(options) {
 | 
						|
  let promises = [];
 | 
						|
 | 
						|
  await new Promise((resolve, reject) => {
 | 
						|
    quotaManagerService.getUsage(request => {
 | 
						|
      if (request.resultCode != Cr.NS_OK) {
 | 
						|
        reject({message: "Clear indexedDB failed"});
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      for (let item of request.result) {
 | 
						|
        let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
 | 
						|
        let scheme = principal.URI.scheme;
 | 
						|
        if (scheme == "http" || scheme == "https" || scheme == "file") {
 | 
						|
          promises.push(new Promise((resolve, reject) => {
 | 
						|
            let clearRequest = quotaManagerService.clearStoragesForPrincipal(principal, null, "idb");
 | 
						|
            clearRequest.callback = () => {
 | 
						|
              if (clearRequest.resultCode == Cr.NS_OK) {
 | 
						|
                resolve();
 | 
						|
              } else {
 | 
						|
                reject({message: "Clear indexedDB failed"});
 | 
						|
              }
 | 
						|
            };
 | 
						|
          }));
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      resolve();
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  return Promise.all(promises);
 | 
						|
};
 | 
						|
 | 
						|
const clearLocalStorage = async function(options) {
 | 
						|
  if (options.since) {
 | 
						|
    return Promise.reject(
 | 
						|
      {message: "Firefox does not support clearing localStorage with 'since'."});
 | 
						|
  }
 | 
						|
 | 
						|
  // The legacy LocalStorage implementation that will eventually be removed
 | 
						|
  // depends on this observer notification.  Some other subsystems like
 | 
						|
  // Reporting headers depend on this too.
 | 
						|
  if (options.hostnames) {
 | 
						|
    for (let hostname of options.hostnames) {
 | 
						|
      Services.obs.notifyObservers(null, "extension:purge-localStorage", hostname);
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    Services.obs.notifyObservers(null, "extension:purge-localStorage");
 | 
						|
  }
 | 
						|
 | 
						|
  if (Services.lsm.nextGenLocalStorageEnabled) {
 | 
						|
    // Ideally we could reuse the logic in Sanitizer.jsm or nsIClearDataService,
 | 
						|
    // but this API exposes an ability to wipe data at a much finger granularity
 | 
						|
    // than those APIs.  So custom logic is used here to wipe only the QM
 | 
						|
    // localStorage client (when in use).
 | 
						|
 | 
						|
    let promises = [];
 | 
						|
 | 
						|
    await new Promise((resolve, reject) => {
 | 
						|
      quotaManagerService.getUsage(request => {
 | 
						|
        if (request.resultCode != Cr.NS_OK) {
 | 
						|
          reject({message: "Clear localStorage failed"});
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        for (let item of request.result) {
 | 
						|
          let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
 | 
						|
          let host = principal.URI.hostPort;
 | 
						|
          if (!options.hostnames || options.hostnames.includes(host)) {
 | 
						|
            promises.push(new Promise((resolve, reject) => {
 | 
						|
              let clearRequest = quotaManagerService.clearStoragesForPrincipal(principal, "default", "ls");
 | 
						|
              clearRequest.callback = () => {
 | 
						|
                if (clearRequest.resultCode == Cr.NS_OK) {
 | 
						|
                  resolve();
 | 
						|
                } else {
 | 
						|
                  reject({message: "Clear localStorage failed"});
 | 
						|
                }
 | 
						|
              };
 | 
						|
            }));
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        resolve();
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    return Promise.all(promises);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const clearPasswords = async function(options) {
 | 
						|
  let loginManager = Services.logins;
 | 
						|
  let yieldCounter = 0;
 | 
						|
 | 
						|
  if (options.since) {
 | 
						|
    // Iterate through the logins and delete any updated after our cutoff.
 | 
						|
    let logins = loginManager.getAllLogins();
 | 
						|
    for (let login of logins) {
 | 
						|
      login.QueryInterface(Ci.nsILoginMetaInfo);
 | 
						|
      if (login.timePasswordChanged >= options.since) {
 | 
						|
        loginManager.removeLogin(login);
 | 
						|
        if (++yieldCounter % YIELD_PERIOD == 0) {
 | 
						|
          await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // Remove everything.
 | 
						|
    loginManager.removeAllLogins();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const clearPluginData = options => {
 | 
						|
  return Sanitizer.items.pluginData.clear(makeRange(options));
 | 
						|
};
 | 
						|
 | 
						|
const doRemoval = (options, dataToRemove, extension) => {
 | 
						|
  if (options.originTypes &&
 | 
						|
      (options.originTypes.protectedWeb || options.originTypes.extension)) {
 | 
						|
    return Promise.reject(
 | 
						|
      {message: "Firefox does not support protectedWeb or extension as originTypes."});
 | 
						|
  }
 | 
						|
 | 
						|
  let removalPromises = [];
 | 
						|
  let invalidDataTypes = [];
 | 
						|
  for (let dataType in dataToRemove) {
 | 
						|
    if (dataToRemove[dataType]) {
 | 
						|
      switch (dataType) {
 | 
						|
        case "cache":
 | 
						|
          removalPromises.push(clearCache());
 | 
						|
          break;
 | 
						|
        case "cookies":
 | 
						|
          removalPromises.push(clearCookies(options));
 | 
						|
          break;
 | 
						|
        case "downloads":
 | 
						|
          removalPromises.push(clearDownloads(options));
 | 
						|
          break;
 | 
						|
        case "formData":
 | 
						|
          removalPromises.push(clearFormData(options));
 | 
						|
          break;
 | 
						|
        case "history":
 | 
						|
          removalPromises.push(clearHistory(options));
 | 
						|
          break;
 | 
						|
        case "indexedDB":
 | 
						|
          removalPromises.push(clearIndexedDB(options));
 | 
						|
          break;
 | 
						|
        case "localStorage":
 | 
						|
          removalPromises.push(clearLocalStorage(options));
 | 
						|
          break;
 | 
						|
        case "passwords":
 | 
						|
          removalPromises.push(clearPasswords(options));
 | 
						|
          break;
 | 
						|
        case "pluginData":
 | 
						|
          removalPromises.push(clearPluginData(options));
 | 
						|
          break;
 | 
						|
        case "serviceWorkers":
 | 
						|
          removalPromises.push(ServiceWorkerCleanUp.removeAll());
 | 
						|
          break;
 | 
						|
        default:
 | 
						|
          invalidDataTypes.push(dataType);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (extension && invalidDataTypes.length) {
 | 
						|
    extension.logger.warn(
 | 
						|
      `Firefox does not support dataTypes: ${invalidDataTypes.toString()}.`);
 | 
						|
  }
 | 
						|
  return Promise.all(removalPromises);
 | 
						|
};
 | 
						|
 | 
						|
this.browsingData = class extends ExtensionAPI {
 | 
						|
  getAPI(context) {
 | 
						|
    let {extension} = context;
 | 
						|
    return {
 | 
						|
      browsingData: {
 | 
						|
        settings() {
 | 
						|
          const PREF_DOMAIN = "privacy.cpd.";
 | 
						|
          // The following prefs are the only ones in Firefox that match corresponding
 | 
						|
          // values used by Chrome when rerturning settings.
 | 
						|
          const PREF_LIST = ["cache", "cookies", "history", "formdata", "downloads"];
 | 
						|
 | 
						|
          // since will be the start of what is returned by Sanitizer.getClearRange
 | 
						|
          // divided by 1000 to convert to ms.
 | 
						|
          // If Sanitizer.getClearRange returns undefined that means the range is
 | 
						|
          // currently "Everything", so we should set since to 0.
 | 
						|
          let clearRange = Sanitizer.getClearRange();
 | 
						|
          let since = clearRange ? clearRange[0] / 1000 : 0;
 | 
						|
          let options = {since};
 | 
						|
 | 
						|
          let dataToRemove = {};
 | 
						|
          let dataRemovalPermitted = {};
 | 
						|
 | 
						|
          for (let item of PREF_LIST) {
 | 
						|
            // The property formData needs a different case than the
 | 
						|
            // formdata preference.
 | 
						|
            const name = item === "formdata" ? "formData" : item;
 | 
						|
            dataToRemove[name] = Preferences.get(`${PREF_DOMAIN}${item}`);
 | 
						|
            // Firefox doesn't have the same concept of dataRemovalPermitted
 | 
						|
            // as Chrome, so it will always be true.
 | 
						|
            dataRemovalPermitted[name] = true;
 | 
						|
          }
 | 
						|
 | 
						|
          return Promise.resolve({options, dataToRemove, dataRemovalPermitted});
 | 
						|
        },
 | 
						|
        remove(options, dataToRemove) {
 | 
						|
          return doRemoval(options, dataToRemove, extension);
 | 
						|
        },
 | 
						|
        removeCache(options) {
 | 
						|
          return doRemoval(options, {cache: true});
 | 
						|
        },
 | 
						|
        removeCookies(options) {
 | 
						|
          return doRemoval(options, {cookies: true});
 | 
						|
        },
 | 
						|
        removeDownloads(options) {
 | 
						|
          return doRemoval(options, {downloads: true});
 | 
						|
        },
 | 
						|
        removeFormData(options) {
 | 
						|
          return doRemoval(options, {formData: true});
 | 
						|
        },
 | 
						|
        removeHistory(options) {
 | 
						|
          return doRemoval(options, {history: true});
 | 
						|
        },
 | 
						|
        removeIndexedDB(options) {
 | 
						|
          return doRemoval(options, {indexedDB: true});
 | 
						|
        },
 | 
						|
        removeLocalStorage(options) {
 | 
						|
          return doRemoval(options, {localStorage: true});
 | 
						|
        },
 | 
						|
        removePasswords(options) {
 | 
						|
          return doRemoval(options, {passwords: true});
 | 
						|
        },
 | 
						|
        removePluginData(options) {
 | 
						|
          return doRemoval(options, {pluginData: true});
 | 
						|
        },
 | 
						|
      },
 | 
						|
    };
 | 
						|
  }
 | 
						|
};
 |