Merge m-c to inbound, a=merge

This commit is contained in:
Wes Kocher 2015-11-11 17:12:26 -08:00
commit e985043b20
65 changed files with 1716 additions and 538 deletions

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -19,10 +19,10 @@
<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="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cb4604d5a578efd027277059ce3e0f6e3af59bd1"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f009c98ba697582c857c5788e5cdf0640e287ae6"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
@ -127,12 +127,12 @@
<default remote="caf" revision="refs/tags/android-4.4.2_r1" sync-j="4"/>
<!-- Emulator specific things -->
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="72ffdf71c68a96309212eb13d63560d66db14c9e"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="8d4018ebd33ac3f1a043b2d54bc578028656a659"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="58800ecb50e4e41cfb0a36cb43c82b73fb3612e5"/>
<project name="platform_bionic" path="bionic" remote="b2g" revision="3e85c4683c121530c1c3a48c696a569bf5f587e2"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="5a50f96a1d7c788817abb7c57acbb75172c1f48d"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="f37bd545063039e30a92f2550ae78c0e6e4e2d08"/>
<project name="platform_external_wpa_supplicant_8" path="external/wpa_supplicant_8" remote="b2g" revision="0c6a6547cd1fd302fa2b0f6e375654df36bf0ec4"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="aa763fa9180a222547824ae6b6064e4851c15a86"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="29cbaa03a380ab69d47c476dd433059f7680837c"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
<project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/>
<project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="6a1bb59af65b6485b1090522f66fac95c3f9e22c"/>

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -19,10 +19,10 @@
<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="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cb4604d5a578efd027277059ce3e0f6e3af59bd1"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "22f8023b112dfae83531b0a075ab9eb9a5444dfa",
"git_revision": "fdb66f75963fa9255f707af87f405d54892e5e7d",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "fbb1b70ce9fdf934ff09885898d9ce5fa9f87b57",
"revision": "3b39cf1a2b4cc636b84a437475db0efa9bb96c00",
"repo_path": "integration/gaia-central"
}

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -18,8 +18,8 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f009c98ba697582c857c5788e5cdf0640e287ae6"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>

View File

@ -15,10 +15,10 @@
<project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="22f8023b112dfae83531b0a075ab9eb9a5444dfa"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="fdb66f75963fa9255f707af87f405d54892e5e7d"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9a58f2e395da17c252f61f28900b5b09aeb813bd"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="aa5b7b7f6ed207ea1adc4df11d1d8bdaeabadd85"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>

View File

@ -2836,8 +2836,7 @@ var BrowserOnClick = {
.getService(Ci.nsIWeakCryptoOverride);
weakCryptoOverride.addWeakCryptoOverride(
msg.data.location.hostname,
PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser),
true /* temporary */);
PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
break;
}
},
@ -7140,6 +7139,7 @@ var gIdentityHandler = {
if (shouldHidePopup) {
this._identityPopup.hidePopup();
}
this.showWeakCryptoInfoBar();
// NOTE: We do NOT update the identity popup (the control center) when
// we receive a new security state on the existing page (i.e. from a
@ -7288,6 +7288,55 @@ var gIdentityHandler = {
this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
},
/**
* Show the weak crypto notification bar.
*/
showWeakCryptoInfoBar() {
if (!this._uriHasHost || !this._isBroken || !this._sslStatus.cipherName ||
this._sslStatus.cipherName.indexOf("_RC4_") < 0) {
return;
}
let notificationBox = gBrowser.getNotificationBox();
let notification = notificationBox.getNotificationWithValue("weak-crypto");
if (notification) {
return;
}
let brandBundle = document.getElementById("bundle_brand");
let brandShortName = brandBundle.getString("brandShortName");
let message = gNavigatorBundle.getFormattedString("weakCryptoOverriding.message",
[brandShortName]);
let host = this._uri.host;
let port = 443;
try {
if (this._uri.port > 0) {
port = this._uri.port;
}
} catch (e) {}
let buttons = [{
label: gNavigatorBundle.getString("revokeOverride.label"),
accessKey: gNavigatorBundle.getString("revokeOverride.accesskey"),
callback: function (aNotification, aButton) {
try {
let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
.getService(Ci.nsIWeakCryptoOverride);
weakCryptoOverride.removeWeakCryptoOverride(host, port,
PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
} catch (e) {
Cu.reportError(e);
}
}
}];
const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
notificationBox.appendNotification(message, "weak-crypto", null,
priority, buttons);
},
/**
* Set up the title and content messages for the identity message popup,
* based on the specified mode, and the details of the SSL cert, where

View File

@ -2,8 +2,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Load directly from the browser-chrome support files of login tests.
const testUrlPath =
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
@ -22,8 +21,13 @@ add_task(function* test_simple() {
"set": [["security.insecure_password.ui.enabled", true]],
}, resolve));
for (let scheme of ["http", "https"]) {
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
for (let [origin, expectWarning] of [
["http://example.com", true],
["http://127.0.0.1", false],
["https://example.com", false],
]) {
let testUrlPath = origin + TEST_URL_PATH;
let tab = gBrowser.addTab(testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
@ -36,7 +40,7 @@ add_task(function* test_simple() {
gIdentityHandler._identityBox.click();
document.getElementById("identity-popup-security-expander").click();
if (scheme == "http") {
if (expectWarning) {
let identityBoxImage = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("page-proxy-favicon"), "")
.getPropertyValue("list-style-image");
@ -64,8 +68,8 @@ add_task(function* test_simple() {
// the scheme is HTTPS.
is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
element => !is_hidden(element)),
scheme == "http",
"The relevant messages should visible or hidden.");
expectWarning,
"The relevant messages should be visible or hidden.");
gIdentityHandler._identityPopup.hidden = true;
gBrowser.removeTab(tab);
@ -82,6 +86,7 @@ add_task(function* test_mixedcontent() {
}, resolve));
// Load the page with the subframe in a new tab.
let testUrlPath = "://example.com" + TEST_URL_PATH;
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([

View File

@ -391,8 +391,7 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
vertical-align: middle;
}
.room-entry-context-item > img {
height: 16px;
.room-entry-context-item > a > img {
width: 16px;
}

View File

@ -798,6 +798,11 @@ muteTab.accesskey = M
unmuteTab.label = Unmute Tab
unmuteTab.accesskey = M
# LOCALIZATION NOTE (weakCryptoOverriding.message): %S is brandShortName
weakCryptoOverriding.message = %S recommends that you don't enter your password, credit card and other personal information on this website.
revokeOverride.label = Don't Trust This Website
revokeOverride.accesskey = D
# LOCALIZATION NOTE (tabgroups.deprecationwarning.description):
# %S is brandShortName
tabgroups.deprecationwarning.description = Heads up! Tab Groups will be removed from %S soon.

View File

@ -2,9 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
// shared-head.js handles imports, constants, and utility functions
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
// Disable logging for faster test runs. Set this pref to true if you want to
// debug a test in your try runs. Both the debugger server and frontend will
@ -12,31 +11,20 @@ var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Services.prefs.setBoolPref("devtools.debugger.log", false);
var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
var { Promise: promise } = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {});
var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
var { DebuggerServer } = require("devtools/server/main");
var { DebuggerClient, ObjectClient } = require("devtools/shared/client/main");
var { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
var EventEmitter = require("devtools/shared/event-emitter");
const { promiseInvoke } = require("devtools/shared/async-utils");
var { TargetFactory } = require("devtools/client/framework/target");
var { Toolbox } = require("devtools/client/framework/toolbox")
// Override promise with deprecated-sync-thenables
promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/test/mochitest/";
const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js";
DevToolsUtils.testing = true;
SimpleTest.registerCleanupFunction(() => {
DevToolsUtils.testing = false;
});
// All tests are asynchronous.
waitForExplicitFinish();
registerCleanupFunction(function* () {
info("finish() was called, cleaning up...");
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
@ -84,7 +72,9 @@ function getChromeWindow(aWindow) {
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
}
function addTab(aUrl, aWindow) {
// Override addTab/removeTab as defined by shared-head, since these have
// an extra window parameter and add a frame script
this.addTab = function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
let deferred = promise.defer();
@ -107,7 +97,7 @@ function addTab(aUrl, aWindow) {
return deferred.promise;
}
function removeTab(aTab, aWindow) {
this.removeTab = function removeTab(aTab, aWindow) {
info("Removing tab.");
let deferred = promise.defer();
@ -1215,30 +1205,3 @@ function getSplitConsole(toolbox, win) {
});
});
}
// This can be removed once debugger uses shared-head.js (bug 1181838)
function synthesizeKeyFromKeyTag(key) {
is(key && key.tagName, "key", "Successfully retrieved the <key> node");
let modifiersAttr = key.getAttribute("modifiers");
let name = null;
if (key.getAttribute("keycode"))
name = key.getAttribute("keycode");
else if (key.getAttribute("key"))
name = key.getAttribute("key");
isnot(name, null, "Successfully retrieved keycode/key");
let modifiers = {
shiftKey: !!modifiersAttr.match("shift"),
ctrlKey: !!modifiersAttr.match("control"),
altKey: !!modifiersAttr.match("alt"),
metaKey: !!modifiersAttr.match("meta"),
accelKey: !!modifiersAttr.match("accel")
};
info("Synthesizing key " + name + " " + JSON.stringify(modifiers));
EventUtils.synthesizeKey(name, modifiers);
}

View File

@ -162,6 +162,12 @@ Selection.prototype = {
setNodeFront: function(value, reason="unknown") {
this.reason = reason;
// If a singleTextChild text node is being set, then set it's parent instead.
let parentNode = value && value.parentNode();
if (value && parentNode && parentNode.singleTextChild === value) {
value = parentNode;
}
// We used to return here if the node had not changed but we now need to
// set the node even if it is already set otherwise it is not possible to
// e.g. highlight the same node twice.

View File

@ -20,7 +20,7 @@ const {require} = scopedCuImport("resource://devtools/shared/Loader.jsm");
const {TargetFactory} = require("devtools/client/framework/target");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const promise = require("promise");
let promise = require("promise");
const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
const CHROME_URL_ROOT = TEST_DIR + "/";

View File

@ -16,7 +16,7 @@ var {HostType} = require("devtools/client/framework/toolbox").Toolbox;
loader.lazyGetter(this, "MarkupView", () => require("devtools/client/markupview/markup-view").MarkupView);
loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/client/inspector/breadcrumbs").HTMLBreadcrumbs);
loader.lazyGetter(this, "ToolSidebar", () => require("devtools/client/framework/sidebar").ToolSidebar);
loader.lazyGetter(this, "SelectorSearch", () => require("devtools/client/inspector/selector-search").SelectorSearch);
loader.lazyGetter(this, "InspectorSearch", () => require("devtools/client/inspector/inspector-search").InspectorSearch);
loader.lazyGetter(this, "strings", () => {
return Services.strings.createBundle("chrome://devtools/locale/inspector.properties");
@ -78,7 +78,20 @@ function InspectorPanel(iframeWindow, toolbox) {
this.panelWin.inspector = this;
this.nodeMenuTriggerInfo = null;
this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
this.onNewRoot = this.onNewRoot.bind(this);
this._setupNodeMenu = this._setupNodeMenu.bind(this);
this._resetNodeMenu = this._resetNodeMenu.bind(this);
this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
this.onNewSelection = this.onNewSelection.bind(this);
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
this.onDetached = this.onDetached.bind(this);
this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
this._target.on("will-navigate", this._onBeforeNavigate);
EventEmitter.decorate(this);
@ -139,31 +152,23 @@ InspectorPanel.prototype = {
_deferredOpen: function(defaultSelection) {
let deferred = promise.defer();
this.onNewRoot = this.onNewRoot.bind(this);
this.walker.on("new-root", this.onNewRoot);
this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
this.lastNodemenuItem = this.nodemenu.lastChild;
this._setupNodeMenu = this._setupNodeMenu.bind(this);
this._resetNodeMenu = this._resetNodeMenu.bind(this);
this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
this.onNewSelection = this.onNewSelection.bind(this);
this.selection.on("new-node-front", this.onNewSelection);
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
this.selection.on("before-new-node-front", this.onBeforeNewSelection);
this.onDetached = this.onDetached.bind(this);
this.selection.on("detached-front", this.onDetached);
this.breadcrumbs = new HTMLBreadcrumbs(this);
this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
this._toolbox.on("host-changed", this.onToolboxHostChanged);
if (this.target.isLocalTab) {
this.browser = this.target.tab.linkedBrowser;
this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
// Show a warning when the debugger is paused.
@ -309,13 +314,31 @@ InspectorPanel.prototype = {
* Hooks the searchbar to show result and auto completion suggestions.
*/
setupSearchBox: function() {
// Initiate the selectors search object.
if (this.searchSuggestions) {
this.searchSuggestions.destroy();
this.searchSuggestions = null;
}
this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
this.searchSuggestions = new SelectorSearch(this, this.searchBox);
this.searchResultsLabel = this.panelDoc.getElementById("inspector-searchlabel");
this.search = new InspectorSearch(this, this.searchBox);
this.search.on("search-cleared", this._updateSearchResultsLabel);
this.search.on("search-result", this._updateSearchResultsLabel);
},
get searchSuggestions() {
return this.search.autocompleter;
},
_updateSearchResultsLabel: function(event, result) {
let str = "";
if (event !== "search-cleared") {
if (result) {
str = strings.formatStringFromName(
"inspector.searchResultsCount2",
[result.resultsIndex + 1, result.resultsLength], 2);
} else {
str = strings.GetStringFromName("inspector.searchResultsNone");
}
}
this.searchResultsLabel.textContent = str;
},
/**
@ -369,7 +392,6 @@ InspectorPanel.prototype = {
*/
setupSidebarToggle: function() {
this._paneToggleButton = this.panelDoc.getElementById("inspector-pane-toggle");
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
this._paneToggleButton.addEventListener("mousedown",
this.onPaneToggleButtonClicked);
this.updatePaneToggleButton();
@ -399,7 +421,6 @@ InspectorPanel.prototype = {
return;
}
this.markup.expandNode(this.selection.nodeFront);
this.setupSearchBox();
this.emit("new-root");
});
};
@ -590,8 +611,6 @@ InspectorPanel.prototype = {
this._paneToggleButton.removeEventListener("mousedown",
this.onPaneToggleButtonClicked);
this._paneToggleButton = null;
this.searchSuggestions.destroy();
this.searchBox = null;
this.selection.off("new-node-front", this.onNewSelection);
this.selection.off("before-new-node", this.onBeforeNewSelection);
this.selection.off("before-new-node-front", this.onBeforeNewSelection);
@ -602,10 +621,12 @@ InspectorPanel.prototype = {
this.panelDoc = null;
this.panelWin = null;
this.breadcrumbs = null;
this.searchSuggestions = null;
this.lastNodemenuItem = null;
this.nodemenu = null;
this._toolbox = null;
this.search.destroy();
this.search = null;
this.searchBox = null;
this._panelDestroyer = promise.all([
sidebarDestroyer,
@ -914,8 +935,7 @@ InspectorPanel.prototype = {
this._markupFrame.setAttribute("context", "inspector-node-popup");
// This is needed to enable tooltips inside the iframe document.
this._boundMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
this._markupFrame.addEventListener("load", this._boundMarkupFrameLoad, true);
this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
this._markupBox.setAttribute("collapsed", true);
this._markupBox.appendChild(this._markupFrame);
@ -924,8 +944,7 @@ InspectorPanel.prototype = {
},
_onMarkupFrameLoad: function() {
this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
delete this._boundMarkupFrameLoad;
this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
this._markupFrame.contentWindow.focus();
@ -940,9 +959,8 @@ InspectorPanel.prototype = {
_destroyMarkup: function() {
let destroyPromise;
if (this._boundMarkupFrameLoad) {
this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
this._boundMarkupFrameLoad = null;
if (this._markupFrame) {
this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
}
if (this.markup) {

View File

@ -4,7 +4,7 @@
"use strict";
const { Cu } = require("chrome");
const {Cu, Ci} = require("chrome");
const promise = require("promise");
loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
@ -13,6 +13,108 @@ loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/client/shar
// Maximum number of selector suggestions shown in the panel.
const MAX_SUGGESTIONS = 15;
/**
* Converts any input field into a document search box.
*
* @param {InspectorPanel} inspector The InspectorPanel whose `walker` attribute
* should be used for document traversal.
* @param {DOMNode} input The input element to which the panel will be attached
* and from where search input will be taken.
*
* Emits the following events:
* - search-cleared: when the search box is emptied
* - search-result: when a search is made and a result is selected
*/
function InspectorSearch(inspector, input) {
this.inspector = inspector;
this.searchBox = input;
this._lastSearched = null;
this._onKeyDown = this._onKeyDown.bind(this);
this._onCommand = this._onCommand.bind(this);
this.searchBox.addEventListener("keydown", this._onKeyDown, true);
this.searchBox.addEventListener("command", this._onCommand, true);
// For testing, we need to be able to wait for the most recent node request
// to finish. Tests can watch this promise for that.
this._lastQuery = promise.resolve(null);
this.autocompleter = new SelectorAutocompleter(inspector, input);
EventEmitter.decorate(this);
}
exports.InspectorSearch = InspectorSearch;
InspectorSearch.prototype = {
get walker() {
return this.inspector.walker;
},
destroy: function() {
this.searchBox.removeEventListener("keydown", this._onKeyDown, true);
this.searchBox.removeEventListener("command", this._onCommand, true);
this.searchBox = null;
this.autocompleter.destroy();
},
_onSearch: function(reverse = false) {
this.doFullTextSearch(this.searchBox.value, reverse)
.catch(e => console.error(e));
},
doFullTextSearch: Task.async(function*(query, reverse) {
let lastSearched = this._lastSearched;
this._lastSearched = query;
if (query.length === 0) {
this.searchBox.classList.remove("devtools-no-search-result");
if (!lastSearched || lastSearched.length > 0) {
this.emit("search-cleared");
}
return;
}
let res = yield this.walker.search(query, { reverse });
// Value has changed since we started this request, we're done.
if (query != this.searchBox.value) {
return;
}
if (res) {
this.inspector.selection.setNodeFront(res.node, "inspectorsearch");
this.searchBox.classList.remove("devtools-no-search-result");
res.query = query;
this.emit("search-result", res);
} else {
this.searchBox.classList.add("devtools-no-search-result");
this.emit("search-result");
}
}),
_onCommand: function() {
if (this.searchBox.value.length === 0) {
this._onSearch();
}
},
_onKeyDown: function(event) {
if (this.searchBox.value.length === 0) {
this.searchBox.removeAttribute("filled");
} else {
this.searchBox.setAttribute("filled", true);
}
if (event.keyCode === event.DOM_VK_RETURN) {
this._onSearch();
} if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_G && event.metaKey) {
this._onSearch(event.shiftKey);
event.preventDefault();
}
}
};
/**
* Converts any input box on a page to a CSS selector search and suggestion box.
*
@ -21,29 +123,19 @@ const MAX_SUGGESTIONS = 15;
* search or not.
*
* @constructor
* @param InspectorPanel aInspector
* @param InspectorPanel inspector
* The InspectorPanel whose `walker` attribute should be used for
* document traversal.
* @param nsiInputElement aInputNode
* @param nsiInputElement inputNode
* The input element to which the panel will be attached and from where
* search input will be taken.
*/
function SelectorSearch(aInspector, aInputNode) {
this.inspector = aInspector;
this.searchBox = aInputNode;
function SelectorAutocompleter(inspector, inputNode) {
this.inspector = inspector;
this.searchBox = inputNode;
this.panelDoc = this.searchBox.ownerDocument;
// initialize variables.
this._lastSearched = null;
this._lastValidSearch = "";
this._lastToLastValidSearch = null;
this._searchResults = null;
this._searchSuggestions = {};
this._searchIndex = 0;
// bind!
this._showPopup = this._showPopup.bind(this);
this._onHTMLSearch = this._onHTMLSearch.bind(this);
this.showSuggestions = this.showSuggestions.bind(this);
this._onSearchKeypress = this._onSearchKeypress.bind(this);
this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
this._onMarkupMutation = this._onMarkupMutation.bind(this);
@ -61,8 +153,7 @@ function SelectorSearch(aInspector, aInputNode) {
};
this.searchPopup = new AutocompletePopup(this.panelDoc, options);
// event listeners.
this.searchBox.addEventListener("command", this._onHTMLSearch, true);
this.searchBox.addEventListener("input", this.showSuggestions, true);
this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
this.inspector.on("markupmutation", this._onMarkupMutation);
@ -72,9 +163,9 @@ function SelectorSearch(aInspector, aInputNode) {
EventEmitter.decorate(this);
}
exports.SelectorSearch = SelectorSearch;
exports.SelectorAutocompleter = SelectorAutocompleter;
SelectorSearch.prototype = {
SelectorAutocompleter.prototype = {
get walker() {
return this.inspector.walker;
},
@ -169,142 +260,33 @@ SelectorSearch.prototype = {
* Removes event listeners and cleans up references.
*/
destroy: function() {
// event listeners.
this.searchBox.removeEventListener("command", this._onHTMLSearch, true);
this.searchBox.removeEventListener("input", this.showSuggestions, true);
this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
this.inspector.off("markupmutation", this._onMarkupMutation);
this.searchPopup.destroy();
this.searchPopup = null;
this.searchBox = null;
this.panelDoc = null;
this._searchResults = null;
this._searchSuggestions = null;
},
_selectResult: function(index) {
return this._searchResults.item(index).then(node => {
this.inspector.selection.setNodeFront(node, "selectorsearch");
});
},
_queryNodes: Task.async(function*(query) {
if (typeof this.hasMultiFrameSearch === "undefined") {
let target = this.inspector.toolbox.target;
this.hasMultiFrameSearch = yield target.actorHasMethod("domwalker",
"multiFrameQuerySelectorAll");
}
if (this.hasMultiFrameSearch) {
return yield this.walker.multiFrameQuerySelectorAll(query);
} else {
return yield this.walker.querySelectorAll(this.walker.rootNode, query);
}
}),
/**
* The command callback for the input box. This function is automatically
* invoked as the user is typing if the input box type is search.
*/
_onHTMLSearch: function() {
let query = this.searchBox.value;
if (query == this._lastSearched) {
this.emit("processing-done");
return;
}
this._lastSearched = query;
this._searchResults = [];
this._searchIndex = 0;
if (query.length == 0) {
this._lastValidSearch = "";
this.searchBox.removeAttribute("filled");
this.searchBox.classList.remove("devtools-no-search-result");
if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup();
}
this.emit("processing-done");
return;
}
this.searchBox.setAttribute("filled", true);
let queryList = null;
this._lastQuery = this._queryNodes(query).then(list => {
return list;
}, (err) => {
// Failures are ok here, just use a null item list;
return null;
}).then(queryList => {
// Value has changed since we started this request, we're done.
if (query != this.searchBox.value) {
if (queryList) {
queryList.release();
}
return promise.reject(null);
}
this._searchResults = queryList || [];
if (this._searchResults && this._searchResults.length > 0) {
this._lastValidSearch = query;
// Even though the selector matched atleast one node, there is still
// possibility of suggestions.
if (query.match(/[\s>+]$/)) {
// If the query has a space or '>' at the end, create a selector to match
// the children of the selector inside the search box by adding a '*'.
this._lastValidSearch += "*";
}
else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
// If the query is a partial descendant selector which does not matches
// any node, remove the last incomplete part and add a '*' to match
// everything. For ex, convert 'foo > b' to 'foo > *' .
let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+]*$/)[0];
this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
}
if (!query.slice(-1).match(/[\.#\s>+]/)) {
// Hide the popup if we have some matching nodes and the query is not
// ending with [.# >] which means that the selector is not at the
// beginning of a new class, tag or id.
if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup();
}
this.searchBox.classList.remove("devtools-no-search-result");
return this._selectResult(0);
}
return this._selectResult(0).then(() => {
this.searchBox.classList.remove("devtools-no-search-result");
}).then(() => this.showSuggestions());
}
if (query.match(/[\s>+]$/)) {
this._lastValidSearch = query + "*";
}
else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
}
this.searchBox.classList.add("devtools-no-search-result");
return this.showSuggestions();
}).then(() => this.emit("processing-done"), Cu.reportError);
},
/**
* Handles keypresses inside the input box.
*/
_onSearchKeypress: function(aEvent) {
_onSearchKeypress: function(event) {
let query = this.searchBox.value;
switch(aEvent.keyCode) {
case aEvent.DOM_VK_RETURN:
if (query == this._lastSearched && this._searchResults) {
this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
}
else {
this._onHTMLSearch();
return;
switch(event.keyCode) {
case event.DOM_VK_RETURN:
case event.DOM_VK_TAB:
if (this.searchPopup.isOpen &&
this.searchPopup.getItemAtIndex(this.searchPopup.itemCount - 1)
.preLabel == query) {
this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
this.searchBox.value = this.searchPopup.selectedItem.label;
this.hidePopup();
}
break;
case aEvent.DOM_VK_UP:
case event.DOM_VK_UP:
if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
this.searchPopup.focus();
if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
@ -316,76 +298,45 @@ SelectorSearch.prototype = {
}
this.searchBox.value = this.searchPopup.selectedItem.label;
}
else if (--this._searchIndex < 0) {
this._searchIndex = this._searchResults.length - 1;
}
break;
case aEvent.DOM_VK_DOWN:
case event.DOM_VK_DOWN:
if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
this.searchPopup.focus();
this.searchPopup.selectedIndex = 0;
this.searchBox.value = this.searchPopup.selectedItem.label;
}
this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
break;
case aEvent.DOM_VK_TAB:
if (this.searchPopup.isOpen &&
this.searchPopup.getItemAtIndex(this.searchPopup.itemCount - 1)
.preLabel == query) {
this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
this.searchBox.value = this.searchPopup.selectedItem.label;
this._onHTMLSearch();
}
break;
case aEvent.DOM_VK_BACK_SPACE:
case aEvent.DOM_VK_DELETE:
// need to throw away the lastValidSearch.
this._lastToLastValidSearch = null;
// This gets the most complete selector from the query. For ex.
// '.foo.ba' returns '.foo' , '#foo > .bar.baz' returns '#foo > .bar'
// '.foo +bar' returns '.foo +' and likewise.
this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
["",""])[1];
return;
default:
return;
}
aEvent.preventDefault();
aEvent.stopPropagation();
if (this._searchResults && this._searchResults.length > 0) {
this._lastQuery = this._selectResult(this._searchIndex).then(() => this.emit("processing-done"));
}
else {
event.preventDefault();
event.stopPropagation();
this.emit("processing-done");
}
},
/**
* Handles keypress and mouse click on the suggestions richlistbox.
*/
_onListBoxKeypress: function(aEvent) {
switch(aEvent.keyCode || aEvent.button) {
case aEvent.DOM_VK_RETURN:
case aEvent.DOM_VK_TAB:
_onListBoxKeypress: function(event) {
switch(event.keyCode || event.button) {
case event.DOM_VK_RETURN:
case event.DOM_VK_TAB:
case 0: // left mouse button
aEvent.stopPropagation();
aEvent.preventDefault();
event.stopPropagation();
event.preventDefault();
this.searchBox.value = this.searchPopup.selectedItem.label;
this.searchBox.focus();
this._onHTMLSearch();
this.hidePopup();
break;
case aEvent.DOM_VK_UP:
case event.DOM_VK_UP:
if (this.searchPopup.selectedIndex == 0) {
this.searchPopup.selectedIndex = -1;
aEvent.stopPropagation();
aEvent.preventDefault();
event.stopPropagation();
event.preventDefault();
this.searchBox.focus();
}
else {
@ -394,11 +345,11 @@ SelectorSearch.prototype = {
}
break;
case aEvent.DOM_VK_DOWN:
case event.DOM_VK_DOWN:
if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
this.searchPopup.selectedIndex = -1;
aEvent.stopPropagation();
aEvent.preventDefault();
event.stopPropagation();
event.preventDefault();
this.searchBox.focus();
}
else {
@ -407,20 +358,15 @@ SelectorSearch.prototype = {
}
break;
case aEvent.DOM_VK_BACK_SPACE:
aEvent.stopPropagation();
aEvent.preventDefault();
case event.DOM_VK_BACK_SPACE:
event.stopPropagation();
event.preventDefault();
this.searchBox.focus();
if (this.searchBox.selectionStart > 0) {
this.searchBox.value =
this.searchBox.value.substring(0, this.searchBox.selectionStart - 1);
}
this._lastToLastValidSearch = null;
let query = this.searchBox.value;
this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
["",""])[1];
this._onHTMLSearch();
this.hidePopup();
break;
}
this.emit("processing-done");
@ -438,12 +384,12 @@ SelectorSearch.prototype = {
/**
* Populates the suggestions list and show the suggestion popup.
*/
_showPopup: function(aList, aFirstPart, aState) {
_showPopup: function(list, firstPart, aState) {
let total = 0;
let query = this.searchBox.value;
let items = [];
for (let [value, count, state] of aList) {
for (let [value, /*count*/, state] of list) {
// for cases like 'div ' or 'div >' or 'div+'
if (query.match(/[\s>+]$/)) {
value = query + value;
@ -461,8 +407,7 @@ SelectorSearch.prototype = {
let item = {
preLabel: query,
label: value,
count: count
label: value
};
// In case of tagNames, change the case to small
@ -489,6 +434,16 @@ SelectorSearch.prototype = {
this.searchPopup.openPopup(this.searchBox);
}
else {
this.hidePopup();
}
},
/**
* Hide the suggestion popup if necessary.
*/
hidePopup: function() {
if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup();
}
},
@ -502,18 +457,18 @@ SelectorSearch.prototype = {
let state = this.state;
let firstPart = "";
if (state == this.States.TAG) {
if (state === this.States.TAG) {
// gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
// 'di' returns 'di' and likewise.
firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
query = query.slice(0, query.length - firstPart.length);
}
else if (state == this.States.CLASS) {
else if (state === this.States.CLASS) {
// gets the class that is being completed. For ex. '.foo.b' returns 'b'
firstPart = query.match(/\.([^\.]*)$/)[1];
query = query.slice(0, query.length - firstPart.length - 1);
}
else if (state == this.States.ID) {
else if (state === this.States.ID) {
// gets the id that is being completed. For ex. '.foo#b' returns 'b'
firstPart = query.match(/#([^#]*)$/)[1];
query = query.slice(0, query.length - firstPart.length - 1);
@ -524,23 +479,31 @@ SelectorSearch.prototype = {
query += "*";
}
this._currentSuggesting = query;
return this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => {
if (this._currentSuggesting != result.query) {
this._lastQuery = this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => {
this.emit("processing-done");
if (result.query !== query) {
// This means that this response is for a previous request and the user
// as since typed something extra leading to a new request.
return;
}
this._lastToLastValidSearch = this._lastValidSearch;
if (state == this.States.CLASS) {
if (state === this.States.CLASS) {
firstPart = "." + firstPart;
}
else if (state == this.States.ID) {
} else if (state === this.States.ID) {
firstPart = "#" + firstPart;
}
// If there is a single tag match and it's what the user typed, then
// don't need to show a popup.
if (result.suggestions.length === 1 &&
result.suggestions[0][0] === firstPart) {
result.suggestions = [];
}
this._showPopup(result.suggestions, firstPart, state);
});
return this._lastQuery;
}
};

View File

@ -154,11 +154,12 @@
class="breadcrumbs-widget-container"
flex="1" orient="horizontal"
clicktoscroll="true"/>
<box id="inspector-searchlabel" />
<textbox id="inspector-searchbox"
type="search"
timeout="50"
class="devtools-searchinput"
placeholder="&inspectorSearchHTML.label2;"/>
placeholder="&inspectorSearchHTML.label3;"/>
<toolbarbutton id="inspector-pane-toggle"
class="devtools-toolbarbutton"
tabindex="0" />

View File

@ -6,7 +6,7 @@ DevToolsModules(
'breadcrumbs.js',
'inspector-commands.js',
'inspector-panel.js',
'selector-search.js'
'inspector-search.js'
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']

View File

@ -8,11 +8,6 @@
const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html";
// Indexes of the keys in the KEY_STATES array that should listen to "keypress"
// event instead of "command". These are keys that don't change the content of
// the search field and thus don't trigger command event.
const LISTEN_KEYPRESS = [3,4,8,18,19,20,21,22];
// The various states of the inspector: [key, id, isValid]
// [
// what key to press,
@ -20,35 +15,45 @@ const LISTEN_KEYPRESS = [3,4,8,18,19,20,21,22];
// is the searched text valid selector
// ]
const KEY_STATES = [
["d", "b1", false],
["i", "b1", false],
["v", "d1", true],
["VK_DOWN", "d2", true], // keypress
["VK_RETURN", "d1", true], //keypress
[".", "d1", false],
["c", "d1", false],
["1", "d2", true],
["VK_DOWN", "d2", true], // keypress
["VK_BACK_SPACE", "d2", false],
["VK_BACK_SPACE", "d2", false],
["VK_BACK_SPACE", "d1", true],
["VK_BACK_SPACE", "d1", false],
["VK_BACK_SPACE", "d1", false],
["VK_BACK_SPACE", "d1", true],
[".", "d1", false],
["c", "d1", false],
["1", "d2", true],
["VK_DOWN", "s2", true], // keypress
["VK_DOWN", "p1", true], // kepress
["VK_UP", "s2", true], // keypress
["VK_UP", "d2", true], // keypress
["VK_UP", "p1", true],
["VK_BACK_SPACE", "p1", false],
["2", "p3", true],
["VK_BACK_SPACE", "p3", false],
["VK_BACK_SPACE", "p3", false],
["VK_BACK_SPACE", "p3", true],
["r", "p3", false],
["#", "b1", true], // #
["d", "b1", true], // #d
["1", "b1", true], // #d1
["VK_RETURN", "d1", true], // #d1
["VK_BACK_SPACE", "d1", true], // #d
["2", "d1", true], // #d2
["VK_RETURN", "d2", true], // #d2
["2", "d2", true], // #d22
["VK_RETURN", "d2", false], // #d22
["VK_BACK_SPACE", "d2", false], // #d2
["VK_RETURN", "d2", true], // #d2
["VK_BACK_SPACE", "d2", true], // #d
["1", "d2", true], // #d1
["VK_RETURN", "d1", true], // #d1
["VK_BACK_SPACE", "d1", true], // #d
["VK_BACK_SPACE", "d1", true], // #
["VK_BACK_SPACE", "d1", true], //
["d", "d1", true], // d
["i", "d1", true], // di
["v", "d1", true], // div
[".", "d1", true], // div.
["c", "d1", true], // div.c
["VK_UP", "d1", true], // div.c1
["VK_TAB", "d1", true], // div.c1
["VK_RETURN", "d2", true], // div.c1
["VK_BACK_SPACE", "d2", true], // div.c
["VK_BACK_SPACE", "d2", true], // div.
["VK_BACK_SPACE", "d2", true], // div
["VK_BACK_SPACE", "d2", true], // di
["VK_BACK_SPACE", "d2", true], // d
["VK_BACK_SPACE", "d2", true], //
[".", "d2", true], // .
["c", "d2", true], // .c
["1", "d2", true], // .c1
["VK_RETURN", "d2", true], // .c1
["VK_RETURN", "s2", true], // .c1
["VK_RETURN", "p1", true], // .c1
["P", "p1", true], // .c1P
["VK_RETURN", "p1", false], // .c1P
];
add_task(function* () {
@ -60,14 +65,18 @@ add_task(function* () {
let index = 0;
for (let [ key, id, isValid ] of KEY_STATES) {
let event = (LISTEN_KEYPRESS.indexOf(index) !== -1) ? "keypress" : "command";
let eventHandled = once(searchBox, event, true);
info(index + ": Pressing key " + key + " to get id " + id);
info(index + ": Pressing key " + key + " to get id " + id + ".");
let done = inspector.searchSuggestions.once("processing-done");
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
yield eventHandled;
yield done;
info("Got processing-done event");
info("Got " + event + " event. Waiting for search query to complete");
if (key === "VK_RETURN") {
info ("Waiting for " + (isValid ? "NO " : "") + "results");
yield inspector.search.once("search-result");
}
info("Waiting for search query to complete");
yield inspector.searchSuggestions._lastQuery;
info(inspector.selection.nodeFront.id + " is selected with text " +

View File

@ -16,14 +16,14 @@ const TEST_DATA = [
{
key: "d",
suggestions: [
{label: "div", count: 4},
{label: "#d1", count: 1},
{label: "#d2", count: 1}
{label: "div"},
{label: "#d1"},
{label: "#d2"}
]
},
{
key: "i",
suggestions: [{label: "div", count: 4}]
suggestions: [{label: "div"}]
},
{
key: "v",
@ -32,22 +32,22 @@ const TEST_DATA = [
{
key: " ",
suggestions: [
{label: "div div", count: 2},
{label: "div span", count: 2}
{label: "div div"},
{label: "div span"}
]
},
{
key: ">",
suggestions: [
{label: "div >div", count: 2},
{label: "div >span", count: 2}
{label: "div >div"},
{label: "div >span"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: [
{label: "div div", count: 2 },
{label: "div span", count: 2}
{label: "div div"},
{label: "div span"}
]
},
{
@ -57,8 +57,8 @@ const TEST_DATA = [
{
key: "VK_BACK_SPACE",
suggestions: [
{label: "div div", count: 2 },
{label: "div span", count: 2}
{label: "div div"},
{label: "div span"}
]
},
{
@ -67,14 +67,14 @@ const TEST_DATA = [
},
{
key: "VK_BACK_SPACE",
suggestions: [{label: "div", count: 4}]
suggestions: [{label: "div"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [
{label: "div", count: 4},
{label: "#d1", count: 1},
{label: "#d2", count: 1}
{label: "div"},
{label: "#d1"},
{label: "#d2"}
]
},
{
@ -83,7 +83,12 @@ const TEST_DATA = [
},
{
key: "p",
suggestions: []
suggestions: [
{label: "p"},
{label: "#p1"},
{label: "#p2"},
{label: "#p3"},
]
},
{
key: " ",
@ -153,14 +158,12 @@ add_task(function* () {
for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct.");
is(actualSuggestions[i].count, suggestions[i].count || 1,
"The count for suggestion at " + i + "th index is correct.");
}
}
});
function formatSuggestions(suggestions) {
return "[" + suggestions
.map(s => "'" + s.label + "' (" + (s.count || 1) + ")")
.map(s => "'" + s.label + "'")
.join(", ") + "]";
}

View File

@ -16,14 +16,14 @@ var TEST_DATA = [
{
key: "d",
suggestions: [
{label: "div", count: 2},
{label: "#d1", count: 1},
{label: "#d2", count: 1}
{label: "div"},
{label: "#d1"},
{label: "#d2"}
]
},
{
key: "i",
suggestions: [{label: "div", count: 2}]
suggestions: [{label: "div"}]
},
{
key: "v",
@ -50,14 +50,14 @@ var TEST_DATA = [
},
{
key: "VK_BACK_SPACE",
suggestions: [{label: "div", count: 2}]
suggestions: [{label: "div"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [
{label: "div", count: 2},
{label: "#d1", count: 1},
{label: "#d2", count: 1}
{label: "div"},
{label: "#d1"},
{label: "#d2"}
]
},
{
@ -67,14 +67,14 @@ var TEST_DATA = [
{
key: ".",
suggestions: [
{label: ".c1", count: 3},
{label: ".c1"},
{label: ".c2"}
]
},
{
key: "c",
suggestions: [
{label: ".c1", count: 3},
{label: ".c1"},
{label: ".c2"}
]
},
@ -85,7 +85,7 @@ var TEST_DATA = [
{
key: "VK_BACK_SPACE",
suggestions: [
{label: ".c1", count: 3},
{label: ".c1"},
{label: ".c2"}
]
},
@ -108,14 +108,14 @@ var TEST_DATA = [
{
key: "VK_BACK_SPACE",
suggestions: [
{label: ".c1", count: 3},
{label: ".c1"},
{label: ".c2"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: [
{label: ".c1", count: 3},
{label: ".c1"},
{label: ".c2"}
]
},
@ -189,14 +189,12 @@ add_task(function* () {
for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct.");
is(actualSuggestions[i].count, suggestions[i].count || 1,
"The count for suggestion at " + i + "th index is correct.");
}
}
});
function formatSuggestions(suggestions) {
return "[" + suggestions
.map(s => "'" + s.label + "' (" + (s.count || 1) + ")")
.map(s => "'" + s.label + "'")
.join(", ") + "]";
}

View File

@ -19,14 +19,14 @@ var TEST_DATA = [
{
key: "d",
suggestions: [
{label: "div", count: 5},
{label: "#d1", count: 2},
{label: "#d2", count: 2}
{label: "div"},
{label: "#d1"},
{label: "#d2"}
]
},
{
key: "i",
suggestions: [{label: "div", count: 5}]
suggestions: [{label: "div"}]
},
{
key: "v",
@ -34,14 +34,14 @@ var TEST_DATA = [
},
{
key: "VK_BACK_SPACE",
suggestions: [{label: "div", count: 5}]
suggestions: [{label: "div"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [
{label: "div", count: 5},
{label: "#d1", count: 2},
{label: "#d2", count: 2}
{label: "div"},
{label: "#d1"},
{label: "#d2"}
]
},
{
@ -51,8 +51,8 @@ var TEST_DATA = [
{
key: ".",
suggestions: [
{label: ".c1", count: 7},
{label: ".c2", count: 3}
{label: ".c1"},
{label: ".c2"}
]
},
{
@ -62,14 +62,14 @@ var TEST_DATA = [
{
key: "#",
suggestions: [
{label: "#b1", count: 2},
{label: "#d1", count: 2},
{label: "#d2", count: 2},
{label: "#p1", count: 2},
{label: "#p2", count: 2},
{label: "#p3", count: 2},
{label: "#s1", count: 2},
{label: "#s2", count: 2}
{label: "#b1"},
{label: "#d1"},
{label: "#d2"},
{label: "#p1"},
{label: "#p2"},
{label: "#p3"},
{label: "#s1"},
{label: "#s2"}
]
},
];
@ -100,14 +100,12 @@ add_task(function* () {
for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct.");
is(actualSuggestions[i].count, suggestions[i].count || 1,
"The count for suggestion at " + i + "th index is correct.");
}
}
});
function formatSuggestions(suggestions) {
return "[" + suggestions
.map(s => "'" + s.label + "' (" + (s.count || 1) + ")")
.map(s => "'" + s.label + "'")
.join(", ") + "]";
}

View File

@ -11,11 +11,14 @@ const TEST_URL = "data:text/html;charset=utf-8," +
"<iframe id=\"iframe-1\" src=\"" +
TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>" +
"<iframe id=\"iframe-2\" src=\"" +
TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>";
TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>" +
"<iframe id='iframe-3' src='data:text/html," +
"<button id=\"b1\">Nested button</button>" +
"<iframe id=\"iframe-4\" src=" + TEST_URL_ROOT + IFRAME_SRC + "></iframe>'>" +
"</iframe>";
add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL);
let {walker} = inspector;
let searchBox = inspector.searchBox;
let popup = inspector.searchSuggestions.searchPopup;
@ -24,41 +27,65 @@ add_task(function* () {
yield focusSearchBoxUsingShortcut(inspector.panelWin);
info("Enter # to search for all ids");
let command = once(searchBox, "command");
let processingDone = once(inspector.searchSuggestions, "processing-done");
EventUtils.synthesizeKey("#", {}, inspector.panelWin);
yield command;
yield processingDone;
info("Wait for search query to complete");
yield inspector.searchSuggestions._lastQuery;
info("Press tab to fill the search input with the first suggestion and " +
"expect a new selection");
let onSelect = inspector.once("inspector-updated");
info("Press tab to fill the search input with the first suggestion");
processingDone = once(inspector.searchSuggestions, "processing-done");
EventUtils.synthesizeKey("VK_TAB", {}, inspector.panelWin);
yield processingDone;
info("Press enter and expect a new selection");
let onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
let node = inspector.selection.nodeFront;
ok(node.id, "b1", "The selected node is #b1");
ok(node.tagName.toLowerCase(), "button",
"The selected node is <button>");
let selectedNodeDoc = yield walker.document(node);
let iframe1 = yield walker.querySelector(walker.rootNode, "#iframe-1");
let iframe1Doc = (yield walker.children(iframe1)).nodes[0];
is(selectedNodeDoc, iframe1Doc, "The selected node is in iframe 1");
yield checkCorrectButton(inspector, "#iframe-1");
info("Press enter to cycle through multiple nodes matching this suggestion");
onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
node = inspector.selection.nodeFront;
ok(node.id, "b1", "The selected node is #b1 again");
ok(node.tagName.toLowerCase(), "button",
"The selected node is <button> again");
yield checkCorrectButton(inspector, "#iframe-2");
selectedNodeDoc = yield walker.document(node);
let iframe2 = yield walker.querySelector(walker.rootNode, "#iframe-2");
let iframe2Doc = (yield walker.children(iframe2)).nodes[0];
is(selectedNodeDoc, iframe2Doc, "The selected node is in iframe 2");
info("Press enter to cycle through multiple nodes matching this suggestion");
onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
yield checkCorrectButton(inspector, "#iframe-3");
info("Press enter to cycle through multiple nodes matching this suggestion");
onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
yield checkCorrectButton(inspector, "#iframe-4");
info("Press enter to cycle through multiple nodes matching this suggestion");
onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
yield checkCorrectButton(inspector, "#iframe-1");
});
let checkCorrectButton = Task.async(function*(inspector, frameSelector) {
let {walker} = inspector;
let node = inspector.selection.nodeFront;
ok(node.id, "b1", "The selected node is #b1");
ok(node.tagName.toLowerCase(), "button",
"The selected node is <button>");
let selectedNodeDoc = yield walker.document(node);
let iframe = yield walker.multiFrameQuerySelectorAll(frameSelector);
iframe = yield iframe.item(0);
let iframeDoc = (yield walker.children(iframe)).nodes[0];
is(selectedNodeDoc, iframeDoc, "The selected node is in " + frameSelector);
});

View File

@ -14,8 +14,9 @@ add_task(function* () {
info("Searching for test node #d1");
yield focusSearchBoxUsingShortcut(inspector.panelWin);
yield synthesizeKeys(["#", "d", "1"], inspector);
yield synthesizeKeys(["#", "d", "1", "VK_RETURN"], inspector);
yield inspector.search.once("search-result");
assertHasResult(inspector, true);
info("Removing node #d1");
@ -25,12 +26,15 @@ add_task(function* () {
info("Pressing return button to search again for node #d1.");
yield synthesizeKeys("VK_RETURN", inspector);
yield inspector.search.once("search-result");
assertHasResult(inspector, false);
info("Emptying the field and searching for a node that doesn't exist: #d3");
let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3"];
let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3",
"VK_RETURN"];
yield synthesizeKeys(keys, inspector);
yield inspector.search.once("search-result");
assertHasResult(inspector, false);
info("Create the #d3 node in the page");
@ -41,6 +45,7 @@ add_task(function* () {
info("Pressing return button to search again for node #d3.");
yield synthesizeKeys("VK_RETURN", inspector);
yield inspector.search.once("search-result");
assertHasResult(inspector, true);
// Catch-all event for remaining server requests when searching for the new

View File

@ -13,15 +13,15 @@ const TEST_URL = TEST_URL_ROOT + "doc_inspector_search-reserved.html";
const TEST_DATA = [
{
key: "#",
suggestions: [{label: "#d1\\.d2", count: 1}]
suggestions: [{label: "#d1\\.d2"}]
},
{
key: "d",
suggestions: [{label: "#d1\\.d2", count: 1}]
suggestions: [{label: "#d1\\.d2"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [{label: "#d1\\.d2", count: 1}]
suggestions: [{label: "#d1\\.d2"}]
},
{
key: "VK_BACK_SPACE",
@ -29,15 +29,15 @@ const TEST_DATA = [
},
{
key: ".",
suggestions: [{label: ".c1\\.c2", count: 1}]
suggestions: [{label: ".c1\\.c2"}]
},
{
key: "c",
suggestions: [{label: ".c1\\.c2", count: 1}]
suggestions: [{label: ".c1\\.c2"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [{label: ".c1\\.c2", count: 1}]
suggestions: [{label: ".c1\\.c2"}]
},
{
key: "VK_BACK_SPACE",
@ -45,8 +45,8 @@ const TEST_DATA = [
},
{
key: "d",
suggestions: [{label: "div", count: 2},
{label: "#d1\\.d2", count: 1}]
suggestions: [{label: "div"},
{label: "#d1\\.d2"}]
},
{
key: "VK_BACK_SPACE",
@ -54,7 +54,7 @@ const TEST_DATA = [
},
{
key:"c",
suggestions: [{label: ".c1\\.c2", count: 1}]
suggestions: [{label: ".c1\\.c2"}]
},
{
key: "VK_BACK_SPACE",
@ -62,15 +62,15 @@ const TEST_DATA = [
},
{
key: "b",
suggestions: [{label: "body", count: 1}]
suggestions: [{label: "body"}]
},
{
key: "o",
suggestions: [{label: "body", count: 1}]
suggestions: [{label: "body"}]
},
{
key: "d",
suggestions: [{label: "body", count: 1}]
suggestions: [{label: "body"}]
},
{
key: "y",
@ -78,20 +78,20 @@ const TEST_DATA = [
},
{
key: " ",
suggestions: [{label: "body div", count: 2}]
suggestions: [{label: "body div"}]
},
{
key: ".",
suggestions: [{label: "body .c1\\.c2", count: 1}]
suggestions: [{label: "body .c1\\.c2"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [{label: "body div", count: 2}]
suggestions: [{label: "body div"}]
},
{
key: "#",
suggestions: [{label: "body #", count: 1},
{label: "body #d1\\.d2", count: 1}]
suggestions: [{label: "body #"},
{label: "body #d1\\.d2"}]
}
];
@ -121,14 +121,12 @@ add_task(function* () {
for (let i = 0; i < suggestions.length; i++) {
is(suggestions[i].label, actualSuggestions[i].label,
"The suggestion at " + i + "th index is correct.");
is(suggestions[i].count || 1, actualSuggestions[i].count,
"The count for suggestion at " + i + "th index is correct.");
}
}
});
function formatSuggestions(suggestions) {
return "[" + suggestions
.map(s => "'" + s.label + "' (" + s.count || 1 + ")")
.map(s => "'" + s.label + "'")
.join(", ") + "]";
}

View File

@ -113,9 +113,8 @@
<!ENTITY inspectorSearchHTML.label2 "Search with CSS Selectors">
<!ENTITY inspectorSearchHTML.key "F">
<!-- LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that will
be shown as the placeholder in the future, once the inspector search box
supports the full text HTML search in Bug 835896. -->
<!-- LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that is
shown as the placeholder for the markup view search in the inspector. -->
<!ENTITY inspectorSearchHTML.label3 "Search HTML">
<!-- LOCALIZATION NOTE (inspectorCopyImageDataUri.label): This is the label

View File

@ -40,7 +40,7 @@ const Toolbar = module.exports = createClass({
dom.div({ className: "devtools-toolbar" },
dom.button({
id: "take-snapshot",
className: `take-snapshot devtools-button`,
className: "take-snapshot devtools-button",
onClick: onTakeSnapshotClick,
title: L10N.getStr("take-snapshot")
}),
@ -50,7 +50,7 @@ const Toolbar = module.exports = createClass({
L10N.getStr("toolbar.breakdownBy"),
dom.select({
id: "select-breakdown",
className: `select-breakdown`,
className: "select-breakdown",
onChange: e => onBreakdownChange(e.target.value),
}, ...breakdowns.map(({ name, displayName }) => dom.option({ key: name, value: name }, displayName)))
),
@ -81,6 +81,7 @@ const Toolbar = module.exports = createClass({
dom.input({
id: "filter",
type: "search",
className: "devtools-searchinput",
placeholder: L10N.getStr("filter.placeholder"),
onChange: event => setFilterString(event.target.value),
value: !!filterString ? filterString : undefined,

View File

@ -59,4 +59,14 @@ add_task(function *() {
widget.setCssValue("contrast(5%) whatever invert('xxx')");
is(widget.getCssValue(), "contrast(5%) invert(0%)",
"setCssValue should handle multiple errors");
info("Test parsing of 'unset'");
widget.setCssValue("unset");
is(widget.getCssValue(), "unset", "setCssValue should handle 'unset'");
info("Test parsing of 'initial'");
widget.setCssValue("initial");
is(widget.getCssValue(), "initial", "setCssValue should handle 'initial'");
info("Test parsing of 'inherit'");
widget.setCssValue("inherit");
is(widget.getCssValue(), "inherit", "setCssValue should handle 'inherit'");
});

View File

@ -98,6 +98,9 @@ const filterList = [
}
];
// Valid values that shouldn't be parsed for filters.
const SPECIAL_VALUES = new Set(["none", "unset", "initial", "inherit"]);
/**
* A CSS Filter editor widget used to add/remove/modify
* filters.
@ -557,7 +560,7 @@ CSSFilterEditorWidget.prototype = {
let name = this.addPresetInput.value;
let value = this.getCssValue();
if (!name || !value || value === "none") {
if (!name || !value || SPECIAL_VALUES.has(value)) {
this.emit("preset-save-error");
return;
}
@ -706,7 +709,8 @@ CSSFilterEditorWidget.prototype = {
this.filters = [];
if (cssValue === "none") {
if (SPECIAL_VALUES.has(cssValue)) {
this._specialValue = cssValue;
this.emit("updated", this.getCssValue());
this.render();
return;
@ -825,7 +829,7 @@ CSSFilterEditorWidget.prototype = {
getCssValue: function() {
return this.filters.map((filter, i) => {
return `${filter.name}(${this.getValueAt(i)})`;
}).join(" ") || "none";
}).join(" ") || this._specialValue || "none";
},
/**
@ -906,7 +910,7 @@ function tokenizeFilterValue(css) {
let filters = [];
let depth = 0;
if (css === "none") {
if (SPECIAL_VALUES.has(css)) {
return filters;
}

View File

@ -15,6 +15,10 @@
%endif
#inspector-searchlabel {
overflow: hidden;
}
#inspector-searchbox {
transition-property: max-width, -moz-padding-end, -moz-padding-start;
transition-duration: 250ms;

View File

@ -100,6 +100,11 @@ html, body, #app, #memory-tool {
flex: 1;
}
#filter {
align-self: stretch;
margin: 2px;
}
/**
* TODO bug 1213100
* Once we figure out how to store invertable buttons (pseudo element like in

View File

@ -61,6 +61,7 @@ const object = require("sdk/util/object");
const events = require("sdk/event/core");
const {Unknown} = require("sdk/platform/xpcom");
const {Class} = require("sdk/core/heritage");
const {WalkerSearch} = require("devtools/server/actors/utils/walker-search");
const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles");
const {
HighlighterActor,
@ -1122,6 +1123,13 @@ types.addDictType("disconnectedNodeArray", {
types.addDictType("dommutation", {});
types.addDictType("searchresult", {
list: "domnodelist",
// Right now there is isn't anything required for metadata,
// but it's json so it can be extended with extra data.
metadata: "array:json"
});
/**
* Server side of a node list as returned by querySelectorAll()
*/
@ -1299,6 +1307,8 @@ var WalkerActor = protocol.ActorClass({
this._activePseudoClassLocks = new Set();
this.showAllAnonymousContent = options.showAllAnonymousContent;
this.walkerSearch = new WalkerSearch(this);
// Nodes which have been removed from the client's known
// ownership tree are considered "orphaned", and stored in
// this set.
@ -1334,7 +1344,13 @@ var WalkerActor = protocol.ActorClass({
// FF42+ Inspector starts managing the Walker, while the inspector also
// starts cleaning itself up automatically on client disconnection.
// So that there is no need to manually release the walker anymore.
autoReleased: true
autoReleased: true,
// XXX: It seems silly that we need to tell the front which capabilities
// its actor has in this way when the target can use actorHasMethod. If
// this was ported to the protocol (Bug 1157048) we could call that inside
// of custom front methods and not need to do traits for this.
multiFrameQuerySelectorAll: true,
textSearch: true,
}
}
},
@ -1376,6 +1392,7 @@ var WalkerActor = protocol.ActorClass({
this.onFrameLoad = null;
this.onFrameUnload = null;
this.walkerSearch.destroy();
this.reflowObserver.off("reflows", this._onReflows);
this.reflowObserver = null;
this._onReflows = null;
@ -2048,6 +2065,33 @@ var WalkerActor = protocol.ActorClass({
}
}),
/**
* Search the document for a given string.
* Results will be searched with the walker-search module (searches through
* tag names, attribute names and values, and text contents).
*
* @returns {searchresult}
* - {NodeList} list
* - {Array<Object>} metadata. Extra information with indices that
* match up with node list.
*/
search: method(function(query) {
let results = this.walkerSearch.search(query);
let nodeList = new NodeListActor(this, results.map(r => r.node));
return {
list: nodeList,
metadata: []
}
}, {
request: {
query: Arg(0),
},
response: {
list: RetVal("searchresult"),
}
}),
/**
* Returns a list of matching results for CSS selector autocompletion.
*
@ -2838,6 +2882,11 @@ var WalkerActor = protocol.ActorClass({
* See https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationRecord
*/
onMutations: function(mutations) {
// Notify any observers that want *all* mutations (even on nodes that aren't
// referenced). This is not sent over the protocol so can only be used by
// scripts running in the server process.
events.emit(this, "any-mutation");
for (let change of mutations) {
let targetActor = this._refMap.get(change.target);
if (!targetActor) {
@ -3315,6 +3364,75 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
impl: "_getNodeFromActor"
}),
/*
* Incrementally search the document for a given string.
* For modern servers, results will be searched with using the WalkerActor
* `search` function (includes tag names, attributes, and text contents).
* Only 1 result is sent back, and calling the method again with the same
* query will send the next result. When there are no more results to be sent
* back, null is sent.
* @param {String} query
* @param {Object} options
* - "reverse": search backwards
* - "selectorOnly": treat input as a selector string (don't search text
* tags, attributes, etc)
*/
search: protocol.custom(Task.async(function*(query, options = { }) {
let nodeList;
let searchType;
let searchData = this.searchData = this.searchData || { };
let selectorOnly = !!options.selectorOnly;
// Backwards compat. Use selector only search if the new
// search functionality isn't implemented, or if the caller (tests)
// want it.
if (selectorOnly || !this.traits.textSearch) {
searchType = "selector";
if (this.traits.multiFrameQuerySelectorAll) {
nodeList = yield this.multiFrameQuerySelectorAll(query);
} else {
nodeList = yield this.querySelectorAll(this.rootNode, query);
}
} else {
searchType = "search";
let result = yield this._search(query, options);
nodeList = result.list;
}
// If this is a new search, start at the beginning.
if (searchData.query !== query ||
searchData.selectorOnly !== selectorOnly) {
searchData.selectorOnly = selectorOnly;
searchData.query = query;
searchData.index = -1;
}
if (!nodeList.length) {
return null;
}
// Move search result cursor and cycle if necessary.
searchData.index = options.reverse ? searchData.index - 1 :
searchData.index + 1;
if (searchData.index >= nodeList.length) {
searchData.index = 0;
}
if (searchData.index < 0) {
searchData.index = nodeList.length - 1;
}
// Send back the single node, along with any relevant search data
let node = yield nodeList.item(searchData.index);
return {
type: searchType,
node: node,
resultsLength: nodeList.length,
resultsIndex: searchData.index,
};
}), {
impl: "_search"
}),
_releaseFront: function(node, force) {
if (node.retained && !force) {
node.reparent(null);
@ -3875,10 +3993,25 @@ DocumentWalker.prototype = {
return this.walker.parentNode();
},
nextNode: function() {
let node = this.walker.currentNode;
if (!node) {
return null;
}
let nextNode = this.walker.nextNode();
while (nextNode && this.filter(nextNode) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
nextNode = this.walker.nextNode();
}
return nextNode;
},
firstChild: function() {
let node = this.walker.currentNode;
if (!node)
if (!node) {
return null;
}
let firstChild = this.walker.firstChild();
while (firstChild && this.filter(firstChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
@ -3890,8 +4023,9 @@ DocumentWalker.prototype = {
lastChild: function() {
let node = this.walker.currentNode;
if (!node)
if (!node) {
return null;
}
let lastChild = this.walker.lastChild();
while (lastChild && this.filter(lastChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {

View File

@ -2084,6 +2084,13 @@ function getRuleText(initialText, line, column) {
if (startOffset === undefined) {
return {offset: 0, text: ""};
}
// If the input didn't have any tokens between the braces (e.g.,
// "div {}"), then the endOffset won't have been set yet; so account
// for that here.
if (endOffset === undefined) {
endOffset = startOffset;
}
// Note that this approach will preserve comments, despite the fact
// that cssTokenizer skips them.
return {offset: textOffset + startOffset,

View File

@ -12,5 +12,6 @@ DevToolsModules(
'map-uri-to-addon-id.js',
'ScriptStore.js',
'stack.js',
'TabSources.js'
'TabSources.js',
'walker-search.js'
)

View File

@ -0,0 +1,278 @@
/* 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/. */
"use strict";
/**
* The walker-search module provides a simple API to index and search strings
* and elements inside a given document.
* It indexes tag names, attribute names and values, and text contents.
* It provides a simple search function that returns a list of nodes that
* matched.
*/
const {Ci, Cu} = require("chrome");
/**
* The WalkerIndex class indexes the document (and all subdocs) from
* a given walker.
*
* It is only indexed the first time the data is accessed and will be
* re-indexed if a mutation happens between requests.
*
* @param {Walker} walker The walker to be indexed
*/
function WalkerIndex(walker) {
this.walker = walker;
this.clearIndex = this.clearIndex.bind(this);
// Kill the index when mutations occur, the next data get will re-index.
this.walker.on("any-mutation", this.clearIndex);
}
WalkerIndex.prototype = {
/**
* Destroy this instance, releasing all data and references
*/
destroy: function() {
this.walker.off("any-mutation", this.clearIndex);
},
clearIndex: function() {
if (!this.currentlyIndexing) {
this._data = null;
}
},
get doc() {
return this.walker.rootDoc;
},
/**
* Get the indexed data
* This getter also indexes if it hasn't been done yet or if the state is
* dirty
*
* @returns Map<String, Array<{type:String, node:DOMNode}>>
* A Map keyed on the searchable value, containing an array with
* objects containing the 'type' (one of ALL_RESULTS_TYPES), and
* the DOM Node.
*/
get data() {
if (!this._data) {
this._data = new Map();
this.index();
}
return this._data;
},
_addToIndex: function(type, node, value) {
// Add an entry for this value if there isn't one
let entry = this._data.get(value);
if (!entry) {
this._data.set(value, []);
}
// Add the type/node to the list
this._data.get(value).push({
type: type,
node: node
});
},
index: function() {
// Handle case where iterating nextNode() with the deepTreeWalker triggers
// a mutation (Bug 1222558)
this.currentlyIndexing = true;
let documentWalker = this.walker.getDocumentWalker(this.doc);
while (documentWalker.nextNode()) {
let node = documentWalker.currentNode;
if (node.nodeType === 1) {
// For each element node, we get the tagname and all attributes names
// and values
let localName = node.localName;
if (localName === "_moz_generated_content_before") {
this._addToIndex("tag", node, "::before");
this._addToIndex("text", node, node.textContent.trim());
} else if (localName === "_moz_generated_content_after") {
this._addToIndex("tag", node, "::after");
this._addToIndex("text", node, node.textContent.trim());
} else {
this._addToIndex("tag", node, node.localName);
}
for (let {name, value} of node.attributes) {
this._addToIndex("attributeName", node, name);
this._addToIndex("attributeValue", node, value);
}
} else if (node.textContent && node.textContent.trim().length) {
// For comments and text nodes, we get the text
this._addToIndex("text", node, node.textContent.trim());
}
}
this.currentlyIndexing = false;
}
};
exports.WalkerIndex = WalkerIndex;
/**
* The WalkerSearch class provides a way to search an indexed document as well
* as find elements that match a given css selector.
*
* Usage example:
* let s = new WalkerSearch(doc);
* let res = s.search("lang", index);
* for (let {matched, results} of res) {
* for (let {node, type} of results) {
* console.log("The query matched a node's " + type);
* console.log("Node that matched", node);
* }
* }
* s.destroy();
*
* @param {Walker} the walker to be searched
*/
function WalkerSearch(walker) {
this.walker = walker;
this.index = new WalkerIndex(this.walker);
}
WalkerSearch.prototype = {
destroy: function() {
this.index.destroy();
this.walker = null;
},
_addResult: function(node, type, results) {
if (!results.has(node)) {
results.set(node, []);
}
let matches = results.get(node);
// Do not add if the exact same result is already in the list
let isKnown = false;
for (let match of matches) {
if (match.type === type) {
isKnown = true;
break;
}
}
if (!isKnown) {
matches.push({type});
}
},
_searchIndex: function(query, options, results) {
for (let [matched, res] of this.index.data) {
if (!options.searchMethod(query, matched)) {
continue;
}
// Add any relevant results (skipping non-requested options).
res.filter(entry => {
return options.types.indexOf(entry.type) !== -1;
}).forEach(({node, type}) => {
this._addResult(node, type, results);
});
}
},
_searchSelectors: function(query, options, results) {
// If the query is just one "word", no need to search because _searchIndex
// will lead the same results since it has access to tagnames anyway
let isSelector = query && query.match(/[ >~.#\[\]]/);
if (options.types.indexOf("selector") === -1 || !isSelector) {
return;
}
let nodes = this.walker._multiFrameQuerySelectorAll(query);
for (let node of nodes) {
this._addResult(node, "selector", results);
}
},
/**
* Search the document
* @param {String} query What to search for
* @param {Object} options The following options are accepted:
* - searchMethod {String} one of WalkerSearch.SEARCH_METHOD_*
* defaults to WalkerSearch.SEARCH_METHOD_CONTAINS (does not apply to
* selector search type)
* - types {Array} a list of things to search for (tag, text, attributes, etc)
* defaults to WalkerSearch.ALL_RESULTS_TYPES
* @return {Array} An array is returned with each item being an object like:
* {
* node: <the dom node that matched>,
* type: <the type of match: one of WalkerSearch.ALL_RESULTS_TYPES>
* }
*/
search: function(query, options={}) {
options.searchMethod = options.searchMethod || WalkerSearch.SEARCH_METHOD_CONTAINS;
options.types = options.types || WalkerSearch.ALL_RESULTS_TYPES;
// Empty strings will return no results, as will non-string input
if (typeof query !== "string") {
query = "";
}
// Store results in a map indexed by nodes to avoid duplicate results
let results = new Map();
// Search through the indexed data
this._searchIndex(query, options, results);
// Search with querySelectorAll
this._searchSelectors(query, options, results);
// Concatenate all results into an Array to return
let resultList = [];
for (let [node, matches] of results) {
for (let {type} of matches) {
resultList.push({
node: node,
type: type,
});
// For now, just do one result per node since the frontend
// doesn't have a way to highlight each result individually
// yet.
break;
}
}
let documents = this.walker.tabActor.windows.map(win=>win.document);
// Sort the resulting nodes by order of appearance in the DOM
resultList.sort((a,b) => {
// Disconnected nodes won't get good results from compareDocumentPosition
// so check the order of their document instead.
if (a.node.ownerDocument != b.node.ownerDocument) {
let indA = documents.indexOf(a.node.ownerDocument);
let indB = documents.indexOf(b.node.ownerDocument);
return indA - indB;
}
// If the same document, then sort on DOCUMENT_POSITION_FOLLOWING (4)
// which means B is after A.
return a.node.compareDocumentPosition(b.node) & 4 ? -1 : 1;
});
return resultList;
}
};
WalkerSearch.SEARCH_METHOD_CONTAINS = (query, candidate) => {
return query && candidate.toLowerCase().indexOf(query.toLowerCase()) !== -1;
};
WalkerSearch.ALL_RESULTS_TYPES = ["tag", "text", "attributeName",
"attributeValue", "selector"];
exports.WalkerSearch = WalkerSearch;

View File

@ -10,6 +10,7 @@ support-files =
inspector_getImageData.html
inspector-delay-image-response.sjs
inspector-helpers.js
inspector-search-data.html
inspector-styles-data.css
inspector-styles-data.html
inspector-traversal-data.html
@ -77,6 +78,8 @@ skip-if = buildapp == 'mulet'
[test_inspector-remove.html]
[test_inspector-resolve-url.html]
[test_inspector-retain.html]
[test_inspector-search.html]
[test_inspector-search-front.html]
[test_inspector-scroll-into-view.html]
[test_inspector-traversal.html]
[test_makeGlobalObjectReference.html]

View File

@ -0,0 +1,52 @@
<html>
<head>
<meta charset="UTF-8">
<title>Inspector Search Test Data</title>
<style>
#pseudo {
display: block;
margin: 0;
}
#pseudo:before {
content: "before element";
}
#pseudo:after {
content: "after element";
}
</style>
<script type="text/javascript">
window.onload = function() {
window.opener.postMessage('ready', '*');
};
</script>
</head>
</body>
<!-- A comment
spread across multiple lines -->
<img width="100" height="100" src="large-image.jpg" />
<h1 id="pseudo">Heading 1</h1>
<p>A p tag with the text 'h1' inside of it.
<strong>A strong h1 result</strong>
</p>
<div id="arrows" northwest="↖" northeast="↗" southeast="↘" southwest="↙">
Unicode arrows
</div>
<h2>Heading 2</h2>
<h2>Heading 2</h2>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h3>Heading 3</h3>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h4>Heading 4</h4>
<h4>Heading 4</h4>
<div class="💩" id="💩" 💩="💩"></div>
</body>
</html>

View File

@ -0,0 +1,220 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=835896
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 835896</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
window.onload = function() {
const Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {Promise: promise} =
Cu.import("resource://gre/modules/Promise.jsm", {});
const {InspectorFront} =
devtools.require("devtools/server/actors/inspector");
const {console} =
Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
SimpleTest.waitForExplicitFinish();
let walkerFront = null;
let inspectee = null;
let inspector = null;
// WalkerFront specific tests. These aren't to excercise search
// edge cases so much as to test the state the Front maintains between
// searches.
// See also test_inspector-search.html
addAsyncTest(function* setup() {
info ("Setting up inspector and walker actors.");
let url = document.getElementById("inspectorContent").href;
yield new Promise(resolve => {
attachURL(url, function(err, client, tab, doc) {
inspectee = doc;
inspector = InspectorFront(client, tab);
resolve();
});
});
walkerFront = yield inspector.getWalker();
ok(walkerFront, "getWalker() should return an actor.");
runNextTest();
});
addAsyncTest(function* testWalkerFrontDefaults() {
info ("Testing search API using WalkerFront.");
let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
let fronts = yield nodes.items();
let frontResult = yield walkerFront.search("");
ok(!frontResult, "Null result on front when searching for ''");
let results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Default options work");
results = yield walkerFront.search("h2", { });
isDeeply(results, {
node: fronts[1],
type: "search",
resultsIndex: 1,
resultsLength: 3
}, "Search works with empty options");
// Clear search data to remove result state on the front
yield walkerFront.search("");
runNextTest();
});
addAsyncTest(function* testMultipleSearches() {
info ("Testing search API using WalkerFront (reverse=false)");
let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
let fronts = yield nodes.items();
let results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[1],
type: "search",
resultsIndex: 1,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[2],
type: "search",
resultsIndex: 2,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
results = yield walkerFront.search("h2");
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
// Clear search data to remove result state on the front
yield walkerFront.search("");
runNextTest();
});
addAsyncTest(function* testMultipleSearchesReverse() {
info ("Testing search API using WalkerFront (reverse=true)");
let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
let fronts = yield nodes.items();
let results = yield walkerFront.search("h2", {reverse: true});
isDeeply(results, {
node: fronts[2],
type: "search",
resultsIndex: 2,
resultsLength: 3
}, "Search works with multiple results (reverse=true)");
results = yield walkerFront.search("h2", {reverse: true});
isDeeply(results, {
node: fronts[1],
type: "search",
resultsIndex: 1,
resultsLength: 3
}, "Search works with multiple results (reverse=true)");
results = yield walkerFront.search("h2", {reverse: true});
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Search works with multiple results (reverse=true)");
results = yield walkerFront.search("h2", {reverse: true});
isDeeply(results, {
node: fronts[2],
type: "search",
resultsIndex: 2,
resultsLength: 3
}, "Search works with multiple results (reverse=true)");
results = yield walkerFront.search("h2", {reverse: false});
isDeeply(results, {
node: fronts[0],
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Search works with multiple results (reverse=false)");
// Clear search data to remove result state on the front
yield walkerFront.search("");
runNextTest();
});
addAsyncTest(function* testBackwardsCompat() {
info ("Simulating a server that doesn't have the new search functionality.");
walkerFront.traits.textSearch = false;
let front = yield walkerFront.querySelector(walkerFront.rootNode, "h1");
let results = yield walkerFront.search("h1");
isDeeply(results, {
node: front,
type: "selector",
resultsIndex: 0,
resultsLength: 1
}, "Only querySelectorAll results being returned");
// Clear search data to remove result state on the front
yield walkerFront.search("");
// Reset the normal textSearch behavior
walkerFront.traits.textSearch = true;
results = yield walkerFront.search("h1");
isDeeply(results, {
node: front,
type: "search",
resultsIndex: 0,
resultsLength: 3
}, "Other results being included");
// Clear search data to remove result state on the front
yield walkerFront.search("");
runNextTest();
});
runNextTest();
};
</script>
</head>
<body>
<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -0,0 +1,300 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=835896
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 835896</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
window.onload = function() {
const Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {Promise: promise} =
Cu.import("resource://gre/modules/Promise.jsm", {});
const {InspectorFront} =
devtools.require("devtools/server/actors/inspector");
const {WalkerSearch, WalkerIndex} =
devtools.require("devtools/server/actors/utils/walker-search");
const {console} =
Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
SimpleTest.waitForExplicitFinish();
let walkerActor = null;
let walkerSearch = null;
let inspectee = null;
let inspector = null;
// WalkerSearch specific tests. This is to make sure search results are
// coming back as expected.
// See also test_inspector-search-front.html.
addAsyncTest(function* setup() {
info ("Setting up inspector and walker actors.");
let url = document.getElementById("inspectorContent").href;
yield new Promise(resolve => {
attachURL(url, function(err, client, tab, doc) {
inspectee = doc;
inspector = InspectorFront(client, tab);
resolve();
});
});
let walkerFront = yield inspector.getWalker();
ok(walkerFront, "getWalker() should return an actor.");
let serverConnection = walkerFront.conn._transport._serverConnection;
walkerActor = serverConnection.getActor(walkerFront.actorID);
ok(walkerActor,
"Got a reference to the walker actor (" + walkerFront.actorID + ")");
walkerSearch = walkerActor.walkerSearch;
runNextTest();
});
addAsyncTest(function* testIndexExists() {
info ("Testing basic index APIs exist.");
let index = new WalkerIndex(walkerActor);
ok(index.data.size > 0, "public index is filled after getting");
index.clearIndex();
ok(!index._data, "private index is empty after clearing");
ok(index.data.size > 0, "public index is filled after getting");
index.destroy();
runNextTest();
});
addAsyncTest(function* testSearchExists() {
info ("Testing basic search APIs exist.");
ok(walkerSearch, "walker search exists on the WalkerActor");
ok(walkerSearch.search, "walker search has `search` method");
ok(walkerSearch.index, "walker search has `index` property");
is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
let search = new WalkerSearch(walkerActor);
ok(search, "a new search instance can be created");
ok(search.search, "new search instance has `search` method");
ok(search.index, "new search instance has `index` property");
isnot(search, walkerSearch, "new search instance differs from the WalkerActor's");
search.destroy();
runNextTest();
});
addAsyncTest(function* testEmptySearch() {
info ("Testing search with an empty query.");
results = walkerSearch.search("");
is(results.length, 0, "No results when searching for ''");
results = walkerSearch.search(null);
is(results.length, 0, "No results when searching for null");
results = walkerSearch.search(undefined);
is(results.length, 0, "No results when searching for undefined");
results = walkerSearch.search(10);
is(results.length, 0, "No results when searching for 10");
runNextTest();
});
addAsyncTest(function* testBasicSearchData() {
let testData = [
{
desc: "Search for tag with one result.",
search: "body",
expected: [
{node: inspectee.body, type: "tag"}
]
},
{
desc: "Search for tag with multiple results",
search: "h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "tag"},
{node: inspectee.querySelectorAll("h2")[1], type: "tag"},
{node: inspectee.querySelectorAll("h2")[2], type: "tag"},
]
},
{
desc: "Search for selector with multiple results",
search: "body > h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
]
},
{
desc: "Search for selector with multiple results",
search: ":root h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
]
},
{
desc: "Search for selector with multiple results",
search: "* h2",
expected: [
{node: inspectee.querySelectorAll("h2")[0], type: "selector"},
{node: inspectee.querySelectorAll("h2")[1], type: "selector"},
{node: inspectee.querySelectorAll("h2")[2], type: "selector"},
]
},
{
desc: "Search with multiple matches in a single tag expecting a single result",
search: "💩",
expected: [
{node: inspectee.getElementById("💩"), type: "attributeName"}
]
},
{
desc: "Search that has tag and text results",
search: "h1",
expected: [
{node: inspectee.querySelector("h1"), type: "tag"},
{node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
{node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
]
},
]
for (let {desc, search, expected} of testData) {
info("Running test: " + desc);
let results = walkerSearch.search(search);
isDeeply(results, expected,
"Search returns correct results with '" + search + "'");
}
runNextTest();
});
addAsyncTest(function* testPseudoElements() {
info ("Testing ::before and ::after element matching");
let beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
inspectee.defaultView).firstChild();
let afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
inspectee.defaultView).lastChild();
let styleText = inspectee.querySelector("style").childNodes[0];
// ::before
let results = walkerSearch.search("::before");
isDeeply(results, [ {node: beforeElt, type: "tag"} ],
"Tag search works for pseudo element");
results = walkerSearch.search("_moz_generated_content_before");
is(results.length, 0, "No results for anon tag name");
results = walkerSearch.search("before element");
isDeeply(results, [
{node: styleText, type: "text"},
{node: beforeElt, type: "text"}
], "Text search works for pseudo element");
// ::after
results = walkerSearch.search("::after");
isDeeply(results, [ {node: afterElt, type: "tag"} ],
"Tag search works for pseudo element");
results = walkerSearch.search("_moz_generated_content_after");
is(results.length, 0, "No results for anon tag name");
results = walkerSearch.search("after element");
isDeeply(results, [
{node: styleText, type: "text"},
{node: afterElt, type: "text"}
], "Text search works for pseudo element");
runNextTest();
});
addAsyncTest(function* testSearchMutationChangeResults() {
info ("Testing search before and after a mutation.");
let expected = [
{node: inspectee.querySelectorAll("h3")[0], type: "tag"},
{node: inspectee.querySelectorAll("h3")[1], type: "tag"},
{node: inspectee.querySelectorAll("h3")[2], type: "tag"},
];
let results = walkerSearch.search("h3");
isDeeply(results, expected, "Search works with tag results");
yield mutateDocumentAndWaitForMutation(() => {
expected[0].node.remove();
});
results = walkerSearch.search("h3");
isDeeply(results, [
expected[1],
expected[2]
], "Results are updated after removal");
yield new Promise(resolve => {
info("Waiting for a mutation to happen");
let observer = new inspectee.defaultView.MutationObserver(() => {
resolve();
});
observer.observe(inspectee, {attributes: true, subtree: true});
inspectee.body.setAttribute("h3", "true");
});
results = walkerSearch.search("h3");
isDeeply(results, [
{node: inspectee.body, type: "attributeName"},
expected[1],
expected[2]
], "Results are updated after addition");
yield new Promise(resolve => {
info("Waiting for a mutation to happen");
let observer = new inspectee.defaultView.MutationObserver(() => {
resolve();
});
observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
inspectee.body.removeAttribute("h3");
expected[1].node.remove();
expected[2].node.remove();
});
results = walkerSearch.search("h3");
is(results.length, 0, "Results are updated after removal");
runNextTest();
});
runNextTest();
function mutateDocumentAndWaitForMutation(mutationFn) {
return new Promise(resolve => {
info("Listening to markup mutation on the inspectee");
let observer = new inspectee.defaultView.MutationObserver(resolve);
observer.observe(inspectee, {childList: true, subtree: true});
mutationFn();
});
}
};
</script>
</head>
<body>
<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -100,6 +100,13 @@ const TEST_DATA = [
column: 4,
expected: {offset: 7, text: "border:1px solid red;content: '}';color:red;"}
},
{
desc: "Rule contains no tokens",
input: "div{}",
line: 1,
column: 1,
expected: {offset: 4, text: ""}
},
];
function run_test() {

View File

@ -75,45 +75,31 @@ Concrete<DeserializedNode>::size(mozilla::MallocSizeOf mallocSizeof) const
class DeserializedEdgeRange : public EdgeRange
{
EdgeVector edges;
DeserializedNode* node;
Edge currentEdge;
size_t i;
void settle() {
front_ = i < edges.length() ? &edges[i] : nullptr;
if (i >= node->edges.length()) {
front_ = nullptr;
return;
}
auto& edge = node->edges[i];
auto referent = node->getEdgeReferent(edge);
currentEdge = mozilla::Move(Edge(edge.name ? NS_strdup(edge.name) : nullptr,
referent));
front_ = &currentEdge;
}
public:
explicit DeserializedEdgeRange()
: edges()
explicit DeserializedEdgeRange(DeserializedNode& node)
: node(&node)
, i(0)
{
settle();
}
bool init(DeserializedNode& node)
{
if (!edges.reserve(node.edges.length()))
return false;
for (DeserializedEdge* edgep = node.edges.begin();
edgep != node.edges.end();
edgep++)
{
char16_t* name = nullptr;
if (edgep->name) {
name = NS_strdup(edgep->name);
if (!name)
return false;
}
auto referent = node.getEdgeReferent(*edgep);
edges.infallibleAppend(mozilla::Move(Edge(name, referent)));
}
settle();
return true;
}
void popFront() override
{
i++;
@ -138,9 +124,9 @@ UniquePtr<EdgeRange>
Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const
{
UniquePtr<DeserializedEdgeRange, JS::DeletePolicy<DeserializedEdgeRange>> range(
js_new<DeserializedEdgeRange>());
js_new<DeserializedEdgeRange>(get()));
if (!range || !range->init(get()))
if (!range)
return nullptr;
return UniquePtr<EdgeRange>(range.release());

View File

@ -68,9 +68,7 @@ DEF_TEST(DeserializedNodeUbiNodes, {
10));
DeserializedEdge edge1(referent1->id);
mocked.addEdge(Move(edge1));
EXPECT_CALL(mocked,
getEdgeReferent(Field(&DeserializedEdge::referent,
referent1->id)))
EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent1->id)))
.Times(1)
.WillOnce(Return(JS::ubi::Node(referent1.get())));
@ -79,9 +77,7 @@ DEF_TEST(DeserializedNodeUbiNodes, {
20));
DeserializedEdge edge2(referent2->id);
mocked.addEdge(Move(edge2));
EXPECT_CALL(mocked,
getEdgeReferent(Field(&DeserializedEdge::referent,
referent2->id)))
EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent2->id)))
.Times(1)
.WillOnce(Return(JS::ubi::Node(referent2.get())));
@ -90,11 +86,15 @@ DEF_TEST(DeserializedNodeUbiNodes, {
30));
DeserializedEdge edge3(referent3->id);
mocked.addEdge(Move(edge3));
EXPECT_CALL(mocked,
getEdgeReferent(Field(&DeserializedEdge::referent,
referent3->id)))
EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent3->id)))
.Times(1)
.WillOnce(Return(JS::ubi::Node(referent3.get())));
ubi.edges(rt);
auto range = ubi.edges(rt);
ASSERT_TRUE(!!range);
for ( ; !range->empty(); range->popFront()) {
// Nothing to do here. This loop ensures that we get each edge referent
// that we expect above.
}
});

View File

@ -278,6 +278,12 @@ MATCHER(UniqueIsNull, "") {
return arg.get() == nullptr;
}
// Matches an edge whose referent is the node with the given id.
MATCHER_P(EdgeTo, id, "") {
return Matcher<const DeserializedEdge&>(Field(&DeserializedEdge::referent, id))
.MatchAndExplain(arg, result_listener);
}
} // namespace testing

View File

@ -46,7 +46,7 @@ interface nsIContentSecurityManager : nsISupports
* https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
*
* This method should only be used when the context of the URI isn't available
* since isSecureContext is preffered as it handles parent contexts.
* since isSecureContext is preferred as it handles parent contexts.
*
* This method returns false instead of throwing upon errors.
*/

View File

@ -1015,7 +1015,7 @@ TelephonyService.prototype = {
aCallback.notifyDialMMI(mmiServiceCode);
if (mmiServiceCode !== RIL.MMI_KS_SC_IMEI && !this._isRadioOn(aClientId)) {
if (mmiServiceCode !== MMI_KS_SC_IMEI && !this._isRadioOn(aClientId)) {
aCallback.notifyDialMMIError(DIAL_ERROR_RADIO_NOT_AVAILABLE);
return;
}

View File

@ -19,8 +19,12 @@ function testGettingIMEI() {
}
// Start test
startTest(function() {
testGettingIMEI()
startTestWithPermissions(['mobileconnection'], function() {
Promise.resolve()
.then(() => gSetRadioEnabledAll(false))
.then(() => testGettingIMEI())
.then(() => gSetRadioEnabledAll(true))
.then(() => testGettingIMEI())
.catch(error => ok(false, "Promise reject: " + error))
.then(finish);
});

View File

@ -3191,7 +3191,11 @@ BytecodeEmitter::emitSwitch(ParseNode* pn)
/* Emit code for evaluating cases and jumping to case statements. */
for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
ParseNode* caseValue = caseNode->pn_left;
if (caseValue && !emitTree(caseValue))
// If the expression is a literal, suppress line number
// emission so that debugging works more naturally.
if (caseValue &&
!emitTree(caseValue, caseValue->isLiteral() ? SUPPRESS_LINENOTE :
EMIT_LINENOTE))
return false;
if (!beforeCases) {
/* prevCaseOffset is the previous JSOP_CASE's bytecode offset. */
@ -7554,7 +7558,7 @@ BytecodeEmitter::emitClass(ParseNode* pn)
}
bool
BytecodeEmitter::emitTree(ParseNode* pn)
BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
{
JS_CHECK_RECURSION(cx, return false);
@ -7567,7 +7571,7 @@ BytecodeEmitter::emitTree(ParseNode* pn)
/* Emit notes to tell the current bytecode's source line number.
However, a couple trees require special treatment; see the
relevant emitter functions for details. */
if (pn->getKind() != PNK_WHILE && pn->getKind() != PNK_FOR &&
if (emitLineNote == EMIT_LINENOTE && pn->getKind() != PNK_WHILE && pn->getKind() != PNK_FOR &&
!updateLineNumberNotes(pn->pn_pos.begin))
return false;

View File

@ -336,8 +336,14 @@ struct BytecodeEmitter
void setJumpOffsetAt(ptrdiff_t off);
// Control whether emitTree emits a line number note.
enum EmitLineNumberNote {
EMIT_LINENOTE,
SUPPRESS_LINENOTE
};
// Emit code for the tree rooted at pn.
bool emitTree(ParseNode* pn);
bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
// Emit function code for the tree rooted at body.
bool emitFunctionScript(ParseNode* body);

View File

@ -0,0 +1,46 @@
// Test how stepping interacts with switch statements.
var g = newGlobal();
g.eval('function bob() { return "bob"; }');
// Stepping into a sparse switch should not stop on literal cases.
evaluate(`function lit(x) { // 1
debugger; // 2
switch(x) { // 3
case "nope": // 4
break; // 5
case "bob": // 6
break; // 7
} // 8
}`, {lineNumber: 1, global: g});
// Stepping into a sparse switch should stop on non-literal cases.
evaluate(`function nonlit(x) { // 1
debugger; // 2
switch(x) { // 3
case bob(): // 4
break; // 5
} // 6
}`, {lineNumber: 1, global: g});
var dbg = Debugger(g);
var badStep = false;
function test(s, okLine) {
dbg.onDebuggerStatement = function(frame) {
frame.onStep = function() {
let thisLine = this.script.getOffsetLocation(this.offset).lineNumber;
// The stop at line 3 is the switch.
if (thisLine > 3) {
assertEq(thisLine, okLine)
frame.onStep = undefined;
}
};
};
g.eval(s);
}
test("lit('bob');", 7);
test("nonlit('bob');", 4);

View File

@ -735,6 +735,7 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY restriction_disallow_master_password_title2 "Disable master password">
<!ENTITY restriction_disallow_guest_browsing_title2 "Disable Guest Browsing">
<!ENTITY restriction_disallow_advanced_settings_title "Disable Advanced Settings">
<!ENTITY restriction_disallow_camera_microphone_title "Block camera and microphone">
<!-- Default Bookmarks titles-->
<!-- LOCALIZATION NOTE (bookmarks_about_browser): link title for about:fennec -->

View File

@ -28,7 +28,8 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
Restriction.DISALLOW_MASTER_PASSWORD,
Restriction.DISALLOW_GUEST_BROWSING,
Restriction.DISALLOW_DEFAULT_THEME,
Restriction.DISALLOW_ADVANCED_SETTINGS
Restriction.DISALLOW_ADVANCED_SETTINGS,
Restriction.DISALLOW_CAMERA_MICROPHONE
);
private Context context;

View File

@ -52,7 +52,9 @@ public enum Restriction {
DISALLOW_DEFAULT_THEME(17, "no_default_theme", 0),
DISALLOW_ADVANCED_SETTINGS(18, "no_advanced_settings", R.string.restriction_disallow_advanced_settings_title);
DISALLOW_ADVANCED_SETTINGS(18, "no_advanced_settings", R.string.restriction_disallow_advanced_settings_title),
DISALLOW_CAMERA_MICROPHONE(19, "no_camera_microphone", R.string.restriction_disallow_camera_microphone_title);
public final int id;
public final String name;

View File

@ -579,6 +579,7 @@
<string name="restriction_disallow_master_password_title">&restriction_disallow_master_password_title2;</string>
<string name="restriction_disallow_guest_browsing_title">&restriction_disallow_guest_browsing_title2;</string>
<string name="restriction_disallow_advanced_settings_title">&restriction_disallow_advanced_settings_title;</string>
<string name="restriction_disallow_camera_microphone_title">&restriction_disallow_camera_microphone_title;</string>
<!-- Miscellaneous -->
<string name="ellipsis">&ellipsis;</string>

View File

@ -6,6 +6,7 @@
this.EXPORTED_SYMBOLS = ["WebrtcUI"];
XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls", "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
var WebrtcUI = {
_notificationId: null,
@ -108,6 +109,12 @@ var WebrtcUI = {
contentWindow.navigator.mozGetUserMediaDevices(
constraints,
function (devices) {
if (!ParentalControls.isAllowed(ParentalControls.CAMERA_MICROPHONE)) {
Services.obs.notifyObservers(null, "getUserMedia:response:deny", aSubject.callID);
WebrtcUI.showBlockMessage(devices);
return;
}
WebrtcUI.prompt(contentWindow, aSubject.callID, constraints.audio,
constraints.video, devices);
},
@ -187,6 +194,31 @@ var WebrtcUI = {
}
},
showBlockMessage: function(aDevices) {
let microphone = false;
let camera = false;
for (let device of aDevices) {
device = device.QueryInterface(Ci.nsIMediaDevice);
if (device.type == "audio") {
microphone = true;
} else if (device.type == "video") {
camera = true;
}
}
let message;
if (microphone && !camera) {
message = Strings.browser.GetStringFromName("getUserMedia.blockedMicrophoneAccess");
} else if (camera && !microphone) {
message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAccess");
} else {
message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAndMicrophoneAccess");
}
NativeWindow.doorhanger.show(message, "webrtc-blocked", [], BrowserApp.selectedTab.id, {});
},
prompt: function prompt(aContentWindow, aCallID, aAudioRequested,
aVideoRequested, aDevices) {
let audioDevices = [];

View File

@ -397,6 +397,9 @@ getUserMedia.audioDevice.prompt = Microphone to use
getUserMedia.sharingCamera.message2 = Camera is on
getUserMedia.sharingMicrophone.message2 = Microphone is on
getUserMedia.sharingCameraAndMicrophone.message2 = Camera and microphone are on
getUserMedia.blockedCameraAccess = Camera has been blocked.
getUserMedia.blockedMicrophoneAccess = Microphone has been blocked.
getUserMedia.blockedCameraAndMicrophoneAccess = Camera and microphone have been blocked.
# LOCALIZATION NOTE (readerMode.toolbarTip):
# Tip shown to users the first time we hide the reader mode toolbar.

View File

@ -8,6 +8,7 @@
#include "MainThreadUtils.h"
#include "SharedSSLState.h"
#include "nss.h"
using namespace mozilla;
using namespace mozilla::psm;
@ -58,5 +59,9 @@ WeakCryptoOverride::RemoveWeakCryptoOverride(const nsACString& aHostName,
const nsPromiseFlatCString& host = PromiseFlatCString(aHostName);
sharedState->IOLayerHelpers().removeInsecureFallbackSite(host, aPort);
// Some servers will fail with SSL_ERROR_ILLEGAL_PARAMETER_ALERT
// unless the session cache is cleared.
SSL_ClearSessionCache();
return NS_OK;
}

View File

@ -11,7 +11,7 @@ interface nsIFile;
interface nsIInterfaceRequestor;
interface nsIArray;
[scriptable, uuid(8c4962aa-e0e0-482e-b1db-7846cb78b3d6)]
[scriptable, uuid(ca8eb4f8-89bc-4479-91c9-1f381e045ed7)]
interface nsIParentalControlsService : nsISupports
{
/**
@ -35,6 +35,7 @@ interface nsIParentalControlsService : nsISupports
const short GUEST_BROWSING = 16; // Disallow usage of guest browsing
const short DEFAULT_THEME = 17; // Use default theme or a special parental controls theme
const short ADVANCED_SETTINGS = 18; // Advanced settings
const short CAMERA_MICROPHONE = 19; // Camera and microphone (WebRTC)
/**
* @returns true if the current user account has parental controls

View File

@ -22,6 +22,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
"@mozilla.org/contentsecuritymanager;1",
"nsIContentSecurityManager");
XPCOMUtils.defineLazyGetter(this, "log", () => {
let logger = LoginHelper.createLogger("LoginManagerContent");
return logger.log.bind(logger);
@ -1130,7 +1134,9 @@ var LoginManagerContent = {
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
let ph = Ci.nsIProtocolHandler;
if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
// Is the connection to localhost? Consider localhost safe for passwords.
if (gContentSecurityManager.isURIPotentiallyTrustworthy(uri) ||
netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {

View File

@ -764,7 +764,6 @@ var Impl = {
// task to complete, but TelemetryStorage blocks on it during shutdown.
TelemetryStorage.runCleanPingArchiveTask();
Telemetry.asyncFetchTelemetryData(function () {});
this._delayedInitTaskDeferred.resolve();
} catch (e) {
this._delayedInitTaskDeferred.reject(e);

View File

@ -174,6 +174,9 @@ var ClientIDImpl = {
// Not yet loaded, return the cached client id if we have one.
let id = Preferences.get(PREF_CACHED_CLIENTID, null);
if (id === null) {
return null;
}
if (!isValidClientID(id)) {
this._log.error("getCachedClientID - invalid client id in preferences, resetting", id);
Preferences.reset(PREF_CACHED_CLIENTID);