Merge m-c to inbound. a=merge

This commit is contained in:
Ryan VanderMeulen 2015-01-14 16:32:58 -05:00
commit 006814a010
70 changed files with 554 additions and 496 deletions

View File

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

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="6fa7a4936414ceb4055fd27f7a30e76790f834fb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="6fa7a4936414ceb4055fd27f7a30e76790f834fb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "3f1ebdadd4043e68d2f6ebf295651e0d4cc5c93f",
"revision": "5e3f9375165bca64a0066f51fbd79bd32e380a6b",
"repo_path": "integration/gaia-central"
}

View File

@ -17,11 +17,11 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
<project name="platform_bionic" path="bionic" remote="b2g" revision="1a2a32eda22ef2cd18f57f423a5e7b22a105a6f8"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -17,12 +17,12 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce6c9b72d863c80c842c65acf3b040baf4901a97"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bcc76f93f5659ac1eb8a769167109fd2d7ca4fbd"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -98,6 +98,15 @@ NfcCallback.prototype = {
Ci.nsINfcRequestCallback]),
};
// Should be mapped to the NFCTagType defined in MozNFCTag.webidl.
let TagType = {
TYPE1: "Type1",
TYPE2: "Type2",
TYPE3: "Type3",
TYPE4: "Type4",
MIFARE_CLASSIC: "MIFARE-Classic"
};
/**
* Implementation of NFCTag.
*
@ -120,8 +129,9 @@ function MozNFCTagImpl(window, sessionToken, tagInfo, ndefInfo) {
this.maxNDEFSize = ndefInfo.maxNDEFSize;
this.isReadOnly = ndefInfo.isReadOnly;
this.isFormatable = ndefInfo.isFormatable;
this.canBeMadeReadOnly = this.type == "type1" || this.type == "type2" ||
this.type == "mifare_classic";
this.canBeMadeReadOnly = this.type == TagType.TYPE1 ||
this.type == TagType.TYPE2 ||
this.type == TagType.MIFARE_CLASSIC;
}
}
MozNFCTagImpl.prototype = {

View File

@ -584,28 +584,16 @@ SelectionCarets::SelectWord()
nsPoint ptInFrame = mDownPoint;
nsLayoutUtils::TransformPoint(rootFrame, ptFrame, ptInFrame);
nsIFrame* currFrame = ptFrame;
nsIContent* newFocusContent = nullptr;
while (currFrame) {
int32_t tabIndexUnused = 0;
if (currFrame->IsFocusable(&tabIndexUnused, true)) {
newFocusContent = currFrame->GetContent();
nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(newFocusContent));
if (domElement)
break;
}
currFrame = currFrame->GetParent();
}
// If target frame is focusable, we should move focus to it. If target frame
// isn't focusable, and our previous focused content is editable, we should
// If target frame is editable, we should move focus to targe frame. If
// target frame isn't editable and our focus content is editable, we should
// clear focus.
nsFocusManager* fm = nsFocusManager::GetFocusManager();
nsIContent* editingHost = ptFrame->GetContent()->GetEditingHost();
if (newFocusContent && currFrame) {
nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(newFocusContent));
fm->SetFocus(domElement,0);
if (editingHost) {
nsCOMPtr<nsIDOMElement> elt = do_QueryInterface(editingHost->GetParent());
if (elt) {
fm->SetFocus(elt, 0);
}
if (!nsContentUtils::HasNonEmptyTextContent(
editingHost, nsContentUtils::eRecurseIntoChildren)) {

View File

@ -39,32 +39,17 @@ class SelectionCaretsTest(MarionetteTestCase):
self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)
self._content = self.marionette.find_element(*self._content_selector)
def _first_word_location(self, el):
'''Get the location (x, y) of the first word in el.
Note: this function has a side effect which changes focus to the
target element el.
'''
sel = SelectionManager(el)
# Move caret behind the first character to get the location of the first
# word.
def _long_press_to_select_first_word(self, el, sel):
# Move caret inside the first word.
el.tap()
sel.move_caret_to_front()
sel.move_caret_by_offset(1)
x, y = sel.caret_location()
return sel.caret_location()
def _long_press_to_select(self, el, x, y):
'''Long press the location (x, y) to select a word.
SelectionCarets should appear. On Windows, those spaces after the
word will also be selected.
'''
long_press_without_contextmenu(self.marionette, el, self._long_press_time,
x, y)
# Long press the caret position. Selection carets should appear, and the
# first word will be selected. On Windows, those spaces after the word
# will also be selected.
long_press_without_contextmenu(self.marionette, el, self._long_press_time, x, y)
def _test_long_press_to_select_a_word(self, el, assertFunc):
sel = SelectionManager(el)
@ -74,8 +59,7 @@ class SelectionCaretsTest(MarionetteTestCase):
target_content = words[0]
# Goal: Select the first word.
x, y = self._first_word_location(el)
self._long_press_to_select(el, x, y)
self._long_press_to_select_first_word(el, sel)
# Ignore extra spaces selected after the word.
assertFunc(target_content, sel.selected_content.rstrip())
@ -95,8 +79,7 @@ class SelectionCaretsTest(MarionetteTestCase):
sel.select_all()
(_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
x, y = self._first_word_location(el)
self._long_press_to_select(el, x, y)
self._long_press_to_select_first_word(el, sel)
# Move the right caret to the end of the content.
(caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
@ -108,8 +91,7 @@ class SelectionCaretsTest(MarionetteTestCase):
# Ignore extra spaces at the beginning of the content in comparison.
assertFunc(target_content.lstrip(), sel.selected_content.lstrip())
def _test_minimum_select_one_character(self, el, assertFunc,
x=None, y=None):
def _test_minimum_select_one_character(self, el, assertFunc):
sel = SelectionManager(el)
original_content = sel.content
words = original_content.split()
@ -118,13 +100,7 @@ class SelectionCaretsTest(MarionetteTestCase):
# Goal: Select the first character.
target_content = original_content[0]
if x and y:
# If we got x and y from the arguments, use it as a hint of the
# location of the first word
pass
else:
x, y = self._first_word_location(el)
self._long_press_to_select(el, x, y)
self._long_press_to_select_first_word(el, sel)
# Move the right caret to the position of the left caret.
(caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
@ -132,24 +108,6 @@ class SelectionCaretsTest(MarionetteTestCase):
assertFunc(target_content, sel.selected_content)
def _test_focus_obtained_by_long_press(self, el1, el2):
'''Test the focus could be changed from el1 to el2 by long press.
If the focus is changed to e2 successfully, SelectionCarets should
appear and could be dragged.
'''
# Goal: Tap to focus el1, and then select the first character on
# el2.
# We want to collect the location of the first word in el2 here
# since self._first_word_location() has the side effect which would
# change the focus.
x, y = self._first_word_location(el2)
el1.tap()
self._test_minimum_select_one_character(el2, self.assertEqual,
x=x, y=y)
########################################################################
# <input> test cases with selection carets enabled
########################################################################
@ -165,18 +123,6 @@ class SelectionCaretsTest(MarionetteTestCase):
self.openTestHtml(enabled=True)
self._test_minimum_select_one_character(self._input, self.assertEqual)
def test_input_focus_obtained_by_long_press_from_textarea(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._textarea, self._input)
def test_input_focus_obtained_by_long_press_from_contenteditable(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._contenteditable, self._input)
def test_input_focus_obtained_by_long_press_from_content_non_editable(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._content, self._input)
########################################################################
# <input> test cases with selection carets disabled
########################################################################
@ -203,18 +149,6 @@ class SelectionCaretsTest(MarionetteTestCase):
self.openTestHtml(enabled=True)
self._test_minimum_select_one_character(self._textarea, self.assertEqual)
def test_textarea_focus_obtained_by_long_press_from_input(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._input, self._textarea)
def test_textarea_focus_obtained_by_long_press_from_contenteditable(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._contenteditable, self._textarea)
def test_textarea_focus_obtained_by_long_press_from_content_non_editable(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._content, self._textarea)
########################################################################
# <textarea> test cases with selection carets disabled
########################################################################
@ -267,18 +201,6 @@ class SelectionCaretsTest(MarionetteTestCase):
self.openTestHtml(enabled=True)
self._test_minimum_select_one_character(self._contenteditable, self.assertEqual)
def test_contenteditable_focus_obtained_by_long_press_from_input(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._input, self._contenteditable)
def test_contenteditable_focus_obtained_by_long_press_from_textarea(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._textarea, self._contenteditable)
def test_contenteditable_focus_obtained_by_long_press_from_content_non_editable(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._content, self._contenteditable)
########################################################################
# <div> contenteditable test cases with selection carets disabled
########################################################################
@ -296,16 +218,3 @@ class SelectionCaretsTest(MarionetteTestCase):
def test_content_non_editable_minimum_select_one_character_by_selection(self):
self.openTestHtml(enabled=True)
self._test_minimum_select_one_character(self._content, self.assertEqual)
def test_content_non_editable_focus_obtained_by_long_press_from_input(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._input, self._content)
def test_content_non_editable_focus_obtained_by_long_press_from_textarea(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._textarea, self._content)
def test_content_non_editable_focus_obtained_by_long_press_from_contenteditable(self):
self.openTestHtml(enabled=True)
self._test_focus_obtained_by_long_press(self._contenteditable, self._content)

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 B

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 B

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 998 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

View File

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

View File

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

View File

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

View File

@ -105,7 +105,7 @@ class SelectionManager(object):
last_rect_list = self.selection_rect_list(range_count - 1)
last_list_length = last_rect_list['length']
first_rect, last_rect = first_rect_list['0'], last_rect_list[str(last_list_length - 1)]
origin_x, origin_y = self.element.rect['x'], self.element.rect['y']
origin_x, origin_y = self.element.location['x'], self.element.location['y']
if self.element.get_attribute('dir') == 'rtl': # such as Arabic
start_pos, end_pos = 'right', 'left'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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