gecko-dev/toolkit/modules/Log.jsm
Andrew McCreight 5dec0e0beb Bug 1432992, part 1 - Remove definitions of Ci, Cr, Cc, and Cu. r=florian
This patch was autogenerated by my decomponents.py

It covers almost every file with the extension js, jsm, html, py,
xhtml, or xul.

It removes blank lines after removed lines, when the removed lines are
preceded by either blank lines or the start of a new block. The "start
of a new block" is defined fairly hackily: either the line starts with
//, ends with */, ends with {, <![CDATA[, """ or '''. The first two
cover comments, the third one covers JS, the fourth covers JS embedded
in XUL, and the final two cover JS embedded in Python. This also
applies if the removed line was the first line of the file.

It covers the pattern matching cases like "var {classes: Cc,
interfaces: Ci, utils: Cu, results: Cr} = Components;". It'll remove
the entire thing if they are all either Ci, Cr, Cc or Cu, or it will
remove the appropriate ones and leave the residue behind. If there's
only one behind, then it will turn it into a normal, non-pattern
matching variable definition. (For instance, "const { classes: Cc,
Constructor: CC, interfaces: Ci, utils: Cu } = Components" becomes
"const CC = Components.Constructor".)

MozReview-Commit-ID: DeSHcClQ7cG

--HG--
extra : rebase_source : d9c41878036c1ef7766ef5e91a7005025bc1d72b
2018-02-06 09:36:57 -08:00

1010 lines
27 KiB
JavaScript

/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Log"];
const ONE_BYTE = 1;
const ONE_KILOBYTE = 1024 * ONE_BYTE;
const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
const STREAM_SEGMENT_SIZE = 4096;
const PR_UINT32_MAX = 0xffffffff;
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
const INTERNAL_FIELDS = new Set(["_level", "_message", "_time", "_namespace"]);
/*
* Dump a message everywhere we can if we have a failure.
*/
function dumpError(text) {
dump(text + "\n");
Cu.reportError(text);
}
this.Log = {
Level: {
Fatal: 70,
Error: 60,
Warn: 50,
Info: 40,
Config: 30,
Debug: 20,
Trace: 10,
All: -1, // We don't want All to be falsy.
Desc: {
70: "FATAL",
60: "ERROR",
50: "WARN",
40: "INFO",
30: "CONFIG",
20: "DEBUG",
10: "TRACE",
"-1": "ALL",
},
Numbers: {
"FATAL": 70,
"ERROR": 60,
"WARN": 50,
"INFO": 40,
"CONFIG": 30,
"DEBUG": 20,
"TRACE": 10,
"ALL": -1,
}
},
get repository() {
delete Log.repository;
Log.repository = new LoggerRepository();
return Log.repository;
},
set repository(value) {
delete Log.repository;
Log.repository = value;
},
LogMessage,
Logger,
LoggerRepository,
Formatter,
BasicFormatter,
MessageOnlyFormatter,
StructuredFormatter,
Appender,
DumpAppender,
ConsoleAppender,
StorageStreamAppender,
FileAppender,
BoundedFileAppender,
ParameterFormatter,
// Logging helper:
// let logger = Log.repository.getLogger("foo");
// logger.info(Log.enumerateInterfaces(someObject).join(","));
enumerateInterfaces: function Log_enumerateInterfaces(aObject) {
let interfaces = [];
for (let i in Ci) {
try {
aObject.QueryInterface(Ci[i]);
interfaces.push(i);
} catch (ex) {}
}
return interfaces;
},
// Logging helper:
// let logger = Log.repository.getLogger("foo");
// logger.info(Log.enumerateProperties(someObject).join(","));
enumerateProperties(aObject, aExcludeComplexTypes) {
let properties = [];
for (let p in aObject) {
try {
if (aExcludeComplexTypes &&
(typeof(aObject[p]) == "object" || typeof(aObject[p]) == "function"))
continue;
properties.push(p + " = " + aObject[p]);
} catch (ex) {
properties.push(p + " = " + ex);
}
}
return properties;
},
_formatError: function _formatError(e) {
let result = e.toString();
if (e.fileName) {
result += " (" + e.fileName;
if (e.lineNumber) {
result += ":" + e.lineNumber;
}
if (e.columnNumber) {
result += ":" + e.columnNumber;
}
result += ")";
}
return result + " " + Log.stackTrace(e);
},
// This is for back compatibility with services/common/utils.js; we duplicate
// some of the logic in ParameterFormatter
exceptionStr: function exceptionStr(e) {
if (!e) {
return "" + e;
}
if (e instanceof Ci.nsIException) {
return e.toString() + " " + Log.stackTrace(e);
} else if (isError(e)) {
return Log._formatError(e);
}
// else
let message = e.message ? e.message : e;
return message + " " + Log.stackTrace(e);
},
stackTrace: function stackTrace(e) {
// Wrapped nsIException
if (e.location) {
let frame = e.location;
let output = [];
while (frame) {
// Works on frames or exceptions, munges file:// URIs to shorten the paths
// FIXME: filename munging is sort of hackish, might be confusing if
// there are multiple extensions with similar filenames
let str = "<file:unknown>";
let file = frame.filename || frame.fileName;
if (file) {
str = file.replace(/^(?:chrome|file):.*?([^\/\.]+\.\w+)$/, "$1");
}
if (frame.lineNumber) {
str += ":" + frame.lineNumber;
}
if (frame.name) {
str = frame.name + "()@" + str;
}
if (str) {
output.push(str);
}
frame = frame.caller;
}
return "Stack trace: " + output.join(" < ");
}
// Standard JS exception
if (e.stack) {
let stack = e.stack;
// Avoid loading Task.jsm if there's no task on the stack.
if (stack.includes("/Task.jsm:"))
stack = Task.Debugging.generateReadableStack(stack);
return "JS Stack trace: " + stack.trim()
.replace(/\n/g, " < ").replace(/@[^@]*?([^\/\.]+\.\w+:)/g, "@$1");
}
return "No traceback available";
}
};
/*
* LogMessage
* Encapsulates a single log event's data
*/
function LogMessage(loggerName, level, message, params) {
this.loggerName = loggerName;
this.level = level;
/*
* Special case to handle "log./level/(object)", for example logging a caught exception
* without providing text or params like: catch(e) { logger.warn(e) }
* Treating this as an empty text with the object in the 'params' field causes the
* object to be formatted properly by BasicFormatter.
*/
if (!params && message && (typeof(message) == "object") &&
(typeof(message.valueOf()) != "string")) {
this.message = null;
this.params = message;
} else {
// If the message text is empty, or a string, or a String object, normal handling
this.message = message;
this.params = params;
}
// The _structured field will correspond to whether this message is to
// be interpreted as a structured message.
this._structured = this.params && this.params.action;
this.time = Date.now();
}
LogMessage.prototype = {
get levelDesc() {
if (this.level in Log.Level.Desc)
return Log.Level.Desc[this.level];
return "UNKNOWN";
},
toString: function LogMsg_toString() {
let msg = "LogMessage [" + this.time + " " + this.level + " " +
this.message;
if (this.params) {
msg += " " + JSON.stringify(this.params);
}
return msg + "]";
}
};
/*
* Logger
* Hierarchical version. Logs to all appenders, assigned or inherited
*/
function Logger(name, repository) {
if (!repository)
repository = Log.repository;
this._name = name;
this.children = [];
this.ownAppenders = [];
this.appenders = [];
this._repository = repository;
}
Logger.prototype = {
_levelPrefName: null,
_levelPrefValue: null,
get name() {
return this._name;
},
_level: null,
get level() {
if (this._levelPrefName) {
// We've been asked to use a preference to configure the logs. If the
// pref has a value we use it, otherwise we continue to use the parent.
const lpv = this._levelPrefValue;
if (lpv) {
const levelValue = Log.Level[lpv];
if (levelValue) {
// stash it in _level just in case a future value of the pref is
// invalid, in which case we end up continuing to use this value.
this._level = levelValue;
return levelValue;
}
} else {
// in case the pref has transitioned from a value to no value, we reset
// this._level and fall through to using the parent.
this._level = null;
}
}
if (this._level != null)
return this._level;
if (this.parent)
return this.parent.level;
dumpError("Log warning: root logger configuration error: no level defined");
return Log.Level.All;
},
set level(level) {
if (this._levelPrefName) {
// I guess we could honor this by nuking this._levelPrefValue, but it
// almost certainly implies confusion, so we'll warn and ignore.
dumpError(`Log warning: The log '${this.name}' is configured to use ` +
`the preference '${this._levelPrefName}' - you must adjust ` +
`the level by setting this preference, not by using the ` +
`level setter`);
return;
}
this._level = level;
},
_parent: null,
get parent() {
return this._parent;
},
set parent(parent) {
if (this._parent == parent) {
return;
}
// Remove ourselves from parent's children
if (this._parent) {
let index = this._parent.children.indexOf(this);
if (index != -1) {
this._parent.children.splice(index, 1);
}
}
this._parent = parent;
parent.children.push(this);
this.updateAppenders();
},
manageLevelFromPref(prefName) {
if (prefName == this._levelPrefName) {
// We've already configured this log with an observer for that pref.
return;
}
if (this._levelPrefName) {
dumpError(`The log '${this.name}' is already configured with the ` +
`preference '${this._levelPrefName}' - ignoring request to ` +
`also use the preference '${prefName}'`);
return;
}
this._levelPrefName = prefName;
XPCOMUtils.defineLazyPreferenceGetter(this, "_levelPrefValue", prefName);
},
updateAppenders: function updateAppenders() {
if (this._parent) {
let notOwnAppenders = this._parent.appenders.filter(function(appender) {
return !this.ownAppenders.includes(appender);
}, this);
this.appenders = notOwnAppenders.concat(this.ownAppenders);
} else {
this.appenders = this.ownAppenders.slice();
}
// Update children's appenders.
for (let i = 0; i < this.children.length; i++) {
this.children[i].updateAppenders();
}
},
addAppender: function Logger_addAppender(appender) {
if (this.ownAppenders.includes(appender)) {
return;
}
this.ownAppenders.push(appender);
this.updateAppenders();
},
removeAppender: function Logger_removeAppender(appender) {
let index = this.ownAppenders.indexOf(appender);
if (index == -1) {
return;
}
this.ownAppenders.splice(index, 1);
this.updateAppenders();
},
/**
* Logs a structured message object.
*
* @param action
* (string) A message action, one of a set of actions known to the
* log consumer.
* @param params
* (object) Parameters to be included in the message.
* If _level is included as a key and the corresponding value
* is a number or known level name, the message will be logged
* at the indicated level. If _message is included as a key, the
* value is used as the descriptive text for the message.
*/
logStructured(action, params) {
if (!action) {
throw "An action is required when logging a structured message.";
}
if (!params) {
this.log(this.level, undefined, {"action": action});
return;
}
if (typeof(params) != "object") {
throw "The params argument is required to be an object.";
}
let level = params._level;
if (level) {
let ulevel = level.toUpperCase();
if (ulevel in Log.Level.Numbers) {
level = Log.Level.Numbers[ulevel];
}
} else {
level = this.level;
}
params.action = action;
this.log(level, params._message, params);
},
log(level, string, params) {
if (this.level > level)
return;
// Hold off on creating the message object until we actually have
// an appender that's responsible.
let message;
let appenders = this.appenders;
for (let appender of appenders) {
if (appender.level > level) {
continue;
}
if (!message) {
message = new LogMessage(this._name, level, string, params);
}
appender.append(message);
}
},
fatal(string, params) {
this.log(Log.Level.Fatal, string, params);
},
error(string, params) {
this.log(Log.Level.Error, string, params);
},
warn(string, params) {
this.log(Log.Level.Warn, string, params);
},
info(string, params) {
this.log(Log.Level.Info, string, params);
},
config(string, params) {
this.log(Log.Level.Config, string, params);
},
debug(string, params) {
this.log(Log.Level.Debug, string, params);
},
trace(string, params) {
this.log(Log.Level.Trace, string, params);
}
};
/*
* LoggerRepository
* Implements a hierarchy of Loggers
*/
function LoggerRepository() {}
LoggerRepository.prototype = {
_loggers: {},
_rootLogger: null,
get rootLogger() {
if (!this._rootLogger) {
this._rootLogger = new Logger("root", this);
this._rootLogger.level = Log.Level.All;
}
return this._rootLogger;
},
set rootLogger(logger) {
throw "Cannot change the root logger";
},
_updateParents: function LogRep__updateParents(name) {
let pieces = name.split(".");
let cur, parent;
// find the closest parent
// don't test for the logger name itself, as there's a chance it's already
// there in this._loggers
for (let i = 0; i < pieces.length - 1; i++) {
if (cur)
cur += "." + pieces[i];
else
cur = pieces[i];
if (cur in this._loggers)
parent = cur;
}
// if we didn't assign a parent above, there is no parent
if (!parent)
this._loggers[name].parent = this.rootLogger;
else
this._loggers[name].parent = this._loggers[parent];
// trigger updates for any possible descendants of this logger
for (let logger in this._loggers) {
if (logger != name && logger.indexOf(name) == 0)
this._updateParents(logger);
}
},
/**
* Obtain a named Logger.
*
* The returned Logger instance for a particular name is shared among
* all callers. In other words, if two consumers call getLogger("foo"),
* they will both have a reference to the same object.
*
* @return Logger
*/
getLogger(name) {
if (name in this._loggers)
return this._loggers[name];
this._loggers[name] = new Logger(name, this);
this._updateParents(name);
return this._loggers[name];
},
/**
* Obtain a Logger that logs all string messages with a prefix.
*
* A common pattern is to have separate Logger instances for each instance
* of an object. But, you still want to distinguish between each instance.
* Since Log.repository.getLogger() returns shared Logger objects,
* monkeypatching one Logger modifies them all.
*
* This function returns a new object with a prototype chain that chains
* up to the original Logger instance. The new prototype has log functions
* that prefix content to each message.
*
* @param name
* (string) The Logger to retrieve.
* @param prefix
* (string) The string to prefix each logged message with.
*/
getLoggerWithMessagePrefix(name, prefix) {
let log = this.getLogger(name);
let proxy = Object.create(log);
proxy.log = (level, string, params) => log.log(level, prefix + string, params);
return proxy;
},
};
/*
* Formatters
* These massage a LogMessage into whatever output is desired.
* BasicFormatter and StructuredFormatter are implemented here.
*/
// Abstract formatter
function Formatter() {}
Formatter.prototype = {
format: function Formatter_format(message) {}
};
// Basic formatter that doesn't do anything fancy.
function BasicFormatter(dateFormat) {
if (dateFormat) {
this.dateFormat = dateFormat;
}
this.parameterFormatter = new ParameterFormatter();
}
BasicFormatter.prototype = {
__proto__: Formatter.prototype,
/**
* Format the text of a message with optional parameters.
* If the text contains ${identifier}, replace that with
* the value of params[identifier]; if ${}, replace that with
* the entire params object. If no params have been substituted
* into the text, format the entire object and append that
* to the message.
*/
formatText(message) {
let params = message.params;
if (typeof(params) == "undefined") {
return message.message || "";
}
// Defensive handling of non-object params
// We could add a special case for NSRESULT values here...
let pIsObject = (typeof(params) == "object" || typeof(params) == "function");
// if we have params, try and find substitutions.
if (this.parameterFormatter) {
// have we successfully substituted any parameters into the message?
// in the log message
let subDone = false;
let regex = /\$\{(\S*)\}/g;
let textParts = [];
if (message.message) {
textParts.push(message.message.replace(regex, (_, sub) => {
// ${foo} means use the params['foo']
if (sub) {
if (pIsObject && sub in message.params) {
subDone = true;
return this.parameterFormatter.format(message.params[sub]);
}
return "${" + sub + "}";
}
// ${} means use the entire params object.
subDone = true;
return this.parameterFormatter.format(message.params);
}));
}
if (!subDone) {
// There were no substitutions in the text, so format the entire params object
let rest = this.parameterFormatter.format(message.params);
if (rest !== null && rest != "{}") {
textParts.push(rest);
}
}
return textParts.join(": ");
}
return undefined;
},
format: function BF_format(message) {
return message.time + "\t" +
message.loggerName + "\t" +
message.levelDesc + "\t" +
this.formatText(message);
}
};
/**
* A formatter that only formats the string message component.
*/
function MessageOnlyFormatter() {
}
MessageOnlyFormatter.prototype = Object.freeze({
__proto__: Formatter.prototype,
format(message) {
return message.message;
},
});
// Structured formatter that outputs JSON based on message data.
// This formatter will format unstructured messages by supplying
// default values.
function StructuredFormatter() { }
StructuredFormatter.prototype = {
__proto__: Formatter.prototype,
format(logMessage) {
let output = {
_time: logMessage.time,
_namespace: logMessage.loggerName,
_level: logMessage.levelDesc
};
for (let key in logMessage.params) {
output[key] = logMessage.params[key];
}
if (!output.action) {
output.action = "UNKNOWN";
}
if (!output._message && logMessage.message) {
output._message = logMessage.message;
}
return JSON.stringify(output);
}
};
/**
* Test an object to see if it is a Mozilla JS Error.
*/
function isError(aObj) {
return (aObj && typeof(aObj) == "object" && "name" in aObj && "message" in aObj &&
"fileName" in aObj && "lineNumber" in aObj && "stack" in aObj);
}
/*
* Parameter Formatters
* These massage an object used as a parameter for a LogMessage into
* a string representation of the object.
*/
function ParameterFormatter() {
this._name = "ParameterFormatter";
}
ParameterFormatter.prototype = {
format(ob) {
try {
if (ob === undefined) {
return "undefined";
}
if (ob === null) {
return "null";
}
// Pass through primitive types and objects that unbox to primitive types.
if ((typeof(ob) != "object" || typeof(ob.valueOf()) != "object") &&
typeof(ob) != "function") {
return ob;
}
if (ob instanceof Ci.nsIException) {
return ob.toString() + " " + Log.stackTrace(ob);
} else if (isError(ob)) {
return Log._formatError(ob);
}
// Just JSONify it. Filter out our internal fields and those the caller has
// already handled.
return JSON.stringify(ob, (key, val) => {
if (INTERNAL_FIELDS.has(key)) {
return undefined;
}
return val;
});
} catch (e) {
dumpError("Exception trying to format object for log message: " + Log.exceptionStr(e));
}
// Fancy formatting failed. Just toSource() it - but even this may fail!
try {
return ob.toSource();
} catch (_) { }
try {
return "" + ob;
} catch (_) {
return "[object]";
}
}
};
/*
* Appenders
* These can be attached to Loggers to log to different places
* Simply subclass and override doAppend to implement a new one
*/
function Appender(formatter) {
this._name = "Appender";
this._formatter = formatter ? formatter : new BasicFormatter();
}
Appender.prototype = {
level: Log.Level.All,
append: function App_append(message) {
if (message) {
this.doAppend(this._formatter.format(message));
}
},
toString: function App_toString() {
return this._name + " [level=" + this.level +
", formatter=" + this._formatter + "]";
},
doAppend: function App_doAppend(formatted) {}
};
/*
* DumpAppender
* Logs to standard out
*/
function DumpAppender(formatter) {
Appender.call(this, formatter);
this._name = "DumpAppender";
}
DumpAppender.prototype = {
__proto__: Appender.prototype,
doAppend: function DApp_doAppend(formatted) {
dump(formatted + "\n");
}
};
/*
* ConsoleAppender
* Logs to the javascript console
*/
function ConsoleAppender(formatter) {
Appender.call(this, formatter);
this._name = "ConsoleAppender";
}
ConsoleAppender.prototype = {
__proto__: Appender.prototype,
// XXX this should be replaced with calls to the Browser Console
append: function App_append(message) {
if (message) {
let m = this._formatter.format(message);
if (message.level > Log.Level.Warn) {
Cu.reportError(m);
return;
}
this.doAppend(m);
}
},
doAppend: function CApp_doAppend(formatted) {
Services.console.logStringMessage(formatted);
}
};
/**
* Append to an nsIStorageStream
*
* This writes logging output to an in-memory stream which can later be read
* back as an nsIInputStream. It can be used to avoid expensive I/O operations
* during logging. Instead, one can periodically consume the input stream and
* e.g. write it to disk asynchronously.
*/
function StorageStreamAppender(formatter) {
Appender.call(this, formatter);
this._name = "StorageStreamAppender";
}
StorageStreamAppender.prototype = {
__proto__: Appender.prototype,
_converterStream: null, // holds the nsIConverterOutputStream
_outputStream: null, // holds the underlying nsIOutputStream
_ss: null,
get outputStream() {
if (!this._outputStream) {
// First create a raw stream. We can bail out early if that fails.
this._outputStream = this.newOutputStream();
if (!this._outputStream) {
return null;
}
// Wrap the raw stream in an nsIConverterOutputStream. We can reuse
// the instance if we already have one.
if (!this._converterStream) {
this._converterStream = Cc["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Ci.nsIConverterOutputStream);
}
this._converterStream.init(this._outputStream, "UTF-8");
}
return this._converterStream;
},
newOutputStream: function newOutputStream() {
let ss = this._ss = Cc["@mozilla.org/storagestream;1"]
.createInstance(Ci.nsIStorageStream);
ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null);
return ss.getOutputStream(0);
},
getInputStream: function getInputStream() {
if (!this._ss) {
return null;
}
return this._ss.newInputStream(0);
},
reset: function reset() {
if (!this._outputStream) {
return;
}
this.outputStream.close();
this._outputStream = null;
this._ss = null;
},
doAppend(formatted) {
if (!formatted) {
return;
}
try {
this.outputStream.writeString(formatted + "\n");
} catch (ex) {
if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
// The underlying output stream is closed, so let's open a new one
// and try again.
this._outputStream = null;
} try {
this.outputStream.writeString(formatted + "\n");
} catch (ex) {
// Ah well, we tried, but something seems to be hosed permanently.
}
}
}
};
/**
* File appender
*
* Writes output to file using OS.File.
*/
function FileAppender(path, formatter) {
Appender.call(this, formatter);
this._name = "FileAppender";
this._encoder = new TextEncoder();
this._path = path;
this._file = null;
this._fileReadyPromise = null;
// This is a promise exposed for testing/debugging the logger itself.
this._lastWritePromise = null;
}
FileAppender.prototype = {
__proto__: Appender.prototype,
_openFile() {
return (async () => {
try {
this._file = await OS.File.open(this._path,
{truncate: true});
} catch (err) {
if (err instanceof OS.File.Error) {
this._file = null;
} else {
throw err;
}
}
})();
},
_getFile() {
if (!this._fileReadyPromise) {
this._fileReadyPromise = this._openFile();
}
return this._fileReadyPromise;
},
doAppend(formatted) {
let array = this._encoder.encode(formatted + "\n");
if (this._file) {
this._lastWritePromise = this._file.write(array);
} else {
this._lastWritePromise = this._getFile().then(_ => {
this._fileReadyPromise = null;
if (this._file) {
return this._file.write(array);
}
return undefined;
});
}
},
reset() {
let fileClosePromise = this._file.close();
return fileClosePromise.then(_ => {
this._file = null;
return OS.File.remove(this._path);
});
}
};
/**
* Bounded File appender
*
* Writes output to file using OS.File. After the total message size
* (as defined by formatted.length) exceeds maxSize, existing messages
* will be discarded, and subsequent writes will be appended to a new log file.
*/
function BoundedFileAppender(path, formatter, maxSize = 2 * ONE_MEGABYTE) {
FileAppender.call(this, path, formatter);
this._name = "BoundedFileAppender";
this._size = 0;
this._maxSize = maxSize;
this._closeFilePromise = null;
}
BoundedFileAppender.prototype = {
__proto__: FileAppender.prototype,
doAppend(formatted) {
if (!this._removeFilePromise) {
if (this._size < this._maxSize) {
this._size += formatted.length;
return FileAppender.prototype.doAppend.call(this, formatted);
}
this._removeFilePromise = this.reset();
}
this._removeFilePromise.then(_ => {
this._removeFilePromise = null;
this.doAppend(formatted);
});
return undefined;
},
reset() {
let fileClosePromise;
if (this._fileReadyPromise) {
// An attempt to open the file may still be in progress.
fileClosePromise = this._fileReadyPromise.then(_ => {
return this._file.close();
});
} else {
fileClosePromise = this._file.close();
}
return fileClosePromise.then(_ => {
this._size = 0;
this._file = null;
return OS.File.remove(this._path);
});
}
};