This commit is contained in:
Ryan VanderMeulen 2015-01-14 16:31:26 -05:00
Родитель 85f72d42db a52fe731d9
Коммит c815fdc069
54 изменённых файлов: 501 добавлений и 350 удалений

Просмотреть файл

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Merge day clobber
Bug 1101553 - remove nsPIPlacesHistoryListenersNotifier

Просмотреть файл

@ -6450,8 +6450,18 @@ function WindowIsClosing()
for (let browser of gBrowser.browsers) {
let ds = browser.docShell;
if (ds.contentViewer && !ds.contentViewer.permitUnload())
// Passing true to permitUnload indicates we plan on closing the window.
// This means that once unload is permitted, all further calls to
// permitUnload will be ignored. This avoids getting multiple prompts
// to unload the page.
if (ds.contentViewer && !ds.contentViewer.permitUnload(true)) {
// ... however, if the user aborts closing, we need to undo that,
// to ensure they get prompted again when we next try to close the window.
// We do this on the window's toplevel docshell instead of on the tab, so
// that all tabs we iterated before will get this reset.
window.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow();
return false;
}
}
return true;

Просмотреть файл

@ -2043,25 +2043,6 @@
return false;
var browser = this.getBrowserForTab(aTab);
if (!aTab._pendingPermitUnload && !aTabWillBeMoved) {
let ds = browser.docShell;
if (ds && ds.contentViewer) {
// We need to block while calling permitUnload() because it
// processes the event queue and may lead to another removeTab()
// call before permitUnload() even returned.
aTab._pendingPermitUnload = true;
let permitUnload = ds.contentViewer.permitUnload();
delete aTab._pendingPermitUnload;
// If we were closed during onbeforeunload, we return false now
// so we don't (try to) close the same tab again. Of course, we
// also stop if the unload was cancelled by the user:
if (aTab.closing || !permitUnload) {
// NB: deliberately keep the _closedDuringPermitUnload set to
// true so we keep exiting early in case of multiple calls.
return false;
}
}
}
var closeWindow = false;
var newTab = false;
@ -2085,6 +2066,26 @@
newTab = true;
}
if (!aTab._pendingPermitUnload && !aTabWillBeMoved) {
let ds = browser.docShell;
if (ds && ds.contentViewer) {
// We need to block while calling permitUnload() because it
// processes the event queue and may lead to another removeTab()
// call before permitUnload() returns.
aTab._pendingPermitUnload = true;
let permitUnload = ds.contentViewer.permitUnload();
delete aTab._pendingPermitUnload;
// If we were closed during onbeforeunload, we return false now
// so we don't (try to) close the same tab again. Of course, we
// also stop if the unload was cancelled by the user:
if (aTab.closing || !permitUnload) {
// NB: deliberately keep the _closedDuringPermitUnload set to
// true so we keep exiting early in case of multiple calls.
return false;
}
}
}
aTab.closing = true;
this._removingTabs.push(aTab);
this._visibleTabs = null; // invalidate cache

Просмотреть файл

@ -126,6 +126,8 @@ skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir
[browser_autocomplete_tag_star_visibility.js]
[browser_backButtonFitts.js]
skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug 1099154: test touches content (attempts to add an event listener directly to the contentWindow)
[browser_beforeunload_duplicate_dialogs.js]
skip-if = e10s # bug 967873 means permitUnload doesn't work in e10s mode
[browser_blob-channelname.js]
[browser_bookmark_titles.js]
skip-if = buildapp == 'mulet' || toolkit == "windows" || e10s # Disabled on Windows due to frequent failures (bugs 825739, 841341) / e10s - Bug 1094205 - places doesn't return the right thing in e10s mode, for some reason

Просмотреть файл

@ -0,0 +1,44 @@
const TEST_PAGE = "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html";
let expectingDialog = false;
function onTabModalDialogLoaded(node) {
ok(expectingDialog, "Should be expecting this dialog.");
expectingDialog = false;
// This accepts the dialog, closing it
node.Dialog.ui.button0.click();
}
// Listen for the dialog being created
Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("browser.tabs.warnOnClose");
Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded");
});
add_task(function* closeLastTabInWindow() {
let newWin = yield promiseOpenAndLoadWindow({}, true);
let firstTab = newWin.gBrowser.selectedTab;
yield promiseTabLoadEvent(firstTab, TEST_PAGE);
let windowClosedPromise = promiseWindowWillBeClosed(newWin);
expectingDialog = true;
// close tab:
document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
yield windowClosedPromise;
ok(!expectingDialog, "There should have been a dialog.");
ok(newWin.closed, "Window should be closed.");
});
add_task(function* closeWindowWithMultipleTabsIncludingOneBeforeUnload() {
Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
let newWin = yield promiseOpenAndLoadWindow({}, true);
let firstTab = newWin.gBrowser.selectedTab;
yield promiseTabLoadEvent(firstTab, TEST_PAGE);
yield promiseTabLoadEvent(newWin.gBrowser.addTab(), "http://example.com/");
let windowClosedPromise = promiseWindowWillBeClosed(newWin);
expectingDialog = true;
newWin.BrowserTryToCloseWindow();
yield windowClosedPromise;
ok(!expectingDialog, "There should have been a dialog.");
ok(newWin.closed, "Window should be closed.");
});

Просмотреть файл

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>Test for bug 1050638 - clicking tab close button twice should close tab even in beforeunload case</title>
<title>Test page that blocks beforeunload. Used in tests for bug 1050638 and bug 305085</title>
</head>
<body>
This page will block beforeunload. It should still be user-closable at all times.

Просмотреть файл

@ -202,14 +202,21 @@ function whenNewWindowLoaded(aOptions, aCallback) {
}, false);
}
function promiseWindowClosed(win) {
let deferred = Promise.defer();
win.addEventListener("unload", function onunload() {
win.removeEventListener("unload", onunload);
deferred.resolve();
function promiseWindowWillBeClosed(win) {
return new Promise((resolve, reject) => {
Services.obs.addObserver(function observe(subject, topic) {
if (subject == win) {
Services.obs.removeObserver(observe, topic);
resolve();
}
}, "domwindowclosed", false);
});
}
function promiseWindowClosed(win) {
let promise = promiseWindowWillBeClosed(win);
win.close();
return deferred.promise;
return promise;
}
function promiseOpenAndLoadWindow(aOptions, aWaitForDelayedStartup=false) {

Просмотреть файл

@ -611,12 +611,8 @@ XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
function DownloadsDataCtor(aPrivate) {
this._isPrivate = aPrivate;
// This Object contains all the available DownloadsDataItem objects, indexed by
// their globally unique identifier. The identifiers of downloads that have
// been removed from the Download Manager data are still present, however the
// associated objects are replaced with the value "null". This is required to
// prevent race conditions when populating the list asynchronously.
this.dataItems = {};
// Contains all the available DownloadsDataItem objects.
this.dataItems = new Set();
// Array of view objects that should be notified when the available download
// data changes.
@ -644,8 +640,8 @@ DownloadsDataCtor.prototype = {
* True if there are finished downloads that can be removed from the list.
*/
get canRemoveFinished() {
for (let [, dataItem] of Iterator(this.dataItems)) {
if (dataItem && !dataItem.inProgress) {
for (let dataItem of this.dataItems) {
if (!dataItem.inProgress) {
return true;
}
}
@ -668,7 +664,7 @@ DownloadsDataCtor.prototype = {
onDownloadAdded(aDownload) {
let dataItem = new DownloadsDataItem(aDownload);
this._downloadToDataItemMap.set(aDownload, dataItem);
this.dataItems[dataItem.downloadGuid] = dataItem;
this.dataItems.add(dataItem);
for (let view of this._views) {
view.onDataItemAdded(dataItem, true);
@ -695,7 +691,7 @@ DownloadsDataCtor.prototype = {
}
this._downloadToDataItemMap.delete(aDownload);
this.dataItems[dataItem.downloadGuid] = null;
this.dataItems.delete(dataItem);
for (let view of this._views) {
view.onDataItemRemoved(dataItem);
}
@ -718,7 +714,7 @@ DownloadsDataCtor.prototype = {
if (oldState != aDataItem.state) {
for (let view of this._views) {
try {
view.getViewItem(aDataItem).onStateChange(oldState);
view.onDataItemStateChanged(aDataItem, oldState);
} catch (ex) {
Cu.reportError(ex);
}
@ -756,7 +752,7 @@ DownloadsDataCtor.prototype = {
}
for (let view of this._views) {
view.getViewItem(aDataItem).onProgressChange();
view.onDataItemChanged(aDataItem);
}
},
@ -801,9 +797,7 @@ DownloadsDataCtor.prototype = {
// Sort backwards by start time, ensuring that the most recent
// downloads are added first regardless of their state.
let loadedItemsArray = [dataItem
for each (dataItem in this.dataItems)
if (dataItem)];
let loadedItemsArray = [...this.dataItems];
loadedItemsArray.sort((a, b) => b.startTime - a.startTime);
loadedItemsArray.forEach(dataItem => aView.onDataItemAdded(dataItem, false));
@ -881,7 +875,6 @@ XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
function DownloadsDataItem(aDownload) {
this._download = aDownload;
this.downloadGuid = "id:" + this._autoIncrementId;
this.file = aDownload.target.path;
this.target = OS.Path.basename(aDownload.target.path);
this.uri = aDownload.source.url;
@ -891,13 +884,6 @@ function DownloadsDataItem(aDownload) {
}
DownloadsDataItem.prototype = {
/**
* The JavaScript API does not need identifiers for Download objects, so they
* are generated sequentially for the corresponding DownloadDataItem.
*/
get _autoIncrementId() ++DownloadsDataItem.prototype.__lastId,
__lastId: 0,
/**
* Updates this object from the underlying Download object.
*/
@ -1250,16 +1236,26 @@ const DownloadsViewPrototype = {
},
/**
* Returns the view item associated with the provided data item for this view.
* Called when the "state" property of a DownloadsDataItem has changed.
*
* @param aDataItem
* DownloadsDataItem object for which the view item is requested.
*
* @return Object that can be used to notify item status events.
* The onDataItemChanged notification will be sent afterwards.
*
* @note Subclasses should override this.
*/
getViewItem(aDataItem) {
onDataItemStateChanged(aDataItem) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
/**
* Called every time any state property of a DownloadsDataItem may have
* changed, including progress properties and the "state" property.
*
* Note that progress notification changes are throttled at the Downloads.jsm
* API level, and there is no throttling mechanism in the front-end.
*
* @note Subclasses should override this.
*/
onDataItemChanged(aDataItem) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
@ -1358,34 +1354,21 @@ DownloadsIndicatorDataCtor.prototype = {
this._updateViews();
},
/**
* Returns the view item associated with the provided data item for this view.
*
* @param aDataItem
* DownloadsDataItem object for which the view item is requested.
*
* @return Object that can be used to notify item status events.
*/
getViewItem(aDataItem) {
let data = this._isPrivate ? PrivateDownloadsIndicatorData
: DownloadsIndicatorData;
return Object.freeze({
onStateChange(aOldState) {
if (aDataItem.state == nsIDM.DOWNLOAD_FINISHED ||
aDataItem.state == nsIDM.DOWNLOAD_FAILED) {
data.attention = true;
}
// DownloadsView
onDataItemStateChanged(aDataItem, aOldState) {
if (aDataItem.state == nsIDM.DOWNLOAD_FINISHED ||
aDataItem.state == nsIDM.DOWNLOAD_FAILED) {
this.attention = true;
}
// Since the state of a download changed, reset the estimated time left.
data._lastRawTimeLeft = -1;
data._lastTimeLeft = -1;
// Since the state of a download changed, reset the estimated time left.
this._lastRawTimeLeft = -1;
this._lastTimeLeft = -1;
},
data._updateViews();
},
onProgressChange() {
data._updateViews();
}
});
// DownloadsView
onDataItemChanged() {
this._updateViews();
},
//////////////////////////////////////////////////////////////////////////////
@ -1480,7 +1463,7 @@ DownloadsIndicatorDataCtor.prototype = {
_activeDataItems() {
let dataItems = this._isPrivate ? PrivateDownloadsData.dataItems
: DownloadsData.dataItems;
for each (let dataItem in dataItems) {
for (let dataItem of dataItems) {
if (dataItem && dataItem.inProgress) {
yield dataItem;
}
@ -1624,19 +1607,16 @@ DownloadsSummaryData.prototype = {
this._updateViews();
},
getViewItem(aDataItem) {
let self = this;
return Object.freeze({
onStateChange(aOldState) {
// Since the state of a download changed, reset the estimated time left.
self._lastRawTimeLeft = -1;
self._lastTimeLeft = -1;
self._updateViews();
},
onProgressChange() {
self._updateViews();
}
});
// DownloadsView
onDataItemStateChanged(aOldState) {
// Since the state of a download changed, reset the estimated time left.
this._lastRawTimeLeft = -1;
this._lastTimeLeft = -1;
},
// DownloadsView
onDataItemChanged() {
this._updateViews();
},
//////////////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -53,14 +53,10 @@ const NOT_AVAILABLE = Number.MAX_VALUE;
* The shell doesn't take care of inserting the item, or removing it when it's no longer
* valid. That's the caller (a DownloadsPlacesView object) responsibility.
*
* The caller is also responsible for "passing over" notification from both the
* download-view and the places-result-observer, in the following manner:
* - The DownloadsPlacesView object implements getViewItem of the download-view
* pseudo interface. It returns this object (therefore we implement
* onStateChangea and onProgressChange here).
* - The DownloadsPlacesView object adds itself as a places result observer and
* calls this object's placesNodeIconChanged, placesNodeTitleChanged and
* placeNodeAnnotationChanged from its callbacks.
* The caller is also responsible for "passing over" notifications. The
* DownloadsPlacesView object implements onDataItemStateChanged and
* onDataItemChanged of the DownloadsView pseudo interface, and registers as a
* Places result observer.
*
* @param [optional] aDataItem
* The data item of a the session download. Required if aPlacesNode is not set
@ -176,13 +172,10 @@ DownloadElementShell.prototype = {
}
if (this._placesNode) {
// Try to extract an extension from the uri.
let ext = this._downloadURIObj.QueryInterface(Ci.nsIURL).fileExtension;
if (ext) {
return "moz-icon://." + ext + "?size=32";
}
return this._placesNode.icon || "moz-icon://.unknown?size=32";
return "moz-icon://.unknown?size=32";
}
// Assert unreachable.
if (this._dataItem) {
throw new Error("Session-download items should always have a target file uri");
}
@ -304,9 +297,9 @@ DownloadElementShell.prototype = {
* - fileName: the downloaded file name on the file system. Set if filePath
* is set.
* - displayName: the user-facing label for the download. This is always
* set. If available, it's set to the downloaded file name. If not,
* the places title for the download uri is used it's set. As a last
* resort, we fallback to the download uri.
* set. If available, it's set to the downloaded file name. If not, this
* means the download does not have Places metadata because it is very old,
* and in this rare case the download uri is used.
* - fileSize (only set for downloads which completed successfully):
* the downloaded file size. For downloads done after the landing of
* bug 826991, this value is "static" - that is, it does not necessarily
@ -348,7 +341,7 @@ DownloadElementShell.prototype = {
this._extractFilePathAndNameFromFileURI(targetFileURI);
this._metaData.displayName = this._metaData.fileName;
} catch (ex) {
this._metaData.displayName = this._placesNode.title || this.downloadURI;
this._metaData.displayName = this.downloadURI;
}
}
}
@ -489,12 +482,6 @@ DownloadElementShell.prototype = {
}
},
_updateDisplayNameAndIcon() {
let metaData = this.getDownloadMetaData();
this._element.setAttribute("displayName", metaData.displayName);
this._element.setAttribute("image", this._getIcon());
},
_updateUI() {
if (!this.active) {
throw new Error("Trying to _updateUI on an inactive download shell");
@ -503,7 +490,9 @@ DownloadElementShell.prototype = {
this._metaData = null;
this._targetFileInfoFetched = false;
this._updateDisplayNameAndIcon();
let metaData = this.getDownloadMetaData();
this._element.setAttribute("displayName", metaData.displayName);
this._element.setAttribute("image", this._getIcon());
// For history downloads done in past releases, the downloads/metaData
// annotation is not set, and therefore we cannot tell the download
@ -515,61 +504,7 @@ DownloadElementShell.prototype = {
}
},
placesNodeIconChanged() {
if (!this._dataItem) {
this._element.setAttribute("image", this._getIcon());
}
},
placesNodeTitleChanged() {
// If there's a file path, we use the leaf name for the title.
if (!this._dataItem && this.active &&
!this.getDownloadMetaData().filePath) {
this._metaData = null;
this._updateDisplayNameAndIcon();
}
},
placesNodeAnnotationChanged(aAnnoName) {
this._annotations.delete(aAnnoName);
if (!this._dataItem && this.active) {
if (aAnnoName == DOWNLOAD_META_DATA_ANNO) {
let metaData = this.getDownloadMetaData();
let annotatedMetaData = this._getAnnotatedMetaData();
metaData.endTime = annotatedMetaData.endTime;
if ("fileSize" in annotatedMetaData) {
metaData.fileSize = annotatedMetaData.fileSize;
} else {
delete metaData.fileSize;
}
if (metaData.state != annotatedMetaData.state) {
metaData.state = annotatedMetaData.state;
if (this._element.selected) {
goUpdateDownloadCommands();
}
}
this._updateDownloadStatusUI();
} else if (aAnnoName == DESTINATION_FILE_URI_ANNO) {
let metaData = this.getDownloadMetaData();
let targetFileURI = this._getAnnotation(DESTINATION_FILE_URI_ANNO);
[metaData.filePath, metaData.fileName] =
this._extractFilePathAndNameFromFileURI(targetFileURI);
metaData.displayName = metaData.fileName;
this._updateDisplayNameAndIcon();
if (this._targetFileInfoFetched) {
// This will also update the download commands if necessary.
this._targetFileInfoFetched = false;
this._fetchTargetFileInfo();
}
}
}
},
/* DownloadView */
onStateChange(aOldState) {
onStateChanged(aOldState) {
let metaData = this.getDownloadMetaData();
metaData.state = this.dataItem.state;
if (aOldState != nsIDM.DOWNLOAD_FINISHED && aOldState != metaData.state) {
@ -583,6 +518,7 @@ DownloadElementShell.prototype = {
}
this._updateDownloadStatusUI();
if (this._element.selected) {
goUpdateDownloadCommands();
} else {
@ -590,8 +526,7 @@ DownloadElementShell.prototype = {
}
},
/* DownloadView */
onProgressChange() {
onChanged() {
this._updateDownloadStatusUI();
},
@ -837,15 +772,6 @@ DownloadsPlacesView.prototype = {
return this._active;
},
_forEachDownloadElementShellForURI(aURI, aCallback) {
if (this._downloadElementsShellsForURI.has(aURI)) {
let downloadElementShells = this._downloadElementsShellsForURI.get(aURI);
for (let des of downloadElementShells) {
aCallback(des);
}
}
},
_getAnnotationsFor(aURI) {
if (!this._cachedAnnotations) {
this._cachedAnnotations = new Map();
@ -934,7 +860,7 @@ DownloadsPlacesView.prototype = {
// data item. Thus, we also check that we make sure we don't have a view item
// already.
if (!shouldCreateShell &&
aDataItem && this.getViewItem(aDataItem) == null) {
aDataItem && !this._viewItemsForDataItems.has(aDataItem)) {
// If there's a past-download-only shell for this download-uri with no
// associated data item, use it for the new data item. Otherwise, go ahead
// and create another shell.
@ -1051,7 +977,7 @@ DownloadsPlacesView.prototype = {
throw new Error("Should have had at leaat one shell for this uri");
}
let shell = this.getViewItem(aDataItem);
let shell = this._viewItemsForDataItems.get(aDataItem);
if (!shells.has(shell)) {
throw new Error("Missing download element shell in shells list for url");
}
@ -1277,21 +1203,9 @@ DownloadsPlacesView.prototype = {
this._removeHistoryDownloadFromView(aPlacesNode);
},
nodeIconChanged(aNode) {
this._forEachDownloadElementShellForURI(aNode.uri,
des => des.placesNodeIconChanged());
},
nodeAnnotationChanged(aNode, aAnnoName) {
this._forEachDownloadElementShellForURI(aNode.uri,
des => des.placesNodeAnnotationChanged(aAnnoName));
},
nodeTitleChanged(aNode, aNewTitle) {
this._forEachDownloadElementShellForURI(aNode.uri,
des => des.placesNodeTitleChanged());
},
nodeAnnotationChanged() {},
nodeIconChanged() {},
nodeTitleChanged() {},
nodeKeywordChanged() {},
nodeDateAddedChanged() {},
nodeLastModifiedChanged() {},
@ -1362,8 +1276,14 @@ DownloadsPlacesView.prototype = {
this._removeSessionDownloadFromView(aDataItem);
},
getViewItem(aDataItem) {
return this._viewItemsForDataItems.get(aDataItem, null);
// DownloadsView
onDataItemStateChanged(aDataItem, aOldState) {
this._viewItemsForDataItems.get(aDataItem).onStateChanged(aOldState);
},
// DownloadsView
onDataItemChanged(aDataItem) {
this._viewItemsForDataItems.get(aDataItem).onChanged();
},
supportsCommand(aCommand) {

Просмотреть файл

@ -554,7 +554,7 @@ const DownloadsPanel = {
// still exist, and update the allowed items interactions accordingly. We
// do these checks on a background thread, and don't prevent the panel to
// be displayed while these checks are being performed.
for each (let viewItem in DownloadsView._viewItems) {
for (let viewItem of DownloadsView._visibleViewItems.values()) {
viewItem.verifyTargetExists();
}
@ -673,11 +673,11 @@ const DownloadsView = {
_dataItems: [],
/**
* Object containing the available DownloadsViewItem objects, indexed by their
* numeric download identifier. There is a limited number of view items in
* the panel at any given time.
* Associates the visible DownloadsDataItem objects with their corresponding
* DownloadsViewItem object. There is a limited number of view items in the
* panel at any given time.
*/
_viewItems: {},
_visibleViewItems: new Map(),
/**
* Called when the number of items in the list changes.
@ -814,30 +814,31 @@ const DownloadsView = {
this._itemCountChanged();
},
/**
* Returns the view item associated with the provided data item for this view.
*
* @param aDataItem
* DownloadsDataItem object for which the view item is requested.
*
* @return Object that can be used to notify item status events.
*/
getViewItem(aDataItem) {
// If the item is visible, just return it, otherwise return a mock object
// that doesn't react to notifications.
if (aDataItem.downloadGuid in this._viewItems) {
return this._viewItems[aDataItem.downloadGuid];
// DownloadsView
onDataItemStateChanged(aDataItem, aOldState) {
let viewItem = this._visibleViewItems.get(aDataItem);
if (viewItem) {
viewItem.onStateChanged(aOldState);
}
},
// DownloadsView
onDataItemChanged(aDataItem) {
let viewItem = this._visibleViewItems.get(aDataItem);
if (viewItem) {
viewItem.onChanged();
}
return this._invisibleViewItem;
},
/**
* Mock DownloadsDataItem object that doesn't react to notifications.
* Associates each richlistitem for a download with its corresponding
* DownloadsViewItemController object.
*/
_invisibleViewItem: Object.freeze({
onStateChange() {},
onProgressChange() {},
}),
_controllersForElements: new Map(),
controllerForElement(element) {
return this._controllersForElements.get(element);
},
/**
* Creates a new view item associated with the specified data item, and adds
@ -850,7 +851,9 @@ const DownloadsView = {
let element = document.createElement("richlistitem");
let viewItem = new DownloadsViewItem(aDataItem, element);
this._viewItems[aDataItem.downloadGuid] = viewItem;
this._visibleViewItems.set(aDataItem, viewItem);
let viewItemController = new DownloadsViewItemController(aDataItem);
this._controllersForElements.set(element, viewItemController);
if (aNewest) {
this.richListBox.insertBefore(element, this.richListBox.firstChild);
} else {
@ -863,14 +866,15 @@ const DownloadsView = {
*/
_removeViewItem(aDataItem) {
DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list.");
let element = this.getViewItem(aDataItem)._element;
let element = this._visibleViewItems.get(aDataItem)._element;
let previousSelectedIndex = this.richListBox.selectedIndex;
this.richListBox.removeChild(element);
if (previousSelectedIndex != -1) {
this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
this.richListBox.itemCount - 1);
}
delete this._viewItems[aDataItem.downloadGuid];
this._visibleViewItems.delete(aDataItem);
this._controllersForElements.delete(element);
},
//////////////////////////////////////////////////////////////////////////////
@ -891,7 +895,7 @@ const DownloadsView = {
while (target.nodeName != "richlistitem") {
target = target.parentNode;
}
new DownloadsViewItemController(target).doCommand(aCommand);
DownloadsView.controllerForElement(target).doCommand(aCommand);
},
onDownloadClick(aEvent) {
@ -965,8 +969,8 @@ const DownloadsView = {
return;
}
let controller = new DownloadsViewItemController(element);
let localFile = controller.dataItem.localFile;
let localFile = DownloadsView.controllerForElement(element)
.dataItem.localFile;
if (!localFile.exists()) {
return;
}
@ -1010,8 +1014,6 @@ function DownloadsViewItem(aDataItem, aElement) {
let attributes = {
"type": "download",
"class": "download-state",
"id": "downloadsItem_" + this.dataItem.downloadGuid,
"downloadGuid": this.dataItem.downloadGuid,
"state": this.dataItem.state,
"progress": this.dataItem.inProgress ? this.dataItem.percentComplete : 100,
"target": this.dataItem.target,
@ -1052,7 +1054,7 @@ DownloadsViewItem.prototype = {
* the download might be the same as before, if the data layer received
* multiple events for the same download.
*/
onStateChange(aOldState) {
onStateChanged(aOldState) {
// If a download just finished successfully, it means that the target file
// now exists and we can extract its specific icon. To ensure that the icon
// is reloaded, we must change the URI used by the XUL image element, for
@ -1072,14 +1074,12 @@ DownloadsViewItem.prototype = {
// Update the user interface after switching states.
this._element.setAttribute("state", this.dataItem.state);
this._updateProgress();
this._updateStatusLine();
},
/**
* Called when the download progress has changed.
*/
onProgressChange() {
onChanged() {
this._updateProgress();
this._updateStatusLine();
},
@ -1283,8 +1283,8 @@ const DownloadsViewController = {
// Other commands are selection-specific.
let element = DownloadsView.richListBox.selectedItem;
return element &&
new DownloadsViewItemController(element).isCommandEnabled(aCommand);
return element && DownloadsView.controllerForElement(element)
.isCommandEnabled(aCommand);
},
doCommand(aCommand) {
@ -1298,7 +1298,7 @@ const DownloadsViewController = {
let element = DownloadsView.richListBox.selectedItem;
if (element) {
// The doCommand function also checks if the command is enabled.
new DownloadsViewItemController(element).doCommand(aCommand);
DownloadsView.controllerForElement(element).doCommand(aCommand);
}
},
@ -1334,9 +1334,8 @@ const DownloadsViewController = {
* Handles all the user interaction events, in particular the "commands",
* related to a single item in the downloads list widgets.
*/
function DownloadsViewItemController(aElement) {
let downloadGuid = aElement.getAttribute("downloadGuid");
this.dataItem = DownloadsCommon.getData(window).dataItems[downloadGuid];
function DownloadsViewItemController(aDataItem) {
this.dataItem = aDataItem;
}
DownloadsViewItemController.prototype = {

Просмотреть файл

@ -49,7 +49,7 @@ add_task(function* test_basic_functionality() {
let itemCount = richlistbox.children.length;
for (let i = 0; i < itemCount; i++) {
let element = richlistbox.children[itemCount - i - 1];
let dataItem = new DownloadsViewItemController(element).dataItem;
let dataItem = DownloadsView.controllerForElement(element).dataItem;
is(dataItem.state, DownloadData[i].state, "Download states match up");
}
});

Просмотреть файл

@ -771,7 +771,7 @@ loop.conversationViews = (function(mozL10n) {
this.props.store.getStoreState("callStateReason");
if (callStateReason === "reject" || callStateReason === "busy" ||
callStateReason === "setup") {
callStateReason === "user-unknown") {
var contactDisplayName = _getContactDisplayName(this.props.contact);
if (contactDisplayName.length) {
return mozL10n.get(

Просмотреть файл

@ -771,7 +771,7 @@ loop.conversationViews = (function(mozL10n) {
this.props.store.getStoreState("callStateReason");
if (callStateReason === "reject" || callStateReason === "busy" ||
callStateReason === "setup") {
callStateReason === "user-unknown") {
var contactDisplayName = _getContactDisplayName(this.props.contact);
if (contactDisplayName.length) {
return mozL10n.get(

Просмотреть файл

@ -374,8 +374,12 @@ loop.store = loop.store || {};
function(err, result) {
if (err) {
console.error("Failed to get outgoing call data", err);
var failureReason = "setup";
if (err.errno == 122) {
failureReason = "user-unknown";
}
this.dispatcher.dispatch(
new sharedActions.ConnectionFailure({reason: "setup"}));
new sharedActions.ConnectionFailure({reason: failureReason}));
return;
}

Просмотреть файл

@ -444,12 +444,22 @@ describe("loop.conversationViews", function () {
{contactName: loop.conversationViews._getContactDisplayName(contact)});
});
it("should show 'contact unavailable' when the reason is 'setup'",
it("should show 'something went wrong' when the reason is 'setup'",
function () {
store.setStoreState({callStateReason: "setup"});
view = mountTestComponent({contact: contact});
sinon.assert.calledWithExactly(document.mozL10n.get,
"generic_failure_title");
});
it("should show 'contact unavailable' when the reason is 'user-unknown'",
function () {
store.setStoreState({callStateReason: "user-unknown"});
view = mountTestComponent({contact: contact});
sinon.assert.calledWithExactly(document.mozL10n.get,
"contact_unavailable_title",
{contactName: loop.conversationViews._getContactDisplayName(contact)});

Просмотреть файл

@ -233,12 +233,12 @@ public class ImmutableViewportMetrics {
public ImmutableViewportMetrics offsetViewportByAndClamp(float dx, float dy) {
if (isRTL) {
return setViewportOrigin(
Math.min(pageRectRight - getWidth(), Math.max(viewportRectLeft + dx, pageRectLeft)),
Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeight())));
Math.min(pageRectRight - getWidthWithoutMargins(), Math.max(viewportRectLeft + dx, pageRectLeft)),
Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins())));
}
return setViewportOrigin(
Math.max(pageRectLeft, Math.min(viewportRectLeft + dx, pageRectRight - getWidth())),
Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeight())));
Math.max(pageRectLeft, Math.min(viewportRectLeft + dx, pageRectRight - getWidthWithoutMargins())),
Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins())));
}
public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) {

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 365 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 350 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 665 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 116 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 271 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 278 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 557 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 110 B

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- Bug 1106935: Temporary replacement for old tablet resources -->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@null"/>

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- Bug 1106935: Temporary replacement for old tablet resources -->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@null"/>

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- Bug 1106935: Temporary replacement for old tablet resources -->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@null"/>

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- Bug 1106935: Temporary replacement for old tablet resources -->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@null"/>

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 511 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 501 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 774 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 122 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 798 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 546 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 643 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 440 B

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- Bug 1106935: Temporary replacement for old tablet resources -->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@null"/>

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- Bug 1106935: Temporary replacement for old tablet resources -->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@null"/>

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 998 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 645 B

Просмотреть файл

@ -5,7 +5,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/restore_defaults"
android:drawable="@drawable/menu"
android:drawable="@drawable/new_tablet_menu"
android:showAsAction="never"
android:title="@string/pref_search_restore_defaults" />
</menu>

Просмотреть файл

@ -118,11 +118,6 @@ public class UpdateService extends IntentService {
} else if (UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD.equals(intent.getAction())) {
mCancelDownload = true;
} else {
if (!UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) {
// Delete the update package used to install the current version.
deleteUpdatePackage(getLastFileName());
}
super.onStartCommand(intent, flags, startId);
}
@ -465,6 +460,11 @@ public class UpdateService extends IntentService {
}
}
if (!info.buildID.equals(getLastBuildID())) {
// Delete the previous package when a new version becomes available.
deleteUpdatePackage(getLastFileName());
}
Log.i(LOGTAG, "downloading update package");
sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADING);

Просмотреть файл

@ -103,6 +103,14 @@ var HelperApps = {
},
getAppsForUri: function getAppsForUri(uri, flags = { }, callback) {
// Return early for well-known internal schemes
if (!uri || uri.schemeIs("about") || uri.schemeIs("chrome")) {
if (callback) {
callback([]);
}
return [];
}
flags.filterBrowsers = "filterBrowsers" in flags ? flags.filterBrowsers : true;
flags.filterHtml = "filterHtml" in flags ? flags.filterHtml : true;
@ -110,9 +118,9 @@ var HelperApps = {
let msg = this._getMessage("Intent:GetHandlers", uri, flags);
let parseData = (d) => {
let apps = []
if (!d)
if (!d) {
return apps;
}
apps = this._parseApps(d.apps);
@ -140,8 +148,6 @@ var HelperApps = {
if (!callback) {
let data = this._sendMessageSync(msg);
if (!data)
return [];
return parseData(data);
} else {
Messaging.sendRequestForResult(msg).then(function(data) {
@ -173,9 +179,10 @@ var HelperApps = {
_getMessage: function(type, uri, options = {}) {
let mimeType = options.mimeType;
if (uri && mimeType == undefined)
if (uri && mimeType == undefined) {
mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || "";
}
return {
type: type,
mime: mimeType,
@ -211,8 +218,9 @@ var HelperApps = {
});
let thread = Services.tm.currentThread;
while (res == null)
while (res == null) {
thread.processNextEvent(true);
}
return res;
},

Просмотреть файл

@ -77,9 +77,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
"resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gNotifier",
"@mozilla.org/browser/nav-history-service;1",
Ci.nsPIPlacesHistoryListenersNotifier);
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
Cu.importGlobalProperties(["URL"]);
@ -529,15 +526,14 @@ let remove = Task.async(function*({guids, urls}, onResult = null) {
}
// 5. Notify observers.
let observers = PlacesUtils.history.getObservers();
let reason = Ci.nsINavHistoryObserver.REASON_DELETED;
for (let {guid, uri, toRemove} of pages) {
gNotifier.notifyOnPageExpired(
uri, // uri
0, // visitTime - There are no more visits
toRemove, // wholeEntry
guid, // guid
Ci.nsINavHistoryObserver.REASON_DELETED, // reason
-1 // transition
);
if (toRemove) {
notify(observers, "onDeleteURI", [uri, guid, reason]);
} else {
notify(observers, "onDeleteVisits", [uri, 0, guid, reason, 0]);
}
}
});

Просмотреть файл

@ -28,7 +28,6 @@ if CONFIG['MOZ_PLACES']:
'nsINavBookmarksService.idl',
'nsITaggingService.idl',
'nsPIPlacesDatabase.idl',
'nsPIPlacesHistoryListenersNotifier.idl',
]
EXPORTS.mozilla.places = [

Просмотреть файл

@ -168,7 +168,6 @@ NS_INTERFACE_MAP_BEGIN(nsNavHistory)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsPIPlacesDatabase)
NS_INTERFACE_MAP_ENTRY(nsPIPlacesHistoryListenersNotifier)
NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService)
NS_IMPL_QUERY_CLASSINFO(nsNavHistory)
@ -3051,9 +3050,7 @@ nsNavHistory::AsyncExecuteLegacyQueries(nsINavHistoryQuery** aQueries,
}
// nsPIPlacesHistoryListenersNotifier ******************************************
NS_IMETHODIMP
nsresult
nsNavHistory::NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime,
bool aWholeEntry, const nsACString& aGUID,
uint16_t aReason, uint32_t aTransitionType)

Просмотреть файл

@ -8,7 +8,6 @@
#include "nsINavHistoryService.h"
#include "nsPIPlacesDatabase.h"
#include "nsPIPlacesHistoryListenersNotifier.h"
#include "nsIBrowserHistory.h"
#include "nsINavBookmarksService.h"
#include "nsIFaviconService.h"
@ -67,7 +66,6 @@ class nsNavHistory MOZ_FINAL : public nsSupportsWeakReference
, public nsIObserver
, public nsIBrowserHistory
, public nsPIPlacesDatabase
, public nsPIPlacesHistoryListenersNotifier
, public mozIStorageVacuumParticipant
{
friend class PlacesSQLQueryBuilder;
@ -80,7 +78,6 @@ public:
NS_DECL_NSIBROWSERHISTORY
NS_DECL_NSIOBSERVER
NS_DECL_NSPIPLACESDATABASE
NS_DECL_NSPIPLACESHISTORYLISTENERSNOTIFIER
NS_DECL_MOZISTORAGEVACUUMPARTICIPANT
/**
@ -183,6 +180,29 @@ public:
*/
nsresult invalidateFrecencies(const nsCString& aPlaceIdsQueryString);
/**
* Calls onDeleteVisits and onDeleteURI notifications on registered listeners
* with the history service.
*
* @param aURI
* The nsIURI object representing the URI of the page being expired.
* @param aVisitTime
* The time, in microseconds, that the page being expired was visited.
* @param aWholeEntry
* Indicates if this is the last visit for this URI.
* @param aGUID
* The unique ID associated with the page.
* @param aReason
* Indicates the reason for the removal.
* See nsINavHistoryObserver::REASON_* constants.
* @param aTransitionType
* If it's a valid TRANSITION_* value, all visits of the specified type
* have been removed.
*/
nsresult NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime,
bool aWholeEntry, const nsACString& aGUID,
uint16_t aReason, uint32_t aTransitionType);
/**
* These functions return non-owning references to the locale-specific
* objects for places components.

Просмотреть файл

@ -1,46 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 expandtab
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIURI;
/**
* This is a private interface used by Places components to notify history
* listeners about important notifications. These should not be used by any
* code that is not part of core.
*
* @note See also: nsINavHistoryObserver
*/
[scriptable, uuid(808cf36c-4c9a-4bdb-91a4-d60a6fc25add)]
interface nsPIPlacesHistoryListenersNotifier : nsISupports
{
/**
* Calls onDeleteVisits and onDeleteURI notifications on registered listeners
* with the history service.
*
* @param aURI
* The nsIURI object representing the URI of the page being expired.
* @param aVisitTime
* The time, in microseconds, that the page being expired was visited.
* @param aWholeEntry
* Indicates if this is the last visit for this URI.
* @param aGUID
* The unique ID associated with the page.
* @param aReason
* Indicates the reason for the removal.
* See nsINavHistoryObserver::REASON_* constants.
* @param aTransitionType
* If it's a valid TRANSITION_* value, all visits of the specified type
* have been removed.
*/
void notifyOnPageExpired(in nsIURI aURI,
in PRTime aVisitTime,
in boolean aWholeEntry,
in ACString aGUID,
in unsigned short aReason,
in unsigned long aTransitionType);
};

Просмотреть файл

@ -27,6 +27,9 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
////////////////////////////////////////////////////////////////////////////////
//// Constants
@ -400,6 +403,24 @@ const EXPIRATION_QUERIES = {
},
};
/**
* Sends a bookmarks notification through the given observers.
*
* @param observers
* array of nsINavBookmarkObserver objects.
* @param notification
* the notification name.
* @param args
* array of arguments to pass to the notification.
*/
function notify(observers, notification, args = []) {
for (let observer of observers) {
try {
observer[notification](...args);
} catch (ex) {}
}
}
////////////////////////////////////////////////////////////////////////////////
//// nsPlacesExpiration definition
@ -430,9 +451,6 @@ function nsPlacesExpiration()
return db;
});
XPCOMUtils.defineLazyServiceGetter(this, "_hsn",
"@mozilla.org/browser/nav-history-service;1",
"nsPIPlacesHistoryListenersNotifier");
XPCOMUtils.defineLazyServiceGetter(this, "_sys",
"@mozilla.org/system-info;1",
"nsIPropertyBag2");
@ -626,9 +644,15 @@ nsPlacesExpiration.prototype = {
let guid = row.getResultByName("guid");
let visitDate = row.getResultByName("visit_date");
let wholeEntry = row.getResultByName("whole_entry");
let reason = Ci.nsINavHistoryObserver.REASON_EXPIRED;
let observers = PlacesUtils.history.getObservers();
// Dispatch expiration notifications to history.
this._hsn.notifyOnPageExpired(uri, visitDate, wholeEntry, guid,
Ci.nsINavHistoryObserver.REASON_EXPIRED, 0);
if (wholeEntry) {
notify(observers, "onDeleteURI", [uri, guid, reason]);
} else {
notify(observers, "onDeleteVisits", [uri, visitDate, guid, reason, 0]);
}
}
},

Просмотреть файл

@ -110,6 +110,30 @@ let AnimationPlayerActor = ActorClass({
return parseFloat(durationText) * 1000;
},
/**
* Get the animation delay from this player, in milliseconds.
* Note that the Web Animations API doesn't yet offer a way to retrieve this
* directly from the AnimationPlayer object, so for now, a delay is only
* returned if found in the node's computed styles.
* @return {Number}
*/
getDelay: function() {
let delayText;
if (this.styles.animationDelay !== "0s") {
delayText = this.styles.animationDelay;
} else if (this.styles.transitionDelay !== "0s") {
delayText = this.styles.transitionDelay;
} else {
return 0;
}
if (delayText.indexOf(",") !== -1) {
delayText = delayText.split(",")[this.playerIndex];
}
return parseFloat(delayText) * 1000;
},
/**
* Get the animation iteration count for this player. That is, how many times
* is the animation scheduled to run.
@ -145,6 +169,7 @@ let AnimationPlayerActor = ActorClass({
playState: this.player.playState,
name: this.player.source.effect.name,
duration: this.getDuration(),
delay: this.getDelay(),
iterationCount: this.getIterationCount(),
/**
* Is the animation currently running on the compositor. This is important for
@ -239,6 +264,7 @@ let AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
playState: this._form.playState,
name: this._form.name,
duration: this._form.duration,
delay: this._form.delay,
iterationCount: this._form.iterationCount,
isRunningOnCompositor: this._form.isRunningOnCompositor
}

Просмотреть файл

@ -3,8 +3,8 @@
.simple-animation {
display: inline-block;
width: 150px;
height: 150px;
width: 50px;
height: 50px;
border-radius: 50%;
background: red;
@ -14,8 +14,8 @@
.multiple-animations {
display: inline-block;
width: 150px;
height: 150px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #eee;
@ -25,8 +25,8 @@
.transition {
display: inline-block;
width: 150px;
height: 150px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #f06;
@ -39,14 +39,50 @@
.short-animation {
display: inline-block;
width: 150px;
height: 150px;
width: 50px;
height: 50px;
border-radius: 50%;
background: purple;
animation: move 1s;
}
.delayed-animation {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
background: rebeccapurple;
animation: move 2s 5s infinite;
}
.delayed-transition {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
background: black;
transition: width 5s 3s;
}
.delayed-transition.get-round {
width: 200px;
}
.delayed-multiple-animations {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
background: green;
animation: move .5s 1s 10, glow 1s .75s 30;
}
@keyframes move {
100% {
transform: translateY(100px);
@ -64,10 +100,14 @@
<div class="multiple-animations"></div>
<div class="transition"></div>
<div class="short-animation"></div>
<div class="delayed-animation"></div>
<div class="delayed-transition"></div>
<div class="delayed-multiple-animations"></div>
<script type="text/javascript">
// Get the transition started when the page loads
// Get the transitions started when the page loads
var players;
addEventListener("load", function() {
document.querySelector(".transition").classList.add("get-round");
document.querySelector(".delayed-transition").classList.add("get-round");
});
</script>

Просмотреть файл

@ -19,6 +19,7 @@ support-files =
[browser_animation_actors_03.js]
[browser_animation_actors_04.js]
[browser_animation_actors_05.js]
[browser_animation_actors_06.js]
[browser_navigateEvents.js]
[browser_storage_dynamic_windows.js]
[browser_storage_listings.js]

Просмотреть файл

@ -36,6 +36,7 @@ function* playerHasAnInitialState(walker, front) {
ok("playState" in player.initialState, "Player's state has playState");
ok("name" in player.initialState, "Player's state has name");
ok("duration" in player.initialState, "Player's state has duration");
ok("delay" in player.initialState, "Player's state has delay");
ok("iterationCount" in player.initialState, "Player's state has iterationCount");
ok("isRunningOnCompositor" in player.initialState, "Player's state has isRunningOnCompositor");
}
@ -67,6 +68,16 @@ function* playerStateIsCorrect(walker, front) {
is(state.duration, 1000, "The 2nd animation's duration is correct");
is(state.iterationCount, 5, "The 2nd animation's iteration count is correct");
is(state.playState, "running", "The 2nd animation's playState is correct");
info("Checking the state of an animation with delay");
state = yield getAnimationStateForNode(walker, front, ".delayed-animation", 0);
is(state.delay, 5000, "The animation delay is correct");
info("Checking the state of an transition with delay");
state = yield getAnimationStateForNode(walker, front, ".delayed-transition", 0);
is(state.delay, 3000, "The transition delay is correct");
}
function* getAnimationStateForNode(walker, front, nodeSelector, playerIndex) {

Просмотреть файл

@ -0,0 +1,50 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that the duration, iterationCount and delay are retrieved correctly for
// multiple animations.
const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());
let form = yield connectDebuggerClient(client);
let inspector = InspectorFront(client, form);
let walker = yield inspector.getWalker();
let front = AnimationsFront(client, form);
yield playerHasAnInitialState(walker, front);
yield closeDebuggerClient(client);
gBrowser.removeCurrentTab();
});
function* playerHasAnInitialState(walker, front) {
let state = yield getAnimationStateForNode(walker, front,
".delayed-multiple-animations", 0);
ok(state.duration, 500, "The duration of the first animation is correct");
ok(state.iterationCount, 10, "The iterationCount of the first animation is correct");
ok(state.delay, 1000, "The delay of the first animation is correct");
state = yield getAnimationStateForNode(walker, front,
".delayed-multiple-animations", 1);
ok(state.duration, 1000, "The duration of the secon animation is correct");
ok(state.iterationCount, 30, "The iterationCount of the secon animation is correct");
ok(state.delay, 750, "The delay of the secon animation is correct");
}
function* getAnimationStateForNode(walker, front, nodeSelector, playerIndex) {
let node = yield walker.querySelector(walker.rootNode, nodeSelector);
let players = yield front.getAnimationPlayersForNode(node);
let player = players[playerIndex];
yield player.ready();
let state = yield player.getCurrentState();
return state;
}