mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to mozilla-central
This commit is contained in:
commit
a656290538
@ -10,6 +10,9 @@ let Ci = Components.interfaces;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm");
|
||||
#ifndef RELEASE_BUILD
|
||||
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
|
||||
#endif
|
||||
Cu.import("resource://gre/modules/NewTabUtils.jsm");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
|
||||
|
@ -131,10 +131,12 @@ Site.prototype = {
|
||||
|
||||
if (this.isPinned())
|
||||
this._updateAttributes(true);
|
||||
#ifndef RELEASE_BUILD
|
||||
// request a staleness check for the thumbnail, which will cause page.js
|
||||
// to be notified and call our refreshThumbnail() method.
|
||||
PageThumbs.captureIfStale(this.url);
|
||||
BackgroundPageThumbs.captureIfStale(this.url);
|
||||
// but still display whatever thumbnail might be available now.
|
||||
#endif
|
||||
this.refreshThumbnail();
|
||||
},
|
||||
|
||||
|
@ -62,6 +62,10 @@ var Downloads = {
|
||||
this.manager.addListener(this._progress);
|
||||
|
||||
this._downloadProgressIndicator = document.getElementById("download-progress");
|
||||
|
||||
if (this.manager.activeDownloadCount) {
|
||||
setTimeout (this._restartWithActiveDownloads.bind(this), 0);
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function dh_uninit() {
|
||||
@ -74,6 +78,24 @@ var Downloads = {
|
||||
}
|
||||
},
|
||||
|
||||
_restartWithActiveDownloads: function() {
|
||||
let activeDownloads = this.manager.activeDownloads;
|
||||
|
||||
while (activeDownloads.hasMoreElements()) {
|
||||
let dl = activeDownloads.getNext();
|
||||
switch (dl.state) {
|
||||
case 0: // Downloading
|
||||
case 5: // Queued
|
||||
this.watchDownload(dl);
|
||||
this.updateInfobar(dl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.manager.activeDownloadCount) {
|
||||
Services.obs.notifyObservers(null, "dl-request", "");
|
||||
}
|
||||
},
|
||||
|
||||
openDownload: function dh_openDownload(aDownload) {
|
||||
let fileURI = aDownload.target
|
||||
|
||||
@ -131,8 +153,8 @@ var Downloads = {
|
||||
|
||||
// Cancels all downloads.
|
||||
cancelDownloads: function dh_cancelDownloads() {
|
||||
for (let info of this._progressNotificationInfo) {
|
||||
this.cancelDownload(info[1].download);
|
||||
for (let [guid, info] of this._progressNotificationInfo) {
|
||||
this.cancelDownload(info.download);
|
||||
}
|
||||
this._downloadCount = 0;
|
||||
this._progressNotificationInfo.clear();
|
||||
@ -172,11 +194,12 @@ var Downloads = {
|
||||
|
||||
showNotification: function dh_showNotification(title, msg, buttons, priority) {
|
||||
this._notificationBox.notificationsHidden = false;
|
||||
return this._notificationBox.appendNotification(msg,
|
||||
let notification = this._notificationBox.appendNotification(msg,
|
||||
title,
|
||||
URI_GENERIC_ICON_DOWNLOAD,
|
||||
priority,
|
||||
buttons);
|
||||
return notification;
|
||||
},
|
||||
|
||||
_showDownloadFailedNotification: function (aDownload) {
|
||||
@ -301,10 +324,9 @@ var Downloads = {
|
||||
}
|
||||
|
||||
let totPercent = 0;
|
||||
for (let info of this._progressNotificationInfo) {
|
||||
// info[0] => download guid
|
||||
// info[1].download => nsIDownload
|
||||
totPercent += info[1].download.percentComplete;
|
||||
for (let [guid, info] of this._progressNotificationInfo) {
|
||||
// info.download => nsIDownload
|
||||
totPercent += info.download.percentComplete;
|
||||
}
|
||||
|
||||
let percentComplete = totPercent / this._progressNotificationInfo.size;
|
||||
@ -313,10 +335,10 @@ var Downloads = {
|
||||
|
||||
_computeDownloadProgressString: function dv_computeDownloadProgressString(aDownload) {
|
||||
let totTransferred = 0, totSize = 0, totSecondsLeft = 0;
|
||||
for (let info of this._progressNotificationInfo) {
|
||||
let size = info[1].download.size;
|
||||
let amountTransferred = info[1].download.amountTransferred;
|
||||
let speed = info[1].download.speed;
|
||||
for (let [guid, info] of this._progressNotificationInfo) {
|
||||
let size = info.download.size;
|
||||
let amountTransferred = info.download.amountTransferred;
|
||||
let speed = info.download.speed;
|
||||
|
||||
totTransferred += amountTransferred;
|
||||
totSize += size;
|
||||
@ -357,13 +379,14 @@ var Downloads = {
|
||||
},
|
||||
|
||||
updateInfobar: function dv_updateInfobar(aDownload) {
|
||||
this._saveDownloadData(aDownload);
|
||||
let message = this._computeDownloadProgressString(aDownload);
|
||||
this._updateCircularProgressMeter();
|
||||
|
||||
if (this._notificationBox && this._notificationBox.notificationsHidden)
|
||||
this._notificationBox.notificationsHidden = false;
|
||||
|
||||
if (this._progressNotification == null ||
|
||||
!this._notificationBox.getNotificationWithValue("download-progress")) {
|
||||
|
||||
let cancelButtonText =
|
||||
Strings.browser.GetStringFromName("downloadCancel");
|
||||
|
||||
@ -395,6 +418,19 @@ var Downloads = {
|
||||
}
|
||||
},
|
||||
|
||||
watchDownload: function dv_watchDownload(aDownload) {
|
||||
this._saveDownloadData(aDownload);
|
||||
this._downloadCount++;
|
||||
this._downloadsInProgress++;
|
||||
if (!this._progressNotificationInfo.get(aDownload.guid)) {
|
||||
this._progressNotificationInfo.set(aDownload.guid, {});
|
||||
}
|
||||
if (!this._progressAlert) {
|
||||
this._progressAlert = new AlertDownloadProgressListener();
|
||||
this.manager.addListener(this._progressAlert);
|
||||
}
|
||||
},
|
||||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
let message = "";
|
||||
let msgTitle = "";
|
||||
@ -405,16 +441,8 @@ var Downloads = {
|
||||
this._runDownloadBooleanMap.set(file.path, (aData == 'true'));
|
||||
break;
|
||||
case "dl-start":
|
||||
this._downloadCount++;
|
||||
this._downloadsInProgress++;
|
||||
let download = aSubject.QueryInterface(Ci.nsIDownload);
|
||||
if (!this._progressNotificationInfo.get(download.guid)) {
|
||||
this._progressNotificationInfo.set(download.guid, {});
|
||||
}
|
||||
if (!this._progressAlert) {
|
||||
this._progressAlert = new AlertDownloadProgressListener();
|
||||
this.manager.addListener(this._progressAlert);
|
||||
}
|
||||
this.watchDownload(download);
|
||||
this.updateInfobar(download);
|
||||
break;
|
||||
case "dl-done":
|
||||
|
@ -44,7 +44,7 @@ HelperAppLauncherDialog.prototype = {
|
||||
|
||||
_getDownloadSize: function dv__getDownloadSize (aSize) {
|
||||
let displaySize = DownloadUtils.convertByteUnits(aSize);
|
||||
if (displaySize[0] > 0) // [0] is size, [1] is units
|
||||
if (!isNaN(displaySize[0]) && displaySize[0] > 0) // [0] is size, [1] is units
|
||||
return displaySize.join("");
|
||||
else {
|
||||
let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
|
@ -240,14 +240,20 @@ CrossSlideHandler.prototype = {
|
||||
*/
|
||||
_fireProgressEvent: function CrossSliding_fireEvent(aState, aEvent) {
|
||||
if (!this.drag)
|
||||
return;
|
||||
return;
|
||||
let event = this.node.ownerDocument.createEvent("Events");
|
||||
let crossAxis = this.drag.crossAxis;
|
||||
let crossAxisName = this.drag.crossAxis;
|
||||
event.initEvent("MozCrossSliding", true, true);
|
||||
event.crossSlidingState = aState;
|
||||
event.position = this.drag.position;
|
||||
event.direction = this.drag.crossAxis;
|
||||
event.delta = this.drag.position[crossAxis] - this.drag.origin[crossAxis];
|
||||
if ('position' in this.drag) {
|
||||
event.position = this.drag.position;
|
||||
if (crossAxisName) {
|
||||
event.direction = crossAxisName;
|
||||
if('origin' in this.drag) {
|
||||
event.delta = this.drag.position[crossAxisName] - this.drag.origin[crossAxisName];
|
||||
}
|
||||
}
|
||||
}
|
||||
aEvent.target.dispatchEvent(event);
|
||||
},
|
||||
|
||||
|
@ -229,7 +229,14 @@ public class BrowserSearch extends HomeFragment
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
// Account for the search engines
|
||||
// Perform the user-entered search if the user clicks on a search engine row.
|
||||
// This row will be disabled if suggestions (in addition to the user-entered term) are showing.
|
||||
if (view instanceof SearchEngineRow) {
|
||||
((SearchEngineRow) view).performUserEnteredSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Account for the search engine rows.
|
||||
position -= getSuggestEngineCount();
|
||||
final Cursor c = mAdapter.getCursor(position);
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||
@ -242,9 +249,13 @@ public class BrowserSearch extends HomeFragment
|
||||
mList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
// Account for the search engines
|
||||
position -= getSuggestEngineCount();
|
||||
// Don't do anything when the user long-clicks on a search engine row.
|
||||
if (view instanceof SearchEngineRow) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Account for the search engine rows.
|
||||
position -= getSuggestEngineCount();
|
||||
return mList.onItemLongClick(parent, view, position, id);
|
||||
}
|
||||
});
|
||||
|
@ -111,31 +111,6 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
||||
mUserEnteredView.setOnClickListener(mClickListener);
|
||||
|
||||
mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text);
|
||||
|
||||
// Handle clicks on this row that don't happen on individual suggestion views.
|
||||
setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Don't do anything if we are showing suggestions.
|
||||
if (mSearchEngine.suggestions.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, perform a search for the user entered term.
|
||||
String searchTerm = getSuggestionTextFromView(mUserEnteredView);
|
||||
if (mSearchListener != null) {
|
||||
mSearchListener.onSearch(mSearchEngine.name, searchTerm);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Intercept long clicks to avoid trying to show a context menu.
|
||||
setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setDescriptionOnSuggestion(View v, String suggestion) {
|
||||
@ -154,6 +129,16 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
||||
setDescriptionOnSuggestion(suggestionText, suggestion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a search for the user-entered term.
|
||||
*/
|
||||
public void performUserEnteredSearch() {
|
||||
String searchTerm = getSuggestionTextFromView(mUserEnteredView);
|
||||
if (mSearchListener != null) {
|
||||
mSearchListener.onSearch(mSearchEngine.name, searchTerm);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSearchTerm(String searchTerm) {
|
||||
mUserEnteredTextView.setText(searchTerm);
|
||||
|
||||
|
@ -510,7 +510,9 @@ var SelectionHandler = {
|
||||
// Remove our listener before we clear the selection
|
||||
selection.QueryInterface(Ci.nsISelectionPrivate).removeSelectionListener(this);
|
||||
// Clear selection without clearing the anchorNode or focusNode
|
||||
selection.collapseToStart();
|
||||
if (selection.rangeCount != 0) {
|
||||
selection.collapseToStart();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -147,13 +147,6 @@ pref("browser.helperApps.alwaysAsk.force", false);
|
||||
pref("browser.helperApps.neverAsk.saveToDisk", "");
|
||||
pref("browser.helperApps.neverAsk.openFile", "");
|
||||
|
||||
#ifdef XP_WIN
|
||||
// By default, security zone information is stored in the Alternate Data Stream
|
||||
// of downloaded executable files on Windows. This preference allows disabling
|
||||
// this feature, and thus the associated system-level execution prompts.
|
||||
pref("browser.download.saveZoneInformation", true);
|
||||
#endif
|
||||
|
||||
// xxxbsmedberg: where should prefs for the toolkit go?
|
||||
pref("browser.chrome.toolbar_tips", true);
|
||||
// 0 = Pictures Only, 1 = Text Only, 2 = Pictures and Text
|
||||
|
@ -67,9 +67,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gDownloadHistory",
|
||||
"@mozilla.org/browser/download-history;1",
|
||||
Ci.nsIDownloadHistory);
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
|
||||
"@mozilla.org/uriloader/external-helper-app-service;1",
|
||||
Ci.nsIExternalHelperAppService);
|
||||
@ -1208,30 +1205,6 @@ DownloadSaver.prototype = {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* This can be called by the saver implementation when the download is already
|
||||
* started, to add it to the browsing history. This method has no effect if
|
||||
* the download is private.
|
||||
*/
|
||||
addToHistory: function ()
|
||||
{
|
||||
if (this.download.source.isPrivate) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sourceUri = NetUtil.newURI(this.download.source.url);
|
||||
let referrer = this.download.source.referrer;
|
||||
let referrerUri = referrer ? NetUtil.newURI(referrer) : null;
|
||||
let targetUri = NetUtil.newURI(new FileUtils.File(
|
||||
this.download.target.path));
|
||||
|
||||
// The start time is always available when we reach this point.
|
||||
let startPRTime = this.download.startTime.getTime() * 1000;
|
||||
|
||||
gDownloadHistory.addDownload(sourceUri, referrerUri, startPRTime,
|
||||
targetUri);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a static representation of the current object state.
|
||||
*
|
||||
@ -1293,11 +1266,6 @@ DownloadCopySaver.prototype = {
|
||||
*/
|
||||
_canceled: false,
|
||||
|
||||
/**
|
||||
* True if the associated download has already been added to browsing history.
|
||||
*/
|
||||
alreadyAddedToHistory: false,
|
||||
|
||||
/**
|
||||
* String corresponding to the entityID property of the nsIResumableChannel
|
||||
* used to execute the download, or null if the channel was not resumable or
|
||||
@ -1320,16 +1288,6 @@ DownloadCopySaver.prototype = {
|
||||
let keepPartialData = download.tryToKeepPartialData;
|
||||
|
||||
return Task.spawn(function task_DCS_execute() {
|
||||
// Add the download to history the first time it is started in this
|
||||
// session. If the download is restarted in a different session, a new
|
||||
// history visit will be added. We do this just to avoid the complexity
|
||||
// of serializing this state between sessions, since adding a new visit
|
||||
// does not have any noticeable side effect.
|
||||
if (!this.alreadyAddedToHistory) {
|
||||
this.addToHistory();
|
||||
this.alreadyAddedToHistory = true;
|
||||
}
|
||||
|
||||
// To reduce the chance that other downloads reuse the same final target
|
||||
// file name, we should create a placeholder as soon as possible, before
|
||||
// starting the network request. The placeholder is also required in case
|
||||
@ -1675,14 +1633,8 @@ DownloadLegacySaver.prototype = {
|
||||
*
|
||||
* @param aRequest
|
||||
* nsIRequest associated to the status update.
|
||||
* @param aAlreadyAddedToHistory
|
||||
* Indicates that the nsIExternalHelperAppService component already
|
||||
* added the download to the browsing history, unless it was started
|
||||
* from a private browsing window. When this parameter is false, the
|
||||
* download is added to the browsing history here. Private downloads
|
||||
* are never added to history even if this parameter is false.
|
||||
*/
|
||||
onTransferStarted: function (aRequest, aAlreadyAddedToHistory)
|
||||
onTransferStarted: function (aRequest)
|
||||
{
|
||||
// Store the entity ID to use for resuming if required.
|
||||
if (this.download.tryToKeepPartialData &&
|
||||
@ -1693,15 +1645,6 @@ DownloadLegacySaver.prototype = {
|
||||
} catch (ex if ex instanceof Components.Exception &&
|
||||
ex.result == Cr.NS_ERROR_NOT_RESUMABLE) { }
|
||||
}
|
||||
|
||||
// For legacy downloads, we must update the referrer at this time.
|
||||
if (aRequest instanceof Ci.nsIHttpChannel && aRequest.referrer) {
|
||||
this.download.source.referrer = aRequest.referrer.spec;
|
||||
}
|
||||
|
||||
if (!aAlreadyAddedToHistory) {
|
||||
this.addToHistory();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1759,7 +1702,6 @@ DownloadLegacySaver.prototype = {
|
||||
this.copySaver = new DownloadCopySaver();
|
||||
this.copySaver.download = this.download;
|
||||
this.copySaver.entityID = this.entityID;
|
||||
this.copySaver.alreadyAddedToHistory = true;
|
||||
}
|
||||
return this.copySaver.execute.apply(this.copySaver, arguments);
|
||||
}
|
||||
|
@ -66,14 +66,6 @@ XPCOMUtils.defineLazyGetter(this, "gParentalControlsService", function() {
|
||||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* ArrayBufferView representing the bytes to be written to the "Zone.Identifier"
|
||||
* Alternate Data Stream to mark a file as coming from the Internet zone.
|
||||
*/
|
||||
XPCOMUtils.defineLazyGetter(this, "gInternetZoneIdentifier", function() {
|
||||
return new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=3\r\n");
|
||||
});
|
||||
|
||||
const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
|
||||
"initWithCallback");
|
||||
|
||||
@ -397,46 +389,15 @@ this.DownloadIntegration = {
|
||||
* @rejects JavaScript exception if any of the operations failed.
|
||||
*/
|
||||
downloadDone: function(aDownload) {
|
||||
return Task.spawn(function () {
|
||||
#ifdef XP_WIN
|
||||
// On Windows, we mark any executable file saved to the NTFS file system
|
||||
// as coming from the Internet security zone. We do this by writing to
|
||||
// the "Zone.Identifier" Alternate Data Stream directly, because the Save
|
||||
// method of the IAttachmentExecute interface would trigger operations
|
||||
// that may cause the application to hang, or other performance issues.
|
||||
// The stream created in this way is forward-compatible with all the
|
||||
// current and future versions of Windows.
|
||||
if (Services.prefs.getBoolPref("browser.download.saveZoneInformation")) {
|
||||
let file = new FileUtils.File(aDownload.target.path);
|
||||
if (file.isExecutable()) {
|
||||
try {
|
||||
let streamPath = aDownload.target.path + ":Zone.Identifier";
|
||||
let stream = yield OS.File.open(streamPath, { create: true });
|
||||
try {
|
||||
yield stream.write(gInternetZoneIdentifier);
|
||||
} finally {
|
||||
yield stream.close();
|
||||
}
|
||||
} catch (ex) {
|
||||
// If writing to the stream fails, we ignore the error and continue.
|
||||
// The Windows API error 123 (ERROR_INVALID_NAME) is expected to
|
||||
// occur when working on a file system that does not support
|
||||
// Alternate Data Streams, like FAT32, thus we don't report this
|
||||
// specific error.
|
||||
if (!(ex instanceof OS.File.Error) || ex.winLastError != 123) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
try {
|
||||
gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
|
||||
new FileUtils.File(aDownload.target.path),
|
||||
aDownload.contentType,
|
||||
aDownload.source.isPrivate);
|
||||
aDownload.contentType, aDownload.source.isPrivate);
|
||||
this.downloadDoneCalled = true;
|
||||
}.bind(this));
|
||||
return Promise.resolve();
|
||||
} catch(ex) {
|
||||
return Promise.reject(ex);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -471,14 +432,12 @@ this.DownloadIntegration = {
|
||||
let deferred = Task.spawn(function DI_launchDownload_task() {
|
||||
let file = new FileUtils.File(aDownload.target.path);
|
||||
|
||||
#ifndef XP_WIN
|
||||
// Ask for confirmation if the file is executable, except on Windows where
|
||||
// the operating system will show the prompt based on the security zone.
|
||||
// We do this here, instead of letting the caller handle the prompt
|
||||
// separately in the user interface layer, for two reasons. The first is
|
||||
// because of its security nature, so that add-ons cannot forget to do
|
||||
// this check. The second is that the system-level security prompt would
|
||||
// be displayed at launch time in any case.
|
||||
// Ask for confirmation if the file is executable. We do this here,
|
||||
// instead of letting the caller handle the prompt separately in the user
|
||||
// interface layer, for two reasons. The first is because of its security
|
||||
// nature, so that add-ons cannot forget to do this check. The second is
|
||||
// that the system-level security prompt, if enabled, would be displayed
|
||||
// at launch time in any case.
|
||||
if (file.isExecutable() && !this.dontOpenFileAndFolder) {
|
||||
// We don't anchor the prompt to a specific window intentionally, not
|
||||
// only because this is the same behavior as the system-level prompt,
|
||||
@ -490,7 +449,6 @@ this.DownloadIntegration = {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// In case of a double extension, like ".tar.gz", we only
|
||||
// consider the last one, because the MIME service cannot
|
||||
|
@ -91,21 +91,16 @@ DownloadLegacyTransfer.prototype = {
|
||||
(aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
|
||||
// The main request has just started. Wait for the associated Download
|
||||
// object to be available before notifying.
|
||||
this._deferDownload.promise.then(download => {
|
||||
download.saver.onTransferStarted(
|
||||
aRequest,
|
||||
this._cancelable instanceof Ci.nsIHelperAppLauncher);
|
||||
this._deferDownload.promise.then(function (aDownload) {
|
||||
aDownload.saver.onTransferStarted(aRequest);
|
||||
}).then(null, Cu.reportError);
|
||||
} else if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
|
||||
(aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
|
||||
// The last file has been received, or the download failed. Wait for the
|
||||
// associated Download object to be available before notifying.
|
||||
this._deferDownload.promise.then(download => {
|
||||
download.saver.onTransferFinished(aRequest, aStatus);
|
||||
this._deferDownload.promise.then(function DLT_OSC_onDownload(aDownload) {
|
||||
aDownload.saver.onTransferFinished(aRequest, aStatus);
|
||||
}).then(null, Cu.reportError);
|
||||
|
||||
// Release the reference to the component executing the download.
|
||||
this._cancelable = null;
|
||||
}
|
||||
},
|
||||
|
||||
@ -169,8 +164,6 @@ DownloadLegacyTransfer.prototype = {
|
||||
init: function DLT_init(aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime,
|
||||
aTempFile, aCancelable, aIsPrivate)
|
||||
{
|
||||
this._cancelable = aCancelable;
|
||||
|
||||
let launchWhenSucceeded = false, contentType = null, launcherPath = null;
|
||||
|
||||
if (aMIMEInfo instanceof Ci.nsIMIMEInfo) {
|
||||
@ -240,12 +233,6 @@ DownloadLegacyTransfer.prototype = {
|
||||
*/
|
||||
_deferDownload: null,
|
||||
|
||||
/**
|
||||
* Reference to the component that is executing the download. This component
|
||||
* allows cancellation through its nsICancelable interface.
|
||||
*/
|
||||
_cancelable: null,
|
||||
|
||||
/**
|
||||
* Indicates that the component that executes the download has notified a
|
||||
* failure condition. In this case, we should never use the component methods
|
||||
|
@ -1676,60 +1676,3 @@ add_task(function test_platform_integration()
|
||||
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks that downloads are added to browsing history when they start.
|
||||
*/
|
||||
add_task(function test_history()
|
||||
{
|
||||
mustInterruptResponses();
|
||||
|
||||
// We will wait for the visit to be notified during the download.
|
||||
yield promiseClearHistory();
|
||||
let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt"));
|
||||
|
||||
// Start a download that is not allowed to finish yet.
|
||||
let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
|
||||
|
||||
// The history notifications should be received before the download completes.
|
||||
let [time, transitionType] = yield promiseVisit;
|
||||
do_check_eq(time, download.startTime.getTime() * 1000);
|
||||
do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
|
||||
|
||||
// Restart and complete the download after clearing history.
|
||||
yield promiseClearHistory();
|
||||
download.cancel();
|
||||
continueResponses();
|
||||
yield download.start();
|
||||
|
||||
// The restart should not have added a new history visit.
|
||||
do_check_false(yield promiseIsURIVisited(httpUrl("interruptible.txt")));
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks that downloads started by nsIHelperAppService are added to the
|
||||
* browsing history when they start.
|
||||
*/
|
||||
add_task(function test_history_tryToKeepPartialData()
|
||||
{
|
||||
// We will wait for the visit to be notified during the download.
|
||||
yield promiseClearHistory();
|
||||
let promiseVisit =
|
||||
promiseWaitForVisit(httpUrl("interruptible_resumable.txt"));
|
||||
|
||||
// Start a download that is not allowed to finish yet.
|
||||
let beforeStartTimeMs = Date.now();
|
||||
let download = yield promiseStartDownload_tryToKeepPartialData();
|
||||
|
||||
// The history notifications should be received before the download completes.
|
||||
let [time, transitionType] = yield promiseVisit;
|
||||
do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
|
||||
|
||||
// The time set by nsIHelperAppService may be different than the start time in
|
||||
// the download object, thus we only check that it is a meaningful time.
|
||||
do_check_true(time >= beforeStartTimeMs * 1000);
|
||||
|
||||
// Complete the download before finishing the test.
|
||||
continueResponses();
|
||||
yield promiseDownloadStopped(download);
|
||||
});
|
||||
|
@ -169,101 +169,6 @@ function promiseTimeout(aTime)
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows waiting for an observer notification once.
|
||||
*
|
||||
* @param aTopic
|
||||
* Notification topic to observe.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves The array [aSubject, aData] from the observed notification.
|
||||
* @rejects Never.
|
||||
*/
|
||||
function promiseTopicObserved(aTopic)
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
|
||||
Services.obs.addObserver(
|
||||
function PTO_observe(aSubject, aTopic, aData) {
|
||||
Services.obs.removeObserver(PTO_observe, aTopic);
|
||||
deferred.resolve([aSubject, aData]);
|
||||
}, aTopic, false);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears history asynchronously.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves When history has been cleared.
|
||||
* @rejects Never.
|
||||
*/
|
||||
function promiseClearHistory()
|
||||
{
|
||||
let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
|
||||
do_execute_soon(function() PlacesUtils.bhistory.removeAllPages());
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a new history visit to be notified for the specified URI.
|
||||
*
|
||||
* @param aUrl
|
||||
* String containing the URI that will be visited.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves Array [aTime, aTransitionType] from nsINavHistoryObserver.onVisit.
|
||||
* @rejects Never.
|
||||
*/
|
||||
function promiseWaitForVisit(aUrl)
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let uri = NetUtil.newURI(aUrl);
|
||||
|
||||
PlacesUtils.history.addObserver({
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver]),
|
||||
onBeginUpdateBatch: function () {},
|
||||
onEndUpdateBatch: function () {},
|
||||
onVisit: function (aURI, aVisitID, aTime, aSessionID, aReferringID,
|
||||
aTransitionType, aGUID, aHidden) {
|
||||
if (aURI.equals(uri)) {
|
||||
PlacesUtils.history.removeObserver(this);
|
||||
deferred.resolve([aTime, aTransitionType]);
|
||||
}
|
||||
},
|
||||
onTitleChanged: function () {},
|
||||
onDeleteURI: function () {},
|
||||
onClearHistory: function () {},
|
||||
onPageChanged: function () {},
|
||||
onDeleteVisits: function () {},
|
||||
}, false);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check browsing history to see whether the given URI has been visited.
|
||||
*
|
||||
* @param aUrl
|
||||
* String containing the URI that will be visited.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves Boolean indicating whether the URI has been visited.
|
||||
* @rejects JavaScript exception.
|
||||
*/
|
||||
function promiseIsURIVisited(aUrl) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
PlacesUtils.asyncHistory.isURIVisited(NetUtil.newURI(aUrl),
|
||||
function (aURI, aIsVisited) {
|
||||
deferred.resolve(aIsVisited);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Download object, setting a temporary file as the target.
|
||||
*
|
||||
@ -536,6 +441,40 @@ function promiseVerifyContents(aPath, aExpectedContents)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds entry for download.
|
||||
*
|
||||
* @param aSourceUrl
|
||||
* String containing the URI for the download source, or null to use
|
||||
* httpUrl("source.txt").
|
||||
*
|
||||
* @return {Promise}
|
||||
* @rejects JavaScript exception.
|
||||
*/
|
||||
function promiseAddDownloadToHistory(aSourceUrl) {
|
||||
let deferred = Promise.defer();
|
||||
PlacesUtils.asyncHistory.updatePlaces(
|
||||
{
|
||||
uri: NetUtil.newURI(aSourceUrl || httpUrl("source.txt")),
|
||||
visits: [{
|
||||
transitionType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
|
||||
visitDate: Date.now()
|
||||
}]
|
||||
},
|
||||
{
|
||||
handleError: function handleError(aResultCode, aPlaceInfo) {
|
||||
let ex = new Components.Exception("Unexpected error in adding visits.",
|
||||
aResultCode);
|
||||
deferred.reject(ex);
|
||||
},
|
||||
handleResult: function () {},
|
||||
handleCompletion: function handleCompletion() {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a socket listener that closes each incoming connection.
|
||||
*
|
||||
|
@ -9,62 +9,6 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
/**
|
||||
* Returns a PRTime in the past usable to add expirable visits.
|
||||
*
|
||||
* @note Expiration ignores any visit added in the last 7 days, but it's
|
||||
* better be safe against DST issues, by going back one day more.
|
||||
*/
|
||||
function getExpirablePRTime()
|
||||
{
|
||||
let dateObj = new Date();
|
||||
// Normalize to midnight
|
||||
dateObj.setHours(0);
|
||||
dateObj.setMinutes(0);
|
||||
dateObj.setSeconds(0);
|
||||
dateObj.setMilliseconds(0);
|
||||
dateObj = new Date(dateObj.getTime() - 8 * 86400000);
|
||||
return dateObj.getTime() * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an expirable history visit for a download.
|
||||
*
|
||||
* @param aSourceUrl
|
||||
* String containing the URI for the download source, or null to use
|
||||
* httpUrl("source.txt").
|
||||
*
|
||||
* @return {Promise}
|
||||
* @rejects JavaScript exception.
|
||||
*/
|
||||
function promiseExpirableDownloadVisit(aSourceUrl)
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
PlacesUtils.asyncHistory.updatePlaces(
|
||||
{
|
||||
uri: NetUtil.newURI(aSourceUrl || httpUrl("source.txt")),
|
||||
visits: [{
|
||||
transitionType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
|
||||
visitDate: getExpirablePRTime(),
|
||||
}]
|
||||
},
|
||||
{
|
||||
handleError: function handleError(aResultCode, aPlaceInfo) {
|
||||
let ex = new Components.Exception("Unexpected error in adding visits.",
|
||||
aResultCode);
|
||||
deferred.reject(ex);
|
||||
},
|
||||
handleResult: function () {},
|
||||
handleCompletion: function handleCompletion() {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Tests
|
||||
|
||||
@ -261,8 +205,6 @@ add_task(function test_notifications_this()
|
||||
*/
|
||||
add_task(function test_history_expiration()
|
||||
{
|
||||
mustInterruptResponses();
|
||||
|
||||
function cleanup() {
|
||||
Services.prefs.clearUserPref("places.history.expiration.max_pages");
|
||||
}
|
||||
@ -271,9 +213,15 @@ add_task(function test_history_expiration()
|
||||
// Set max pages to 0 to make the download expire.
|
||||
Services.prefs.setIntPref("places.history.expiration.max_pages", 0);
|
||||
|
||||
// Add expirable visit for downloads.
|
||||
yield promiseAddDownloadToHistory();
|
||||
yield promiseAddDownloadToHistory(httpUrl("interruptible.txt"));
|
||||
|
||||
let list = yield promiseNewDownloadList();
|
||||
let downloadOne = yield promiseNewDownload();
|
||||
let downloadTwo = yield promiseNewDownload(httpUrl("interruptible.txt"));
|
||||
list.add(downloadOne);
|
||||
list.add(downloadTwo);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let removeNotifications = 0;
|
||||
@ -286,27 +234,18 @@ add_task(function test_history_expiration()
|
||||
};
|
||||
list.addView(downloadView);
|
||||
|
||||
// Work with one finished download and one canceled download.
|
||||
// Start download one.
|
||||
yield downloadOne.start();
|
||||
|
||||
// Start download two and then cancel it.
|
||||
downloadTwo.start();
|
||||
yield downloadTwo.cancel();
|
||||
|
||||
// We must replace the visits added while executing the downloads with visits
|
||||
// that are older than 7 days, otherwise they will not be expired.
|
||||
yield promiseClearHistory();
|
||||
yield promiseExpirableDownloadVisit();
|
||||
yield promiseExpirableDownloadVisit(httpUrl("interruptible.txt"));
|
||||
|
||||
// After clearing history, we can add the downloads to be removed to the list.
|
||||
list.add(downloadOne);
|
||||
list.add(downloadTwo);
|
||||
|
||||
// Force a history expiration.
|
||||
let expire = Cc["@mozilla.org/places/expiration;1"]
|
||||
.getService(Ci.nsIObserver);
|
||||
expire.observe(null, "places-debug-start-expiration", -1);
|
||||
|
||||
// Wait for both downloads to be removed.
|
||||
yield deferred.promise;
|
||||
|
||||
cleanup();
|
||||
@ -317,6 +256,10 @@ add_task(function test_history_expiration()
|
||||
*/
|
||||
add_task(function test_history_clear()
|
||||
{
|
||||
// Add expirable visit for downloads.
|
||||
yield promiseAddDownloadToHistory();
|
||||
yield promiseAddDownloadToHistory();
|
||||
|
||||
let list = yield promiseNewDownloadList();
|
||||
let downloadOne = yield promiseNewDownload();
|
||||
let downloadTwo = yield promiseNewDownload();
|
||||
@ -337,9 +280,8 @@ add_task(function test_history_clear()
|
||||
yield downloadOne.start();
|
||||
yield downloadTwo.start();
|
||||
|
||||
yield promiseClearHistory();
|
||||
PlacesUtils.history.removeAllPages();
|
||||
|
||||
// Wait for the removal notifications that may still be pending.
|
||||
yield deferred.promise;
|
||||
});
|
||||
|
||||
|
@ -127,12 +127,16 @@ WorkerHandle.prototype = {
|
||||
return;
|
||||
}
|
||||
delete workerCache[url];
|
||||
// close all the ports we have handed out.
|
||||
for (let [portid, port] of this._worker.ports) {
|
||||
port.close();
|
||||
}
|
||||
this._worker.ports.clear();
|
||||
this._worker.ports = null;
|
||||
this._worker.browserPromise.then(browser => {
|
||||
browser.parentNode.removeChild(browser);
|
||||
});
|
||||
// wipe things out just incase other reference have snuck out somehow...
|
||||
this._worker.ports.clear();
|
||||
this._worker.ports = null;
|
||||
this._worker.browserPromise = null;
|
||||
this._worker = null;
|
||||
}
|
||||
|
@ -2,6 +2,13 @@
|
||||
* 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/. */
|
||||
|
||||
/**
|
||||
* WARNING: BackgroundPageThumbs.jsm is currently excluded from release builds.
|
||||
* If you use it, you must also exclude your caller when RELEASE_BUILD is
|
||||
* defined, as described here:
|
||||
* https://wiki.mozilla.org/Platform/Channel-specific_build_defines
|
||||
*/
|
||||
|
||||
const EXPORTED_SYMBOLS = [
|
||||
"BackgroundPageThumbs",
|
||||
];
|
||||
@ -10,6 +17,11 @@ const DEFAULT_CAPTURE_TIMEOUT = 30000; // ms
|
||||
const DESTROY_BROWSER_TIMEOUT = 60000; // ms
|
||||
const FRAME_SCRIPT_URL = "chrome://global/content/backgroundPageThumbsContent.js";
|
||||
|
||||
// If a request for a thumbnail comes in and we find one that is "stale"
|
||||
// (or don't find one at all) we automatically queue a request to generate a
|
||||
// new one.
|
||||
const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.
|
||||
|
||||
const TELEMETRY_HISTOGRAM_ID_PREFIX = "FX_THUMBNAILS_BG_";
|
||||
|
||||
// possible FX_THUMBNAILS_BG_CAPTURE_DONE_REASON telemetry values
|
||||
@ -32,6 +44,11 @@ const BackgroundPageThumbs = {
|
||||
*
|
||||
* The page is loaded anonymously, and plug-ins are disabled.
|
||||
*
|
||||
* WARNING: BackgroundPageThumbs.jsm is currently excluded from release
|
||||
* builds. If you use it, you must also exclude your caller when
|
||||
* RELEASE_BUILD is defined, as described here:
|
||||
* https://wiki.mozilla.org/Platform/Channel-specific_build_defines
|
||||
*
|
||||
* @param url The URL to capture.
|
||||
* @param options An optional object that configures the capture. Its
|
||||
* properties are the following, and all are optional:
|
||||
@ -69,6 +86,37 @@ const BackgroundPageThumbs = {
|
||||
this._processCaptureQueue();
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if an existing thumbnail for the specified URL is either missing
|
||||
* or stale, and if so, queues a background request to capture it. That
|
||||
* capture process will send a notification via the observer service on
|
||||
* capture, so consumers should watch for such observations if they want to
|
||||
* be notified of an updated thumbnail.
|
||||
*
|
||||
* WARNING: BackgroundPageThumbs.jsm is currently excluded from release
|
||||
* builds. If you use it, you must also exclude your caller when
|
||||
* RELEASE_BUILD is defined, as described here:
|
||||
* https://wiki.mozilla.org/Platform/Channel-specific_build_defines
|
||||
*
|
||||
* @param url The URL to capture.
|
||||
* @param options An optional object that configures the capture. See
|
||||
* capture() for description.
|
||||
*/
|
||||
captureIfStale: function PageThumbs_captureIfStale(url, options={}) {
|
||||
PageThumbsStorage.isFileRecentForURL(url, MAX_THUMBNAIL_AGE_SECS).then(
|
||||
result => {
|
||||
if (result.ok) {
|
||||
if (options.onDone)
|
||||
options.onDone(url);
|
||||
return;
|
||||
}
|
||||
this.capture(url, options);
|
||||
}, err => {
|
||||
if (options.onDone)
|
||||
options.onDone(url);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures that initialization of the thumbnail browser's parent window has
|
||||
* begun.
|
||||
|
@ -17,11 +17,6 @@ const LATEST_STORAGE_VERSION = 3;
|
||||
const EXPIRATION_MIN_CHUNK_SIZE = 50;
|
||||
const EXPIRATION_INTERVAL_SECS = 3600;
|
||||
|
||||
// If a request for a thumbnail comes in and we find one that is "stale"
|
||||
// (or don't find one at all) we automatically queue a request to generate a
|
||||
// new one.
|
||||
const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.
|
||||
|
||||
/**
|
||||
* Name of the directory in the profile that contains the thumbnails.
|
||||
*/
|
||||
@ -197,32 +192,6 @@ this.PageThumbs = {
|
||||
return PageThumbsStorage.getFilePathForURL(aUrl);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if an existing thumbnail for the specified URL is either missing
|
||||
* or stale, and if so, queues a background request to capture it. That
|
||||
* capture process will send a notification via the observer service on
|
||||
* capture, so consumers should watch for such observations if they want to
|
||||
* be notified of an updated thumbnail.
|
||||
*
|
||||
* @return {Promise} that's resolved on completion.
|
||||
*/
|
||||
captureIfStale: function PageThumbs_captureIfStale(aUrl) {
|
||||
let deferredResult = Promise.defer();
|
||||
let filePath = PageThumbsStorage.getFilePathForURL(aUrl);
|
||||
PageThumbsWorker.post(
|
||||
"isFileRecent",
|
||||
[filePath, MAX_THUMBNAIL_AGE_SECS]
|
||||
).then(result => {
|
||||
if (!result.ok) {
|
||||
// Sadly there is currently a circular dependency between this module
|
||||
// and BackgroundPageThumbs, so do the import locally.
|
||||
let BPT = Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", {}).BackgroundPageThumbs;
|
||||
BPT.capture(aUrl, {onDone: deferredResult.resolve});
|
||||
}
|
||||
});
|
||||
return deferredResult.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Captures a thumbnail for the given window.
|
||||
* @param aWindow The DOM window to capture a thumbnail from.
|
||||
@ -626,6 +595,11 @@ this.PageThumbsStorage = {
|
||||
return PageThumbsWorker.post("touchIfExists", [this.getFilePathForURL(aURL)]);
|
||||
},
|
||||
|
||||
isFileRecentForURL: function Storage_isFileRecentForURL(aURL, aMaxSecs) {
|
||||
return PageThumbsWorker.post("isFileRecent",
|
||||
[this.getFilePathForURL(aURL), aMaxSecs]);
|
||||
},
|
||||
|
||||
_calculateMD5Hash: function Storage_calculateMD5Hash(aValue) {
|
||||
let hash = gCryptoHash;
|
||||
let value = gUnicodeConverter.convertToByteArray(aValue);
|
||||
|
@ -3,4 +3,6 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
toolkit.jar:
|
||||
#ifndef RELEASE_BUILD
|
||||
+ content/global/backgroundPageThumbsContent.js (content/backgroundPageThumbsContent.js)
|
||||
#endif
|
||||
|
@ -12,8 +12,9 @@ EXTRA_COMPONENTS += [
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'BackgroundPageThumbs.jsm',
|
||||
'PageThumbs.jsm',
|
||||
'PageThumbsWorker.js',
|
||||
]
|
||||
|
||||
if not CONFIG['RELEASE_BUILD']:
|
||||
EXTRA_JS_MODULES += ['BackgroundPageThumbs.jsm']
|
||||
|
@ -3,7 +3,6 @@
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
MOCHITEST_BROWSER_FILES := \
|
||||
browser_thumbnails_background.js \
|
||||
browser_thumbnails_capture.js \
|
||||
browser_thumbnails_expiration.js \
|
||||
browser_thumbnails_privacy.js \
|
||||
@ -13,12 +12,18 @@ MOCHITEST_BROWSER_FILES := \
|
||||
browser_thumbnails_bug726727.js \
|
||||
browser_thumbnails_bug727765.js \
|
||||
browser_thumbnails_bug818225.js \
|
||||
browser_thumbnails_update.js \
|
||||
head.js \
|
||||
background_red.html \
|
||||
background_red_scroll.html \
|
||||
background_red_redirect.sjs \
|
||||
privacy_cache_control.sjs \
|
||||
$(NULL)
|
||||
|
||||
ifndef RELEASE_BUILD
|
||||
MOCHITEST_BROWSER_FILES += \
|
||||
browser_thumbnails_background.js \
|
||||
browser_thumbnails_update.js \
|
||||
thumbnails_background.sjs \
|
||||
thumbnails_update.sjs \
|
||||
$(NULL)
|
||||
endif
|
||||
|
@ -2,9 +2,16 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* These tests check the auto-update facility of the thumbnail service.
|
||||
* These tests check the auto-update facility of the background thumbnail
|
||||
* service.
|
||||
*/
|
||||
|
||||
const imports = {};
|
||||
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm", imports);
|
||||
registerCleanupFunction(function () {
|
||||
imports.BackgroundPageThumbs._destroy();
|
||||
});
|
||||
|
||||
function runTests() {
|
||||
// A "trampoline" - a generator that iterates over sub-iterators
|
||||
let tests = [
|
||||
@ -68,12 +75,12 @@ function simpleCaptureTest() {
|
||||
is(numNotifications, 1, "got notification of item being created.");
|
||||
// The capture is now "fresh" - so requesting the URL should not cause
|
||||
// a new capture.
|
||||
PageThumbs.captureIfStale(URL);
|
||||
imports.BackgroundPageThumbs.captureIfStale(URL);
|
||||
is(numNotifications, 1, "still only 1 notification of item being created.");
|
||||
|
||||
ensureThumbnailStale(URL);
|
||||
// Ask for it to be updated.
|
||||
PageThumbs.captureIfStale(URL);
|
||||
imports.BackgroundPageThumbs.captureIfStale(URL);
|
||||
// But it's async, so wait - our observer above will call next() when
|
||||
// the notification comes.
|
||||
});
|
||||
@ -99,13 +106,13 @@ function errorResponseUpdateTest() {
|
||||
// As we set the thumbnail very stale, allowing 1 second of "slop" here
|
||||
// works around this while still keeping the test valid.
|
||||
let now = Date.now() - 1000 ;
|
||||
PageThumbs.captureIfStale(URL).then(() => {
|
||||
imports.BackgroundPageThumbs.captureIfStale(URL, { onDone: () => {
|
||||
ok(getThumbnailModifiedTime(URL) >= now, "modified time should be >= now");
|
||||
retrieveImageDataForURL(URL, function ([r, g, b]) {
|
||||
is("" + [r,g,b], "" + [0, 255, 0], "thumbnail is still green");
|
||||
next();
|
||||
});
|
||||
}).then(null, err => {ok(false, "Error in captureIfStale: " + err)});
|
||||
}});
|
||||
yield undefined; // wait for callback to call 'next'...
|
||||
}
|
||||
|
||||
@ -127,7 +134,7 @@ function goodResponseUpdateTest() {
|
||||
// As we set the thumbnail very stale, allowing 1 second of "slop" here
|
||||
// works around this while still keeping the test valid.
|
||||
let now = Date.now() - 1000 ;
|
||||
PageThumbs.captureIfStale(URL).then(() => {
|
||||
imports.BackgroundPageThumbs.captureIfStale(URL, { onDone: () => {
|
||||
ok(getThumbnailModifiedTime(URL) >= now, "modified time should be >= now");
|
||||
// the captureIfStale request saw a 200 response with the red body, so we
|
||||
// expect to see the red version here.
|
||||
@ -135,7 +142,7 @@ function goodResponseUpdateTest() {
|
||||
is("" + [r,g,b], "" + [255, 0, 0], "thumbnail is now red");
|
||||
next();
|
||||
});
|
||||
}).then(null, err => {ok(false, "Error in captureIfStale: " + err)});
|
||||
}});
|
||||
yield undefined; // wait for callback to call 'next'...
|
||||
}
|
||||
|
||||
|
@ -715,6 +715,210 @@ ThreadActor.prototype = {
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle resume requests that include a forceCompletion request.
|
||||
*
|
||||
* @param Object aRequest
|
||||
* The request packet received over the RDP.
|
||||
* @returns A response packet.
|
||||
*/
|
||||
_forceCompletion: function TA__forceCompletion(aRequest) {
|
||||
// TODO: remove this when Debugger.Frame.prototype.pop is implemented in
|
||||
// bug 736733.
|
||||
return {
|
||||
error: "notImplemented",
|
||||
message: "forced completion is not yet implemented."
|
||||
};
|
||||
},
|
||||
|
||||
_makeOnEnterFrame: function TA__makeOnEnterFrame({ pauseAndRespond }) {
|
||||
return aFrame => {
|
||||
const generatedLocation = getFrameLocation(aFrame);
|
||||
let { url } = this.synchronize(this.sources.getOriginalLocation(
|
||||
generatedLocation));
|
||||
|
||||
return this.sources.isBlackBoxed(url)
|
||||
? undefined
|
||||
: pauseAndRespond(aFrame);
|
||||
};
|
||||
},
|
||||
|
||||
_makeOnPop: function TA__makeOnPop({ thread, pauseAndRespond, createValueGrip }) {
|
||||
return function (aCompletion) {
|
||||
// onPop is called with 'this' set to the current frame.
|
||||
|
||||
const generatedLocation = getFrameLocation(this);
|
||||
const { url } = thread.synchronize(thread.sources.getOriginalLocation(
|
||||
generatedLocation));
|
||||
|
||||
if (thread.sources.isBlackBoxed(url)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Note that we're popping this frame; we need to watch for
|
||||
// subsequent step events on its caller.
|
||||
this.reportedPop = true;
|
||||
|
||||
return pauseAndRespond(this, aPacket => {
|
||||
aPacket.why.frameFinished = {};
|
||||
if (!aCompletion) {
|
||||
aPacket.why.frameFinished.terminated = true;
|
||||
} else if (aCompletion.hasOwnProperty("return")) {
|
||||
aPacket.why.frameFinished.return = createValueGrip(aCompletion.return);
|
||||
} else if (aCompletion.hasOwnProperty("yield")) {
|
||||
aPacket.why.frameFinished.return = createValueGrip(aCompletion.yield);
|
||||
} else {
|
||||
aPacket.why.frameFinished.throw = createValueGrip(aCompletion.throw);
|
||||
}
|
||||
return aPacket;
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
_makeOnStep: function TA__makeOnStep({ thread, pauseAndRespond, startFrame,
|
||||
startLocation }) {
|
||||
return function () {
|
||||
// onStep is called with 'this' set to the current frame.
|
||||
|
||||
const generatedLocation = getFrameLocation(this);
|
||||
const newLocation = thread.synchronize(thread.sources.getOriginalLocation(
|
||||
generatedLocation));
|
||||
|
||||
// Cases when we should pause because we have executed enough to consider
|
||||
// a "step" to have occured:
|
||||
//
|
||||
// 1.1. We change frames.
|
||||
// 1.2. We change URLs (can happen without changing frames thanks to
|
||||
// source mapping).
|
||||
// 1.3. We change lines.
|
||||
//
|
||||
// Cases when we should always continue execution, even if one of the
|
||||
// above cases is true:
|
||||
//
|
||||
// 2.1. We are in a source mapped region, but inside a null mapping
|
||||
// (doesn't correlate to any region of original source)
|
||||
// 2.2. The source we are in is black boxed.
|
||||
|
||||
// Cases 2.1 and 2.2
|
||||
if (newLocation.url == null
|
||||
|| thread.sources.isBlackBoxed(newLocation.url)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Cases 1.1, 1.2 and 1.3
|
||||
if (this !== startFrame
|
||||
|| startLocation.url !== newLocation.url
|
||||
|| startLocation.line !== newLocation.line) {
|
||||
return pauseAndRespond(this);
|
||||
}
|
||||
|
||||
// Otherwise, let execution continue (we haven't executed enough code to
|
||||
// consider this a "step" yet).
|
||||
return undefined;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the JS hook functions for stepping.
|
||||
*/
|
||||
_makeSteppingHooks: function TA__makeSteppingHooks(aStartLocation) {
|
||||
// Bind these methods and state because some of the hooks are called
|
||||
// with 'this' set to the current frame. Rather than repeating the
|
||||
// binding in each _makeOnX method, just do it once here and pass it
|
||||
// in to each function.
|
||||
const steppingHookState = {
|
||||
pauseAndRespond: (aFrame, onPacket=(k)=>k) => {
|
||||
this._pauseAndRespond(aFrame, { type: "resumeLimit" }, onPacket);
|
||||
},
|
||||
createValueGrip: this.createValueGrip.bind(this),
|
||||
thread: this,
|
||||
startFrame: this.youngestFrame,
|
||||
startLocation: aStartLocation
|
||||
};
|
||||
|
||||
return {
|
||||
onEnterFrame: this._makeOnEnterFrame(steppingHookState),
|
||||
onPop: this._makeOnPop(steppingHookState),
|
||||
onStep: this._makeOnStep(steppingHookState)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle attaching the various stepping hooks we need to attach when we
|
||||
* receive a resume request with a resumeLimit property.
|
||||
*
|
||||
* @param Object aRequest
|
||||
* The request packet received over the RDP.
|
||||
* @returns A promise that resolves to true once the hooks are attached, or is
|
||||
* rejected with an error packet.
|
||||
*/
|
||||
_handleResumeLimit: function TA__handleResumeLimit(aRequest) {
|
||||
let steppingType = aRequest.resumeLimit.type;
|
||||
if (["step", "next", "finish"].indexOf(steppingType) == -1) {
|
||||
return reject({ error: "badParameterType",
|
||||
message: "Unknown resumeLimit type" });
|
||||
}
|
||||
|
||||
const generatedLocation = getFrameLocation(this.youngestFrame);
|
||||
return this.sources.getOriginalLocation(generatedLocation)
|
||||
.then(originalLocation => {
|
||||
const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation);
|
||||
|
||||
// Make sure there is still a frame on the stack if we are to continue
|
||||
// stepping.
|
||||
let stepFrame = this._getNextStepFrame(this.youngestFrame);
|
||||
if (stepFrame) {
|
||||
switch (steppingType) {
|
||||
case "step":
|
||||
this.dbg.onEnterFrame = onEnterFrame;
|
||||
// Fall through.
|
||||
case "next":
|
||||
stepFrame.onStep = onStep;
|
||||
stepFrame.onPop = onPop;
|
||||
break;
|
||||
case "finish":
|
||||
stepFrame.onPop = onPop;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the onStep and onPop hooks from the given frame and all of the frames
|
||||
* below it.
|
||||
*
|
||||
* @param Debugger.Frame aFrame
|
||||
* The frame we want to clear the stepping hooks from.
|
||||
*/
|
||||
_clearSteppingHooks: function TA__clearSteppingHooks(aFrame) {
|
||||
while (aFrame) {
|
||||
aFrame.onStep = undefined;
|
||||
aFrame.onPop = undefined;
|
||||
aFrame = aFrame.older;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listen to the debuggee's DOM events if we received a request to do so.
|
||||
*
|
||||
* @param Object aRequest
|
||||
* The resume request packet received over the RDP.
|
||||
*/
|
||||
_maybeListenToEvents: function TA__maybeListenToEvents(aRequest) {
|
||||
// Break-on-DOMEvents is only supported in content debugging.
|
||||
let events = aRequest.pauseOnDOMEvents;
|
||||
if (this.global && events &&
|
||||
(events == "*" ||
|
||||
(Array.isArray(events) && events.length))) {
|
||||
this._pauseOnDOMEvents = events;
|
||||
let els = Cc["@mozilla.org/eventlistenerservice;1"]
|
||||
.getService(Ci.nsIEventListenerService);
|
||||
els.addListenerForAllEvents(this.global, this._allEventsListener, true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a protocol request to resume execution of the debuggee.
|
||||
*/
|
||||
@ -740,147 +944,14 @@ ThreadActor.prototype = {
|
||||
}
|
||||
|
||||
if (aRequest && aRequest.forceCompletion) {
|
||||
// TODO: remove this when Debugger.Frame.prototype.pop is implemented in
|
||||
// bug 736733.
|
||||
if (typeof this.frame.pop != "function") {
|
||||
return { error: "notImplemented",
|
||||
message: "forced completion is not yet implemented." };
|
||||
}
|
||||
|
||||
this.dbg.getNewestFrame().pop(aRequest.completionValue);
|
||||
let packet = this._resumed();
|
||||
this._popThreadPause();
|
||||
return { type: "resumeLimit", frameFinished: aRequest.forceCompletion };
|
||||
return this._forceCompletion(aRequest);
|
||||
}
|
||||
|
||||
let resumeLimitHandled;
|
||||
if (aRequest && aRequest.resumeLimit) {
|
||||
// Bind these methods because some of the hooks are called with 'this'
|
||||
// set to the current frame.
|
||||
let pauseAndRespond = (aFrame, onPacket=function (k) k) => {
|
||||
this._pauseAndRespond(aFrame, { type: "resumeLimit" }, onPacket);
|
||||
};
|
||||
let createValueGrip = this.createValueGrip.bind(this);
|
||||
|
||||
let startFrame = this.youngestFrame;
|
||||
const generatedLocation = getFrameLocation(this.youngestFrame);
|
||||
resumeLimitHandled = this.sources.getOriginalLocation(generatedLocation)
|
||||
.then((startLocation) => {
|
||||
// Define the JS hook functions for stepping.
|
||||
|
||||
let onEnterFrame = aFrame => {
|
||||
const generatedLocation = getFrameLocation(aFrame);
|
||||
let { url } = this.synchronize(this.sources.getOriginalLocation(
|
||||
generatedLocation));
|
||||
|
||||
return this.sources.isBlackBoxed(url)
|
||||
? undefined
|
||||
: pauseAndRespond(aFrame);
|
||||
};
|
||||
|
||||
let thread = this;
|
||||
|
||||
let onPop = function TA_onPop(aCompletion) {
|
||||
// onPop is called with 'this' set to the current frame.
|
||||
|
||||
const generatedLocation = getFrameLocation(this);
|
||||
let { url } = thread.synchronize(thread.sources.getOriginalLocation(
|
||||
generatedLocation));
|
||||
|
||||
if (thread.sources.isBlackBoxed(url)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Note that we're popping this frame; we need to watch for
|
||||
// subsequent step events on its caller.
|
||||
this.reportedPop = true;
|
||||
|
||||
return pauseAndRespond(this, aPacket => {
|
||||
aPacket.why.frameFinished = {};
|
||||
if (!aCompletion) {
|
||||
aPacket.why.frameFinished.terminated = true;
|
||||
} else if (aCompletion.hasOwnProperty("return")) {
|
||||
aPacket.why.frameFinished.return = createValueGrip(aCompletion.return);
|
||||
} else if (aCompletion.hasOwnProperty("yield")) {
|
||||
aPacket.why.frameFinished.return = createValueGrip(aCompletion.yield);
|
||||
} else {
|
||||
aPacket.why.frameFinished.throw = createValueGrip(aCompletion.throw);
|
||||
}
|
||||
return aPacket;
|
||||
});
|
||||
};
|
||||
|
||||
let onStep = function TA_onStep() {
|
||||
// onStep is called with 'this' set to the current frame.
|
||||
|
||||
const generatedLocation = getFrameLocation(this);
|
||||
const newLocation = thread.synchronize(
|
||||
thread.sources.getOriginalLocation(generatedLocation));
|
||||
|
||||
// Cases when we should pause because we have executed enough to
|
||||
// consider a "step" to have occured:
|
||||
//
|
||||
// 1.1. We change frames.
|
||||
// 1.2. We change URLs (can happen without changing frames thanks to
|
||||
// source mapping).
|
||||
// 1.3. We change lines.
|
||||
//
|
||||
// Cases when we should always continue execution, even if one of the
|
||||
// above cases is true:
|
||||
//
|
||||
// 2.1. We are in a source mapped region, but inside a null mapping
|
||||
// (doesn't correlate to any region of original source)
|
||||
// 2.2. The source we are in is black boxed.
|
||||
|
||||
// Cases 2.1 and 2.2
|
||||
if (newLocation.url == null
|
||||
|| thread.sources.isBlackBoxed(newLocation.url)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Cases 1.1, 1.2 and 1.3
|
||||
if (this !== startFrame
|
||||
|| startLocation.url !== newLocation.url
|
||||
|| startLocation.line !== newLocation.line) {
|
||||
return pauseAndRespond(this);
|
||||
}
|
||||
|
||||
// Otherwise, let execution continue (we haven't executed enough code to
|
||||
// consider this a "step" yet).
|
||||
return undefined;
|
||||
};
|
||||
|
||||
let steppingType = aRequest.resumeLimit.type;
|
||||
if (["step", "next", "finish"].indexOf(steppingType) == -1) {
|
||||
throw { error: "badParameterType",
|
||||
message: "Unknown resumeLimit type" };
|
||||
}
|
||||
// Make sure there is still a frame on the stack if we are to continue
|
||||
// stepping.
|
||||
let stepFrame = this._getNextStepFrame(startFrame);
|
||||
if (stepFrame) {
|
||||
switch (steppingType) {
|
||||
case "step":
|
||||
this.dbg.onEnterFrame = onEnterFrame;
|
||||
// Fall through.
|
||||
case "next":
|
||||
stepFrame.onStep = onStep;
|
||||
stepFrame.onPop = onPop;
|
||||
break;
|
||||
case "finish":
|
||||
stepFrame.onPop = onPop;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
resumeLimitHandled = this._handleResumeLimit(aRequest)
|
||||
} else {
|
||||
// Clear any previous stepping hooks on a plain resumption.
|
||||
let frame = this.youngestFrame;
|
||||
while (frame) {
|
||||
frame.onStep = undefined;
|
||||
frame.onPop = undefined;
|
||||
frame = frame.older;
|
||||
}
|
||||
this._clearSteppingHooks(this.youngestFrame);
|
||||
resumeLimitHandled = resolve(true);
|
||||
}
|
||||
|
||||
@ -889,16 +960,7 @@ ThreadActor.prototype = {
|
||||
this._options.pauseOnExceptions = aRequest.pauseOnExceptions;
|
||||
this._options.ignoreCaughtExceptions = aRequest.ignoreCaughtExceptions;
|
||||
this.maybePauseOnExceptions();
|
||||
// Break-on-DOMEvents is only supported in content debugging.
|
||||
let events = aRequest.pauseOnDOMEvents;
|
||||
if (this.global && events &&
|
||||
(events == "*" ||
|
||||
(Array.isArray(events) && events.length))) {
|
||||
this._pauseOnDOMEvents = events;
|
||||
let els = Cc["@mozilla.org/eventlistenerservice;1"]
|
||||
.getService(Ci.nsIEventListenerService);
|
||||
els.addListenerForAllEvents(this.global, this._allEventsListener, true);
|
||||
}
|
||||
this._maybeListenToEvents(aRequest);
|
||||
}
|
||||
|
||||
let packet = this._resumed();
|
||||
|
Loading…
Reference in New Issue
Block a user