Merge m-c to inbound a=merge CLOSED TREE

This commit is contained in:
Wes Kocher 2015-01-15 17:55:54 -08:00
Родитель cc2a95ecff 5d3dbd02ae
Коммит ed4c2aa486
78 изменённых файлов: 2604 добавлений и 407 удалений

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<!-- 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"/>
@ -129,7 +129,7 @@
<!-- Stock Android things -->
<project name="platform/external/icu4c" path="external/icu4c" revision="2bb01561780583cc37bc667f0ea79f48a122d8a2"/>
<!-- dolphin specific things -->
<project name="device/sprd" path="device/sprd" revision="b94ac10f073ed2503505e59f5097d4e286423037"/>
<project name="device/sprd" path="device/sprd" revision="56f0085d1105fd6519d5d3773f91d48b2ad66d5c"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="4e58336019b5cbcfd134caf55b142236cf986618"/>
<project name="platform/frameworks/av" path="frameworks/av" revision="4387fe988e5a1001f29ce05fcfda03ed2d32137b"/>
<project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<!-- 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"/>

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<!-- 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"/>

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

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

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "03effd58034893d2a12907faa6a8a41d3e923b50",
"revision": "1764121939b82f4ded4ca30761df48c3841028b0",
"repo_path": "integration/gaia-central"
}

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
<project name="platform_bionic" path="bionic" remote="b2g" revision="1a2a32eda22ef2cd18f57f423a5e7b22a105a6f8"/>

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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"/>

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

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

@ -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="ebc90190771a945d405f5d36efd813db6f77f965"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
<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="3f4cd30032f7d9002421bdb78860c28c78760888"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>

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

@ -2562,7 +2562,7 @@ let BrowserOnClick = {
let mm = window.messageManager;
mm.addMessageListener("Browser:CertExceptionError", this);
mm.addMessageListener("Browser:SiteBlockedError", this);
mm.addMessageListener("Browser:NetworkError", this);
mm.addMessageListener("Browser:EnableOnlineMode", this);
mm.addMessageListener("Browser:SendSSLErrorReport", this);
mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
},
@ -2571,7 +2571,7 @@ let BrowserOnClick = {
let mm = window.messageManager;
mm.removeMessageListener("Browser:CertExceptionError", this);
mm.removeMessageListener("Browser:SiteBlockedError", this);
mm.removeMessageListener("Browser:NetworkError", this);
mm.removeMessageListener("Browser:EnableOnlineMode", this);
mm.removeMessageListener("Browser:SendSSLErrorReport", this);
mm.removeMessageListener("Browser:SetSSLErrorReportAuto", this);
},
@ -2605,9 +2605,12 @@ let BrowserOnClick = {
this.onAboutBlocked(msg.data.elementId, msg.data.isMalware,
msg.data.isTopFrame, msg.data.location);
break;
case "Browser:NetworkError":
// Reset network state, the error page will refresh on its own.
Services.io.offline = false;
case "Browser:EnableOnlineMode":
if (Services.io.offline) {
// Reset network state and refresh the page.
Services.io.offline = false;
msg.target.reload();
}
break;
case "Browser:SendSSLErrorReport":
this.onSSLErrorReport(msg.target, msg.data.elementId,

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

@ -568,7 +568,8 @@ let ClickEventHandler = {
this.onAboutBlocked(originalTarget, ownerDoc);
return;
} else if (ownerDoc.documentURI.startsWith("about:neterror")) {
this.onAboutNetError(originalTarget, ownerDoc);
this.onAboutNetError(event, ownerDoc.documentURI);
return;
}
let [href, node] = this._hrefAndLinkNodeForClickEvent(event);
@ -635,12 +636,18 @@ let ClickEventHandler = {
});
},
onAboutNetError: function (targetElement, ownerDoc) {
let elmId = targetElement.getAttribute("id");
if (elmId != "errorTryAgain" || !/e=netOffline/.test(ownerDoc.documentURI)) {
onAboutNetError: function (event, documentURI) {
let elmId = event.originalTarget.getAttribute("id");
if (elmId != "errorTryAgain" || !/e=netOffline/.test(documentURI)) {
return;
}
sendSyncMessage("Browser:NetworkError", {});
// browser front end will handle clearing offline mode and refreshing
// the page *if* we're in offline mode now. Otherwise let the error page
// handle the click.
if (Services.io.offline) {
event.preventDefault();
sendAsyncMessage("Browser:EnableOnlineMode", {});
}
},
/**

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

@ -56,12 +56,12 @@ function checkPage() {
// the actual example.com.
Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
Services.obs.addObserver(function observer(aSubject, aTopic) {
ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
"online.");
finish();
}, "network:offline-status-changed", false);
gBrowser.contentDocument.getElementById("errorTryAgain").click();
ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
"online.");
finish();
}
registerCleanupFunction(function() {

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

@ -37,8 +37,8 @@ function runTests() {
yield setLinks("-1");
// Test with enhanced = false
NewTabUtils.allPages.enhanced = false;
yield addNewTabPageTab();
yield customizeNewTabPage("classic");
let {type, enhanced, title} = getData(0);
is(type, "organic", "directory link is organic");
isnot(enhanced, "", "directory link has enhanced image");
@ -47,8 +47,8 @@ function runTests() {
is(getData(1), null, "history link pushed out by directory link");
// Test with enhanced = true
NewTabUtils.allPages.enhanced = true;
yield addNewTabPageTab();
yield customizeNewTabPage("enhanced");
({type, enhanced, title} = getData(0));
is(type, "organic", "directory link is still organic");
isnot(enhanced, "", "directory link still has enhanced image");
@ -67,12 +67,15 @@ function runTests() {
is(getData(1), null, "directory link pushed out by pinned history link");
// Test pinned link with enhanced = false
NewTabUtils.allPages.enhanced = false;
yield addNewTabPageTab();
yield customizeNewTabPage("classic");
({type, enhanced, title} = getData(0));
isnot(type, "enhanced", "history link is not enhanced");
is(enhanced, "", "history link has no enhanced image");
is(title, "site#-1");
is(getData(1), null, "directory link still pushed out by pinned history link");
ok(getContentDocument().getElementById("newtab-intro-what"),
"'What is this page?' link exists");
}

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

@ -684,3 +684,34 @@ function whenSearchInitDone() {
});
return deferred.promise;
}
/**
* Changes the newtab customization option and waits for the panel to open and close
*
* @param {string} aTheme
* Can be any of("blank"|"classic"|"enhanced")
*/
function customizeNewTabPage(aTheme) {
let document = getContentDocument();
let panel = document.getElementById("newtab-customize-panel");
let customizeButton = document.getElementById("newtab-customize-button");
// Attache onShown the listener on panel
panel.addEventListener("popupshown", function onShown() {
panel.removeEventListener("popupshown", onShown);
// Get the element for the specific option and click on it,
// then trigger an escape to close the panel
document.getElementById("newtab-customize-" + aTheme).click();
executeSoon(() => { panel.hidePopup(); });
});
// Attache the listener for panel closing, this will resolve the promise
panel.addEventListener("popuphidden", function onHidden() {
panel.removeEventListener("popuphidden", onHidden);
executeSoon(TestRunner.next);
});
// Click on the customize button to display the panel
customizeButton.click();
}

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

@ -193,21 +193,26 @@ PlayerWidget.prototype = {
});
let titleHTML = "";
// Name
// Name.
if (state.name) {
// Css animations have names
// Css animations have names.
titleHTML += L10N.getStr("player.animationNameLabel");
titleHTML += "<strong>" + state.name + "</strong>";
} else {
// Css transitions don't
// Css transitions don't.
titleHTML += L10N.getStr("player.transitionNameLabel");
}
// Duration and iteration count
// Duration, delay and iteration count.
titleHTML += "<span class='meta-data'>";
titleHTML += L10N.getStr("player.animationDurationLabel");
titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
this.getFormattedTime(state.duration)) + "</strong>";
if (state.delay) {
titleHTML += L10N.getStr("player.animationDelayLabel");
titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
this.getFormattedTime(state.delay)) + "</strong>";
}
titleHTML += L10N.getStr("player.animationIterationCountLabel");
let count = state.iterationCount || L10N.getStr("player.infiniteIterationCount");
titleHTML += "<strong>" + count + "</strong>";
@ -215,7 +220,7 @@ PlayerWidget.prototype = {
titleEl.innerHTML = titleHTML;
// Timeline widget
// Timeline widget.
let timelineEl = createNode({
parent: this.el,
attributes: {
@ -223,7 +228,7 @@ PlayerWidget.prototype = {
}
});
// Playback control buttons container
// Playback control buttons container.
let playbackControlsEl = createNode({
parent: timelineEl,
attributes: {
@ -241,7 +246,7 @@ PlayerWidget.prototype = {
}
});
// Sliders container
// Sliders container.
let slidersContainerEl = createNode({
parent: timelineEl,
attributes: {
@ -249,9 +254,9 @@ PlayerWidget.prototype = {
}
});
let max = state.duration; // Infinite iterations
let max = state.duration; // Infinite iterations.
if (state.iterationCount) {
// Finite iterations
// Finite iterations.
max = state.iterationCount * state.duration;
}
@ -267,6 +272,7 @@ PlayerWidget.prototype = {
"min": "0",
"max": max,
"step": "10",
"value": "0",
// The currentTime isn't settable yet, so disable the timeline slider
"disabled": "true"
}
@ -280,7 +286,7 @@ PlayerWidget.prototype = {
}
});
this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
this.getFormattedTime());
this.getFormattedTime(0));
this.containerEl.appendChild(this.el);
},
@ -290,14 +296,11 @@ PlayerWidget.prototype = {
* @param {Number} time Defaults to the player's currentTime.
* @return {String} The formatted time, e.g. "10.55"
*/
getFormattedTime: function(time=this.player.state.currentTime) {
let str = time/1000 + "";
str = str.split(".");
if (str.length === 1) {
return str[0] + ".00";
} else {
return str[0] + "." + str[1].substring(0, 2);
}
getFormattedTime: function(time) {
return (time/1000).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
},
/**
@ -390,6 +393,12 @@ PlayerWidget.prototype = {
displayTime: function(time) {
let state = this.player.state;
// If the animation is delayed, don't start displaying the time until the
// delay has passed.
if (state.delay) {
time = Math.max(0, time - state.delay);
}
this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
this.getFormattedTime(time));
if (!state.iterationCount && time !== state.duration) {

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

@ -11,8 +11,10 @@ support-files =
[browser_animation_play_pause_button.js]
[browser_animation_playerFronts_are_refreshed.js]
[browser_animation_playerWidgets_destroy.js]
[browser_animation_playerWidgets_meta_data.js]
[browser_animation_refresh_when_active.js]
[browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
[browser_animation_shows_player_on_valid_node.js]
[browser_animation_timeline_animates.js]
[browser_animation_ui_updates_when_animation_changes.js]
[browser_animation_timeline_waits_for_delay.js]
[browser_animation_ui_updates_when_animation_changes.js]

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

@ -0,0 +1,49 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that player widgets show the right player meta-data (name, duration,
// iteration count, delay).
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the simple animated node");
yield selectNode(".animated", inspector);
let titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
ok(titleEl,
"The player widget has a title element, where meta-data should be displayed");
let nameEl = titleEl.querySelector("strong");
ok(nameEl, "The first <strong> tag was retrieved, it should contain the name");
is(nameEl.textContent, "simple-animation", "The animation name is correct");
let metaDataEl = titleEl.querySelector(".meta-data");
ok(metaDataEl, "The meta-data element exists");
let metaDataEls = metaDataEl.querySelectorAll("strong");
is(metaDataEls.length, 2, "2 meta-data elements were found");
is(metaDataEls[0].textContent, "2.00s",
"The first meta-data is the duration, and is correct");
info("Select the node with the delayed animation");
yield selectNode(".delayed", inspector);
titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
nameEl = titleEl.querySelector("strong");
is(nameEl.textContent, "simple-animation", "The animation name is correct");
metaDataEls = titleEl.querySelectorAll(".meta-data strong");
is(metaDataEls.length, 3,
"3 meta-data elements were found for the delayed animation");
is(metaDataEls[0].textContent, "3.00s",
"The first meta-data is the duration, and is correct");
is(metaDataEls[1].textContent, "60.00s",
"The second meta-data is the delay, and is correct");
is(metaDataEls[2].textContent, "10",
"The third meta-data is the iteration count, and is correct");
});

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

@ -0,0 +1,24 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the currentTime timeline doesn't move if the animation is currently
// waiting for an animation-delay.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the delayed animation node");
yield selectNode(".delayed", inspector);
let widget = panel.playerWidgets[0];
let timeline = widget.currentTimeEl;
is(timeline.value, 0, "The timeline is at 0 since the animation hasn't started");
let timeLabel = widget.timeDisplayEl;
is(timeLabel.textContent, "0.00s", "The current time is 0");
});

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

@ -4,8 +4,8 @@
<meta charset="UTF-8">
<style>
.ball {
width: 100px;
height: 100px;
width: 80px;
height: 80px;
border-radius: 50%;
background: #f06;
@ -13,25 +13,33 @@
}
.still {
top: 50px;
left: 50px;
top: 0;
left: 10px;
}
.animated {
top: 200px;
left: 200px;
top: 100px;
left: 10px;
animation: simple-animation 2s infinite alternate;
}
.multi {
top: 100px;
left: 400px;
top: 200px;
left: 10px;
animation: simple-animation 2s infinite alternate,
other-animation 5s infinite alternate;
}
.delayed {
top: 300px;
left: 10px;
background: rebeccapurple;
animation: simple-animation 3s 60s 10;
}
@keyframes simple-animation {
100% {
transform: translateX(300px);
@ -50,5 +58,6 @@
<div class="ball still"></div>
<div class="ball animated"></div>
<div class="ball multi"></div>
<div class="ball delayed"></div>
</body>
</html>

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

@ -30,6 +30,7 @@ support-files =
[browser_canvas-frontend-img-thumbnails-01.js]
[browser_canvas-frontend-img-thumbnails-02.js]
[browser_canvas-frontend-open.js]
skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in e10s mode
[browser_canvas-frontend-record-01.js]
[browser_canvas-frontend-record-02.js]
[browser_canvas-frontend-record-03.js]

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

@ -118,7 +118,7 @@ skip-if = e10s && debug
[browser_dbg_auto-pretty-print-02.js]
skip-if = e10s && debug
[browser_dbg_bfcache.js]
skip-if = e10s # TODO
skip-if = e10s || true # bug 1113935
[browser_dbg_blackboxing-01.js]
skip-if = e10s && debug
[browser_dbg_blackboxing-02.js]

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

@ -12,24 +12,19 @@ const TAB_URL_2 = EXAMPLE_URL + "doc_recursion-stack.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gSources;
function test() {
initDebugger(TAB_URL_1).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
const test = Task.async(function* () {
info("Starting browser_dbg_bfcache.js's `test`.");
testFirstPage()
.then(testLocationChange)
.then(testBack)
.then(testForward)
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
}
([gTab, gDebuggee, gPanel]) = yield initDebugger(TAB_URL_1);
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
yield testFirstPage();
yield testLocationChange();
yield testBack();
yield testForward();
return closeDebuggerAndFinish(gPanel);
});
function testFirstPage() {
info("Testing first page.");
@ -38,33 +33,35 @@ function testFirstPage() {
// this function to return first.
executeSoon(() => gDebuggee.firstCall());
return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(() => {
validateFirstPage();
});
return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
.then(validateFirstPage);
}
function testLocationChange() {
info("Navigating to a different page.");
return navigateActiveTabTo(gPanel, TAB_URL_2, gDebugger.EVENTS.SOURCES_ADDED).then(() => {
validateSecondPage();
});
return navigateActiveTabTo(gPanel,
TAB_URL_2,
gDebugger.EVENTS.SOURCES_ADDED)
.then(validateSecondPage);
}
function testBack() {
info("Going back.");
return navigateActiveTabInHistory(gPanel, "back", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
validateFirstPage();
});
return navigateActiveTabInHistory(gPanel,
"back",
gDebugger.EVENTS.SOURCES_ADDED)
.then(validateFirstPage);
}
function testForward() {
info("Going forward.");
return navigateActiveTabInHistory(gPanel, "forward", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
validateSecondPage();
});
return navigateActiveTabInHistory(gPanel,
"forward",
gDebugger.EVENTS.SOURCES_ADDED)
.then(validateSecondPage);
}
function validateFirstPage() {

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

@ -6,6 +6,7 @@
const {Cu} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
@ -18,6 +19,23 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
/**
* ToolSidebar provides methods to register tabs in the sidebar.
* It's assumed that the sidebar contains a xul:tabbox.
* Typically, you'll want the tabbox parameter to be a XUL tabbox like this:
*
* <tabbox id="inspector-sidebar" handleCtrlTab="false" class="devtools-sidebar-tabs">
* <tabs/>
* <tabpanels flex="1"/>
* </tabbox>
*
* The ToolSidebar API has a method to add new tabs, so the tabs and tabpanels
* nodes can be empty. But they can also already contain items before the
* ToolSidebar is created.
*
* Tabs added through the addTab method are only identified by an ID and a URL
* which is used as the href of an iframe node that is inserted in the newly
* created tabpanel.
* Tabs already present before the ToolSidebar is created may contain anything.
* However, these tabs must have ID attributes if it is required for the various
* methods that accept an ID as argument to work here.
*
* @param {Node} tabbox
* <tabbox> node;
@ -25,38 +43,168 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
* Related ToolPanel instance;
* @param {String} uid
* Unique ID
* @param {Boolean} showTabstripe
* Show the tabs.
* @param {Object} options
* - hideTabstripe: Should the tabs be hidden. Defaults to false
* - showAllTabsMenu: Should a drop-down menu be displayed in case tabs
* become hidden. Defaults to false.
* - disableTelemetry: By default, switching tabs on and off in the sidebar
* will record tool usage in telemetry, pass this option to true to avoid it.
*
* Events raised:
* - new-tab-registered : After a tab has been added via addTab. The tab ID
* is passed with the event. This however, is raised before the tab iframe
* is fully loaded.
* - <tabid>-ready : After the tab iframe has been loaded
* - <tabid>-selected : After tab <tabid> was selected
* - select : Same as above, but for any tab, the ID is passed with the event
* - <tabid>-unselected : After tab <tabid> is unselected
*/
function ToolSidebar(tabbox, panel, uid, showTabstripe=true)
{
function ToolSidebar(tabbox, panel, uid, options={}) {
EventEmitter.decorate(this);
this._tabbox = tabbox;
this._uid = uid;
this._panelDoc = this._tabbox.ownerDocument;
this._toolPanel = panel;
this._options = options;
this._onTabBoxOverflow = this._onTabBoxOverflow.bind(this);
this._onTabBoxUnderflow = this._onTabBoxUnderflow.bind(this);
try {
this._width = Services.prefs.getIntPref("devtools.toolsidebar-width." + this._uid);
} catch(e) {}
this._telemetry = new Telemetry();
if (!options.disableTelemetry) {
this._telemetry = new Telemetry();
}
this._tabbox.tabpanels.addEventListener("select", this, true);
this._tabs = new Map();
if (!showTabstripe) {
// Check for existing tabs in the DOM and add them.
this.addExistingTabs();
if (this._options.hideTabstripe) {
this._tabbox.setAttribute("hidetabs", "true");
}
if (this._options.showAllTabsMenu) {
this.addAllTabsMenu();
}
this._toolPanel.emit("sidebar-created", this);
}
exports.ToolSidebar = ToolSidebar;
ToolSidebar.prototype = {
TAB_ID_PREFIX: "sidebar-tab-",
TABPANEL_ID_PREFIX: "sidebar-panel-",
/**
* Add a "…" button at the end of the tabstripe that toggles a dropdown menu
* containing the list of all tabs if any become hidden due to lack of room.
*
* If the ToolSidebar was created with the "showAllTabsMenu" option set to
* true, this is already done automatically. If not, you may call this
* function at any time to add the menu.
*/
addAllTabsMenu: function() {
if (this._allTabsBtn) {
return;
}
let tabs = this._tabbox.tabs;
// Create a toolbar and insert it first in the tabbox
let allTabsToolbar = this._panelDoc.createElementNS(XULNS, "toolbar");
this._tabbox.insertBefore(allTabsToolbar, tabs);
// Move the tabs inside and make them flex
allTabsToolbar.appendChild(tabs);
tabs.setAttribute("flex", "1");
// Create the dropdown menu next to the tabs
this._allTabsBtn = this._panelDoc.createElementNS(XULNS, "toolbarbutton");
this._allTabsBtn.setAttribute("class", "devtools-sidebar-alltabs");
this._allTabsBtn.setAttribute("type", "menu");
this._allTabsBtn.setAttribute("label", l10n("sidebar.showAllTabs.label"));
this._allTabsBtn.setAttribute("tooltiptext", l10n("sidebar.showAllTabs.tooltip"));
this._allTabsBtn.setAttribute("hidden", "true");
allTabsToolbar.appendChild(this._allTabsBtn);
let menuPopup = this._panelDoc.createElementNS(XULNS, "menupopup");
this._allTabsBtn.appendChild(menuPopup);
// Listening to tabs overflow event to toggle the alltabs button
tabs.addEventListener("overflow", this._onTabBoxOverflow, false);
tabs.addEventListener("underflow", this._onTabBoxUnderflow, false);
// Add menuitems to the alltabs menu if there are already tabs in the
// sidebar
for (let [id, tab] of this._tabs) {
this._addItemToAllTabsMenu(id, tab, tab.hasAttribute("selected"));
}
},
removeAllTabsMenu: function() {
if (!this._allTabsBtn) {
return;
}
let tabs = this._tabbox.tabs;
tabs.removeEventListener("overflow", this._onTabBoxOverflow, false);
tabs.removeEventListener("underflow", this._onTabBoxUnderflow, false);
// Moving back the tabs as a first child of the tabbox
this._tabbox.insertBefore(tabs, this._tabbox.tabpanels);
this._tabbox.querySelector("toolbar").remove();
this._allTabsBtn = null;
},
_onTabBoxOverflow: function() {
this._allTabsBtn.removeAttribute("hidden");
},
_onTabBoxUnderflow: function() {
this._allTabsBtn.setAttribute("hidden", "true");
},
/**
* Add an item in the allTabs menu for a given tab.
*/
_addItemToAllTabsMenu: function(id, tab, selected=false) {
if (!this._allTabsBtn) {
return;
}
let item = this._panelDoc.createElementNS(XULNS, "menuitem");
item.setAttribute("id", "sidebar-alltabs-item-" + id);
item.setAttribute("label", tab.getAttribute("label"));
item.setAttribute("type", "checkbox");
if (selected) {
item.setAttribute("checked", true);
}
// The auto-checking of menuitems in this menu doesn't work, so let's do
// it manually
item.setAttribute("autocheck", false);
this._allTabsBtn.querySelector("menupopup").appendChild(item);
item.addEventListener("click", () => {
this._tabbox.selectedTab = tab;
}, false);
tab.allTabsMenuItem = item;
return item;
},
/**
* Register a tab. A tab is a document.
* The document must have a title, which will be used as the name of the tab.
@ -64,22 +212,31 @@ ToolSidebar.prototype = {
* @param {string} tab uniq id
* @param {string} url
*/
addTab: function ToolSidebar_addTab(id, url, selected=false) {
addTab: function(id, url, selected=false) {
let iframe = this._panelDoc.createElementNS(XULNS, "iframe");
iframe.className = "iframe-" + id;
iframe.setAttribute("flex", "1");
iframe.setAttribute("src", url);
iframe.tooltip = "aHTMLTooltip";
let tab = this._tabbox.tabs.appendItem();
// Creating the tab and adding it to the tabbox
let tab = this._panelDoc.createElementNS(XULNS, "tab");
this._tabbox.tabs.appendChild(tab);
tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
tab.setAttribute("id", "sidebar-tab-" + id);
tab.setAttribute("id", this.TAB_ID_PREFIX + id);
// Add the tab to the allTabs menu if exists
let allTabsItem = this._addItemToAllTabsMenu(id, tab, selected);
let onIFrameLoaded = (event) => {
let doc = event.target;
let win = doc.defaultView;
tab.setAttribute("label", doc.title);
if (allTabsItem) {
allTabsItem.setAttribute("label", doc.title);
}
iframe.removeEventListener("load", onIFrameLoaded, true);
if ("setPanel" in win) {
win.setPanel(this._toolPanel, iframe);
@ -90,7 +247,7 @@ ToolSidebar.prototype = {
iframe.addEventListener("load", onIFrameLoaded, true);
let tabpanel = this._panelDoc.createElementNS(XULNS, "tabpanel");
tabpanel.setAttribute("id", "sidebar-panel-" + id);
tabpanel.setAttribute("id", this.TABPANEL_ID_PREFIX + id);
tabpanel.appendChild(iframe);
this._tabbox.tabpanels.appendChild(tabpanel);
@ -99,7 +256,7 @@ ToolSidebar.prototype = {
tabpanel.appendChild(this._tooltip);
this._tooltip.page = true;
tab.linkedPanel = "sidebar-panel-" + id;
tab.linkedPanel = this.TABPANEL_ID_PREFIX + id;
// We store the index of this tab.
this._tabs.set(id, tab);
@ -116,37 +273,78 @@ ToolSidebar.prototype = {
this.emit("new-tab-registered", id);
},
untitledTabsIndex: 0,
/**
* Search for existing tabs in the markup that aren't know yet and add them.
*/
addExistingTabs: function() {
let knownTabs = [...this._tabs.values()];
for (let tab of this._tabbox.tabs.querySelectorAll("tab")) {
if (knownTabs.indexOf(tab) !== -1) {
continue;
}
// Find an ID for this unknown tab
let id = tab.getAttribute("id") || "untitled-tab-" + (this.untitledTabsIndex++);
// Register the tab
this._tabs.set(id, tab);
this.emit("new-tab-registered", id);
}
},
/**
* Remove an existing tab.
* @param {String} tabId The ID of the tab that was used to register it, or
* the tab id attribute value if the tab existed before the sidebar got created.
* @param {String} tabPanelId Optional. If provided, this ID will be used
* instead of the tabId to retrieve and remove the corresponding <tabpanel>
*/
removeTab: Task.async(function*(id) {
let tab = this._tabbox.tabs.querySelector("tab#sidebar-tab-" + id);
removeTab: Task.async(function*(tabId, tabPanelId) {
// Remove the tab if it can be found
let tab = this.getTab(tabId);
if (!tab) {
return;
}
let win = this.getWindowForTab(id);
if ("destroy" in win) {
let win = this.getWindowForTab(tabId);
if (win && ("destroy" in win)) {
yield win.destroy();
}
tab.remove();
let panel = this.getTab(id);
// Also remove the tabpanel
let panel = this.getTabPanel(tabPanelId || tabId);
if (panel) {
panel.remove();
}
this._tabs.delete(id);
this.emit("tab-unregistered", id);
this._tabs.delete(tabId);
this.emit("tab-unregistered", tabId);
}),
/**
* Show or hide a specific tab
*/
toggleTab: function(id, isVisible) {
let tab = this.getTab(id);
if (!tab) {
return;
}
tab.hidden = !isVisible;
if (this._allTabsBtn) {
this._allTabsBtn.querySelector("#sidebar-alltabs-item-" + id).hidden = !isVisible;
}
},
/**
* Select a specific tab.
*/
select: function ToolSidebar_select(id) {
let tab = this._tabs.get(id);
select: function(id) {
let tab = this.getTab(id);
if (tab) {
this._tabbox.selectedTab = tab;
}
@ -155,7 +353,7 @@ ToolSidebar.prototype = {
/**
* Return the id of the selected tab.
*/
getCurrentTabID: function ToolSidebar_getCurrentTabID() {
getCurrentTabID: function() {
let currentID = null;
for (let [id, tab] of this._tabs) {
if (this._tabbox.tabs.selectedItem == tab) {
@ -167,42 +365,75 @@ ToolSidebar.prototype = {
},
/**
* Returns the requested tab based on the id.
*
* @param String id
* unique id of the requested tab.
* Returns the requested tab panel based on the id.
* @param {String} id
* @return {DOMNode}
*/
getTab: function ToolSidebar_getTab(id) {
return this._tabbox.tabpanels.querySelector("#sidebar-panel-" + id);
getTabPanel: function(id) {
// Search with and without the ID prefix as there might have been existing
// tabpanels by the time the sidebar got created
return this._tabbox.tabpanels.querySelector("#" + this.TABPANEL_ID_PREFIX + id + ", #" + id);
},
/**
* Return the tab based on the provided id, if one was registered with this id.
* @param {String} id
* @return {DOMNode}
*/
getTab: function(id) {
return this._tabs.get(id);
},
/**
* Event handler.
*/
handleEvent: function ToolSidebar_eventHandler(event) {
if (event.type == "select") {
if (this._currentTool == this.getCurrentTabID()) {
// Tool hasn't changed.
return;
}
handleEvent: function(event) {
if (event.type !== "select" || this._destroyed) {
return;
}
let previousTool = this._currentTool;
this._currentTool = this.getCurrentTabID();
if (previousTool) {
if (this._currentTool == this.getCurrentTabID()) {
// Tool hasn't changed.
return;
}
let previousTool = this._currentTool;
this._currentTool = this.getCurrentTabID();
if (previousTool) {
if (this._telemetry) {
this._telemetry.toolClosed(previousTool);
this.emit(previousTool + "-unselected");
}
this.emit(previousTool + "-unselected");
}
if (this._telemetry) {
this._telemetry.toolOpened(this._currentTool);
this.emit(this._currentTool + "-selected");
this.emit("select", this._currentTool);
}
this.emit(this._currentTool + "-selected");
this.emit("select", this._currentTool);
// Handlers for "select"/"...-selected"/"...-unselected" events might have
// destroyed the sidebar in the meantime.
if (this._destroyed) {
return;
}
// Handle menuitem selection if the allTabsMenu is there by unchecking all
// items except the selected one.
let tab = this._tabbox.selectedTab;
if (tab.allTabsMenuItem) {
for (let otherItem of this._allTabsBtn.querySelectorAll("menuitem")) {
otherItem.removeAttribute("checked");
}
tab.allTabsMenuItem.setAttribute("checked", true);
}
},
/**
* Toggle sidebar's visibility state.
*/
toggle: function ToolSidebar_toggle() {
toggle: function() {
if (this._tabbox.hasAttribute("hidden")) {
this.show();
} else {
@ -213,7 +444,7 @@ ToolSidebar.prototype = {
/**
* Show the sidebar.
*/
show: function ToolSidebar_show() {
show: function() {
if (this._width) {
this._tabbox.width = this._width;
}
@ -225,7 +456,7 @@ ToolSidebar.prototype = {
/**
* Show the sidebar.
*/
hide: function ToolSidebar_hide() {
hide: function() {
Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width);
this._tabbox.setAttribute("hidden", "true");
@ -235,12 +466,16 @@ ToolSidebar.prototype = {
/**
* Return the window containing the tab content.
*/
getWindowForTab: function ToolSidebar_getWindowForTab(id) {
getWindowForTab: function(id) {
if (!this._tabs.has(id)) {
return null;
}
let panel = this._panelDoc.getElementById(this._tabs.get(id).linkedPanel);
// Get the tabpanel and make sure it contains an iframe
let panel = this.getTabPanel(id);
if (!panel || !panel.firstChild || !panel.firstChild.contentWindow) {
return;
}
return panel.firstChild.contentWindow;
},
@ -249,12 +484,16 @@ ToolSidebar.prototype = {
*/
destroy: Task.async(function*() {
if (this._destroyed) {
return promise.resolve(null);
return;
}
this._destroyed = true;
Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width);
if (this._allTabsBtn) {
this.removeAllTabsMenu();
}
this._tabbox.tabpanels.removeEventListener("select", this, true);
// Note that we check for the existence of this._tabbox.tabpanels at each
@ -263,7 +502,7 @@ ToolSidebar.prototype = {
while (this._tabbox.tabpanels && this._tabbox.tabpanels.hasChildNodes()) {
let panel = this._tabbox.tabpanels.firstChild;
let win = panel.firstChild.contentWindow;
if ("destroy" in win) {
if (win && ("destroy" in win)) {
yield win.destroy();
}
panel.remove();
@ -273,7 +512,7 @@ ToolSidebar.prototype = {
this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
}
if (this._currentTool) {
if (this._currentTool && this._telemetry) {
this._telemetry.toolClosed(this._currentTool);
}
@ -283,7 +522,21 @@ ToolSidebar.prototype = {
this._tabbox = null;
this._panelDoc = null;
this._toolPanel = null;
return promise.resolve(null);
}),
})
}
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
let l10n = function(aName, ...aArgs) {
try {
if (aArgs.length == 0) {
return bundle.GetStringFromName(aName);
} else {
return bundle.formatStringFromName(aName, aArgs, aArgs.length);
}
} catch (ex) {
Services.console.logStringMessage("Error reading '" + aName + "'");
}
};
return l10n;
});

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

@ -4,6 +4,7 @@ support-files =
browser_toolbox_options_disable_js.html
browser_toolbox_options_disable_js_iframe.html
browser_toolbox_options_disable_cache.sjs
browser_toolbox_sidebar_tool.xul
head.js
helper_disable_cache.js
doc_theme.css
@ -39,6 +40,8 @@ skip-if = e10s # Bug 1070837 - devtools/framework/toolbox.js |doc| getter not e1
skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
[browser_toolbox_sidebar.js]
[browser_toolbox_sidebar_events.js]
[browser_toolbox_sidebar_existing_tabs.js]
[browser_toolbox_sidebar_overflow_menu.js]
[browser_toolbox_tabsswitch_shortcuts.js]
[browser_toolbox_tool_ready.js]
[browser_toolbox_tool_remote_reopen.js]

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

@ -3,8 +3,7 @@
// Tests that disabling JavaScript for a tab works as it should.
const TEST_URI = "http://example.com/browser/browser/devtools/framework/" +
"test/browser_toolbox_options_disable_js.html";
const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_js.html";
let doc;
let toolbox;

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

@ -111,7 +111,7 @@ function test() {
is(label, 4, "Found the right amount of tabs.");
is(panel.sidebar._tabbox.selectedPanel, panels[0], "First tab is selected");
ok(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct");
is(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct");
panel.sidebar.once("tab1-unselected", function() {
ok(true, "received 'unselected' event");
@ -154,6 +154,7 @@ function test() {
panel.sidebar = new ToolSidebar(tabbox, panel, "testbug865688", true);
panel.sidebar.show();
is(panel.panelDoc.getElementById("sidebar").width, 420, "Width restored")
finishUp(panel);
});
}

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

@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the sidebar widget auto-registers existing tabs.
const Cu = Components.utils;
const {ToolSidebar} = devtools.require("devtools/framework/sidebar");
const testToolURL = "data:text/xml;charset=utf8,<?xml version='1.0'?>" +
"<?xml-stylesheet href='chrome://browser/skin/devtools/common.css' type='text/css'?>" +
"<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
"<hbox flex='1'><description flex='1'>test tool</description>" +
"<splitter class='devtools-side-splitter'/>" +
"<tabbox flex='1' id='sidebar' class='devtools-sidebar-tabs'>" +
"<tabs><tab id='tab1' label='tab 1'></tab><tab id='tab2' label='tab 2'></tab></tabs>" +
"<tabpanels flex='1'><tabpanel id='tabpanel1'>tab 1</tabpanel><tabpanel id='tabpanel2'>tab 2</tabpanel></tabpanels>" +
"</tabbox></hbox></window>";
const testToolDefinition = {
id: "testTool",
url: testToolURL,
label: "Test Tool",
isTargetSupported: () => true,
build: (iframeWindow, toolbox) => {
return promise.resolve({
target: toolbox.target,
toolbox: toolbox,
isReady: true,
destroy: () => {},
panelDoc: iframeWindow.document,
});
}
};
add_task(function*() {
let tab = yield addTab("about:blank");
let target = TargetFactory.forTab(tab);
gDevTools.registerTool(testToolDefinition);
let toolbox = yield gDevTools.showToolbox(target, testToolDefinition.id);
let toolPanel = toolbox.getPanel(testToolDefinition.id);
let tabbox = toolPanel.panelDoc.getElementById("sidebar");
info("Creating the sidebar widget");
let sidebar = new ToolSidebar(tabbox, toolPanel, "bug1101569");
info("Checking that existing tabs have been registered");
ok(sidebar.getTab("tab1"), "Existing tab 1 was found");
ok(sidebar.getTab("tab2"), "Existing tab 2 was found");
ok(sidebar.getTabPanel("tabpanel1"), "Existing tabpanel 1 was found");
ok(sidebar.getTabPanel("tabpanel2"), "Existing tabpanel 2 was found");
info("Checking that the sidebar API works with existing tabs");
sidebar.select("tab2");
is(tabbox.selectedTab, tabbox.querySelector("#tab2"),
"Existing tabs can be selected");
sidebar.select("tab1");
is(tabbox.selectedTab, tabbox.querySelector("#tab1"),
"Existing tabs can be selected");
is(sidebar.getCurrentTabID(), "tab1", "getCurrentTabID returns the expected id");
info("Removing a tab");
sidebar.removeTab("tab2", "tabpanel2");
ok(!sidebar.getTab("tab2"), "Tab 2 was removed correctly");
ok(!sidebar.getTabPanel("tabpanel2"), "Tabpanel 2 was removed correctly");
sidebar.destroy();
gDevTools.unregisterTool(testToolDefinition.id);
gBrowser.removeCurrentTab();
});

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

@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the sidebar widget correctly displays the "all tabs..." button
// when the tabs overflow.
const {ToolSidebar} = devtools.require("devtools/framework/sidebar");
const testToolDefinition = {
id: "testTool",
url: CHROME_URL_ROOT + "browser_toolbox_sidebar_tool.xul",
label: "Test Tool",
isTargetSupported: () => true,
build: (iframeWindow, toolbox) => {
return {
target: toolbox.target,
toolbox: toolbox,
isReady: true,
destroy: () => {},
panelDoc: iframeWindow.document,
};
}
};
add_task(function*() {
let tab = yield addTab("about:blank");
let target = TargetFactory.forTab(tab);
gDevTools.registerTool(testToolDefinition);
let toolbox = yield gDevTools.showToolbox(target, testToolDefinition.id);
let toolPanel = toolbox.getPanel(testToolDefinition.id);
let tabbox = toolPanel.panelDoc.getElementById("sidebar");
info("Creating the sidebar widget");
let sidebar = new ToolSidebar(tabbox, toolPanel, "bug1101569", {
showAllTabsMenu: true
});
let allTabsMenu = toolPanel.panelDoc.querySelector(".devtools-sidebar-alltabs");
ok(allTabsMenu, "The all-tabs menu is available");
is(allTabsMenu.getAttribute("hidden"), "true", "The menu is hidden for now");
info("Adding 10 tabs to the sidebar widget");
for (let nb = 0; nb < 10; nb ++) {
let url = `data:text/html;charset=utf8,<title>tab ${nb}</title><p>Test tab ${nb}</p>`;
sidebar.addTab("tab" + nb, url, nb === 0);
}
info("Fake an overflow event so that the all-tabs menu is visible");
sidebar._onTabBoxOverflow();
ok(!allTabsMenu.hasAttribute("hidden"), "The all-tabs menu is now shown");
info("Select each tab, one by one");
for (let nb = 0; nb < 10; nb ++) {
let id = "tab" + nb;
info("Found tab item nb " + nb);
let item = allTabsMenu.querySelector("#sidebar-alltabs-item-" + id);
info("Click on the tab");
EventUtils.sendMouseEvent({type: "click"}, item, toolPanel.panelDoc.defaultView);
is(tabbox.selectedTab.id, "sidebar-tab-" + id,
"The selected tab is now nb " + nb);
}
info("Fake an underflow event so that the all-tabs menu gets hidden");
sidebar._onTabBoxUnderflow();
is(allTabsMenu.getAttribute("hidden"), "true", "The all-tabs menu is hidden");
yield sidebar.destroy();
gDevTools.unregisterTool(testToolDefinition.id);
gBrowser.removeCurrentTab();
});

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

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!-- 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/. -->
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
<box flex="1" class="devtools-responsive-container theme-body">
<vbox flex="1" class="devtools-main-content" id="content">test</vbox>
<splitter class="devtools-side-splitter"/>
<tabbox flex="1" id="sidebar" class="devtools-sidebar-tabs">
<tabs/>
<tabpanels flex="1"/>
</tabbox>
</box>
</window>

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

@ -8,6 +8,9 @@ const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {})
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const URL_ROOT = "http://example.com/browser/browser/devtools/framework/test/";
const CHROME_URL_ROOT = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
let TargetFactory = devtools.TargetFactory;
// All test are asynchronous

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

@ -4,8 +4,7 @@
"use strict";
// Common code shared by browser_toolbox_options_disable_cache-*.js
const TEST_URI = "http://mochi.test:8888/browser/browser/devtools/framework/" +
"test/browser_toolbox_options_disable_cache.sjs";
const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_cache.sjs";
let tabs = [
{
title: "Tab 0",

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

@ -347,8 +347,6 @@ InspectorPanel.prototype = {
"animationinspector" == defaultTab);
}
let ruleViewTab = this.sidebar.getTab("ruleview");
this.sidebar.show();
},

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

@ -2129,7 +2129,7 @@ function ElementEditor(aContainer, aNode) {
}
// Make the new attribute space editable.
editableField({
this.newAttr.editMode = editableField({
element: this.newAttr,
trigger: "dblclick",
stopOnReturn: true,
@ -2235,7 +2235,7 @@ ElementEditor.prototype = {
}
// Make the attribute editable.
editableField({
attr.editMode = editableField({
element: inner,
trigger: "dblclick",
stopOnReturn: true,
@ -2257,7 +2257,7 @@ ElementEditor.prototype = {
aEditor.input.select();
}
},
done: (aVal, aCommit) => {
done: (aVal, aCommit, direction) => {
if (!aCommit || aVal === initial) {
return;
}
@ -2269,6 +2269,7 @@ ElementEditor.prototype = {
// parsed out of the input element. Restore original attribute if
// parsing fails.
try {
this.refocusOnEdit(aAttr.name, attr, direction);
this._saveAttribute(aAttr.name, undoMods);
doMods.removeAttribute(aAttr.name);
this._applyAttributes(aVal, attr, doMods, undoMods);
@ -2347,6 +2348,97 @@ ElementEditor.prototype = {
}
},
/**
* Listen to mutations, and when the attribute list is regenerated
* try to focus on the attribute after the one that's being edited now.
* If the attribute order changes, go to the beginning of the attribute list.
*/
refocusOnEdit: function(attrName, attrNode, direction) {
// Only allow one refocus on attribute change at a time, so when there's
// more than 1 request in parallel, the last one wins.
if (this._editedAttributeObserver) {
this.markup._inspector.off("markupmutation", this._editedAttributeObserver);
this._editedAttributeObserver = null;
}
let container = this.markup.getContainer(this.node);
let activeAttrs = [...this.attrList.childNodes].filter(el => el.style.display != "none");
let attributeIndex = activeAttrs.indexOf(attrNode);
let onMutations = this._editedAttributeObserver = (e, mutations) => {
let isDeletedAttribute = false;
let isNewAttribute = false;
for (let mutation of mutations) {
let inContainer = this.markup.getContainer(mutation.target) === container;
if (!inContainer) {
continue;
}
let isOriginalAttribute = mutation.attributeName === attrName;
isDeletedAttribute = isDeletedAttribute || isOriginalAttribute && mutation.newValue === null;
isNewAttribute = isNewAttribute || mutation.attributeName !== attrName;
}
let isModifiedOrder = isDeletedAttribute && isNewAttribute;
this._editedAttributeObserver = null;
// "Deleted" attributes are merely hidden, so filter them out.
let visibleAttrs = [...this.attrList.childNodes].filter(el => el.style.display != "none");
let activeEditor;
if (visibleAttrs.length > 0) {
if (!direction) {
// No direction was given; stay on current attribute.
activeEditor = visibleAttrs[attributeIndex];
} else if (isModifiedOrder) {
// The attribute was renamed, reordering the existing attributes.
// So let's go to the beginning of the attribute list for consistency.
activeEditor = visibleAttrs[0];
} else {
let newAttributeIndex;
if (isDeletedAttribute) {
newAttributeIndex = attributeIndex;
} else {
if (direction == Ci.nsIFocusManager.MOVEFOCUS_FORWARD) {
newAttributeIndex = attributeIndex + 1;
} else if (direction == Ci.nsIFocusManager.MOVEFOCUS_BACKWARD) {
newAttributeIndex = attributeIndex - 1;
}
}
// The number of attributes changed (deleted), or we moved through the array
// so check we're still within bounds.
if (newAttributeIndex >= 0 && newAttributeIndex <= visibleAttrs.length - 1) {
activeEditor = visibleAttrs[newAttributeIndex];
}
}
}
// Either we have no attributes left,
// or we just edited the last attribute and want to move on.
if (!activeEditor) {
activeEditor = this.newAttr;
}
// Refocus was triggered by tab or shift-tab.
// Continue in edit mode.
if (direction) {
activeEditor.editMode();
} else {
// Refocus was triggered by enter.
// Exit edit mode (but restore focus).
let editable = activeEditor === this.newAttr ? activeEditor : activeEditor.querySelector(".editable");
editable.focus();
}
this.markup.emit("refocusedonedit");
};
// Start listening for mutations until we find an attributes change
// that modifies this attribute.
this.markup._inspector.once("markupmutation", onMutations);
},
/**
* Called when the tag name editor has is done editing.
*/

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

@ -63,6 +63,7 @@ skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
[browser_markupview_html_edit_02.js]
[browser_markupview_html_edit_03.js]
[browser_markupview_image_tooltip.js]
[browser_markupview_keybindings_01.js]
[browser_markupview_mutation_01.js]
[browser_markupview_mutation_02.js]
[browser_markupview_navigation.js]
@ -83,6 +84,7 @@ skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
[browser_markupview_tag_edit_09.js]
[browser_markupview_tag_edit_10.js]
[browser_markupview_tag_edit_11.js]
[browser_markupview_tag_edit_12.js]
[browser_markupview_textcontent_edit_01.js]
[browser_markupview_toggle_01.js]
[browser_markupview_toggle_02.js]

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

@ -0,0 +1,47 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests tabbing through attributes on a node
const TEST_URL = "data:text/html;charset=utf8,<div a b c d e id='test'></div>";
add_task(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
info("Focusing the tag editor of the test element");
let {editor} = yield getContainerForSelector("div", inspector);
editor.tag.focus();
info("Pressing tab and expecting to focus the ID attribute, always first");
EventUtils.sendKey("tab", inspector.panelWin);
checkFocusedAttribute("id");
info("Hit enter to turn the attribute to edit mode");
EventUtils.sendKey("return", inspector.panelWin);
checkFocusedAttribute("id", true);
// Check the order of the other attributes in the DOM to the check they appear
// correctly in the markup-view
let attributes = [...getNode("div").attributes].filter(attr => attr.name !== "id");
info("Tabbing forward through attributes in edit mode");
for (let {name} of attributes) {
collapseSelectionAndTab(inspector);
checkFocusedAttribute(name, true);
}
info("Tabbing backward through attributes in edit mode");
// Just reverse the attributes other than id and remove the first one since
// it's already focused now.
let reverseAttributes = attributes.reverse();
reverseAttributes.shift();
for (let {name} of reverseAttributes) {
collapseSelectionAndShiftTab(inspector);
checkFocusedAttribute(name, true);
}
});

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

@ -0,0 +1,115 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that focus position is correct when tabbing through and editing
// attributes.
const TEST_URL = "data:text/html;charset=utf8,<div id='attr' c='3' b='2' a='1'></div><div id='delattr' last='1' tobeinvalid='2'></div>";
add_task(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
yield testAttributeEditing(inspector);
yield testAttributeDeletion(inspector);
});
function* testAttributeEditing(inspector) {
info("Testing focus position after attribute editing");
// Modifying attributes reorders them in the internal representation to move
// the modified attribute to the end. breadcrumbs.js will update attributes
// to match original order if you selectNode before modifying attributes.
// So, hacky workaround for consistency with manual testing.
// Should be removed after Bug 1093593.
yield selectNode("#attr", inspector);
info("Setting the first non-id attribute in edit mode");
yield activateFirstAttribute("#attr", inspector); // focuses id
collapseSelectionAndTab(inspector); // focuses the first attr after id
// Detect the attributes order from the DOM, instead of assuming an order in
// the test, because the NamedNodeMap returned by element.attributes doesn't
// guaranty any specific order.
// Filter out the id attribute as the markup-view places it first anyway.
let attrs = getNodeAttributesOtherThanId("#attr");
info("Editing this attribute, keeping the same name, and tabbing to the next");
yield editAttributeAndTab(attrs[0].name + '="99"', inspector);
checkFocusedAttribute(attrs[1].name, true);
info("Editing the new focused attribute, keeping the name, and tabbing to the previous");
yield editAttributeAndTab(attrs[1].name + '="99"', inspector, true);
checkFocusedAttribute(attrs[0].name, true);
info("Editing attribute name, changes attribute order");
yield editAttributeAndTab("d='4'", inspector);
checkFocusedAttribute("id", true);
// Escape of the currently focused field for the next test
EventUtils.sendKey("escape", inspector.panelWin);
}
function* testAttributeDeletion(inspector) {
info("Testing focus position after attribute deletion");
// Modifying attributes reorders them in the internal representation to move
// the modified attribute to the end. breadcrumbs.js will update attributes
// to match original order if you selectNode before modifying attributes.
// So, hacky workaround for consistency with manual testing.
// Should be removed after Bug 1093593.
yield selectNode("#delattr", inspector);
info("Setting the first non-id attribute in edit mode");
yield activateFirstAttribute("#delattr", inspector); // focuses id
collapseSelectionAndTab(inspector); // focuses the first attr after id
// Detect the attributes order from the DOM, instead of assuming an order in
// the test, because the NamedNodeMap returned by element.attributes doesn't
// guaranty any specific order.
// Filter out the id attribute as the markup-view places it first anyway.
let attrs = getNodeAttributesOtherThanId("#delattr");
info("Entering an invalid attribute to delete the attribute");
yield editAttributeAndTab('"', inspector);
checkFocusedAttribute(attrs[1].name, true);
info("Deleting the last attribute");
yield editAttributeAndTab(" ", inspector);
// Check we're on the newattr element
let focusedAttr = Services.focus.focusedElement;
ok(focusedAttr.classList.contains("styleinspector-propertyeditor"), "in newattr");
is(focusedAttr.tagName, "input", "newattr is active");
}
function* editAttributeAndTab(newValue, inspector, goPrevious) {
var onEditMutation = inspector.markup.once("refocusedonedit");
inspector.markup.doc.activeElement.value = newValue;
if (goPrevious) {
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
inspector.panelWin);
} else {
EventUtils.sendKey("tab", inspector.panelWin);
}
yield onEditMutation;
}
/**
* Given a markup container, focus and turn in edit mode its first attribute
* field.
*/
function* activateFirstAttribute(container, inspector) {
let {editor} = yield getContainerForSelector(container, inspector);
editor.tag.focus();
// Go to "id" attribute and trigger edit mode.
EventUtils.sendKey("tab", inspector.panelWin);
EventUtils.sendKey("return", inspector.panelWin);
}
function getNodeAttributesOtherThanId(selector) {
return [...getNode(selector).attributes].filter(attr => attr.name !== "id");
}

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

@ -526,3 +526,38 @@ function promiseNextTick() {
executeSoon(deferred.resolve);
return deferred.promise;
}
/**
* Collapses the current text selection in an input field and tabs to the next
* field.
*/
function collapseSelectionAndTab(inspector) {
EventUtils.sendKey("tab", inspector.panelWin); // collapse selection and move caret to end
EventUtils.sendKey("tab", inspector.panelWin); // next element
}
/**
* Collapses the current text selection in an input field and tabs to the
* previous field.
*/
function collapseSelectionAndShiftTab(inspector) {
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
inspector.panelWin); // collapse selection and move caret to end
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
inspector.panelWin); // previous element
}
/**
* Check that the current focused element is an attribute element in the markup
* view.
* @param {String} attrName The attribute name expected to be found
* @param {Boolean} editMode Whether or not the attribute should be in edit mode
*/
function checkFocusedAttribute(attrName, editMode) {
let focusedAttr = Services.focus.focusedElement;
is(focusedAttr ? focusedAttr.parentNode.dataset.attr : undefined,
attrName, attrName + " attribute editor is currently focused.");
is(focusedAttr ? focusedAttr.tagName : undefined,
editMode ? "input": "span",
editMode ? attrName + " is in edit mode" : attrName + " is not in edit mode");
}

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

@ -11,7 +11,7 @@
*
* editableField({
* element: spanToEdit,
* done: function(value, commit) {
* done: function(value, commit, direction) {
* if (commit) {
* spanToEdit.textContent = value;
* }
@ -62,9 +62,11 @@ Cu.import("resource://gre/modules/devtools/event-emitter.js");
* with the current value of the text input.
* {function} done:
* Called when input is committed or blurred. Called with
* current value and a boolean telling the caller whether to
* commit the change. This function is called before the editor
* has been torn down.
* current value, a boolean telling the caller whether to
* commit the change, and the direction of the next element to be
* selected. Direction may be one of nsIFocusManager.MOVEFOCUS_FORWARD,
* nsIFocusManager.MOVEFOCUS_BACKWARD, or null (no movement).
* This function is called before the editor has been torn down.
* {function} destroy:
* Called when the editor is destroyed and has been torn down.
* {string} advanceChars:
@ -103,6 +105,7 @@ exports.editableField = editableField;
* defaults to "click"
* @param {function} aCallback
* Called when the editor is activated.
* @return {function} function which calls aCallback
*/
function editableItem(aOptions, aCallback)
{
@ -148,6 +151,13 @@ function editableItem(aOptions, aCallback)
// Mark the element editable field for tab
// navigation while editing.
element._editable = true;
// Save the trigger type so we can dispatch this later
element._trigger = trigger;
return function turnOnEditMode() {
aCallback(element);
}
}
exports.editableItem = this.editableItem;
@ -769,7 +779,7 @@ InplaceEditor.prototype = {
/**
* Call the client's done handler and clear out.
*/
_apply: function InplaceEditor_apply(aEvent)
_apply: function InplaceEditor_apply(aEvent, direction)
{
if (this._applied) {
return;
@ -779,7 +789,7 @@ InplaceEditor.prototype = {
if (this.done) {
let val = this.input.value.trim();
return this.done(this.cancelled ? this.initial : val, !this.cancelled);
return this.done(this.cancelled ? this.initial : val, !this.cancelled, direction);
}
return null;
@ -945,7 +955,7 @@ InplaceEditor.prototype = {
}
}
this._apply();
this._apply(aEvent, direction);
// Close the popup if open
if (this.popup && this.popup.isOpen) {
@ -958,9 +968,11 @@ InplaceEditor.prototype = {
let next = moveFocus(this.doc.defaultView, direction);
// If the next node to be focused has been tagged as an editable
// node, send it a click event to trigger
// node, trigger editing using the configured event
if (next && next.ownerDocument === this.doc && next._editable) {
next.click();
let e = this.doc.createEvent('Event');
e.initEvent(next._trigger, true, true);
next.dispatchEvent(e);
}
}

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

@ -441,7 +441,7 @@ StyleSheetEditor.prototype = {
* Toggled the disabled state of the underlying stylesheet.
*/
toggleDisabled: function() {
this.styleSheet.toggleDisabled();
this.styleSheet.toggleDisabled().then(null, Cu.reportError);
},
/**
@ -483,7 +483,8 @@ StyleSheetEditor.prototype = {
let transitionsEnabled = Services.prefs.getBoolPref(TRANSITION_PREF);
this.styleSheet.update(this._state.text, transitionsEnabled);
this.styleSheet.update(this._state.text, transitionsEnabled)
.then(null, Cu.reportError);
},
/**

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

@ -54,18 +54,21 @@ add_task(function*() {
yield TimelineController.toggleRecording();
ok(true, "Recording has ended.");
is(TimelineController.getMarkers().length, 0,
"There are no markers available.");
// TODO: Re-enable this assertion as part of bug 1120830
// is(TimelineController.getMarkers().length, 0,
// "There are no markers available.");
isnot(TimelineController.getMemory().length, 0,
"There are some memory measurements available.");
is(TimelineView.markersOverview.selectionEnabled, true,
"The selection should now be enabled for the markers overview.");
is(TimelineView.markersOverview.hasSelection(), false,
"The markers overview should not have a selection after recording.");
// TODO: Re-enable this assertion as part of bug 1120830
// is(TimelineView.markersOverview.hasSelection(), false,
// "The markers overview should not have a selection after recording.");
is(TimelineView.memoryOverview.selectionEnabled, true,
"The selection should now be enabled for the memory overview.");
is(TimelineView.memoryOverview.hasSelection(), false,
"The memory overview should not have a selection after recording.");
// TODO: Re-enable this assertion as part of bug 1120830
// is(TimelineView.memoryOverview.hasSelection(), false,
// "The memory overview should not have a selection after recording.");
});

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

@ -3555,8 +3555,8 @@ JSTerm.prototype = {
deferred.resolve(window);
};
let tab = this.sidebar.getTab("variablesview");
if (tab) {
let tabPanel = this.sidebar.getTabPanel("variablesview");
if (tabPanel) {
if (this.sidebar.getCurrentTabID() == "variablesview") {
onTabReady();
}

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

@ -589,9 +589,6 @@ Section "-Application" APP_IDX
${EndUnless}
${EndIf}
; Add the Firewall entries during install
Call AddFirewallEntries
!ifdef MOZ_MAINTENANCE_SERVICE
${If} $TmpVal == "HKLM"
; Add the registry keys for allowed certificates.
@ -627,6 +624,9 @@ Section "-InstallEndCleanup"
${GetShortcutsLogPath} $0
WriteIniStr "$0" "TASKBAR" "Migrated" "true"
; Add the Firewall entries during install
Call AddFirewallEntries
; Refresh desktop icons
System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_DWORDFLUSH}, i 0, i 0)"

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

@ -938,7 +938,7 @@ FunctionEnd
!macroend
!define MountRegistryIntoHKU "!insertmacro MountRegistryIntoHKU"
!define un.MountRegistryIntoHKU "!insertmacro MountRegistryIntoHKU"
;
; Unmounts all user ntuser.dat files into the registry as a subkey of HKU
!macro UnmountRegistryIntoHKU
; $0 is used as an index for HKEY_USERS enumeration
@ -1556,6 +1556,72 @@ FunctionEnd
!macroend
!define PushFilesToCheck "!insertmacro PushFilesToCheck"
; Pushes the string "true" to the top of the stack if the Firewall service is
; running and pushes the string "false" to the top of the stack if it isn't.
!define SC_MANAGER_ALL_ACCESS 0x3F
!define SERVICE_QUERY_CONFIG 0x0001
!define SERVICE_QUERY_STATUS 0x0004
!define SERVICE_RUNNING 0x4
!macro IsFirewallSvcRunning
Push $R9
Push $R8
Push $R7
Push $R6
Push "false"
System::Call 'advapi32::OpenSCManagerW(n, n, i ${SC_MANAGER_ALL_ACCESS}) i.R6'
${If} $R6 != 0
; MpsSvc is the Firewall service on Windows Vista and above.
; When opening the service with SERVICE_QUERY_CONFIG the return value will
; be 0 if the service is not installed.
System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_CONFIG}) i.R7'
${If} $R7 != 0
System::Call 'advapi32::CloseServiceHandle(i R7) n'
; Open the service with SERVICE_QUERY_CONFIG so its status can be queried.
System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_STATUS}) i.R7'
${Else}
; SharedAccess is the Firewall service on Windows XP.
; When opening the service with SERVICE_QUERY_CONFIG the return value will
; be 0 if the service is not installed.
System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_CONFIG}) i.R7'
${If} $R7 != 0
System::Call 'advapi32::CloseServiceHandle(i R7) n'
; Open the service with SERVICE_QUERY_CONFIG so its status can be
; queried.
System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_STATUS}) i.R7'
${EndIf}
${EndIf}
; Did the calls to OpenServiceW succeed?
${If} $R7 != 0
System::Call '*(i,i,i,i,i,i,i) i.R9'
; Query the current status of the service.
System::Call 'advapi32::QueryServiceStatus(i R7, i $R9) i'
System::Call '*$R9(i, i.R8)'
System::Free $R9
System::Call 'advapi32::CloseServiceHandle(i R7) n'
IntFmt $R8 "0x%X" $R8
${If} $R8 == ${SERVICE_RUNNING}
Pop $R9
Push "true"
${EndIf}
${EndIf}
System::Call 'advapi32::CloseServiceHandle(i R6) n'
${EndIf}
Exch 1
Pop $R6
Exch 1
Pop $R7
Exch 1
Pop $R8
Exch 1
Pop $R9
!macroend
!define IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
!define un.IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
; Sets this installation as the default browser by setting the registry keys
; under HKEY_CURRENT_USER via registry calls and using the AppAssocReg NSIS
; plugin for Vista and above. This is a function instead of a macro so it is
@ -1619,8 +1685,13 @@ Function FixShortcutAppModelIDs
${EndIf}
FunctionEnd
; Helper for adding Firewall exceptions during install and after app update.
Function AddFirewallEntries
liteFirewallW::AddRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
${IsFirewallSvcRunning}
Pop $0
${If} "$0" == "true"
liteFirewallW::AddRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
${EndIf}
FunctionEnd
; The !ifdef NO_LOG prevents warnings when compiling the installer.nsi due to

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

@ -464,8 +464,6 @@ Section "Uninstall"
${EndIf}
${EndIf}
liteFirewallW::RemoveRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
; Refresh desktop icons otherwise the start menu internet item won't be
; removed and other ugly things will happen like recreation of the app's
; clients registry key by the OS under some conditions.
@ -489,6 +487,11 @@ Section "Uninstall"
Call un.UninstallServiceIfNotUsed
!endif
${un.IsFirewallSvcRunning}
Pop $0
${If} "$0" == "true"
liteFirewallW::RemoveRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
${EndIf}
SectionEnd
################################################################################

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

@ -26,6 +26,11 @@ player.transitionNameLabel=Transition
# displayed before the animation duration.
player.animationDurationLabel=Duration:
# LOCALIZATION NOTE (player.animationDelayLabel):
# This string is displayed in each animation player widget. It is the label
# displayed before the animation delay.
player.animationDelayLabel=Delay:
# LOCALIZATION NOTE (player.animationIterationCountLabel):
# This string is displayed in each animation player widget. It is the label
# displayed before the number of times the animation is set to repeat.

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

@ -71,6 +71,16 @@ browserConsoleCmd.commandkey=j
# This is the tooltip of the pick button in the toolbox toolbar
pickButton.tooltip=Pick an element from the page
# LOCALIZATION NOTE (sidebar.showAllTabs.label)
# This is the label shown in the show all tabs button in the tabbed side
# bar, when there's no enough space to show all tabs at once
sidebar.showAllTabs.label=
# LOCALIZATION NOTE (sidebar.showAllTabs.tooltip)
# This is the tooltip shown when hover over the '…' button in the tabbed side
# bar, when there's no enough space to show all tabs at once
sidebar.showAllTabs.tooltip=All tabs
# LOCALIZATION NOTE (options.darkTheme.label)
# Used as a label for dark theme
options.darkTheme.label=Dark theme

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

@ -142,6 +142,7 @@ function PreviewController(win, tab) {
this.preview = this.win.createTabPreview(this);
this.linkedBrowser.addEventListener("MozAfterPaint", this, false);
this.linkedBrowser.addEventListener("resize", this, false);
this.tab.addEventListener("TabAttrModified", this, false);
XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
@ -171,6 +172,7 @@ PreviewController.prototype = {
Ci.nsIDOMEventListener]),
destroy: function () {
this.tab.removeEventListener("TabAttrModified", this, false);
this.linkedBrowser.removeEventListener("resize", this, false);
this.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
// Break cycles, otherwise we end up leaking the window with everything
@ -201,9 +203,17 @@ PreviewController.prototype = {
// Resizes the canvasPreview to 0x0, essentially freeing its memory.
// updateCanvasPreview() will detect the size mismatch as a resize event
// the next time it is called.
resetCanvasPreview: function () {
this.canvasPreview.width = 0;
this.canvasPreview.height = 0;
resetCanvasPreview: function () this.resizeCanvasPreview(0, 0),
resizeCanvasPreview: function (width, height) {
this.canvasPreview.width = width;
this.canvasPreview.height = height;
},
get wasResizedSinceLastPreview () {
let bx = this.linkedBrowser.boxObject;
return bx.width != this.canvasPreview.width ||
bx.height != this.canvasPreview.height;
},
get zoom() {
@ -219,13 +229,15 @@ PreviewController.prototype = {
updateCanvasPreview: function () {
let win = this.linkedBrowser.contentWindow;
let bx = this.linkedBrowser.boxObject;
// If we resized then we need to flush layout so that the previews are up
// to date. Layout flushing for resizes is deferred for background tabs so
// we may need to force it. (bug 526620)
let flushLayout = this.wasResizedSinceLastPreview;
// Check for resize
if (bx.width != this.canvasPreview.width ||
bx.height != this.canvasPreview.height) {
if (flushLayout) {
// Invalidate the entire area and repaint
this.onTabPaint({left:0, top:0, right:win.innerWidth, bottom:win.innerHeight});
this.canvasPreview.width = bx.width;
this.canvasPreview.height = bx.height;
this.resizeCanvasPreview(bx.width, bx.height);
}
// Draw dirty regions
@ -233,6 +245,9 @@ PreviewController.prototype = {
let scale = this.zoom;
let flags = this.canvasPreviewFlags;
if (flushLayout)
flags &= ~Ci.nsIDOMCanvasRenderingContext2D.DRAWWINDOW_DO_NOT_FLUSH;
// The dirty region may include parts that are offscreen so we clip to the
// canvas area.
this.dirtyRegion.intersectRect(0, 0, win.innerWidth, win.innerHeight);
@ -372,6 +387,19 @@ PreviewController.prototype = {
case "TabAttrModified":
this.updateTitleAndTooltip();
break;
case "resize":
// We need to invalidate our window's other tabs' previews since layout
// due to resizing is delayed for background tabs. Note that this
// resize may not be the first after the main window has been resized -
// the user may be switching to our tab which forces the resize.
this.win.previews.forEach(function (p) {
let controller = p.controller.wrappedJSObject;
if (controller.wasResizedSinceLastPreview) {
controller.resetCanvasPreview();
p.invalidate();
}
});
break;
}
}
};

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

@ -7,7 +7,8 @@
there are overrides for each platform in their devedition.css files. */
:root {
--space-above-tabbar: 1px;
--tab-toolbar-navbar-overlap: 0px;
--space-above-tabbar: 0px;
--toolbarbutton-text-shadow: none;
--panel-ui-button-background-size: 1px calc(100% - 1px);
--panel-ui-button-background-position: 1px 0px;
@ -32,9 +33,9 @@
--tab-separator-color: #474C50;
--tab-selection-color: #f5f7fa;
--tab-selection-background-color: #1a4666;
--tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
0 8px 3px -5px #2b82bf inset,
0 -1px 0 rgba(0,0,0,.2) inset;
--tab-selection-box-shadow: 0 2px 0 #D7F1FF inset,
0 -2px 0 rgba(0,0,0,.05) inset,
0 -1px 0 rgba(0,0,0,.3) inset;
--pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, rgba(0,0,0,0.4) 16%, transparent 70%);
/* Toolbar buttons */
@ -92,9 +93,9 @@
--tab-separator-color: #C6C6C7;
--tab-selection-color: #f5f7fa;
--tab-selection-background-color: #4c9ed9;
--tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
0 8px 3px -5px #319BDB inset,
0 -1px 0 #2A7CB1 inset;
--tab-selection-box-shadow: 0 2px 0 #9FDFFF inset,
0 -2px 0 rgba(0,0,0,.05) inset,
0 -1px 0 rgba(0,0,0,.2) inset;
--pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, transparent 16%);
@ -177,7 +178,7 @@
#navigator-toolbox > toolbar:not(#TabsToolbar):not(#toolbar-menubar),
.browserContainer > findbar,
#browser-bottombox {
background: var(--chrome-secondary-background-color) !important;
background-color: var(--chrome-secondary-background-color) !important;
color: var(--chrome-color);
}
@ -227,9 +228,10 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
/* Nav bar specific stuff */
#nav-bar {
margin-top: 0 !important;
border: none !important;
border-top: none !important;
border-bottom: none !important;
border-radius: 0 !important;
box-shadow: 0 1px var(--chrome-nav-bar-separator-color) inset !important;
box-shadow: 0 -1px var(--chrome-nav-bar-separator-color) !important;
background-image: none !important;
}
@ -288,7 +290,6 @@ searchbar:not([oneoffui]) .search-go-button {
}
.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content {
/* The -2px in `calc` is the height of `tabToolbarNavbarOverlap` plus a 1px offset from the center */
background-image: var(--pinned-tab-glow);
background-position: center;
background-size: 100%;
@ -311,13 +312,6 @@ searchbar:not([oneoffui]) .search-go-button {
width: 36px;
}
/* Override @tabToolbarNavbarOverlap@ */
#TabsToolbar .toolbarbutton-1,
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down {
margin-bottom: 0;
}
#TabsToolbar > #new-tab-button:hover,
.tabs-newtab-button:hover {
/* Important needed because !important is used in browser.css */

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

@ -191,7 +191,8 @@
.theme-toolbar,
.devtools-toolbar,
.devtools-sidebar-tabs > tabs,
.devtools-sidebar-tabs tabs,
.devtools-sidebar-alltabs,
.CodeMirror-dialog { /* General toolbar styling */
color: var(--theme-body-color-alt);
background-color: var(--theme-toolbar-background);

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

@ -194,7 +194,8 @@
.theme-toolbar,
.devtools-toolbar,
.devtools-sidebar-tabs > tabs,
.devtools-sidebar-tabs tabs,
.devtools-sidebar-alltabs,
.CodeMirror-dialog { /* General toolbar styling */
color: var(--theme-body-color-alt);
background-color: var(--theme-toolbar-background);

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

@ -11,7 +11,8 @@
/* Toolbars */
.devtools-toolbar,
.devtools-sidebar-tabs > tabs {
.devtools-sidebar-tabs tabs,
.devtools-sidebar-alltabs {
-moz-appearance: none;
padding: 0;
border-width: 0;
@ -416,19 +417,30 @@
color: var(--theme-body-color);
}
.devtools-sidebar-tabs > tabs {
.devtools-sidebar-tabs tabs {
position: static;
font: inherit;
margin-bottom: 0;
overflow: hidden;
}
.devtools-sidebar-tabs > tabs > .tabs-right,
.devtools-sidebar-tabs > tabs > .tabs-left {
.devtools-sidebar-alltabs {
margin: 0;
border-width: 0 0 1px 0;
-moz-border-start-width: 1px;
border-style: solid;
}
.devtools-sidebar-alltabs dropmarker {
display: none;
}
.devtools-sidebar-tabs > tabs > tab {
.devtools-sidebar-tabs tabs > .tabs-right,
.devtools-sidebar-tabs tabs > .tabs-left {
display: none;
}
.devtools-sidebar-tabs tabs > tab {
-moz-appearance: none;
/* We want to match the height of a toolbar with a toolbarbutton
* First, we need to replicated the padding of toolbar (4px),
@ -449,70 +461,70 @@
text-shadow: none;
}
.devtools-sidebar-tabs > tabs > tab:first-child {
.devtools-sidebar-tabs tabs > tab:first-child {
-moz-border-start-width: 0;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab {
.theme-dark .devtools-sidebar-tabs tabs > tab {
border-image: @smallSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab:hover {
.theme-dark .devtools-sidebar-tabs tabs > tab:hover {
background: hsla(206,37%,4%,.2);
border-image: @smallSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab:hover:active {
.theme-dark .devtools-sidebar-tabs tabs > tab:hover:active {
background: hsla(206,37%,4%,.4);
border-image: @smallSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab {
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab {
border-image: @solidSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab:hover {
background: hsla(206,37%,4%,.2);
border-image: @solidSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover:active {
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab:hover:active {
background: hsla(206,37%,4%,.4);
border-image: @solidSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected],
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
.theme-dark .devtools-sidebar-tabs tabs > tab[selected],
.theme-dark .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
color: var(--theme-selection-color);
background: #1d4f73;
border-image: @solidSeparatorDark@ 1 1;
}
.theme-light .devtools-sidebar-tabs > tabs > tab {
.theme-light .devtools-sidebar-tabs tabs > tab {
border-image: @smallSeparatorLight@ 1 1;
}
.theme-light .devtools-sidebar-tabs > tabs > tab:hover {
.theme-light .devtools-sidebar-tabs tabs > tab:hover {
background: #ddd;
border-image: @smallSeparatorLight@ 1 1;
}
.theme-light .devtools-sidebar-tabs > tabs > tab:hover:active {
.theme-light .devtools-sidebar-tabs tabs > tab:hover:active {
background: #ddd;
border-image: @smallSeparatorLight@ 1 1;
}
.theme-light .devtools-sidebar-tabs > tabs > tab[selected] + tab {
.theme-light .devtools-sidebar-tabs tabs > tab[selected] + tab {
border-image: @solidSeparatorLight@;
}
.theme-light .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
.theme-light .devtools-sidebar-tabs tabs > tab[selected] + tab:hover {
background: #ddd;
border-image: @solidSeparatorLight@;
}
.theme-light .devtools-sidebar-tabs > tabs > tab[selected],
.theme-light .devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
.theme-light .devtools-sidebar-tabs tabs > tab[selected],
.theme-light .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
color: var(--theme-selection-color);
background: #4c9ed9;
border-image: @solidSeparatorLight@;
@ -734,7 +746,7 @@
-moz-box-align: center;
min-width: 32px;
min-height: 24px;
max-width: 127px;
max-width: 110px;
margin: 0;
padding: 0;
border-style: solid;

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

@ -944,7 +944,7 @@ ContactDB.prototype = {
if (DEBUG) debug("No object ID passed");
return;
}
this.newTxn("readwrite", STORE_NAME, function(txn, store) {
this.newTxn("readwrite", SAVED_GETALL_STORE_NAME, function(txn, store) {
store.openCursor().onsuccess = function(e) {
let cursor = e.target.result;
if (cursor) {

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

@ -4,3 +4,7 @@
skip-if = os == "android"
[test_contacts_upgrade.xul]
skip-if = os == "android"
[test_contacts_cache.xul]
skip-if = os == "android"

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

@ -0,0 +1,144 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<window title="Mozilla Bug 1114520"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript;version=1.7">
<![CDATA[
"use strict";
const { 'utils': Cu } = Components;
Cu.import("resource://gre/modules/ContactDB.jsm", window);
Cu.importGlobalProperties(["indexedDB"]);
let contactsDB = new ContactDB();
contactsDB.init();
function makeFailure(reason, skipDelete) {
return function() {
ok(false, reason);
if (skipDelete) {
SimpleTest.finish();
return;
}
deleteDatabase(SimpleTest.finish);
};
};
function deleteDatabase(then) {
contactsDB.close();
let req = indexedDB.deleteDatabase(DB_NAME);
req.onsuccess = then;
req.onblocked = makeFailure("blocked", true);
req.onupgradeneeded = makeFailure("onupgradeneeded", true);
req.onerror = makeFailure("onerror", true);
};
let CONTACT_PROPS = {
id: "ab74671e36be41b680f8f030e7e24ea2",
properties: {
name: ["Magnificentest foo bar the third"],
givenName: ["foo"],
familyName: ["bar"]
}
};
let ANOTHER_CONTACT_PROPS = {
id: "b461d53d548b4e8aaa8256911a415f79",
properties: {
name: ["Magnificentest foo bar the fourth"],
givenName: ["foo"],
familyName: ["bar"]
}
};
let Tests = [function() {
info("Save contact");
contactsDB.saveContact(CONTACT_PROPS, function() {
ok(true, "Saved contact successfully");
next();
});
}, function() {
info("Save another contact");
contactsDB.saveContact(ANOTHER_CONTACT_PROPS, function() {
ok(true, "Saved contact successfully");
next();
});
}, function() {
info("Get all contacts so cache is built");
contactsDB.getAll(function(contacts) {
ok(true, "Got all contacts " + contacts.length);
next();
}, function(e) {
makeFailure("Unexpected error getting contacts " + e);
}, {"sortBy":"givenName","sortOrder":"ascending"});
}, function() {
info("Contacts cache should have both ids");
let contactsCount = 0;
contactsDB.newTxn("readonly", SAVED_GETALL_STORE_NAME, function(txn, store) {
store.openCursor().onsuccess = function(e) {
let cursor = e.target.result;
if (!cursor) {
makeFailure("Wrong cache");
return;
}
ok(cursor.value.length == 2, "Both contacts ids are in the cache");
next();
};
}, null, makeFailure);
}, function() {
info("Remove contact " + CONTACT_PROPS.id);
contactsDB.removeContact(CONTACT_PROPS.id, function() {
ok(true, "Removed contact");
next();
}, function() {
makeFailure("Unexpected error removing contact " + e);
});
}, function() {
info("Contacts cache should have only one id");
contactsDB.newTxn("readonly", SAVED_GETALL_STORE_NAME, function(txn, store) {
store.openCursor().onsuccess = function(e) {
let cursor = e.target.result;
if (!cursor) {
makeFailure;
return;
}
ok(cursor.value.length == 1, "Only one contacts id is in the cache");
ok(cursor.value[0] == ANOTHER_CONTACT_PROPS.id, "And it is the right id");
next();
};
}, null, makeFailure);
}, function() {
deleteDatabase(next);
}];
function next() {
let step = Tests.shift();
if (step) {
step();
} else {
info("All done");
SimpleTest.finish();
}
}
SimpleTest.waitForExplicitFinish();
next();
]]>
</script>
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1114520"
target="_blank">Mozilla Bug 1114520</a>
</body>
</window>

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

@ -0,0 +1,68 @@
/* 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 "NetIdManager.h"
NetIdManager::NetIdManager()
: mNextNetId(MIN_NET_ID)
{
}
int NetIdManager::getNextNetId()
{
// Modified from
// http://androidxref.com/5.0.0_r2/xref/frameworks/base/services/
// core/java/com/android/server/ConnectivityService.java#764
int netId = mNextNetId;
if (++mNextNetId > MAX_NET_ID) {
mNextNetId = MIN_NET_ID;
}
return netId;
}
void NetIdManager::acquire(const nsString& aInterfaceName,
NetIdInfo* aNetIdInfo)
{
// Lookup or create one.
if (!mInterfaceToNetIdHash.Get(aInterfaceName, aNetIdInfo)) {
aNetIdInfo->mNetId = getNextNetId();
aNetIdInfo->mRefCnt = 1;
} else {
aNetIdInfo->mRefCnt++;
}
// Update hash and return.
mInterfaceToNetIdHash.Put(aInterfaceName, *aNetIdInfo);
return;
}
bool NetIdManager::lookup(const nsString& aInterfaceName,
NetIdInfo* aNetIdInfo)
{
return mInterfaceToNetIdHash.Get(aInterfaceName, aNetIdInfo);
}
bool NetIdManager::release(const nsString& aInterfaceName,
NetIdInfo* aNetIdInfo)
{
if (!mInterfaceToNetIdHash.Get(aInterfaceName, aNetIdInfo)) {
return false; // No such key.
}
aNetIdInfo->mRefCnt--;
// Update the hash if still be referenced.
if (aNetIdInfo->mRefCnt > 0) {
mInterfaceToNetIdHash.Put(aInterfaceName, *aNetIdInfo);
return true;
}
// No longer be referenced. Remove the entry.
mInterfaceToNetIdHash.Remove(aInterfaceName);
return true;
}

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

@ -0,0 +1,45 @@
/* 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/. */
#ifndef NetIdManager_h
#define NetIdManager_h
#include "nsString.h"
#include "nsDataHashtable.h"
// NetId is a logical network identifier defined by netd.
// A network is typically a physical one (i.e. PhysicalNetwork.cpp)
// for netd but it could be a virtual network as well.
// We currently use physical network only and use one-to-one
// network-interface mapping.
class NetIdManager {
public:
// keep in sync with system/netd/NetworkController.cpp
enum {
MIN_NET_ID = 100,
MAX_NET_ID = 65535,
};
// We need to count the number of references since different
// application like data and mms may use the same interface.
struct NetIdInfo {
int mNetId;
int mRefCnt;
};
public:
NetIdManager();
bool lookup(const nsString& aInterfaceName, NetIdInfo* aNetIdInfo);
void acquire(const nsString& aInterfaceName, NetIdInfo* aNetIdInfo);
bool release(const nsString& aInterfaceName, NetIdInfo* aNetIdInfo);
private:
int getNextNetId();
int mNextNetId;
nsDataHashtable<nsStringHashKey, NetIdInfo> mInterfaceToNetIdHash;
};
#endif

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

@ -308,6 +308,17 @@ NetworkManager.prototype = {
debug("Network '" + networkId + "' registered.");
},
_addSubnetRoutes: function(network) {
let ips = {};
let prefixLengths = {};
let length = network.getAddresses(ips, prefixLengths);
for (let i = 0; i < length; i++) {
debug('Adding subnet routes: ' + ips.value[i] + '/' + prefixLengths.value[i]);
gNetworkService.modifyRoute(Ci.nsINetworkService.MODIFY_ROUTE_ADD,
network.name, ips.value[i], prefixLengths.value[i]);
}
},
updateNetworkInterface: function(network) {
if (!(network instanceof Ci.nsINetworkInterface)) {
throw Components.Exception("Argument must be nsINetworkInterface.",
@ -320,35 +331,50 @@ NetworkManager.prototype = {
}
debug("Network " + network.type + "/" + network.name +
" changed state to " + network.state);
// Note that since Lollipop we need to allocate and initialize
// something through netd, so we add createNetwork/destroyNetwork
// to deal with that explicitly.
switch (network.state) {
case Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED:
// Add host route for data calls
if (this.isNetworkTypeMobile(network.type)) {
gNetworkService.removeHostRoutes(network.name);
this.setHostRoutes(network);
}
// Dun type is a special case where we add the default route to a
// secondary table.
if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
this.setSecondaryDefaultRoute(network);
}
// Remove pre-created default route and let setAndConfigureActive()
// to set default route only on preferred network
gNetworkService.removeDefaultRoute(network);
this.setAndConfigureActive();
// Update data connection when Wifi connected/disconnected
if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && this.mRil) {
for (let i = 0; i < this.mRil.numRadioInterfaces; i++) {
this.mRil.getRadioInterface(i).updateRILNetworkInterface();
gNetworkService.createNetwork(network.name, () => {
// Add host route for data calls
if (this.isNetworkTypeMobile(network.type)) {
gNetworkService.removeHostRoutes(network.name);
this.setHostRoutes(network);
}
}
// Dun type is a special case where we add the default route to a
// secondary table.
if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
this.setSecondaryDefaultRoute(network);
}
// Remove pre-created default route and let setAndConfigureActive()
// to set default route only on preferred network
gNetworkService.removeDefaultRoute(network);
this.onConnectionChanged(network);
this._addSubnetRoutes(network);
this.setAndConfigureActive();
// Update data connection when Wifi connected/disconnected
if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && this.mRil) {
for (let i = 0; i < this.mRil.numRadioInterfaces; i++) {
this.mRil.getRadioInterface(i).updateRILNetworkInterface();
}
}
this.onConnectionChanged(network);
// Probing the public network accessibility after routing table is ready
CaptivePortalDetectionHelper
.notify(CaptivePortalDetectionHelper.EVENT_CONNECT, this.active);
// Notify outer modules like MmsService to start the transaction after
// the configuration of the network interface is done.
Services.obs.notifyObservers(network, TOPIC_CONNECTION_STATE_CHANGED,
this.convertConnectionType(network));
});
// Probing the public network accessibility after routing table is ready
CaptivePortalDetectionHelper
.notify(CaptivePortalDetectionHelper.EVENT_CONNECT, this.active);
break;
case Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED:
// Remove host route for data calls
@ -378,13 +404,14 @@ NetworkManager.prototype = {
}
}
gNetworkService.destroyNetwork(network.name, () => {
// Notify outer modules like MmsService to start the transaction after
// the configuration of the network interface is done.
Services.obs.notifyObservers(network, TOPIC_CONNECTION_STATE_CHANGED,
this.convertConnectionType(network));
});
break;
}
// Notify outer modules like MmsService to start the transaction after
// the configuration of the network interface is done.
Services.obs.notifyObservers(network, TOPIC_CONNECTION_STATE_CHANGED,
this.convertConnectionType(network));
},
unregisterNetworkInterface: function(network) {
@ -435,14 +462,22 @@ NetworkManager.prototype = {
},
_updateRoutes: function(doAdd, ipAddresses, networkName, gateways) {
let getMaxPrefixLength = (aIp) => {
return aIp.match(this.REGEXP_IPV4) ? IPV4_MAX_PREFIX_LENGTH : IPV6_MAX_PREFIX_LENGTH;
}
let promises = [];
ipAddresses.forEach((aIpAddress) => {
let gateway = this.selectGateway(gateways, aIpAddress);
if (gateway) {
promises.push((doAdd)
? gNetworkService.addHostRoute(networkName, gateway, aIpAddress)
: gNetworkService.removeHostRoute(networkName, gateway, aIpAddress));
? gNetworkService.modifyRoute(Ci.nsINetworkService.MODIFY_ROUTE_ADD,
networkName, aIpAddress,
getMaxPrefixLength(aIpAddress), gateway)
: gNetworkService.modifyRoute(Ci.nsINetworkService.MODIFY_ROUTE_REMOVE,
networkName, aIpAddress,
getMaxPrefixLength(aIpAddress), gateway));
}
});
@ -1271,6 +1306,10 @@ NetworkManager.prototype = {
_setDefaultRouteAndDNS: function(network, oldInterface) {
gNetworkService.setDefaultRoute(network, oldInterface, function(success) {
if (!success) {
gNetworkService.destroyNetwork(network, function() {});
return;
}
gNetworkService.setDNS(network, function(result) {
gNetworkService.setNetworkProxy(network);
});

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

@ -255,22 +255,12 @@ NetworkService.prototype = {
},
resetRoutingTable: function(network) {
let ips = {};
let prefixLengths = {};
let length = network.getAddresses(ips, prefixLengths);
let options = {
cmd: "removeNetworkRoute",
ifname: network.name
};
for (let i = 0; i < length; i++) {
let ip = ips.value[i];
let prefixLength = prefixLengths.value[i];
let options = {
cmd: "removeNetworkRoute",
ifname: network.name,
ip: ip,
prefixLength: prefixLength
};
this.controlMessage(options);
}
this.controlMessage(options);
},
setDNS: function(networkInterface, callback) {
@ -312,8 +302,20 @@ NetworkService.prototype = {
this.controlMessage(options);
},
_setHostRoute: function(doAdd, interfaceName, gateway, host) {
let command = doAdd ? "addHostRoute" : "removeHostRoute";
modifyRoute: function(action, interfaceName, host, prefixLength, gateway) {
let command;
switch (action) {
case Ci.nsINetworkService.MODIFY_ROUTE_ADD:
command = 'addHostRoute';
break;
case Ci.nsINetworkService.MODIFY_ROUTE_REMOVE:
command = 'removeHostRoute';
break;
default:
if (DEBUG) debug('Unknown action: ' + action);
return Promise.reject();
}
if (DEBUG) debug(command + " " + host + " on " + interfaceName);
let deferred = Promise.defer();
@ -321,6 +323,7 @@ NetworkService.prototype = {
cmd: command,
ifname: interfaceName,
gateway: gateway,
prefixLength: prefixLength,
ip: host
};
this.controlMessage(options, function(data) {
@ -333,14 +336,6 @@ NetworkService.prototype = {
return deferred.promise;
},
addHostRoute: function(interfaceName, gateway, host) {
return this._setHostRoute(true, interfaceName, gateway, host);
},
removeHostRoute: function(interfaceName, gateway, host) {
return this._setHostRoute(false, interfaceName, gateway, host);
},
removeHostRoutes: function(ifname) {
if(DEBUG) debug("Going to remove all host routes on " + ifname);
let options = {
@ -567,6 +562,28 @@ NetworkService.prototype = {
});
},
createNetwork: function(interfaceName, callback) {
let params = {
cmd: "createNetwork",
ifname: interfaceName
};
this.controlMessage(params, function(result) {
callback.nativeCommandResult(!result.error);
});
},
destroyNetwork: function(interfaceName, callback) {
let params = {
cmd: "destroyNetwork",
ifname: interfaceName
};
this.controlMessage(params, function(result) {
callback.nativeCommandResult(!result.error);
});
},
shutdown: false,
observe: function observe(aSubject, aTopic, aData) {

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

@ -248,12 +248,6 @@ const CommandFunc NetworkUtils::sNetworkInterfaceSetAlarmChain[] = {
NetworkUtils::networkInterfaceAlarmSuccess
};
const CommandFunc NetworkUtils::sSetDnsChain[] = {
NetworkUtils::setDefaultInterface,
NetworkUtils::setInterfaceDns,
NetworkUtils::setDnsSuccess,
};
/**
* Helper function to get the mask from given prefix length.
*/
@ -918,13 +912,34 @@ void NetworkUtils::setDefaultInterface(CommandChain* aChain,
doCommand(command, aChain, aCallback);
}
void NetworkUtils::removeDefaultRoute(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
// FIXME: (Bug 1121795) We only remove the first gateway to the default route.
// For dual stack (ipv4/ipv6) device, one of the gateway would
// not be added to the default route.
snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s 0.0.0.0/0 %s",
GET_FIELD(mNetId), GET_CHAR(mIfname), GET_CHAR(mGateways[0]));
doCommand(command, aChain, aCallback);
}
void NetworkUtils::setInterfaceDns(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
int written = snprintf(command, sizeof command, "resolver setifdns %s %s",
GET_CHAR(mIfname), GET_CHAR(mDomain));
int written;
if (SDK_VERSION >= 20) {
written = snprintf(command, sizeof command, "resolver setnetdns %d %s",
GET_FIELD(mNetId), GET_CHAR(mDomain));
} else {
written = snprintf(command, sizeof command, "resolver setifdns %s %s",
GET_CHAR(mIfname), GET_CHAR(mDomain));
}
nsTArray<nsString>& dnses = GET_FIELD(mDnses);
uint32_t length = dnses.Length();
@ -949,6 +964,188 @@ void NetworkUtils::setInterfaceDns(CommandChain* aChain,
doCommand(command, aChain, aCallback);
}
void NetworkUtils::clearAddrForInterface(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
snprintf(command, MAX_COMMAND_SIZE - 1, "interface clearaddrs %s", GET_CHAR(mIfname));
doCommand(command, aChain, aCallback);
}
void NetworkUtils::createNetwork(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
snprintf(command, MAX_COMMAND_SIZE - 1, "network create %d", GET_FIELD(mNetId));
doCommand(command, aChain, aCallback);
}
void NetworkUtils::destroyNetwork(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
snprintf(command, MAX_COMMAND_SIZE - 1, "network destroy %d", GET_FIELD(mNetId));
doCommand(command, aChain, aCallback);
}
void NetworkUtils::addInterfaceToNetwork(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add %d %s",
GET_FIELD(mNetId), GET_CHAR(mIfname));
doCommand(command, aChain, aCallback);
}
void NetworkUtils::addRouteToInterface(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
struct MyCallback {
static void callback(CommandCallback::CallbackType aOriginalCallback,
CommandChain* aChain,
bool aError,
mozilla::dom::NetworkResultOptions& aResult)
{
NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
NU_DBG("addRouteToInterface's reason: %s", reason.get());
if (aError && reason.EqualsASCII("addRoute() failed (File exists)")) {
NU_DBG("Ignore \"File exists\" error when adding host route.");
return aOriginalCallback(aChain, false, aResult);
}
aOriginalCallback(aChain, aError, aResult);
}
};
CommandCallback wrappedCallback(MyCallback::callback, aCallback);
modifyRouteOnInterface(aChain, wrappedCallback, aResult, true);
}
void NetworkUtils::removeRouteFromInterface(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
modifyRouteOnInterface(aChain, aCallback, aResult, false);
}
void NetworkUtils::modifyRouteOnInterface(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult,
bool aDoAdd)
{
char command[MAX_COMMAND_SIZE];
// AOSP adds host route to its interface table but it doesn't work for
// B2G because we cannot set fwmark per application. So, we add
// all host routes to legacy_system table except scope link route.
nsCString ipOrSubnetIp = NS_ConvertUTF16toUTF8(GET_FIELD(mIp));
nsCString gatewayOrEmpty;
const char* legacyOrEmpty = "legacy 0 "; // Add to legacy by default.
if (GET_FIELD(mGateway).IsEmpty()) {
ipOrSubnetIp = getSubnetIp(ipOrSubnetIp, GET_FIELD(mPrefixLength));
legacyOrEmpty = ""; // Add to interface table for scope link route.
} else {
gatewayOrEmpty = nsCString(" ") + NS_ConvertUTF16toUTF8(GET_FIELD(mGateway));
}
const char* action = aDoAdd ? "add" : "remove";
snprintf(command, MAX_COMMAND_SIZE - 1, "network route %s%s %d %s %s/%d%s",
legacyOrEmpty, action,
GET_FIELD(mNetId), GET_CHAR(mIfname), ipOrSubnetIp.get(),
GET_FIELD(mPrefixLength), gatewayOrEmpty.get());
doCommand(command, aChain, aCallback);
}
void NetworkUtils::addDefaultRouteToNetwork(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
// FIXME: (Bug 1121795) We only add the first gateway to the default route.
// For dual stack (ipv4/ipv6) device, one of the gateway would
// not be added to the default route.
snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s 0.0.0.0/0 %s",
GET_FIELD(mNetId), GET_CHAR(mIfname), GET_CHAR(mGateways[0]));
struct MyCallback {
static void callback(CommandCallback::CallbackType aOriginalCallback,
CommandChain* aChain,
bool aError,
mozilla::dom::NetworkResultOptions& aResult)
{
NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
NU_DBG("addDefaultRouteToNetwork's reason: %s", reason.get());
if (aError && reason.EqualsASCII("addRoute() failed (File exists)")) {
NU_DBG("Ignore \"File exists\" error when adding host route.");
return aOriginalCallback(aChain, false, aResult);
}
aOriginalCallback(aChain, aError, aResult);
}
};
CommandCallback wrappedCallback(MyCallback::callback, aCallback);
doCommand(command, aChain, wrappedCallback);
}
void NetworkUtils::setDefaultNetwork(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
snprintf(command, MAX_COMMAND_SIZE - 1, "network default set %d", GET_FIELD(mNetId));
doCommand(command, aChain, aCallback);
}
void NetworkUtils::setIpv6Enabled(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult,
bool aEnabled)
{
char command[MAX_COMMAND_SIZE];
snprintf(command, MAX_COMMAND_SIZE - 1, "interface ipv6 %s %s",
GET_CHAR(mIfname), aEnabled ? "enable" : "disable");
struct MyCallback {
static void callback(CommandCallback::CallbackType aOriginalCallback,
CommandChain* aChain,
bool aError,
mozilla::dom::NetworkResultOptions& aResult)
{
aOriginalCallback(aChain, false, aResult);
}
};
CommandCallback wrappedCallback(MyCallback::callback, aCallback);
doCommand(command, aChain, wrappedCallback);
}
void NetworkUtils::enableIpv6(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
setIpv6Enabled(aChain, aCallback, aResult, true);
}
void NetworkUtils::disableIpv6(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
setIpv6Enabled(aChain, aCallback, aResult, false);
}
#undef GET_CHAR
#undef GET_FIELD
@ -1053,14 +1250,6 @@ void NetworkUtils::networkInterfaceAlarmSuccess(CommandChain* aChain,
finalizeSuccess(aChain, aResult);
}
void NetworkUtils::setDnsSuccess(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
postMessage(aChain->getParams(), aResult);
finalizeSuccess(aChain, aResult);
}
void NetworkUtils::updateUpStreamFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
postMessage(aOptions, aResult);
@ -1107,6 +1296,22 @@ void NetworkUtils::setDnsFail(NetworkParams& aOptions, NetworkResultOptions& aRe
postMessage(aOptions, aResult);
}
void NetworkUtils::defaultAsyncSuccessHandler(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
NU_DBG("defaultAsyncSuccessHandler");
aResult.mRet = true;
postMessage(aChain->getParams(), aResult);
finalizeSuccess(aChain, aResult);
}
void NetworkUtils::defaultAsyncFailureHandler(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
aResult.mRet = false;
postMessage(aOptions, aResult);
}
#undef ASSIGN_FIELD
#undef ASSIGN_FIELD_VALUE
@ -1170,6 +1375,8 @@ void NetworkUtils::ExecuteCommand(NetworkParams aOptions)
BUILD_ENTRY(enableInterface),
BUILD_ENTRY(disableInterface),
BUILD_ENTRY(resetConnections),
BUILD_ENTRY(createNetwork),
BUILD_ENTRY(destroyNetwork),
#undef BUILD_ENTRY
};
@ -1266,14 +1473,14 @@ void NetworkUtils::onNetdMessage(NetdCommand* aCommand)
gPending = false;
}
if (gCurrentCommand.callback) {
{
char buf[BUF_SIZE];
join(gReason, INTERFACE_DELIMIT, BUF_SIZE, buf);
NetworkResultOptions result;
result.mResultCode = code;
result.mResultReason = NS_ConvertUTF8toUTF16(buf);
(*gCurrentCommand.callback)(gCurrentCommand.chain, isError(code), result);
(gCurrentCommand.callback)(gCurrentCommand.chain, isError(code), result);
gReason.Clear();
}
@ -1335,8 +1542,28 @@ CommandResult NetworkUtils::setDNS(NetworkParams& aOptions)
property_set("net.dnschange", num);
// DNS needs to be set through netd since JellyBean (4.3).
if (SDK_VERSION >= 20) {
// Lollipop.
static CommandFunc COMMAND_CHAIN[] = {
setInterfaceDns,
defaultAsyncSuccessHandler
};
NetIdManager::NetIdInfo netIdInfo;
if (!mNetIdManager.lookup(aOptions.mIfname, &netIdInfo)) {
return -1;
}
aOptions.mNetId = netIdInfo.mNetId;
runChain(aOptions, COMMAND_CHAIN, setDnsFail);
return CommandResult::Pending();
}
if (SDK_VERSION >= 18) {
runChain(aOptions, sSetDnsChain, setDnsFail);
// JB, KK.
static CommandFunc COMMAND_CHAIN[] = {
setDefaultInterface,
setInterfaceDns,
defaultAsyncSuccessHandler
};
runChain(aOptions, COMMAND_CHAIN, setDnsFail);
return CommandResult::Pending();
}
@ -1439,6 +1666,33 @@ CommandResult NetworkUtils::resetConnections(NetworkParams& aOptions) {
* Set default route and DNS servers for given network interface.
*/
CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions)
{
if (SDK_VERSION < 20) {
return setDefaultRouteLegacy(aOptions);
}
static CommandFunc COMMAND_CHAIN[] = {
addDefaultRouteToNetwork,
setDefaultNetwork,
defaultAsyncSuccessHandler,
};
NetIdManager::NetIdInfo netIdInfo;
if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
ERROR("No such interface");
return -1;
}
aOptions.mNetId = netIdInfo.mNetId;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
/**
* Set default route and DNS servers for given network interface by obsoleted libnetutils.
*/
CommandResult NetworkUtils::setDefaultRouteLegacy(NetworkParams& aOptions)
{
NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
@ -1493,6 +1747,37 @@ CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions)
*/
CommandResult NetworkUtils::removeDefaultRoute(NetworkParams& aOptions)
{
NU_DBG("Calling NetworkUtils::removeDefaultRoute");
if (SDK_VERSION < 20) {
return removeDefaultRouteLegacy(aOptions);
}
static CommandFunc COMMAND_CHAIN[] = {
removeDefaultRoute,
defaultAsyncSuccessHandler,
};
NetIdManager::NetIdInfo netIdInfo;
if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
ERROR("No such interface: %s", GET_CHAR(mIfname));
return -1;
}
NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
aOptions.mNetId = netIdInfo.mNetId;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
/**
* Remove default route for given network interface by obsoleted libnetutils.
*/
CommandResult NetworkUtils::removeDefaultRouteLegacy(NetworkParams& aOptions)
{
// Legacy libnetutils calls before Lollipop.
uint32_t length = aOptions.mGateways.Length();
for (uint32_t i = 0; i < length; i++) {
NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);
@ -1514,6 +1799,34 @@ CommandResult NetworkUtils::removeDefaultRoute(NetworkParams& aOptions)
* Add host route for given network interface.
*/
CommandResult NetworkUtils::addHostRoute(NetworkParams& aOptions)
{
if (SDK_VERSION < 20) {
return addHostRouteLegacy(aOptions);
}
static CommandFunc COMMAND_CHAIN[] = {
addRouteToInterface,
defaultAsyncSuccessHandler,
};
NetIdManager::NetIdInfo netIdInfo;
if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
ERROR("No such interface: %s", GET_CHAR(mIfname));
return -1;
}
NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
aOptions.mNetId = netIdInfo.mNetId;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
/**
* Add host route for given network interface.
*/
CommandResult NetworkUtils::addHostRouteLegacy(NetworkParams& aOptions)
{
NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
NS_ConvertUTF16toUTF8 autoHostname(aOptions.mIp);
@ -1538,6 +1851,34 @@ CommandResult NetworkUtils::addHostRoute(NetworkParams& aOptions)
* Remove host route for given network interface.
*/
CommandResult NetworkUtils::removeHostRoute(NetworkParams& aOptions)
{
if (SDK_VERSION < 20) {
return removeHostRouteLegacy(aOptions);
}
static CommandFunc COMMAND_CHAIN[] = {
removeRouteFromInterface,
defaultAsyncSuccessHandler,
};
NetIdManager::NetIdInfo netIdInfo;
if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
ERROR("No such interface: %s", GET_CHAR(mIfname));
return -1;
}
NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
aOptions.mNetId = netIdInfo.mNetId;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
/**
* Remove host route for given network interface.
*/
CommandResult NetworkUtils::removeHostRouteLegacy(NetworkParams& aOptions)
{
NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
NS_ConvertUTF16toUTF8 autoHostname(aOptions.mIp);
@ -1562,11 +1903,89 @@ CommandResult NetworkUtils::removeHostRoute(NetworkParams& aOptions)
* Remove the routes associated with the named interface.
*/
CommandResult NetworkUtils::removeHostRoutes(NetworkParams& aOptions)
{
if (SDK_VERSION < 20) {
return removeHostRoutesLegacy(aOptions);
}
NU_DBG("Don't know how to remove host routes on a interface");
return SUCCESS;
}
/**
* Remove the routes associated with the named interface.
*/
CommandResult NetworkUtils::removeHostRoutesLegacy(NetworkParams& aOptions)
{
return mNetUtils->do_ifc_remove_host_routes(GET_CHAR(mIfname));
}
CommandResult NetworkUtils::removeNetworkRoute(NetworkParams& aOptions)
{
if (SDK_VERSION < 20) {
return removeNetworkRouteLegacy(aOptions);
}
static CommandFunc COMMAND_CHAIN[] = {
clearAddrForInterface,
defaultAsyncSuccessHandler,
};
NetIdManager::NetIdInfo netIdInfo;
if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
ERROR("interface %s is not present in any network", GET_CHAR(mIfname));
return -1;
}
NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
aOptions.mNetId = netIdInfo.mNetId;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
nsCString NetworkUtils::getSubnetIp(const nsCString& aIp, int aPrefixLength)
{
int type = getIpType(aIp.get());
if (AF_INET6 == type) {
struct in6_addr in6;
if (inet_pton(AF_INET6, aIp.get(), &in6) != 1) {
return nsCString();
}
uint32_t p, i, p1, mask;
p = aPrefixLength;
i = 0;
while (i < 4) {
p1 = p > 32 ? 32 : p;
p -= p1;
mask = p1 ? ~0x0 << (32 - p1) : 0;
in6.s6_addr32[i++] &= htonl(mask);
}
char subnetStr[INET6_ADDRSTRLEN];
if (!inet_ntop(AF_INET6, &in6, subnetStr, sizeof subnetStr)) {
return nsCString();
}
return nsCString(subnetStr);
}
if (AF_INET == type) {
uint32_t ip = inet_addr(aIp.get());
uint32_t netmask = makeMask(aPrefixLength);
uint32_t subnet = ip & netmask;
struct in_addr addr;
addr.s_addr = subnet;
return nsCString(inet_ntoa(addr));
}
return nsCString();
}
CommandResult NetworkUtils::removeNetworkRouteLegacy(NetworkParams& aOptions)
{
NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
NS_ConvertUTF16toUTF8 autoIp(aOptions.mIp);
@ -1857,6 +2276,74 @@ CommandResult NetworkUtils::updateUpStream(NetworkParams& aOptions)
return CommandResult::Pending();
}
/**
* handling upstream interface change event.
*/
CommandResult NetworkUtils::createNetwork(NetworkParams& aOptions)
{
if (SDK_VERSION < 20) {
return SUCCESS;
}
static CommandFunc COMMAND_CHAIN[] = {
createNetwork,
enableIpv6,
addInterfaceToNetwork,
defaultAsyncSuccessHandler,
};
NetIdManager::NetIdInfo netIdInfo;
mNetIdManager.acquire(GET_FIELD(mIfname), &netIdInfo);
if (netIdInfo.mRefCnt > 1) {
// Already created. Just return.
NU_DBG("Interface %s (%d) has been created.", GET_CHAR(mIfname),
netIdInfo.mNetId);
return SUCCESS;
}
NU_DBG("Request netd to create a network with netid %d", netIdInfo.mNetId);
// Newly created netid. Ask netd to create network.
aOptions.mNetId = netIdInfo.mNetId;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
/**
* handling upstream interface change event.
*/
CommandResult NetworkUtils::destroyNetwork(NetworkParams& aOptions)
{
if (SDK_VERSION < 20) {
return SUCCESS;
}
static CommandFunc COMMAND_CHAIN[] = {
disableIpv6,
destroyNetwork,
defaultAsyncSuccessHandler,
};
NetIdManager::NetIdInfo netIdInfo;
if (!mNetIdManager.release(GET_FIELD(mIfname), &netIdInfo)) {
ERROR("No existing netid for %s", GET_CHAR(mIfname));
return -1;
}
if (netIdInfo.mRefCnt > 0) {
// Still be referenced. Just return.
NU_DBG("Someone is still using this interface.");
return SUCCESS;
}
NU_DBG("Interface %s (%d) is no longer used. Tell netd to destroy.",
GET_CHAR(mIfname), netIdInfo.mNetId);
aOptions.mNetId = netIdInfo.mNetId;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason)
{
NetworkResultOptions result;

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

@ -10,12 +10,55 @@
#include "mozilla/dom/network/NetUtils.h"
#include "mozilla/ipc/Netd.h"
#include "nsTArray.h"
#include "NetIdManager.h"
class NetworkParams;
class CommandChain;
typedef void (*CommandCallback)(CommandChain*, bool,
mozilla::dom::NetworkResultOptions& aResult);
class CommandCallback {
public:
typedef void (*CallbackType)(CommandChain*, bool,
mozilla::dom::NetworkResultOptions& aResult);
typedef void (*CallbackWrapperType)(CallbackType aOriginalCallback,
CommandChain*, bool,
mozilla::dom::NetworkResultOptions& aResult);
CommandCallback()
: mCallback(nullptr)
, mCallbackWrapper(nullptr)
{
}
CommandCallback(CallbackType aCallback)
: mCallback(aCallback)
, mCallbackWrapper(nullptr)
{
}
CommandCallback(CallbackWrapperType aCallbackWrapper,
CommandCallback aOriginalCallback)
: mCallback(aOriginalCallback.mCallback)
, mCallbackWrapper(aCallbackWrapper)
{
}
void operator()(CommandChain* aChain, bool aError,
mozilla::dom::NetworkResultOptions& aResult)
{
if (mCallbackWrapper) {
return mCallbackWrapper(mCallback, aChain, aError, aResult);
}
if (mCallback) {
return mCallback(aChain, aError, aResult);
}
}
private:
CallbackType mCallback;
CallbackWrapperType mCallbackWrapper;
};
typedef void (*CommandFunc)(CommandChain*, CommandCallback,
mozilla::dom::NetworkResultOptions& aResult);
typedef void (*MessageCallback)(mozilla::dom::NetworkResultOptions& aResult);
@ -109,6 +152,7 @@ public:
#undef COPY_FIELD
}
// Followings attributes are 1-to-1 mapping to NetworkCommandOptions.
int32_t mId;
nsString mCmd;
nsString mDomain;
@ -152,6 +196,9 @@ public:
long mGateway_long;
long mDns1_long;
long mDns2_long;
// Auxiliary information required to carry accros command chain.
int mNetId; // A locally defined id per interface.
};
// CommandChain store the necessary information to execute command one by one.
@ -258,6 +305,16 @@ private:
CommandResult setUSBTethering(NetworkParams& aOptions);
CommandResult enableUsbRndis(NetworkParams& aOptions);
CommandResult updateUpStream(NetworkParams& aOptions);
CommandResult createNetwork(NetworkParams& aOptions);
CommandResult destroyNetwork(NetworkParams& aOptions);
CommandResult addHostRouteLegacy(NetworkParams& aOptions);
CommandResult removeHostRouteLegacy(NetworkParams& aOptions);
CommandResult removeHostRoutesLegacy(NetworkParams& aOptions);
CommandResult setDefaultRouteLegacy(NetworkParams& aOptions);
CommandResult removeDefaultRouteLegacy(NetworkParams& aOptions);
CommandResult removeNetworkRouteLegacy(NetworkParams& aOptions);
/**
* function pointer array holds all netd commands should be executed
@ -277,7 +334,6 @@ private:
static const CommandFunc sNetworkInterfaceEnableAlarmChain[];
static const CommandFunc sNetworkInterfaceDisableAlarmChain[];
static const CommandFunc sNetworkInterfaceSetAlarmChain[];
static const CommandFunc sSetDnsChain[];
/**
* Individual netd command stored in command chain.
@ -318,7 +374,23 @@ private:
static void updateUpStreamSuccess(PARAMS);
static void setDhcpServerSuccess(PARAMS);
static void wifiOperationModeSuccess(PARAMS);
static void setDnsSuccess(PARAMS);
static void clearAddrForInterface(PARAMS);
static void createNetwork(PARAMS);
static void destroyNetwork(PARAMS);
static void addInterfaceToNetwork(PARAMS);
static void addDefaultRouteToNetwork(PARAMS);
static void setDefaultNetwork(PARAMS);
static void removeDefaultRoute(PARAMS);
static void removeNetworkRouteSuccess(PARAMS);
static void removeNetworkRoute(PARAMS);
static void addRouteToInterface(PARAMS);
static void removeRouteFromInterface(PARAMS);
static void modifyRouteOnInterface(PARAMS, bool aDoAdd);
static void enableIpv6(PARAMS);
static void disableIpv6(PARAMS);
static void setIpv6Enabled(PARAMS, bool aEnabled);
static void defaultAsyncSuccessHandler(PARAMS);
#undef PARAMS
/**
@ -333,6 +405,7 @@ private:
static void setDhcpServerFail(PARAMS);
static void networkInterfaceAlarmFail(PARAMS);
static void setDnsFail(PARAMS);
static void defaultAsyncFailureHandler(PARAMS);
#undef PARAMS
/**
@ -370,6 +443,8 @@ private:
const CommandFunc (&aCmds)[N],
ErrorCallback aError);
static nsCString getSubnetIp(const nsCString& aIp, int aPrefixLength);
/**
* Callback function to send netd result to main thread.
*/
@ -379,6 +454,8 @@ private:
* Utility class to access libnetutils.
*/
nsAutoPtr<NetUtils> mNetUtils;
NetIdManager mNetIdManager;
};
#endif

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

@ -44,6 +44,7 @@ UNIFIED_SOURCES += [
'MozMtpDatabase.cpp',
'MozMtpServer.cpp',
'MozMtpStorage.cpp',
'NetIdManager.cpp',
'NetworkUtils.cpp',
'NetworkWorker.cpp',
'nsVolume.cpp',

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

@ -159,9 +159,12 @@ interface nsIDhcpRequestCallback : nsISupports
/**
* Provide network services.
*/
[scriptable, uuid(9f1d78e0-1314-11e4-9191-0800200c9a66)]
[scriptable, uuid(f7e23599-176a-435b-9ecf-7e5274bdfa76)]
interface nsINetworkService : nsISupports
{
const long MODIFY_ROUTE_ADD = 0;
const long MODIFY_ROUTE_REMOVE = 1;
/**
* Enable or disable Wifi Tethering
*
@ -314,36 +317,28 @@ interface nsINetworkService : nsISupports
void removeDefaultRoute(in nsINetworkInterface networkInterface);
/**
* Add host route.
* Modify route.
*
* @param action
* nsINetworkService.MODIFY_ROUTE_ADD to add route and
* nsINetworkService.MODIFY_ROUTE_REMOVE to remove.
* @param interfaceName
* Network interface name for the output of the host route.
* @param gateway
* Gateway ip for the output of the host route.
* @param host
* Host ip we want to add route for.
*
* @return A deferred promise that resolves on success or rejects with a
* specified reason otherwise.
*/
jsval addHostRoute(in DOMString interfaceName, in DOMString gateway,
in DOMString host);
/**
* Remove host route.
*
* @param interfaceName
* Network interface name for the output of the host route.
* @param gateway
* Gateway ip for the output of the host route.
* @param host
* Host ip we want to remove route for.
* @param prefixLength
* The prefix length of the route we'd like to modify.
* @param [optional] gateway
* Gateway ip for the output of the host route.
*
* @return A deferred promise that resolves on success or rejects with a
* specified reason otherwise.
*/
jsval removeHostRoute(in DOMString interfaceName, in DOMString gateway,
in DOMString host);
jsval modifyRoute(in long action,
in DOMString interfaceName,
in DOMString host,
in long prefixLength,
[optional] in DOMString gateway);
/**
* Remove all host routes.
@ -470,4 +465,28 @@ interface nsINetworkService : nsISupports
*/
void resetConnections(in DOMString interfaceName,
in nsINativeCommandCallback callback);
/**
* Create network (required to call prior to any networking operation)
*
* @param networkInterface
* The network interface name which we want to reset.
*
* @param callback
* Callback to notify the result of resetting connections.
*/
void createNetwork(in DOMString interfaceName,
in nsINativeCommandCallback callback);
/**
* Destroy network (required to call prior to any networking operation)
*
* @param networkInterface
* The network interface name which we want to reset.
*
* @param callback
* Callback to notify the result of resetting connections.
*/
void destroyNetwork(in DOMString interfaceName,
in nsINativeCommandCallback callback);
};

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

@ -15503,7 +15503,7 @@ ICCContactHelperObject.prototype = {
*/
readAllPhonebookSets: function(pbrs, onsuccess, onerror) {
let allContacts = [], pbrIndex = 0;
let readPhonebook = function readPhonebook(contacts) {
let readPhonebook = function(contacts) {
if (contacts) {
allContacts = allContacts.concat(contacts);
}

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

@ -125,15 +125,219 @@ add_test(function test_read_icc_contacts() {
let record = context.ICCRecordHelper;
let contactHelper = context.ICCContactHelper;
let ril = context.RIL;
let test_data = [
//Record 1.
{
comment: "Test read SIM adn contact",
rawData: {
simType: CARD_APPTYPE_SIM,
contactType: GECKO_CARDCONTACT_TYPE_ADN,
adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
},
expectedContact: [{
recordId: 1,
alphaId: "name",
number: "111111"
}],
},
//Record 2.
{
comment: "Test read SIM fdn contact",
rawData: {
simType: CARD_APPTYPE_SIM,
contactType: GECKO_CARDCONTACT_TYPE_FDN,
adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
},
expectedContact: [{
recordId: 1,
alphaId: "name",
number: "111111"
}],
},
//Record 3.
{
comment: "Test read USIM adn contact",
rawData: {
simType: CARD_APPTYPE_USIM,
contactType: GECKO_CARDCONTACT_TYPE_ADN,
pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}],
adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
email: "hello@mail.com",
anr: "123456",
},
expectedContact: [{
pbrIndex: 0,
recordId: 1,
alphaId: "name",
number: "111111",
email: "hello@mail.com",
anr: ["123456"]
}],
},
//Record 4.
{
comment: "Test read USIM adn contacts",
rawData: {
simType: CARD_APPTYPE_USIM,
contactType: GECKO_CARDCONTACT_TYPE_ADN,
pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}},
{adn:{fileId: 0x6f3b}, email: {}, anr0: {}}],
adnLike: [{recordId: 1, alphaId: "name1", number: "111111"},
{recordId: 2, alphaId: "name2", number: "222222"}],
email: "hello@mail.com",
anr: "123456",
},
expectedContact: [
{
pbrIndex: 0,
recordId: 1,
alphaId: "name1",
number: "111111",
email: "hello@mail.com",
anr: ["123456"]
}, {
pbrIndex: 0,
recordId: 2,
alphaId: "name2",
number: "222222",
email: "hello@mail.com",
anr: ["123456"]
}, {
pbrIndex: 1,
recordId: 1,
alphaId: "name1",
number: "111111",
email: "hello@mail.com",
anr: ["123456"]
}, {
pbrIndex: 1,
recordId: 2,
alphaId: "name2",
number: "222222",
email: "hello@mail.com",
anr: ["123456"]
}
],
},
//Record 5.
{
comment: "Test read USIM fdn contact",
rawData: {
simType: CARD_APPTYPE_USIM,
contactType: GECKO_CARDCONTACT_TYPE_FDN,
adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
},
expectedContact: [{
recordId: 1,
alphaId: "name",
number: "111111"
}],
},
//Record 6.
{
comment: "Test read RUIM adn contact",
rawData: {
simType: CARD_APPTYPE_RUIM,
contactType: GECKO_CARDCONTACT_TYPE_ADN,
adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
},
expectedContact: [{
recordId: 1,
alphaId: "name",
number: "111111"
}],
},
//Record 7.
{
comment: "Test read RUIM fdn contact",
rawData: {
simType: CARD_APPTYPE_RUIM,
contactType: GECKO_CARDCONTACT_TYPE_FDN,
adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
},
expectedContact: [{
recordId: 1,
alphaId: "name",
number: "111111"
}],
},
//Record 8.
{
comment: "Test read RUIM adn contact with enhanced phone book",
rawData: {
simType: CARD_APPTYPE_RUIM,
contactType: GECKO_CARDCONTACT_TYPE_ADN,
pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}],
adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
email: "hello@mail.com",
anr: "123456",
enhancedPhoneBook: true,
},
expectedContact: [{
pbrIndex: 0,
recordId: 1,
alphaId: "name",
number: "111111",
email: "hello@mail.com",
anr: ["123456"]
}],
},
//Record 9.
{
comment: "Test read RUIM adn contacts with enhanced phone book",
rawData: {
simType: CARD_APPTYPE_RUIM,
contactType: GECKO_CARDCONTACT_TYPE_ADN,
pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}},
{adn:{fileId: 0x6f3b}, email: {}, anr0: {}}],
adnLike: [{recordId: 1, alphaId: "name1", number: "111111"},
{recordId: 2, alphaId: "name2", number: "222222"}],
email: "hello@mail.com",
anr: "123456",
enhancedPhoneBook: true,
},
expectedContact: [
{
pbrIndex: 0,
recordId: 1,
alphaId: "name1",
number: "111111",
email: "hello@mail.com",
anr: ["123456"]
}, {
pbrIndex: 0,
recordId: 2,
alphaId: "name2",
number: "222222",
email: "hello@mail.com",
anr: ["123456"]
}, {
pbrIndex: 1,
recordId: 1,
alphaId: "name1",
number: "111111",
email: "hello@mail.com",
anr: ["123456"]
}, {
pbrIndex: 1,
recordId: 2,
alphaId: "name2",
number: "222222",
email: "hello@mail.com",
anr: ["123456"]
}
],
},
];
function do_test(aSimType, aContactType, aExpectedContact, aEnhancedPhoneBook) {
ril.appType = aSimType;
ril._isCdma = (aSimType === CARD_APPTYPE_RUIM);
ril.iccInfoPrivate.cst = (aEnhancedPhoneBook) ?
function do_test(aTestData, aExpectedContact) {
ril.appType = aTestData.simType;
ril._isCdma = (aTestData.simType === CARD_APPTYPE_RUIM);
ril.iccInfoPrivate.cst = (aTestData.enhancedPhoneBook) ?
[0x20, 0x0C, 0x0, 0x0, 0x0]:
[0x20, 0x00, 0x0, 0x0, 0x0];
ril.iccInfoPrivate.sst = (aSimType === CARD_APPTYPE_SIM)?
ril.iccInfoPrivate.sst = (aTestData.simType === CARD_APPTYPE_SIM)?
[0x20, 0x0, 0x0, 0x0, 0x0]:
[0x2, 0x0, 0x0, 0x0, 0x0];
@ -143,30 +347,25 @@ add_test(function test_read_icc_contacts() {
};
record.readPBR = function readPBR(onsuccess, onerror) {
onsuccess([{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}]);
onsuccess(JSON.parse(JSON.stringify(aTestData.pbrs)));
};
record.readADNLike = function readADNLike(fileId, onsuccess, onerror) {
onsuccess([{recordId: 1, alphaId: "name", number: "111111"}])
onsuccess(JSON.parse(JSON.stringify(aTestData.adnLike)));
};
record.readEmail = function readEmail(fileId, fileType, recordNumber, onsuccess, onerror) {
onsuccess("hello@mail.com");
onsuccess(aTestData.email);
};
record.readANR = function readANR(fileId, fileType, recordNumber, onsuccess, onerror) {
onsuccess("123456");
onsuccess(aTestData.anr);
};
let onsuccess = function onsuccess(contacts) {
let contact = contacts[0];
for (let key in contact) {
do_print("check " + key);
if (Array.isArray(contact[key])) {
do_check_eq(contact[key][0], aExpectedContact[key]);
} else {
do_check_eq(contact[key], aExpectedContact[key]);
}
for (let i = 0; i < contacts.length; i++) {
do_print("check contacts[" + i + "]:" + JSON.stringify(contacts[i]));
deepEqual(contacts[i], aExpectedContact[i]);
}
};
@ -175,52 +374,13 @@ add_test(function test_read_icc_contacts() {
do_check_true(false);
};
contactHelper.readICCContacts(aSimType, aContactType, onsuccess, onerror);
contactHelper.readICCContacts(aTestData.simType, aTestData.contactType, onsuccess, onerror);
}
let expectedContact1 = {
pbrIndex: 0,
recordId: 1,
alphaId: "name",
number: "111111"
};
let expectedContact2 = {
pbrIndex: 0,
recordId: 1,
alphaId: "name",
number: "111111",
email: "hello@mail.com",
anr: "123456"
};
// SIM
do_print("Test read SIM adn contacts");
do_test(CARD_APPTYPE_SIM, GECKO_CARDCONTACT_TYPE_ADN, expectedContact1);
do_print("Test read SIM fdn contacts");
do_test(CARD_APPTYPE_SIM, GECKO_CARDCONTACT_TYPE_FDN, expectedContact1);
// USIM
do_print("Test read USIM adn contacts");
do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, expectedContact2);
do_print("Test read USIM fdn contacts");
do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_FDN, expectedContact1);
// RUIM
do_print("Test read RUIM adn contacts");
do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, expectedContact1);
do_print("Test read RUIM fdn contacts");
do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_FDN, expectedContact1);
// RUIM with enhanced phone book
do_print("Test read RUIM adn contacts with enhanced phone book");
do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, expectedContact2, true);
do_print("Test read RUIM fdn contacts with enhanced phone book");
do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_FDN, expectedContact1, true);
for (let i = 0; i < test_data.length; i++) {
do_print(test_data[i].comment);
do_test(test_data[i].rawData, test_data[i].expectedContact);
}
run_next_test();
});

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

@ -203,6 +203,39 @@ methods of other kinds of objects.
* and the fourth line begins at offset 10.
`getAllColumnOffsets()`:
: Return an array describing the relationship between bytecode instruction
offsets and source code positions in this script. Unlike getAllOffsets(),
which returns all offsets that are entry points for each line,
getAllColumnOffsets() returns all offsets that are entry points for each
(line, column) pair.
The elements of the array are objects, each of which describes a single
entry point, and contains the following properties:
* lineNumber: the line number for which offset is an entry point
* columnNumber: the column number for which offset is an entry point
* offset: the bytecode instruction offset of the entry point
For example, suppose we have a script for the following source code:
```language-js
a=[]
for (i=1; i < 10; i++)
// It's hip to be square.
a[i] = i*i;
```
Calling `getAllColumnOffsets()` on that code might yield an array like this:
```language-js
[{ lineNumber: 0, columnNumber: 0, offset: 0 },
{ lineNumber: 1, columnNumber: 5, offset: 5 },
{ lineNumber: 1, columnNumber: 10, offset: 20 },
{ lineNumber: 3, columnNumber: 4, offset: 10 }]
<code>getLineOffsets(<i>line</i>)</code>
: Return an array of bytecode instruction offsets representing the entry
points to source line <i>line</i>. If the script contains no executable
@ -269,4 +302,6 @@ methods of other kinds of objects.
<i>offset</i> is not a valid bytecode offset in this script, throw an
error.
<code>isInCatchScope([<i>offset</i>])</code>
: This is `true` if this offset falls within the scope of a try block, and
`false` otherwise.

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

@ -4,6 +4,13 @@
package org.mozilla.gecko.tests;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.util.GeckoRequest;
import org.mozilla.gecko.util.NativeJSObject;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Instrumentation;
import android.os.SystemClock;
import android.util.FloatMath;
@ -160,4 +167,29 @@ class MotionEventHelper {
tap(x, y);
tap(x, y);
}
/**
* dragSync() can accidentally trigger longpress events on slower devices.
* Consumers can instruct Gecko to ignore them.
*/
public void disableGeckoLongpress() {
final JSONObject json = new JSONObject();
try {
json.put("isLongPressEnabled", 0);
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error - Error creating request to ignore longpress events.", e);
return;
}
GeckoAppShell.sendRequestToGecko(new GeckoRequest("ContextMenu:SetIsLongpressEnabled", json) {
@Override
public void onResponse(NativeJSObject response) {
Log.d(LOGTAG, "Gecko received request to ignore longpress events.");
}
@Override
public void onError(NativeJSObject error) {
Log.d(LOGTAG, "No response from Gecko on request to ignore longpress events.");
}
});
}
}

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

@ -26,6 +26,9 @@ public class testAxisLocking extends PixelTest {
// load page and check we're at 0,0
loadAndVerifyBoxes(url);
// Tell Gecko to ignore any longpress events triggered by MEH.
meh.disableGeckoLongpress();
// drag page upwards by 100 pixels with a slight angle. verify that
// axis locking prevents any horizontal scrolling
Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint();

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

@ -36,6 +36,9 @@ public class testCheck extends PixelTest {
mDriver.startCheckerboardRecording();
// Tell Gecko to ignore any longpress events triggered by MEH.
meh.disableGeckoLongpress();
// Scroll repeatedly downwards, then upwards. On each iteration of i,
// increase the scroll distance to test different scroll amounts.
for (int i = 2; i < 7; i++) {

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

@ -24,6 +24,9 @@ public class testFlingCorrectness extends PixelTest {
// load page and check we're at 0,0
loadAndVerifyBoxes(url);
// Tell Gecko to ignore any longpress events triggered by MEH.
meh.disableGeckoLongpress();
// drag page upwards by 200 pixels (use two drags instead of one in case
// the screen size is small)
Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint();

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

@ -24,6 +24,9 @@ public class testPanCorrectness extends PixelTest {
// load page and check we're at 0,0
loadAndVerifyBoxes(url);
// Tell Gecko to ignore any longpress events triggered by MEH.
meh.disableGeckoLongpress();
// drag page upwards by 100 pixels
Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint();
meh.dragSync(10, 150, 10, 50);

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

@ -30,6 +30,9 @@ public class testVkbOverlap extends PixelTest {
// scroll to the bottom of the page and let it settle
Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint();
MotionEventHelper meh = new MotionEventHelper(getInstrumentation(), mDriver.getGeckoLeft(), mDriver.getGeckoTop());
// Tell Gecko to ignore any longpress events triggered by MEH.
meh.disableGeckoLongpress();
meh.dragSync(10, 150, 10, 50);
// the input field has a green background, so let's count the number of green pixels

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

@ -2267,9 +2267,14 @@ var NativeWindow = {
contextmenus: {
items: {}, // a list of context menu items that we may show
DEFAULT_HTML5_ORDER: -1, // Sort order for HTML5 context menu items
_isLongPressEnabled: 1, // Android longpress events can be ignored during robocop tests.
init: function() {
BrowserApp.deck.addEventListener("contextmenu", this.show.bind(this), false);
Messaging.addListener((data) => {
return {result: (this._isLongPressEnabled = data.isLongPressEnabled)};
}, "ContextMenu:SetIsLongpressEnabled");
},
add: function() {
@ -2562,6 +2567,11 @@ var NativeWindow = {
* for chrome consumers to do lazy menuitem construction
*/
show: function(event) {
if (!this._isLongPressEnabled) {
dump("Longpress Event is ignored by request");
return;
}
// Android Long-press / contextmenu event provides clientX/Y data. This is not provided
// by mochitest: test_browserElement_inproc_ContextmenuEvents.html.
if (!event.clientX || !event.clientY) {

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

@ -1752,8 +1752,8 @@ class Mochitest(MochitestUtilsMixin):
self.setTestRoot(options)
# Until we have all green, this only runs on bc* jobs (not dt* jobs)
if options.browserChrome and not options.subsuite:
# Until we have all green, this only runs on bc*/dt* jobs
if options.browserChrome:
options.runByDir = True
if not options.runByDir:

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

@ -148,7 +148,7 @@ function ensureModuleIsOpen() {
* @param args
* array of arguments to pass to the notification.
*/
function notify(observers, notification, args) {
function notify(observers, notification, args = []) {
for (let observer of observers) {
try {
observer[notification](...args);
@ -253,7 +253,7 @@ this.History = Object.freeze({
* A callback invoked for each page found.
*
* @return (Promise)
* A promise resoled once the operation is complete.
* A promise resolved once the operation is complete.
* @resolve (bool)
* `true` if at least one page was removed, `false` otherwise.
* @throws (TypeError)
@ -328,7 +328,7 @@ this.History = Object.freeze({
* The full URI of the page or the GUID of the page.
*
* @return (Promise)
* A promise resoled once the operation is complete.
* A promise resolved once the operation is complete.
* @resolve (bool)
* `true` if the page has been visited, `false` otherwise.
* @throws (Error)
@ -339,6 +339,28 @@ this.History = Object.freeze({
throw new Error("Method not implemented");
},
/**
* Clear all history.
*
* @return (Promise)
* A promise resolved once the operation is complete.
*/
clear() {
ensureModuleIsOpen();
return Task.spawn(function* () {
let promise = clear();
operationsBarrier.client.addBlocker("History.clear", promise);
try {
return (yield promise);
} finally {
// Cleanup the barrier.
operationsBarrier.client.removeBlocker(promise);
}
});
},
/**
* Possible values for the `transition` property of `VisitInfo`
* objects.
@ -453,6 +475,34 @@ let invalidateFrecencies = Task.async(function*(db, idList) {
);
});
// Inner implementation of History.clear().
let clear = Task.async(function* () {
let db = yield DBConnPromised;
// Remove all history.
yield db.execute("DELETE FROM moz_historyvisits");
// Clear the registered embed visits.
PlacesUtils.history.clearEmbedVisits();
// Expiration will take care of orphans.
let observers = PlacesUtils.history.getObservers();
notify(observers, "onClearHistory");
// Invalidate frecencies for the remaining places. This must happen
// after the notification to ensure it runs enqueued to expiration.
yield db.execute(
`UPDATE moz_places SET frecency =
(CASE
WHEN url BETWEEN 'place:' AND 'place;'
THEN 0
ELSE -1
END)
WHERE frecency > 0`);
// Notify frecency change observers.
notify(observers, "onManyFrecenciesChanged");
});
// Inner implementation of History.remove.
let remove = Task.async(function*({guids, urls}, onResult = null) {

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

@ -29,7 +29,7 @@ XPCOMUtils.defineLazyGetter(this, "localFileCtor",
"nsILocalFile", "initWithPath"));
XPCOMUtils.defineLazyGetter(this, "filenamesRegex",
() => new RegExp("^bookmarks-([0-9\-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=\+\-]{24})){0,1}\.(json(lz4)?)$", "i")
() => /^bookmarks-([0-9-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=+-]{24})){0,1}\.(json(lz4)?)$/i
);
/**
@ -265,7 +265,7 @@ this.PlacesBackups = {
"https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
for (let i = 0; i < this._entries.length; i++) {
let rx = new RegExp("\.json(lz4)?$");
let rx = /\.json(lz4)?$/;
if (this._entries[i].leafName.match(rx))
return this._entries[i];
}
@ -282,7 +282,7 @@ this.PlacesBackups = {
return Task.spawn(function* () {
let entries = yield this.getBackupFiles();
for (let entry of entries) {
let rx = new RegExp("\.json(lz4)?$");
let rx = /\.json(lz4)?$/;
if (OS.Path.basename(entry).match(rx)) {
return entry;
}