Merge m-c to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-10-08 16:11:21 +02:00
commit 819e5baeb9
106 changed files with 2517 additions and 1536 deletions

View File

@ -15,7 +15,7 @@
<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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<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="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
@ -122,7 +122,7 @@
<project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/>
<default remote="caf" revision="LNX.LA.3.5.2.1.1" sync-j="4"/>
<!-- Platform common things -->
<project name="device-shinano-common" path="device/sony/shinano-common" remote="b2g" revision="2f09386bb130b0111219cfc830316797392d48b0"/>
<project name="device-shinano-common" path="device/sony/shinano-common" remote="b2g" revision="4a5672c356ba539095109d827ba103cd8dfdcf1a"/>
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
<project name="device/qcom/common" path="device/qcom/common" revision="2501e5940ba69ece7654ff85611c76ae5bda299c"/>
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="d620691cad7aee780018e98159ff03bf99840317"/>

View File

@ -15,7 +15,7 @@
<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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<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="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>

View File

@ -17,7 +17,7 @@
</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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>

View File

@ -15,7 +15,7 @@
<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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>

View File

@ -2,7 +2,7 @@
"config_version": 2,
"tooltool_manifest": "releng-flame-kk.tt",
"mock_target": "mozilla-centos6-x86_64",
"mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2", "dosfstools"],
"mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2", "dosfstools", "java-1.6.0-openjdk"],
"mock_files": [
["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"],
["/builds/crash-stats-api.token", "/builds/crash-stats-api.token"]

View File

@ -15,7 +15,7 @@
<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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<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="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "b99837aa2294348317bcae68acabe71d9a83d774",
"git_revision": "4973f57cd8f9a62a95f783a24eac32da2bde99fc",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "cd2549520049a8532b966d3edda167f7bc444fba",
"revision": "c728de03bc96ef160fd5a662b3efcd5cf2c2b844",
"repo_path": "integration/gaia-central"
}

View File

@ -15,7 +15,7 @@
<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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<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="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>

View File

@ -18,7 +18,7 @@
<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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>

View File

@ -15,7 +15,7 @@
<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="b99837aa2294348317bcae68acabe71d9a83d774"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
<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="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
@ -141,7 +141,7 @@
<default remote="caf" revision="refs/tags/android-5.1.0_r1" sync-j="4"/>
<!-- Nexus 5 specific things -->
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="fe7df1bc8dd0fd71571505d7be1c31a4ad1e40fb"/>
<project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="844d9fe1c7114c6d586fbea611cbb7038413d762"/>
<project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="2900636d764df131e7914923c1ca813fc8879a7b"/>
<project name="device_lge_hammerhead-kernel" path="device/lge/hammerhead-kernel" remote="b2g" revision="8b3ffcfdd3d3852eca5488628f8bb2a08acbffa7"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="5d0ae53d9588c3d70c005aec9be94af9a534de16"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="c15b6e266136cd0cdd9b94d0bbed1962d9dd6672"/>

View File

@ -229,20 +229,14 @@ var StarUI = {
gEditItemOverlay.initPanel({ node: aNode
, hiddenRows: ["description", "location",
"loadInSidebar", "keyword"] });
"loadInSidebar", "keyword"]
, focusedElement: "preferred" });
}),
panelShown:
function SU_panelShown(aEvent) {
if (aEvent.target == this.panel) {
if (!this._element("editBookmarkPanelContent").hidden) {
let fieldToFocus = "editBMPanel_" +
gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField");
var elt = this._element(fieldToFocus);
elt.focus();
elt.select();
}
else {
if (this._element("editBookmarkPanelContent").hidden) {
// Note this isn't actually used anymore, we should remove this
// once we decide not to bring back the page bookmarked notification
this.panel.focus();

View File

@ -255,6 +255,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
"resource:///modules/ReaderParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
"resource://gre/modules/LoginManagerParent.jsm");
var gInitialPages = [
"about:blank",
"about:newtab",
@ -1192,6 +1195,10 @@ var gBrowserInit = {
}
}, false, true);
gBrowser.addEventListener("InsecureLoginFormsStateChange", function() {
gIdentityHandler.refreshForInsecureLoginForms();
});
let uriToLoad = this._getUriToLoad();
if (uriToLoad && uriToLoad != "about:blank") {
if (uriToLoad instanceof Ci.nsISupportsArray) {
@ -6990,10 +6997,7 @@ var gIdentityHandler = {
}
// Then, update the user interface with the available data.
if (this._identityBox) {
this.refreshIdentityBlock();
}
this.refreshIdentityBlock();
// NOTE: We do NOT update the identity popup (the control center) when
// we receive a new security state. If the user opened the popup and looks
@ -7001,6 +7005,20 @@ var gIdentityHandler = {
// contents.
},
/**
* This is called asynchronously when requested by the Logins module, after
* the insecure login forms state for the page has been updated.
*/
refreshForInsecureLoginForms() {
// Check this._uri because we don't want to refresh the user interface if
// this is called before the first page load in the window for any reason.
if (!this._uri) {
Cu.reportError("Unexpected early call to refreshForInsecureLoginForms.");
return;
}
this.refreshIdentityBlock();
},
/**
* Attempt to provide proper IDN treatment for host names
*/
@ -7037,6 +7055,10 @@ var gIdentityHandler = {
* Updates the identity block user interface with the data from this object.
*/
refreshIdentityBlock() {
if (!this._identityBox) {
return;
}
let icon_label = "";
let tooltip = "";
let icon_country_label = "";
@ -7105,6 +7127,11 @@ var gIdentityHandler = {
this._identityBox.classList.add("weakCipher");
}
}
if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
// Insecure login forms can only be present on "unknown identity"
// pages, either already insecure or with mixed active content loaded.
this._identityBox.classList.add("insecureLoginForms");
}
tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
}
@ -7142,6 +7169,12 @@ var gIdentityHandler = {
connection = "secure";
}
// Determine if there are insecure login forms.
let loginforms = "secure";
if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
loginforms = "insecure";
}
// Determine the mixed content state.
let mixedcontent = [];
if (this._isMixedPassiveContentLoaded) {
@ -7179,6 +7212,7 @@ var gIdentityHandler = {
for (let id of elementIDs) {
let element = document.getElementById(id);
updateAttribute(element, "connection", connection);
updateAttribute(element, "loginforms", loginforms);
updateAttribute(element, "ciphers", ciphers);
updateAttribute(element, "mixedcontent", mixedcontent);
updateAttribute(element, "isbroken", this._isBroken);

View File

@ -13,6 +13,6 @@ add_task(function* test_settingsOpen() {
Services.obs.notifyObservers(principal, "notifications-open-settings", null);
let tab = yield tabPromise;
ok(tab, "The notification settings tab opened");
BrowserTestUtils.removeTab(tab);
yield BrowserTestUtils.removeTab(tab);
});
});

View File

@ -269,7 +269,7 @@ tags = mcb
tags = mcb
[browser_bug906190.js]
tags = mcb
skip-if = buildapp == "mulet" || e10s # Bug 1093642 - test manipulates content and relies on content focus
skip-if = buildapp == "mulet" || e10s || os == "linux" # Bug 1093642 - test manipulates content and relies on content focus, Bug 1212520 - Re-enable on Linux
[browser_mixedContentFromOnunload.js]
tags = mcb
[browser_mixedContentFramesOnHttp.js]
@ -323,6 +323,7 @@ skip-if = e10s # Bug 863514 - no gesture support.
[browser_homeDrop.js]
skip-if = buildapp == 'mulet'
[browser_identity_UI.js]
[browser_insecureLoginForms.js]
[browser_keywordBookmarklets.js]
skip-if = e10s # Bug 1102025 - different principals for the bookmarklet only in e10s mode (unclear if test or 'real' issue)
[browser_keywordSearch.js]

View File

@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
* 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/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
}
/**
* Checks the insecure login forms logic for the identity block.
*/
add_task(function* test_simple() {
for (let scheme of ["http", "https"]) {
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
let { gIdentityHandler } = gBrowser.ownerGlobal;
gIdentityHandler._identityBox.click();
document.getElementById("identity-popup-security-expander").click();
if (scheme == "http") {
let identityBoxImage = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("page-proxy-favicon"), "")
.getPropertyValue("list-style-image");
let securityViewBG = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-popup-securityView"), "")
.getPropertyValue("background-image");
let securityContentBG = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-popup-security-content"), "")
.getPropertyValue("background-image");
is(identityBoxImage,
"url(\"chrome://browser/skin/identity-mixed-active-loaded.svg\")",
"Using expected icon image in the identity block");
is(securityViewBG,
"url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
"Using expected icon image in the Control Center main view");
is(securityContentBG,
"url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
"Using expected icon image in the Control Center subview");
}
// Messages should be visible when the scheme is HTTP, and invisible when
// 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.");
gIdentityHandler._identityPopup.hidden = true;
gBrowser.removeTab(tab);
}
});
/**
* Checks that the insecure login forms logic does not regress mixed content
* blocking messages when mixed active content is loaded.
*/
add_task(function* test_mixedcontent() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.mixed_content.block_active_content", false]],
}, resolve));
// Load the page with the subframe in a new tab.
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
assertMixedContentBlockingState(browser, { activeLoaded: true,
activeBlocked: false,
passiveLoaded: false });
gBrowser.removeTab(tab);
});

View File

@ -888,6 +888,13 @@ function assertMixedContentBlockingState(tabbrowser, states = {}) {
}
}
if (activeLoaded || activeBlocked || passiveLoaded) {
doc.getElementById("identity-popup-security-expander").click();
is(Array.filter(doc.querySelectorAll("[observes=identity-popup-mcb-learn-more]"),
element => !is_hidden(element)).length, 1,
"The 'Learn more' link should be visible once.");
}
gIdentityHandler._identityPopup.hidden = true;
}

View File

@ -41,6 +41,7 @@
<description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
<description class="identity-popup-warning-yellow"
when-ciphers="weak">&identity.weakEncryption;</description>
<description when-loginforms="insecure">&identity.insecureLoginForms;</description>
</vbox>
</vbox>
<button id="identity-popup-security-expander"
@ -116,7 +117,11 @@
when-connection="secure secure-ev"/>
<!-- Connection is Not Secure -->
<description when-connection="not-secure">&identity.description.insecure;</description>
<description when-connection="not-secure"
and-when-loginforms="secure">&identity.description.insecure;</description>
<!-- Insecure login forms -->
<description when-loginforms="insecure">&identity.description.insecureLoginForms;</description>
<!-- Weak Cipher -->
<description when-ciphers="weak">&identity.description.weakCipher;</description>
@ -138,8 +143,14 @@
class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Active Mixed Content Blocking Disabled -->
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded;</description>
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
<description when-mixedcontent="active-loaded"
and-when-loginforms="secure">&identity.description.activeLoaded;</description>
<description when-mixedcontent="active-loaded"
and-when-loginforms="secure">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Show only the first message when there are insecure login forms,
and make sure the Learn More link is included. -->
<description when-mixedcontent="active-loaded"
and-when-loginforms="insecure">&identity.description.activeLoaded; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Buttons to enable/disable mixed content blocking. -->
<button when-mixedcontent="active-blocked"

View File

@ -15,10 +15,10 @@ button::-moz-focus-inner {
z-index: 1020; /* required to have it superimposed to the video element */
border: 0;
left: 1.2rem;
right: .7rem;
height: 2.6rem;
right: 1.2rem;
height: 2.4rem;
position: absolute;
bottom: 1.5rem;
bottom: 1.2rem;
}
html[dir="rtl"] .conversation-toolbar {
@ -48,11 +48,11 @@ html[dir="rtl"] .conversation-toolbar > li {
.conversation-toolbar .btn {
background-position: center;
background-size: 28px;
background-size: 24px;
background-repeat: no-repeat;
background-color: transparent;
height: 28px;
width: 33px;
height: 24px;
width: 24px;
}
.conversation-toolbar-media-btn-group-box {
@ -855,8 +855,8 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
}
.standalone-room-wrapper > .media-layout {
/* 50px is the header, 3em is the footer. */
height: calc(100% - 50px - 3em);
/* 50px is the header, 10px is the bottom margin. */
height: calc(100% - 50px - 10px);
margin: 0 10px;
}
@ -977,8 +977,8 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
@media screen and (max-width:640px) {
.standalone-room-wrapper > .media-layout {
/* 50px is height of header, 25px is height of footer. */
height: calc(100% - 50px - 25px);
/* 50px is height of header, 10px is bottom margin. */
height: calc(100% - 50px - 10px);
}
.media-layout > .media-wrapper {
@ -1427,8 +1427,8 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
left: 0;
}
.standalone .room-conversation-wrapper .video-layout-wrapper {
/* 50px: header's height; 25px: footer's height */
height: calc(100% - 50px - 25px);
/* 50px: header's height; 10px: bottom margin */
height: calc(100% - 50px - 10px);
}
.standalone .room-conversation .video_wrapper.remote_wrapper {
width: 100%;

View File

@ -25,7 +25,8 @@ npm_install:
# assets
.PHONY: dist
dist:
cp -pr content dist
mkdir -p dist
cp -pR content dist
NODE_ENV="production" $(NODE_LOCAL_BIN)/webpack \
-p -v --display-errors
sed 's#webappEntryPoint.js#js/standalone.js#' \

View File

@ -57,74 +57,27 @@ body,
z-index: 1000;
}
/* Footer */
/* Mozilla Logo */
.footer-logo {
width: 100px;
margin: 0 auto;
height: 30px;
.focus-stream > .standalone-moz-logo {
width: 50px;
height: 13px;
background-size: contain;
background-image: url("../img/mozilla-logo.svg#logo-white");
background-repeat: no-repeat;
}
.rooms-footer {
background: #000;
margin: 0 10px;
text-align: left;
height: 3em;
position: relative;
}
html[dir="rtl"] .rooms-footer {
text-align: right;
}
.rooms-footer p {
/* Right-margin offset to account for .footer-logo plus 20px. */
/* Zero other margins due to 1em margin from reset.css. */
margin: 0 120px 0 0;
/* Vertically align in the middle. */
position: absolute;
top: 50%;
transform: translate(0, -50%);
bottom: 1.2rem;
right: 1.2rem;
left: auto;
}
html[dir="rtl"] .rooms-footer p {
margin: 0 0 0 120px;
}
.rooms-footer a {
color: #555;
}
.rooms-footer .footer-logo {
/* Vertically-align in the middle. */
position: absolute;
top: 50%;
transform: translate(0, -50%);
/* Align to the right. */
right: 0;
}
html[dir="rtl"] .rooms-footer .footer-logo {
left: 0;
html[dir="rtl"] .focus-stream > .standalone-moz-logo {
left: 1.2rem;
right: auto;
}
@media screen and (max-width:640px) {
.rooms-footer {
font-size: 80%;
height: 25px;
text-align: center;
}
.rooms-footer p {
margin: 0;
width: 100%;
}
.rooms-footer .footer-logo {
.focus-stream > .standalone-moz-logo {
display: none;
}
}

View File

@ -416,20 +416,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomFooter = React.createClass({displayName: "StandaloneRoomFooter",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
React.createElement("footer", {className: "rooms-footer"},
React.createElement("div", {className: "footer-logo"})
)
);
}
});
var StandaloneRoomView = React.createClass({displayName: "StandaloneRoomView",
mixins: [
Backbone.Events,
@ -678,14 +664,26 @@ loop.standaloneRoomViews = (function(mozL10n) {
publishStream: this.publishStream,
show: true,
video: {enabled: !this.state.videoMuted,
visible: this._roomIsActive()}})
),
React.createElement(StandaloneRoomFooter, {dispatcher: this.props.dispatcher})
visible: this._roomIsActive()}}),
React.createElement(StandaloneMozLogo, {dispatcher: this.props.dispatcher})
)
)
);
}
});
var StandaloneMozLogo = React.createClass({displayName: "StandaloneMozLogo",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
React.createElement("div", {className: "standalone-moz-logo"})
);
}
});
var StandaloneRoomControllerView = React.createClass({displayName: "StandaloneRoomControllerView",
mixins: [
loop.store.StoreMixin("activeRoomStore")
@ -726,7 +724,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneRoomView: StandaloneRoomView,

View File

@ -416,20 +416,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomFooter = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
<footer className="rooms-footer">
<div className="footer-logo" />
</footer>
);
}
});
var StandaloneRoomView = React.createClass({
mixins: [
Backbone.Events,
@ -679,13 +665,25 @@ loop.standaloneRoomViews = (function(mozL10n) {
show={true}
video={{enabled: !this.state.videoMuted,
visible: this._roomIsActive()}} />
<StandaloneMozLogo dispatcher={this.props.dispatcher}/>
</sharedViews.MediaLayoutView>
<StandaloneRoomFooter dispatcher={this.props.dispatcher} />
</div>
);
}
});
var StandaloneMozLogo = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
<div className="standalone-moz-logo" />
);
}
});
var StandaloneRoomControllerView = React.createClass({
mixins: [
loop.store.StoreMixin("activeRoomStore")
@ -726,7 +724,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneRoomView: StandaloneRoomView,

View File

@ -315,7 +315,8 @@ var BookmarkPropertiesPanel = {
switch (this._action) {
case ACTION_EDIT:
gEditItemOverlay.initPanel({ node: this._node
, hiddenRows: this._hiddenRows });
, hiddenRows: this._hiddenRows
, focusedElement: "first" });
acceptButton.disabled = gEditItemOverlay.readOnly;
break;
case ACTION_ADD:
@ -323,7 +324,8 @@ var BookmarkPropertiesPanel = {
// Edit the new item
gEditItemOverlay.initPanel({ node: this._node
, hiddenRows: this._hiddenRows
, postData: this._postData });
, postData: this._postData
, focusedElement: "first" });
// Empty location field if the uri is about:blank, this way inserting a new
// url will be easier for the user, Accept button will be automatically

View File

@ -56,12 +56,13 @@ var gEditItemOverlay = {
PlacesUIUtils.isContentsReadOnly(parent);
}
}
let focusedElement = aInitInfo.focusedElement;
return this._paneInfo = { itemId, itemGuid, isItem,
isURI, uri, title,
isBookmark, isFolderShortcut, isParentReadOnly,
bulkTagging, uris,
visibleRows, postData, isTag };
visibleRows, postData, isTag, focusedElement };
},
get initialized() {
@ -181,7 +182,7 @@ var gEditItemOverlay = {
let { itemId, itemGuid, isItem,
isURI, uri, title,
isBookmark, bulkTagging, uris,
visibleRows } = this._setPaneInfo(aInfo);
visibleRows, focusedElement } = this._setPaneInfo(aInfo);
let showOrCollapse =
(rowId, isAppropriateForInput, nameInHiddenRows = null) => {
@ -252,6 +253,24 @@ var gEditItemOverlay = {
window.addEventListener("unload", this, false);
this._observersAdded = true;
}
// The focusedElement possible values are:
// * preferred: focus the field that the user touched first the last
// time the pane was shown (either namePicker or tagsField)
// * first: focus the first non collapsed textbox
// Note: since all controls are collapsed by default, we don't get the
// default XUL dialog behavior, that selects the first control, so we set
// the focus explicitly.
let elt;
if (focusedElement === "preferred") {
elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField"));
} else if (focusedElement === "first") {
elt = document.querySelector("textbox:not([collapsed=true])");
}
if (elt) {
elt.focus();
elt.select();
}
},
/**

View File

@ -322,6 +322,15 @@ var withBookmarksDialog = Task.async(function* (autoCancel, openFn, taskFn) {
yield waitForCondition(() => dialogWin.gEditItemOverlay.initialized,
"EditItemOverlay should be initialized");
// Check the first textbox is focused.
let doc = dialogWin.document;
let elt = doc.querySelector("textbox:not([collapsed=true])");
if (elt) {
info("waiting for focus on the first textfield");
yield waitForCondition(() => doc.activeElement == elt.inputField,
"The first non collapsed textbox should have been focused");
}
info("withBookmarksDialog: executing the task");
try {
yield taskFn(dialogWin);
@ -331,7 +340,7 @@ var withBookmarksDialog = Task.async(function* (autoCancel, openFn, taskFn) {
ok(false, "The test should have closed the dialog!");
}
info("withBookmarksDialog: canceling the dialog");
dialogWin.document.documentElement.cancelDialog();
doc.documentElement.cancelDialog();
}
}
});

View File

@ -765,7 +765,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
#forward-button > .toolbarbutton-icon {
background-clip: padding-box;
padding-left: 9px;
padding-left: calc(var(--backbutton-urlbar-overlap) + 4px);
padding-right: 3px;
border: 1px solid #9a9a9a;
border-left-style: none;
@ -1206,7 +1206,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
-moz-appearance: none;
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
padding: 0 9px;
margin-inline-start: 2px;
margin-inline-start: 5px;
border-inline-start: 1px solid var(--urlbar-separator-color);
border-image: linear-gradient(transparent 15%,
var(--urlbar-separator-color) 15%,

View File

@ -8,7 +8,6 @@ browser.jar:
#include ../shared/jar.inc.mn
skin/classic/browser/sanitizeDialog.css
skin/classic/browser/aboutSessionRestore-window-icon.png
skin/classic/browser/aboutCertError.css
skin/classic/browser/aboutCertError_sectionCollapsed.png
skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
skin/classic/browser/aboutCertError_sectionExpanded.png

View File

@ -1,72 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
background: -moz-Dialog;
}
body {
margin: 0;
padding: 0 1em;
color: -moz-FieldText;
font: message-box;
}
h1 {
margin: 0 0 .6em 0;
border-bottom: 1px solid ThreeDLightShadow;
font-size: 160%;
}
h2 {
font-size: 130%;
}
#errorPageContainer {
position: relative;
min-width: 13em;
max-width: 52em;
margin: 4em auto;
border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
border-radius: 10px;
padding: 3em;
-moz-padding-start: 30px;
background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
background-origin: content-box;
}
#errorPageContainer:-moz-dir(rtl) {
background-position: right 0;
}
#errorTitle {
-moz-margin-start: 80px;
}
#errorLongContent {
-moz-margin-start: 80px;
}
.expander > button {
-moz-padding-start: 20px;
-moz-margin-start: -20px;
background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
border: none;
font: inherit;
color: inherit;
cursor: pointer;
}
.expander > button:-moz-dir(rtl) {
background-position: right center;
}
.expander[collapsed] > button {
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
}
.expander[collapsed] > button:-moz-dir(rtl) {
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
}

View File

@ -1898,8 +1898,7 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
margin: 0;
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
padding: 0 9px;
margin-inline-start: 2px;
border-inline-end-style: none;
margin-inline-start: 5px;
border-inline-start: 1px solid var(--urlbar-separator-color);
border-image: linear-gradient(transparent 15%,
var(--urlbar-separator-color) 15%,

View File

@ -7,7 +7,6 @@ browser.jar:
#include ../shared/jar.inc.mn
skin/classic/browser/sanitizeDialog.css
skin/classic/browser/aboutSessionRestore-window-icon.png
skin/classic/browser/aboutCertError.css
skin/classic/browser/aboutCertError_sectionCollapsed.png
skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
skin/classic/browser/aboutCertError_sectionExpanded.png

View File

@ -5,7 +5,7 @@
%endif
/* Hide all conditional elements by default. */
:-moz-any([when-connection],[when-mixedcontent],[when-ciphers]) {
:-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
display: none;
}
@ -15,6 +15,8 @@
#identity-popup[connection=secure] [when-connection~=secure],
#identity-popup[connection=chrome] [when-connection~=chrome],
#identity-popup[connection=file] [when-connection~=file],
/* Show insecure login forms messages when needed. */
#identity-popup[loginforms=insecure] [when-loginforms=insecure],
/* Show weak cipher messages when needed. */
#identity-popup[ciphers=weak] [when-ciphers~=weak],
/* Show mixed content warnings when needed */
@ -28,6 +30,14 @@
display: inherit;
}
/* Hide redundant messages based on insecure login forms presence. */
#identity-popup[loginforms=secure] [and-when-loginforms=insecure] {
display: none;
}
#identity-popup[loginforms=insecure] [and-when-loginforms=secure] {
display: none;
}
/* Hide 'not secure' message in subview when weak cipher or mixed content messages are shown. */
#identity-popup-securityView-body:-moz-any([mixedcontent],[ciphers]) > description[when-connection=not-secure],
/* Hide 'passive-loaded (only)' message when there is mixed passive content loaded and active blocked. */
@ -219,6 +229,8 @@
background-image: url(chrome://browser/skin/controlcenter/conn-degraded.svg);
}
#identity-popup[loginforms=insecure] #identity-popup-securityView,
#identity-popup[loginforms=insecure] #identity-popup-security-content,
#identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-securityView,
#identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-security-content {
background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg);

View File

@ -123,6 +123,7 @@
list-style-image: url(chrome://browser/skin/identity-secure.svg);
}
.insecureLoginForms > #identity-icons > #page-proxy-favicon[pageproxystate="valid"],
.mixedActiveContent > #identity-icons > #page-proxy-favicon[pageproxystate="valid"] {
list-style-image: url(chrome://browser/skin/identity-mixed-active-loaded.svg);
}

View File

@ -7,6 +7,7 @@
# be specified once. As a result, the source file paths are relative
# to the location of the actual manifest.
skin/classic/browser/aboutCertError.css (../shared/aboutCertError.css)
skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css)
skin/classic/browser/aboutNetError_info.svg (../shared/aboutNetError_info.svg)
skin/classic/browser/aboutNetError_alert.svg (../shared/aboutNetError_alert.svg)

View File

@ -99,7 +99,7 @@
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box {
padding-left: 7px;
padding-left: calc(var(--backbutton-urlbar-overlap) + 3px);
}
/* This changes the direction of the main notification box on the url bar. */

View File

@ -1,72 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
background: -moz-Dialog;
}
body {
margin: 0;
padding: 0 1em;
color: -moz-FieldText;
font: message-box;
}
h1 {
margin: 0 0 .6em 0;
border-bottom: 1px solid ThreeDLightShadow;
font-size: 160%;
}
h2 {
font-size: 130%;
}
#errorPageContainer {
position: relative;
min-width: 13em;
max-width: 52em;
margin: 4em auto;
border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
border-radius: 10px;
padding: 3em;
-moz-padding-start: 30px;
background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
background-origin: content-box;
}
#errorPageContainer:-moz-dir(rtl) {
background-position: right 0;
}
#errorTitle {
-moz-margin-start: 80px;
}
#errorLongContent {
-moz-margin-start: 80px;
}
.expander > button {
-moz-padding-start: 20px;
-moz-margin-start: -20px;
background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
border: none;
font: inherit;
color: inherit;
cursor: pointer;
}
.expander > button:-moz-dir(rtl) {
background-position: right center;
}
.expander[collapsed] > button {
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
}
.expander[collapsed] > button:-moz-dir(rtl) {
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
}

View File

@ -12,25 +12,23 @@
%include windowsShared.inc
%filter substitution
%define toolbarShadowColor hsla(209,67%,12%,0.35)
%define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
%define forwardTransitionLength 150ms
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
%define conditionalForwardWithUrlbarWidth 30
:root {
--space-above-tabbar: 15px;
--backbutton-urlbar-overlap: 5px;
--backbutton-urlbar-overlap: 6px;
--toolbarbutton-vertical-inner-padding: 2px;
--toolbarbutton-vertical-outer-padding: 8px;
--toolbarbutton-hover-background: rgba(0,0,0,.1);
--toolbarbutton-hover-bordercolor: rgba(0,0,0,.1);
--toolbarbutton-hover-bordercolor: rgba(0,0,0,.2);
--toolbarbutton-hover-boxshadow: none;
--toolbarbutton-active-background: rgba(0,0,0,.15);
--toolbarbutton-active-bordercolor: rgba(0,0,0,.15);
--toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
--toolbarbutton-active-boxshadow: 0 0 0 1px rgba(0,0,0,.15) inset;
--toolbarbutton-checkedhover-backgroundcolor: rgba(0,0,0,.1);
@ -51,18 +49,6 @@
--urlbar-separator-color: hsla(0,0%,16%,.2);
}
#nav-bar[brighttext] {
--toolbarbutton-hover-background: rgba(255,255,255,.15);
--toolbarbutton-hover-bordercolor: rgba(255,255,255,.15);
--toolbarbutton-hover-boxshadow: none;
--toolbarbutton-active-background: rgba(255,255,255,.3);
--toolbarbutton-active-bordercolor: rgba(255,255,255,.3);
--toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.3) inset;
--toolbarbutton-checkedhover-backgroundcolor: rgba(255,255,255,.2);
}
#menubar-items {
-moz-box-orient: vertical; /* for flex hack */
}
@ -727,6 +713,8 @@ toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
padding: var(--toolbarbutton-vertical-inner-padding) 6px;
background-origin: padding-box !important;
background-clip: padding-box !important;
border: 1px solid transparent;
border-radius: 1px;
transition-property: background-color, border-color, box-shadow;
@ -856,10 +844,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
opacity: .3;
}
@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
border-color: hsla(210,4%,10%,.1);
}
.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover > .toolbarbutton-text,
#nav-bar .toolbarbutton-1:not([disabled=true]) > .toolbarbutton-menubutton-button[open] + .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
#nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
@ -919,6 +903,15 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
/* unified back/forward button */
:-moz-any(#back-button, #forward-button) > .toolbarbutton-icon {
border-color: var(--urlbar-border-color-hover) !important;
}
:-moz-any(#back-button, #forward-button):not(:hover):not(:active):not([open=true]) > .toolbarbutton-icon,
:-moz-any(#back-button, #forward-button)[disabled=true] > .toolbarbutton-icon {
background-color: rgba(255,255,255,.15) !important;
}
#forward-button {
-moz-box-align: stretch; /* let the button shape grow vertically with the location bar */
padding: 0 !important;
@ -929,12 +922,13 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
}
#forward-button > .toolbarbutton-icon {
background-clip: padding-box !important;
border-left-style: none !important;
border-radius: 0 !important;
padding-left: 9px !important;
padding-left: calc(var(--backbutton-urlbar-overlap) + 3px) !important;
padding-right: 3px !important;
width: 31px !important; /* horizontal padding + border + actual icon width */
% icon width + border + horizontal padding without --backbutton-urlbar-overlap
%define forwardButtonWidth 25
width: calc(@forwardButtonWidth@px + var(--backbutton-urlbar-overlap)) !important;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
@ -942,7 +936,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
margin-left: -@conditionalForwardWithUrlbarWidth@px;
margin-left: calc(-@forwardButtonWidth@px - var(--backbutton-urlbar-overlap));
}
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
@ -952,7 +946,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
/* when not hovered anymore, trigger a new transition to hide the forward button immediately */
margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
margin-left: calc(-@forwardButtonWidth@.01px - var(--backbutton-urlbar-overlap));
}
#back-button {
@ -963,8 +957,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
position: relative !important;
z-index: 1 !important;
border-radius: 0 10000px 10000px 0 !important;
--toolbarbutton-hover-boxshadow: 0 1px 0 0 hsla(210,4%,10%,.25),
0 0 0 1px hsla(210,4%,10%,.25);
}
#back-button:-moz-locale-dir(rtl) {
@ -977,64 +969,8 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
#back-button > .toolbarbutton-icon {
border-radius: 10000px !important;
background-clip: padding-box !important;
background-color: hsla(210,25%,98%,.08) !important;
padding: 6px !important;
border-style: none !important;
width: 30px !important; /* horizontal padding + border + actual icon width */
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.25),
0 0 0 1px hsla(210,4%,10%,.25);
transition-property: background-color, box-shadow !important;
transition-duration: 250ms !important;
}
#back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
background-color: hsla(210,4%,10%,.08) !important;
}
#back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
#back-button[open="true"] > .toolbarbutton-icon {
background-color: hsla(210,4%,10%,.12) !important;
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.25),
0 0 0 1px hsla(210,4%,10%,.25),
0 1px 0 0 hsla(210,80%,20%,.1) inset !important;
}
@media (-moz-os-version: windows-xp),
(-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
#back-button > .toolbarbutton-icon {
background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)) !important;
box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(210,54%,20%,.25),
0 1px 0 hsla(210,54%,20%,.35) !important;
}
#back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
background-color: hsla(210,48%,96%,.75) !important;
box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(210,54%,20%,.3),
0 1px 0 hsla(210,54%,20%,.4),
0 0 4px hsla(210,54%,20%,.2) !important;
}
#back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
#back-button[open="true"] > .toolbarbutton-icon {
background-color: hsla(210,54%,20%,.15) !important;
box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
0 0 1px hsla(210,54%,20%,.2) inset,
0 0 0 1px hsla(210,54%,20%,.4),
0 1px 0 hsla(210,54%,20%,.2) !important;
transition: none;
}
#main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
0 1px 0 hsla(210,54%,20%,.65) !important;
transition: none;
}
width: 32px !important; /* icon width + horizontal padding + border */
}
#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
@ -1173,13 +1109,46 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
/* ::::: Location Bar ::::: */
#nav-bar {
--urlbar-border-color: ThreeDShadow;
--urlbar-border-color-hover: var(--urlbar-border-color);
}
#nav-bar:-moz-lwtheme {
--urlbar-border-color: rgba(0,0,0,.32);
}
@media (-moz-windows-default-theme) {
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7),
(-moz-os-version: windows-win8) {
#nav-bar:not(:-moz-lwtheme) {
--urlbar-border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
--urlbar-border-color-hover: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
}
}
@media (-moz-os-version: windows-win10) {
#nav-bar:not(:-moz-lwtheme) {
--urlbar-border-color: hsl(0,0%,90%);
--urlbar-border-color-hover: hsl(0,0%,80%);
}
}
}
#urlbar,
.searchbar-textbox {
-moz-appearance: none;
margin: 0 3px;
padding: 0;
background-clip: padding-box;
border: 1px solid ThreeDShadow;
border: 1px solid;
border-color: var(--urlbar-border-color);
}
#urlbar:hover,
.searchbar-textbox:hover {
border-color: var(--urlbar-border-color-hover);
}
/* overlap the urlbar's border */
@ -1190,7 +1159,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
@media (-moz-windows-default-theme) {
#urlbar,
.searchbar-textbox {
@navbarTextboxCustomBorder@
border-radius: 1px;
}
@ -1199,31 +1167,19 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
(-moz-os-version: windows-win8) {
#urlbar:not(:-moz-lwtheme),
.searchbar-textbox:not(:-moz-lwtheme) {
border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
box-shadow: 0 1px 0 hsla(0,0%,0%,.01) inset,
0 1px 0 hsla(0,0%,100%,.1);
}
#urlbar:not(:-moz-lwtheme):hover,
.searchbar-textbox:not(:-moz-lwtheme):hover {
border-color: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
}
}
@media (-moz-os-version: windows-win10) {
#urlbar:not(:-moz-lwtheme),
.searchbar-textbox:not(:-moz-lwtheme) {
border-color: hsl(0,0%,90%);
padding: 1px;
transition-property: border-color, box-shadow;
transition-duration: .1s;
}
#urlbar:not(:-moz-lwtheme):hover,
.searchbar-textbox:not(:-moz-lwtheme):hover {
border-color: hsl(0,0%,80%);
}
#urlbar:not(:-moz-lwtheme)[focused],
.searchbar-textbox:not(:-moz-lwtheme)[focused] {
box-shadow: 0 0 0 1px Highlight inset;
@ -1260,7 +1216,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
#urlbar:-moz-lwtheme,
.searchbar-textbox:-moz-lwtheme {
background-color: rgba(255,255,255,.8);
@navbarTextboxCustomBorder@
color: black;
}
@ -1301,7 +1256,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
}
:root {
--backbutton-urlbar-overlap: 8px;
--backbutton-urlbar-overlap: 9px;
}
}
@ -1609,7 +1564,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
border-style: none;
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
padding: 0 9px;
margin-inline-start: 2px;
margin-inline-start: 5px;
border-inline-start: 1px solid var(--urlbar-separator-color);
border-image: linear-gradient(transparent 15%,
var(--urlbar-separator-color) 15%,

View File

@ -7,7 +7,6 @@ browser.jar:
#include ../shared/jar.inc.mn
skin/classic/browser/sanitizeDialog.css
skin/classic/browser/aboutSessionRestore-window-icon.png
skin/classic/browser/aboutCertError.css
skin/classic/browser/aboutCertError_sectionCollapsed.png
skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
skin/classic/browser/aboutCertError_sectionExpanded.png

View File

@ -164,6 +164,7 @@ devtools.jar:
* skin/themes/common.css (themes/common.css)
* skin/themes/dark-theme.css (themes/dark-theme.css)
* skin/themes/light-theme.css (themes/light-theme.css)
skin/themes/variables.css (themes/variables.css)
skin/themes/images/add.svg (themes/images/add.svg)
skin/themes/images/filters.svg (themes/images/filters.svg)
skin/themes/images/filter-swatch.svg (themes/images/filter-swatch.svg)

View File

@ -23,6 +23,11 @@ add_task(function*() {
parentContainer.elt.scrollIntoView(true);
info("Testing putting an element back in it's original place");
yield dragElementToOriginalLocation("#firstChild", inspector);
is(parent.children[0].id, "firstChild", "#firstChild is still the first child of #test");
is(parent.children[1].id, "middleChild", "#middleChild is still the second child of #test");
info("Testing switching elements inside their parent");
yield moveElementDown("#firstChild", "#middleChild", inspector);
@ -95,6 +100,27 @@ function* dragContainer(selector, targetOffset, inspector) {
return updated;
};
function* dragElementToOriginalLocation(selector, inspector) {
let el = yield getContainerForSelector(selector, inspector);
let height = el.tagLine.getBoundingClientRect().height;
info("Picking up and putting back down " + selector);
function onMutation() {
ok(false, "Mutation received from dragging a node back to its location");
}
inspector.on("markupmutation", onMutation);
yield dragContainer(selector, {x: 0, y: 0}, inspector);
// Wait a bit to make sure the event never fires.
// This doesn't need to catch *all* cases, since the mutation
// will cause failure later in the test when it checks element ordering.
yield new Promise(resolve => {
setTimeout(resolve, 500);
});
inspector.off("markupmutation", onMutation);
}
function* moveElementDown(selector, next, inspector) {
let onMutated = inspector.once("markupmutation");
let uiUpdate = inspector.once("inspector-updated");
@ -106,6 +132,7 @@ function* moveElementDown(selector, next, inspector) {
yield dragContainer(selector, {x: 0, y: Math.round(height) + 2}, inspector);
yield onMutated;
let mutations = yield onMutated;
is(mutations.length, 2, "2 mutations");
yield uiUpdate;
};

View File

@ -14,7 +14,6 @@
"use strict";
const {cssTokenizer} = require("devtools/client/sourceeditor/css-tokenizer");
const {Cc, Ci, Cu} = require("chrome");
Cu.importGlobalProperties(["CSS"]);
const promise = require("promise");
@ -43,6 +42,85 @@ const BLANK_LINE_RX = /^[ \t]*(?:\r\n|\n|\r|\f|$)/;
// bypass the property name validity heuristic.
const COMMENT_PARSING_HEURISTIC_BYPASS_CHAR = "!";
/**
* A generator function that lexes a CSS source string, yielding the
* CSS tokens. Comment tokens are dropped.
*
* @param {String} CSS source string
* @yield {CSSToken} The next CSSToken that is lexed
* @see CSSToken for details about the returned tokens
*/
function* cssTokenizer(string) {
let lexer = DOMUtils.getCSSLexer(string);
while (true) {
let token = lexer.nextToken();
if (!token) {
break;
}
// None of the existing consumers want comments.
if (token.tokenType !== "comment") {
yield token;
}
}
}
/**
* Pass |string| to the CSS lexer and return an array of all the
* returned tokens. Comment tokens are not included. In addition to
* the usual information, each token will have starting and ending
* line and column information attached. Specifically, each token
* has an additional "loc" attribute. This attribute is an object
* of the form {line: L, column: C}. Lines and columns are both zero
* based.
*
* It's best not to add new uses of this function. In general it is
* simpler and better to use the CSSToken offsets, rather than line
* and column. Also, this function lexes the entire input string at
* once, rather than lazily yielding a token stream. Use
* |cssTokenizer| or |DOMUtils.getCSSLexer| instead.
*
* @param{String} string The input string.
* @return {Array} An array of tokens (@see CSSToken) that have
* line and column information.
*/
function cssTokenizerWithLineColumn(string) {
let lexer = DOMUtils.getCSSLexer(string);
let result = [];
let prevToken = undefined;
while (true) {
let token = lexer.nextToken();
let lineNumber = lexer.lineNumber;
let columnNumber = lexer.columnNumber;
if (prevToken) {
prevToken.loc.end = {
line: lineNumber,
column: columnNumber
};
}
if (!token) {
break;
}
if (token.tokenType === "comment") {
// We've already dealt with the previous token's location.
prevToken = undefined;
} else {
let startLoc = {
line: lineNumber,
column: columnNumber
};
token.loc = {start: startLoc};
result.push(token);
prevToken = token;
}
}
return result;
}
/**
* Escape a comment body. Find the comment start and end strings in a
* string and inserts backslashes so that the resulting text can
@ -992,6 +1070,8 @@ function parseSingleValue(value) {
};
}
exports.cssTokenizer = cssTokenizer;
exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;
exports.escapeCSSComment = escapeCSSComment;
// unescapeCSSComment is exported for testing.
exports._unescapeCSSComment = unescapeCSSComment;

View File

@ -17,6 +17,7 @@ DevToolsModules(
'AppCacheUtils.jsm',
'autocomplete-popup.js',
'browser-loader.js',
'css-parsing-utils.js',
'Curl.jsm',
'DeveloperToolbar.jsm',
'devices.js',

View File

@ -8,7 +8,7 @@
var Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {escapeCSSComment, _unescapeCSSComment} =
devtools.require("devtools/client/styleinspector/css-parsing-utils");
devtools.require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
{

View File

@ -6,9 +6,9 @@
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {parseDeclarations, _parseCommentDeclarations} =
require("devtools/client/styleinspector/css-parsing-utils");
require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
// Simple test

View File

@ -6,13 +6,13 @@
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {
parsePseudoClassesAndAttributes,
SELECTOR_ATTRIBUTE,
SELECTOR_ELEMENT,
SELECTOR_PSEUDO_CLASS
} = require("devtools/client/styleinspector/css-parsing-utils");
} = require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
// Test that a null input throws an exception

View File

@ -6,8 +6,8 @@
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
const {parseSingleValue} = require("devtools/client/styleinspector/css-parsing-utils");
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {parseSingleValue} = require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
{input: null, throws: true},

View File

@ -8,7 +8,7 @@
var Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {parseDeclarations, RuleRewriter} =
devtools.require("devtools/client/styleinspector/css-parsing-utils");
devtools.require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
{

View File

@ -10,6 +10,11 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_attribute-parsing-02.js]
[test_bezierCanvas.js]
[test_cubicBezier.js]
[test_escapeCSSComment.js]
[test_parseDeclarations.js]
[test_parsePseudoClassesAndAttributes.js]
[test_parseSingleValue.js]
[test_rewriteDeclarations.js]
[test_undoStack.js]
[test_VariablesView_filtering-without-controller.js]
[test_VariablesView_getString_promise.js]

View File

@ -14,12 +14,13 @@ const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/client/framework/gDevTools.jsm");
const themeURIs = {
light: "chrome://devtools/skin/themes/light-theme.css",
dark: "chrome://devtools/skin/themes/dark-theme.css"
const VARIABLES_URI = "chrome://devtools/skin/themes/variables.css";
const THEME_SELECTOR_STRINGS = {
light: ":root.theme-light {",
dark: ":root.theme-dark {"
}
const cachedThemes = {};
let variableFileContents;
/**
* Returns a string of the file found at URI
@ -37,19 +38,29 @@ function readURI (uri) {
}
/**
* Takes a theme name and either returns it from the cache,
* or fetches the theme CSS file and caches it.
* Takes a theme name and returns the contents of its variable rule block.
* The first time this runs fetches the variables CSS file and caches it.
*/
function getThemeFile (name) {
// Use the cached theme, or generate it
let themeFile = cachedThemes[name] || readURI(themeURIs[name]).match(/--theme-.*: .*;/g).join("\n");
// Cache if not already cached
if (!cachedThemes[name]) {
cachedThemes[name] = themeFile;
if (!variableFileContents) {
variableFileContents = readURI(VARIABLES_URI);
}
return themeFile;
// If there's no theme expected for this name, use `light` as default.
let selector = THEME_SELECTOR_STRINGS[name] ||
THEME_SELECTOR_STRINGS["light"];
// This is a pretty naive way to find the contents between:
// selector {
// name: val;
// }
// There is test coverage for this feature (browser_theme.js)
// so if an } is introduced in the variables file it will catch that.
let theme = variableFileContents;
theme = theme.substring(theme.indexOf(selector));
theme = theme.substring(0, theme.indexOf("}"));
return theme;
}
/**
@ -66,17 +77,11 @@ const getTheme = exports.getTheme = () => Services.prefs.getCharPref("devtools.t
*/
const getColor = exports.getColor = (type, theme) => {
let themeName = theme || getTheme();
// If there's no theme URIs for this theme, use `light` as default.
if (!themeURIs[themeName]) {
themeName = "light";
}
let themeFile = getThemeFile(themeName);
let match;
let match = themeFile.match(new RegExp("--theme-" + type + ": (.*);"));
// Return the appropriate variable in the theme, or otherwise, null.
return (match = themeFile.match(new RegExp("--theme-" + type + ": (.*);"))) ? match[1] : null;
return match ? match[1] : null;
};
/**

View File

@ -14,7 +14,7 @@ const { Cu } = require("chrome");
const { ViewHelpers } = Cu.import("resource:///modules/devtools/client/shared/widgets/ViewHelpers.jsm", {});
const STRINGS_URI = "chrome://browser/locale/devtools/filterwidget.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const {cssTokenizer} = require("devtools/client/sourceeditor/css-tokenizer");
const {cssTokenizer} = require("devtools/client/shared/css-parsing-utils");
loader.lazyGetter(this, "asyncStorage",
() => require("devtools/shared/shared/async-storage"));

View File

@ -4,10 +4,10 @@
const { Cc, Ci, Cu } = require('chrome');
const {cssTokenizer, cssTokenizerWithLineColumn} =
require("devtools/client/sourceeditor/css-tokenizer");
require("devtools/client/shared/css-parsing-utils");
/**
* Here is what this file (+ ./css-tokenizer.js) do.
* Here is what this file (+ css-parsing-utils.js) do.
*
* The main objective here is to provide as much suggestions to the user editing
* a stylesheet in Style Editor. The possible things that can be suggested are:
@ -22,7 +22,7 @@ const {cssTokenizer, cssTokenizerWithLineColumn} =
* edited by the user, figure out what token or word is being written and last
* but the most difficult, what is being edited.
*
* The file 'css-tokenizer' helps in converting the CSS into meaningful tokens,
* The file 'css-parsing-utils' helps to convert the CSS into meaningful tokens,
* each having a certain type associated with it. These tokens help us to figure
* out the currently edited word and to write a CSS state machine to figure out
* what the user is currently editing. By that, I mean, whether he is editing a

View File

@ -1,95 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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";
const {Cc, Ci} = require("chrome");
loader.lazyGetter(this, "DOMUtils", () => {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
/**
* A generator function that lexes a CSS source string, yielding the
* CSS tokens. Comment tokens are dropped.
*
* @param {String} CSS source string
* @yield {CSSToken} The next CSSToken that is lexed
* @see CSSToken for details about the returned tokens
*/
function* cssTokenizer(string) {
let lexer = DOMUtils.getCSSLexer(string);
while (true) {
let token = lexer.nextToken();
if (!token) {
break;
}
// None of the existing consumers want comments.
if (token.tokenType !== "comment") {
yield token;
}
}
}
exports.cssTokenizer = cssTokenizer;
/**
* Pass |string| to the CSS lexer and return an array of all the
* returned tokens. Comment tokens are not included. In addition to
* the usual information, each token will have starting and ending
* line and column information attached. Specifically, each token
* has an additional "loc" attribute. This attribute is an object
* of the form {line: L, column: C}. Lines and columns are both zero
* based.
*
* It's best not to add new uses of this function. In general it is
* simpler and better to use the CSSToken offsets, rather than line
* and column. Also, this function lexes the entire input string at
* once, rather than lazily yielding a token stream. Use
* |cssTokenizer| or |DOMUtils.getCSSLexer| instead.
*
* @param{String} string The input string.
* @return {Array} An array of tokens (@see CSSToken) that have
* line and column information.
*/
function cssTokenizerWithLineColumn(string) {
let lexer = DOMUtils.getCSSLexer(string);
let result = [];
let prevToken = undefined;
while (true) {
let token = lexer.nextToken();
let lineNumber = lexer.lineNumber;
let columnNumber = lexer.columnNumber;
if (prevToken) {
prevToken.loc.end = {
line: lineNumber,
column: columnNumber
};
}
if (!token) {
break;
}
if (token.tokenType === "comment") {
// We've already dealt with the previous token's location.
prevToken = undefined;
} else {
let startLoc = {
line: lineNumber,
column: columnNumber
};
token.loc = {start: startLoc};
result.push(token);
prevToken = token;
}
}
return result;
}
exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;

View File

@ -7,7 +7,6 @@
DevToolsModules(
'autocomplete.js',
'css-autocompleter.js',
'css-tokenizer.js',
'debugger.js',
'editor.js'
)

View File

@ -5,11 +5,9 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
DevToolsModules(
'computed-view.js',
'css-parsing-utils.js',
'rule-view.js',
'style-inspector-menu.js',
'style-inspector-overlays.js',

View File

@ -33,7 +33,7 @@ const {
SELECTOR_ATTRIBUTE,
SELECTOR_ELEMENT,
SELECTOR_PSEUDO_CLASS
} = require("devtools/client/styleinspector/css-parsing-utils");
} = require("devtools/client/shared/css-parsing-utils");
loader.lazyRequireGetter(this, "overlays",
"devtools/client/styleinspector/style-inspector-overlays");
loader.lazyRequireGetter(this, "EventEmitter",

View File

@ -1,4 +0,0 @@
{
// Extend from the common devtools xpcshell eslintrc config.
"extends": "../../../../.eslintrc.xpcshell"
}

View File

@ -1,12 +0,0 @@
[DEFAULT]
tags = devtools
head =
tail =
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_escapeCSSComment.js]
[test_parseDeclarations.js]
[test_parsePseudoClassesAndAttributes.js]
[test_parseSingleValue.js]
[test_rewriteDeclarations.js]

View File

@ -10,7 +10,7 @@ const {Cc, Ci, Cu} = require("chrome");
const {setTimeout, clearTimeout} =
Cu.import("resource://gre/modules/Timer.jsm", {});
const {parseDeclarations} =
require("devtools/client/styleinspector/css-parsing-utils");
require("devtools/client/shared/css-parsing-utils");
const promise = require("promise");
loader.lazyServiceGetter(this, "domUtils",

View File

@ -3,47 +3,7 @@
* 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/. */
/* Colors are taken from:
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
* Changes should be kept in sync with commandline.css and commandline.inc.css.
*/
:root {
--theme-body-background: #14171a;
--theme-sidebar-background: #181d20;
--theme-contrast-background: #b28025;
--theme-tab-toolbar-background: #252c33;
--theme-toolbar-background: #343c45;
--theme-selection-background: #1d4f73;
--theme-selection-background-semitransparent: rgba(29, 79, 115, .5);
--theme-selection-color: #f5f7fa;
--theme-splitter-color: black;
--theme-comment: #757873;
--theme-body-color: #8fa1b2;
--theme-body-color-alt: #b6babf;
--theme-content-color1: #a9bacb;
--theme-content-color2: #8fa1b2;
--theme-content-color3: #5f7387;
--theme-highlight-green: #70bf53;
--theme-highlight-blue: #46afe3;
--theme-highlight-bluegrey: #5e88b0;
--theme-highlight-purple: #6b7abb;
--theme-highlight-lightorange: #d99b28;
--theme-highlight-orange: #d96629;
--theme-highlight-red: #eb5368;
--theme-highlight-pink: #df80ff;
/* Colors used in Graphs, like performance tools. Mostly similar to some "highlight-*" colors. */
--theme-graphs-green: #70bf53;
--theme-graphs-blue: #46afe3;
--theme-graphs-bluegrey: #5e88b0;
--theme-graphs-purple: #df80ff;
--theme-graphs-yellow: #d99b28;
--theme-graphs-red: #eb5368;
--theme-graphs-grey: #757873;
}
@import url(variables.css);
.theme-body {
background: var(--theme-body-background);

View File

@ -3,47 +3,7 @@
* 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/. */
/* Colors are taken from:
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
* Changes should be kept in sync with commandline.css and commandline.inc.css.
*/
:root {
--theme-body-background: #fcfcfc;
--theme-sidebar-background: #f7f7f7;
--theme-contrast-background: #e6b064;
--theme-tab-toolbar-background: #ebeced;
--theme-toolbar-background: #f0f1f2;
--theme-selection-background: #4c9ed9;
--theme-selection-background-semitransparent: rgba(76, 158, 217, .23);
--theme-selection-color: #f5f7fa;
--theme-splitter-color: #aaaaaa;
--theme-comment: #757873;
--theme-body-color: #18191a;
--theme-body-color-alt: #585959;
--theme-content-color1: #292e33;
--theme-content-color2: #8fa1b2;
--theme-content-color3: #667380;
--theme-highlight-green: #2cbb0f;
--theme-highlight-blue: #0088cc;
--theme-highlight-bluegrey: #0072ab;
--theme-highlight-purple: #5b5fff;
--theme-highlight-lightorange: #d97e00;
--theme-highlight-orange: #f13c00;
--theme-highlight-red: #ed2655;
--theme-highlight-pink: #b82ee5;
/* Colors used in Graphs, like performance tools. Similar colors to Chrome's timeline. */
--theme-graphs-green: #85d175;
--theme-graphs-blue: #83b7f6;
--theme-graphs-bluegrey: #0072ab;
--theme-graphs-purple: #b693eb;
--theme-graphs-yellow: #efc052;
--theme-graphs-red: #e57180;
--theme-graphs-grey: #cccccc;
}
@import url(variables.css);
.theme-body {
background: var(--theme-body-background);

View File

@ -0,0 +1,91 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
/* Variable declarations for light and dark devtools themes.
* Colors are taken from:
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
* Changes should be kept in sync with commandline.css and commandline.inc.css.
*/
/* IMPORTANT NOTE:
* This file is parsed in js (see client/shared/theme.js)
* so the formatting should be consistent (i.e. no '}' inside a rule).
*/
:root.theme-light {
--theme-body-background: #fcfcfc;
--theme-sidebar-background: #f7f7f7;
--theme-contrast-background: #e6b064;
--theme-tab-toolbar-background: #ebeced;
--theme-toolbar-background: #f0f1f2;
--theme-selection-background: #4c9ed9;
--theme-selection-background-semitransparent: rgba(76, 158, 217, .23);
--theme-selection-color: #f5f7fa;
--theme-splitter-color: #aaaaaa;
--theme-comment: #757873;
--theme-body-color: #18191a;
--theme-body-color-alt: #585959;
--theme-content-color1: #292e33;
--theme-content-color2: #8fa1b2;
--theme-content-color3: #667380;
--theme-highlight-green: #2cbb0f;
--theme-highlight-blue: #0088cc;
--theme-highlight-bluegrey: #0072ab;
--theme-highlight-purple: #5b5fff;
--theme-highlight-lightorange: #d97e00;
--theme-highlight-orange: #f13c00;
--theme-highlight-red: #ed2655;
--theme-highlight-pink: #b82ee5;
/* Colors used in Graphs, like performance tools. Similar colors to Chrome's timeline. */
--theme-graphs-green: #85d175;
--theme-graphs-blue: #83b7f6;
--theme-graphs-bluegrey: #0072ab;
--theme-graphs-purple: #b693eb;
--theme-graphs-yellow: #efc052;
--theme-graphs-red: #e57180;
--theme-graphs-grey: #cccccc;
}
:root.theme-dark {
--theme-body-background: #14171a;
--theme-sidebar-background: #181d20;
--theme-contrast-background: #b28025;
--theme-tab-toolbar-background: #252c33;
--theme-toolbar-background: #343c45;
--theme-selection-background: #1d4f73;
--theme-selection-background-semitransparent: rgba(29, 79, 115, .5);
--theme-selection-color: #f5f7fa;
--theme-splitter-color: black;
--theme-comment: #757873;
--theme-body-color: #8fa1b2;
--theme-body-color-alt: #b6babf;
--theme-content-color1: #a9bacb;
--theme-content-color2: #8fa1b2;
--theme-content-color3: #5f7387;
--theme-highlight-green: #70bf53;
--theme-highlight-blue: #46afe3;
--theme-highlight-bluegrey: #5e88b0;
--theme-highlight-purple: #6b7abb;
--theme-highlight-lightorange: #d99b28;
--theme-highlight-orange: #d96629;
--theme-highlight-red: #eb5368;
--theme-highlight-pink: #df80ff;
/* Colors used in Graphs, like performance tools. Mostly similar to some "highlight-*" colors. */
--theme-graphs-green: #70bf53;
--theme-graphs-blue: #46afe3;
--theme-graphs-bluegrey: #5e88b0;
--theme-graphs-purple: #df80ff;
--theme-graphs-yellow: #d99b28;
--theme-graphs-red: #eb5368;
--theme-graphs-grey: #757873;
}

View File

@ -2662,7 +2662,23 @@ var WalkerActor = protocol.ActorClass({
return null;
}
parent.rawNode.insertBefore(node.rawNode, sibling ? sibling.rawNode : null);
let rawNode = node.rawNode;
let rawParent = parent.rawNode;
let rawSibling = sibling ? sibling.rawNode : null;
// Don't bother inserting a node if the document position isn't going
// to change. This prevents needless iframes reloading and mutations.
if (rawNode.parentNode === rawParent) {
let currentNextSibling = this.nextSibling(node);
currentNextSibling = currentNextSibling ? currentNextSibling.rawNode :
null;
if (rawNode === rawSibling || currentNextSibling === rawSibling) {
return;
}
}
rawParent.insertBefore(rawNode, rawSibling);
}, {
request: {
node: Arg(0, "domnode"),

View File

@ -26,7 +26,7 @@ loader.lazyGetter(this, "DOMUtils", () => {
});
loader.lazyGetter(this, "RuleRewriter", () => {
return require("devtools/client/styleinspector/css-parsing-utils").RuleRewriter;
return require("devtools/client/shared/css-parsing-utils").RuleRewriter;
});
// The PageStyle actor flattens the DOM CSS objects a little bit, merging

View File

@ -39,36 +39,63 @@ addTest(function setup() {
});
});
addTest(function testRearrange() {
let longlist = null;
let nodeA = null;
let nextNode = null;
addAsyncTest(function* testRearrange() {
let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
let children = yield gWalker.children(longlist);
let nodeA = children.nodes[0];
is(nodeA.id, "a", "Got the expected node.");
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(listFront => {
longlist = listFront;
}).then(() => {
return gWalker.children(longlist);
}).then(response => {
nodeA = response.nodes[0];
is(nodeA.id, "a", "Got the expected node.");
// Move nodeA to the end of the list.
return gWalker.insertBefore(nodeA, longlist, null);
}).then(() => {
ok(!gInspectee.querySelector("#a").nextSibling, "a should now be at the end of the list.");
return gWalker.children(longlist);
}).then(response => {
is(nodeA, response.nodes[response.nodes.length - 1], "a should now be the last returned child.");
// Now move it to the middle of the list.
nextNode = response.nodes[13];
return gWalker.insertBefore(nodeA, longlist, nextNode);
}).then(response => {
let sibling = new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
return gWalker.children(longlist);
}).then(response => {
is(nodeA, response.nodes[13], "a should be where we expect it.");
is(nextNode, response.nodes[14], "next node should be where we expect it.");
}).then(runNextTest));
// Move nodeA to the end of the list.
yield gWalker.insertBefore(nodeA, longlist, null);
ok(!gInspectee.querySelector("#a").nextSibling, "a should now be at the end of the list.");
children = yield gWalker.children(longlist);
is(nodeA, children.nodes[children.nodes.length - 1], "a should now be the last returned child.");
// Now move it to the middle of the list.
let nextNode = children.nodes[13];
yield gWalker.insertBefore(nodeA, longlist, nextNode);
let sibling =
new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
children = yield gWalker.children(longlist);
is(nodeA, children.nodes[13], "a should be where we expect it.");
is(nextNode, children.nodes[14], "next node should be where we expect it.");
runNextTest();
});
addAsyncTest(function* testInsertInvalidInput() {
let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
let children = yield gWalker.children(longlist);
let nodeA = children.nodes[0];
let nextSibling = children.nodes[1];
// Now move it to the original location and make sure no mutation happens.
let hasMutated = false;
let observer = new gInspectee.defaultView.MutationObserver(() => {
hasMutated = true;
});
observer.observe(longlist.rawNode(), {
childList: true,
});
yield gWalker.insertBefore(nodeA, longlist, nodeA);
ok(!hasMutated, "hasn't mutated");
hasMutated = false;
yield gWalker.insertBefore(nodeA, longlist, nextSibling);
ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
hasMutated = false;
yield gWalker.insertBefore(nodeA, longlist);
ok(hasMutated, "has mutated after inserting with null sibling");
hasMutated = false;
yield gWalker.insertBefore(nodeA, longlist);
ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
observer.disconnect();
runNextTest();
});
addTest(function cleanup() {
@ -77,7 +104,6 @@ addTest(function cleanup() {
runNextTest();
});
</script>
</head>
<body>

View File

@ -5,7 +5,9 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MobileMessageDatabaseService.h"
#include "AndroidBridge.h"
#include "SmsManager.h"
namespace mozilla {
namespace dom {
@ -13,6 +15,11 @@ namespace mobilemessage {
NS_IMPL_ISUPPORTS(MobileMessageDatabaseService, nsIMobileMessageDatabaseService)
MobileMessageDatabaseService::MobileMessageDatabaseService()
{
SmsManager::Init();
}
NS_IMETHODIMP
MobileMessageDatabaseService::GetMessageMoz(int32_t aMessageId,
nsIMobileMessageCallback* aRequest)
@ -60,9 +67,29 @@ MobileMessageDatabaseService::CreateMessageCursor(bool aHasStartDate,
uint64_t aThreadId,
bool aReverse,
nsIMobileMessageCursorCallback* aCallback,
nsICursorContinueCallback** aResult)
nsICursorContinueCallback** aCursor)
{
return NS_ERROR_NOT_IMPLEMENTED;
if (!AndroidBridge::Bridge()) {
*aCursor = nullptr;
return NS_OK;
}
nsCOMPtr<nsICursorContinueCallback> cursor =
AndroidBridge::Bridge()->CreateMessageCursor(aHasStartDate,
aStartDate,
aHasEndDate,
aEndDate,
aNumbers,
aNumbersCount,
aDelivery,
aHasRead,
aRead,
aHasThreadId,
aThreadId,
aReverse,
aCallback);
cursor.forget(aCursor);
return NS_OK;
}
NS_IMETHODIMP
@ -76,11 +103,18 @@ MobileMessageDatabaseService::MarkMessageRead(int32_t aMessageId,
}
NS_IMETHODIMP
MobileMessageDatabaseService::CreateThreadCursor(nsIMobileMessageCursorCallback* aCallback,
nsICursorContinueCallback** aResult)
MobileMessageDatabaseService::CreateThreadCursor(nsIMobileMessageCursorCallback* aRequest,
nsICursorContinueCallback** aCursor)
{
NS_NOTYETIMPLEMENTED("Implement me!");
return NS_ERROR_NOT_IMPLEMENTED;
if (!AndroidBridge::Bridge()) {
*aCursor = nullptr;
return NS_OK;
}
nsCOMPtr<nsICursorContinueCallback> cursor =
AndroidBridge::Bridge()->CreateThreadCursor(aRequest);
cursor.forget(aCursor);
return NS_OK;
}
} // namespace mobilemessage

View File

@ -20,6 +20,8 @@ private:
~MobileMessageDatabaseService() {}
public:
MobileMessageDatabaseService();
NS_DECL_ISUPPORTS
NS_DECL_NSIMOBILEMESSAGEDATABASESERVICE
};

View File

@ -0,0 +1,397 @@
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SmsManager.h"
#include "mozilla/dom/mobilemessage/Constants.h"
#include "mozilla/dom/mobilemessage/PSms.h"
#include "mozilla/dom/mobilemessage/SmsParent.h"
#include "mozilla/dom/mobilemessage/SmsTypes.h"
#include "mozilla/dom/mobilemessage/Types.h"
#include "mozilla/dom/MobileMessageThread.h"
#include "mozilla/dom/SmsMessage.h"
#include "mozilla/Services.h"
#include "nsIMobileMessageDatabaseService.h"
#include "nsIObserverService.h"
using namespace mozilla::dom;
using namespace mozilla::dom::mobilemessage;
namespace mozilla {
/*static*/
void
SmsManager::NotifySmsReceived(jni::String::Param aSender,
jni::String::Param aBody,
int32_t aMessageClass,
int64_t aTimestamp)
{
// TODO Need to correct the message `threadId` parameter value. Bug 859098
SmsMessageData message;
message.id() = 0;
message.threadId() = 0;
message.iccId() = EmptyString();
message.delivery() = eDeliveryState_Received;
message.deliveryStatus() = eDeliveryStatus_Success;
message.sender() = aSender ? nsString(aSender) : EmptyString();
message.receiver() = EmptyString();
message.body() = aBody ? nsString(aBody) : EmptyString();
message.messageClass() = static_cast<MessageClass>(aMessageClass);
message.timestamp() = aTimestamp;
message.sentTimestamp() = aTimestamp;
message.deliveryTimestamp() = aTimestamp;
message.read() = false;
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=] () {
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
return;
}
nsCOMPtr<nsIDOMMozSmsMessage> domMessage = new SmsMessage(message);
obs->NotifyObservers(domMessage, kSmsReceivedObserverTopic, nullptr);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifySmsSent(int32_t aId,
jni::String::Param aReceiver,
jni::String::Param aBody,
int64_t aTimestamp,
int32_t aRequestId)
{
// TODO Need to add the message `messageClass` parameter value. Bug 804476
// TODO Need to correct the message `threadId` parameter value. Bug 859098
SmsMessageData message;
message.id() = aId;
message.threadId() = 0;
message.iccId() = EmptyString();
message.delivery() = eDeliveryState_Sent;
message.deliveryStatus() = eDeliveryStatus_Pending;
message.sender() = EmptyString();
message.receiver() = aReceiver ? nsString(aReceiver) : EmptyString();
message.body() = aBody ? nsString(aBody) : EmptyString();
message.messageClass() = eMessageClass_Normal;
message.timestamp() = aTimestamp;
message.sentTimestamp() = aTimestamp;
message.deliveryTimestamp() = aTimestamp;
message.read() = true;
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
/*
* First, we are going to notify all SmsManager that a message has
* been sent. Then, we will notify the SmsRequest object about it.
*/
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
return;
}
nsCOMPtr<nsIDOMMozSmsMessage> domMessage = new SmsMessage(message);
obs->NotifyObservers(domMessage, kSmsSentObserverTopic, nullptr);
nsCOMPtr<nsIMobileMessageCallback> request =
AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
if (!request) {
return;
}
request->NotifyMessageSent(domMessage);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifySmsDelivery(int32_t aId,
int32_t aDeliveryStatus,
jni::String::Param aReceiver,
jni::String::Param aBody,
int64_t aTimestamp)
{
// TODO Need to add the message `messageClass` parameter value. Bug 804476
// TODO Need to correct the message `threadId` parameter value. Bug 859098
SmsMessageData message;
message.id() = aId;
message.threadId() = 0;
message.iccId() = EmptyString();
message.delivery() = eDeliveryState_Sent;
message.deliveryStatus() = static_cast<DeliveryStatus>(aDeliveryStatus);
message.sender() = EmptyString();
message.receiver() = aReceiver ? nsString(aReceiver) : EmptyString();
message.body() = aBody ? nsString(aBody) : EmptyString();
message.messageClass() = eMessageClass_Normal;
message.timestamp() = aTimestamp;
message.sentTimestamp() = aTimestamp;
message.deliveryTimestamp() = aTimestamp;
message.read() = true;
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
return;
}
nsCOMPtr<nsIDOMMozSmsMessage> domMessage = new SmsMessage(message);
const char* topic = (message.deliveryStatus() == eDeliveryStatus_Success)
? kSmsDeliverySuccessObserverTopic
: kSmsDeliveryErrorObserverTopic;
obs->NotifyObservers(domMessage, topic, nullptr);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifySmsSendFailed(int32_t aError, int32_t aRequestId)
{
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCallback> request =
AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
if(!request) {
return;
}
request->NotifySendMessageFailed(aError, nullptr);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifyGetSms(int32_t aId,
int32_t aDeliveryStatus,
jni::String::Param aReceiver,
jni::String::Param aSender,
jni::String::Param aBody,
int64_t aTimestamp,
bool aRead,
int32_t aRequestId)
{
nsString receiver(aReceiver);
DeliveryState state = receiver.IsEmpty() ? eDeliveryState_Received
: eDeliveryState_Sent;
// TODO Need to add the message `messageClass` parameter value. Bug 804476
// TODO Need to correct the message `threadId` parameter value. Bug 859098
SmsMessageData message;
message.id() = aId;
message.threadId() = 0;
message.iccId() = EmptyString();
message.delivery() = state;
message.deliveryStatus() = static_cast<DeliveryStatus>(aDeliveryStatus);
message.sender() = aSender ? nsString(aSender) : EmptyString();
message.receiver() = receiver;
message.body() = aBody ? nsString(aBody) : EmptyString();
message.messageClass() = eMessageClass_Normal;
message.timestamp() = aTimestamp;
message.sentTimestamp() = aTimestamp;
message.deliveryTimestamp() = aTimestamp;
message.read() = aRead;
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCallback> request =
AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
if (!request) {
return;
}
nsCOMPtr<nsIDOMMozSmsMessage> domMessage = new SmsMessage(message);
request->NotifyMessageGot(domMessage);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifyGetSmsFailed(int32_t aError, int32_t aRequestId)
{
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCallback> request =
AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
if (!request) {
return;
}
request->NotifyGetMessageFailed(aError);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifySmsDeleted(bool aDeleted, int32_t aRequestId)
{
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCallback> request =
AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
if (!request) {
return;
}
// For android, we support only single SMS deletion.
bool deleted = aDeleted;
request->NotifyMessageDeleted(&deleted, 1);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifySmsDeleteFailed(int32_t aError, int32_t aRequestId)
{
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCallback> request =
AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
if (!request) {
return;
}
request->NotifyDeleteMessageFailed(aError);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifyCursorError(int32_t aError, int32_t aRequestId)
{
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCursorCallback> request =
AndroidBridge::Bridge()->DequeueSmsCursorRequest(aRequestId);
if (!request) {
return;
}
request->NotifyCursorError(aError);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifyThreadCursorResult(int64_t aId,
jni::String::Param aLastMessageSubject,
jni::String::Param aBody,
int64_t aUnreadCount,
jni::ObjectArray::Param aParticipants,
int64_t aTimestamp,
jni::String::Param aLastMessageType,
int32_t aRequestId)
{
ThreadData thread;
thread.id() = aId;
thread.lastMessageSubject() = aLastMessageSubject ?
nsString(aLastMessageSubject) :
EmptyString();
thread.body() = aBody ? nsString(aBody) : EmptyString();
thread.unreadCount() = aUnreadCount;
thread.timestamp() = aTimestamp;
thread.lastMessageType() = eMessageType_SMS;
JNIEnv* const env = jni::GetEnvForThread();
jobjectArray participants = aParticipants.Get();
jsize length = env->GetArrayLength(participants);
for (jsize i = 0; i < length; ++i) {
jstring participant =
static_cast<jstring>(env->GetObjectArrayElement(participants, i));
if (participant) {
thread.participants().AppendElement(nsJNIString(participant, env));
}
}
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCursorCallback> request =
AndroidBridge::Bridge()->GetSmsCursorRequest(aRequestId);
if (!request) {
return;
}
nsCOMArray<nsIDOMMozMobileMessageThread> arr;
arr.AppendElement(new MobileMessageThread(thread));
nsIDOMMozMobileMessageThread** elements;
int32_t size;
size = arr.Forget(&elements);
request->NotifyCursorResult(reinterpret_cast<nsISupports**>(elements),
size);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifyMessageCursorResult(int32_t aMessageId,
int32_t aDeliveryStatus,
jni::String::Param aReceiver,
jni::String::Param aSender,
jni::String::Param aBody,
int64_t aTimestamp,
int64_t aThreadId,
bool aRead,
int32_t aRequestId)
{
nsString receiver = nsString(aReceiver);
DeliveryState state = receiver.IsEmpty() ? eDeliveryState_Received
: eDeliveryState_Sent;
// TODO Need to add the message `messageClass` parameter value. Bug 804476
SmsMessageData message;
message.id() = aMessageId;
message.threadId() = aThreadId;
message.iccId() = EmptyString();
message.delivery() = state;
message.deliveryStatus() = static_cast<DeliveryStatus>(aDeliveryStatus);
message.sender() = aSender ? nsString(aSender) : EmptyString();
message.receiver() = receiver;
message.body() = aBody ? nsString(aBody) : EmptyString();
message.messageClass() = eMessageClass_Normal;
message.timestamp() = aTimestamp;
message.sentTimestamp() = aTimestamp;
message.deliveryTimestamp() = aTimestamp;
message.read() = aRead;
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCursorCallback> request =
AndroidBridge::Bridge()->GetSmsCursorRequest(aRequestId);
if (!request) {
return;
}
nsCOMArray<nsIDOMMozSmsMessage> arr;
arr.AppendElement(new SmsMessage(message));
nsIDOMMozSmsMessage** elements;
int32_t size;
size = arr.Forget(&elements);
request->NotifyCursorResult(reinterpret_cast<nsISupports**>(elements),
size);
});
NS_DispatchToMainThread(runnable);
}
/*static*/
void
SmsManager::NotifyCursorDone(int32_t aRequestId)
{
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
nsCOMPtr<nsIMobileMessageCursorCallback> request =
AndroidBridge::Bridge()->DequeueSmsCursorRequest(aRequestId);
if (!request) {
return;
}
request->NotifyCursorDone();
});
NS_DispatchToMainThread(runnable);
}
} // namespace

View File

@ -0,0 +1,73 @@
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef SmsManager_h__
#define SmsManager_h__
#include "GeneratedJNINatives.h"
namespace mozilla {
class SmsManager : public widget::GeckoSmsManager::Natives<SmsManager>
{
private:
SmsManager();
public:
static void NotifySmsReceived(jni::String::Param aSender,
jni::String::Param aBody,
int32_t aMessageClass,
int64_t aTimestamp);
static void NotifySmsSent(int32_t aId,
jni::String::Param aReceiver,
jni::String::Param aBody,
int64_t aTimestamp,
int32_t aRequestId);
static void NotifySmsDelivery(int32_t aId,
int32_t aDeliveryStatus,
jni::String::Param aReceiver,
jni::String::Param aBody,
int64_t aTimestamp);
static void NotifySmsSendFailed(int32_t aError,
int32_t aRequestId);
static void NotifyGetSms(int32_t aId,
int32_t aDeliveryStatus,
jni::String::Param aReceiver,
jni::String::Param aSender,
jni::String::Param aBody,
int64_t aTimestamp,
bool aRead,
int32_t aRequestId);
static void NotifyGetSmsFailed(int32_t aError,
int32_t aRequestId);
static void NotifySmsDeleted(bool aDeleted,
int32_t aRequestId);
static void NotifySmsDeleteFailed(int32_t aError,
int32_t aRequestId);
static void NotifyCursorError(int32_t aError,
int32_t aRequestId);
static void NotifyThreadCursorResult(int64_t aId,
jni::String::Param aLastMessageSubject,
jni::String::Param aBody,
int64_t aUnreadCount,
jni::ObjectArray::Param aParticipants,
int64_t aTimestamp,
jni::String::Param aLastMessageType,
int32_t aRequestId);
static void NotifyMessageCursorResult(int32_t aMessageId,
int32_t aDeliveryStatus,
jni::String::Param aReceiver,
jni::String::Param aSender,
jni::String::Param aBody,
int64_t aTimestamp,
int64_t aThreadId,
bool aRead,
int32_t aRequestId);
static void NotifyCursorDone(int32_t aRequestId);
};
} // namespace
#endif // SmsManager_h__

View File

@ -19,6 +19,7 @@ EXPORTS.mozilla.dom.mobilemessage += [
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
SOURCES += [
'android/MobileMessageDatabaseService.cpp',
'android/SmsManager.cpp',
'android/SmsService.cpp',
]
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
@ -46,6 +47,7 @@ EXPORTS.mozilla.dom += [
'DOMMobileMessageError.h',
'MmsMessage.h',
'MobileMessageManager.h',
'MobileMessageThread.h',
'SmsMessage.h',
]
@ -75,6 +77,7 @@ IPDL_SOURCES += [
LOCAL_INCLUDES += [
'/dom/base',
'/widget/android',
]
include('/ipc/chromium/chromium-config.mozbuild')

View File

@ -28,6 +28,14 @@
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!-- WebSMS -->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-feature android:name="android.hardware.telephony"/>
<!-- Tab Queue -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

View File

@ -122,3 +122,5 @@ fi
MOZ_JSDOWNLOADS=1
MOZ_TIME_MANAGER=1
MOZ_WEBSMS_BACKEND=1

View File

@ -47,6 +47,8 @@
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-feature android:name="android.hardware.telephony"/>
#endif
<uses-feature android:name="android.hardware.location" android:required="false"/>

View File

@ -33,6 +33,7 @@ import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.FileUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.GeckoRequest;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
@ -2344,31 +2345,57 @@ public abstract class GeckoApp
return;
}
Tabs tabs = Tabs.getInstance();
Tab tab = tabs.getSelectedTab();
final Tabs tabs = Tabs.getInstance();
final Tab tab = tabs.getSelectedTab();
if (tab == null) {
moveTaskToBack(true);
return;
}
if (tab.doBack())
return;
// Give Gecko a chance to handle the back press first, then fallback to the Java UI.
GeckoAppShell.sendRequestToGecko(new GeckoRequest("Browser:OnBackPressed", null) {
@Override
public void onResponse(NativeJSObject nativeJSObject) {
if (!nativeJSObject.getBoolean("handled")) {
// Default behavior is Gecko didn't prevent.
onDefault();
}
}
if (tab.isExternal()) {
moveTaskToBack(true);
tabs.closeTab(tab);
return;
}
@Override
public void onError(NativeJSObject error) {
// Default behavior is Gecko didn't prevent, via failure.
onDefault();
}
int parentId = tab.getParentId();
Tab parent = tabs.getTab(parentId);
if (parent != null) {
// The back button should always return to the parent (not a sibling).
tabs.closeTab(tab, parent);
return;
}
// Return from Gecko thread, then back-press through the Java UI.
private void onDefault() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
if (tab.doBack()) {
return;
}
moveTaskToBack(true);
if (tab.isExternal()) {
moveTaskToBack(true);
tabs.closeTab(tab);
return;
}
final int parentId = tab.getParentId();
final Tab parent = tabs.getTab(parentId);
if (parent != null) {
// The back button should always return to the parent (not a sibling).
tabs.closeTab(tab, parent);
return;
}
moveTaskToBack(true);
}
});
}
});
}
@Override

View File

@ -2381,31 +2381,42 @@ public class GeckoAppShell
SmsManager.getInstance().deleteMessage(aMessageId, aRequestId);
}
@WrapForJNI(stubName = "CreateMessageListWrapper")
public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
@WrapForJNI(stubName = "CreateMessageCursorWrapper")
public static void createMessageCursor(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId) {
if (!SmsManager.isEnabled()) {
return;
}
SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId);
SmsManager.getInstance().createMessageCursor(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aHasThreadId, aThreadId, aReverse, aRequestId);
}
@WrapForJNI(stubName = "GetNextMessageInListWrapper")
public static void getNextMessageInList(int aListId, int aRequestId) {
@WrapForJNI(stubName = "GetNextMessageWrapper")
public static void getNextMessage(int aRequestId) {
if (!SmsManager.isEnabled()) {
return;
}
SmsManager.getInstance().getNextMessageInList(aListId, aRequestId);
SmsManager.getInstance().getNextMessage(aRequestId);
}
@WrapForJNI
public static void clearMessageList(int aListId) {
@WrapForJNI(stubName = "CreateThreadCursorWrapper")
public static void createThreadCursor(int aRequestId) {
Log.i("GeckoAppShell", "CreateThreadCursorWrapper!");
if (!SmsManager.isEnabled()) {
return;
}
SmsManager.getInstance().clearMessageList(aListId);
SmsManager.getInstance().createThreadCursor(aRequestId);
}
@WrapForJNI(stubName = "GetNextThreadWrapper")
public static void getNextThread(int aRequestId) {
if (!SmsManager.isEnabled()) {
return;
}
SmsManager.getInstance().getNextThread(aRequestId);
}
/* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */

View File

@ -5,6 +5,8 @@
package org.mozilla.gecko;
import org.mozilla.gecko.annotation.WrapForJNI;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@ -18,7 +20,7 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.HandlerThread;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.util.Log;
@ -27,6 +29,12 @@ import static android.telephony.SmsMessage.MessageClass;
import static org.mozilla.gecko.SmsManager.ISmsManager;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
@ -183,7 +191,7 @@ class Postman
}
}
class SmsIOThread extends Thread {
class SmsIOThread extends HandlerThread {
private final static SmsIOThread sInstance = new SmsIOThread();
private Handler mHandler;
@ -192,17 +200,18 @@ class SmsIOThread extends Thread {
return sInstance;
}
public boolean execute(Runnable r) {
return mHandler.post(r);
SmsIOThread() {
super("SmsIOThread");
}
@Override
public void run() {
Looper.prepare();
public void start() {
super.start();
mHandler = new Handler(getLooper());
}
mHandler = new Handler();
Looper.loop();
public boolean execute(Runnable r) {
return mHandler.post(r);
}
}
@ -214,57 +223,40 @@ class MessagesListManager
return sInstance;
}
private final ArrayList<Cursor> mCursors = new ArrayList<>();
private final HashMap<Integer, Cursor> mCursors = new HashMap<>();
public int add(Cursor aCursor) {
int size = mCursors.size();
for (int i=0; i<size; ++i) {
if (mCursors.get(i) == null) {
mCursors.set(i, aCursor);
return i;
}
public void add(int id, Cursor aCursor) {
if (mCursors.containsKey(id)) {
Log.e("GeckoSmsManager", "Trying to overwrite cursor!");
return;
}
mCursors.add(aCursor);
return size;
mCursors.put(id, aCursor);
}
public Cursor get(int aId) {
if (aId < 0 || mCursors.size() <= aId) {
Log.e("GeckoSmsManager", "Trying to get an unknown list!");
if (!mCursors.containsKey(aId)) {
Log.e("GeckoSmsManager", "Cursor doesn't exist!");
return null;
}
Cursor cursor = mCursors.get(aId);
if (cursor == null) {
Log.e("GeckoSmsManager", "Trying to get an empty list!");
}
return cursor;
return mCursors.get(aId);
}
public void remove(int aId) {
if (aId < 0 || mCursors.size() <= aId) {
Log.e("GeckoSmsManager", "Trying to destroy an unknown list!");
if (!mCursors.containsKey(aId)) {
Log.e("GeckoSmsManager", "Cursor doesn't exist!");
return;
}
Cursor cursor = mCursors.set(aId, null);
if (cursor == null) {
Log.e("GeckoSmsManager", "Trying to destroy an empty list!");
return;
}
cursor.close();
mCursors.remove(aId);
}
public void clear() {
for (int i=0; i<mCursors.size(); ++i) {
Cursor c = mCursors.get(i);
if (c != null) {
c.close();
}
Set<Map.Entry<Integer, Cursor>> entries = mCursors.entrySet();
Iterator<Map.Entry<Integer, Cursor>> it = entries.iterator();
while (it.hasNext()) {
it.next().getValue().close();
}
mCursors.clear();
@ -282,7 +274,7 @@ public class GeckoSmsManager
/*
* Make sure that the following error codes are in sync with |ErrorType| in:
* dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl
* The error code are owned by the DOM.
* The error codes are owned by the DOM.
*/
public final static int kNoError = 0;
public final static int kNoSignalError = 1;
@ -305,8 +297,9 @@ public class GeckoSmsManager
private final static int kMaxMessageSize = 160;
private final static Uri kSmsContentUri = Uri.parse("content://sms");
private final static Uri kSmsSentContentUri = Uri.parse("content://sms/sent");
private final static Uri kSmsContentUri = Uri.parse("content://sms");
private final static Uri kSmsSentContentUri = Uri.parse("content://sms/sent");
private final static Uri kSmsThreadsContentUri = Uri.parse("content://sms/conversations");
private final static int kSmsTypeInbox = 1;
private final static int kSmsTypeSentbox = 2;
@ -351,7 +344,9 @@ public class GeckoSmsManager
private final static int kMessageClassClass2 = 3;
private final static int kMessageClassClass3 = 4;
private final static String[] kRequiredMessageRows = { "_id", "address", "body", "date", "type", "status" };
private final static String[] kRequiredMessageRows = { "_id", "address", "body", "date", "type", "status", "read", "thread_id" };
private final static String[] kRequiredMessageRowsForThread = { "_id", "address", "body", "read", "subject", "date" };
private final static String[] kThreadProjection = { "thread_id" };
// Used to generate monotonically increasing GUIDs.
private static final AtomicInteger pendingIntentGuid = new AtomicInteger(Integer.MIN_VALUE);
@ -660,11 +655,14 @@ public class GeckoSmsManager
throw new InvalidTypeException();
}
boolean read = cursor.getInt(cursor.getColumnIndex("read")) != 0;
notifyGetSms(cursor.getInt(cursor.getColumnIndex("_id")),
deliveryStatus,
receiver, sender,
cursor.getString(cursor.getColumnIndex("body")),
cursor.getLong(cursor.getColumnIndex("date")),
read,
mRequestId);
} catch (NotFoundException e) {
Log.i("GeckoSmsManager", "Message id " + mMessageId + " not found");
@ -736,23 +734,55 @@ public class GeckoSmsManager
}
}
private void getMessageFromCursorAndNotify(Cursor aCursor, int aRequestId) throws Exception {
int type = aCursor.getInt(aCursor.getColumnIndex("type"));
int deliveryStatus = kDeliveryStateUnknown;
String sender = "";
String receiver = "";
if (type == kSmsTypeInbox) {
deliveryStatus = kDeliveryStatusSuccess;
sender = aCursor.getString(aCursor.getColumnIndex("address"));
} else if (type == kSmsTypeSentbox) {
deliveryStatus = getGeckoDeliveryStatus(aCursor.getInt(aCursor.getColumnIndex("status")));
receiver = aCursor.getString(aCursor.getColumnIndex("address"));
} else {
throw new Exception("Shouldn't ever get a message here that's not from inbox or sentbox");
}
boolean read = aCursor.getInt(aCursor.getColumnIndex("read")) != 0;
notifyMessageCursorResult(aCursor.getInt(aCursor.getColumnIndex("_id")),
deliveryStatus,
receiver, sender,
aCursor.getString(aCursor.getColumnIndex("body")),
aCursor.getLong(aCursor.getColumnIndex("date")),
aCursor.getLong(aCursor.getColumnIndex("thread_id")),
read,
aRequestId);
}
@Override
public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
class CreateMessageListRunnable implements Runnable {
public void createMessageCursor(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId) {
class CreateMessageCursorRunnable implements Runnable {
private final long mStartDate;
private final long mEndDate;
private final String[] mNumbers;
private final int mNumbersCount;
private final String mDelivery;
private final boolean mHasThreadId;
private final long mThreadId;
private final boolean mReverse;
private final int mRequestId;
CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
CreateMessageCursorRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId) {
mStartDate = aStartDate;
mEndDate = aEndDate;
mNumbers = aNumbers;
mNumbersCount = aNumbersCount;
mDelivery = aDelivery;
mHasThreadId = aHasThreadId;
mThreadId = aThreadId;
mReverse = aReverse;
mRequestId = aRequestId;
}
@ -763,169 +793,236 @@ public class GeckoSmsManager
boolean closeCursor = true;
try {
// TODO: should use the |selectionArgs| argument in |ContentResolver.query()|.
ArrayList<String> restrictions = new ArrayList<String>();
StringBuilder restrictions = new StringBuilder();
Formatter formatter = new Formatter(restrictions);
if (mStartDate >= 0) {
restrictions.add("date >= " + mStartDate);
formatter.format("date >= '%d' AND ", mStartDate);
}
if (mEndDate >= 0) {
restrictions.add("date <= " + mEndDate);
formatter.format("date <= '%d' AND ", mEndDate);
}
if (mNumbersCount > 0) {
final StringBuilder numberRestriction = new StringBuilder("address IN ('");
numberRestriction.append(mNumbers[0]).append("'");
formatter.format("address IN ('%s'", mNumbers[0]);
for (int i=1; i<mNumbersCount; ++i) {
numberRestriction.append(", '").append(mNumbers[i]).append("'");
for (int i = 1; i < mNumbersCount; ++i) {
formatter.format(", '%s'", mNumbers[i]);
}
numberRestriction.append(')');
restrictions.add(numberRestriction.toString());
formatter.format(") AND ");
}
if (mDelivery == null) {
restrictions.add("type IN ('" + kSmsTypeSentbox + "', '" + kSmsTypeInbox + "')");
if (mDelivery == null || mDelivery.isEmpty()) {
formatter.format("type IN ('%d', '%d') AND ", kSmsTypeSentbox, kSmsTypeInbox);
} else if (mDelivery.equals("sent")) {
restrictions.add("type = " + kSmsTypeSentbox);
formatter.format("type = '%d' AND ", kSmsTypeSentbox);
} else if (mDelivery.equals("received")) {
restrictions.add("type = " + kSmsTypeInbox);
formatter.format("type = '%d' AND ", kSmsTypeInbox);
} else {
throw new UnexpectedDeliveryStateException();
throw new Exception("Unexpected delivery state: " + mDelivery);
}
final StringBuilder restrictionText = new StringBuilder();
if (!restrictions.isEmpty()) {
restrictionText.append(restrictions.get(0));
if (mHasThreadId) {
formatter.format("thread_id = '%d' AND ", mThreadId);
}
for (int i=1; i<restrictions.size(); ++i) {
restrictionText.append(" AND ").append(restrictions.get(i));
}
// Final 'AND 1' is a no-op so we don't have to special case the last
// condition.
formatter.format("1");
ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
cursor = cr.query(kSmsContentUri, kRequiredMessageRows, restrictionText.toString(), null,
cursor = cr.query(kSmsContentUri,
kRequiredMessageRows,
restrictions.toString(),
null,
mReverse ? "date DESC" : "date ASC");
if (cursor.getCount() == 0) {
notifyNoMessageInList(mRequestId);
notifyCursorDone(mRequestId);
return;
}
MessagesListManager.getInstance().add(mRequestId, cursor);
cursor.moveToFirst();
int type = cursor.getInt(cursor.getColumnIndex("type"));
int deliveryStatus;
String sender = "";
String receiver = "";
if (type == kSmsTypeInbox) {
deliveryStatus = kDeliveryStatusSuccess;
sender = cursor.getString(cursor.getColumnIndex("address"));
} else if (type == kSmsTypeSentbox) {
deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
receiver = cursor.getString(cursor.getColumnIndex("address"));
} else {
throw new UnexpectedDeliveryStateException();
}
int listId = MessagesListManager.getInstance().add(cursor);
getMessageFromCursorAndNotify(cursor, mRequestId);
closeCursor = false;
notifyListCreated(listId,
cursor.getInt(cursor.getColumnIndex("_id")),
deliveryStatus,
receiver, sender,
cursor.getString(cursor.getColumnIndex("body")),
cursor.getLong(cursor.getColumnIndex("date")),
mRequestId);
} catch (UnexpectedDeliveryStateException e) {
Log.e("GeckoSmsManager", "Unexcepted delivery state type", e);
notifyReadingMessageListFailed(kUnknownError, mRequestId);
} catch (Exception e) {
Log.e("GeckoSmsManager", "Error while trying to create a message list cursor", e);
notifyReadingMessageListFailed(kUnknownError, mRequestId);
notifyCursorError(kUnknownError, mRequestId);
} finally {
// Close the cursor if MessagesListManager isn't taking care of it.
// We could also just check if it is in the MessagesListManager list but
// that would be less efficient.
if (cursor != null && closeCursor) {
if (closeCursor && cursor != null) {
cursor.close();
}
}
}
}
if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId))) {
Log.e("GeckoSmsManager", "Failed to add CreateMessageListRunnable to the SmsIOThread");
notifyReadingMessageListFailed(kUnknownError, aRequestId);
if (!SmsIOThread.getInstance().execute(new CreateMessageCursorRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aHasThreadId, aThreadId, aReverse, aRequestId))) {
Log.e("GeckoSmsManager", "Failed to add CreateMessageCursorRunnable to the SmsIOThread");
notifyCursorError(kUnknownError, aRequestId);
}
}
@Override
public void getNextMessageInList(int aListId, int aRequestId) {
class GetNextMessageInListRunnable implements Runnable {
private final int mListId;
public void getNextMessage(int aRequestId) {
class GetNextMessageRunnable implements Runnable {
private final int mRequestId;
GetNextMessageInListRunnable(int aListId, int aRequestId) {
mListId = aListId;
GetNextMessageRunnable(int aRequestId) {
mRequestId = aRequestId;
}
@Override
public void run() {
Cursor cursor = null;
boolean closeCursor = true;
try {
cursor = MessagesListManager.getInstance().get(mRequestId);
if (!cursor.moveToNext()) {
MessagesListManager.getInstance().remove(mRequestId);
notifyCursorDone(mRequestId);
return;
}
getMessageFromCursorAndNotify(cursor, mRequestId);
closeCursor = false;
} catch (Exception e) {
Log.e("GeckoSmsManager", "Error while trying to get the next message of a list", e);
notifyCursorError(kUnknownError, mRequestId);
} finally {
if (closeCursor) {
cursor.close();
}
}
}
}
if (!SmsIOThread.getInstance().execute(new GetNextMessageRunnable(aRequestId))) {
Log.e("GeckoSmsManager", "Failed to add GetNextMessageRunnable to the SmsIOThread");
notifyCursorError(kUnknownError, aRequestId);
}
}
private void getThreadFromCursorAndNotify(Cursor aCursor, int aRequestId) throws Exception {
ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
long id = aCursor.getLong(aCursor.getColumnIndex("thread_id"));
Cursor msgCursor = cr.query(kSmsContentUri,
kRequiredMessageRowsForThread,
"thread_id = " + id,
null,
"date DESC");
if (msgCursor == null || msgCursor.getCount() == 0) {
throw new Exception("Empty thread " + id);
}
msgCursor.moveToFirst();
String lastMessageSubject = msgCursor.getString(msgCursor.getColumnIndex("subject"));
String body = msgCursor.getString(msgCursor.getColumnIndex("body"));
long timestamp = msgCursor.getLong(msgCursor.getColumnIndex("date"));
HashSet<String> participants = new HashSet<>();
do {
String p = msgCursor.getString(msgCursor.getColumnIndex("address"));
participants.add(p);
} while (msgCursor.moveToNext());
//TODO: handle MMS
String lastMessageType = "sms";
msgCursor = cr.query(kSmsContentUri,
kRequiredMessageRowsForThread,
"thread_id = " + id + " AND read = 0",
null,
null);
if (msgCursor == null) {
Log.e("GeckoSmsManager", "We should never get here, should have errored before");
throw new Exception("Empty thread " + id);
}
long unreadCount = msgCursor.getCount();
notifyThreadCursorResult(id, lastMessageSubject, body, unreadCount, participants.toArray(), timestamp, lastMessageType, aRequestId);
}
@Override
public void createThreadCursor(int aRequestId) {
class CreateThreadCursorRunnable implements Runnable {
private final int mRequestId;
CreateThreadCursorRunnable(int aRequestId) {
mRequestId = aRequestId;
}
@Override
public void run() {
try {
Cursor cursor = MessagesListManager.getInstance().get(mListId);
if (!cursor.moveToNext()) {
MessagesListManager.getInstance().remove(mListId);
notifyNoMessageInList(mRequestId);
ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
Cursor cursor = cr.query(kSmsThreadsContentUri,
kThreadProjection,
null,
null,
"date DESC");
if (cursor == null || !cursor.moveToFirst()) {
notifyCursorDone(mRequestId);
return;
}
int type = cursor.getInt(cursor.getColumnIndex("type"));
int deliveryStatus;
String sender = "";
String receiver = "";
MessagesListManager.getInstance().add(mRequestId, cursor);
if (type == kSmsTypeInbox) {
deliveryStatus = kDeliveryStatusSuccess;
sender = cursor.getString(cursor.getColumnIndex("address"));
} else if (type == kSmsTypeSentbox) {
deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
receiver = cursor.getString(cursor.getColumnIndex("address"));
} else {
throw new UnexpectedDeliveryStateException();
}
int listId = MessagesListManager.getInstance().add(cursor);
notifyGotNextMessage(cursor.getInt(cursor.getColumnIndex("_id")),
deliveryStatus,
receiver, sender,
cursor.getString(cursor.getColumnIndex("body")),
cursor.getLong(cursor.getColumnIndex("date")),
mRequestId);
} catch (UnexpectedDeliveryStateException e) {
Log.e("GeckoSmsManager", "Unexcepted delivery state type", e);
notifyReadingMessageListFailed(kUnknownError, mRequestId);
getThreadFromCursorAndNotify(cursor, mRequestId);
} catch (Exception e) {
Log.e("GeckoSmsManager", "Error while trying to get the next message of a list", e);
notifyReadingMessageListFailed(kUnknownError, mRequestId);
Log.e("GeckoSmsManager", "Error while trying to create thread cursor: " + e);
notifyCursorError(kUnknownError, mRequestId);
}
}
}
if (!SmsIOThread.getInstance().execute(new GetNextMessageInListRunnable(aListId, aRequestId))) {
Log.e("GeckoSmsManager", "Failed to add GetNextMessageInListRunnable to the SmsIOThread");
notifyReadingMessageListFailed(kUnknownError, aRequestId);
if (!SmsIOThread.getInstance().execute(new CreateThreadCursorRunnable(aRequestId))) {
Log.e("GeckoSmsManager", "Failed to add CreateThreadCursorRunnable to the SmsIOThread");
notifyCursorError(kUnknownError, aRequestId);
}
}
@Override
public void clearMessageList(int aListId) {
MessagesListManager.getInstance().remove(aListId);
public void getNextThread(int aRequestId) {
class GetNextThreadRunnable implements Runnable {
private final int mRequestId;
GetNextThreadRunnable(int aRequestId) {
mRequestId = aRequestId;
}
@Override
public void run() {
try {
Cursor cursor = MessagesListManager.getInstance().get(mRequestId);
if (!cursor.moveToNext()) {
MessagesListManager.getInstance().remove(mRequestId);
notifyCursorDone(mRequestId);
return;
}
getThreadFromCursorAndNotify(cursor, mRequestId);
} catch (Exception e) {
Log.e("GeckoSmsManager", "Error while trying to create thread cursor: " + e);
notifyCursorError(kUnknownError, mRequestId);
}
}
}
if (!SmsIOThread.getInstance().execute(new GetNextThreadRunnable(aRequestId))) {
Log.e("GeckoSmsManager", "Failed to add GetNextThreadRunnable to the SmsIOThread");
notifyCursorError(kUnknownError, aRequestId);
}
}
@Override
@ -983,24 +1080,32 @@ public class GeckoSmsManager
private static final long serialVersionUID = 51883196784325305L;
}
static class UnexpectedDeliveryStateException extends Exception {
private static final long serialVersionUID = 494122763684005716L;
}
static class UnmatchingIdException extends Exception {
private static final long serialVersionUID = 158467542575633280L;
}
@WrapForJNI
private static native void notifySmsReceived(String aSender, String aBody, int aMessageClass, long aTimestamp);
@WrapForJNI
private static native void notifySmsSent(int aId, String aReceiver, String aBody, long aTimestamp, int aRequestId);
@WrapForJNI
private static native void notifySmsDelivery(int aId, int aDeliveryStatus, String aReceiver, String aBody, long aTimestamp);
@WrapForJNI
private static native void notifySmsSendFailed(int aError, int aRequestId);
private static native void notifyGetSms(int aId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
@WrapForJNI
private static native void notifyGetSms(int aId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, boolean aRead, int aRequestId);
@WrapForJNI
private static native void notifyGetSmsFailed(int aError, int aRequestId);
@WrapForJNI
private static native void notifySmsDeleted(boolean aDeleted, int aRequestId);
@WrapForJNI
private static native void notifySmsDeleteFailed(int aError, int aRequestId);
private static native void notifyNoMessageInList(int aRequestId);
private static native void notifyListCreated(int aListId, int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
private static native void notifyGotNextMessage(int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
private static native void notifyReadingMessageListFailed(int aError, int aRequestId);
@WrapForJNI
private static native void notifyCursorError(int aError, int aRequestId);
@WrapForJNI
private static native void notifyThreadCursorResult(long aId, String aLastMessageSubject, String aBody, long aUnreadCount, Object[] aParticipants, long aTimestamp, String aLastMessageType, int aRequestId);
@WrapForJNI
private static native void notifyMessageCursorResult(int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, long aThreadId, boolean aRead, int aRequestId);
@WrapForJNI
private static native void notifyCursorDone(int aRequestId);
}

View File

@ -31,9 +31,10 @@ public class SmsManager {
void send(String aNumber, String aMessage, int aRequestId);
void getMessage(int aMessageId, int aRequestId);
void deleteMessage(int aMessageId, int aRequestId);
void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId);
void getNextMessageInList(int aListId, int aRequestId);
void clearMessageList(int aListId);
void createMessageCursor(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId);
void createThreadCursor(int aRequestId);
void getNextThread(int aRequestId);
void getNextMessage(int aRequestId);
}
}

View File

@ -14,7 +14,6 @@ import java.util.Locale;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
@ -535,14 +534,11 @@ public class BrowserSearch extends HomeFragment
}
getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, null, mSearchEngineSuggestionLoaderCallbacks);
// Start search history suggestions query only in nightly. Bug 1201325
if (AppConstants.NIGHTLY_BUILD) {
// Saved suggestions
if (mSearchHistorySuggestionLoaderCallback == null) {
mSearchHistorySuggestionLoaderCallback = new SearchHistorySuggestionLoaderCallbacks();
}
getLoaderManager().restartLoader(LOADER_ID_SAVED_SUGGESTION, null, mSearchHistorySuggestionLoaderCallback);
// Saved suggestions
if (mSearchHistorySuggestionLoaderCallback == null) {
mSearchHistorySuggestionLoaderCallback = new SearchHistorySuggestionLoaderCallbacks();
}
getLoaderManager().restartLoader(LOADER_ID_SAVED_SUGGESTION, null, mSearchHistorySuggestionLoaderCallback);
}
private void setSuggestions(ArrayList<String> suggestions) {

View File

@ -5,7 +5,6 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
@ -75,9 +74,6 @@ class SearchEngineRow extends AnimatedHeightLayout {
private int mMaxSavedSuggestions;
private int mMaxSearchSuggestions;
// Remove this default limit value in Bug 1201325
private static final int SUGGESTIONS_MAX = 4;
public SearchEngineRow(Context context) {
this(context, null);
}
@ -269,14 +265,10 @@ class SearchEngineRow extends AnimatedHeightLayout {
* @return the global count of how many suggestions have been bound/shown in the search engine row
*/
private int updateFromSearchEngine(boolean animate, int recycledSuggestionCount, int savedSuggestionCount) {
// Remove this default limit value in Bug 1201325
int maxSuggestions = SUGGESTIONS_MAX;
if (AppConstants.NIGHTLY_BUILD) {
maxSuggestions = mMaxSearchSuggestions;
// If there are less than max saved searches on phones, fill the space with more search engine suggestions
if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
}
int maxSuggestions = mMaxSearchSuggestions;
// If there are less than max saved searches on phones, fill the space with more search engine suggestions
if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
}
int suggestionCounter = 0;
@ -320,13 +312,6 @@ class SearchEngineRow extends AnimatedHeightLayout {
// Set the initial content description.
setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
if (!AppConstants.NIGHTLY_BUILD) {
if (searchSuggestionsEnabled) {
updateFromSearchEngine(animate, mSuggestionView.getChildCount(), 0);
}
return;
}
final int recycledSuggestionCount = mSuggestionView.getChildCount();
final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);

View File

@ -767,13 +767,6 @@ OnSharedPreferenceChangeListener
i--;
continue;
}
} else if (PREFS_HISTORY_SAVED_SEARCH.equals(key)) {
// Remove settings UI if not on Nightly
if (!AppConstants.NIGHTLY_BUILD) {
preferences.removePreference(pref);
i--;
continue;
}
} else if (PREFS_TRACKING_PROTECTION.equals(key)) {
// Remove UI for global TP pref in non-Nightly builds.
if (!AppConstants.NIGHTLY_BUILD) {

View File

@ -20,6 +20,39 @@ var Reader = {
return this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
},
/**
* BackPressListener (listeners / ReaderView Ids).
*/
_backPressListeners: [],
_backPressViewIds: [],
/**
* Set a backPressListener for this tabId / ReaderView Id pair.
*/
_addBackPressListener: function(tabId, viewId, listener) {
this._backPressListeners[tabId] = listener;
this._backPressViewIds[viewId] = tabId;
},
/**
* Remove a backPressListener for this ReaderView Id.
*/
_removeBackPressListener: function(viewId) {
let tabId = this._backPressViewIds[viewId];
if (tabId != undefined) {
this._backPressListeners[tabId] = null;
delete this._backPressViewIds[viewId];
}
},
/**
* If the requested tab has a backPress listener, return its results, else false.
*/
onBackPress: function(tabId) {
let listener = this._backPressListeners[tabId];
return { handled: (listener ? listener() : false) };
},
observe: function Reader_observe(aMessage, aTopic, aData) {
switch (aTopic) {
case "Reader:FetchContent": {
@ -66,6 +99,29 @@ var Reader = {
});
break;
// On DropdownClosed in ReaderView, we cleanup / clear existing BackPressListener.
case "Reader:DropdownClosed": {
this._removeBackPressListener(message.data);
break;
}
// On DropdownOpened in ReaderView, we add BackPressListener to handle a subsequent BACK request.
case "Reader:DropdownOpened": {
let tabId = BrowserApp.selectedTab.id;
this._addBackPressListener(tabId, message.data, () => {
// User hit BACK key while ReaderView has the banner font-dropdown opened.
// Close it and return prevent-default.
if (message.target.messageManager) {
message.target.messageManager.sendAsyncMessage("Reader:CloseDropdown");
return true;
}
// We can assume ReaderView banner's font-dropdown doesn't need to be closed.
return false;
});
break;
}
case "Reader:FaviconRequest": {
Messaging.sendRequestForResult({
type: "Reader:FaviconRequest",

View File

@ -188,17 +188,19 @@ lazilyLoadedObserverScripts.forEach(function (aScript) {
// Lazily-loaded browser scripts that use message listeners.
[
["Reader", [
"Reader:AddToList",
"Reader:ArticleGet",
"Reader:FaviconRequest",
"Reader:ListStatusRequest",
"Reader:RemoveFromList",
"Reader:Share",
"Reader:ToolbarHidden",
"Reader:SystemUIVisibility",
"Reader:UpdateReaderButton",
"Reader:SetIntPref",
"Reader:SetCharPref",
["Reader:AddToList", false],
["Reader:ArticleGet", false],
["Reader:DropdownClosed", true], // 'true' allows us to survive mid-air cycle-collection.
["Reader:DropdownOpened", false],
["Reader:FaviconRequest", false],
["Reader:ListStatusRequest", false],
["Reader:RemoveFromList", false],
["Reader:Share", false],
["Reader:ToolbarHidden", false],
["Reader:SystemUIVisibility", false],
["Reader:UpdateReaderButton", false],
["Reader:SetIntPref", false],
["Reader:SetCharPref", false],
], "chrome://browser/content/Reader.js"],
].forEach(aScript => {
let [name, messages, script] = aScript;
@ -211,11 +213,21 @@ lazilyLoadedObserverScripts.forEach(function (aScript) {
let mm = window.getGroupMessageManager("browsers");
let listener = (message) => {
mm.removeMessageListener(message.name, listener);
mm.addMessageListener(message.name, window[name]);
let listenAfterClose = false;
for (let [name, laClose] of messages) {
if (message.name === name) {
listenAfterClose = laClose;
break;
}
}
mm.addMessageListener(message.name, window[name], listenAfterClose);
window[name].receiveMessage(message);
};
messages.forEach((message) => {
mm.addMessageListener(message, listener);
let [name, listenAfterClose] = message;
mm.addMessageListener(name, listener, listenAfterClose);
});
});
@ -4731,6 +4743,11 @@ var BrowserEventHandler = {
InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
// ReaderViews support backPress listeners.
Messaging.addListener(() => {
return Reader.onBackPress(BrowserApp.selectedTab.id);
}, "Browser:OnBackPressed");
},
handleEvent: function(aEvent) {

View File

@ -28,6 +28,14 @@ builds:
task: tasks/builds/b2g_emulator_kk_opt.yml
debug:
task: tasks/builds/b2g_emulator_kk_debug.yml
emulator-x86-kk:
platforms:
- b2g
types:
opt:
task: tasks/builds/b2g_emulator_x86_kk_opt.yml
debug:
task: tasks/builds/b2g_emulator_x86_kk_debug.yml
emulator-l:
platforms:
- b2g

View File

@ -5,6 +5,11 @@
$inherits:
from: tasks/branches/base_jobs.yml
# Flags specific to this branch
flags:
post-build:
- simulator
builds:
android-api-11:
platforms:
@ -34,3 +39,9 @@ builds:
task: tasks/builds/b2g_flame_kk_ota_opt.yml
debug:
task: tasks/builds/b2g_flame_kk_ota_debug.yml
post-build:
simulator:
allowed_build_tasks:
- tasks/builds/mulet_linux.yml
task: tasks/post-builds/mulet_simulator.yml

View File

@ -9,7 +9,6 @@ $inherits:
flags:
post-build:
- upload-symbols
- simulator
builds:
linux64_gecko:
@ -225,10 +224,6 @@ post-build:
- tasks/builds/dbg_linux64.yml
- tasks/builds/android_api_11.yml
task: tasks/post-builds/upload_symbols.yml
simulator:
allowed_build_tasks:
- tasks/builds/mulet_linux.yml
task: tasks/post-builds/mulet_simulator.yml
tests:
cppunit:

View File

@ -1,3 +1,4 @@
const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
EventManager,

View File

@ -75,7 +75,7 @@ this.InsecurePasswordUtils = {
* both places. Look at
* https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
*/
_checkIfURIisSecure : function(uri) {
checkIfURIisSecure : function(uri) {
let isSafe = false;
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
let ph = Ci.nsIProtocolHandler;
@ -109,7 +109,7 @@ this.InsecurePasswordUtils = {
// We are at the top, nothing to check here
return false;
}
if (!this._checkIfURIisSecure(uri)) {
if (!this.checkIfURIisSecure(uri)) {
// We are insecure
return true;
}
@ -127,7 +127,7 @@ this.InsecurePasswordUtils = {
checkForInsecurePasswords : function (aForm) {
var domDoc = aForm.ownerDocument;
let pageURI = domDoc.defaultView.top.document.documentURIObject;
let isSafePage = this._checkIfURIisSecure(pageURI);
let isSafePage = this.checkIfURIisSecure(pageURI);
if (!isSafePage) {
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);

View File

@ -15,12 +15,14 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
"resource://gre/modules/LoginRecipes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
"resource://gre/modules/InsecurePasswordUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
"resource://gre/modules/LoginRecipes.jsm");
XPCOMUtils.defineLazyGetter(this, "log", () => {
let logger = LoginHelper.createLogger("LoginManagerContent");
@ -414,6 +416,18 @@ var LoginManagerContent = {
return null;
};
// Returns true if this window or any subframes have insecure login forms.
let hasInsecureLoginForms = (thisWindow, parentIsInsecure) => {
let doc = thisWindow.document;
let isInsecure =
parentIsInsecure ||
!InsecurePasswordUtils.checkIfURIisSecure(doc.documentURIObject);
let hasLoginForm = !!this.stateForDocument(doc).loginForm;
return (hasLoginForm && isInsecure) ||
Array.some(thisWindow.frames,
frame => hasInsecureLoginForms(frame, isInsecure));
};
// Store the actual form to use on the state for the top-level document.
let topState = this.stateForDocument(topWindow.document);
topState.loginFormForFill = getFirstLoginForm(topWindow);
@ -423,6 +437,7 @@ var LoginManagerContent = {
messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
loginFormOrigin,
loginFormPresent: !!topState.loginFormForFill,
hasInsecureLoginForms: hasInsecureLoginForms(topWindow, false),
});
},

View File

@ -569,12 +569,23 @@ var LoginManagerParent = {
return loginFormState;
},
/**
* Returns true if the page currently loaded in the given browser element has
* insecure login forms. This state may be updated asynchronously, in which
* case a custom event named InsecureLoginFormsStateChange will be dispatched
* on the browser element.
*/
hasInsecureLoginForms(browser) {
return !!this.stateForBrowser(browser).hasInsecureLoginForms;
},
/**
* Called to indicate whether a login form on the currently loaded page is
* present or not. This is one of the factors used to control the visibility
* of the password fill doorhanger.
*/
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent }) {
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent,
hasInsecureLoginForms }) {
const ANCHOR_DELAY_MS = 200;
let state = this.stateForBrowser(browser);
@ -583,8 +594,13 @@ var LoginManagerParent = {
// processed in order, this will always be the latest version to use.
state.loginFormOrigin = loginFormOrigin;
state.loginFormPresent = loginFormPresent;
state.hasInsecureLoginForms = hasInsecureLoginForms;
// Apply the data to the currently displayed icon later.
// Report the insecure login form state immediately.
browser.dispatchEvent(new browser.ownerDocument.defaultView
.CustomEvent("InsecureLoginFormsStateChange"));
// Apply the data to the currently displayed login fill icon later.
if (!state.anchorDeferredTask) {
state.anchorDeferredTask = new DeferredTask(
() => this.updateLoginAnchor(browser),

View File

@ -2,11 +2,14 @@
support-files =
authenticate.sjs
form_basic.html
insecure_test.html
insecure_test_subframe.html
multiple_forms.html
[browser_DOMFormHasPassword.js]
[browser_DOMInputPasswordAdded.js]
[browser_filldoorhanger.js]
[browser_hasInsecureLoginForms.js]
[browser_notifications.js]
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
[browser_passwordmgr_editing.js]

View File

@ -0,0 +1,93 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/LoginManagerParent.jsm", this);
const testUrlPath =
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
}
/**
* Checks that hasInsecureLoginForms is true for a simple HTTP page and false
* for a simple HTTPS page.
*/
add_task(function* test_simple() {
for (let scheme of ["http", "https"]) {
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
Assert.equal(LoginManagerParent.hasInsecureLoginForms(browser),
scheme == "http");
gBrowser.removeTab(tab);
}
});
/**
* Checks that hasInsecureLoginForms is true if a password field is present in
* an HTTP page loaded as a subframe of a top-level HTTPS page, when mixed
* active content blocking is disabled.
*
* When the subframe is navigated to an HTTPS page, hasInsecureLoginForms should
* be set to false.
*
* Moving back in history should set hasInsecureLoginForms to true again.
*/
add_task(function* test_subframe_navigation() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.mixed_content.block_active_content", false]],
}, resolve));
// Load the page with the subframe in a new tab.
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
// Navigate the subframe to a secure page.
let promiseSubframeReady = Promise.all([
BrowserTestUtils.browserLoaded(browser, true),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("test-iframe")
.contentDocument.getElementById("test-link").click();
});
yield promiseSubframeReady;
Assert.ok(!LoginManagerParent.hasInsecureLoginForms(browser));
// Navigate back to the insecure page. We only have to wait for the
// InsecureLoginFormsStateChange event that is triggered by pageshow.
let promise = waitForInsecureLoginFormsStateChange(browser, 1);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("test-iframe")
.contentWindow.history.back();
});
yield promise;
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
gBrowser.removeTab(tab);
});

View File

@ -0,0 +1,9 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- This frame is initially loaded over HTTP. -->
<iframe id="test-iframe"
src="http://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html"/>
</body></html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<form>
<input name="password" type="password">
</form>
<!-- Link to reload this page over HTTPS. -->
<a id="test-link"
href="https://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html">HTTPS</a>
</body></html>

View File

@ -38,6 +38,7 @@ var AboutReader = function(mm, win, articlePromise) {
this._mm.addMessageListener("Reader:Removed", this);
this._mm.addMessageListener("Sidebar:VisibilityChange", this);
this._mm.addMessageListener("ReadingList:VisibilityStatus", this);
this._mm.addMessageListener("Reader:CloseDropdown", this);
this._docRef = Cu.getWeakReference(doc);
this._winRef = Cu.getWeakReference(win);
@ -178,6 +179,15 @@ AboutReader.prototype = {
return this._toolbarVertical = Services.prefs.getBoolPref("reader.toolbar.vertical");
},
// Provides unique view Id.
get viewId() {
let _viewId = Cc["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator).generateUUID().toString();
Object.defineProperty(this, "viewId", { value: _viewId });
return _viewId;
},
receiveMessage: function (message) {
switch (message.name) {
case "Reader:Added": {
@ -190,6 +200,14 @@ AboutReader.prototype = {
}
break;
}
// Triggered by Android user pressing BACK while the banner font-dropdown is open.
case "Reader:CloseDropdown": {
// Just close it.
this._closeDropdown();
break;
}
case "Reader:Removed": {
if (message.data.url == this._article.url) {
if (this._isReadingListItem != 0) {
@ -246,10 +264,14 @@ AboutReader.prototype = {
break;
case "unload":
// Close the Banners Font-dropdown, cleanup Android BackPressListener.
this._closeDropdown();
this._mm.removeMessageListener("Reader:Added", this);
this._mm.removeMessageListener("Reader:Removed", this);
this._mm.removeMessageListener("Sidebar:VisibilityChange", this);
this._mm.removeMessageListener("ReadingList:VisibilityStatus", this);
this._mm.removeMessageListener("Reader:CloseDropdown", this);
this._windowUnloaded = true;
break;
}
@ -584,8 +606,7 @@ AboutReader.prototype = {
},
_setToolbarVisibility: function(visible) {
let dropdown = this._doc.getElementById("style-dropdown");
dropdown.classList.remove("open");
this._closeDropdown();
if (this._getToolbarVisibility() === visible) {
return;
@ -943,13 +964,41 @@ AboutReader.prototype = {
event.stopPropagation();
if (dropdown.classList.contains("open")) {
dropdown.classList.remove("open");
this._closeDropdown();
} else {
dropdown.classList.add("open");
this._openDropdown();
if (this._isToolbarVertical) {
updatePopupPosition();
}
}
}, true);
},
/*
* If the ReaderView banner font-dropdown is closed, open it.
*/
_openDropdown: function() {
let dropdown = this._doc.getElementById("style-dropdown");
if (dropdown.classList.contains("open")) {
return;
}
// Trigger BackPressListener initialization in Android.
dropdown.classList.add("open");
this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId);
},
/*
* If the ReaderView banner font-dropdown is opened, close it.
*/
_closeDropdown: function() {
let dropdown = this._doc.getElementById("style-dropdown");
if (!dropdown.classList.contains("open")) {
return;
}
// Trigger BackPressListener cleanup in Android.
dropdown.classList.remove("open");
this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
},
};

View File

@ -38,6 +38,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
const CHANGE_THROTTLE_INTERVAL_MS = 5 * 60 * 1000;
// The maximum length of a string (e.g. description) in the addons section.
const MAX_ADDON_STRING_LENGTH = 100;
/**
* This is a policy object used to override behavior for testing.
*/
@ -249,6 +252,21 @@ function getGfxField(aPropertyName, aDefault) {
return aDefault;
}
/**
* Returns a substring of the input string.
*
* @param {String} aString The input string.
* @param {Integer} aMaxLength The maximum length of the returned substring. If this is
* greater than the length of the input string, we return the whole input string.
* @return {String} The substring or null if the input string is null.
*/
function limitStringToLength(aString, aMaxLength) {
if (aString === null) {
return null;
}
return aString.substring(0, aMaxLength);
}
/**
* Get the information about a graphic adapter.
*
@ -500,11 +518,11 @@ EnvironmentAddonBuilder.prototype = {
activeAddons[addon.id] = {
blocklisted: (addon.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
description: addon.description,
name: addon.name,
description: limitStringToLength(addon.description, MAX_ADDON_STRING_LENGTH),
name: limitStringToLength(addon.name, MAX_ADDON_STRING_LENGTH),
userDisabled: addon.userDisabled,
appDisabled: addon.appDisabled,
version: addon.version,
version: limitStringToLength(addon.version, MAX_ADDON_STRING_LENGTH),
scope: addon.scope,
type: addon.type,
foreignInstall: addon.foreignInstall,
@ -540,11 +558,11 @@ EnvironmentAddonBuilder.prototype = {
activeTheme = {
id: theme.id,
blocklisted: (theme.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
description: theme.description,
name: theme.name,
description: limitStringToLength(theme.description, MAX_ADDON_STRING_LENGTH),
name: limitStringToLength(theme.name, MAX_ADDON_STRING_LENGTH),
userDisabled: theme.userDisabled,
appDisabled: theme.appDisabled,
version: theme.version,
version: limitStringToLength(theme.version, MAX_ADDON_STRING_LENGTH),
scope: theme.scope,
foreignInstall: theme.foreignInstall,
hasBinaryComponents: theme.hasBinaryComponents,
@ -575,9 +593,9 @@ EnvironmentAddonBuilder.prototype = {
let updateDate = new Date(Math.max(0, tag.lastModifiedTime));
activePlugins.push({
name: tag.name,
version: tag.version,
description: tag.description,
name: limitStringToLength(tag.name, MAX_ADDON_STRING_LENGTH),
version: limitStringToLength(tag.version, MAX_ADDON_STRING_LENGTH),
description: limitStringToLength(tag.description, MAX_ADDON_STRING_LENGTH),
blocklisted: tag.blocklisted,
disabled: tag.disabled,
clicktoplay: tag.clicktoplay,

View File

@ -299,3 +299,8 @@ The following is a partial list of collected preferences.
- ``browser.urlbar.unifiedcomplete``: True if the urlbar's UnifiedComplete back-end is enabled.
- ``browser.urlbar.userMadeSearchSuggestionsChoice``: True if the user has clicked Yes or No in the urlbar's opt-in notification. Defaults to false.
activeAddons
~~~~~~~~~~~~
Starting from Firefox 44, the length of the following string fields: ``name``, ``description`` and ``version`` is limited to 100 characters. The same limitation applies to the same fields in ``theme`` and ``activePlugins``.

View File

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>tel-longfields-xpi@tests.mozilla.org</em:id>
<em:version>This is a really long addon version, that will get limited to 100 characters. We're much longer, we're at about 116.</em:version>
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>This is a really long addon name, that will get limited to 100 characters. We're much longer, we're at about 219. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nullam sodales. Yeah, Latin placeholder.</em:name>
<em:description>This is a really long addon description, that will get limited to 100 characters. We're much longer, we're at about 200. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nullam sodales.</em:description>
<em:bootstrap>true</em:bootstrap>
</Description>
</RDF>

View File

@ -1017,6 +1017,37 @@ add_task(function* test_signedAddon() {
}
});
add_task(function* test_addonsFieldsLimit() {
const ADDON_INSTALL_URL = gDataRoot + "long-fields.xpi";
const ADDON_ID = "tel-longfields-xpi@tests.mozilla.org";
// Set the clock in the future so our changes don't get throttled.
gNow = fakeNow(futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE));
// Install the addon and wait for the TelemetryEnvironment to pick it up.
let deferred = PromiseUtils.defer();
TelemetryEnvironment.registerChangeListener("test_longFieldsAddon", deferred.resolve);
yield AddonTestUtils.installXPIFromURL(ADDON_INSTALL_URL);
yield deferred.promise;
TelemetryEnvironment.unregisterChangeListener("test_longFieldsAddon");
let data = TelemetryEnvironment.currentEnvironment;
checkEnvironmentData(data);
// Check that the addon is available and that the string fields are limited.
Assert.ok(ADDON_ID in data.addons.activeAddons, "Add-on should be in the environment.");
let targetAddon = data.addons.activeAddons[ADDON_ID];
// TelemetryEnvironment limits the length of string fields for activeAddons to 100 chars,
// to mitigate misbehaving addons.
Assert.lessOrEqual(targetAddon.version.length, 100,
"The version string must have been limited");
Assert.lessOrEqual(targetAddon.name.length, 100,
"The name string must have been limited");
Assert.lessOrEqual(targetAddon.description.length, 100,
"The description string must have been limited");
});
add_task(function* test_changeThrottling() {
const PREF_TEST = "toolkit.telemetry.test.pref1";
const PREFS_TO_WATCH = new Map([

Some files were not shown because too many files have changed in this diff Show More