Bug 1751093 - Clean up Cloud Storage API and its usage in about:preferences r=preferences-reviewers,Gijs

Differential Revision: https://phabricator.services.mozilla.com/D137248
This commit is contained in:
Punam Dahiya 2022-04-07 10:17:13 +00:00
parent 9bd876e7c3
commit 2b39c9ff18
19 changed files with 13 additions and 1445 deletions

View File

@ -425,10 +425,6 @@
class="accessory-button"
data-l10n-id="download-choose-folder"/>
</hbox>
<!-- Additional radio button added to support CloudStorage - Bug 1357171 -->
<radio id="saveToCloud"
value="true"
hidden="true"/>
<radio id="alwaysAsk"
value="false"
data-l10n-id="download-always-ask-where"/>

View File

@ -724,7 +724,6 @@ var gMainPane = {
setEventListener("typeColumn", "click", gMainPane.sort);
setEventListener("actionColumn", "click", gMainPane.sort);
setEventListener("chooseFolder", "command", gMainPane.chooseFolder);
setEventListener("saveWhere", "command", gMainPane.handleSaveToCommand);
Preferences.get("browser.download.folderList").on(
"change",
gMainPane.displayDownloadDirPref.bind(gMainPane)
@ -2920,8 +2919,6 @@ var gMainPane = {
* 1 - The system's downloads folder is the default download location.
* 2 - The default download location is elsewhere as specified in
* browser.download.dir.
* 3 - The default download location is elsewhere as specified by
* cloud storage API getDownloadFolder
* browser.download.downloadDir
* deprecated.
* browser.download.defaultFolder
@ -2945,102 +2942,10 @@ var gMainPane = {
chooseFolder.disabled =
!useDownloadDirPreference.value || dirPreference.locked;
this.readCloudStorage().catch(Cu.reportError);
// don't override the preference's value in UI
return undefined;
},
/**
* Show/Hide the cloud storage radio button with provider name as label if
* cloud storage provider is in use.
* Select cloud storage radio button if browser.download.useDownloadDir is true
* and browser.download.folderList has value 3. Enables/disables the folder field
* and Browse button if cloud storage radio button is selected.
*
*/
async readCloudStorage() {
// Get preferred provider in use display name
let providerDisplayName = await CloudStorage.getProviderIfInUse();
if (providerDisplayName) {
// Show cloud storage radio button with provider name in label
let saveToCloudRadio = document.getElementById("saveToCloud");
document.l10n.setAttributes(
saveToCloudRadio,
"save-files-to-cloud-storage",
{
"service-name": providerDisplayName,
}
);
saveToCloudRadio.hidden = false;
let useDownloadDirPref = Preferences.get(
"browser.download.useDownloadDir"
);
let folderListPref = Preferences.get("browser.download.folderList");
// Check if useDownloadDir is true and folderListPref is set to Cloud Storage value 3
// before selecting cloudStorageradio button. Disable folder field and Browse button if
// 'Save to Cloud Storage Provider' radio option is selected
if (useDownloadDirPref.value && folderListPref.value === 3) {
document.getElementById("saveWhere").selectedItem = saveToCloudRadio;
document.getElementById("downloadFolder").disabled = true;
document.getElementById("chooseFolder").disabled = true;
}
}
},
/**
* Handle clicks to 'Save To <custom path> or <system default downloads>' and
* 'Save to <cloud storage provider>' if cloud storage radio button is displayed in UI.
* Sets browser.download.folderList value and Enables/disables the folder field and Browse
* button based on option selected.
*/
handleSaveToCommand(event) {
return this.handleSaveToCommandTask(event).catch(Cu.reportError);
},
async handleSaveToCommandTask(event) {
if (event.target.id !== "saveToCloud" && event.target.id !== "saveTo") {
return;
}
// Check if Save To Cloud Storage Provider radio option is displayed in UI
// before continuing.
let saveToCloudRadio = document.getElementById("saveToCloud");
if (!saveToCloudRadio.hidden) {
// When switching between SaveTo and SaveToCloud radio button
// with useDownloadDirPref value true, if selectedIndex is other than
// SaveTo radio button disable downloadFolder filefield and chooseFolder button
let saveWhere = document.getElementById("saveWhere");
let useDownloadDirPref = Preferences.get(
"browser.download.useDownloadDir"
);
if (useDownloadDirPref.value) {
let downloadFolder = document.getElementById("downloadFolder");
let chooseFolder = document.getElementById("chooseFolder");
downloadFolder.disabled =
saveWhere.selectedIndex || useDownloadDirPref.locked;
chooseFolder.disabled =
saveWhere.selectedIndex || useDownloadDirPref.locked;
}
// Set folderListPref value depending on radio option
// selected. folderListPref should be set to 3 if Save To Cloud Storage Provider
// option is selected. If user switch back to 'Save To' custom path or system
// default Downloads, check pref 'browser.download.dir' before setting respective
// folderListPref value. If currentDirPref is unspecified folderList should
// default to 1
let folderListPref = Preferences.get("browser.download.folderList");
let saveTo = document.getElementById("saveTo");
if (saveWhere.selectedItem == saveToCloudRadio) {
folderListPref.value = 3;
} else if (saveWhere.selectedItem == saveTo) {
let currentDirPref = Preferences.get("browser.download.dir");
folderListPref.value = currentDirPref.value
? await this._folderToIndex(currentDirPref.value)
: 1;
}
}
},
/**
* Displays a file picker in which the user can choose the location where
* downloads are automatically saved, updating preferences and UI in
@ -3120,11 +3025,9 @@ var gMainPane = {
var iconUrlSpec;
let folderIndex = folderListPref.value;
// For legacy users using cloudstorage pref with folderIndex as 3 (See bug 1751093),
// compute folderIndex using value in currentDirPref
if (folderIndex == 3) {
// When user has selected cloud storage, use value in currentDirPref to
// compute index to display download folder label and icon to avoid
// displaying blank downloadFolder label and icon on load of preferences UI
// Set folderIndex to 1 if currentDirPref is unspecified
folderIndex = currentDirPref.value
? await this._folderToIndex(currentDirPref.value)
: 1;

View File

@ -62,7 +62,6 @@ XPCOMUtils.defineLazyServiceGetters(this, {
XPCOMUtils.defineLazyModuleGetters(this, {
AMTelemetry: "resource://gre/modules/AddonManager.jsm",
BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
CloudStorage: "resource://gre/modules/CloudStorage.jsm",
ContextualIdentityService:
"resource://gre/modules/ContextualIdentityService.jsm",
DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",

View File

@ -58,7 +58,6 @@ support-files =
[browser_change_app_handler.js]
skip-if = os != "win" # Windows-specific handler application selection dialog
[browser_checkspelling.js]
[browser_cloud_storage.js]
[browser_connection.js]
[browser_connection_bug388287.js]
[browser_connection_bug1445991.js]

View File

@ -1,159 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.defineModuleGetter(
this,
"CloudStorage",
"resource://gre/modules/CloudStorage.jsm"
);
const DROPBOX_DOWNLOAD_FOLDER = "Dropbox";
const CLOUD_SERVICES_PREF = "cloud.services.";
function create_subdir(dir, subdirname) {
let subdir = dir.clone();
subdir.append(subdirname);
if (subdir.exists()) {
subdir.remove(true);
}
subdir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
return subdir;
}
/**
* Replaces a directory service entry with a given nsIFile.
*/
function registerFakePath(key, folderName) {
let dirsvc = Services.dirsvc.QueryInterface(Ci.nsIProperties);
// Create a directory inside the profile and register it as key
let profD = dirsvc.get("ProfD", Ci.nsIFile);
// create a subdir just to keep our files out of the way
let file = create_subdir(profD, folderName);
let originalFile;
try {
// If a file is already provided save it and undefine, otherwise set will
// throw for persistent entries (ones that are cached).
originalFile = dirsvc.get(key, Ci.nsIFile);
dirsvc.undefine(key);
} catch (e) {
// dirsvc.get will throw if nothing provides for the key and dirsvc.undefine
// will throw if it's not a persistent entry, in either case we don't want
// to set the original file in cleanup.
originalFile = undefined;
}
dirsvc.set(key, file);
registerCleanupFunction(() => {
dirsvc.undefine(key);
if (originalFile) {
dirsvc.set(key, originalFile);
}
});
}
async function mock_dropbox() {
// Mock Dropbox Download folder in Home directory
let downloadFolder = PathUtils.join(
Services.dirsvc.get("Home", Ci.nsIFile).path,
DROPBOX_DOWNLOAD_FOLDER,
"Downloads"
);
await IOUtils.makeDirectory(downloadFolder);
console.log(downloadFolder);
registerCleanupFunction(async () => {
await IOUtils.remove(downloadFolder, {
recursive: true,
ignoreAbsent: true,
});
});
}
add_setup(async function() {
// Create mock Dropbox download folder for cloudstorage API
// Set prefs required to display second radio option
// 'Save to Dropbox' under Downloads
let folderName = "CloudStorage";
registerFakePath("Home", folderName);
await mock_dropbox();
await SpecialPowers.pushPrefEnv({
set: [
[CLOUD_SERVICES_PREF + "api.enabled", true],
[CLOUD_SERVICES_PREF + "storage.key", "Dropbox"],
],
});
});
add_task(async function test_initProvider() {
// Get preferred provider key
let preferredProvider = await CloudStorage.getPreferredProvider();
is(preferredProvider, "Dropbox", "Cloud Storage preferred provider key");
let isInitialized = await CloudStorage.init();
is(isInitialized, true, "Providers Metadata successfully initialized");
// Get preferred provider in use display name
let providerDisplayName = await CloudStorage.getProviderIfInUse();
is(
providerDisplayName,
"Dropbox",
"Cloud Storage preferred provider display name"
);
});
add_task(async function() {
await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
let doc = gBrowser.selectedBrowser.contentDocument;
let saveWhereOptions = doc.getElementById("saveWhere");
let saveToCloud = doc.getElementById("saveToCloud");
is(saveWhereOptions.itemCount, 3, "Radio options count");
is_element_visible(saveToCloud, "Save to Dropbox option is visible");
let saveTo = doc.getElementById("saveTo");
ok(saveTo.selected, "Ensure first option is selected by default");
is(
Services.prefs.getIntPref("browser.download.folderList"),
1,
"Set to system downloadsfolder as the default download location"
);
let downloadFolder = doc.getElementById("downloadFolder");
let chooseFolder = doc.getElementById("chooseFolder");
is(downloadFolder.disabled, false, "downloadFolder filefield is enabled");
is(chooseFolder.disabled, false, "chooseFolder button is enabled");
// Test click of second radio option sets browser.download.folderList as 3
// which means the default download location is elsewhere as specified by
// cloud storage API getDownloadFolder and pref cloud.services.storage.key
saveToCloud.click();
is(
Services.prefs.getIntPref("browser.download.folderList"),
3,
"Default download location is elsewhere as specified by cloud storage API"
);
is(downloadFolder.disabled, true, "downloadFolder filefield is disabled");
is(chooseFolder.disabled, true, "chooseFolder button is disabled");
// Test selecting first radio option enables downloadFolder filefield and chooseFolder button
saveTo.click();
is(downloadFolder.disabled, false, "downloadFolder filefield is enabled");
is(chooseFolder.disabled, false, "chooseFolder button is enabled");
// Test selecting third radio option keeps downloadFolder and chooseFolder elements disabled
let alwaysAsk = doc.getElementById("alwaysAsk");
saveToCloud.click();
alwaysAsk.click();
is(downloadFolder.disabled, true, "downloadFolder filefield is disabled");
is(chooseFolder.disabled, true, "chooseFolder button is disabled");
saveTo.click();
ok(saveTo.selected, "Reset back first option as selected by default");
gBrowser.removeCurrentTab();
});

View File

@ -1404,8 +1404,3 @@ httpsonly-radio-disabled =
desktop-folder-name = Desktop
downloads-folder-name = Downloads
choose-download-folder-title = Choose Download Folder:
# Variables:
# $service-name (String) - Name of a cloud storage provider like Dropbox, Google Drive, etc...
save-files-to-cloud-storage =
.label = Save files to { $service-name }

View File

@ -2111,7 +2111,6 @@
"toolkit/components/captivedetect/test/unit/xpcshell.ini": 14.38,
"toolkit/components/cascade_bloom_filter/test/xpcshell/xpcshell.ini": 1.22,
"toolkit/components/cleardata/tests/unit/xpcshell.ini": 17.69,
"toolkit/components/cloudstorage/tests/unit/xpcshell.ini": 2.12,
"toolkit/components/commandlines/test/unit/xpcshell.ini": 2.35,
"toolkit/components/commandlines/test/unit_unix/xpcshell.ini": 1.0,
"toolkit/components/contentprefs/tests/unit_cps2/xpcshell.ini": 38.97,

View File

@ -2106,7 +2106,6 @@
"toolkit/components/captivedetect/test/unit/xpcshell.ini": 6.31,
"toolkit/components/cascade_bloom_filter/test/xpcshell/xpcshell.ini": 0.44,
"toolkit/components/cleardata/tests/unit/xpcshell.ini": 7.83,
"toolkit/components/cloudstorage/tests/unit/xpcshell.ini": 11.11,
"toolkit/components/commandlines/test/unit/xpcshell.ini": 1.35,
"toolkit/components/commandlines/test/unit_win/xpcshell.ini": 0.49,
"toolkit/components/contentprefs/tests/unit_cps2/xpcshell.ini": 19.36,

View File

@ -1,769 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Java Script module that helps consumers store data directly
* to cloud storage provider download folders.
*
* Takes cloud storage providers metadata as JSON input on Mac, Linux and Windows.
*
* Handles scan, prompt response save and exposes preferred storage provider.
*/
"use strict";
var EXPORTED_SYMBOLS = ["CloudStorage"];
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
ChromeUtils.defineModuleGetter(
this,
"Downloads",
"resource://gre/modules/Downloads.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"FileUtils",
"resource://gre/modules/FileUtils.jsm"
);
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
const CLOUD_SERVICES_PREF = "cloud.services.";
const CLOUD_PROVIDERS_URI = "resource://cloudstorage/providers.json";
/**
* Provider metadata JSON is loaded from resource://cloudstorage/providers.json
* Sample providers.json format
*
* {
* "Dropbox": {
* "displayName": "Dropbox",
* "relativeDownloadPath": ["homeDir", "Dropbox"],
* "relativeDiscoveryPath": {
* "linux": ["homeDir", ".dropbox", "info.json"],
* "macosx": ["homeDir", ".dropbox", "info.json"],
* "win": ["LocalAppData", "Dropbox", "info.json"]
* },
* "typeSpecificData": {
* "default": "Downloads",
* "screenshot": "Screenshots"
* }
* }
*
* Providers JSON is flat list of providers metdata with property as key in format @Provider
*
* @Provider - Unique cloud provider key, possible values: "Dropbox", "GDrive"
*
* @displayName - cloud storage name displayed in the prompt.
*
* @relativeDownloadPath - download path on user desktop for a cloud storage provider.
* By default downloadPath is a concatenation of home dir and name of dropbox folder.
* Example value: ["homeDir", "Dropbox"]
*
* @relativeDiscoveryPath - Lists discoveryPath by platform. Provider is not supported on a platform
* if its value doesn't exist in relativeDiscoveryPath. relativeDiscoveryPath by platform is stored
* as an array ofsubdirectories, which when concatenated, forms discovery path.
* During scan discoveryPath is checked for the existence of cloud storage provider on user desktop.
*
* @typeSpecificData - provides folder name for a cloud storage depending
* on type of data downloaded. Default folder is 'Downloads'. Other options are
* 'screenshot' depending on provider support.
*/
/**
*
* Internal cloud services prefs
*
* cloud.services.api.enabled - set to true to initialize and use Cloud Storage module
*
* cloud.services.storage.key - set to string with preferred provider key
*
* cloud.services.lastPrompt - set to time when last prompt was shown
*
* cloud.services.interval.prompt - set to time interval in days after which prompt should be shown
*
* cloud.services.rejected.key - set to string with comma separated provider keys rejected
* by user when prompted to opt-in
*
* browser.download.folderList - set to int and indicates the location users wish to save downloaded files to.
* 0 - The desktop is the default download location.
* 1 - The system's downloads folder is the default download location.
* 2 - The default download location is elsewhere as specified in
* browser.download.dir.
* 3 - The default download location is elsewhere as specified by
* cloud storage API getDownloadFolder
*
* browser.download.dir - local file handle
* A local folder user may have selected for downloaded files to be
* saved. This folder is enabled when folderList equals 2.
*/
/**
* The external API exported by this module.
*/
var CloudStorage = {
/**
* Init method to initialize providers metadata
*/
async init() {
let isInitialized = null;
try {
// Invoke internal method asynchronously to read and
// parse providers metadata from JSON
isInitialized = await CloudStorageInternal.initProviders();
} catch (err) {
Cu.reportError(err);
}
return isInitialized;
},
/**
* A promise to wait for init to complete, used for tests.
*/
get promiseInit() {
return CloudStorageInternal.promiseInit;
},
/**
* Returns information to allow the consumer to decide whether showing
* a doorhanger prompt is appropriate. If a preferred provider is set
* on desktop, user is not prompted again and method returns null.
*
* @return {Promise} which resolves to an object with property name
* as 'key' and 'value'.
* 'key' property is provider key such as 'Dropbox', 'GDrive'.
* 'value' property contains metadata for respective provider.
* Resolves null if it's not appropriate to prompt.
*/
promisePromptInfo() {
return CloudStorageInternal.promisePromptInfo();
},
/**
* Save user response from doorhanger prompt.
* If user confirms and checks 'always remember', update prefs
* cloud.services.storage.key and browser.download.folderList to pick
* download location from cloud storage API
* If user denies, save provider as rejected in cloud.services.rejected.key
*
* @param key
* cloud storage provider key from provider metadata
* @param remember
* bool value indicating whether user has asked to always remember
* the settings
* @param selected
* bool value by default set to false indicating if user has selected
* to save downloaded file with cloud provider
*/
savePromptResponse(key, remember, selected = false) {
Services.prefs.setIntPref(
CLOUD_SERVICES_PREF + "lastprompt",
Math.floor(Date.now() / 1000)
);
if (remember) {
if (selected) {
CloudStorageInternal.setCloudStoragePref(key);
} else {
// Store provider as rejected by setting cloud.services.rejected.key
// and not use in re-prompt
CloudStorageInternal.handleRejected(key);
}
}
},
/**
* Retrieve download folder of an opted-in storage provider
* by type specific data
* @param typeSpecificData
* type of data downloaded, options are 'default', 'screenshot'
* @return {Promise} which resolves to full path to provider download folder
*/
getDownloadFolder(typeSpecificData) {
return CloudStorageInternal.getDownloadFolder(typeSpecificData);
},
/**
* Get key of provider opted-in by user to store downloaded files
*
* @return {String}
* Storage provider key from provider metadata. Return empty string
* if user has not selected a preferred provider.
*/
getPreferredProvider() {
return CloudStorageInternal.preferredProviderKey;
},
/**
* Get metadata of provider opted-in by user to store downloaded files.
* Return preferred provider metadata without scanning by doing simple lookup
* inside storage providers metadata using preferred provider key
*
* @return {Object}
* Object with preferred provider metadata. Return null
* if user has not selected a preferred provider.
*/
getPreferredProviderMetaData() {
return CloudStorageInternal.getPreferredProviderMetaData();
},
/**
* Get display name of a provider actively in use to store downloaded files
*
* @return {String}
* String with provider display name. Returns null if a provider
* is not in use.
*/
getProviderIfInUse() {
return CloudStorageInternal.getProviderIfInUse();
},
/**
* Get providers found on user desktop. Used for unit tests
*
* @return {Promise}
* @resolves
* Map object with entries key set to storage provider key and values set to
* storage provider metadata
*/
getStorageProviders() {
return CloudStorageInternal.getStorageProviders();
},
};
/**
* The internal API for the CloudStorage module.
*/
var CloudStorageInternal = {
/**
* promiseInit saves returned init method promise and is
* used to wait for initialization to complete.
*/
promiseInit: null,
/**
* Internal property having storage providers data
*/
providersMetaData: null,
async _downloadJSON(uri) {
let json = null;
try {
let response = await fetch(uri);
if (response.ok) {
json = await response.json();
}
} catch (e) {
Cu.reportError("Fetching " + uri + " results in error: " + e);
}
return json;
},
/**
* Reset 'browser.download.folderList' cloud storage value '3' back
* to '2' or '1' depending on custom path or system default Downloads path
* in pref 'browser.download.dir'.
*/
async resetFolderListPref() {
let folderListValue = Services.prefs.getIntPref(
"browser.download.folderList",
0
);
if (folderListValue !== 3) {
return;
}
let downloadDirPath = null;
try {
let file = Services.prefs.getComplexValue(
"browser.download.dir",
Ci.nsIFile
);
downloadDirPath = file.path;
} catch (e) {}
if (
!downloadDirPath ||
downloadDirPath === (await Downloads.getSystemDownloadsDirectory())
) {
// if downloadDirPath is the Downloads folder path or unspecified
folderListValue = 1;
} else if (
downloadDirPath === Services.dirsvc.get("Desk", Ci.nsIFile).path
) {
// if downloadDirPath is the Desktop path
folderListValue = 0;
} else {
// otherwise
folderListValue = 2;
}
Services.prefs.setIntPref("browser.download.folderList", folderListValue);
},
/**
* Loads storage providers metadata asynchronously from providers.json.
*
* @returns {Promise} with resolved boolean value true if providers
* metadata is successfully initialized
*/
async initProviders() {
// Cloud Storage API should continue initialization and load providers metadata
// only if a consumer add-on using API sets pref 'cloud.services.api.enabled' to true
// If API is not enabled, check and reset cloud storage value in folderList pref.
if (!this.isAPIEnabled) {
this.resetFolderListPref().catch(err => {
Cu.reportError("CloudStorage: Failed to reset folderList pref " + err);
});
return false;
}
let response = await this._downloadJSON(CLOUD_PROVIDERS_URI);
this.providersMetaData = await this._parseProvidersJSON(response);
let providersCount = Object.keys(this.providersMetaData).length;
if (providersCount > 0) {
// Array of boolean results for each provider handled for custom downloadpath
let handledProviders = await this.initDownloadPathIfProvidersExist();
if (handledProviders.length === providersCount) {
return true;
}
}
return false;
},
/**
* Load parsed metadata inside providers object
*/
_parseProvidersJSON(providers) {
if (!providers) {
return {};
}
// Use relativeDiscoveryPath to filter providers object by platform.
// DownloadPath and discoveryPath are stored as
// array of subdirectories inside providers.json
// Update providers object discoveryPath and downloadPath
// property values by concatenating subdirectories and forming platform
// specific directory path
Object.getOwnPropertyNames(providers).forEach(key => {
if (
providers[key].relativeDiscoveryPath.hasOwnProperty(
AppConstants.platform
)
) {
providers[key].discoveryPath = this._concatPath(
providers[key].relativeDiscoveryPath[AppConstants.platform]
);
providers[key].downloadPath = this._concatPath(
providers[key].relativeDownloadPath
);
} else {
// delete key not supported on AppConstants.platform
delete providers[key];
}
});
return providers;
},
/**
* Concatenate subdir value inside array to form
* platform specific directory path
*
* @param arrDirs
* String Array containing sub directories name
* @returns Path of type String
*/
_concatPath(arrDirs) {
let dirPath = "";
for (let subDir of arrDirs) {
switch (subDir) {
case "homeDir":
subDir = OS.Constants.Path.homeDir ? OS.Constants.Path.homeDir : "";
break;
case "LocalAppData":
if (OS.Constants.Win) {
let nsIFileLocal = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
subDir = nsIFileLocal && nsIFileLocal.path ? nsIFileLocal.path : "";
} else {
subDir = "";
}
break;
}
dirPath = OS.Path.join(dirPath, subDir);
}
return dirPath;
},
/**
* Check for custom download paths and override providers metadata
* downloadPath property
*
* For dropbox open config file ~/.dropbox/info.json
* and override downloadPath with path found
* See https://www.dropbox.com/en/help/desktop-web/find-folder-paths
*
* For all other providers we are using downloadpath from providers.json
*
* @returns {Promise} with array boolean values for respective provider. Value is true if a
* provider exist on user desktop and its downloadPath is updated. Promise returns with
* resolved array value when all providers in metadata are handled.
*/
initDownloadPathIfProvidersExist() {
let providerKeys = Object.keys(this.providersMetaData);
let promises = providerKeys.map(key => {
return key === "Dropbox"
? this._initDropbox(key)
: Promise.resolve(false);
});
return Promise.all(promises);
},
/**
* Read Dropbox info.json and override providers metadata
* downloadPath property
*
* @return {Promise}
* @resolves
* false if dropbox provider is not found. Returns true if dropbox service exist
* on user desktop and downloadPath in providermetadata is updated with
* value read from config file info.json
*/
async _initDropbox(key) {
// Check if Dropbox provider exist on desktop before continuing
if (
!(await this._checkIfAssetExists(
this.providersMetaData[key].discoveryPath
))
) {
return false;
}
// Check in cloud.services.rejected.key if Dropbox is previously rejected before continuing
let rejectedKeys = this.cloudStorageRejectedKeys.split(",");
if (rejectedKeys.includes(key)) {
return false;
}
let file = null;
try {
file = new FileUtils.File(this.providersMetaData[key].discoveryPath);
} catch (ex) {
return false;
}
let data = await this._downloadJSON(Services.io.newFileURI(file).spec);
if (!data) {
return false;
}
let path = data && data.personal && data.personal.path;
if (!path) {
return false;
}
let isUsable = await this._isUsableDirectory(path);
if (isUsable) {
this.providersMetaData.Dropbox.downloadPath = path;
}
return isUsable;
},
/**
* Determines if a given directory is valid and can be used to download files
*
* @param full absolute path to the directory
*
* @return {Promise} which resolves true if we can use the directory, false otherwise.
*/
async _isUsableDirectory(path) {
let isUsable = false;
try {
let info = await OS.File.stat(path);
isUsable = info.isDir;
} catch (e) {
// Directory doesn't exist, so isUsable will still be false
}
return isUsable;
},
/**
* Retrieve download folder of preferred provider by type specific data
*
* @param dataType
* type of data downloaded, options are 'default', 'screenshot'
* default value is 'default'
* @return {Promise} which resolves to full path to download folder
* Resolves null if a valid download folder is not found.
*/
async getDownloadFolder(dataType = "default") {
// Wait for cloudstorage to initialize if providers metadata is not available
if (!this.providersMetaData) {
let isInitialized = await this.promiseInit;
if (!isInitialized && !this.providersMetaData) {
Cu.reportError(
"CloudStorage: Failed to initialize and retrieve download folder "
);
return null;
}
}
let key = this.preferredProviderKey;
if (!key || !this.providersMetaData.hasOwnProperty(key)) {
return null;
}
let provider = this.providersMetaData[key];
if (!provider.typeSpecificData[dataType]) {
return null;
}
let downloadDirPath = OS.Path.join(
provider.downloadPath,
provider.typeSpecificData[dataType]
);
if (!(await this._isUsableDirectory(downloadDirPath))) {
return null;
}
return downloadDirPath;
},
/**
* Return scanned provider info used by consumer inside doorhanger prompt.
* @return {Promise}
* which resolves to an object with property 'key' as found provider and
* property 'value' as provider metadata.
* Resolves null if no provider info is returned.
*/
async promisePromptInfo() {
// Check if user has not previously opted-in for preferred provider download folder
// and if time elapsed since last prompt shown has exceeded maximum allowed interval
// in pref cloud.services.interval.prompt before continuing to scan for providers
if (!this.preferredProviderKey && this.shouldPrompt()) {
return this.scan();
}
return Promise.resolve(null);
},
/**
* Check if its time to prompt by reading lastprompt service pref.
* Return true if pref doesn't exist or last prompt time is
* more than prompt interval
*/
shouldPrompt() {
let lastPrompt = this.lastPromptTime;
let now = Math.floor(Date.now() / 1000);
let interval = now - lastPrompt;
// Convert prompt interval to seconds
let maxAllow = this.promptInterval * 24 * 60 * 60;
return interval >= maxAllow;
},
/**
* Scans for local storage providers available on user desktop
*
* providers list is read in order as specified in providers.json.
* If a user has multiple cloud storage providers on desktop, return the first
* provider after filtering the rejected keys
*
* @return {Promise}
* which resolves to an object providerInfo with found provider key and value
* as provider metadata. Resolves null if no valid provider found
*/
async scan() {
let providers = await this.getStorageProviders();
if (!providers.size) {
// No storage services installed on user desktop
return null;
}
// Filter the rejected providers in cloud.services.rejected.key
// from the providers map object
let rejectedKeys = this.cloudStorageRejectedKeys.split(",");
for (let rejectedKey of rejectedKeys) {
providers.delete(rejectedKey);
}
// Pick first storage provider from providers
let provider = providers.entries().next().value;
if (provider) {
return { key: provider[0], value: provider[1] };
}
return null;
},
/**
* Checks if the asset with input path exist on
* file system
* @return {Promise}
* @resolves
* boolean value of file existence check
*/
_checkIfAssetExists(path) {
return OS.File.exists(path).catch(err => {
Cu.reportError(`Couldn't check existance of ${path}`, err);
return false;
});
},
/**
* get access to all local storage providers available on user desktop
*
* @return {Promise}
* @resolves
* Map object with entries key set to storage provider key and values set to
* storage provider metadata
*/
async getStorageProviders() {
let providers = Object.entries(this.providersMetaData || {});
// Array of promises with boolean value exist for respective storage.
let promises = providers.map(([, provider]) =>
this._checkIfAssetExists(provider.discoveryPath)
);
let results = await Promise.all(promises);
// Filter providers array to remove provider with discoveryPath asset exist resolved value false
providers = providers.filter((_, idx) => results[idx]);
return new Map(providers);
},
/**
* Save the rejected provider in cloud.services.rejected.key. Pref
* stores rejected keys value as comma separated string.
*
* @param key
* Provider key to be saved in cloud.services.rejected.key pref
*/
handleRejected(key) {
let rejected = this.cloudStorageRejectedKeys;
if (!rejected) {
Services.prefs.setCharPref(CLOUD_SERVICES_PREF + "rejected.key", key);
} else {
// Pref exists with previous rejected keys, append
// key at the end and update pref
let keys = rejected.split(",");
if (key) {
keys.push(key);
}
Services.prefs.setCharPref(
CLOUD_SERVICES_PREF + "rejected.key",
keys.join(",")
);
}
},
/**
*
* Sets pref cloud.services.storage.key. It updates download browser.download.folderList
* value to 3 indicating download location is stored elsewhere, as specified by
* cloud storage API getDownloadFolder
*
* @param key
* cloud storage provider key from provider metadata
*/
setCloudStoragePref(key) {
Services.prefs.setCharPref(CLOUD_SERVICES_PREF + "storage.key", key);
Services.prefs.setIntPref("browser.download.folderList", 3);
},
/**
* get access to preferred provider metadata by using preferred provider key
*
* @return {Object}
* Object with preferred provider metadata. Returns null if preferred provider is not set
*/
getPreferredProviderMetaData() {
// Use preferred provider key to retrieve metadata from ProvidersMetaData
return this.providersMetaData.hasOwnProperty(this.preferredProviderKey)
? this.providersMetaData[this.preferredProviderKey]
: null;
},
/**
* Get provider display name if cloud storage API is used by an add-on
* and user has set preferred provider and a valid download directory
* path exists on user desktop.
*
* @return {String}
* String with preferred provider display name. Returns null if provider is not in use.
*/
async getProviderIfInUse() {
// Check if consumer add-on is present and user has set preferred provider key
// and a valid download path exist on user desktop
if (
this.isAPIEnabled &&
this.preferredProviderKey &&
(await this.getDownloadFolder())
) {
let provider = this.getPreferredProviderMetaData();
return provider.displayName || null;
}
return null;
},
};
/**
* Provider key retrieved from service pref cloud.services.storage.key
*/
XPCOMUtils.defineLazyPreferenceGetter(
CloudStorageInternal,
"preferredProviderKey",
CLOUD_SERVICES_PREF + "storage.key",
""
);
/**
* Provider keys rejected by user for default download
*/
XPCOMUtils.defineLazyPreferenceGetter(
CloudStorageInternal,
"cloudStorageRejectedKeys",
CLOUD_SERVICES_PREF + "rejected.key",
""
);
/**
* Lastprompt time in seconds, by default set to 0
*/
XPCOMUtils.defineLazyPreferenceGetter(
CloudStorageInternal,
"lastPromptTime",
CLOUD_SERVICES_PREF + "lastprompt",
0 /* 0 second */
);
/**
* show prompt interval in days, by default set to 0
*/
XPCOMUtils.defineLazyPreferenceGetter(
CloudStorageInternal,
"promptInterval",
CLOUD_SERVICES_PREF + "interval.prompt",
0 /* 0 days */
);
/**
* generic pref that shows if cloud storage API is in use, by default set to false.
* Re-run CloudStorage init evertytime pref is set.
*/
XPCOMUtils.defineLazyPreferenceGetter(
CloudStorageInternal,
"isAPIEnabled",
CLOUD_SERVICES_PREF + "api.enabled",
false,
() => CloudStorage.init()
);
CloudStorageInternal.promiseInit = CloudStorage.init();

View File

@ -1,27 +0,0 @@
{
"Dropbox": {
"displayName": "Dropbox",
"relativeDownloadPath": ["homeDir", "Dropbox"],
"relativeDiscoveryPath": {
"linux": ["homeDir", ".dropbox", "info.json"],
"macosx": ["homeDir", ".dropbox", "info.json"],
"win": ["LocalAppData", "Dropbox", "info.json"]
},
"typeSpecificData": {
"default": "Downloads",
"screenshot": "Screenshots"
}
},
"GDrive": {
"displayName": "Google Drive",
"relativeDownloadPath": ["homeDir", "Google Drive"],
"relativeDiscoveryPath": {
"macosx": ["homeDir", "Library", "Application Support", "Google", "Drive"],
"win": ["LocalAppData", "Google", "Drive"]
},
"typeSpecificData": {
"default": "Downloads"
}
}
}

View File

@ -1,7 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
% resource cloudstorage %content/
content/ (content/*)

View File

@ -1,18 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPCSHELL_TESTS_MANIFESTS += [
"tests/unit/xpcshell.ini",
]
JAR_MANIFESTS += ["jar.mn"]
EXTRA_JS_MODULES += [
"CloudStorage.jsm",
]
with Files("**"):
BUG_COMPONENT = ("Toolkit", "General")

View File

@ -1 +0,0 @@
{"personal": {"path": "Test"}}

View File

@ -1,321 +0,0 @@
"use strict";
// Globals
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"CloudStorage",
"resource://gre/modules/CloudStorage.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"FileUtils",
"resource://gre/modules/FileUtils.jsm"
);
const CLOUD_SERVICES_PREF = "cloud.services.";
const DROPBOX_DOWNLOAD_FOLDER = "Dropbox";
const GOOGLE_DRIVE_DOWNLOAD_FOLDER = "Google Drive";
const DROPBOX_CONFIG_FOLDER =
AppConstants.platform === "win" ? "Dropbox" : ".dropbox";
const DROPBOX_KEY = "Dropbox";
const GDRIVE_KEY = "GDrive";
var nsIDropboxFile, nsIGDriveFile;
function run_test() {
initPrefs();
registerFakePath("Home", do_get_file("cloud/"));
registerFakePath("LocalAppData", do_get_file("cloud/"));
registerCleanupFunction(() => {
cleanupPrefs();
});
run_next_test();
}
function initPrefs() {
Services.prefs.setBoolPref(CLOUD_SERVICES_PREF + "api.enabled", true);
}
/**
* Replaces a directory service entry with a given nsIFile.
*/
function registerFakePath(key, file) {
let dirsvc = Services.dirsvc.QueryInterface(Ci.nsIProperties);
let originalFile;
try {
// If a file is already provided save it and undefine, otherwise set will
// throw for persistent entries (ones that are cached).
originalFile = dirsvc.get(key, Ci.nsIFile);
dirsvc.undefine(key);
} catch (e) {
// dirsvc.get will throw if nothing provides for the key and dirsvc.undefine
// will throw if it's not a persistent entry, in either case we don't want
// to set the original file in cleanup.
originalFile = undefined;
}
dirsvc.set(key, file);
registerCleanupFunction(() => {
dirsvc.undefine(key);
if (originalFile) {
dirsvc.set(key, originalFile);
}
});
}
function mock_dropbox() {
let discoveryFolder = null;
if (AppConstants.platform === "win") {
discoveryFolder = FileUtils.getFile("LocalAppData", [
DROPBOX_CONFIG_FOLDER,
]);
} else {
discoveryFolder = FileUtils.getFile("Home", [DROPBOX_CONFIG_FOLDER]);
}
discoveryFolder.append("info.json");
let fileDir = discoveryFolder.parent;
if (!fileDir.exists()) {
fileDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
}
do_get_file("cloud/info.json").copyTo(fileDir, "info.json");
let exist = fileDir.exists();
Assert.ok(exist, "file exists on desktop");
// Mock Dropbox Download folder in Home directory
let downloadFolder = FileUtils.getFile("Home", [
DROPBOX_DOWNLOAD_FOLDER,
"Downloads",
]);
if (!downloadFolder.exists()) {
downloadFolder.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
}
registerCleanupFunction(() => {
if (discoveryFolder.exists()) {
discoveryFolder.remove(false);
}
if (downloadFolder.exists()) {
downloadFolder.remove(false);
}
});
return discoveryFolder;
}
function mock_gdrive() {
let discoveryFolder = null;
if (AppConstants.platform === "win") {
discoveryFolder = FileUtils.getFile("LocalAppData", ["Google", "Drive"]);
} else {
discoveryFolder = FileUtils.getFile("Home", [
"Library",
"Application Support",
"Google",
"Drive",
]);
}
if (!discoveryFolder.exists()) {
discoveryFolder.createUnique(
Ci.nsIFile.DIRECTORY_TYPE,
FileUtils.PERMS_DIRECTORY
);
}
let exist = discoveryFolder.exists();
Assert.ok(exist, "file exists on desktop");
// Mock Google Drive Download folder in Home directory
let downloadFolder = FileUtils.getFile("Home", [
GOOGLE_DRIVE_DOWNLOAD_FOLDER,
"Downloads",
]);
if (!downloadFolder.exists()) {
downloadFolder.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
}
registerCleanupFunction(() => {
if (discoveryFolder.exists()) {
discoveryFolder.remove(false);
}
if (downloadFolder.exists()) {
downloadFolder.remove(false);
}
});
return discoveryFolder;
}
function cleanupPrefs() {
try {
Services.prefs.clearUserPref(CLOUD_SERVICES_PREF + "lastprompt");
Services.prefs.clearUserPref(CLOUD_SERVICES_PREF + "storage.key");
Services.prefs.clearUserPref(CLOUD_SERVICES_PREF + "rejected.key");
Services.prefs.clearUserPref(CLOUD_SERVICES_PREF + "interval.prompt");
Services.prefs.clearUserPref(CLOUD_SERVICES_PREF + "api.enabled");
Services.prefs.setIntPref("browser.download.folderList", 2);
} catch (e) {
do_throw("Failed to cleanup prefs: " + e);
}
}
function promiseGetStorageProviders() {
return CloudStorage.getStorageProviders();
}
function promisePromptInfo() {
return CloudStorage.promisePromptInfo();
}
async function checkScan(expectedKey) {
let metadata = await promiseGetStorageProviders();
let scanProvider = await promisePromptInfo();
if (!expectedKey) {
Assert.equal(metadata.size, 0, "Number of storage providers");
Assert.ok(!scanProvider, "No provider in scan results");
} else {
Assert.ok(metadata.size, "Number of storage providers");
Assert.equal(
scanProvider.key,
expectedKey,
"Scanned provider key returned"
);
}
return metadata;
}
async function checkSavedPromptResponse(
aKey,
metadata,
remember,
selected = false
) {
CloudStorage.savePromptResponse(aKey, remember, selected);
if (remember && selected) {
// Save prompt response with option to always remember the setting
// and provider with aKey selected as cloud storage provider
// Sets user download settings to always save to cloud
// Check preferred provider key, should be set to dropbox
let prefProvider = CloudStorage.getPreferredProvider();
Assert.equal(
prefProvider,
aKey,
"Saved Response preferred provider key returned"
);
// Check browser.download.folderlist pref should be set to 3
Assert.equal(
Services.prefs.getIntPref("browser.download.folderList"),
3,
"Default download location set to 3"
);
// Preferred download folder should be set to provider downloadPath from metadata
let path = await CloudStorage.getDownloadFolder();
let nsIDownloadFolder = new FileUtils.File(path);
Assert.ok(nsIDownloadFolder, "Download folder retrieved");
Assert.equal(
nsIDownloadFolder.parent.path,
metadata.get(aKey).downloadPath,
"Default download Folder Path"
);
} else if (remember && !selected) {
// Save prompt response with option to always remember the setting
// and provider with aKey rejected as cloud storage provider
// Sets cloud.services.rejected.key pref with provider key.
// Provider is ignored in next scan and never re-prompted again
let scanResult = await promisePromptInfo();
if (scanResult) {
Assert.notEqual(
scanResult.key,
DROPBOX_KEY,
"Scanned provider key returned is not Dropbox"
);
} else {
Assert.ok(!scanResult, "No provider in scan results");
}
}
}
add_task(async function test_checkInit() {
let isInitialized = await CloudStorage.promiseInit;
Assert.ok(isInitialized, "Providers Metadata successfully initialized");
});
add_task(async function test_noStorageProvider() {
await checkScan();
cleanupPrefs();
});
/**
* Check scan and save prompt response flow if only dropbox exists on desktop.
*/
add_task(async function test_dropboxStorageProvider() {
nsIDropboxFile = mock_dropbox();
let result = await checkScan(DROPBOX_KEY);
// Always save to cloud
await checkSavedPromptResponse(DROPBOX_KEY, result, true, true);
cleanupPrefs();
// Reject dropbox as cloud storage provider and never re-prompt again
await checkSavedPromptResponse(DROPBOX_KEY, result, true);
// Uninstall dropbox by removing discovery folder
nsIDropboxFile.remove(false);
cleanupPrefs();
});
/**
* Check scan and save prompt response flow if only gdrive exists on desktop.
*/
add_task(async function test_gDriveStorageProvider() {
nsIGDriveFile = mock_gdrive();
let result;
if (AppConstants.platform === "linux") {
result = await checkScan();
} else {
result = await checkScan(GDRIVE_KEY);
}
if (result.size || AppConstants.platform !== "linux") {
// Always save to cloud
await checkSavedPromptResponse(GDRIVE_KEY, result, true, true);
cleanupPrefs();
// Reject Google Drive as cloud storage provider and never re-prompt again
await checkSavedPromptResponse(GDRIVE_KEY, result, true);
}
// Uninstall gDrive by removing discovery folder /Home/Library/Application Support/Google/Drive
nsIGDriveFile.remove(false);
cleanupPrefs();
});
/**
* Check scan and save prompt response flow if multiple provider exists on desktop.
*/
add_task(async function test_multipleStorageProvider() {
nsIDropboxFile = mock_dropbox();
nsIGDriveFile = mock_gdrive();
// Dropbox picked by scan if multiple providers found
let result = await checkScan(DROPBOX_KEY);
// Always save to cloud
await checkSavedPromptResponse(DROPBOX_KEY, result, true, true);
cleanupPrefs();
// Reject dropbox as cloud storage provider and never re-prompt again
await checkSavedPromptResponse(DROPBOX_KEY, result, true);
// Uninstall dropbox and gdrive by removing discovery folder
nsIDropboxFile.remove(false);
nsIGDriveFile.remove(false);
cleanupPrefs();
});

View File

@ -1,8 +0,0 @@
[DEFAULT]
head =
firefox-appdir = browser
skip-if = toolkit == 'android'
support-files =
cloud/**
[test_cloudstorage.js]

View File

@ -68,11 +68,6 @@ ChromeUtils.defineModuleGetter(
"NetUtil",
"resource://gre/modules/NetUtil.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"CloudStorage",
"resource://gre/modules/CloudStorage.jsm"
);
XPCOMUtils.defineLazyServiceGetter(
this,
@ -381,14 +376,6 @@ var DownloadIntegration = {
directoryPath = await this.getSystemDownloadsDirectory();
}
break;
case 3: // Cloud Storage
try {
directoryPath = await CloudStorage.getDownloadFolder();
} catch (ex) {}
if (!directoryPath) {
directoryPath = await this.getSystemDownloadsDirectory();
}
break;
default:
directoryPath = await this.getSystemDownloadsDirectory();
}

View File

@ -135,13 +135,22 @@ add_task(async function test_getPreferredDownloadsDirectory() {
}
registerCleanupFunction(cleanupPrefs);
// Should return the system downloads directory.
Services.prefs.setIntPref(folderListPrefName, 1);
// For legacy cloudstorage users with folderListPrefName as 3,
// Should return the system downloads directory because the dir preference
// is not set.
Services.prefs.setIntPref(folderListPrefName, 3);
let systemDir = await DownloadIntegration.getSystemDownloadsDirectory();
let downloadDir = await DownloadIntegration.getPreferredDownloadsDirectory();
Assert.notEqual(downloadDir, "");
Assert.equal(downloadDir, systemDir);
// Should return the system downloads directory.
Services.prefs.setIntPref(folderListPrefName, 1);
systemDir = await DownloadIntegration.getSystemDownloadsDirectory();
downloadDir = await DownloadIntegration.getPreferredDownloadsDirectory();
Assert.notEqual(downloadDir, "");
Assert.equal(downloadDir, systemDir);
// Should return the desktop directory.
Services.prefs.setIntPref(folderListPrefName, 0);
downloadDir = await DownloadIntegration.getPreferredDownloadsDirectory();

View File

@ -27,7 +27,6 @@ DIRS += [
"certviewer",
"cleardata",
"clearsitedata",
"cloudstorage",
"commandlines",
"contentprefs",
"contextualidentity",

View File

@ -306,13 +306,6 @@ nsUnknownContentTypeDialog.prototype = {
// Check to make sure we have a valid directory, otherwise, prompt
if (result) {
// Notifications for CloudStorage API consumers to show offer
// prompts while downloading. See Bug 1365129
Services.obs.notifyObservers(
null,
"cloudstorage-prompt-notification",
result.path
);
// This path is taken when we have a writable default download directory.
aLauncher.saveDestinationAvailable(result);
return;