Merge m-c to inbound. a=merge
@ -15,7 +15,7 @@
|
|||||||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
|
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
|
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
|
||||||
|
@ -4,6 +4,6 @@
|
|||||||
"remote": "",
|
"remote": "",
|
||||||
"branch": ""
|
"branch": ""
|
||||||
},
|
},
|
||||||
"revision": "52f7b7099a47ab3904a70d9a295ab0ed927ad59e",
|
"revision": "3e43be9b8c24802b40fdfbcf17895c4355e6d238",
|
||||||
"repo_path": "/integration/gaia-central"
|
"repo_path": "/integration/gaia-central"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</project>
|
</project>
|
||||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
|
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||||
</project>
|
</project>
|
||||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="191d805f4911628d37a8a90a1e23a6013995138f"/>
|
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d711d1e469eeeecf25a02b2407a542a598918b2c"/>
|
||||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
|
||||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||||
|
@ -1616,6 +1616,8 @@ pref("loop.debug.loglevel", "Error");
|
|||||||
pref("loop.debug.dispatcher", false);
|
pref("loop.debug.dispatcher", false);
|
||||||
pref("loop.debug.websocket", false);
|
pref("loop.debug.websocket", false);
|
||||||
pref("loop.debug.sdk", false);
|
pref("loop.debug.sdk", false);
|
||||||
|
pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
|
||||||
|
pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
|
||||||
|
|
||||||
// serverURL to be assigned by services team
|
// serverURL to be assigned by services team
|
||||||
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
|
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
|
||||||
|
@ -694,7 +694,7 @@ function gKeywordURIFixup({ target: browser, data: fixupInfo }) {
|
|||||||
// whether the original input would be vaguely interpretable as a URL,
|
// whether the original input would be vaguely interpretable as a URL,
|
||||||
// so figure that out first.
|
// so figure that out first.
|
||||||
let alternativeURI = deserializeURI(fixupInfo.fixedURI);
|
let alternativeURI = deserializeURI(fixupInfo.fixedURI);
|
||||||
if (!fixupInfo.fixupUsedKeyword || !alternativeURI || !alternativeURI.host) {
|
if (!fixupInfo.keywordProviderName || !alternativeURI || !alternativeURI.host) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2400,13 +2400,13 @@ let BrowserOnClick = {
|
|||||||
receiveMessage: function (msg) {
|
receiveMessage: function (msg) {
|
||||||
switch (msg.name) {
|
switch (msg.name) {
|
||||||
case "Browser:CertExceptionError":
|
case "Browser:CertExceptionError":
|
||||||
this.onAboutCertError(msg.target, msg.json.elementId,
|
this.onAboutCertError(msg.target, msg.data.elementId,
|
||||||
msg.json.isTopFrame, msg.json.location,
|
msg.data.isTopFrame, msg.data.location,
|
||||||
msg.objects.failedChannel);
|
msg.data.sslStatusAsString);
|
||||||
break;
|
break;
|
||||||
case "Browser:SiteBlockedError":
|
case "Browser:SiteBlockedError":
|
||||||
this.onAboutBlocked(msg.json.elementId, msg.json.isMalware,
|
this.onAboutBlocked(msg.data.elementId, msg.data.isMalware,
|
||||||
msg.json.isTopFrame, msg.json.location);
|
msg.data.isTopFrame, msg.data.location);
|
||||||
break;
|
break;
|
||||||
case "Browser:NetworkError":
|
case "Browser:NetworkError":
|
||||||
// Reset network state, the error page will refresh on its own.
|
// Reset network state, the error page will refresh on its own.
|
||||||
@ -2415,7 +2415,7 @@ let BrowserOnClick = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onAboutCertError: function (browser, elementId, isTopFrame, location, failedChannel) {
|
onAboutCertError: function (browser, elementId, isTopFrame, location, sslStatusAsString) {
|
||||||
let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
|
let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
|
||||||
|
|
||||||
switch (elementId) {
|
switch (elementId) {
|
||||||
@ -2423,8 +2423,11 @@ let BrowserOnClick = {
|
|||||||
if (isTopFrame) {
|
if (isTopFrame) {
|
||||||
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
|
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
|
||||||
}
|
}
|
||||||
let sslStatus = failedChannel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
|
|
||||||
.SSLStatus;
|
let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
|
||||||
|
.getService(Ci.nsISerializationHelper);
|
||||||
|
let sslStatus = serhelper.deserializeObject(sslStatusAsString);
|
||||||
|
sslStatus.QueryInterface(Components.interfaces.nsISSLStatus);
|
||||||
let params = { exceptionAdded : false,
|
let params = { exceptionAdded : false,
|
||||||
sslStatus : sslStatus };
|
sslStatus : sslStatus };
|
||||||
|
|
||||||
|
@ -424,12 +424,23 @@ let ClickEventHandler = {
|
|||||||
let docshell = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
|
let docshell = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIWebNavigation)
|
.getInterface(Ci.nsIWebNavigation)
|
||||||
.QueryInterface(Ci.nsIDocShell);
|
.QueryInterface(Ci.nsIDocShell);
|
||||||
|
let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
|
||||||
|
.getService(Ci.nsISerializationHelper);
|
||||||
|
let serializedSSLStatus = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
let serializable = docShell.failedChannel.securityInfo
|
||||||
|
.QueryInterface(Ci.nsISSLStatusProvider)
|
||||||
|
.SSLStatus
|
||||||
|
.QueryInterface(Ci.nsISerializable);
|
||||||
|
serializedSSLStatus = serhelper.serializeToString(serializable);
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
sendAsyncMessage("Browser:CertExceptionError", {
|
sendAsyncMessage("Browser:CertExceptionError", {
|
||||||
location: ownerDoc.location.href,
|
location: ownerDoc.location.href,
|
||||||
elementId: targetElement.getAttribute("id"),
|
elementId: targetElement.getAttribute("id"),
|
||||||
isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView),
|
isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView),
|
||||||
}, {
|
sslStatusAsString: serializedSSLStatus
|
||||||
failedChannel: docshell.failedChannel
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -104,15 +104,15 @@ skip-if = os == "linux" # Bug 924307
|
|||||||
skip-if = e10s # Bug ?????? - no about:home support yet
|
skip-if = e10s # Bug ?????? - no about:home support yet
|
||||||
[browser_aboutSyncProgress.js]
|
[browser_aboutSyncProgress.js]
|
||||||
[browser_action_keyword.js]
|
[browser_action_keyword.js]
|
||||||
skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux
|
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
|
||||||
[browser_action_searchengine.js]
|
[browser_action_searchengine.js]
|
||||||
skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux
|
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
|
||||||
[browser_action_searchengine_alias.js]
|
[browser_action_searchengine_alias.js]
|
||||||
skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux
|
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
|
||||||
[browser_addKeywordSearch.js]
|
[browser_addKeywordSearch.js]
|
||||||
skip-if = e10s
|
skip-if = e10s
|
||||||
[browser_search_favicon.js]
|
[browser_search_favicon.js]
|
||||||
skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux
|
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
|
||||||
[browser_alltabslistener.js]
|
[browser_alltabslistener.js]
|
||||||
skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 951680; e10s: Bug ?????? - notifications don't work correctly.
|
skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 951680; e10s: Bug ?????? - notifications don't work correctly.
|
||||||
[browser_autocomplete_a11y_label.js]
|
[browser_autocomplete_a11y_label.js]
|
||||||
|
541
browser/components/loop/GoogleImporter.jsm
Normal file
@ -0,0 +1,541 @@
|
|||||||
|
/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Timer.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||||
|
"resource://gre/modules/Promise.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||||
|
"resource://gre/modules/Task.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Log",
|
||||||
|
"resource://gre/modules/Log.jsm");
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = ["GoogleImporter"];
|
||||||
|
|
||||||
|
let log = Log.repository.getLogger("Loop.Importer.Google");
|
||||||
|
log.level = Log.Level.Debug;
|
||||||
|
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that reads and maps the respective node value from specific
|
||||||
|
* XML DOMNodes to fields on a `target` object.
|
||||||
|
* Example: the value for field 'fullName' can be read from the XML DOMNode
|
||||||
|
* 'name', so that's the mapping we need to make; get the nodeValue of
|
||||||
|
* the node called 'name' and tack it to the target objects' 'fullName'
|
||||||
|
* property.
|
||||||
|
*
|
||||||
|
* @param {Map} fieldMap Map object containing the field name -> node
|
||||||
|
* name mapping
|
||||||
|
* @param {XMLDOMNode} node DOM node to fetch the values from for each field
|
||||||
|
* @param {String} ns XML namespace for the DOM nodes to retrieve. Optional.
|
||||||
|
* @param {Object} target Object to store the values found. Optional.
|
||||||
|
* Defaults to a new object.
|
||||||
|
* @param {Boolean} wrapInArray Indicates whether to map the field values in
|
||||||
|
* an Array. Optional. Defaults to `false`.
|
||||||
|
* @returns The `target` object with the node values mapped to the appropriate fields.
|
||||||
|
*/
|
||||||
|
const extractFieldsFromNode = function(fieldMap, node, ns = null, target = {}, wrapInArray = false) {
|
||||||
|
for (let [field, nodeName] of fieldMap) {
|
||||||
|
let nodeList = ns ? node.getElementsByTagNameNS(ns, nodeName) :
|
||||||
|
node.getElementsByTagName(nodeName);
|
||||||
|
if (nodeList.length) {
|
||||||
|
if (!nodeList[0].firstChild) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let value = nodeList[0].firstChild.nodeValue;
|
||||||
|
target[field] = wrapInArray ? [value] : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that reads the type of (email-)address or phone number from an
|
||||||
|
* XMLDOMNode.
|
||||||
|
*
|
||||||
|
* @param {XMLDOMNode} node
|
||||||
|
* @returns String that depicts the type of field value.
|
||||||
|
*/
|
||||||
|
const getFieldType = function(node) {
|
||||||
|
if (node.hasAttribute("rel")) {
|
||||||
|
let rel = node.getAttribute("rel");
|
||||||
|
// The 'rel' attribute is formatted like: http://schemas.google.com/g/2005#work.
|
||||||
|
return rel.substr(rel.lastIndexOf("#") + 1);
|
||||||
|
}
|
||||||
|
if (node.hasAttribute("label")) {
|
||||||
|
return node.getAttribute("label");
|
||||||
|
}
|
||||||
|
return "other";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the preferred entry of a contact. Returns the first entry when no
|
||||||
|
* preferred flag is set.
|
||||||
|
*
|
||||||
|
* @param {Object} contact The contact object to check for preferred entries
|
||||||
|
* @param {String} which Type of entry to check. Optional, defaults to 'email'
|
||||||
|
* @throws An Error when no (preferred) entries are listed for this contact.
|
||||||
|
*/
|
||||||
|
const getPreferred = function(contact, which = "email") {
|
||||||
|
if (!(which in contact) || !contact[which].length) {
|
||||||
|
throw new Error("No " + which + " entry available.");
|
||||||
|
}
|
||||||
|
let preferred = contact[which][0];
|
||||||
|
contact[which].some(function(entry) {
|
||||||
|
if (entry.pref) {
|
||||||
|
preferred = entry;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return preferred;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch an auth token (clientID or client secret), which may be overridden by
|
||||||
|
* a pref if it's set.
|
||||||
|
*
|
||||||
|
* @param {String} paramValue Initial, default, value of the parameter
|
||||||
|
* @param {String} prefName Fully qualified name of the pref to check for
|
||||||
|
* @param {Boolean} encode Whether to URLEncode the param string
|
||||||
|
*/
|
||||||
|
const getUrlParam = function(paramValue, prefName, encode = true) {
|
||||||
|
if (Services.prefs.getPrefType(prefName))
|
||||||
|
paramValue = Services.prefs.getCharPref(prefName);
|
||||||
|
paramValue = Services.urlFormatter.formatURL(paramValue);
|
||||||
|
|
||||||
|
return encode ? encodeURIComponent(paramValue) : paramValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let gAuthWindow, gProfileId;
|
||||||
|
const kAuthWindowSize = {
|
||||||
|
width: 420,
|
||||||
|
height: 460
|
||||||
|
};
|
||||||
|
const kContactsMaxResults = 10000000;
|
||||||
|
const kContactsChunkSize = 100;
|
||||||
|
const kTitlebarPollTimeout = 200;
|
||||||
|
const kNS_GD = "http://schemas.google.com/g/2005";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GoogleImporter class.
|
||||||
|
*
|
||||||
|
* Main entrypoint is the `startImport` method which calls several tasks necessary
|
||||||
|
* to import contacts from Google.
|
||||||
|
* Authentication is performed using an OAuth strategy which is loaded in a popup
|
||||||
|
* window.
|
||||||
|
*/
|
||||||
|
this.GoogleImporter = function() {};
|
||||||
|
|
||||||
|
this.GoogleImporter.prototype = {
|
||||||
|
/**
|
||||||
|
* Start the import process of contacts from the Google service, using its Contacts
|
||||||
|
* API - https://developers.google.com/google-apps/contacts/v3/.
|
||||||
|
* The import consists of four tasks:
|
||||||
|
* 1. Get the authentication code which can be used to retrieve an OAuth token
|
||||||
|
* pair. This is the bulk of the authentication flow that will be handled in
|
||||||
|
* a popup window by Google. The user will need to login to the Google service
|
||||||
|
* with his or her account and grant permission to our app to manage their
|
||||||
|
* contacts.
|
||||||
|
* 2. Get the tokenset from the Google service, using the authentication code
|
||||||
|
* that was retrieved in task 1.
|
||||||
|
* 3. Fetch all the contacts from the Google service, using the OAuth tokenset
|
||||||
|
* that was retrieved in task 2.
|
||||||
|
* 4. Process the contacts, map them to the MozContact format and store each
|
||||||
|
* contact in the database, if it doesn't exist yet.
|
||||||
|
*
|
||||||
|
* @param {Object} options Options to control the behavior of the import.
|
||||||
|
* Not used by this importer class.
|
||||||
|
* @param {Function} callback Function to invoke when the import process
|
||||||
|
* is done or when an error occurs that halts
|
||||||
|
* the import process. The first argument passed
|
||||||
|
* in an Error object or `null` and the second
|
||||||
|
* argument is an object with import statistics.
|
||||||
|
* @param {LoopContacts} db Instance of the LoopContacts database object,
|
||||||
|
* which will store the newly found contacts
|
||||||
|
* @param {nsIDomWindow} windowRef Reference to the ChromeWindow the import is
|
||||||
|
* invoked from. It will be used to be able to
|
||||||
|
* open a window for the OAuth process with chrome
|
||||||
|
* privileges.
|
||||||
|
*/
|
||||||
|
startImport: function(options, callback, db, windowRef) {
|
||||||
|
Task.spawn(function* () {
|
||||||
|
let code = yield this._promiseAuthCode(windowRef);
|
||||||
|
let tokenSet = yield this._promiseTokenSet(code);
|
||||||
|
let contactEntries = yield this._promiseContactEntries(tokenSet);
|
||||||
|
let {total, success, ids} = yield this._processContacts(contactEntries, db);
|
||||||
|
yield this._purgeContacts(ids, db);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: total,
|
||||||
|
success: success
|
||||||
|
};
|
||||||
|
}.bind(this)).then(stats => callback(null, stats),
|
||||||
|
error => callback(error))
|
||||||
|
.then(null, ex => log.error(ex.fileName + ":" + ex.lineNumber + ": " + ex.message));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task that yields an authentication code that is returned after the user signs
|
||||||
|
* in to the Google service. This code can be used by this class to retrieve an
|
||||||
|
* OAuth tokenset.
|
||||||
|
*
|
||||||
|
* @param {nsIDOMWindow} windowRef Reference to the ChromeWindow the import is
|
||||||
|
* invoked from. It will be used to be able to
|
||||||
|
* open a window for the OAuth process with chrome
|
||||||
|
* privileges.
|
||||||
|
* @throws An `Error` object when authentication fails, or the authentication
|
||||||
|
* code as a String.
|
||||||
|
*/
|
||||||
|
_promiseAuthCode: Task.async(function* (windowRef) {
|
||||||
|
// Close a window that got lost in a previous login attempt.
|
||||||
|
if (gAuthWindow && !gAuthWindow.closed) {
|
||||||
|
gAuthWindow.close();
|
||||||
|
gAuthWindow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = getUrlParam("https://accounts.google.com/o/oauth2/",
|
||||||
|
"loop.oauth.google.URL", false) +
|
||||||
|
"auth?response_type=code&client_id=" +
|
||||||
|
getUrlParam("%GOOGLE_OAUTH_API_CLIENTID%", "loop.oauth.google.clientIdOverride");
|
||||||
|
for (let param of ["redirect_uri", "scope"]) {
|
||||||
|
url += "&" + param + "=" + encodeURIComponent(
|
||||||
|
Services.prefs.getCharPref("loop.oauth.google." + param));
|
||||||
|
}
|
||||||
|
const features = "centerscreen,resizable=yes,toolbar=no,menubar=no,status=no,directories=no," +
|
||||||
|
"width=" + kAuthWindowSize.width + ",height=" + kAuthWindowSize.height;
|
||||||
|
gAuthWindow = windowRef.openDialog(windowRef.getBrowserURL(), "_blank", features, url);
|
||||||
|
gAuthWindow.focus();
|
||||||
|
|
||||||
|
let code;
|
||||||
|
// The following loops runs as long as the OAuth windows' titlebar doesn't
|
||||||
|
// yield a response from the Google service. If an error occurs, the loop
|
||||||
|
// will terminate early.
|
||||||
|
while (!code) {
|
||||||
|
if (!gAuthWindow || gAuthWindow.closed) {
|
||||||
|
throw new Error("Popup window was closed before authentication succeeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
let matches = gAuthWindow.document.title.match(/(error|code)=(.*)$/);
|
||||||
|
if (matches && matches.length) {
|
||||||
|
let [, type, message] = matches;
|
||||||
|
gAuthWindow.close();
|
||||||
|
gAuthWindow = null;
|
||||||
|
if (type == "error") {
|
||||||
|
throw new Error("Google authentication failed with error: " + message.trim());
|
||||||
|
} else if (type == "code") {
|
||||||
|
code = message.trim();
|
||||||
|
} else {
|
||||||
|
throw new Error("Unknown response from Google");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield new Promise(resolve => setTimeout(resolve, kTitlebarPollTimeout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch an OAuth tokenset, that will be used to authenticate Google API calls,
|
||||||
|
* using the authentication token retrieved in `_promiseAuthCode`.
|
||||||
|
*
|
||||||
|
* @param {String} code The authentication code.
|
||||||
|
* @returns an `Error` object upon failure or an object containing OAuth tokens.
|
||||||
|
*/
|
||||||
|
_promiseTokenSet: function(code) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||||
|
.createInstance(Ci.nsIXMLHttpRequest);
|
||||||
|
|
||||||
|
request.open("POST", getUrlParam("https://accounts.google.com/o/oauth2/",
|
||||||
|
"loop.oauth.google.URL",
|
||||||
|
false) + "token");
|
||||||
|
|
||||||
|
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
request.onload = function() {
|
||||||
|
if (request.status < 400) {
|
||||||
|
let tokenSet = JSON.parse(request.responseText);
|
||||||
|
tokenSet.date = Date.now();
|
||||||
|
resolve(tokenSet);
|
||||||
|
} else {
|
||||||
|
reject(new Error(request.status + " " + request.statusText));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onerror = function(error) {
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = "grant_type=authorization_code&code=" + encodeURIComponent(code) +
|
||||||
|
"&client_id=" + getUrlParam("%GOOGLE_OAUTH_API_CLIENTID%",
|
||||||
|
"loop.oauth.google.clientIdOverride") +
|
||||||
|
"&client_secret=" + getUrlParam("%GOOGLE_OAUTH_API_KEY%",
|
||||||
|
"loop.oauth.google.clientSecretOverride") +
|
||||||
|
"&redirect_uri=" + encodeURIComponent(Services.prefs.getCharPref(
|
||||||
|
"loop.oauth.google.redirect_uri"));
|
||||||
|
|
||||||
|
request.send(body);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all the contacts in a users' address book.
|
||||||
|
*
|
||||||
|
* @see https://developers.google.com/google-apps/contacts/v3/#retrieving_all_contacts
|
||||||
|
*
|
||||||
|
* @param {Object} tokenSet OAuth tokenset used to authenticate the request
|
||||||
|
* @returns An `Error` object upon failure or an Array of contact XML nodes.
|
||||||
|
*/
|
||||||
|
_promiseContactEntries: function(tokenSet) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||||
|
.createInstance(Ci.nsIXMLHttpRequest);
|
||||||
|
|
||||||
|
request.open("GET", getUrlParam("https://www.google.com/m8/feeds/contacts/default/full",
|
||||||
|
"loop.oauth.google.getContactsURL",
|
||||||
|
false) + "?max-results=" + kContactsMaxResults);
|
||||||
|
|
||||||
|
request.setRequestHeader("Content-Type", "application/xml; charset=utf-8");
|
||||||
|
request.setRequestHeader("GData-Version", "3.0");
|
||||||
|
request.setRequestHeader("Authorization", "Bearer " + tokenSet.access_token);
|
||||||
|
|
||||||
|
request.onload = function() {
|
||||||
|
if (request.status < 400) {
|
||||||
|
let doc = request.responseXML;
|
||||||
|
// First get the profile id.
|
||||||
|
let currNode = doc.documentElement.firstChild;
|
||||||
|
while (currNode) {
|
||||||
|
if (currNode.nodeType == 1 && currNode.localName == "id") {
|
||||||
|
gProfileId = currNode.firstChild.nodeValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currNode = currNode.nextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then kick of the importing of contact entries.
|
||||||
|
let entries = Array.prototype.slice.call(doc.querySelectorAll("entry"));
|
||||||
|
resolve(entries);
|
||||||
|
} else {
|
||||||
|
reject(new Error(request.status + " " + request.statusText));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onerror = function(error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.send();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the contact XML nodes that Google provides, convert them to the MozContact
|
||||||
|
* format, check if the contact already exists in the database and when it doesn't,
|
||||||
|
* store it permanently.
|
||||||
|
* During this process statistics are collected about the amount of successful
|
||||||
|
* imports. The consumer of this class may use these statistics to inform the
|
||||||
|
* user.
|
||||||
|
*
|
||||||
|
* @param {Array} contactEntries List of XML DOMNodes contact entries.
|
||||||
|
* @param {LoopContacts} db Instance of the LoopContacts database
|
||||||
|
* object, which will store the newly found
|
||||||
|
* contacts.
|
||||||
|
* @returns An `Error` object upon failure or an Object with statistics in the
|
||||||
|
* following format: `{ total: 25, success: 13, ids: {} }`.
|
||||||
|
*/
|
||||||
|
_processContacts: Task.async(function* (contactEntries, db) {
|
||||||
|
let stats = {
|
||||||
|
total: contactEntries.length,
|
||||||
|
success: 0,
|
||||||
|
ids: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let entry of contactEntries) {
|
||||||
|
let contact = this._processContactFields(entry);
|
||||||
|
|
||||||
|
stats.ids[contact.id] = 1;
|
||||||
|
let existing = yield db.promise("getByServiceId", contact.id);
|
||||||
|
if (existing) {
|
||||||
|
yield db.promise("remove", existing._guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the contact contains neither email nor phone number, then it is not
|
||||||
|
// useful in the Loop address book: do not add.
|
||||||
|
if (!("email" in contact) && !("tel" in contact)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield db.promise("add", contact);
|
||||||
|
stats.success++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an XML node to map the appropriate data to MozContact field equivalents.
|
||||||
|
*
|
||||||
|
* @param {XMLDOMNode} entry The contact XML node in Google format to process.
|
||||||
|
* @returns `null` if the contact entry appears to be invalid or an Object containing
|
||||||
|
* all the contact data found in the XML.
|
||||||
|
*/
|
||||||
|
_processContactFields: function(entry) {
|
||||||
|
// Basic fields in the main 'atom' namespace.
|
||||||
|
let contact = extractFieldsFromNode(new Map([
|
||||||
|
["id", "id"],
|
||||||
|
// published: n/a
|
||||||
|
["updated", "updated"]
|
||||||
|
// bday: n/a
|
||||||
|
]), entry);
|
||||||
|
|
||||||
|
// Fields that need to wrapped in an Array.
|
||||||
|
extractFieldsFromNode(new Map([
|
||||||
|
["name", "fullName"],
|
||||||
|
["givenName", "givenName"],
|
||||||
|
["familyName", "familyName"],
|
||||||
|
["additionalName", "additionalName"]
|
||||||
|
]), entry, kNS_GD, contact, true);
|
||||||
|
|
||||||
|
// The 'note' field needs to wrapped in an array, but its source node is not
|
||||||
|
// namespaced.
|
||||||
|
extractFieldsFromNode(new Map([
|
||||||
|
["note", "content"]
|
||||||
|
]), entry, null, contact, true);
|
||||||
|
|
||||||
|
// Process physical, earthly addresses.
|
||||||
|
let addressNodes = entry.getElementsByTagNameNS(kNS_GD, "structuredPostalAddress");
|
||||||
|
if (addressNodes.length) {
|
||||||
|
contact.adr = [];
|
||||||
|
for (let [,addressNode] of Iterator(addressNodes)) {
|
||||||
|
let adr = extractFieldsFromNode(new Map([
|
||||||
|
["countryName", "country"],
|
||||||
|
["locality", "city"],
|
||||||
|
["postalCode", "postcode"],
|
||||||
|
["region", "region"],
|
||||||
|
["streetAddress", "street"]
|
||||||
|
]), addressNode, kNS_GD);
|
||||||
|
if (Object.keys(adr).length) {
|
||||||
|
adr.pref = (addressNode.getAttribute("primary") == "true");
|
||||||
|
adr.type = [getFieldType(addressNode)];
|
||||||
|
contacts.adr.push(adr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process email addresses.
|
||||||
|
let emailNodes = entry.getElementsByTagNameNS(kNS_GD, "email");
|
||||||
|
if (emailNodes.length) {
|
||||||
|
contact.email = [];
|
||||||
|
for (let [,emailNode] of Iterator(emailNodes)) {
|
||||||
|
contact.email.push({
|
||||||
|
pref: (emailNode.getAttribute("primary") == "true"),
|
||||||
|
type: [getFieldType(emailNode)],
|
||||||
|
value: emailNode.getAttribute("address")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process telephone numbers.
|
||||||
|
let phoneNodes = entry.getElementsByTagNameNS(kNS_GD, "phoneNumber");
|
||||||
|
if (phoneNodes.length) {
|
||||||
|
contact.tel = [];
|
||||||
|
for (let [,phoneNode] of Iterator(phoneNodes)) {
|
||||||
|
contact.tel.push({
|
||||||
|
pref: (phoneNode.getAttribute("primary") == "true"),
|
||||||
|
type: [getFieldType(phoneNode)],
|
||||||
|
value: phoneNode.firstChild.nodeValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let orgNodes = entry.getElementsByTagNameNS(kNS_GD, "organization");
|
||||||
|
if (orgNodes.length) {
|
||||||
|
contact.org = [];
|
||||||
|
contact.jobTitle = [];
|
||||||
|
for (let [,orgNode] of Iterator(orgNodes)) {
|
||||||
|
contact.org.push(orgNode.getElementsByTagNameNS(kNS_GD, "orgName")[0].firstChild.nodeValue);
|
||||||
|
contact.jobTitle.push(orgNode.getElementsByTagNameNS(kNS_GD, "orgTitle")[0].firstChild.nodeValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contact.category = ["google"];
|
||||||
|
|
||||||
|
// Basic sanity checking: make sure the name field isn't empty
|
||||||
|
if (!("name" in contact) || contact.name[0].length == 0) {
|
||||||
|
if (("familyName" in contact) && ("givenName" in contact)) {
|
||||||
|
// First, try to synthesize a full name from the name fields.
|
||||||
|
// Ordering is culturally sensitive, but we don't have
|
||||||
|
// cultural origin information available here. The best we
|
||||||
|
// can really do is "family, given additional"
|
||||||
|
contact.name = [contact.familyName[0] + ", " + contact.givenName[0]];
|
||||||
|
if (("additionalName" in contact)) {
|
||||||
|
contact.name[0] += " " + contact.additionalName[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let profileTitle = extractFieldsFromNode(new Map([["title", "title"]]), entry);
|
||||||
|
if (("title" in profileTitle)) {
|
||||||
|
contact.name = [profileTitle.title];
|
||||||
|
} else if ("familyName" in contact) {
|
||||||
|
contact.name = [contact.familyName[0]];
|
||||||
|
} else if ("givenName" in contact) {
|
||||||
|
contact.name = [contact.givenName[0]];
|
||||||
|
} else if ("org" in contact) {
|
||||||
|
contact.name = [contact.org[0]];
|
||||||
|
} else {
|
||||||
|
let email;
|
||||||
|
try {
|
||||||
|
email = getPreferred(contact);
|
||||||
|
} catch (ex) {}
|
||||||
|
if (email) {
|
||||||
|
contact.name = [email.value];
|
||||||
|
} else {
|
||||||
|
let tel;
|
||||||
|
try {
|
||||||
|
tel = getPreferred(contact, "phone");
|
||||||
|
} catch (ex) {}
|
||||||
|
if (tel) {
|
||||||
|
contact.name = [tel.value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contact;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all contacts from the database that are not present anymore in the
|
||||||
|
* remote data-source.
|
||||||
|
*
|
||||||
|
* @param {Object} ids Map of IDs collected earlier of all the contacts
|
||||||
|
* that are available on the remote data-source
|
||||||
|
* @param {LoopContacts} db Instance of the LoopContacts database object, which
|
||||||
|
* will store the newly found contacts
|
||||||
|
*/
|
||||||
|
_purgeContacts: Task.async(function* (ids, db) {
|
||||||
|
let contacts = yield db.promise("getAll");
|
||||||
|
let profileId = "https://www.google.com/m8/feeds/contacts/" + encodeURIComponent(gProfileId);
|
||||||
|
let processed = 0;
|
||||||
|
|
||||||
|
for (let [guid, contact] of Iterator(contacts)) {
|
||||||
|
if (++processed % kContactsChunkSize === 0) {
|
||||||
|
// Skip a beat every time we processed a chunk.
|
||||||
|
yield new Promise(resolve => Services.tm.currentThread.dispatch(resolve,
|
||||||
|
Ci.nsIThread.DISPATCH_NORMAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contact.id.indexOf(profileId) >= 0 && !ids[contact.id]) {
|
||||||
|
yield db.promise("remove", guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
@ -10,8 +10,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "console",
|
|||||||
"resource://gre/modules/devtools/Console.jsm");
|
"resource://gre/modules/devtools/Console.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
|
XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
|
||||||
"resource:///modules/loop/LoopStorage.jsm");
|
"resource:///modules/loop/LoopStorage.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||||
|
"resource://gre/modules/Promise.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "CardDavImporter",
|
XPCOMUtils.defineLazyModuleGetter(this, "CardDavImporter",
|
||||||
"resource:///modules/loop/CardDavImporter.jsm");
|
"resource:///modules/loop/CardDavImporter.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "GoogleImporter",
|
||||||
|
"resource:///modules/loop/GoogleImporter.jsm");
|
||||||
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
|
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
|
||||||
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
|
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
|
||||||
return new EventEmitter();
|
return new EventEmitter();
|
||||||
@ -324,7 +328,8 @@ let LoopContactsInternal = Object.freeze({
|
|||||||
* Map of contact importer names to instances
|
* Map of contact importer names to instances
|
||||||
*/
|
*/
|
||||||
_importServices: {
|
_importServices: {
|
||||||
"carddav": new CardDavImporter()
|
"carddav": new CardDavImporter(),
|
||||||
|
"google": new GoogleImporter()
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -770,7 +775,7 @@ let LoopContactsInternal = Object.freeze({
|
|||||||
* `Error` object or `null`. The second argument will
|
* `Error` object or `null`. The second argument will
|
||||||
* be the result of the operation, if successfull.
|
* be the result of the operation, if successfull.
|
||||||
*/
|
*/
|
||||||
startImport: function(options, callback) {
|
startImport: function(options, windowRef, callback) {
|
||||||
if (!("service" in options)) {
|
if (!("service" in options)) {
|
||||||
callback(new Error("No import service specified in options"));
|
callback(new Error("No import service specified in options"));
|
||||||
return;
|
return;
|
||||||
@ -779,7 +784,8 @@ let LoopContactsInternal = Object.freeze({
|
|||||||
callback(new Error("Unknown import service specified: " + options.service));
|
callback(new Error("Unknown import service specified: " + options.service));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._importServices[options.service].startImport(options, callback, this);
|
this._importServices[options.service].startImport(options, callback,
|
||||||
|
LoopContacts, windowRef);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -858,14 +864,26 @@ this.LoopContacts = Object.freeze({
|
|||||||
return LoopContactsInternal.unblock(guid, callback);
|
return LoopContactsInternal.unblock(guid, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
startImport: function(options, callback) {
|
startImport: function(options, windowRef, callback) {
|
||||||
return LoopContactsInternal.startImport(options, callback);
|
return LoopContactsInternal.startImport(options, windowRef, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
search: function(query, callback) {
|
search: function(query, callback) {
|
||||||
return LoopContactsInternal.search(query, callback);
|
return LoopContactsInternal.search(query, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
promise: function(method, ...params) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this[method](...params, (error, result) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
on: (...params) => eventEmitter.on(...params),
|
on: (...params) => eventEmitter.on(...params),
|
||||||
|
|
||||||
once: (...params) => eventEmitter.once(...params),
|
once: (...params) => eventEmitter.once(...params),
|
||||||
|
@ -97,15 +97,6 @@ const injectObjectAPI = function(api, targetWindow) {
|
|||||||
return contentObj;
|
return contentObj;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the two-digit hexadecimal code for a byte
|
|
||||||
*
|
|
||||||
* @param {byte} charCode
|
|
||||||
*/
|
|
||||||
const toHexString = function(charCode) {
|
|
||||||
return ("0" + charCode.toString(16)).slice(-2);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject the loop API into the given window. The caller must be sure the
|
* Inject the loop API into the given window. The caller must be sure the
|
||||||
* window is a loop content window (eg, a panel, chatwindow, or similar).
|
* window is a loop content window (eg, a panel, chatwindow, or similar).
|
||||||
@ -212,6 +203,25 @@ function injectLoopAPI(targetWindow) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a list of (new) contacts from an external data source.
|
||||||
|
*
|
||||||
|
* @param {Object} options Property bag of options for the importer
|
||||||
|
* @param {Function} callback Function that will be invoked once the operation
|
||||||
|
* finished. The first argument passed will be an
|
||||||
|
* `Error` object or `null`. The second argument will
|
||||||
|
* be the result of the operation, if successfull.
|
||||||
|
*/
|
||||||
|
startImport: {
|
||||||
|
enumerable: true,
|
||||||
|
writable: true,
|
||||||
|
value: function(options, callback) {
|
||||||
|
LoopContacts.startImport(options, getChromeWindow(targetWindow), function(...results) {
|
||||||
|
callback(...[cloneValueInto(r, targetWindow) for (r of results)]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns translated strings associated with an element. Designed
|
* Returns translated strings associated with an element. Designed
|
||||||
* for use with l10n.js
|
* for use with l10n.js
|
||||||
@ -553,42 +563,6 @@ function injectLoopAPI(targetWindow) {
|
|||||||
return MozLoopService.generateUUID();
|
return MozLoopService.generateUUID();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Compose a URL pointing to the location of an avatar by email address.
|
|
||||||
* At the moment we use the Gravatar service to match email addresses with
|
|
||||||
* avatars. This might change in the future as avatars might come from another
|
|
||||||
* source.
|
|
||||||
*
|
|
||||||
* @param {String} emailAddress Users' email address
|
|
||||||
* @param {Number} size Size of the avatar image to return in pixels.
|
|
||||||
* Optional. Default value: 40.
|
|
||||||
* @return the URL pointing to an avatar matching the provided email address.
|
|
||||||
*/
|
|
||||||
getUserAvatar: {
|
|
||||||
enumerable: true,
|
|
||||||
writable: true,
|
|
||||||
value: function(emailAddress, size = 40) {
|
|
||||||
if (!emailAddress) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the MD5 dance.
|
|
||||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
|
||||||
.createInstance(Ci.nsICryptoHash);
|
|
||||||
hasher.init(Ci.nsICryptoHash.MD5);
|
|
||||||
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
|
|
||||||
.createInstance(Ci.nsIStringInputStream);
|
|
||||||
stringStream.data = emailAddress.trim().toLowerCase();
|
|
||||||
hasher.updateFromStream(stringStream, -1);
|
|
||||||
let hash = hasher.finish(false);
|
|
||||||
// Convert the binary hash data to a hex string.
|
|
||||||
let md5Email = [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
|
|
||||||
|
|
||||||
// Compose the Gravatar URL.
|
|
||||||
return "http://www.gravatar.com/avatar/" + md5Email + ".jpg?default=blank&s=" + size;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function onStatusChanged(aSubject, aTopic, aData) {
|
function onStatusChanged(aSubject, aTopic, aData) {
|
||||||
|
@ -167,7 +167,7 @@ let MozLoopPushHandler = {
|
|||||||
switch (msg.status) {
|
switch (msg.status) {
|
||||||
case 200:
|
case 200:
|
||||||
this._retryEnd(); // reset retry mechanism
|
this._retryEnd(); // reset retry mechanism
|
||||||
this.registered = true;
|
this.registered = true;
|
||||||
if (this.pushUrl !== msg.pushEndpoint) {
|
if (this.pushUrl !== msg.pushEndpoint) {
|
||||||
this.pushUrl = msg.pushEndpoint;
|
this.pushUrl = msg.pushEndpoint;
|
||||||
this._registerCallback(null, this.pushUrl);
|
this._registerCallback(null, this.pushUrl);
|
||||||
@ -181,11 +181,11 @@ let MozLoopPushHandler = {
|
|||||||
|
|
||||||
case 409:
|
case 409:
|
||||||
this._registerCallback("error: PushServer ChannelID already in use");
|
this._registerCallback("error: PushServer ChannelID already in use");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
this._registerCallback("error: PushServer registration failure, status = " + msg.status);
|
this._registerCallback("error: PushServer registration failure, status = " + msg.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -131,6 +131,16 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
document.body.removeEventListener("click", this._onBodyClick);
|
document.body.removeEventListener("click", this._onBodyClick);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentShouldUpdate: function(nextProps, nextState) {
|
||||||
|
let currContact = this.props.contact;
|
||||||
|
let nextContact = nextProps.contact;
|
||||||
|
return (
|
||||||
|
currContact.name[0] !== nextContact.name[0] ||
|
||||||
|
currContact.blocked !== nextContact.blocked ||
|
||||||
|
this.getPreferredEmail(currContact).value !== this.getPreferredEmail(nextContact).value
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
handleAction: function(actionName) {
|
handleAction: function(actionName) {
|
||||||
if (this.props.handleContactAction) {
|
if (this.props.handleContactAction) {
|
||||||
this.props.handleContactAction(this.props.contact, actionName);
|
this.props.handleContactAction(this.props.contact, actionName);
|
||||||
@ -149,19 +159,20 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getPreferredEmail: function() {
|
getPreferredEmail: function(contact = this.props.contact) {
|
||||||
// The model currently does not enforce a name to be present, but we're
|
let email;
|
||||||
// going to assume it is awaiting more advanced validation of required fields
|
// A contact may not contain email addresses, but only a phone number instead.
|
||||||
// by the model. (See bug 1069918)
|
if (contact.email) {
|
||||||
let email = this.props.contact.email[0];
|
email = contact.email[0];
|
||||||
this.props.contact.email.some(function(address) {
|
contact.email.some(function(address) {
|
||||||
if (address.pref) {
|
if (address.pref) {
|
||||||
email = address;
|
email = address;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
return email;
|
}
|
||||||
|
return email || { value: "" };
|
||||||
},
|
},
|
||||||
|
|
||||||
canEdit: function() {
|
canEdit: function() {
|
||||||
@ -181,9 +192,7 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
React.DOM.li({className: contactCSSClass, onMouseLeave: this.hideDropdownMenu},
|
React.DOM.li({className: contactCSSClass, onMouseLeave: this.hideDropdownMenu},
|
||||||
React.DOM.div({className: "avatar"},
|
React.DOM.div({className: "avatar"}),
|
||||||
React.DOM.img({src: navigator.mozLoop.getUserAvatar(email.value)})
|
|
||||||
),
|
|
||||||
React.DOM.div({className: "details"},
|
React.DOM.div({className: "details"},
|
||||||
React.DOM.div({className: "username"}, React.DOM.strong(null, names.firstName), " ", names.lastName,
|
React.DOM.div({className: "username"}, React.DOM.strong(null, names.firstName), " ", names.lastName,
|
||||||
React.DOM.i({className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}),
|
React.DOM.i({className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}),
|
||||||
@ -211,7 +220,8 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
const ContactsList = React.createClass({displayName: 'ContactsList',
|
const ContactsList = React.createClass({displayName: 'ContactsList',
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
contacts: {}
|
contacts: {},
|
||||||
|
importBusy: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -227,11 +237,12 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
// circumvent blocking the main event loop.
|
// circumvent blocking the main event loop.
|
||||||
let addContactsInChunks = () => {
|
let addContactsInChunks = () => {
|
||||||
contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
|
contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
|
||||||
this.handleContactAddOrUpdate(contact);
|
this.handleContactAddOrUpdate(contact, false);
|
||||||
});
|
});
|
||||||
if (contacts.length) {
|
if (contacts.length) {
|
||||||
setTimeout(addContactsInChunks, 0);
|
setTimeout(addContactsInChunks, 0);
|
||||||
}
|
}
|
||||||
|
this.forceUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
addContactsInChunks(contacts);
|
addContactsInChunks(contacts);
|
||||||
@ -252,11 +263,13 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContactAddOrUpdate: function(contact) {
|
handleContactAddOrUpdate: function(contact, render = true) {
|
||||||
let contacts = this.state.contacts;
|
let contacts = this.state.contacts;
|
||||||
let guid = String(contact._guid);
|
let guid = String(contact._guid);
|
||||||
contacts[guid] = contact;
|
contacts[guid] = contact;
|
||||||
this.setState({});
|
if (render) {
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContactRemove: function(contact) {
|
handleContactRemove: function(contact) {
|
||||||
@ -266,7 +279,7 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
delete contacts[guid];
|
delete contacts[guid];
|
||||||
this.setState({});
|
this.forceUpdate();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContactRemoveAll: function() {
|
handleContactRemoveAll: function() {
|
||||||
@ -274,6 +287,16 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleImportButtonClick: function() {
|
handleImportButtonClick: function() {
|
||||||
|
this.setState({ importBusy: true });
|
||||||
|
navigator.mozLoop.startImport({
|
||||||
|
service: "google"
|
||||||
|
}, (err, stats) => {
|
||||||
|
this.setState({ importBusy: false });
|
||||||
|
// TODO: bug 1076764 - proper error and success reporting.
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAddContactButtonClick: function() {
|
handleAddContactButtonClick: function() {
|
||||||
@ -321,12 +344,15 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
return contact.blocked ? "blocked" : "available";
|
return contact.blocked ? "blocked" : "available";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: bug 1076767 - add a spinner whilst importing contacts.
|
||||||
return (
|
return (
|
||||||
React.DOM.div(null,
|
React.DOM.div(null,
|
||||||
React.DOM.div({className: "content-area"},
|
React.DOM.div({className: "content-area"},
|
||||||
ButtonGroup(null,
|
ButtonGroup(null,
|
||||||
Button({caption: mozL10n.get("import_contacts_button"),
|
Button({caption: this.state.importBusy
|
||||||
disabled: true,
|
? mozL10n.get("importing_contacts_progress_button")
|
||||||
|
: mozL10n.get("import_contacts_button"),
|
||||||
|
disabled: this.state.importBusy,
|
||||||
onClick: this.handleImportButtonClick}),
|
onClick: this.handleImportButtonClick}),
|
||||||
Button({caption: mozL10n.get("new_contact_button"),
|
Button({caption: mozL10n.get("new_contact_button"),
|
||||||
onClick: this.handleAddContactButtonClick})
|
onClick: this.handleAddContactButtonClick})
|
||||||
|
@ -131,6 +131,16 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
document.body.removeEventListener("click", this._onBodyClick);
|
document.body.removeEventListener("click", this._onBodyClick);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentShouldUpdate: function(nextProps, nextState) {
|
||||||
|
let currContact = this.props.contact;
|
||||||
|
let nextContact = nextProps.contact;
|
||||||
|
return (
|
||||||
|
currContact.name[0] !== nextContact.name[0] ||
|
||||||
|
currContact.blocked !== nextContact.blocked ||
|
||||||
|
this.getPreferredEmail(currContact).value !== this.getPreferredEmail(nextContact).value
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
handleAction: function(actionName) {
|
handleAction: function(actionName) {
|
||||||
if (this.props.handleContactAction) {
|
if (this.props.handleContactAction) {
|
||||||
this.props.handleContactAction(this.props.contact, actionName);
|
this.props.handleContactAction(this.props.contact, actionName);
|
||||||
@ -149,19 +159,20 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getPreferredEmail: function() {
|
getPreferredEmail: function(contact = this.props.contact) {
|
||||||
// The model currently does not enforce a name to be present, but we're
|
let email;
|
||||||
// going to assume it is awaiting more advanced validation of required fields
|
// A contact may not contain email addresses, but only a phone number instead.
|
||||||
// by the model. (See bug 1069918)
|
if (contact.email) {
|
||||||
let email = this.props.contact.email[0];
|
email = contact.email[0];
|
||||||
this.props.contact.email.some(function(address) {
|
contact.email.some(function(address) {
|
||||||
if (address.pref) {
|
if (address.pref) {
|
||||||
email = address;
|
email = address;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
return email;
|
}
|
||||||
|
return email || { value: "" };
|
||||||
},
|
},
|
||||||
|
|
||||||
canEdit: function() {
|
canEdit: function() {
|
||||||
@ -181,9 +192,7 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={contactCSSClass} onMouseLeave={this.hideDropdownMenu}>
|
<li className={contactCSSClass} onMouseLeave={this.hideDropdownMenu}>
|
||||||
<div className="avatar">
|
<div className="avatar" />
|
||||||
<img src={navigator.mozLoop.getUserAvatar(email.value)} />
|
|
||||||
</div>
|
|
||||||
<div className="details">
|
<div className="details">
|
||||||
<div className="username"><strong>{names.firstName}</strong> {names.lastName}
|
<div className="username"><strong>{names.firstName}</strong> {names.lastName}
|
||||||
<i className={cx({"icon icon-google": this.props.contact.category[0] == "google"})} />
|
<i className={cx({"icon icon-google": this.props.contact.category[0] == "google"})} />
|
||||||
@ -211,7 +220,8 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
const ContactsList = React.createClass({
|
const ContactsList = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
contacts: {}
|
contacts: {},
|
||||||
|
importBusy: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -227,11 +237,12 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
// circumvent blocking the main event loop.
|
// circumvent blocking the main event loop.
|
||||||
let addContactsInChunks = () => {
|
let addContactsInChunks = () => {
|
||||||
contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
|
contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
|
||||||
this.handleContactAddOrUpdate(contact);
|
this.handleContactAddOrUpdate(contact, false);
|
||||||
});
|
});
|
||||||
if (contacts.length) {
|
if (contacts.length) {
|
||||||
setTimeout(addContactsInChunks, 0);
|
setTimeout(addContactsInChunks, 0);
|
||||||
}
|
}
|
||||||
|
this.forceUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
addContactsInChunks(contacts);
|
addContactsInChunks(contacts);
|
||||||
@ -252,11 +263,13 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContactAddOrUpdate: function(contact) {
|
handleContactAddOrUpdate: function(contact, render = true) {
|
||||||
let contacts = this.state.contacts;
|
let contacts = this.state.contacts;
|
||||||
let guid = String(contact._guid);
|
let guid = String(contact._guid);
|
||||||
contacts[guid] = contact;
|
contacts[guid] = contact;
|
||||||
this.setState({});
|
if (render) {
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContactRemove: function(contact) {
|
handleContactRemove: function(contact) {
|
||||||
@ -266,7 +279,7 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
delete contacts[guid];
|
delete contacts[guid];
|
||||||
this.setState({});
|
this.forceUpdate();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContactRemoveAll: function() {
|
handleContactRemoveAll: function() {
|
||||||
@ -274,6 +287,16 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleImportButtonClick: function() {
|
handleImportButtonClick: function() {
|
||||||
|
this.setState({ importBusy: true });
|
||||||
|
navigator.mozLoop.startImport({
|
||||||
|
service: "google"
|
||||||
|
}, (err, stats) => {
|
||||||
|
this.setState({ importBusy: false });
|
||||||
|
// TODO: bug 1076764 - proper error and success reporting.
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAddContactButtonClick: function() {
|
handleAddContactButtonClick: function() {
|
||||||
@ -321,12 +344,15 @@ loop.contacts = (function(_, mozL10n) {
|
|||||||
return contact.blocked ? "blocked" : "available";
|
return contact.blocked ? "blocked" : "available";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: bug 1076767 - add a spinner whilst importing contacts.
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="content-area">
|
<div className="content-area">
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button caption={mozL10n.get("import_contacts_button")}
|
<Button caption={this.state.importBusy
|
||||||
disabled
|
? mozL10n.get("importing_contacts_progress_button")
|
||||||
|
: mozL10n.get("import_contacts_button")}
|
||||||
|
disabled={this.state.importBusy}
|
||||||
onClick={this.handleImportButtonClick} />
|
onClick={this.handleImportButtonClick} />
|
||||||
<Button caption={mozL10n.get("new_contact_button")}
|
<Button caption={mozL10n.get("new_contact_button")}
|
||||||
onClick={this.handleAddContactButtonClick} />
|
onClick={this.handleAddContactButtonClick} />
|
||||||
|
@ -12,10 +12,12 @@ loop.conversation = (function(mozL10n) {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
|
var sharedMixins = loop.shared.mixins;
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
||||||
|
|
||||||
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
|
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
|
||||||
|
mixins: [sharedMixins.DropdownMenuMixin],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
model: React.PropTypes.object.isRequired,
|
model: React.PropTypes.object.isRequired,
|
||||||
@ -24,25 +26,11 @@ loop.conversation = (function(mozL10n) {
|
|||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
showDeclineMenu: false,
|
showMenu: false,
|
||||||
video: true
|
video: true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {showDeclineMenu: this.props.showDeclineMenu};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
window.addEventListener("click", this.clickHandler);
|
|
||||||
window.addEventListener("blur", this._hideDeclineMenu);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
window.removeEventListener("click", this.clickHandler);
|
|
||||||
window.removeEventListener("blur", this._hideDeclineMenu);
|
|
||||||
},
|
|
||||||
|
|
||||||
clickHandler: function(e) {
|
clickHandler: function(e) {
|
||||||
var target = e.target;
|
var target = e.target;
|
||||||
if (!target.classList.contains('btn-chevron')) {
|
if (!target.classList.contains('btn-chevron')) {
|
||||||
@ -104,7 +92,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
var dropdownMenuClassesDecline = React.addons.classSet({
|
var dropdownMenuClassesDecline = React.addons.classSet({
|
||||||
"native-dropdown-menu": true,
|
"native-dropdown-menu": true,
|
||||||
"conversation-window-dropdown": true,
|
"conversation-window-dropdown": true,
|
||||||
"visually-hidden": !this.state.showDeclineMenu
|
"visually-hidden": !this.state.showMenu
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
React.DOM.div({className: "call-window"},
|
React.DOM.div({className: "call-window"},
|
||||||
@ -117,13 +105,11 @@ loop.conversation = (function(mozL10n) {
|
|||||||
React.DOM.div({className: "btn-group-chevron"},
|
React.DOM.div({className: "btn-group-chevron"},
|
||||||
React.DOM.div({className: "btn-group"},
|
React.DOM.div({className: "btn-group"},
|
||||||
|
|
||||||
React.DOM.button({className: "btn btn-error btn-decline",
|
React.DOM.button({className: "btn btn-decline",
|
||||||
onClick: this._handleDecline},
|
onClick: this._handleDecline},
|
||||||
mozL10n.get("incoming_call_cancel_button")
|
mozL10n.get("incoming_call_cancel_button")
|
||||||
),
|
),
|
||||||
React.DOM.div({className: "btn-chevron",
|
React.DOM.div({className: "btn-chevron", onClick: this.toggleDropdownMenu})
|
||||||
onClick: this._toggleDeclineMenu}
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
|
||||||
React.DOM.ul({className: dropdownMenuClassesDecline},
|
React.DOM.ul({className: dropdownMenuClassesDecline},
|
||||||
|
@ -49,6 +49,20 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact > .details {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact:hover > .details {
|
||||||
|
/* Hovering the contact shows the icons/ buttons, which takes up horizontal
|
||||||
|
* space. This causes the fixed-size avatar to resize horizontally, so we assign
|
||||||
|
* a flex value equivalent to the maximum pixel value to avoid the resizing
|
||||||
|
* to happen. Consider this a hack. */
|
||||||
|
flex: 190;
|
||||||
|
}
|
||||||
|
|
||||||
.contact > .avatar {
|
.contact > .avatar {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
@ -14,6 +14,7 @@ BROWSER_CHROME_MANIFESTS += [
|
|||||||
|
|
||||||
EXTRA_JS_MODULES.loop += [
|
EXTRA_JS_MODULES.loop += [
|
||||||
'CardDavImporter.jsm',
|
'CardDavImporter.jsm',
|
||||||
|
'GoogleImporter.jsm',
|
||||||
'LoopContacts.jsm',
|
'LoopContacts.jsm',
|
||||||
'LoopStorage.jsm',
|
'LoopStorage.jsm',
|
||||||
'MozLoopAPI.jsm',
|
'MozLoopAPI.jsm',
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
support-files =
|
support-files =
|
||||||
|
fixtures/google_auth.txt
|
||||||
|
fixtures/google_contacts.txt
|
||||||
|
fixtures/google_token.txt
|
||||||
|
google_service.sjs
|
||||||
head.js
|
head.js
|
||||||
loop_fxa.sjs
|
loop_fxa.sjs
|
||||||
../../../../base/content/test/general/browser_fxa_oauth.html
|
../../../../base/content/test/general/browser_fxa_oauth.html
|
||||||
|
|
||||||
[browser_CardDavImporter.js]
|
[browser_CardDavImporter.js]
|
||||||
[browser_fxa_login.js]
|
[browser_fxa_login.js]
|
||||||
|
[browser_GoogleImporter.js]
|
||||||
skip-if = e10s
|
skip-if = e10s
|
||||||
[browser_loop_fxa_server.js]
|
[browser_loop_fxa_server.js]
|
||||||
[browser_LoopContacts.js]
|
[browser_LoopContacts.js]
|
||||||
|
@ -3,46 +3,6 @@
|
|||||||
|
|
||||||
const {CardDavImporter} = Cu.import("resource:///modules/loop/CardDavImporter.jsm", {});
|
const {CardDavImporter} = Cu.import("resource:///modules/loop/CardDavImporter.jsm", {});
|
||||||
|
|
||||||
const mockDb = {
|
|
||||||
_store: { },
|
|
||||||
_next_guid: 1,
|
|
||||||
|
|
||||||
add: function(details, callback) {
|
|
||||||
if (!("id" in details)) {
|
|
||||||
callback(new Error("No 'id' field present"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
details._guid = this._next_guid++;
|
|
||||||
this._store[details._guid] = details;
|
|
||||||
callback(null, details);
|
|
||||||
},
|
|
||||||
remove: function(guid, callback) {
|
|
||||||
if (!guid in this._store) {
|
|
||||||
callback(new Error("Could not find _guid '" + guid + "' in database"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
delete this._store[guid];
|
|
||||||
callback(null);
|
|
||||||
},
|
|
||||||
get: function(guid, callback) {
|
|
||||||
callback(null, this._store[guid]);
|
|
||||||
},
|
|
||||||
getByServiceId: function(serviceId, callback) {
|
|
||||||
for (let guid in this._store) {
|
|
||||||
if (serviceId === this._store[guid].id) {
|
|
||||||
callback(null, this._store[guid]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback(null, null);
|
|
||||||
},
|
|
||||||
removeAll: function(callback) {
|
|
||||||
this._store = {};
|
|
||||||
this._next_guid = 1;
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const kAuth = {
|
const kAuth = {
|
||||||
"method": "basic",
|
"method": "basic",
|
||||||
"user": "username",
|
"user": "username",
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
const {GoogleImporter} = Cu.import("resource:///modules/loop/GoogleImporter.jsm", {});
|
||||||
|
|
||||||
|
let importer = new GoogleImporter();
|
||||||
|
|
||||||
|
function promiseImport() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
importer.startImport({}, function(err, stats) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(stats);
|
||||||
|
}
|
||||||
|
}, mockDb, window);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(function* test_GoogleImport() {
|
||||||
|
let stats;
|
||||||
|
// An error may throw and the test will fail when that happens.
|
||||||
|
stats = yield promiseImport();
|
||||||
|
|
||||||
|
// Assert the world.
|
||||||
|
Assert.equal(stats.total, 5, "Five contacts should get processed");
|
||||||
|
Assert.equal(stats.success, 5, "Five contacts should be imported");
|
||||||
|
|
||||||
|
yield promiseImport();
|
||||||
|
Assert.equal(Object.keys(mockDb._store).length, 5, "Database should contain only five contact after reimport");
|
||||||
|
|
||||||
|
let c = mockDb._store[mockDb._next_guid - 5];
|
||||||
|
Assert.equal(c.name[0], "John Smith", "Full name should match");
|
||||||
|
Assert.equal(c.givenName[0], "John", "Given name should match");
|
||||||
|
Assert.equal(c.familyName[0], "Smith", "Family name should match");
|
||||||
|
Assert.equal(c.email[0].type, "other", "Email type should match");
|
||||||
|
Assert.equal(c.email[0].value, "john.smith@example.com", "Email should match");
|
||||||
|
Assert.equal(c.email[0].pref, true, "Pref should match");
|
||||||
|
Assert.equal(c.category[0], "google", "Category should match");
|
||||||
|
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/0", "UID should match and be scoped to provider");
|
||||||
|
|
||||||
|
c = mockDb._store[mockDb._next_guid - 4];
|
||||||
|
Assert.equal(c.name[0], "Jane Smith", "Full name should match");
|
||||||
|
Assert.equal(c.givenName[0], "Jane", "Given name should match");
|
||||||
|
Assert.equal(c.familyName[0], "Smith", "Family name should match");
|
||||||
|
Assert.equal(c.email[0].type, "other", "Email type should match");
|
||||||
|
Assert.equal(c.email[0].value, "jane.smith@example.com", "Email should match");
|
||||||
|
Assert.equal(c.email[0].pref, true, "Pref should match");
|
||||||
|
Assert.equal(c.category[0], "google", "Category should match");
|
||||||
|
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/1", "UID should match and be scoped to provider");
|
||||||
|
|
||||||
|
c = mockDb._store[mockDb._next_guid - 3];
|
||||||
|
Assert.equal(c.name[0], "Davy Randall Jones", "Full name should match");
|
||||||
|
Assert.equal(c.givenName[0], "Davy Randall", "Given name should match");
|
||||||
|
Assert.equal(c.familyName[0], "Jones", "Family name should match");
|
||||||
|
Assert.equal(c.email[0].type, "other", "Email type should match");
|
||||||
|
Assert.equal(c.email[0].value, "davy.jones@example.com", "Email should match");
|
||||||
|
Assert.equal(c.email[0].pref, true, "Pref should match");
|
||||||
|
Assert.equal(c.category[0], "google", "Category should match");
|
||||||
|
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/2", "UID should match and be scoped to provider");
|
||||||
|
|
||||||
|
c = mockDb._store[mockDb._next_guid - 2];
|
||||||
|
Assert.equal(c.name[0], "noname@example.com", "Full name should match");
|
||||||
|
Assert.equal(c.email[0].type, "other", "Email type should match");
|
||||||
|
Assert.equal(c.email[0].value, "noname@example.com", "Email should match");
|
||||||
|
Assert.equal(c.email[0].pref, true, "Pref should match");
|
||||||
|
Assert.equal(c.category[0], "google", "Category should match");
|
||||||
|
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/3", "UID should match and be scoped to provider");
|
||||||
|
|
||||||
|
c = mockDb._store[mockDb._next_guid - 1];
|
||||||
|
Assert.equal(c.name[0], "lycnix", "Full name should match");
|
||||||
|
Assert.equal(c.email[0].type, "other", "Email type should match");
|
||||||
|
Assert.equal(c.email[0].value, "lycnix", "Email should match");
|
||||||
|
Assert.equal(c.email[0].pref, true, "Pref should match");
|
||||||
|
Assert.equal(c.category[0], "google", "Category should match");
|
||||||
|
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/7", "UID should match and be scoped to provider");
|
||||||
|
});
|
@ -0,0 +1,5 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Success code=test-code</title></head>
|
||||||
|
<body>Le Code.</body>
|
||||||
|
</html>
|
@ -0,0 +1,94 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<feed gd:etag="W/"DUQNRHc8cCt7I2A9XRdSF04."" xmlns="http://www.w3.org/2005/Atom" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:gd="http://schemas.google.com/g/2005" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/">
|
||||||
|
<id>tester@mochi.com</id>
|
||||||
|
<updated>2014-09-26T13:16:35.978Z</updated>
|
||||||
|
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
|
||||||
|
<title>Mochi Tester's Contacts</title>
|
||||||
|
<link href="http://www.google.com/" rel="alternate" type="text/html"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full" rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full" rel="http://schemas.google.com/g/2005#post" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/batch" rel="http://schemas.google.com/g/2005#batch" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full?max-results=25" rel="self" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full?start-index=26&max-results=25" rel="next" type="application/atom+xml"/>
|
||||||
|
<author>
|
||||||
|
<name>Mochi Tester</name>
|
||||||
|
<email>tester@mochi.com</email>
|
||||||
|
</author>
|
||||||
|
<generator uri="http://www.google.com/m8/feeds" version="1.0">Contacts</generator>
|
||||||
|
<openSearch:totalResults>25</openSearch:totalResults>
|
||||||
|
<openSearch:startIndex>1</openSearch:startIndex>
|
||||||
|
<openSearch:itemsPerPage>10000000</openSearch:itemsPerPage>
|
||||||
|
<entry gd:etag=""R3YyejRVLit7I2A9WhJWEkkNQwc."">
|
||||||
|
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/0</id>
|
||||||
|
<updated>2012-08-17T23:50:36.892Z</updated>
|
||||||
|
<app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-17T23:50:36.892Z</app:edited>
|
||||||
|
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
|
||||||
|
<title>John Smith</title>
|
||||||
|
<link gd:etag=""Ug92D34SfCt7I2BmLHJTRgVzTlgrJXEAU08."" href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/0" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/0" rel="self" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/0" rel="edit" type="application/atom+xml"/>
|
||||||
|
<gd:name>
|
||||||
|
<gd:fullName>John Smith</gd:fullName>
|
||||||
|
<gd:givenName>John</gd:givenName>
|
||||||
|
<gd:familyName>Smith</gd:familyName>
|
||||||
|
</gd:name>
|
||||||
|
<gd:email address="john.smith@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||||
|
<gContact:website href="http://www.google.com/profiles/109576547678240773721" rel="profile"/>
|
||||||
|
</entry>
|
||||||
|
<entry gd:etag=""R3YyejRVLit7I2A9WhJWEkkNQwc."">
|
||||||
|
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/1</id>
|
||||||
|
<updated>2012-08-17T23:50:36.892Z</updated>
|
||||||
|
<app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-17T23:50:36.892Z</app:edited>
|
||||||
|
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
|
||||||
|
<title>Jane Smith</title>
|
||||||
|
<link gd:etag=""WA9BY1xFWit7I2BhLEkieCxLHEYTGCYuNxo."" href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/1" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/1" rel="self" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/1" rel="edit" type="application/atom+xml"/>
|
||||||
|
<gd:name>
|
||||||
|
<gd:fullName>Jane Smith</gd:fullName>
|
||||||
|
<gd:givenName>Jane</gd:givenName>
|
||||||
|
<gd:familyName>Smith</gd:familyName>
|
||||||
|
</gd:name>
|
||||||
|
<gd:email address="jane.smith@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||||
|
<gContact:website href="http://www.google.com/profiles/112886528199784431028" rel="profile"/>
|
||||||
|
</entry>
|
||||||
|
<entry gd:etag=""R3YyejRVLit7I2A9WhJWEkkNQwc."">
|
||||||
|
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/2</id>
|
||||||
|
<updated>2012-08-17T23:50:36.892Z</updated>
|
||||||
|
<app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-17T23:50:36.892Z</app:edited>
|
||||||
|
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
|
||||||
|
<title>Davy Randall Jones</title>
|
||||||
|
<link gd:etag=""KiV2PkYRfCt7I2BuD1AzEBFxD1VcGjwBUyA."" href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/2" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/2" rel="self" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/2" rel="edit" type="application/atom+xml"/>
|
||||||
|
<gd:name>
|
||||||
|
<gd:fullName>Davy Randall Jones</gd:fullName>
|
||||||
|
<gd:givenName>Davy Randall</gd:givenName>
|
||||||
|
<gd:familyName>Jones</gd:familyName>
|
||||||
|
</gd:name>
|
||||||
|
<gd:email address="davy.jones@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||||
|
<gContact:website href="http://www.google.com/profiles/109710625881478599011" rel="profile"/>
|
||||||
|
</entry>
|
||||||
|
<entry gd:etag=""Q3w7ezVSLit7I2A9WB5WGUkNRgE."">
|
||||||
|
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/3</id>
|
||||||
|
<updated>2007-08-01T05:45:52.203Z</updated>
|
||||||
|
<app:edited xmlns:app="http://www.w3.org/2007/app">2007-08-01T05:45:52.203Z</app:edited>
|
||||||
|
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
|
||||||
|
<title/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/3" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/3" rel="self" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/3" rel="edit" type="application/atom+xml"/>
|
||||||
|
<gd:email address="noname@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||||
|
</entry>
|
||||||
|
<entry gd:etag=""Q3w7ezVSLit7I2A9WB5WGUkNRgE."">
|
||||||
|
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/7</id>
|
||||||
|
<updated>2007-08-01T05:45:52.203Z</updated>
|
||||||
|
<app:edited xmlns:app="http://www.w3.org/2007/app">2007-08-01T05:45:52.203Z</app:edited>
|
||||||
|
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
|
||||||
|
<title/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/7" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/7" rel="self" type="application/atom+xml"/>
|
||||||
|
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/7" rel="edit" type="application/atom+xml"/>
|
||||||
|
<gd:email address="lycnix" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||||
|
</entry>
|
||||||
|
</feed>
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"access_token": "test-token"
|
||||||
|
}
|
147
browser/components/loop/test/mochitest/google_service.sjs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {classes: Cc, interfaces: Ci, Constructor: CC} = Components;
|
||||||
|
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
|
||||||
|
"nsIBinaryInputStream",
|
||||||
|
"setInputStream");
|
||||||
|
|
||||||
|
function handleRequest(req, res) {
|
||||||
|
try {
|
||||||
|
reallyHandleRequest(req, res);
|
||||||
|
} catch (ex) {
|
||||||
|
res.setStatusLine("1.0", 200, "AlmostOK");
|
||||||
|
let msg = "Error handling request: " + ex + "\n" + ex.stack;
|
||||||
|
log(msg);
|
||||||
|
res.write(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(msg) {
|
||||||
|
// dump("GOOGLE-SERVER-MOCK: " + msg + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const kBasePath = "browser/browser/components/loop/test/mochitest/fixtures/";
|
||||||
|
|
||||||
|
const kStatusCodes = {
|
||||||
|
400: "Bad Request",
|
||||||
|
401: "Unauthorized",
|
||||||
|
403: "Forbidden",
|
||||||
|
404: "Not Found",
|
||||||
|
405: "Method Not Allowed",
|
||||||
|
500: "Internal Server Error",
|
||||||
|
501: "Not Implemented",
|
||||||
|
503: "Service Unavailable"
|
||||||
|
};
|
||||||
|
|
||||||
|
function HTTPError(code = 500, message) {
|
||||||
|
this.code = code;
|
||||||
|
this.name = kStatusCodes[code] || "HTTPError";
|
||||||
|
this.message = message || this.name;
|
||||||
|
}
|
||||||
|
HTTPError.prototype = new Error();
|
||||||
|
HTTPError.prototype.constructor = HTTPError;
|
||||||
|
|
||||||
|
function sendError(res, err) {
|
||||||
|
if (!(err instanceof HTTPError)) {
|
||||||
|
err = new HTTPError(typeof err == "number" ? err : 500,
|
||||||
|
err.message || typeof err == "string" ? err : "");
|
||||||
|
}
|
||||||
|
res.setStatusLine("1.1", err.code, err.name);
|
||||||
|
res.write(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseQuery(query, params = {}) {
|
||||||
|
for (let param of query.replace(/^[?&]/, "").split(/(?:&|\?)/)) {
|
||||||
|
param = param.split("=");
|
||||||
|
if (!param[0])
|
||||||
|
continue;
|
||||||
|
params[unescape(param[0])] = unescape(param[1]);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRequestBody(req) {
|
||||||
|
let avail;
|
||||||
|
let bytes = [];
|
||||||
|
let body = new BinaryInputStream(req.bodyInputStream);
|
||||||
|
|
||||||
|
while ((avail = body.available()) > 0)
|
||||||
|
Array.prototype.push.apply(bytes, body.readByteArray(avail));
|
||||||
|
|
||||||
|
return String.fromCharCode.apply(null, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputStream(path) {
|
||||||
|
let file = Cc["@mozilla.org/file/directory_service;1"]
|
||||||
|
.getService(Ci.nsIProperties)
|
||||||
|
.get("CurWorkD", Ci.nsILocalFile);
|
||||||
|
for (let part of path.split("/"))
|
||||||
|
file.append(part);
|
||||||
|
let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||||
|
.createInstance(Ci.nsIFileInputStream);
|
||||||
|
fileStream.init(file, 1, 0, false);
|
||||||
|
return fileStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAuth(req) {
|
||||||
|
if (!req.hasHeader("Authorization"))
|
||||||
|
throw new HTTPError(401, "No Authorization header provided.");
|
||||||
|
|
||||||
|
let auth = req.getHeader("Authorization");
|
||||||
|
if (auth != "Bearer test-token")
|
||||||
|
throw new HTTPError(401, "Invalid Authorization header content: '" + auth + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
function reallyHandleRequest(req, res) {
|
||||||
|
log("method: " + req.method);
|
||||||
|
|
||||||
|
let body = getRequestBody(req);
|
||||||
|
log("body: " + body);
|
||||||
|
|
||||||
|
let contentType = req.hasHeader("Content-Type") ? req.getHeader("Content-Type") : null;
|
||||||
|
log("contentType: " + contentType);
|
||||||
|
|
||||||
|
let params = parseQuery(req.queryString);
|
||||||
|
parseQuery(body, params);
|
||||||
|
log("params: " + JSON.stringify(params));
|
||||||
|
|
||||||
|
// Delegate an authentication request to the correct handler.
|
||||||
|
if ("action" in params) {
|
||||||
|
methodHandlers[params.action](req, res, params);
|
||||||
|
} else {
|
||||||
|
sendError(res, 501);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function respondWithFile(res, fileName, mimeType) {
|
||||||
|
res.setStatusLine("1.1", 200, "OK");
|
||||||
|
res.setHeader("Content-Type", mimeType);
|
||||||
|
|
||||||
|
let inputStream = getInputStream(kBasePath + fileName);
|
||||||
|
res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const methodHandlers = {
|
||||||
|
auth: function(req, res, params) {
|
||||||
|
respondWithFile(res, "google_auth.txt", "text/html");
|
||||||
|
},
|
||||||
|
|
||||||
|
token: function(req, res, params) {
|
||||||
|
respondWithFile(res, "google_token.txt", "application/json");
|
||||||
|
},
|
||||||
|
|
||||||
|
contacts: function(req, res, params) {
|
||||||
|
try {
|
||||||
|
checkAuth(req);
|
||||||
|
} catch (ex) {
|
||||||
|
sendError(res, ex, ex.code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
respondWithFile(res, "google_contacts.txt", "text/xml");
|
||||||
|
}
|
||||||
|
};
|
@ -198,3 +198,51 @@ let mockPushHandler = {
|
|||||||
this._notificationCallback(version);
|
this._notificationCallback(version);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockDb = {
|
||||||
|
_store: { },
|
||||||
|
_next_guid: 1,
|
||||||
|
|
||||||
|
add: function(details, callback) {
|
||||||
|
if (!("id" in details)) {
|
||||||
|
callback(new Error("No 'id' field present"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
details._guid = this._next_guid++;
|
||||||
|
this._store[details._guid] = details;
|
||||||
|
callback(null, details);
|
||||||
|
},
|
||||||
|
remove: function(guid, callback) {
|
||||||
|
if (!guid in this._store) {
|
||||||
|
callback(new Error("Could not find _guid '" + guid + "' in database"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete this._store[guid];
|
||||||
|
callback(null);
|
||||||
|
},
|
||||||
|
getAll: function(callback) {
|
||||||
|
callback(null, this._store);
|
||||||
|
},
|
||||||
|
get: function(guid, callback) {
|
||||||
|
callback(null, this._store[guid]);
|
||||||
|
},
|
||||||
|
getByServiceId: function(serviceId, callback) {
|
||||||
|
for (let guid in this._store) {
|
||||||
|
if (serviceId === this._store[guid].id) {
|
||||||
|
callback(null, this._store[guid]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(null, null);
|
||||||
|
},
|
||||||
|
removeAll: function(callback) {
|
||||||
|
this._store = {};
|
||||||
|
this._next_guid = 1;
|
||||||
|
callback(null);
|
||||||
|
},
|
||||||
|
promise: function(method, ...params) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this[method](...params, (err, res) => err ? reject(err) : resolve(res));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -7,4 +7,4 @@ support-files =
|
|||||||
manifest.webapp
|
manifest.webapp
|
||||||
|
|
||||||
[browser_manifest_editor.js]
|
[browser_manifest_editor.js]
|
||||||
skip-if = os == "linux"
|
skip-if = true # Bug 989169 - Very intermittent, but App Manager about to be removed
|
||||||
|
@ -26,8 +26,11 @@ Bug 901519 - [app manager] data store for connections
|
|||||||
<script type="application/javascript;version=1.8">
|
<script type="application/javascript;version=1.8">
|
||||||
const Cu = Components.utils;
|
const Cu = Components.utils;
|
||||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
DebuggerServer.init(function () { return true; });
|
|
||||||
DebuggerServer.addBrowserActors();
|
if (!DebuggerServer.initialized) {
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
}
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
@ -20,8 +20,11 @@ Bug 901520 - [app manager] data store for device
|
|||||||
<script type="application/javascript;version=1.8">
|
<script type="application/javascript;version=1.8">
|
||||||
const Cu = Components.utils;
|
const Cu = Components.utils;
|
||||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
DebuggerServer.init(function () { return true; });
|
|
||||||
DebuggerServer.addBrowserActors();
|
if (!DebuggerServer.initialized) {
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
}
|
||||||
|
|
||||||
function compare(o1, o2, msg) {
|
function compare(o1, o2, msg) {
|
||||||
is(JSON.stringify(o1), JSON.stringify(o2), msg);
|
is(JSON.stringify(o1), JSON.stringify(o2), msg);
|
||||||
|
@ -20,8 +20,11 @@ Bug 912646 - Closing app toolbox causes phone to disconnect
|
|||||||
const Cu = Components.utils;
|
const Cu = Components.utils;
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
DebuggerServer.init(function () { return true; });
|
|
||||||
DebuggerServer.addBrowserActors();
|
if (!DebuggerServer.initialized) {
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
}
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
@ -15,6 +15,7 @@ support-files =
|
|||||||
code_blackboxing_three.js
|
code_blackboxing_three.js
|
||||||
code_blackboxing_two.js
|
code_blackboxing_two.js
|
||||||
code_breakpoints-break-on-last-line-of-script-on-reload.js
|
code_breakpoints-break-on-last-line-of-script-on-reload.js
|
||||||
|
code_breakpoints-other-tabs.js
|
||||||
code_function-search-01.js
|
code_function-search-01.js
|
||||||
code_function-search-02.js
|
code_function-search-02.js
|
||||||
code_function-search-03.js
|
code_function-search-03.js
|
||||||
@ -42,6 +43,8 @@ support-files =
|
|||||||
doc_binary_search.html
|
doc_binary_search.html
|
||||||
doc_blackboxing.html
|
doc_blackboxing.html
|
||||||
doc_breakpoints-break-on-last-line-of-script-on-reload.html
|
doc_breakpoints-break-on-last-line-of-script-on-reload.html
|
||||||
|
doc_breakpoints-other-tabs.html
|
||||||
|
doc_breakpoints-reload.html
|
||||||
doc_closures.html
|
doc_closures.html
|
||||||
doc_closure-optimized-out.html
|
doc_closure-optimized-out.html
|
||||||
doc_cmd-break.html
|
doc_cmd-break.html
|
||||||
@ -133,7 +136,9 @@ skip-if = os == "mac" || e10s # Bug 895426
|
|||||||
[browser_dbg_breakpoints-editor.js]
|
[browser_dbg_breakpoints-editor.js]
|
||||||
[browser_dbg_breakpoints-highlight.js]
|
[browser_dbg_breakpoints-highlight.js]
|
||||||
[browser_dbg_breakpoints-new-script.js]
|
[browser_dbg_breakpoints-new-script.js]
|
||||||
|
[browser_dbg_breakpoints-other-tabs.js]
|
||||||
[browser_dbg_breakpoints-pane.js]
|
[browser_dbg_breakpoints-pane.js]
|
||||||
|
[browser_dbg_breakpoints-reload.js]
|
||||||
[browser_dbg_chrome-create.js]
|
[browser_dbg_chrome-create.js]
|
||||||
[browser_dbg_chrome-debugging.js]
|
[browser_dbg_chrome-debugging.js]
|
||||||
[browser_dbg_clean-exit-window.js]
|
[browser_dbg_clean-exit-window.js]
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that setting a breakpoint in one tab, doesn't cause another tab at
|
||||||
|
* the same source to pause at that location.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TAB_URL = EXAMPLE_URL + "doc_breakpoints-other-tabs.html";
|
||||||
|
|
||||||
|
let test = Task.async(function* () {
|
||||||
|
const [tab1, debuggee1, panel1] = yield initDebugger(TAB_URL);
|
||||||
|
const [tab2, debuggee2, panel2] = yield initDebugger(TAB_URL);
|
||||||
|
|
||||||
|
yield ensureSourceIs(panel1, "code_breakpoints-other-tabs.js", true);
|
||||||
|
|
||||||
|
const sources = panel1.panelWin.DebuggerView.Sources;
|
||||||
|
|
||||||
|
yield panel1.addBreakpoint({
|
||||||
|
url: sources.selectedValue,
|
||||||
|
line: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
const paused = waitForThreadEvents(panel2, "paused");
|
||||||
|
executeSoon(() => debuggee2.testCase());
|
||||||
|
const packet = yield paused;
|
||||||
|
|
||||||
|
is(packet.why.type, "debuggerStatement",
|
||||||
|
"Should have stopped at the debugger statement, not the other tab's breakpoint");
|
||||||
|
is(packet.frame.where.line, 3,
|
||||||
|
"Should have stopped at line 3 (debugger statement), not line 2 (other tab's breakpoint)");
|
||||||
|
|
||||||
|
yield teardown(panel1);
|
||||||
|
yield resumeDebuggerThenCloseAndFinish(panel2);
|
||||||
|
});
|
@ -0,0 +1,35 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that setting a breakpoint on code that gets run on load, will get
|
||||||
|
* hit when we reload.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TAB_URL = EXAMPLE_URL + "doc_breakpoints-reload.html";
|
||||||
|
|
||||||
|
let test = Task.async(function* () {
|
||||||
|
requestLongerTimeout(4);
|
||||||
|
|
||||||
|
const [tab, debuggee, panel] = yield initDebugger(TAB_URL);
|
||||||
|
|
||||||
|
yield ensureSourceIs(panel, "doc_breakpoints-reload.html", true);
|
||||||
|
|
||||||
|
const sources = panel.panelWin.DebuggerView.Sources;
|
||||||
|
|
||||||
|
yield panel.addBreakpoint({
|
||||||
|
url: sources.selectedValue,
|
||||||
|
line: 10 // "break on me" string
|
||||||
|
});
|
||||||
|
|
||||||
|
const paused = waitForThreadEvents(panel, "paused");
|
||||||
|
reloadActiveTab(panel);
|
||||||
|
const packet = yield paused;
|
||||||
|
|
||||||
|
is(packet.why.type, "breakpoint",
|
||||||
|
"Should have hit the breakpoint after the reload");
|
||||||
|
is(packet.frame.where.line, 10,
|
||||||
|
"Should have stopped at line 10, where we set the breakpoint");
|
||||||
|
|
||||||
|
yield resumeDebuggerThenCloseAndFinish(panel);
|
||||||
|
});
|
@ -0,0 +1,4 @@
|
|||||||
|
function testCase() {
|
||||||
|
var foo = "break on me";
|
||||||
|
debugger;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
<!-- Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Debugger Breakpoints Other Tabs Test Page</title>
|
||||||
|
</head>
|
||||||
|
<script src="code_breakpoints-other-tabs.js"></script>
|
12
browser/devtools/debugger/test/doc_breakpoints-reload.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!-- Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Debugger Breakpoints Other Tabs Test Page</title>
|
||||||
|
</head>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
window.foo = "break on me";
|
||||||
|
}());
|
||||||
|
</script>
|
@ -121,7 +121,7 @@ ProfilerConnection.prototype = {
|
|||||||
// Older Gecko versions don't have an existing implementation, in which case
|
// Older Gecko versions don't have an existing implementation, in which case
|
||||||
// all the methods we need can be easily mocked.
|
// all the methods we need can be easily mocked.
|
||||||
if (this._target.form && this._target.form.framerateActor) {
|
if (this._target.form && this._target.form.framerateActor) {
|
||||||
this._framerate = new FramerateFront(this._target.client, this._target.form);
|
this._framerate = new FramerateFront(this._target.client, this._target.form);
|
||||||
} else {
|
} else {
|
||||||
this._framerate = {
|
this._framerate = {
|
||||||
startRecording: () => {},
|
startRecording: () => {},
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
* about:telemetry.
|
* about:telemetry.
|
||||||
*
|
*
|
||||||
* You can view telemetry stats for large groups of Firefox users at
|
* You can view telemetry stats for large groups of Firefox users at
|
||||||
* metrics.mozilla.com.
|
* telemetry.mozilla.org.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version";
|
const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version";
|
||||||
@ -170,6 +170,11 @@ Telemetry.prototype = {
|
|||||||
userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG",
|
userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG",
|
||||||
timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS"
|
timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS"
|
||||||
},
|
},
|
||||||
|
webide: {
|
||||||
|
histogram: "DEVTOOLS_WEBIDE_OPENED_BOOLEAN",
|
||||||
|
userHistogram: "DEVTOOLS_WEBIDE_OPENED_PER_USER_FLAG",
|
||||||
|
timerHistogram: "DEVTOOLS_WEBIDE_TIME_ACTIVE_SECONDS"
|
||||||
|
},
|
||||||
custom: {
|
custom: {
|
||||||
histogram: "DEVTOOLS_CUSTOM_OPENED_BOOLEAN",
|
histogram: "DEVTOOLS_CUSTOM_OPENED_BOOLEAN",
|
||||||
userHistogram: "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG",
|
userHistogram: "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG",
|
||||||
@ -194,7 +199,7 @@ Telemetry.prototype = {
|
|||||||
this.logOncePerBrowserVersion(charts.userHistogram, true);
|
this.logOncePerBrowserVersion(charts.userHistogram, true);
|
||||||
}
|
}
|
||||||
if (charts.timerHistogram) {
|
if (charts.timerHistogram) {
|
||||||
this._timers.set(charts.timerHistogram, new Date());
|
this.startTimer(charts.timerHistogram);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -205,12 +210,31 @@ Telemetry.prototype = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let startTime = this._timers.get(charts.timerHistogram);
|
this.stopTimer(charts.timerHistogram);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record the start time for a timing-based histogram entry.
|
||||||
|
*
|
||||||
|
* @param String histogramId
|
||||||
|
* Histogram in which the data is to be stored.
|
||||||
|
*/
|
||||||
|
startTimer: function(histogramId) {
|
||||||
|
this._timers.set(histogramId, new Date());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the timer and log elasped time for a timing-based histogram entry.
|
||||||
|
*
|
||||||
|
* @param String histogramId
|
||||||
|
* Histogram in which the data is to be stored.
|
||||||
|
*/
|
||||||
|
stopTimer: function(histogramId) {
|
||||||
|
let startTime = this._timers.get(histogramId);
|
||||||
if (startTime) {
|
if (startTime) {
|
||||||
let time = (new Date() - startTime) / 1000;
|
let time = (new Date() - startTime) / 1000;
|
||||||
this.log(charts.timerHistogram, time);
|
this.log(histogramId, time);
|
||||||
this._timers.delete(charts.timerHistogram);
|
this._timers.delete(histogramId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -258,11 +282,8 @@ Telemetry.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
for (let [histogram, time] of this._timers) {
|
for (let histogramId of this._timers.keys()) {
|
||||||
time = (new Date() - time) / 1000;
|
this.stopTimer(histogramId);
|
||||||
|
|
||||||
this.log(histogram, time);
|
|
||||||
this._timers.delete(histogram);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -88,7 +88,7 @@ function CheckLockState() {
|
|||||||
// ADB check
|
// ADB check
|
||||||
if (AppManager.selectedRuntime instanceof USBRuntime) {
|
if (AppManager.selectedRuntime instanceof USBRuntime) {
|
||||||
let device = Devices.getByName(AppManager.selectedRuntime.id);
|
let device = Devices.getByName(AppManager.selectedRuntime.id);
|
||||||
if (device.summonRoot) {
|
if (device && device.summonRoot) {
|
||||||
device.isRoot().then(isRoot => {
|
device.isRoot().then(isRoot => {
|
||||||
if (isRoot) {
|
if (isRoot) {
|
||||||
adbCheckResult.textContent = sYes;
|
adbCheckResult.textContent = sYes;
|
||||||
|
@ -21,6 +21,7 @@ const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
|
|||||||
const {GetAvailableAddons} = require("devtools/webide/addons");
|
const {GetAvailableAddons} = require("devtools/webide/addons");
|
||||||
const {GetTemplatesJSON, GetAddonsJSON} = require("devtools/webide/remote-resources");
|
const {GetTemplatesJSON, GetAddonsJSON} = require("devtools/webide/remote-resources");
|
||||||
const utils = require("devtools/webide/utils");
|
const utils = require("devtools/webide/utils");
|
||||||
|
const Telemetry = require("devtools/shared/telemetry");
|
||||||
|
|
||||||
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
|
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
|
||||||
|
|
||||||
@ -47,6 +48,9 @@ window.addEventListener("unload", function onUnload() {
|
|||||||
|
|
||||||
let UI = {
|
let UI = {
|
||||||
init: function() {
|
init: function() {
|
||||||
|
this._telemetry = new Telemetry();
|
||||||
|
this._telemetry.toolOpened("webide");
|
||||||
|
|
||||||
AppManager.init();
|
AppManager.init();
|
||||||
|
|
||||||
this.onMessage = this.onMessage.bind(this);
|
this.onMessage = this.onMessage.bind(this);
|
||||||
@ -85,6 +89,8 @@ let UI = {
|
|||||||
AppManager.off("app-manager-update", this.appManagerUpdate);
|
AppManager.off("app-manager-update", this.appManagerUpdate);
|
||||||
AppManager.uninit();
|
AppManager.uninit();
|
||||||
window.removeEventListener("message", this.onMessage);
|
window.removeEventListener("message", this.onMessage);
|
||||||
|
this.updateConnectionTelemetry();
|
||||||
|
this._telemetry.toolClosed("webide");
|
||||||
},
|
},
|
||||||
|
|
||||||
canWindowClose: function() {
|
canWindowClose: function() {
|
||||||
@ -117,6 +123,7 @@ let UI = {
|
|||||||
case "connection":
|
case "connection":
|
||||||
this.updateRuntimeButton();
|
this.updateRuntimeButton();
|
||||||
this.updateCommands();
|
this.updateCommands();
|
||||||
|
this.updateConnectionTelemetry();
|
||||||
break;
|
break;
|
||||||
case "project":
|
case "project":
|
||||||
this._updatePromise = Task.spawn(function() {
|
this._updatePromise = Task.spawn(function() {
|
||||||
@ -225,12 +232,13 @@ let UI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
busyWithProgressUntil: function(promise, operationDescription) {
|
busyWithProgressUntil: function(promise, operationDescription) {
|
||||||
this.busyUntil(promise, operationDescription);
|
let busy = this.busyUntil(promise, operationDescription);
|
||||||
let win = document.querySelector("window");
|
let win = document.querySelector("window");
|
||||||
let progress = document.querySelector("#action-busy-determined");
|
let progress = document.querySelector("#action-busy-determined");
|
||||||
progress.mode = "undetermined";
|
progress.mode = "undetermined";
|
||||||
win.classList.add("busy-determined");
|
win.classList.add("busy-determined");
|
||||||
win.classList.remove("busy-undetermined");
|
win.classList.remove("busy-undetermined");
|
||||||
|
return busy;
|
||||||
},
|
},
|
||||||
|
|
||||||
busyUntil: function(promise, operationDescription) {
|
busyUntil: function(promise, operationDescription) {
|
||||||
@ -372,6 +380,7 @@ let UI = {
|
|||||||
connectToRuntime: function(runtime) {
|
connectToRuntime: function(runtime) {
|
||||||
let name = runtime.getName();
|
let name = runtime.getName();
|
||||||
let promise = AppManager.connectToRuntime(runtime);
|
let promise = AppManager.connectToRuntime(runtime);
|
||||||
|
promise.then(() => this.initConnectionTelemetry());
|
||||||
return this.busyUntil(promise, "connecting to runtime");
|
return this.busyUntil(promise, "connecting to runtime");
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -396,6 +405,47 @@ let UI = {
|
|||||||
this.lastConnectedRuntime);
|
this.lastConnectedRuntime);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_actionsToLog: new Set(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each new connection, track whether play and debug were ever used. Only
|
||||||
|
* one value is collected for each button, even if they are used multiple
|
||||||
|
* times during a connection.
|
||||||
|
*/
|
||||||
|
initConnectionTelemetry: function() {
|
||||||
|
this._actionsToLog.add("play");
|
||||||
|
this._actionsToLog.add("debug");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action occurred. Log that it happened, and remove it from the loggable
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
onAction: function(action) {
|
||||||
|
if (!this._actionsToLog.has(action)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logActionState(action, true);
|
||||||
|
this._actionsToLog.delete(action);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection status changed or we are shutting down. Record any loggable
|
||||||
|
* actions as having not occurred.
|
||||||
|
*/
|
||||||
|
updateConnectionTelemetry: function() {
|
||||||
|
for (let action of this._actionsToLog.values()) {
|
||||||
|
this.logActionState(action, false);
|
||||||
|
}
|
||||||
|
this._actionsToLog.clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
logActionState: function(action, state) {
|
||||||
|
let histogramId = "DEVTOOLS_WEBIDE_CONNECTION_" +
|
||||||
|
action.toUpperCase() + "_USED";
|
||||||
|
this._telemetry.log(histogramId, state);
|
||||||
|
},
|
||||||
|
|
||||||
/********** PROJECTS **********/
|
/********** PROJECTS **********/
|
||||||
|
|
||||||
// Panel & button
|
// Panel & button
|
||||||
@ -837,8 +887,7 @@ let UI = {
|
|||||||
splitter.setAttribute("hidden", "true");
|
splitter.setAttribute("hidden", "true");
|
||||||
document.querySelector("#action-button-debug").removeAttribute("active");
|
document.querySelector("#action-button-debug").removeAttribute("active");
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
let Cmds = {
|
let Cmds = {
|
||||||
quit: function() {
|
quit: function() {
|
||||||
@ -1108,17 +1157,28 @@ let Cmds = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
play: function() {
|
play: function() {
|
||||||
|
let busy;
|
||||||
switch(AppManager.selectedProject.type) {
|
switch(AppManager.selectedProject.type) {
|
||||||
case "packaged":
|
case "packaged":
|
||||||
return UI.busyWithProgressUntil(AppManager.installAndRunProject(), "installing and running app");
|
busy = UI.busyWithProgressUntil(AppManager.installAndRunProject(),
|
||||||
|
"installing and running app");
|
||||||
|
break;
|
||||||
case "hosted":
|
case "hosted":
|
||||||
return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
|
busy = UI.busyUntil(AppManager.installAndRunProject(),
|
||||||
|
"installing and running app");
|
||||||
|
break;
|
||||||
case "runtimeApp":
|
case "runtimeApp":
|
||||||
return UI.busyUntil(AppManager.launchOrReloadRuntimeApp(), "launching / reloading app");
|
busy = UI.busyUntil(AppManager.launchOrReloadRuntimeApp(), "launching / reloading app");
|
||||||
|
break;
|
||||||
case "tab":
|
case "tab":
|
||||||
return UI.busyUntil(AppManager.reloadTab(), "reloading tab");
|
busy = UI.busyUntil(AppManager.reloadTab(), "reloading tab");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return promise.reject();
|
if (!busy) {
|
||||||
|
return promise.reject();
|
||||||
|
}
|
||||||
|
UI.onAction("play");
|
||||||
|
return busy;
|
||||||
},
|
},
|
||||||
|
|
||||||
stop: function() {
|
stop: function() {
|
||||||
@ -1126,6 +1186,7 @@ let Cmds = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
toggleToolbox: function() {
|
toggleToolbox: function() {
|
||||||
|
UI.onAction("debug");
|
||||||
if (UI.toolboxIframe) {
|
if (UI.toolboxIframe) {
|
||||||
UI.destroyToolbox();
|
UI.destroyToolbox();
|
||||||
return promise.resolve();
|
return promise.resolve();
|
||||||
|
@ -26,6 +26,7 @@ const {USBRuntime, WiFiRuntime, SimulatorRuntime,
|
|||||||
gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
|
gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
|
||||||
const discovery = require("devtools/toolkit/discovery/discovery");
|
const discovery = require("devtools/toolkit/discovery/discovery");
|
||||||
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
||||||
|
const Telemetry = require("devtools/shared/telemetry");
|
||||||
|
|
||||||
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
|
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
|
||||||
|
|
||||||
@ -68,6 +69,8 @@ exports.AppManager = AppManager = {
|
|||||||
|
|
||||||
this.observe = this.observe.bind(this);
|
this.observe = this.observe.bind(this);
|
||||||
Services.prefs.addObserver(WIFI_SCANNING_PREF, this, false);
|
Services.prefs.addObserver(WIFI_SCANNING_PREF, this, false);
|
||||||
|
|
||||||
|
this._telemetry = new Telemetry();
|
||||||
},
|
},
|
||||||
|
|
||||||
uninit: function() {
|
uninit: function() {
|
||||||
@ -372,6 +375,25 @@ exports.AppManager = AppManager = {
|
|||||||
}
|
}
|
||||||
}, deferred.reject);
|
}, deferred.reject);
|
||||||
|
|
||||||
|
// Record connection result in telemetry
|
||||||
|
let logResult = result => {
|
||||||
|
this._telemetry.log("DEVTOOLS_WEBIDE_CONNECTION_RESULT", result);
|
||||||
|
if (runtime.type) {
|
||||||
|
this._telemetry.log("DEVTOOLS_WEBIDE_" + runtime.type +
|
||||||
|
"_CONNECTION_RESULT", result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
deferred.promise.then(() => logResult(true), () => logResult(false));
|
||||||
|
|
||||||
|
// If successful, record connection time in telemetry
|
||||||
|
deferred.promise.then(() => {
|
||||||
|
const timerId = "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS";
|
||||||
|
this._telemetry.startTimer(timerId);
|
||||||
|
this.connection.once(Connection.Events.STATUS_CHANGED, () => {
|
||||||
|
this._telemetry.stopTimer(timerId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -33,3 +33,4 @@ support-files =
|
|||||||
[test_addons.html]
|
[test_addons.html]
|
||||||
[test_deviceinfo.html]
|
[test_deviceinfo.html]
|
||||||
[test_autoconnect_runtime.html]
|
[test_autoconnect_runtime.html]
|
||||||
|
[test_telemetry.html]
|
||||||
|
@ -115,6 +115,14 @@ function waitForUpdate(win, update) {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitForTime(time) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
setTimeout(() => {
|
||||||
|
deferred.resolve();
|
||||||
|
}, time);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
function documentIsLoaded(doc) {
|
function documentIsLoaded(doc) {
|
||||||
let deferred = promise.defer();
|
let deferred = promise.defer();
|
||||||
if (doc.readyState == "complete") {
|
if (doc.readyState == "complete") {
|
||||||
|
@ -20,8 +20,11 @@
|
|||||||
|
|
||||||
Task.spawn(function* () {
|
Task.spawn(function* () {
|
||||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
DebuggerServer.init(function () { return true; });
|
|
||||||
DebuggerServer.addBrowserActors();
|
if (!DebuggerServer.initialized) {
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
}
|
||||||
|
|
||||||
let win = yield openWebIDE();
|
let win = yield openWebIDE();
|
||||||
|
|
||||||
|
@ -18,6 +18,18 @@
|
|||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
let win;
|
||||||
|
|
||||||
|
SimpleTest.registerCleanupFunction(() => {
|
||||||
|
Task.spawn(function*() {
|
||||||
|
if (win) {
|
||||||
|
yield closeWebIDE(win);
|
||||||
|
}
|
||||||
|
DebuggerServer.destroy();
|
||||||
|
yield removeAllProjects();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Task.spawn(function* () {
|
Task.spawn(function* () {
|
||||||
|
|
||||||
function isPlayActive() {
|
function isPlayActive() {
|
||||||
@ -29,10 +41,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
DebuggerServer.init(function () { return true; });
|
|
||||||
DebuggerServer.addBrowserActors();
|
|
||||||
|
|
||||||
let win = yield openWebIDE();
|
if (!DebuggerServer.initialized) {
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
}
|
||||||
|
|
||||||
|
win = yield openWebIDE();
|
||||||
|
|
||||||
win.AppManager.runtimeList.usb.push({
|
win.AppManager.runtimeList.usb.push({
|
||||||
connect: function(connection) {
|
connect: function(connection) {
|
||||||
@ -119,12 +134,6 @@
|
|||||||
|
|
||||||
yield win.Cmds.disconnectRuntime();
|
yield win.Cmds.disconnectRuntime();
|
||||||
|
|
||||||
yield closeWebIDE(win);
|
|
||||||
|
|
||||||
DebuggerServer.destroy();
|
|
||||||
|
|
||||||
yield removeAllProjects();
|
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
255
browser/devtools/webide/test/test_telemetry.html
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf8">
|
||||||
|
<title></title>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||||
|
<script type="application/javascript;version=1.8" src="head.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
const Telemetry = require("devtools/shared/telemetry");
|
||||||
|
const { USBRuntime, WiFiRuntime, SimulatorRuntime, gRemoteRuntime,
|
||||||
|
gLocalRuntime } = require("devtools/webide/runtimes");
|
||||||
|
|
||||||
|
// Because we need to gather stats for the period of time that a tool has
|
||||||
|
// been opened we make use of setTimeout() to create tool active times.
|
||||||
|
const TOOL_DELAY = 200;
|
||||||
|
|
||||||
|
function patchTelemetry() {
|
||||||
|
Telemetry.prototype.telemetryInfo = {};
|
||||||
|
Telemetry.prototype._oldlog = Telemetry.prototype.log;
|
||||||
|
Telemetry.prototype.log = function(histogramId, value) {
|
||||||
|
if (histogramId) {
|
||||||
|
if (!this.telemetryInfo[histogramId]) {
|
||||||
|
this.telemetryInfo[histogramId] = [];
|
||||||
|
}
|
||||||
|
this.telemetryInfo[histogramId].push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetTelemetry() {
|
||||||
|
Telemetry.prototype.log = Telemetry.prototype._oldlog;
|
||||||
|
delete Telemetry.prototype._oldlog;
|
||||||
|
delete Telemetry.prototype.telemetryInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cycleWebIDE() {
|
||||||
|
return Task.spawn(function*() {
|
||||||
|
let win = yield openWebIDE();
|
||||||
|
// Wait a bit, so we're open for a non-zero time
|
||||||
|
yield waitForTime(TOOL_DELAY);
|
||||||
|
yield closeWebIDE(win);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFakeRuntimes(win) {
|
||||||
|
// We use the real runtimes here (and switch out some functionality)
|
||||||
|
// so we can ensure that logging happens as it would in real use.
|
||||||
|
|
||||||
|
let usb = new USBRuntime("fakeUSB");
|
||||||
|
// Use local pipe instead
|
||||||
|
usb.connect = function(connection) {
|
||||||
|
ok(connection, win.AppManager.connection, "connection is valid");
|
||||||
|
connection.host = null; // force connectPipe
|
||||||
|
connection.connect();
|
||||||
|
return promise.resolve();
|
||||||
|
};
|
||||||
|
win.AppManager.runtimeList.usb.push(usb);
|
||||||
|
|
||||||
|
let wifi = new WiFiRuntime("fakeWiFi");
|
||||||
|
// Use local pipe instead
|
||||||
|
wifi.connect = function(connection) {
|
||||||
|
ok(connection, win.AppManager.connection, "connection is valid");
|
||||||
|
connection.host = null; // force connectPipe
|
||||||
|
connection.connect();
|
||||||
|
return promise.resolve();
|
||||||
|
};
|
||||||
|
win.AppManager.runtimeList.wifi.push(wifi);
|
||||||
|
|
||||||
|
let sim = new SimulatorRuntime("fakeSimulator");
|
||||||
|
// Use local pipe instead
|
||||||
|
sim.connect = function(connection) {
|
||||||
|
ok(connection, win.AppManager.connection, "connection is valid");
|
||||||
|
connection.host = null; // force connectPipe
|
||||||
|
connection.connect();
|
||||||
|
return promise.resolve();
|
||||||
|
};
|
||||||
|
sim.getName = function() {
|
||||||
|
return this.version;
|
||||||
|
};
|
||||||
|
win.AppManager.runtimeList.simulator.push(sim);
|
||||||
|
|
||||||
|
let remote = gRemoteRuntime;
|
||||||
|
// Use local pipe instead
|
||||||
|
remote.connect = function(connection) {
|
||||||
|
ok(connection, win.AppManager.connection, "connection is valid");
|
||||||
|
connection.host = null; // force connectPipe
|
||||||
|
connection.connect();
|
||||||
|
return promise.resolve();
|
||||||
|
};
|
||||||
|
let local = gLocalRuntime;
|
||||||
|
win.AppManager.runtimeList.custom = [gRemoteRuntime, gLocalRuntime];
|
||||||
|
|
||||||
|
win.AppManager.update("runtimelist");
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTestApp(win) {
|
||||||
|
return Task.spawn(function*() {
|
||||||
|
let packagedAppLocation = getTestFilePath("app");
|
||||||
|
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startConnection(win, type, index) {
|
||||||
|
let panelNode = win.document.querySelector("#runtime-panel");
|
||||||
|
let items = panelNode.querySelectorAll(".runtime-panel-item-" + type);
|
||||||
|
if (index === undefined) {
|
||||||
|
is(items.length, 1, "Found one runtime button");
|
||||||
|
}
|
||||||
|
|
||||||
|
let deferred = promise.defer();
|
||||||
|
win.AppManager.connection.once(
|
||||||
|
win.Connection.Events.CONNECTED,
|
||||||
|
() => deferred.resolve());
|
||||||
|
|
||||||
|
items[index || 0].click();
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitUntilConnected(win) {
|
||||||
|
return Task.spawn(function*() {
|
||||||
|
ok(win.document.querySelector("window").className, "busy", "UI is busy");
|
||||||
|
yield win.UI._busyPromise;
|
||||||
|
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToRuntime(win, type, index) {
|
||||||
|
return Task.spawn(function*() {
|
||||||
|
yield startConnection(win, type, index);
|
||||||
|
yield waitUntilConnected(win);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkResults() {
|
||||||
|
let result = Telemetry.prototype.telemetryInfo;
|
||||||
|
for (let [histId, value] of Iterator(result)) {
|
||||||
|
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
|
||||||
|
ok(value.length === 1 && !!value[0],
|
||||||
|
"Per user value " + histId + " has a single value of true");
|
||||||
|
} else if (histId.endsWith("OPENED_BOOLEAN")) {
|
||||||
|
ok(value.length > 1, histId + " has more than one entry");
|
||||||
|
|
||||||
|
let okay = value.every(function(element) {
|
||||||
|
return !!element;
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(okay, "All " + histId + " entries are true");
|
||||||
|
} else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
|
||||||
|
ok(value.length > 1, histId + " has more than one entry");
|
||||||
|
|
||||||
|
let okay = value.every(function(element) {
|
||||||
|
return element > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(okay, "All " + histId + " entries have time > 0");
|
||||||
|
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTION_RESULT") {
|
||||||
|
ok(value.length === 5, histId + " has 5 connection results");
|
||||||
|
|
||||||
|
let okay = value.every(function(element) {
|
||||||
|
return !!element;
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(okay, "All " + histId + " connections succeeded");
|
||||||
|
} else if (histId.endsWith("CONNECTION_RESULT")) {
|
||||||
|
ok(value.length === 1 && !!value[0],
|
||||||
|
histId + " has 1 successful connection");
|
||||||
|
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS") {
|
||||||
|
ok(value.length === 5, histId + " has 5 connection results");
|
||||||
|
|
||||||
|
let okay = value.every(function(element) {
|
||||||
|
return element > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(okay, "All " + histId + " connections have time > 0");
|
||||||
|
} else if (histId.endsWith("USED")) {
|
||||||
|
ok(value.length === 5, histId + " has 5 connection actions");
|
||||||
|
|
||||||
|
let okay = value.every(function(element) {
|
||||||
|
return !element;
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(okay, "All " + histId + " actions were skipped");
|
||||||
|
} else {
|
||||||
|
ok(false, "Unexpected " + histId + " was logged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
let win;
|
||||||
|
|
||||||
|
SimpleTest.registerCleanupFunction(() => {
|
||||||
|
Task.spawn(function*() {
|
||||||
|
if (win) {
|
||||||
|
yield closeWebIDE(win);
|
||||||
|
}
|
||||||
|
DebuggerServer.destroy();
|
||||||
|
yield removeAllProjects();
|
||||||
|
resetTelemetry();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Task.spawn(function*() {
|
||||||
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
|
|
||||||
|
if (!DebuggerServer.initialized) {
|
||||||
|
DebuggerServer.init(function () { return true; });
|
||||||
|
DebuggerServer.addBrowserActors();
|
||||||
|
}
|
||||||
|
|
||||||
|
patchTelemetry();
|
||||||
|
|
||||||
|
// Cycle once, so we can test for multiple opens
|
||||||
|
yield cycleWebIDE();
|
||||||
|
|
||||||
|
win = yield openWebIDE();
|
||||||
|
// Wait a bit, so we're open for a non-zero time
|
||||||
|
yield waitForTime(TOOL_DELAY);
|
||||||
|
addFakeRuntimes(win);
|
||||||
|
yield addTestApp(win);
|
||||||
|
|
||||||
|
// Each one should log a connection result and non-zero connection
|
||||||
|
// time
|
||||||
|
yield connectToRuntime(win, "usb");
|
||||||
|
yield waitForTime(TOOL_DELAY);
|
||||||
|
yield connectToRuntime(win, "wifi");
|
||||||
|
yield waitForTime(TOOL_DELAY);
|
||||||
|
yield connectToRuntime(win, "simulator");
|
||||||
|
yield waitForTime(TOOL_DELAY);
|
||||||
|
yield connectToRuntime(win, "custom", 0 /* remote */);
|
||||||
|
yield waitForTime(TOOL_DELAY);
|
||||||
|
yield connectToRuntime(win, "custom", 1 /* local */);
|
||||||
|
yield waitForTime(TOOL_DELAY);
|
||||||
|
yield closeWebIDE(win);
|
||||||
|
|
||||||
|
checkResults();
|
||||||
|
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
configure.in
@ -3968,6 +3968,19 @@ if test -z "$MOZ_GOOGLE_API_KEY"; then
|
|||||||
fi
|
fi
|
||||||
AC_SUBST(MOZ_GOOGLE_API_KEY)
|
AC_SUBST(MOZ_GOOGLE_API_KEY)
|
||||||
|
|
||||||
|
# Allow to specify a Google OAuth API key file that contains the client ID and
|
||||||
|
# the secret key to be used for various Google OAuth API requests.
|
||||||
|
MOZ_ARG_WITH_STRING(google-oauth-api-keyfile,
|
||||||
|
[ --with-google-oauth-api-keyfile=file Use the client id and secret key contained in the given keyfile for Google OAuth API requests],
|
||||||
|
[MOZ_GOOGLE_OAUTH_API_CLIENTID=`cat $withval | cut -f 1 -d " "`
|
||||||
|
MOZ_GOOGLE_OAUTH_API_KEY=`cat $withval | cut -f 2 -d " "`])
|
||||||
|
if test -z "$MOZ_GOOGLE_OAUTH_API_CLIENTID"; then
|
||||||
|
MOZ_GOOGLE_OAUTH_API_CLIENTID=no-google-oauth-api-clientid
|
||||||
|
MOZ_GOOGLE_OAUTH_API_KEY=no-google-oauth-api-key
|
||||||
|
fi
|
||||||
|
AC_SUBST(MOZ_GOOGLE_OAUTH_API_CLIENTID)
|
||||||
|
AC_SUBST(MOZ_GOOGLE_OAUTH_API_KEY)
|
||||||
|
|
||||||
# Allow specifying a Bing API key file that contains the client ID and the
|
# Allow specifying a Bing API key file that contains the client ID and the
|
||||||
# secret key to be used for the Bing Translation API requests.
|
# secret key to be used for the Bing Translation API requests.
|
||||||
MOZ_ARG_WITH_STRING(bing-api-keyfile,
|
MOZ_ARG_WITH_STRING(bing-api-keyfile,
|
||||||
|
@ -300,20 +300,16 @@ nsDefaultURIFixup::GetFixupURIInfo(const nsACString& aStringURI, uint32_t aFixup
|
|||||||
ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(ourHandler));
|
ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(ourHandler));
|
||||||
extHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default");
|
extHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default");
|
||||||
|
|
||||||
nsCOMPtr<nsIURI> uri;
|
|
||||||
if (ourHandler != extHandler || !PossiblyHostPortUrl(uriString)) {
|
if (ourHandler != extHandler || !PossiblyHostPortUrl(uriString)) {
|
||||||
// Just try to create an URL out of it
|
// Just try to create an URL out of it
|
||||||
rv = NS_NewURI(getter_AddRefs(uri), uriString, nullptr);
|
rv = NS_NewURI(getter_AddRefs(info->mFixedURI), uriString, nullptr);
|
||||||
if (NS_SUCCEEDED(rv)) {
|
|
||||||
info->mFixedURI = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!uri && rv != NS_ERROR_MALFORMED_URI) {
|
if (!info->mFixedURI && rv != NS_ERROR_MALFORMED_URI) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri && ourHandler == extHandler && sFixupKeywords &&
|
if (info->mFixedURI && ourHandler == extHandler && sFixupKeywords &&
|
||||||
(aFixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS)) {
|
(aFixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS)) {
|
||||||
nsCOMPtr<nsIExternalProtocolService> extProtService =
|
nsCOMPtr<nsIExternalProtocolService> extProtService =
|
||||||
do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
|
do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
|
||||||
@ -328,18 +324,17 @@ nsDefaultURIFixup::GetFixupURIInfo(const nsACString& aStringURI, uint32_t aFixup
|
|||||||
// It's more likely the user wants to search, and so we
|
// It's more likely the user wants to search, and so we
|
||||||
// chuck this over to their preferred search provider instead:
|
// chuck this over to their preferred search provider instead:
|
||||||
if (!handlerExists) {
|
if (!handlerExists) {
|
||||||
nsresult rv = KeywordToURI(uriString, aPostData, getter_AddRefs(uri));
|
TryKeywordFixupForURIInfo(uriString, info, aPostData);
|
||||||
if (NS_SUCCEEDED(rv) && uri) {
|
|
||||||
info->mFixupUsedKeyword = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri) {
|
if (info->mFixedURI) {
|
||||||
if (aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI)
|
if (!info->mPreferredURI) {
|
||||||
info->mFixupCreatedAlternateURI = MakeAlternateURI(uri);
|
if (aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI)
|
||||||
info->mPreferredURI = uri;
|
info->mFixupCreatedAlternateURI = MakeAlternateURI(info->mFixedURI);
|
||||||
|
info->mPreferredURI = info->mFixedURI;
|
||||||
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,9 +369,10 @@ nsDefaultURIFixup::GetFixupURIInfo(const nsACString& aStringURI, uint32_t aFixup
|
|||||||
// Test whether keywords need to be fixed up
|
// Test whether keywords need to be fixed up
|
||||||
if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) &&
|
if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) &&
|
||||||
!inputHadDuffProtocol) {
|
!inputHadDuffProtocol) {
|
||||||
KeywordURIFixup(uriString, info, aPostData);
|
if (NS_SUCCEEDED(KeywordURIFixup(uriString, info, aPostData)) &&
|
||||||
if (info->mPreferredURI)
|
info->mPreferredURI) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Did the caller want us to try an alternative URI?
|
// Did the caller want us to try an alternative URI?
|
||||||
@ -415,12 +411,7 @@ nsDefaultURIFixup::GetFixupURIInfo(const nsACString& aStringURI, uint32_t aFixup
|
|||||||
// If we still haven't been able to construct a valid URI, try to force a
|
// If we still haven't been able to construct a valid URI, try to force a
|
||||||
// keyword match. This catches search strings with '.' or ':' in them.
|
// keyword match. This catches search strings with '.' or ':' in them.
|
||||||
if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP)) {
|
if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP)) {
|
||||||
rv = KeywordToURI(aStringURI, aPostData, getter_AddRefs(info->mPreferredURI));
|
rv = TryKeywordFixupForURIInfo(aStringURI, info, aPostData);
|
||||||
if (NS_SUCCEEDED(rv) && info->mPreferredURI)
|
|
||||||
{
|
|
||||||
info->mFixupUsedKeyword = true;
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
@ -428,9 +419,11 @@ nsDefaultURIFixup::GetFixupURIInfo(const nsACString& aStringURI, uint32_t aFixup
|
|||||||
|
|
||||||
NS_IMETHODIMP nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
|
NS_IMETHODIMP nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
|
||||||
nsIInputStream **aPostData,
|
nsIInputStream **aPostData,
|
||||||
nsIURI **aURI)
|
nsIURIFixupInfo **aInfo)
|
||||||
{
|
{
|
||||||
*aURI = nullptr;
|
nsRefPtr<nsDefaultURIFixupInfo> info = new nsDefaultURIFixupInfo(aKeyword);
|
||||||
|
NS_ADDREF(*aInfo = info);
|
||||||
|
|
||||||
if (aPostData) {
|
if (aPostData) {
|
||||||
*aPostData = nullptr;
|
*aPostData = nullptr;
|
||||||
}
|
}
|
||||||
@ -451,10 +444,14 @@ NS_IMETHODIMP nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
|
|||||||
|
|
||||||
ipc::OptionalInputStreamParams postData;
|
ipc::OptionalInputStreamParams postData;
|
||||||
ipc::OptionalURIParams uri;
|
ipc::OptionalURIParams uri;
|
||||||
if (!contentChild->SendKeywordToURI(keyword, &postData, &uri)) {
|
nsAutoString providerName;
|
||||||
|
if (!contentChild->SendKeywordToURI(keyword, &providerName, &postData, &uri)) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CopyUTF8toUTF16(keyword, info->mKeywordAsSent);
|
||||||
|
info->mKeywordProviderName = providerName;
|
||||||
|
|
||||||
if (aPostData) {
|
if (aPostData) {
|
||||||
nsTArray<ipc::FileDescriptor> fds;
|
nsTArray<ipc::FileDescriptor> fds;
|
||||||
nsCOMPtr<nsIInputStream> temp = DeserializeInputStream(postData, fds);
|
nsCOMPtr<nsIInputStream> temp = DeserializeInputStream(postData, fds);
|
||||||
@ -464,7 +461,7 @@ NS_IMETHODIMP nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
|
|||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIURI> temp = DeserializeURI(uri);
|
nsCOMPtr<nsIURI> temp = DeserializeURI(uri);
|
||||||
temp.forget(aURI);
|
info->mPreferredURI = temp.forget();
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,7 +483,8 @@ NS_IMETHODIMP nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
|
|||||||
responseType.Assign(mozKeywordSearch);
|
responseType.Assign(mozKeywordSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultEngine->GetSubmission(NS_ConvertUTF8toUTF16(keyword),
|
NS_ConvertUTF8toUTF16 keywordW(keyword);
|
||||||
|
defaultEngine->GetSubmission(keywordW,
|
||||||
responseType,
|
responseType,
|
||||||
NS_LITERAL_STRING("keyword"),
|
NS_LITERAL_STRING("keyword"),
|
||||||
getter_AddRefs(submission));
|
getter_AddRefs(submission));
|
||||||
@ -504,21 +502,9 @@ NS_IMETHODIMP nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
|
|||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This notification is meant for Firefox Health Report so it
|
defaultEngine->GetName(info->mKeywordProviderName);
|
||||||
// can increment counts from the search engine. The assumption
|
info->mKeywordAsSent = keywordW;
|
||||||
// here is that this keyword/submission will eventually result
|
return submission->GetUri(getter_AddRefs(info->mPreferredURI));
|
||||||
// in a search. Since we only generate a URI here, there is the
|
|
||||||
// possibility we'll increment the counter without actually
|
|
||||||
// incurring a search. A robust solution would involve currying
|
|
||||||
// the search engine's name through various function calls.
|
|
||||||
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
|
|
||||||
if (obsSvc) {
|
|
||||||
// Note that "keyword-search" refers to a search via the url
|
|
||||||
// bar, not a bookmarks keyword search.
|
|
||||||
obsSvc->NotifyObservers(defaultEngine, "keyword-search", NS_ConvertUTF8toUTF16(keyword).get());
|
|
||||||
}
|
|
||||||
|
|
||||||
return submission->GetUri(aURI);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -528,6 +514,22 @@ NS_IMETHODIMP nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword,
|
|||||||
return NS_ERROR_NOT_AVAILABLE;
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to deal with passing around uri fixup stuff
|
||||||
|
nsresult
|
||||||
|
nsDefaultURIFixup::TryKeywordFixupForURIInfo(const nsACString & aURIString,
|
||||||
|
nsDefaultURIFixupInfo* aFixupInfo,
|
||||||
|
nsIInputStream **aPostData)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIURIFixupInfo> keywordInfo;
|
||||||
|
nsresult rv = KeywordToURI(aURIString, aPostData, getter_AddRefs(keywordInfo));
|
||||||
|
if (NS_SUCCEEDED(rv)) {
|
||||||
|
keywordInfo->GetKeywordProviderName(aFixupInfo->mKeywordProviderName);
|
||||||
|
keywordInfo->GetKeywordAsSent(aFixupInfo->mKeywordAsSent);
|
||||||
|
keywordInfo->GetPreferredURI(getter_AddRefs(aFixupInfo->mPreferredURI));
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
bool nsDefaultURIFixup::MakeAlternateURI(nsIURI *aURI)
|
bool nsDefaultURIFixup::MakeAlternateURI(nsIURI *aURI)
|
||||||
{
|
{
|
||||||
if (!Preferences::GetRootBranch())
|
if (!Preferences::GetRootBranch())
|
||||||
@ -923,9 +925,10 @@ bool nsDefaultURIFixup::PossiblyByteExpandedFileName(const nsAString& aIn)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nsDefaultURIFixup::KeywordURIFixup(const nsACString & aURIString,
|
nsresult
|
||||||
nsDefaultURIFixupInfo* aFixupInfo,
|
nsDefaultURIFixup::KeywordURIFixup(const nsACString & aURIString,
|
||||||
nsIInputStream **aPostData)
|
nsDefaultURIFixupInfo* aFixupInfo,
|
||||||
|
nsIInputStream **aPostData)
|
||||||
{
|
{
|
||||||
// These are keyword formatted strings
|
// These are keyword formatted strings
|
||||||
// "what is mozilla"
|
// "what is mozilla"
|
||||||
@ -1023,7 +1026,6 @@ void nsDefaultURIFixup::KeywordURIFixup(const nsACString & aURIString,
|
|||||||
looksLikeIpv6 = false;
|
looksLikeIpv6 = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult rv;
|
|
||||||
nsAutoCString asciiHost;
|
nsAutoCString asciiHost;
|
||||||
nsAutoCString host;
|
nsAutoCString host;
|
||||||
|
|
||||||
@ -1041,7 +1043,7 @@ void nsDefaultURIFixup::KeywordURIFixup(const nsACString & aURIString,
|
|||||||
((foundDots + foundDigits == pos - 1) ||
|
((foundDots + foundDigits == pos - 1) ||
|
||||||
(foundColons == 1 && firstColonLoc > lastDotLoc &&
|
(foundColons == 1 && firstColonLoc > lastDotLoc &&
|
||||||
foundDots + foundDigits + foundColons == pos - 1))) {
|
foundDots + foundDigits + foundColons == pos - 1))) {
|
||||||
return;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t posWithNoTrailingSlash = pos;
|
uint32_t posWithNoTrailingSlash = pos;
|
||||||
@ -1054,15 +1056,16 @@ void nsDefaultURIFixup::KeywordURIFixup(const nsACString & aURIString,
|
|||||||
((foundDots + foundDigits == posWithNoTrailingSlash) ||
|
((foundDots + foundDigits == posWithNoTrailingSlash) ||
|
||||||
(foundColons == 1 && firstColonLoc > lastDotLoc &&
|
(foundColons == 1 && firstColonLoc > lastDotLoc &&
|
||||||
foundDots + foundDigits + foundColons == posWithNoTrailingSlash))) {
|
foundDots + foundDigits + foundColons == posWithNoTrailingSlash))) {
|
||||||
return;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are only colons and only hexadecimal characters ([a-z][0-9])
|
// If there are only colons and only hexadecimal characters ([a-z][0-9])
|
||||||
// enclosed in [], then don't do a keyword lookup
|
// enclosed in [], then don't do a keyword lookup
|
||||||
if (looksLikeIpv6) {
|
if (looksLikeIpv6) {
|
||||||
return;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsresult rv = NS_OK;
|
||||||
// We do keyword lookups if a space or quote preceded the dot, colon
|
// We do keyword lookups if a space or quote preceded the dot, colon
|
||||||
// or question mark (or if the latter were not found)
|
// or question mark (or if the latter were not found)
|
||||||
// or when the host is the same as asciiHost and there are no
|
// or when the host is the same as asciiHost and there are no
|
||||||
@ -1073,11 +1076,7 @@ void nsDefaultURIFixup::KeywordURIFixup(const nsACString & aURIString,
|
|||||||
(isValidAsciiHost && isValidHost && !hasAsciiAlpha &&
|
(isValidAsciiHost && isValidHost && !hasAsciiAlpha &&
|
||||||
host.EqualsIgnoreCase(asciiHost.get()))) {
|
host.EqualsIgnoreCase(asciiHost.get()))) {
|
||||||
|
|
||||||
rv = KeywordToURI(aFixupInfo->mOriginalInput, aPostData,
|
rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo, aPostData);
|
||||||
getter_AddRefs(aFixupInfo->mPreferredURI));
|
|
||||||
if (NS_SUCCEEDED(rv) && aFixupInfo->mPreferredURI) {
|
|
||||||
aFixupInfo->mFixupUsedKeyword = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// ... or if there is no question mark or colon, and there is either no
|
// ... or if there is no question mark or colon, and there is either no
|
||||||
// dot, or exactly 1 and it is the first or last character of the input:
|
// dot, or exactly 1 and it is the first or last character of the input:
|
||||||
@ -1086,17 +1085,14 @@ void nsDefaultURIFixup::KeywordURIFixup(const nsACString & aURIString,
|
|||||||
firstColonLoc == uint32_t(kNotFound) && firstQMarkLoc == uint32_t(kNotFound)) {
|
firstColonLoc == uint32_t(kNotFound) && firstQMarkLoc == uint32_t(kNotFound)) {
|
||||||
|
|
||||||
if (isValidAsciiHost && IsDomainWhitelisted(asciiHost, firstDotLoc)) {
|
if (isValidAsciiHost && IsDomainWhitelisted(asciiHost, firstDotLoc)) {
|
||||||
return;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get here, we don't have a valid URI, or we did but the
|
// If we get here, we don't have a valid URI, or we did but the
|
||||||
// host is not whitelisted, so we do a keyword search *anyway*:
|
// host is not whitelisted, so we do a keyword search *anyway*:
|
||||||
rv = KeywordToURI(aFixupInfo->mOriginalInput, aPostData,
|
rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo, aPostData);
|
||||||
getter_AddRefs(aFixupInfo->mPreferredURI));
|
|
||||||
if (NS_SUCCEEDED(rv) && aFixupInfo->mPreferredURI) {
|
|
||||||
aFixupInfo->mFixupUsedKeyword = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool nsDefaultURIFixup::IsDomainWhitelisted(const nsAutoCString aAsciiHost,
|
bool nsDefaultURIFixup::IsDomainWhitelisted(const nsAutoCString aAsciiHost,
|
||||||
@ -1134,7 +1130,6 @@ nsresult NS_NewURIFixup(nsIURIFixup **aURIFixup)
|
|||||||
NS_IMPL_ISUPPORTS(nsDefaultURIFixupInfo, nsIURIFixupInfo)
|
NS_IMPL_ISUPPORTS(nsDefaultURIFixupInfo, nsIURIFixupInfo)
|
||||||
|
|
||||||
nsDefaultURIFixupInfo::nsDefaultURIFixupInfo(const nsACString& aOriginalInput):
|
nsDefaultURIFixupInfo::nsDefaultURIFixupInfo(const nsACString& aOriginalInput):
|
||||||
mFixupUsedKeyword(false),
|
|
||||||
mFixupChangedProtocol(false),
|
mFixupChangedProtocol(false),
|
||||||
mFixupCreatedAlternateURI(false)
|
mFixupCreatedAlternateURI(false)
|
||||||
{
|
{
|
||||||
@ -1178,9 +1173,16 @@ nsDefaultURIFixupInfo::GetFixedURI(nsIURI** aFixedURI)
|
|||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsDefaultURIFixupInfo::GetFixupUsedKeyword(bool* aOut)
|
nsDefaultURIFixupInfo::GetKeywordProviderName(nsAString& aOut)
|
||||||
{
|
{
|
||||||
*aOut = mFixupUsedKeyword;
|
aOut = mKeywordProviderName;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsDefaultURIFixupInfo::GetKeywordAsSent(nsAString& aOut)
|
||||||
|
{
|
||||||
|
aOut = mKeywordAsSent;
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,9 +30,12 @@ private:
|
|||||||
nsresult FixupURIProtocol(const nsACString& aIn,
|
nsresult FixupURIProtocol(const nsACString& aIn,
|
||||||
nsDefaultURIFixupInfo* aFixupInfo,
|
nsDefaultURIFixupInfo* aFixupInfo,
|
||||||
nsIURI** aURI);
|
nsIURI** aURI);
|
||||||
void KeywordURIFixup(const nsACString &aStringURI,
|
nsresult KeywordURIFixup(const nsACString &aStringURI,
|
||||||
nsDefaultURIFixupInfo* aFixupInfo,
|
nsDefaultURIFixupInfo* aFixupInfo,
|
||||||
nsIInputStream** aPostData);
|
nsIInputStream** aPostData);
|
||||||
|
nsresult TryKeywordFixupForURIInfo(const nsACString &aStringURI,
|
||||||
|
nsDefaultURIFixupInfo* aFixupInfo,
|
||||||
|
nsIInputStream** aPostData);
|
||||||
bool PossiblyByteExpandedFileName(const nsAString& aIn);
|
bool PossiblyByteExpandedFileName(const nsAString& aIn);
|
||||||
bool PossiblyHostPortUrl(const nsACString& aUrl);
|
bool PossiblyHostPortUrl(const nsACString& aUrl);
|
||||||
bool MakeAlternateURI(nsIURI *aURI);
|
bool MakeAlternateURI(nsIURI *aURI);
|
||||||
@ -58,9 +61,10 @@ private:
|
|||||||
nsCOMPtr<nsISupports> mConsumer;
|
nsCOMPtr<nsISupports> mConsumer;
|
||||||
nsCOMPtr<nsIURI> mPreferredURI;
|
nsCOMPtr<nsIURI> mPreferredURI;
|
||||||
nsCOMPtr<nsIURI> mFixedURI;
|
nsCOMPtr<nsIURI> mFixedURI;
|
||||||
bool mFixupUsedKeyword;
|
|
||||||
bool mFixupChangedProtocol;
|
bool mFixupChangedProtocol;
|
||||||
bool mFixupCreatedAlternateURI;
|
bool mFixupCreatedAlternateURI;
|
||||||
|
nsString mKeywordProviderName;
|
||||||
|
nsString mKeywordAsSent;
|
||||||
nsAutoCString mOriginalInput;
|
nsAutoCString mOriginalInput;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
@ -201,6 +201,10 @@
|
|||||||
#include "mozilla/dom/ScriptSettings.h"
|
#include "mozilla/dom/ScriptSettings.h"
|
||||||
#include "mozilla/dom/URLSearchParams.h"
|
#include "mozilla/dom/URLSearchParams.h"
|
||||||
|
|
||||||
|
#ifdef MOZ_TOOLKIT_SEARCH
|
||||||
|
#include "nsIBrowserSearchService.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
|
static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
|
||||||
|
|
||||||
#if defined(DEBUG_bryner) || defined(DEBUG_chb)
|
#if defined(DEBUG_bryner) || defined(DEBUG_chb)
|
||||||
@ -4583,6 +4587,7 @@ nsDocShell::LoadURIWithBase(const char16_t * aURI,
|
|||||||
aLoadFlags &= ~LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
aLoadFlags &= ~LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIURIFixupInfo> fixupInfo;
|
||||||
if (sURIFixup) {
|
if (sURIFixup) {
|
||||||
// Call the fixup object. This will clobber the rv from NS_NewURI
|
// Call the fixup object. This will clobber the rv from NS_NewURI
|
||||||
// above, but that's fine with us. Note that we need to do this even
|
// above, but that's fine with us. Note that we need to do this even
|
||||||
@ -4596,7 +4601,6 @@ nsDocShell::LoadURIWithBase(const char16_t * aURI,
|
|||||||
fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS;
|
fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS;
|
||||||
}
|
}
|
||||||
nsCOMPtr<nsIInputStream> fixupStream;
|
nsCOMPtr<nsIInputStream> fixupStream;
|
||||||
nsCOMPtr<nsIURIFixupInfo> fixupInfo;
|
|
||||||
rv = sURIFixup->GetFixupURIInfo(uriString, fixupFlags,
|
rv = sURIFixup->GetFixupURIInfo(uriString, fixupFlags,
|
||||||
getter_AddRefs(fixupStream),
|
getter_AddRefs(fixupStream),
|
||||||
getter_AddRefs(fixupInfo));
|
getter_AddRefs(fixupInfo));
|
||||||
@ -4607,7 +4611,7 @@ nsDocShell::LoadURIWithBase(const char16_t * aURI,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fixupStream) {
|
if (fixupStream) {
|
||||||
// CreateFixupURI only returns a post data stream if it succeeded
|
// GetFixupURIInfo only returns a post data stream if it succeeded
|
||||||
// and changed the URI, in which case we should override the
|
// and changed the URI, in which case we should override the
|
||||||
// passed-in post data.
|
// passed-in post data.
|
||||||
postStream = fixupStream;
|
postStream = fixupStream;
|
||||||
@ -4666,6 +4670,13 @@ nsDocShell::LoadURIWithBase(const char16_t * aURI,
|
|||||||
loadInfo->SetHeadersStream(aHeaderStream);
|
loadInfo->SetHeadersStream(aHeaderStream);
|
||||||
loadInfo->SetBaseURI(aBaseURI);
|
loadInfo->SetBaseURI(aBaseURI);
|
||||||
|
|
||||||
|
if (fixupInfo) {
|
||||||
|
nsAutoString searchProvider, keyword;
|
||||||
|
fixupInfo->GetKeywordProviderName(searchProvider);
|
||||||
|
fixupInfo->GetKeywordAsSent(keyword);
|
||||||
|
MaybeNotifyKeywordSearchLoading(searchProvider, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
rv = LoadURI(uri, loadInfo, extraFlags, true);
|
rv = LoadURI(uri, loadInfo, extraFlags, true);
|
||||||
|
|
||||||
// Save URI string in case it's needed later when
|
// Save URI string in case it's needed later when
|
||||||
@ -7382,6 +7393,7 @@ nsDocShell::EndPageLoad(nsIWebProgress * aProgress,
|
|||||||
//
|
//
|
||||||
// First try keyword fixup
|
// First try keyword fixup
|
||||||
//
|
//
|
||||||
|
nsAutoString keywordProviderName, keywordAsSent;
|
||||||
if (aStatus == NS_ERROR_UNKNOWN_HOST && mAllowKeywordFixup) {
|
if (aStatus == NS_ERROR_UNKNOWN_HOST && mAllowKeywordFixup) {
|
||||||
bool keywordsEnabled =
|
bool keywordsEnabled =
|
||||||
Preferences::GetBool("keyword.enabled", false);
|
Preferences::GetBool("keyword.enabled", false);
|
||||||
@ -7412,11 +7424,12 @@ nsDocShell::EndPageLoad(nsIWebProgress * aProgress,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (keywordsEnabled && (kNotFound == dotLoc)) {
|
if (keywordsEnabled && (kNotFound == dotLoc)) {
|
||||||
|
nsCOMPtr<nsIURIFixupInfo> info;
|
||||||
// only send non-qualified hosts to the keyword server
|
// only send non-qualified hosts to the keyword server
|
||||||
if (!mOriginalUriString.IsEmpty()) {
|
if (!mOriginalUriString.IsEmpty()) {
|
||||||
sURIFixup->KeywordToURI(mOriginalUriString,
|
sURIFixup->KeywordToURI(mOriginalUriString,
|
||||||
getter_AddRefs(newPostData),
|
getter_AddRefs(newPostData),
|
||||||
getter_AddRefs(newURI));
|
getter_AddRefs(info));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//
|
//
|
||||||
@ -7438,13 +7451,19 @@ nsDocShell::EndPageLoad(nsIWebProgress * aProgress,
|
|||||||
NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) {
|
NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) {
|
||||||
sURIFixup->KeywordToURI(utf8Host,
|
sURIFixup->KeywordToURI(utf8Host,
|
||||||
getter_AddRefs(newPostData),
|
getter_AddRefs(newPostData),
|
||||||
getter_AddRefs(newURI));
|
getter_AddRefs(info));
|
||||||
} else {
|
} else {
|
||||||
sURIFixup->KeywordToURI(host,
|
sURIFixup->KeywordToURI(host,
|
||||||
getter_AddRefs(newPostData),
|
getter_AddRefs(newPostData),
|
||||||
getter_AddRefs(newURI));
|
getter_AddRefs(info));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info->GetPreferredURI(getter_AddRefs(newURI));
|
||||||
|
if (newURI) {
|
||||||
|
info->GetKeywordAsSent(keywordAsSent);
|
||||||
|
info->GetKeywordProviderName(keywordProviderName);
|
||||||
|
}
|
||||||
} // end keywordsEnabled
|
} // end keywordsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7477,6 +7496,8 @@ nsDocShell::EndPageLoad(nsIWebProgress * aProgress,
|
|||||||
if (doCreateAlternate) {
|
if (doCreateAlternate) {
|
||||||
newURI = nullptr;
|
newURI = nullptr;
|
||||||
newPostData = nullptr;
|
newPostData = nullptr;
|
||||||
|
keywordProviderName.Truncate();
|
||||||
|
keywordAsSent.Truncate();
|
||||||
sURIFixup->CreateFixupURI(oldSpec,
|
sURIFixup->CreateFixupURI(oldSpec,
|
||||||
nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI,
|
nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI,
|
||||||
getter_AddRefs(newPostData),
|
getter_AddRefs(newPostData),
|
||||||
@ -7497,6 +7518,10 @@ nsDocShell::EndPageLoad(nsIWebProgress * aProgress,
|
|||||||
newURI->GetSpec(newSpec);
|
newURI->GetSpec(newSpec);
|
||||||
NS_ConvertUTF8toUTF16 newSpecW(newSpec);
|
NS_ConvertUTF8toUTF16 newSpecW(newSpec);
|
||||||
|
|
||||||
|
// This notification is meant for Firefox Health Report so it
|
||||||
|
// can increment counts from the search engine
|
||||||
|
MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent);
|
||||||
|
|
||||||
return LoadURI(newSpecW.get(), // URI string
|
return LoadURI(newSpecW.get(), // URI string
|
||||||
LOAD_FLAGS_NONE, // Load flags
|
LOAD_FLAGS_NONE, // Load flags
|
||||||
nullptr, // Referring URI
|
nullptr, // Referring URI
|
||||||
@ -13508,3 +13533,36 @@ nsDocShell::GetURLSearchParams()
|
|||||||
{
|
{
|
||||||
return mURLSearchParams;
|
return mURLSearchParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString &aProvider,
|
||||||
|
const nsString &aKeyword) {
|
||||||
|
|
||||||
|
if (aProvider.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (XRE_GetProcessType() == GeckoProcessType_Content) {
|
||||||
|
dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
|
||||||
|
if (contentChild) {
|
||||||
|
contentChild->SendNotifyKeywordSearchLoading(aProvider, aKeyword);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MOZ_TOOLKIT_SEARCH
|
||||||
|
nsCOMPtr<nsIBrowserSearchService> searchSvc = do_GetService("@mozilla.org/browser/search-service;1");
|
||||||
|
if (searchSvc) {
|
||||||
|
nsCOMPtr<nsISearchEngine> searchEngine;
|
||||||
|
searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine));
|
||||||
|
if (searchEngine) {
|
||||||
|
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
|
||||||
|
if (obsSvc) {
|
||||||
|
// Note that "keyword-search" refers to a search via the url
|
||||||
|
// bar, not a bookmarks keyword search.
|
||||||
|
obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@ -978,6 +978,9 @@ private:
|
|||||||
nsIDocShellTreeItem* aOriginalRequestor,
|
nsIDocShellTreeItem* aOriginalRequestor,
|
||||||
nsIDocShellTreeItem** _retval);
|
nsIDocShellTreeItem** _retval);
|
||||||
|
|
||||||
|
// Notify consumers of a search being loaded through the observer service:
|
||||||
|
void MaybeNotifyKeywordSearchLoading(const nsString &aProvider, const nsString &aKeyword);
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
// We're counting the number of |nsDocShells| to help find leaks
|
// We're counting the number of |nsDocShells| to help find leaks
|
||||||
static unsigned long gNumberOfDocShells;
|
static unsigned long gNumberOfDocShells;
|
||||||
|
@ -12,7 +12,7 @@ interface nsIInputStream;
|
|||||||
/**
|
/**
|
||||||
* Interface indicating what we found/corrected when fixing up a URI
|
* Interface indicating what we found/corrected when fixing up a URI
|
||||||
*/
|
*/
|
||||||
[scriptable, uuid(62aac1e0-3da8-4920-bd1b-a54fc2e2eb24)]
|
[scriptable, uuid(4819f183-b532-4932-ac09-b309cd853be7)]
|
||||||
interface nsIURIFixupInfo : nsISupports
|
interface nsIURIFixupInfo : nsISupports
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -36,9 +36,16 @@ interface nsIURIFixupInfo : nsISupports
|
|||||||
readonly attribute nsIURI fixedURI;
|
readonly attribute nsIURI fixedURI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the preferred option ended up using a keyword search.
|
* The name of the keyword search provider used to provide a keyword search;
|
||||||
|
* empty string if no keyword search was done.
|
||||||
*/
|
*/
|
||||||
readonly attribute boolean fixupUsedKeyword;
|
readonly attribute AString keywordProviderName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keyword as used for the search (post trimming etc.)
|
||||||
|
* empty string if no keyword search was done.
|
||||||
|
*/
|
||||||
|
readonly attribute AString keywordAsSent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether we changed the protocol instead of using one from the input as-is.
|
* Whether we changed the protocol instead of using one from the input as-is.
|
||||||
@ -63,7 +70,7 @@ interface nsIURIFixupInfo : nsISupports
|
|||||||
/**
|
/**
|
||||||
* Interface implemented by objects capable of fixing up strings into URIs
|
* Interface implemented by objects capable of fixing up strings into URIs
|
||||||
*/
|
*/
|
||||||
[scriptable, uuid(49298f2b-3630-4874-aecc-522300a7fead)]
|
[scriptable, uuid(d2a78abe-e678-4103-9bcc-dd1377460c44)]
|
||||||
interface nsIURIFixup : nsISupports
|
interface nsIURIFixup : nsISupports
|
||||||
{
|
{
|
||||||
/** No fixup flags. */
|
/** No fixup flags. */
|
||||||
@ -146,7 +153,7 @@ interface nsIURIFixup : nsISupports
|
|||||||
* @throws NS_ERROR_FAILURE if the resulting URI requires submission of POST
|
* @throws NS_ERROR_FAILURE if the resulting URI requires submission of POST
|
||||||
* data and aPostData is null.
|
* data and aPostData is null.
|
||||||
*/
|
*/
|
||||||
nsIURI keywordToURI(in AUTF8String aKeyword,
|
nsIURIFixupInfo keywordToURI(in AUTF8String aKeyword,
|
||||||
[optional] out nsIInputStream aPostData);
|
[optional] out nsIInputStream aPostData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -95,7 +95,6 @@ skip-if = e10s # Bug ?????? - event handler checks event.target is the content d
|
|||||||
[browser_onbeforeunload_navigation.js]
|
[browser_onbeforeunload_navigation.js]
|
||||||
skip-if = e10s
|
skip-if = e10s
|
||||||
[browser_search_notification.js]
|
[browser_search_notification.js]
|
||||||
skip-if = e10s
|
|
||||||
[browser_timelineMarkers-01.js]
|
[browser_timelineMarkers-01.js]
|
||||||
[browser_timelineMarkers-02.js]
|
[browser_timelineMarkers-02.js]
|
||||||
skip-if = e10s
|
skip-if = e10s
|
||||||
|
@ -4,6 +4,27 @@
|
|||||||
function test() {
|
function test() {
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
|
const kSearchEngineID = "test_urifixup_search_engine";
|
||||||
|
const kSearchEngineURL = "http://localhost/?search={searchTerms}";
|
||||||
|
Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get",
|
||||||
|
kSearchEngineURL);
|
||||||
|
|
||||||
|
let oldDefaultEngine = Services.search.defaultEngine;
|
||||||
|
Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID);
|
||||||
|
|
||||||
|
let selectedName = Services.search.defaultEngine.name;
|
||||||
|
is(selectedName, kSearchEngineID, "Check fake search engine is selected");
|
||||||
|
|
||||||
|
registerCleanupFunction(function() {
|
||||||
|
if (oldDefaultEngine) {
|
||||||
|
Services.search.defaultEngine = oldDefaultEngine;
|
||||||
|
}
|
||||||
|
let engine = Services.search.getEngineByName(kSearchEngineID);
|
||||||
|
if (engine) {
|
||||||
|
Services.search.removeEngine(engine);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let tab = gBrowser.addTab();
|
let tab = gBrowser.addTab();
|
||||||
gBrowser.selectedTab = tab;
|
gBrowser.selectedTab = tab;
|
||||||
|
|
||||||
|
@ -530,7 +530,7 @@ function run_test() {
|
|||||||
|
|
||||||
// Check booleans on input:
|
// Check booleans on input:
|
||||||
let couldDoKeywordLookup = flags & urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
|
let couldDoKeywordLookup = flags & urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
|
||||||
do_check_eq(info.fixupUsedKeyword, couldDoKeywordLookup && expectKeywordLookup);
|
do_check_eq(!!info.keywordProviderName, couldDoKeywordLookup && expectKeywordLookup);
|
||||||
do_check_eq(info.fixupChangedProtocol, expectProtocolChange);
|
do_check_eq(info.fixupChangedProtocol, expectProtocolChange);
|
||||||
do_check_eq(info.fixupCreatedAlternateURI, makeAlternativeURI && alternativeURI != null);
|
do_check_eq(info.fixupCreatedAlternateURI, makeAlternativeURI && alternativeURI != null);
|
||||||
|
|
||||||
|
@ -185,6 +185,10 @@ using namespace mozilla::system;
|
|||||||
#include "mozilla/Sandbox.h"
|
#include "mozilla/Sandbox.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef MOZ_TOOLKIT_SEARCH
|
||||||
|
#include "nsIBrowserSearchService.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
|
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
|
||||||
static const char* sClipboardTextFlavors[] = { kUnicodeMime };
|
static const char* sClipboardTextFlavors[] = { kUnicodeMime };
|
||||||
|
|
||||||
@ -3805,7 +3809,9 @@ ContentParent::RecvSetFakeVolumeState(const nsString& fsName, const int32_t& fsS
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ContentParent::RecvKeywordToURI(const nsCString& aKeyword, OptionalInputStreamParams* aPostData,
|
ContentParent::RecvKeywordToURI(const nsCString& aKeyword,
|
||||||
|
nsString* aProviderName,
|
||||||
|
OptionalInputStreamParams* aPostData,
|
||||||
OptionalURIParams* aURI)
|
OptionalURIParams* aURI)
|
||||||
{
|
{
|
||||||
nsCOMPtr<nsIURIFixup> fixup = do_GetService(NS_URIFIXUP_CONTRACTID);
|
nsCOMPtr<nsIURIFixup> fixup = do_GetService(NS_URIFIXUP_CONTRACTID);
|
||||||
@ -3814,20 +3820,45 @@ ContentParent::RecvKeywordToURI(const nsCString& aKeyword, OptionalInputStreamPa
|
|||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIInputStream> postData;
|
nsCOMPtr<nsIInputStream> postData;
|
||||||
nsCOMPtr<nsIURI> uri;
|
nsCOMPtr<nsIURIFixupInfo> info;
|
||||||
|
|
||||||
if (NS_FAILED(fixup->KeywordToURI(aKeyword, getter_AddRefs(postData),
|
if (NS_FAILED(fixup->KeywordToURI(aKeyword, getter_AddRefs(postData),
|
||||||
getter_AddRefs(uri)))) {
|
getter_AddRefs(info)))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
info->GetKeywordProviderName(*aProviderName);
|
||||||
|
|
||||||
nsTArray<mozilla::ipc::FileDescriptor> fds;
|
nsTArray<mozilla::ipc::FileDescriptor> fds;
|
||||||
SerializeInputStream(postData, *aPostData, fds);
|
SerializeInputStream(postData, *aPostData, fds);
|
||||||
MOZ_ASSERT(fds.IsEmpty());
|
MOZ_ASSERT(fds.IsEmpty());
|
||||||
|
|
||||||
|
nsCOMPtr<nsIURI> uri;
|
||||||
|
info->GetPreferredURI(getter_AddRefs(uri));
|
||||||
SerializeURI(uri, *aURI);
|
SerializeURI(uri, *aURI);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ContentParent::RecvNotifyKeywordSearchLoading(const nsString &aProvider,
|
||||||
|
const nsString &aKeyword) {
|
||||||
|
#ifdef MOZ_TOOLKIT_SEARCH
|
||||||
|
nsCOMPtr<nsIBrowserSearchService> searchSvc = do_GetService("@mozilla.org/browser/search-service;1");
|
||||||
|
if (searchSvc) {
|
||||||
|
nsCOMPtr<nsISearchEngine> searchEngine;
|
||||||
|
searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine));
|
||||||
|
if (searchEngine) {
|
||||||
|
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
|
||||||
|
if (obsSvc) {
|
||||||
|
// Note that "keyword-search" refers to a search via the url
|
||||||
|
// bar, not a bookmarks keyword search.
|
||||||
|
obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ContentParent::ShouldContinueFromReplyTimeout()
|
ContentParent::ShouldContinueFromReplyTimeout()
|
||||||
{
|
{
|
||||||
|
@ -631,9 +631,14 @@ private:
|
|||||||
|
|
||||||
virtual bool RecvSetFakeVolumeState(const nsString& fsName, const int32_t& fsState) MOZ_OVERRIDE;
|
virtual bool RecvSetFakeVolumeState(const nsString& fsName, const int32_t& fsState) MOZ_OVERRIDE;
|
||||||
|
|
||||||
virtual bool RecvKeywordToURI(const nsCString& aKeyword, OptionalInputStreamParams* aPostData,
|
virtual bool RecvKeywordToURI(const nsCString& aKeyword,
|
||||||
|
nsString* aProviderName,
|
||||||
|
OptionalInputStreamParams* aPostData,
|
||||||
OptionalURIParams* aURI) MOZ_OVERRIDE;
|
OptionalURIParams* aURI) MOZ_OVERRIDE;
|
||||||
|
|
||||||
|
virtual bool RecvNotifyKeywordSearchLoading(const nsString &aProvider,
|
||||||
|
const nsString &aKeyword) MOZ_OVERRIDE;
|
||||||
|
|
||||||
virtual void ProcessingError(Result what) MOZ_OVERRIDE;
|
virtual void ProcessingError(Result what) MOZ_OVERRIDE;
|
||||||
|
|
||||||
virtual bool RecvAllocateLayerTreeId(uint64_t* aId) MOZ_OVERRIDE;
|
virtual bool RecvAllocateLayerTreeId(uint64_t* aId) MOZ_OVERRIDE;
|
||||||
|
@ -670,7 +670,9 @@ parent:
|
|||||||
async SetFakeVolumeState(nsString fsName, int32_t fsState);
|
async SetFakeVolumeState(nsString fsName, int32_t fsState);
|
||||||
|
|
||||||
sync KeywordToURI(nsCString keyword)
|
sync KeywordToURI(nsCString keyword)
|
||||||
returns (OptionalInputStreamParams postData, OptionalURIParams uri);
|
returns (nsString providerName, OptionalInputStreamParams postData, OptionalURIParams uri);
|
||||||
|
|
||||||
|
sync NotifyKeywordSearchLoading(nsString providerName, nsString keyword);
|
||||||
|
|
||||||
// Tell the compositor to allocate a layer tree id for nested remote mozbrowsers.
|
// Tell the compositor to allocate a layer tree id for nested remote mozbrowsers.
|
||||||
sync AllocateLayerTreeId()
|
sync AllocateLayerTreeId()
|
||||||
|
@ -126,6 +126,9 @@ DEFINES['BIN_SUFFIX'] = '"%s"' % CONFIG['BIN_SUFFIX']
|
|||||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk2', 'gonk', 'qt'):
|
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk2', 'gonk', 'qt'):
|
||||||
DEFINES['MOZ_ENABLE_FREETYPE'] = True
|
DEFINES['MOZ_ENABLE_FREETYPE'] = True
|
||||||
|
|
||||||
|
if CONFIG['MOZ_TOOLKIT_SEARCH']:
|
||||||
|
DEFINES['MOZ_TOOLKIT_SEARCH'] = True
|
||||||
|
|
||||||
for var in ('MOZ_PERMISSIONS', 'MOZ_CHILD_PERMISSIONS'):
|
for var in ('MOZ_PERMISSIONS', 'MOZ_CHILD_PERMISSIONS'):
|
||||||
if CONFIG[var]:
|
if CONFIG[var]:
|
||||||
DEFINES[var] = True
|
DEFINES[var] = True
|
||||||
|
@ -9,6 +9,7 @@ var NotificationTest = (function () {
|
|||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
// turn on testing pref (used by notification.cpp, and mock the alerts
|
// turn on testing pref (used by notification.cpp, and mock the alerts
|
||||||
SpecialPowers.setBoolPref("notification.prompt.testing", true);
|
SpecialPowers.setBoolPref("notification.prompt.testing", true);
|
||||||
|
SpecialPowers.setAllAppsLaunchable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function teardown_testing_env() {
|
function teardown_testing_env() {
|
||||||
|
@ -719,12 +719,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void StackWalkCallback(void* aPc, void* aSp, void* aClosure)
|
static void StackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp,
|
||||||
|
void* aClosure)
|
||||||
{
|
{
|
||||||
StackTrace* st = (StackTrace*) aClosure;
|
StackTrace* st = (StackTrace*) aClosure;
|
||||||
MOZ_ASSERT(st->mLength < MaxFrames);
|
MOZ_ASSERT(st->mLength < MaxFrames);
|
||||||
st->mPcs[st->mLength] = aPc;
|
st->mPcs[st->mLength] = aPc;
|
||||||
st->mLength++;
|
st->mLength++;
|
||||||
|
MOZ_ASSERT(st->mLength == aFrameNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Cmp(const void* aA, const void* aB)
|
static int Cmp(const void* aA, const void* aB)
|
||||||
@ -755,7 +757,7 @@ StackTrace::Print(const Writer& aWriter, CodeAddressService* aLocService) const
|
|||||||
static const size_t buflen = 1024;
|
static const size_t buflen = 1024;
|
||||||
char buf[buflen];
|
char buf[buflen];
|
||||||
for (uint32_t i = 0; i < mLength; i++) {
|
for (uint32_t i = 0; i < mLength; i++) {
|
||||||
aLocService->GetLocation(Pc(i), buf, buflen);
|
aLocService->GetLocation(i + 1, Pc(i), buf, buflen);
|
||||||
aWriter.Write(" %s\n", buf);
|
aWriter.Write(" %s\n", buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1574,7 +1576,8 @@ Options::BadArg(const char* aArg)
|
|||||||
|
|
||||||
#ifdef XP_MACOSX
|
#ifdef XP_MACOSX
|
||||||
static void
|
static void
|
||||||
NopStackWalkCallback(void* aPc, void* aSp, void* aClosure)
|
NopStackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp,
|
||||||
|
void* aClosure)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -27,7 +27,6 @@ import org.mozilla.gecko.db.BrowserContract.Combined;
|
|||||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||||
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
|
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
|
||||||
import org.mozilla.gecko.db.BrowserDB;
|
import org.mozilla.gecko.db.BrowserDB;
|
||||||
import org.mozilla.gecko.db.DBUtils;
|
|
||||||
import org.mozilla.gecko.db.SuggestedSites;
|
import org.mozilla.gecko.db.SuggestedSites;
|
||||||
import org.mozilla.gecko.distribution.Distribution;
|
import org.mozilla.gecko.distribution.Distribution;
|
||||||
import org.mozilla.gecko.favicons.Favicons;
|
import org.mozilla.gecko.favicons.Favicons;
|
||||||
@ -47,6 +46,7 @@ import org.mozilla.gecko.health.SessionInformation;
|
|||||||
import org.mozilla.gecko.home.BrowserSearch;
|
import org.mozilla.gecko.home.BrowserSearch;
|
||||||
import org.mozilla.gecko.home.HomeBanner;
|
import org.mozilla.gecko.home.HomeBanner;
|
||||||
import org.mozilla.gecko.home.HomePager;
|
import org.mozilla.gecko.home.HomePager;
|
||||||
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
|
||||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||||
import org.mozilla.gecko.home.HomePanelsManager;
|
import org.mozilla.gecko.home.HomePanelsManager;
|
||||||
import org.mozilla.gecko.home.SearchEngine;
|
import org.mozilla.gecko.home.SearchEngine;
|
||||||
@ -74,6 +74,7 @@ import org.mozilla.gecko.util.StringUtils;
|
|||||||
import org.mozilla.gecko.util.ThreadUtils;
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
import org.mozilla.gecko.util.UIAsyncTask;
|
import org.mozilla.gecko.util.UIAsyncTask;
|
||||||
import org.mozilla.gecko.widget.ButtonToast;
|
import org.mozilla.gecko.widget.ButtonToast;
|
||||||
|
import org.mozilla.gecko.widget.ButtonToast.ToastListener;
|
||||||
import org.mozilla.gecko.widget.GeckoActionProvider;
|
import org.mozilla.gecko.widget.GeckoActionProvider;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@ -139,6 +140,7 @@ public class BrowserApp extends GeckoApp
|
|||||||
BrowserSearch.OnEditSuggestionListener,
|
BrowserSearch.OnEditSuggestionListener,
|
||||||
HomePager.OnNewTabsListener,
|
HomePager.OnNewTabsListener,
|
||||||
OnUrlOpenListener,
|
OnUrlOpenListener,
|
||||||
|
OnUrlOpenInBackgroundListener,
|
||||||
ActionModeCompat.Presenter,
|
ActionModeCompat.Presenter,
|
||||||
LayoutInflater.Factory {
|
LayoutInflater.Factory {
|
||||||
private static final String LOGTAG = "GeckoBrowserApp";
|
private static final String LOGTAG = "GeckoBrowserApp";
|
||||||
@ -1771,7 +1773,13 @@ public class BrowserApp extends GeckoApp
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to switch to an open tab with the given URL.
|
* Attempts to switch to an open tab with the given URL.
|
||||||
|
* <p>
|
||||||
|
* If the tab exists, this method cancels any in-progress editing as well as
|
||||||
|
* calling {@link Tabs#selectTab(int)}.
|
||||||
*
|
*
|
||||||
|
* @param url of tab to switch to.
|
||||||
|
* @param flags to obey: if {@link OnUrlOpenListener.Flags#ALLOW_SWITCH_TO_TAB}
|
||||||
|
* is not present, return false.
|
||||||
* @return true if we successfully switched to a tab, false otherwise.
|
* @return true if we successfully switched to a tab, false otherwise.
|
||||||
*/
|
*/
|
||||||
private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
|
private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
|
||||||
@ -1792,6 +1800,26 @@ public class BrowserApp extends GeckoApp
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return maybeSwitchToTab(tab.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to switch to an open tab with the given unique tab ID.
|
||||||
|
* <p>
|
||||||
|
* If the tab exists, this method cancels any in-progress editing as well as
|
||||||
|
* calling {@link Tabs#selectTab(int)}.
|
||||||
|
*
|
||||||
|
* @param id of tab to switch to.
|
||||||
|
* @return true if we successfully switched to the tab, false otherwise.
|
||||||
|
*/
|
||||||
|
private boolean maybeSwitchToTab(int id) {
|
||||||
|
final Tabs tabs = Tabs.getInstance();
|
||||||
|
final Tab tab = tabs.getTab(id);
|
||||||
|
|
||||||
|
if (tab == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the target tab to null so it does not get selected (on editing
|
// Set the target tab to null so it does not get selected (on editing
|
||||||
// mode exit) in lieu of the tab we are about to select.
|
// mode exit) in lieu of the tab we are about to select.
|
||||||
mTargetTabForEditingMode = null;
|
mTargetTabForEditingMode = null;
|
||||||
@ -3088,6 +3116,53 @@ public class BrowserApp extends GeckoApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HomePager.OnUrlOpenInBackgroundListener
|
||||||
|
@Override
|
||||||
|
public void onUrlOpenInBackground(final String url, EnumSet<OnUrlOpenInBackgroundListener.Flags> flags) {
|
||||||
|
if (url == null) {
|
||||||
|
throw new IllegalArgumentException("url must not be null");
|
||||||
|
}
|
||||||
|
if (flags == null) {
|
||||||
|
throw new IllegalArgumentException("flags must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isPrivate = flags.contains(OnUrlOpenInBackgroundListener.Flags.PRIVATE);
|
||||||
|
|
||||||
|
int loadFlags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
|
||||||
|
if (isPrivate) {
|
||||||
|
loadFlags |= Tabs.LOADURL_PRIVATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Tab newTab = Tabs.getInstance().loadUrl(url, loadFlags);
|
||||||
|
|
||||||
|
// We switch to the desired tab by unique ID, which closes any window
|
||||||
|
// for a race between opening the tab and closing it, and switching to
|
||||||
|
// it. We could also switch to the Tab explicitly, but we don't want to
|
||||||
|
// hold a reference to the Tab itself in the anonymous listener class.
|
||||||
|
final int newTabId = newTab.getId();
|
||||||
|
|
||||||
|
final ToastListener listener = new ButtonToast.ToastListener() {
|
||||||
|
@Override
|
||||||
|
public void onButtonClicked() {
|
||||||
|
maybeSwitchToTab(newTabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onToastHidden(ButtonToast.ReasonHidden reason) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
final String message = isPrivate ?
|
||||||
|
getResources().getString(R.string.new_private_tab_opened) :
|
||||||
|
getResources().getString(R.string.new_tab_opened);
|
||||||
|
final String buttonMessage = getResources().getString(R.string.switch_button_message);
|
||||||
|
getButtonToast().show(false,
|
||||||
|
message,
|
||||||
|
ButtonToast.LENGTH_SHORT,
|
||||||
|
buttonMessage,
|
||||||
|
R.drawable.switch_button_icon,
|
||||||
|
listener);
|
||||||
|
}
|
||||||
|
|
||||||
// BrowserSearch.OnSearchListener
|
// BrowserSearch.OnSearchListener
|
||||||
@Override
|
@Override
|
||||||
public void onSearch(SearchEngine engine, String text) {
|
public void onSearch(SearchEngine engine, String text) {
|
||||||
|
@ -48,6 +48,7 @@ GARBAGE += \
|
|||||||
classes.dex \
|
classes.dex \
|
||||||
gecko.ap_ \
|
gecko.ap_ \
|
||||||
res/values/strings.xml \
|
res/values/strings.xml \
|
||||||
|
res/raw/browsersearch.json \
|
||||||
res/raw/suggestedsites.json \
|
res/raw/suggestedsites.json \
|
||||||
.aapt.deps \
|
.aapt.deps \
|
||||||
fennec_ids.txt \
|
fennec_ids.txt \
|
||||||
@ -259,11 +260,12 @@ $(ANDROID_GENERATED_RESFILES): $(call mkdir_deps,$(sort $(dir $(ANDROID_GENERATE
|
|||||||
|
|
||||||
|
|
||||||
# This .deps pattern saves an invocation of the sub-Make: the single
|
# This .deps pattern saves an invocation of the sub-Make: the single
|
||||||
# invocation generates both strings.xml and suggestedsites.json. The
|
# invocation generates strings.xml, browsersearch.json, and
|
||||||
# trailing semi-colon defines an empty recipe: defining no recipe at
|
# suggestedsites.json. The trailing semi-colon defines an empty
|
||||||
# all causes Make to treat the target differently, in a way that
|
# recipe: defining no recipe at all causes Make to treat the target
|
||||||
# defeats our dependencies.
|
# differently, in a way that defeats our dependencies.
|
||||||
res/values/strings.xml: .locales.deps ;
|
res/values/strings.xml: .locales.deps ;
|
||||||
|
res/raw/browsersearch.json: .locales.deps ;
|
||||||
res/raw/suggestedsites.json: .locales.deps ;
|
res/raw/suggestedsites.json: .locales.deps ;
|
||||||
|
|
||||||
all_resources = \
|
all_resources = \
|
||||||
|
@ -101,8 +101,27 @@ public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
|
|||||||
|
|
||||||
final RemoteClient client = clients.get(groupPosition);
|
final RemoteClient client = clients.get(groupPosition);
|
||||||
|
|
||||||
|
// UI elements whose state depends on isExpanded, roughly from left to
|
||||||
|
// right: device type icon; client name text color; expanded state
|
||||||
|
// indicator.
|
||||||
|
final int deviceTypeResId;
|
||||||
|
final int textColorResId;
|
||||||
|
final int deviceExpandedResId;
|
||||||
|
|
||||||
|
if (isExpanded && !client.tabs.isEmpty()) {
|
||||||
|
deviceTypeResId = "desktop".equals(client.deviceType) ? R.drawable.sync_desktop : R.drawable.sync_mobile;
|
||||||
|
textColorResId = R.color.home_text_color;
|
||||||
|
deviceExpandedResId = R.drawable.home_group_expanded;
|
||||||
|
} else {
|
||||||
|
deviceTypeResId = "desktop".equals(client.deviceType) ? R.drawable.sync_desktop_inactive : R.drawable.sync_mobile_inactive;
|
||||||
|
textColorResId = R.color.home_text_color_disabled;
|
||||||
|
deviceExpandedResId = R.drawable.home_group_collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now update the UI.
|
||||||
final TextView nameView = (TextView) view.findViewById(R.id.client);
|
final TextView nameView = (TextView) view.findViewById(R.id.client);
|
||||||
nameView.setText(client.name);
|
nameView.setText(client.name);
|
||||||
|
nameView.setTextColor(context.getResources().getColor(textColorResId));
|
||||||
|
|
||||||
final TextView lastModifiedView = (TextView) view.findViewById(R.id.last_synced);
|
final TextView lastModifiedView = (TextView) view.findViewById(R.id.last_synced);
|
||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
@ -113,22 +132,13 @@ public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
|
|||||||
// Therefore, we must handle null.
|
// Therefore, we must handle null.
|
||||||
final ImageView deviceTypeView = (ImageView) view.findViewById(R.id.device_type);
|
final ImageView deviceTypeView = (ImageView) view.findViewById(R.id.device_type);
|
||||||
if (deviceTypeView != null) {
|
if (deviceTypeView != null) {
|
||||||
if ("desktop".equals(client.deviceType)) {
|
deviceTypeView.setImageResource(deviceTypeResId);
|
||||||
deviceTypeView.setBackgroundResource(R.drawable.sync_desktop);
|
|
||||||
} else {
|
|
||||||
deviceTypeView.setBackgroundResource(R.drawable.sync_mobile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final ImageView deviceExpandedView = (ImageView) view.findViewById(R.id.device_expanded);
|
final ImageView deviceExpandedView = (ImageView) view.findViewById(R.id.device_expanded);
|
||||||
if (deviceExpandedView != null) {
|
if (deviceExpandedView != null) {
|
||||||
// If there are no tabs to display, don't show an indicator at all.
|
// If there are no tabs to display, don't show an indicator at all.
|
||||||
if (client.tabs.isEmpty()) {
|
deviceExpandedView.setImageResource(client.tabs.isEmpty() ? 0 : deviceExpandedResId);
|
||||||
deviceExpandedView.setBackgroundResource(0);
|
|
||||||
} else {
|
|
||||||
final int resourceId = isExpanded ? R.drawable.home_group_expanded : R.drawable.home_group_collapsed;
|
|
||||||
deviceExpandedView.setBackgroundResource(resourceId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
|
@ -5,30 +5,29 @@
|
|||||||
|
|
||||||
package org.mozilla.gecko.home;
|
package org.mozilla.gecko.home;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.mozilla.gecko.EditBookmarkDialog;
|
import org.mozilla.gecko.EditBookmarkDialog;
|
||||||
import org.mozilla.gecko.GeckoApp;
|
|
||||||
import org.mozilla.gecko.GeckoAppShell;
|
import org.mozilla.gecko.GeckoAppShell;
|
||||||
import org.mozilla.gecko.GeckoEvent;
|
import org.mozilla.gecko.GeckoEvent;
|
||||||
import org.mozilla.gecko.GeckoProfile;
|
import org.mozilla.gecko.GeckoProfile;
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.ReaderModeUtils;
|
import org.mozilla.gecko.ReaderModeUtils;
|
||||||
import org.mozilla.gecko.Tab;
|
|
||||||
import org.mozilla.gecko.Tabs;
|
|
||||||
import org.mozilla.gecko.Telemetry;
|
import org.mozilla.gecko.Telemetry;
|
||||||
import org.mozilla.gecko.TelemetryContract;
|
import org.mozilla.gecko.TelemetryContract;
|
||||||
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
|
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
|
||||||
import org.mozilla.gecko.db.BrowserDB;
|
import org.mozilla.gecko.db.BrowserDB;
|
||||||
import org.mozilla.gecko.favicons.Favicons;
|
import org.mozilla.gecko.favicons.Favicons;
|
||||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||||
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
|
||||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||||
import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
|
import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
|
||||||
import org.mozilla.gecko.util.Clipboard;
|
import org.mozilla.gecko.util.Clipboard;
|
||||||
import org.mozilla.gecko.util.StringUtils;
|
import org.mozilla.gecko.util.StringUtils;
|
||||||
import org.mozilla.gecko.util.ThreadUtils;
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
import org.mozilla.gecko.util.UIAsyncTask;
|
import org.mozilla.gecko.util.UIAsyncTask;
|
||||||
import org.mozilla.gecko.widget.ButtonToast;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
@ -72,6 +71,9 @@ public abstract class HomeFragment extends Fragment {
|
|||||||
// On URL open listener
|
// On URL open listener
|
||||||
protected OnUrlOpenListener mUrlOpenListener;
|
protected OnUrlOpenListener mUrlOpenListener;
|
||||||
|
|
||||||
|
// Helper for opening a tab in the background.
|
||||||
|
private OnUrlOpenInBackgroundListener mUrlOpenInBackgroundListener;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
@ -82,12 +84,20 @@ public abstract class HomeFragment extends Fragment {
|
|||||||
throw new ClassCastException(activity.toString()
|
throw new ClassCastException(activity.toString()
|
||||||
+ " must implement HomePager.OnUrlOpenListener");
|
+ " must implement HomePager.OnUrlOpenListener");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mUrlOpenInBackgroundListener = (OnUrlOpenInBackgroundListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement HomePager.OnUrlOpenInBackgroundListener");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDetach() {
|
public void onDetach() {
|
||||||
super.onDetach();
|
super.onDetach();
|
||||||
mUrlOpenListener = null;
|
mUrlOpenListener = null;
|
||||||
|
mUrlOpenInBackgroundListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -205,40 +215,23 @@ public abstract class HomeFragment extends Fragment {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
|
// Some pinned site items have "user-entered" urls. URLs entered in
|
||||||
final boolean isPrivate = (item.getItemId() == R.id.home_open_private_tab);
|
// the PinSiteDialog are wrapped in a special URI until we can get a
|
||||||
if (isPrivate) {
|
// valid URL. If the url is a user-entered url, decode the URL
|
||||||
flags |= Tabs.LOADURL_PRIVATE;
|
// before loading it.
|
||||||
|
final String url = StringUtils.decodeUserEnteredUrl(info.isInReadingList()
|
||||||
|
? ReaderModeUtils.getAboutReaderForUrl(info.url)
|
||||||
|
: info.url);
|
||||||
|
|
||||||
|
final EnumSet<OnUrlOpenInBackgroundListener.Flags> flags = EnumSet.noneOf(OnUrlOpenInBackgroundListener.Flags.class);
|
||||||
|
if (item.getItemId() == R.id.home_open_private_tab) {
|
||||||
|
flags.add(OnUrlOpenInBackgroundListener.Flags.PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mUrlOpenInBackgroundListener.onUrlOpenInBackground(url, flags);
|
||||||
|
|
||||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
|
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
|
||||||
|
|
||||||
final String url = (info.isInReadingList() ? ReaderModeUtils.getAboutReaderForUrl(info.url) : info.url);
|
|
||||||
|
|
||||||
// Some pinned site items have "user-entered" urls. URLs entered in the PinSiteDialog are wrapped in
|
|
||||||
// a special URI until we can get a valid URL. If the url is a user-entered url, decode the URL before loading it.
|
|
||||||
final Tab newTab = Tabs.getInstance().loadUrl(StringUtils.decodeUserEnteredUrl(url), flags);
|
|
||||||
final int newTabId = newTab.getId(); // We don't want to hold a reference to the Tab.
|
|
||||||
|
|
||||||
final String message = isPrivate ?
|
|
||||||
getResources().getString(R.string.new_private_tab_opened) :
|
|
||||||
getResources().getString(R.string.new_tab_opened);
|
|
||||||
final String buttonMessage = getResources().getString(R.string.switch_button_message);
|
|
||||||
final GeckoApp geckoApp = (GeckoApp) context;
|
|
||||||
geckoApp.getButtonToast().show(false,
|
|
||||||
message,
|
|
||||||
ButtonToast.LENGTH_SHORT,
|
|
||||||
buttonMessage,
|
|
||||||
R.drawable.switch_button_icon,
|
|
||||||
new ButtonToast.ToastListener() {
|
|
||||||
@Override
|
|
||||||
public void onButtonClicked() {
|
|
||||||
Tabs.getInstance().selectTab(newTabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onToastHidden(ButtonToast.ReasonHidden reason) { }
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,27 @@ public class HomePager extends ViewPager {
|
|||||||
public void onUrlOpen(String url, EnumSet<Flags> flags);
|
public void onUrlOpen(String url, EnumSet<Flags> flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for requesting a new tab be opened in the background.
|
||||||
|
* <p>
|
||||||
|
* This is the <code>HomeFragment</code> equivalent of opening a new tab by
|
||||||
|
* long clicking a link and selecting the "Open new [private] tab" context
|
||||||
|
* menu option.
|
||||||
|
*/
|
||||||
|
public interface OnUrlOpenInBackgroundListener {
|
||||||
|
public enum Flags {
|
||||||
|
PRIVATE,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a new tab with the given URL
|
||||||
|
*
|
||||||
|
* @param url to open.
|
||||||
|
* @param flags to open new tab with.
|
||||||
|
*/
|
||||||
|
public void onUrlOpenInBackground(String url, EnumSet<Flags> flags);
|
||||||
|
}
|
||||||
|
|
||||||
public interface OnNewTabsListener {
|
public interface OnNewTabsListener {
|
||||||
public void onNewTabs(List<String> urls);
|
public void onNewTabs(List<String> urls);
|
||||||
}
|
}
|
||||||
|
@ -28,16 +28,12 @@ strings-xml-in := $(srcdir)/../strings.xml.in
|
|||||||
|
|
||||||
GARBAGE += $(strings-xml)
|
GARBAGE += $(strings-xml)
|
||||||
|
|
||||||
dir-res-raw := ../res/raw
|
dir-res-raw := ../res/raw
|
||||||
suggestedsites-json := $(dir-res-raw)/suggestedsites.json
|
suggestedsites := $(dir-res-raw)/suggestedsites.json
|
||||||
|
browsersearch := $(dir-res-raw)/browsersearch.json
|
||||||
GARBAGE += \
|
|
||||||
$(suggestedsites-json) \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
libs realchrome:: \
|
libs realchrome:: \
|
||||||
$(strings-xml) \
|
$(strings-xml) \
|
||||||
$(suggestedsites-json) \
|
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
chrome-%:: AB_CD=$*
|
chrome-%:: AB_CD=$*
|
||||||
@ -45,6 +41,7 @@ chrome-%::
|
|||||||
@$(MAKE) \
|
@$(MAKE) \
|
||||||
$(dir-res-values)-$(AB_rCD)/strings.xml \
|
$(dir-res-values)-$(AB_rCD)/strings.xml \
|
||||||
$(dir-res-raw)-$(AB_rCD)/suggestedsites.json \
|
$(dir-res-raw)-$(AB_rCD)/suggestedsites.json \
|
||||||
|
$(dir-res-raw)-$(AB_rCD)/browsersearch.json \
|
||||||
AB_CD=$*
|
AB_CD=$*
|
||||||
|
|
||||||
# setup the path to bookmarks.inc. copied and tweaked version of MERGE_FILE from config/config.mk
|
# setup the path to bookmarks.inc. copied and tweaked version of MERGE_FILE from config/config.mk
|
||||||
@ -94,21 +91,42 @@ $(dir-strings-xml)/strings.xml: $(strings-xml-preqs)
|
|||||||
$< \
|
$< \
|
||||||
-o $@)
|
-o $@)
|
||||||
|
|
||||||
suggestedsites-srcdir := $(if $(filter en-US,$(AB_CD)),,$(or $(realpath $(L10NBASEDIR)),$(abspath $(L10NBASEDIR)))/$(AB_CD)/mobile/chrome)
|
# Arg 1: Valid Make identifier, like suggestedsites.
|
||||||
|
# Arg 2: File name, like suggestedsites.json.
|
||||||
|
define generated_file_template
|
||||||
|
|
||||||
# Determine the ../res/raw[-*] path. This can be ../res/raw when no
|
# Determine the ../res/raw[-*] path. This can be ../res/raw when no
|
||||||
# locale is explicitly specified.
|
# locale is explicitly specified.
|
||||||
suggestedsites-json-bypath = $(filter %/suggestedsites.json,$(MAKECMDGOALS))
|
$(1)-bypath = $(filter %/$(2),$(MAKECMDGOALS))
|
||||||
ifeq (,$(strip $(suggestedsites-json-bypath)))
|
ifeq (,$$(strip $$($(1)-bypath)))
|
||||||
suggestedsites-json-bypath = $(suggestedsites-json)
|
$(1)-bypath = $($(1))
|
||||||
endif
|
endif
|
||||||
suggestedsites-dstdir-raw = $(patsubst %/,%,$(dir $(suggestedsites-json-bypath)))
|
$(1)-dstdir-raw = $$(patsubst %/,%,$$(dir $$($(1)-bypath)))
|
||||||
|
|
||||||
|
GARBAGE += $($(1))
|
||||||
|
|
||||||
|
libs realchrome:: $($(1))
|
||||||
|
endef
|
||||||
|
|
||||||
|
# L10NBASEDIR is not defined for en-US.
|
||||||
|
l10n-srcdir := $(if $(filter en-US,$(AB_CD)),,$(or $(realpath $(L10NBASEDIR)),$(abspath $(L10NBASEDIR)))/$(AB_CD)/mobile/chrome)
|
||||||
|
|
||||||
|
$(eval $(call generated_file_template,suggestedsites,suggestedsites.json))
|
||||||
|
|
||||||
$(suggestedsites-dstdir-raw)/suggestedsites.json: FORCE
|
$(suggestedsites-dstdir-raw)/suggestedsites.json: FORCE
|
||||||
$(call py_action,generate_suggestedsites, \
|
$(call py_action,generate_suggestedsites, \
|
||||||
--verbose \
|
--verbose \
|
||||||
--android-package-name=$(ANDROID_PACKAGE_NAME) \
|
--android-package-name=$(ANDROID_PACKAGE_NAME) \
|
||||||
--resources=$(srcdir)/../resources \
|
--resources=$(srcdir)/../resources \
|
||||||
$(if $(filter en-US,$(AB_CD)),,--srcdir=$(suggestedsites-srcdir)) \
|
$(if $(filter en-US,$(AB_CD)),,--srcdir=$(l10n-srcdir)) \
|
||||||
|
--srcdir=$(topsrcdir)/mobile/locales/en-US/chrome \
|
||||||
|
$@)
|
||||||
|
|
||||||
|
$(eval $(call generated_file_template,browsersearch,browsersearch.json))
|
||||||
|
|
||||||
|
$(browsersearch-dstdir-raw)/browsersearch.json: FORCE
|
||||||
|
$(call py_action,generate_browsersearch, \
|
||||||
|
--verbose \
|
||||||
|
$(if $(filter en-US,$(AB_CD)),,--srcdir=$(l10n-srcdir)) \
|
||||||
--srcdir=$(topsrcdir)/mobile/locales/en-US/chrome \
|
--srcdir=$(topsrcdir)/mobile/locales/en-US/chrome \
|
||||||
$@)
|
$@)
|
||||||
|
@ -134,10 +134,10 @@ public class SendTabDeviceListArrayAdapter extends ArrayAdapter<ParcelableClient
|
|||||||
|
|
||||||
private static int getImage(ParcelableClientRecord record) {
|
private static int getImage(ParcelableClientRecord record) {
|
||||||
if ("mobile".equals(record.type)) {
|
if ("mobile".equals(record.type)) {
|
||||||
return R.drawable.sync_mobile;
|
return R.drawable.sync_mobile_inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
return R.drawable.sync_desktop;
|
return R.drawable.sync_desktop_inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void switchState(State newState) {
|
public void switchState(State newState) {
|
||||||
|
Before Width: | Height: | Size: 549 B After Width: | Height: | Size: 542 B |
Before Width: | Height: | Size: 474 B After Width: | Height: | Size: 552 B |
BIN
mobile/android/base/resources/drawable-hdpi/sync_desktop.png
Normal file
After Width: | Height: | Size: 325 B |
After Width: | Height: | Size: 357 B |
BIN
mobile/android/base/resources/drawable-hdpi/sync_mobile.png
Normal file
After Width: | Height: | Size: 309 B |
After Width: | Height: | Size: 310 B |
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 381 B |
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 406 B |
Before Width: | Height: | Size: 206 B After Width: | Height: | Size: 218 B |
After Width: | Height: | Size: 256 B |
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 260 B |
After Width: | Height: | Size: 250 B |
Before Width: | Height: | Size: 582 B After Width: | Height: | Size: 469 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 487 B |
BIN
mobile/android/base/resources/drawable-xhdpi/sync_desktop.png
Normal file
After Width: | Height: | Size: 383 B |
After Width: | Height: | Size: 419 B |
BIN
mobile/android/base/resources/drawable-xhdpi/sync_mobile.png
Normal file
After Width: | Height: | Size: 431 B |
After Width: | Height: | Size: 445 B |
Before Width: | Height: | Size: 898 B After Width: | Height: | Size: 607 B |
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 667 B |
BIN
mobile/android/base/resources/drawable-xxhdpi/sync_desktop.png
Normal file
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 738 B |
BIN
mobile/android/base/resources/drawable-xxhdpi/sync_mobile.png
Normal file
After Width: | Height: | Size: 602 B |
After Width: | Height: | Size: 546 B |
@ -18,7 +18,8 @@
|
|||||||
android:layout_width="@dimen/favicon_bg"
|
android:layout_width="@dimen/favicon_bg"
|
||||||
android:layout_height="@dimen/favicon_bg"
|
android:layout_height="@dimen/favicon_bg"
|
||||||
android:layout_marginLeft="10dip"
|
android:layout_marginLeft="10dip"
|
||||||
android:layout_marginRight="10dip" />
|
android:layout_marginRight="10dip"
|
||||||
|
android:scaleType="center" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -48,6 +49,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_marginLeft="10dip"
|
android:layout_marginLeft="10dip"
|
||||||
android:layout_marginRight="10dip" />
|
android:layout_marginRight="10dip"
|
||||||
|
android:scaleType="center" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|