blob: d37e67b33fe1fec69c6d0b0d01a5dc10d28b7dc2 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var WallpaperUtil = {
strings: null, // Object that contains all the flags
syncFs: null, // syncFileSystem handler
webkitFs: null // webkitFileSystem handler
};
/**
* Deletes |wallpaperFileName| and its associated thumbnail from local FS.
* @param {string} wallpaperFilename Name of the file that will be deleted
*/
WallpaperUtil.deleteWallpaperFromLocalFS = function(wallpaperFilename) {
WallpaperUtil.requestLocalFS(function(fs) {
var originalPath =
Constants.WallpaperDirNameEnum.ORIGINAL + '/' + wallpaperFilename;
var thumbnailPath =
Constants.WallpaperDirNameEnum.THUMBNAIL + '/' + wallpaperFilename;
fs.root.getFile(
originalPath, {create: false},
function(fe) {
fe.remove(function() {}, null);
},
// NotFoundError is expected. After we receive a delete
// event from either original wallpaper or wallpaper
// thumbnail, we delete both of them in local FS to achieve
// a faster synchronization. So each file is expected to be
// deleted twice and the second attempt is a noop.
function(e) {
if (e.name != 'NotFoundError')
WallpaperUtil.onFileSystemError(e);
});
fs.root.getFile(
thumbnailPath, {create: false},
function(fe) {
fe.remove(function() {}, null);
},
function(e) {
if (e.name != 'NotFoundError')
WallpaperUtil.onFileSystemError(e);
});
});
};
/**
* Loads a wallpaper from sync file system and saves it and its thumbnail to
* local file system.
* @param {string} wallpaperFileEntry File name of wallpaper image.
*/
WallpaperUtil.storeWallpaperFromSyncFSToLocalFS = function(wallpaperFileEntry) {
var filenName = wallpaperFileEntry.name;
var storeDir = Constants.WallpaperDirNameEnum.ORIGINAL;
if (filenName.indexOf(Constants.CustomWallpaperThumbnailSuffix) != -1)
storeDir = Constants.WallpaperDirNameEnum.THUMBNAIL;
filenName = filenName.replace(Constants.CustomWallpaperThumbnailSuffix, '');
wallpaperFileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function() {
WallpaperUtil.storeWallpaperToLocalFS(filenName, reader.result, storeDir);
};
reader.readAsArrayBuffer(file);
}, WallpaperUtil.onFileSystemError);
};
/**
* Deletes |wallpaperFileName| and its associated thumbnail from syncFileSystem.
* @param {string} wallpaperFilename Name of the file that will be deleted.
*/
WallpaperUtil.deleteWallpaperFromSyncFS = function(wallpaperFilename) {
var thumbnailFilename =
wallpaperFilename + Constants.CustomWallpaperThumbnailSuffix;
var success = function(fs) {
fs.root.getFile(
wallpaperFilename, {create: false},
function(fe) {
fe.remove(function() {}, null);
},
function(e) {
// NotFoundError is expected under the following scenario:
// The user uses a same account on device A and device B.
// The current wallpaper is a third party wallpaper. Then
// the user changes it to a ONLINE wallpaper on device A.
// Sync file system change and local file system change
// will then both be fired on device B, which makes the
// third party wallpaper be deleted twice from the sync
// file system. We should ignore this error.
if (e.name != 'NotFoundError')
WallpaperUtil.onFileSystemError(e);
});
fs.root.getFile(
thumbnailFilename, {create: false},
function(fe) {
fe.remove(function() {}, null);
},
function(e) {
// Same as above.
if (e.name != 'NotFoundError')
WallpaperUtil.onFileSystemError(e);
});
};
WallpaperUtil.requestSyncFS(success);
};
/**
* Executes callback after requesting the sync settings.
* @param {function} callback The callback will be executed.
*/
WallpaperUtil.enabledSyncThemesCallback = function(callback) {
chrome.wallpaperPrivate.getSyncSetting(function(setting) {
callback(setting.syncThemes);
});
};
/**
* Request a syncFileSystem handle and run callback on it.
* @param {function} callback The callback to execute after syncFileSystem
* handler is available.
*/
WallpaperUtil.requestSyncFS = function(callback) {
WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
if (!syncEnabled)
return;
if (WallpaperUtil.syncFs) {
callback(WallpaperUtil.syncFs);
} else {
chrome.syncFileSystem.requestFileSystem(function(fs) {
WallpaperUtil.syncFs = fs;
callback(WallpaperUtil.syncFs);
});
}
});
};
/**
* Request a Local Fs handle and run callback on it.
* @param {function} callback The callback to execute after Local handler is
* available.
*/
WallpaperUtil.requestLocalFS = function(callback) {
if (WallpaperUtil.webkitFs) {
callback(WallpaperUtil.webkitFs);
} else {
window.webkitRequestFileSystem(
window.PERSISTENT, 1024 * 1024 * 100, function(fs) {
WallpaperUtil.webkitFs = fs;
callback(fs);
});
}
};
/**
* Print error to console.error.
* @param {Event} e The error will be printed to console.error.
*/
// TODO(ranj): Handle different errors differently.
WallpaperUtil.onFileSystemError = function(e) {
console.error(e);
};
/**
* Write jpeg/png file data into file entry.
* @param {FileEntry} fileEntry The file entry that going to be writen.
* @param {ArrayBuffer} wallpaperData Data for image file.
* @param {function=} writeCallback The callback that will be executed after
* writing data.
*/
WallpaperUtil.writeFile = function(fileEntry, wallpaperData, writeCallback) {
fileEntry.createWriter(function(fileWriter) {
var blob = new Blob([new Int8Array(wallpaperData)]);
fileWriter.write(blob);
if (writeCallback)
writeCallback();
}, WallpaperUtil.onFileSystemError);
};
/**
* Write jpeg/png file data into syncFileSystem.
* @param {string} wallpaperFilename The filename that going to be writen.
* @param {ArrayBuffer} wallpaperData Data for image file.
*/
WallpaperUtil.storeWallpaperToSyncFS = function(
wallpaperFilename, wallpaperData) {
var callback = function(fs) {
fs.root.getFile(
wallpaperFilename, {create: false}, function() {}, // already exists
function(e) { // not exists, create
fs.root.getFile(wallpaperFilename, {create: true}, function(fe) {
WallpaperUtil.writeFile(fe, wallpaperData);
}, WallpaperUtil.onFileSystemError);
});
};
WallpaperUtil.requestSyncFS(callback);
};
/**
* Stores jpeg/png wallpaper into |localDir| in local file system.
* @param {string} wallpaperFilename File name of wallpaper image.
* @param {ArrayBuffer} wallpaperData The wallpaper data.
* @param {string} saveDir The path to store wallpaper in local file system.
*/
WallpaperUtil.storeWallpaperToLocalFS = function(
wallpaperFilename, wallpaperData, saveDir) {
if (!wallpaperData) {
console.error('wallpaperData is null');
return;
}
var getDirSuccess = function(dirEntry) {
dirEntry.getFile(
wallpaperFilename, {create: false}, function() {}, // already exists
function(e) { // not exists, create
dirEntry.getFile(wallpaperFilename, {create: true}, function(fe) {
WallpaperUtil.writeFile(fe, wallpaperData);
}, WallpaperUtil.onFileSystemError);
});
};
WallpaperUtil.requestLocalFS(function(fs) {
fs.root.getDirectory(
saveDir, {create: true}, getDirSuccess,
WallpaperUtil.onFileSystemError);
});
};
/**
* Sets wallpaper from synced file system.
* @param {string} wallpaperFilename File name used to set wallpaper.
* @param {string} wallpaperLayout Layout used to set wallpaper.
* @param {function=} onSuccess Callback if set successfully.
*/
WallpaperUtil.setCustomWallpaperFromSyncFS = function(
wallpaperFilename, wallpaperLayout, onSuccess) {
var setWallpaperFromSyncCallback = function(fs) {
if (!wallpaperFilename) {
console.error('wallpaperFilename is not provided.');
return;
}
if (!wallpaperLayout)
wallpaperLayout = 'CENTER_CROPPED';
fs.root.getFile(
wallpaperFilename, {create: false},
function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function() {
chrome.wallpaperPrivate.setCustomWallpaper(
reader.result, wallpaperLayout, true /*generateThumbnail=*/,
wallpaperFilename, false /*previewMode=*/,
function(thumbnailData) {
// TODO(ranj): Ignore 'canceledWallpaper' error.
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
return;
}
if (onSuccess)
onSuccess();
});
};
reader.readAsArrayBuffer(file);
}, WallpaperUtil.onFileSystemError);
},
function(e) {} // fail to read file, expected due to download delay
);
};
WallpaperUtil.requestSyncFS(setWallpaperFromSyncCallback);
};
/**
* Saves value to local storage that associates with key.
* @param {string} key The key that associates with value.
* @param {string} value The value to save to local storage.
* @param {function=} opt_callback The callback on success.
*/
WallpaperUtil.saveToLocalStorage = function(key, value, opt_callback) {
var items = {};
items[key] = value;
Constants.WallpaperLocalStorage.set(items, opt_callback);
};
/**
* Saves value to sync storage that associates with key if sync theme is
* enabled.
* @param {string} key The key that associates with value.
* @param {string} value The value to save to sync storage.
* @param {function=} opt_callback The callback on success.
*/
WallpaperUtil.saveToSyncStorage = function(key, value, opt_callback) {
var items = {};
items[key] = value;
WallpaperUtil.enabledSyncThemesCallback(function(syncEnabled) {
if (syncEnabled)
Constants.WallpaperSyncStorage.set(items, opt_callback);
});
};
/**
* Saves user's wallpaper infomation to local and sync storage. Note that local
* value should be saved first.
* @param {string} url The url address of wallpaper. For custom wallpaper, it is
* the file name.
* @param {string} layout The wallpaper layout.
* @param {string} source The wallpaper source.
* @param {string} appName The third party app name. If the current wallpaper is
* set by the built-in wallpaper picker, it is set to an empty string.
*/
WallpaperUtil.saveWallpaperInfo = function(url, layout, source, appName) {
chrome.wallpaperPrivate.recordWallpaperUMA(source);
// In order to keep the wallpaper sync working across different versions, we
// have to revert DAILY/THIRDPARTY type wallpaper info to ONLINE/CUSTOM type
// after record the correct UMA stats.
source = (source == Constants.WallpaperSourceEnum.Daily) ?
Constants.WallpaperSourceEnum.Online :
source;
source = (source == Constants.WallpaperSourceEnum.ThirdParty) ?
Constants.WallpaperSourceEnum.Custom :
source;
var wallpaperInfo = {
url: url,
layout: layout,
source: source,
appName: appName,
};
WallpaperUtil.saveToLocalStorage(
Constants.AccessLocalWallpaperInfoKey, wallpaperInfo, function() {
WallpaperUtil.saveToSyncStorage(
Constants.AccessSyncWallpaperInfoKey, wallpaperInfo);
});
};
/**
* Downloads resources from url. Calls onSuccess and opt_onFailure accordingly.
* @param {string} url The url address where we should fetch resources.
* @param {string} type The response type of XMLHttprequest.
* @param {function} onSuccess The success callback. It must be called with
* current XMLHttprequest object.
* @param {function} onFailure The failure callback.
* @param {XMLHttpRequest=} opt_xhr The XMLHttpRequest object.
*/
WallpaperUtil.fetchURL = function(url, type, onSuccess, onFailure, opt_xhr) {
var xhr;
if (opt_xhr)
xhr = opt_xhr;
else
xhr = new XMLHttpRequest();
try {
// Do not use loadend here to handle both success and failure case. It gets
// complicated with abortion. Unexpected error message may show up. See
// http://crbug.com/242581.
xhr.addEventListener('load', function(e) {
if (this.status == 200) {
onSuccess(this);
} else {
onFailure(this.status);
}
});
xhr.addEventListener('error', onFailure);
xhr.open('GET', url, true);
xhr.responseType = type;
xhr.send(null);
} catch (e) {
onFailure();
}
};
/**
* A convenience wrapper for setting online wallpapers with preview disabled.
* @param {string} url The url address where we should fetch resources.
* @param {string} layout The layout of online wallpaper.
* @param {function} onSuccess The success callback.
* @param {function} onFailure The failure callback.
*/
WallpaperUtil.setOnlineWallpaperWithoutPreview = function(
url, layout, onSuccess, onFailure) {
chrome.wallpaperPrivate.setWallpaperIfExists(
url, layout, false /*previewMode=*/, exists => {
if (exists) {
onSuccess();
return;
}
this.fetchURL(url, 'arraybuffer', xhr => {
if (xhr.response != null) {
chrome.wallpaperPrivate.setWallpaper(
xhr.response, layout, url, false /*previewMode=*/, onSuccess);
} else {
onFailure();
}
}, onFailure);
});
};
/**
* Creates a blob of type 'image/png'.
* @param {string} data The image data.
*/
WallpaperUtil.createPngBlob = function(data) {
return new Blob([new Int8Array(data)], {'type': 'image/png'});
};
/**
* Displays the image by creating an image blob.
* @param {Object} imageElement The image element.
* @param {string} data The image data.
* @param {function} opt_callback An optional callback, called after the image
* finishes loading.
*/
WallpaperUtil.displayImage = function(imageElement, data, opt_callback) {
imageElement.src =
window.URL.createObjectURL(WallpaperUtil.createPngBlob(data));
imageElement.addEventListener('load', function(e) {
if (opt_callback)
opt_callback();
// Revoke the url since it won't be used anymore after the image is loaded.
window.URL.revokeObjectURL(imageElement.src);
});
};
/**
* Sets the value of the daily refresh toggle.
* @param {boolean} checked The value used to set the checkbox.
*/
WallpaperUtil.setSurpriseMeCheckboxValue = function(checked) {
document.querySelectorAll('.daily-refresh-slider').forEach(element => {
element.classList.toggle('checked', checked);
});
};
/**
* Gets the state of the daily refresh toggle.
* @return {boolean} The value of the checkbox.
*/
WallpaperUtil.getSurpriseMeCheckboxValue = function() {
return document.querySelector('.daily-refresh-slider')
.classList.contains('checked');
};
/**
* A convenience wrapper for displaying the thumbnail image.
* @param {Object} imageElement The image element.
* @param {string} url The base url of the wallpaper.
* @param {string} source The source of the wallpaper corresponding to
* |WallpaperSourceEnum|.
*/
WallpaperUtil.displayThumbnail = function(imageElement, url, source) {
chrome.wallpaperPrivate.getThumbnail(url, source, data => {
if (data) {
WallpaperUtil.displayImage(imageElement, data, null /*opt_callback=*/);
} else {
// The only known case for hitting this branch is when showing the
// wallpaper picker for the first time after OOBE, the |saveThumbnail|
// operation within |WallpaperThumbnailsGridItem.decorate| hasn't
// completed. See http://crbug.com/792829.
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.send(null);
xhr.addEventListener('load', function(e) {
if (xhr.status === 200) {
WallpaperUtil.displayImage(
imageElement, xhr.response, null /*opt_callback=*/);
}
});
}
});
};
/**
* Runs chrome.test.sendMessage in test environment. Does nothing if running
* in production environment.
*
* @param {string} message Test message to send.
*/
WallpaperUtil.testSendMessage = function(message) {
var test = chrome.test || window.top.chrome.test;
if (test)
test.sendMessage(message);
};
/**
* Gets the daily refresh info from sync storage, or local storage if the former
* is not available.
* @param {function} callback A callback that takes the value of the info, or
* null if the value is invalid.
*/
WallpaperUtil.getDailyRefreshInfo = function(callback) {
WallpaperUtil.enabledSyncThemesCallback(syncEnabled => {
var parseInfo = dailyRefreshInfoJson => {
if (!dailyRefreshInfoJson) {
callback(null);
return;
}
var dailyRefreshInfo = JSON.parse(dailyRefreshInfoJson);
if (!dailyRefreshInfo || !dailyRefreshInfo.hasOwnProperty('enabled') ||
!dailyRefreshInfo.hasOwnProperty('collectionId') ||
!dailyRefreshInfo.hasOwnProperty('resumeToken')) {
callback(null);
return;
}
callback(dailyRefreshInfo);
};
if (syncEnabled) {
Constants.WallpaperSyncStorage.get(
Constants.AccessSyncDailyRefreshInfoKey, items => {
var dailyRefreshInfoJson =
items[Constants.AccessSyncDailyRefreshInfoKey];
if (dailyRefreshInfoJson) {
parseInfo(dailyRefreshInfoJson);
} else {
Constants.WallpaperLocalStorage.get(
Constants.AccessLocalDailyRefreshInfoKey, items => {
dailyRefreshInfoJson =
items[Constants.AccessLocalDailyRefreshInfoKey];
parseInfo(dailyRefreshInfoJson);
if (dailyRefreshInfoJson) {
WallpaperUtil.saveToSyncStorage(
Constants.AccessSyncDailyRefreshInfoKey,
dailyRefreshInfoJson);
}
});
}
});
} else {
Constants.WallpaperLocalStorage.get(
Constants.AccessLocalDailyRefreshInfoKey, items => {
parseInfo(items[Constants.AccessLocalDailyRefreshInfoKey]);
});
}
});
};
/**
* Saves the daily refresh info to local and sync storage.
* @param {Object} dailyRefreshInfo The daily refresh info.
*/
WallpaperUtil.saveDailyRefreshInfo = function(dailyRefreshInfo) {
// Discard |resumeToken| to prevent the server from potentially fingerprinting
// the end user. Therefore, |resumeToken| will always be null when sending
// |getSurpriseMeImage| requests.
// TODO(crbug.com/810169): Implement the mechanism to avoid duplicate
// wallpapers on the client side.
dailyRefreshInfo.resumeToken = null;
var dailyRefreshInfoJson = JSON.stringify(dailyRefreshInfo);
WallpaperUtil.saveToLocalStorage(
Constants.AccessLocalDailyRefreshInfoKey, dailyRefreshInfoJson,
null /*opt_callback=*/);
WallpaperUtil.saveToSyncStorage(
Constants.AccessSyncDailyRefreshInfoKey, dailyRefreshInfoJson,
null /*opt_callback=*/);
};