merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2013-09-10 10:27:04 +02:00
commit 70740809ce
23 changed files with 452 additions and 608 deletions

View File

@ -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");

View File

@ -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();
},

View File

@ -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":

View File

@ -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");

View File

@ -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);
},

View File

@ -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);
}
});

View File

@ -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);

View File

@ -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();
}
}
},

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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);
});

View File

@ -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.
*

View File

@ -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;
});

View File

@ -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;
}

View File

@ -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.

View File

@ -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);

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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'...
}

View File

@ -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();