Bug 816254 - Add logging to Downloads Panel r=mak

This commit is contained in:
Christian Sonne 2013-02-15 17:34:18 -08:00
parent b2ae6ae8ea
commit 1a0b7acc7e
5 changed files with 189 additions and 6 deletions

View File

@ -306,6 +306,9 @@ pref("browser.urlbar.trimURLs", true);
pref("browser.altClickSave", false);
// Enable logging downloads operations to the Error Console.
pref("browser.download.debug", false);
// Number of milliseconds to wait for the http headers (and thus
// the Content-Disposition filename) before giving up and falling back to
// picking a filename without that info in hand so that the user sees some

View File

@ -121,7 +121,9 @@ const DownloadsPanel = {
*/
initialize: function DP_initialize(aCallback)
{
DownloadsCommon.log("Attempting to initialize DownloadsPanel for a window.");
if (this._state != this.kStateUninitialized) {
DownloadsCommon.log("DownloadsPanel is already initialized.");
DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
aCallback);
return;
@ -137,11 +139,16 @@ const DownloadsPanel = {
// Now that data loading has eventually started, load the required XUL
// elements and initialize our views.
DownloadsCommon.log("Ensuring DownloadsPanel overlay loaded.");
DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
function DP_I_callback() {
DownloadsViewController.initialize();
DownloadsCommon.log("Attaching DownloadsView...");
DownloadsCommon.getData(window).addView(DownloadsView);
DownloadsCommon.log("DownloadsView attached - the panel for this window",
"should now see download items come in.");
DownloadsPanel._attachEventListeners();
DownloadsCommon.log("DownloadsPanel initialized.");
aCallback();
});
},
@ -153,7 +160,9 @@ const DownloadsPanel = {
*/
terminate: function DP_terminate()
{
DownloadsCommon.log("Attempting to terminate DownloadsPanel for a window.");
if (this._state == this.kStateUninitialized) {
DownloadsCommon.log("DownloadsPanel was never initialized. Nothing to do.");
return;
}
@ -169,6 +178,7 @@ const DownloadsPanel = {
this._state = this.kStateUninitialized;
DownloadsSummary.active = false;
DownloadsCommon.log("DownloadsPanel terminated.");
},
//////////////////////////////////////////////////////////////////////////////
@ -191,7 +201,10 @@ const DownloadsPanel = {
*/
showPanel: function DP_showPanel()
{
DownloadsCommon.log("Opening the downloads panel.");
if (this.isPanelShowing) {
DownloadsCommon.log("Panel is already showing - focusing instead.");
this._focusPanel();
return;
}
@ -204,6 +217,7 @@ const DownloadsPanel = {
setTimeout(function () DownloadsPanel._openPopupIfDataReady(), 0);
}.bind(this));
DownloadsCommon.log("Waiting for the downloads panel to appear.");
this._state = this.kStateWaitingData;
},
@ -213,7 +227,10 @@ const DownloadsPanel = {
*/
hidePanel: function DP_hidePanel()
{
DownloadsCommon.log("Closing the downloads panel.");
if (!this.isPanelShowing) {
DownloadsCommon.log("Downloads panel is not showing - nothing to do.");
return;
}
@ -223,6 +240,7 @@ const DownloadsPanel = {
// was open, then the onPopupHidden event handler has already updated the
// current state, otherwise we must update the state ourselves.
this._state = this.kStateHidden;
DownloadsCommon.log("Downloads panel is now closed.");
},
/**
@ -298,6 +316,7 @@ const DownloadsPanel = {
return;
}
DownloadsCommon.log("Downloads panel has shown.");
this._state = this.kStateShown;
// Since at most one popup is open at any given time, we can set globally.
@ -319,6 +338,8 @@ const DownloadsPanel = {
return;
}
DownloadsCommon.log("Downloads panel has hidden.");
// Removes the keyfocus attribute so that we stop handling keyboard
// navigation.
this.keyFocusing = false;
@ -341,6 +362,7 @@ const DownloadsPanel = {
*/
showDownloadsHistory: function DP_showDownloadsHistory()
{
DownloadsCommon.log("Showing download history.");
// Hide the panel before showing another window, otherwise focus will return
// to the browser window when the panel closes automatically.
this.hidePanel();
@ -445,6 +467,8 @@ const DownloadsPanel = {
return;
}
DownloadsCommon.log("Received a paste event.");
let trans = Cc["@mozilla.org/widget/transferable;1"]
.createInstance(Ci.nsITransferable);
trans.init(null);
@ -464,6 +488,7 @@ const DownloadsPanel = {
}
let uri = NetUtil.newURI(url);
DownloadsCommon.log("Pasted URL seems valid. Starting download.");
saveURL(uri.spec, name || uri.spec, null, true, true,
undefined, document);
} catch (ex) {}
@ -525,9 +550,13 @@ const DownloadsPanel = {
}
if (aAnchor) {
DownloadsCommon.log("Opening downloads panel popup.");
this.panel.openPopup(aAnchor, "bottomcenter topright", 0, 0, false,
null);
} else {
DownloadsCommon.error("We can't find the anchor! Failure case - opening",
"downloads panel on TabsToolbar. We should never",
"get here!");
Components.utils.reportError(
"Downloads button cannot be found");
}
@ -597,6 +626,7 @@ const DownloadsOverlayLoader = {
}
this._overlayLoading = true;
DownloadsCommon.log("Loading overlay ", aOverlay);
document.loadOverlay(aOverlay, DOL_EOL_loadCallback.bind(this));
},
@ -663,12 +693,16 @@ const DownloadsView = {
*/
_itemCountChanged: function DV_itemCountChanged()
{
DownloadsCommon.log("The downloads item count has changed - we are tracking",
this._dataItems.length, "downloads in total.");
let count = this._dataItems.length;
let hiddenCount = count - this.kItemCountLimit;
if (count > 0) {
DownloadsCommon.log("Setting the panel's hasdownloads attribute to true.");
DownloadsPanel.panel.setAttribute("hasdownloads", "true");
} else {
DownloadsCommon.log("Removing the panel's hasdownloads attribute.");
DownloadsPanel.panel.removeAttribute("hasdownloads");
}
@ -704,6 +738,7 @@ const DownloadsView = {
*/
onDataLoadStarting: function DV_onDataLoadStarting()
{
DownloadsCommon.log("onDataLoadStarting called for DownloadsView.");
this.loading = true;
},
@ -712,6 +747,8 @@ const DownloadsView = {
*/
onDataLoadCompleted: function DV_onDataLoadCompleted()
{
DownloadsCommon.log("onDataLoadCompleted called for DownloadsView.");
this.loading = false;
// We suppressed item count change notifications during the batch load, at
@ -730,6 +767,9 @@ const DownloadsView = {
*/
onDataInvalidated: function DV_onDataInvalidated()
{
DownloadsCommon.log("Downloads data has been invalidated. Cleaning up",
"DownloadsView.");
DownloadsPanel.terminate();
// Clear the list by replacing with a shallow copy.
@ -755,6 +795,9 @@ const DownloadsView = {
*/
onDataItemAdded: function DV_onDataItemAdded(aDataItem, aNewest)
{
DownloadsCommon.log("A new download data item was added - aNewest =",
aNewest);
if (aNewest) {
this._dataItems.unshift(aDataItem);
} else {
@ -790,6 +833,8 @@ const DownloadsView = {
*/
onDataItemRemoved: function DV_onDataItemRemoved(aDataItem)
{
DownloadsCommon.log("A download data item was removed.");
let itemIndex = this._dataItems.indexOf(aDataItem);
this._dataItems.splice(itemIndex, 1);
@ -837,6 +882,9 @@ const DownloadsView = {
*/
_addViewItem: function DV_addViewItem(aDataItem, aNewest)
{
DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.",
"aNewest =", aNewest);
let element = document.createElement("richlistitem");
let viewItem = new DownloadsViewItem(aDataItem, element);
this._viewItems[aDataItem.downloadGuid] = viewItem;
@ -852,6 +900,7 @@ const DownloadsView = {
*/
_removeViewItem: function DV_removeViewItem(aDataItem)
{
DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list.");
let element = this.getViewItem(aDataItem)._element;
let previousSelectedIndex = this.richListBox.selectedIndex;
this.richListBox.removeChild(element);

View File

@ -59,6 +59,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsLogger",
"resource:///modules/DownloadsLogger.jsm");
const nsIDM = Ci.nsIDownloadManager;
@ -90,6 +92,22 @@ XPCOMUtils.defineLazyGetter(this, "DownloadsLocalFileCtor", function () {
const kPartialDownloadSuffix = ".part";
const kPrefDebug = "browser.download.debug";
let DebugPrefObserver = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe: function PDO_observe(aSubject, aTopic, aData) {
this.debugEnabled = Services.prefs.getBoolPref(kPrefDebug);
}
}
XPCOMUtils.defineLazyGetter(DebugPrefObserver, "debugEnabled", function () {
Services.prefs.addObserver(kPrefDebug, DebugPrefObserver, true);
return Services.prefs.getBoolPref(kPrefDebug);
});
////////////////////////////////////////////////////////////////////////////////
//// DownloadsCommon
@ -98,6 +116,27 @@ const kPartialDownloadSuffix = ".part";
* and provides shared methods for all the instances of the user interface.
*/
this.DownloadsCommon = {
log: function DC_log(...aMessageArgs) {
delete this.log;
this.log = function DC_log(...aMessageArgs) {
if (!DebugPrefObserver.debugEnabled) {
return;
}
DownloadsLogger.log.apply(DownloadsLogger, aMessageArgs);
}
this.log.apply(this, aMessageArgs);
},
error: function DC_error(...aMessageArgs) {
delete this.error;
this.error = function DC_error(...aMessageArgs) {
if (!DebugPrefObserver.debugEnabled) {
return;
}
DownloadsLogger.reportError.apply(DownloadsLogger, aMessageArgs);
}
this.error.apply(this, aMessageArgs);
},
/**
* Returns an object whose keys are the string names from the downloads string
* bundle, and whose values are either the translated strings or functions
@ -687,7 +726,8 @@ DownloadsDataCtor.prototype = {
return existingItem;
}
}
DownloadsCommon.log("Creating a new DownloadsDataItem with downloadGuid =",
downloadGuid);
let dataItem = new DownloadsDataItem(aSource);
this.dataItems[downloadGuid] = dataItem;
@ -759,6 +799,7 @@ DownloadsDataCtor.prototype = {
if (aActiveOnly) {
if (this._loadState == this.kLoadNone) {
DownloadsCommon.log("Loading only active downloads from the persistence database");
// Indicate to the views that a batch loading operation is in progress.
this._views.forEach(
function (view) view.onDataLoadStarting()
@ -777,6 +818,7 @@ DownloadsDataCtor.prototype = {
this._views.forEach(
function (view) view.onDataLoadCompleted()
);
DownloadsCommon.log("Active downloads done loading.");
}
} else {
if (this._loadState != this.kLoadAll) {
@ -784,6 +826,7 @@ DownloadsDataCtor.prototype = {
// columns are read in the _initFromDataRow method of DownloadsDataItem.
// Order by descending download identifier so that the most recent
// downloads are notified first to the listening views.
DownloadsCommon.log("Loading all downloads from the persistence database.");
let dbConnection = Services.downloads.DBConnection;
let statement = dbConnection.createAsyncStatement(
"SELECT guid, target, name, source, referrer, state, "
@ -834,12 +877,14 @@ DownloadsDataCtor.prototype = {
handleError: function DD_handleError(aError)
{
Cu.reportError("Database statement execution error (" + aError.result +
"): " + aError.message);
DownloadsCommon.error("Database statement execution error (",
aError.result, "): ", aError.message);
},
handleCompletion: function DD_handleCompletion(aReason)
{
DownloadsCommon.log("Loading all downloads from database completed with reason:",
aReason);
this._pendingStatement = null;
// To ensure that we don't inadvertently delete more downloads from the
@ -868,13 +913,16 @@ DownloadsDataCtor.prototype = {
case "download-manager-remove-download-guid":
// If a single download was removed, remove the corresponding data item.
if (aSubject) {
this._removeDataItem(aSubject.QueryInterface(Ci.nsISupportsCString)
.data);
let downloadGuid = aSubject.data.QueryInterface(Ci.nsISupportsCString);
DownloadsCommon.log("A single download with id",
downloadGuid, "was removed.");
this._removeDataItem(downloadGuid);
break;
}
// Multiple downloads have been removed. Iterate over known downloads
// and remove those that don't exist anymore.
DownloadsCommon.log("Multiple downloads were removed.");
for each (let dataItem in this.dataItems) {
if (dataItem) {
// Bug 449811 - We have to bind to the dataItem because Javascript
@ -883,6 +931,8 @@ DownloadsDataCtor.prototype = {
Services.downloads.getDownloadByGUID(dataItemBinding.downloadGuid,
function(aStatus, aResult) {
if (aStatus == Components.results.NS_ERROR_NOT_AVAILABLE) {
DownloadsCommon.log("Removing download with id",
dataItemBinding.downloadGuid);
this._removeDataItem(dataItemBinding.downloadGuid);
}
}.bind(this));
@ -916,6 +966,7 @@ DownloadsDataCtor.prototype = {
let wasInProgress = dataItem.inProgress;
DownloadsCommon.log("A download changed its state to:", aDownload.state);
dataItem.state = aDownload.state;
dataItem.referrer = aDownload.referrer && aDownload.referrer.spec;
dataItem.resumable = aDownload.resumable;
@ -1034,7 +1085,9 @@ DownloadsDataCtor.prototype = {
*/
_notifyDownloadEvent: function DD_notifyDownloadEvent(aType)
{
DownloadsCommon.log("Attempting to notify that a new download has started or finished.");
if (DownloadsCommon.useToolkitUI) {
DownloadsCommon.log("Cancelling notification - we're using the toolkit downloads manager.");
return;
}
@ -1048,6 +1101,7 @@ DownloadsDataCtor.prototype = {
// For new downloads after the first one, don't show the panel
// automatically, but provide a visible notification in the topmost
// browser window, if the status indicator is already visible.
DownloadsCommon.log("Showing new download notification.");
browserWin.DownloadsIndicatorView.showEventNotification(aType);
return;
}

View File

@ -0,0 +1,76 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
/**
* The contents of this file were copied almost entirely from
* toolkit/identity/LogUtils.jsm. Until we've got a more generalized logging
* mechanism for toolkit, I think this is going to be how we roll.
*/
"use strict";
this.EXPORTED_SYMBOLS = ["DownloadsLogger"];
const PREF_DEBUG = "browser.download.debug";
const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
this.DownloadsLogger = {
_generateLogMessage: function _generateLogMessage(args) {
// create a string representation of a list of arbitrary things
let strings = [];
for (let arg of args) {
if (typeof arg === 'string') {
strings.push(arg);
} else if (arg === undefined) {
strings.push('undefined');
} else if (arg === null) {
strings.push('null');
} else {
try {
strings.push(JSON.stringify(arg, null, 2));
} catch(err) {
strings.push("<<something>>");
}
}
};
return 'Downloads: ' + strings.join(' ');
},
/**
* log() - utility function to print a list of arbitrary things
*
* Enable with about:config pref browser.download.debug
*/
log: function DL_log(...args) {
let output = this._generateLogMessage(args);
dump(output + "\n");
// Additionally, make the output visible in the Error Console
Services.console.logStringMessage(output);
},
/**
* reportError() - report an error through component utils as well as
* our log function
*/
reportError: function DL_reportError(...aArgs) {
// Report the error in the browser
let output = this._generateLogMessage(aArgs);
Cu.reportError(output);
dump("ERROR:" + output + "\n");
for (let frame = Components.stack.caller; frame; frame = frame.caller) {
dump("\t" + frame + "\n");
}
}
};

View File

@ -18,8 +18,9 @@ EXTRA_PP_COMPONENTS = \
DownloadsStartup.js \
$(NULL)
EXTRA_PP_JS_MODULES = \
EXTRA_JS_MODULES = \
DownloadsCommon.jsm \
DownloadsLogger.jsm \
$(NULL)
include $(topsrcdir)/config/rules.mk