merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-01-09 14:16:30 +01:00
commit e478167826
50 changed files with 1221 additions and 411 deletions

View File

@ -1466,6 +1466,8 @@
let index = tab._tPos;
let filter = this.mTabFilters[index];
aBrowser.webProgress.removeProgressListener(filter);
// Make sure the browser is destroyed so it unregisters from observer notifications
aBrowser.destroy();
// Change the "remote" attribute.
let parent = aBrowser.parentNode;

View File

@ -123,6 +123,7 @@ skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir
[browser_autocomplete_no_title.js]
[browser_autocomplete_autoselect.js]
[browser_autocomplete_oldschool_wrap.js]
[browser_autocomplete_tag_star_visibility.js]
[browser_backButtonFitts.js]
skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug 1099154: test touches content (attempts to add an event listener directly to the contentWindow)
[browser_blob-channelname.js]
@ -197,8 +198,7 @@ skip-if = buildapp == 'mulet' || e10s # Bug 1093206 - need to re-enable tests re
skip-if = e10s # Bug 1102020 - test tries to use browserDOMWindow.openURI to open a link, and gets a null rv where it expects a window
[browser_bug550565.js]
[browser_bug553455.js]
skip-if = true # Bug 1094312
#skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works on mulet? ; for e10s, indefinite waiting halfway through the test, tracked in bug 1093586
skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works on mulet?
[browser_bug555224.js]
skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
[browser_bug555767.js]
@ -232,7 +232,6 @@ skip-if = e10s # Bug 1093756 - can't bookmark the data: url in e10s somehow
[browser_bug581947.js]
skip-if = e10s
[browser_bug585558.js]
skip-if = true # Bug 1094312 - Disabling browser_bug553455.js made this permafail
[browser_bug585785.js]
[browser_bug585830.js]
[browser_bug590206.js]

View File

@ -0,0 +1,105 @@
add_task(function*() {
// This test is only relevant if UnifiedComplete is enabled.
Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true);
registerCleanupFunction(() => {
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
Services.prefs.clearUserPref("browser.urlbar.unifiedcomplete");
Services.prefs.clearUserPref("browser.urlbar.suggest.bookmark");
});
function* addTagItem(tagName) {
let uri = NetUtil.newURI(`http://example.com/this/is/tagged/${tagName}`);
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
uri,
PlacesUtils.bookmarks.DEFAULT_INDEX,
`test ${tagName}`);
PlacesUtils.tagging.tagURI(uri, [tagName]);
yield PlacesTestUtils.addVisits([{uri: uri, title: `Test page with tag ${tagName}`}]);
}
// We use different tags for each part of the test, as otherwise the
// autocomplete code tries to be smart by using the previously cached element
// without updating it (since all parameters it knows about are the same).
let testcases = [{
description: "Test with suggest.bookmark=true",
tagName: "tagtest1",
prefs: {
"suggest.bookmark": true,
},
input: "tagtest1",
expected: {
type: "bookmark-tag",
typeImageVisible: true,
},
}, {
description: "Test with suggest.bookmark=false",
tagName: "tagtest2",
prefs: {
"suggest.bookmark": false,
},
input: "tagtest2",
expected: {
type: "tag",
typeImageVisible: false,
},
}, {
description: "Test with suggest.bookmark=true (again)",
tagName: "tagtest3",
prefs: {
"suggest.bookmark": true,
},
input: "tagtest3",
expected: {
type: "bookmark-tag",
typeImageVisible: true,
},
}, {
description: "Test with bookmark restriction token",
tagName: "tagtest4",
prefs: {
"suggest.bookmark": true,
},
input: "* tagtest4",
expected: {
type: "bookmark-tag",
typeImageVisible: true,
},
}, {
description: "Test with history restriction token",
tagName: "tagtest5",
prefs: {
"suggest.bookmark": true,
},
input: "^ tagtest5",
expected: {
type: "tag",
typeImageVisible: false,
},
}];
for (let testcase of testcases) {
info(`Test case: ${testcase.description}`);
yield addTagItem(testcase.tagName);
for (let prefName of Object.keys(testcase.prefs)) {
Services.prefs.setBoolPref(`browser.urlbar.${prefName}`, testcase.prefs[prefName]);
}
yield promiseAutocompleteResultPopup(testcase.input);
let result = gURLBar.popup.richlistbox.children[1];
ok(result && !result.collasped, "Should have result");
is(result.getAttribute("type"), testcase.expected.type, "Result should have expected type");
if (testcase.expected.typeImageVisible) {
is_element_visible(result._typeImage, "Type image should be visible");
} else {
is_element_hidden(result._typeImage, "Type image should be hidden");
}
gURLBar.popup.hidePopup();
yield promisePopupHidden(gURLBar.popup);
}
});

View File

@ -7,6 +7,7 @@ const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xp
const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
const PROGRESS_NOTIFICATION = "addon-progress-notification";
var rootDir = getRootDirectory(gTestPath);
var path = rootDir.split('/');
@ -23,15 +24,25 @@ var gApp = document.getElementById("bundle_brand").getString("brandShortName");
var gVersion = Services.appinfo.version;
var check_notification;
function wait_for_notification(aCallback) {
info("Waiting for notification");
function wait_for_progress_notification(aCallback) {
wait_for_notification(PROGRESS_NOTIFICATION, aCallback, "popupshowing");
}
function wait_for_notification(aId, aCallback, aEvent = "popupshown") {
info("Waiting for " + aId + " notification");
check_notification = function() {
PopupNotifications.panel.removeEventListener("popupshown", check_notification, false);
info("Saw notification");
// Ignore the progress notification unless that is the notification we want
if (aId != PROGRESS_NOTIFICATION && PopupNotifications.panel.childNodes[0].id == PROGRESS_NOTIFICATION)
return;
PopupNotifications.panel.removeEventListener(aEvent, check_notification, false);
info("Saw a notification");
is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
if (PopupNotifications.panel.childNodes.length)
is(PopupNotifications.panel.childNodes[0].id, aId, "Should have seen the right notification");
aCallback(PopupNotifications.panel);
};
PopupNotifications.panel.addEventListener("popupshown", check_notification, false);
PopupNotifications.panel.addEventListener(aEvent, check_notification, false);
}
function wait_for_notification_close(aCallback) {
@ -103,9 +114,8 @@ function test_disabled_install() {
Services.prefs.setBoolPref("xpinstall.enabled", false);
// Wait for the disabled notification
wait_for_notification(function(aPanel) {
wait_for_notification("xpinstall-disabled-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "xpinstall-disabled-notification", "Should have seen installs disabled");
is(notification.button.label, "Enable", "Should have seen the right button");
is(notification.getAttribute("label"),
"Software installation is currently disabled. Click Enable and try again.");
@ -141,9 +151,8 @@ function test_disabled_install() {
function test_blocked_install() {
// Wait for the blocked notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-blocked-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-blocked-notification", "Should have seen the install blocked");
is(notification.button.label, "Allow", "Should have seen the right button");
is(notification.getAttribute("label"),
gApp + " prevented this site (example.com) from asking you to install " +
@ -153,9 +162,8 @@ function test_blocked_install() {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-complete-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
is(notification.getAttribute("label"),
"XPI Test will be installed after you restart " + gApp + ".",
@ -192,16 +200,12 @@ function test_blocked_install() {
function test_whitelisted_install() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-complete-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
is(notification.getAttribute("label"),
"XPI Test will be installed after you restart " + gApp + ".",
@ -233,14 +237,10 @@ function test_whitelisted_install() {
function test_failed_download() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the failed notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-failed-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
is(notification.getAttribute("label"),
"The add-on could not be downloaded because of a connection failure " +
"on example.com.",
@ -264,14 +264,10 @@ function test_failed_download() {
function test_corrupt_file() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the failed notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-failed-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
is(notification.getAttribute("label"),
"The add-on downloaded from example.com could not be installed " +
"because it appears to be corrupt.",
@ -295,14 +291,10 @@ function test_corrupt_file() {
function test_incompatible() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the failed notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-failed-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
is(notification.getAttribute("label"),
"XPI Test could not be installed because it is not compatible with " +
gApp + " " + gVersion + ".",
@ -326,16 +318,12 @@ function test_incompatible() {
function test_restartless() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-complete-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.getAttribute("label"),
"XPI Test has been installed successfully.",
"Should have seen the right message");
@ -369,16 +357,12 @@ function test_restartless() {
function test_multiple() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-complete-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
is(notification.getAttribute("label"),
"2 add-ons will be installed after you restart " + gApp + ".",
@ -415,16 +399,12 @@ function test_multiple() {
function test_url() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-complete-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
is(notification.getAttribute("label"),
"XPI Test will be installed after you restart " + gApp + ".",
@ -485,13 +465,10 @@ function test_wronghost() {
gBrowser.removeEventListener("load", arguments.callee, true);
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-failed-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
is(notification.getAttribute("label"),
"The add-on downloaded from example.com could not be installed " +
"because it appears to be corrupt.",
@ -509,16 +486,12 @@ function test_wronghost() {
function test_reload() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-complete-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
is(notification.getAttribute("label"),
"XPI Test will be installed after you restart " + gApp + ".",
@ -566,16 +539,12 @@ function test_reload() {
function test_theme() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-complete-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
is(notification.getAttribute("label"),
"Theme Test will be installed after you restart " + gApp + ".",
@ -613,18 +582,13 @@ function test_theme() {
function test_renotify_blocked() {
// Wait for the blocked notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-blocked-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-blocked-notification", "Should have seen the install blocked");
wait_for_notification_close(function () {
info("Timeouts after this probably mean bug 589954 regressed");
executeSoon(function () {
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-blocked-notification",
"Should have seen the install blocked - 2nd time");
wait_for_notification("addon-install-blocked-notification", function(aPanel) {
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 2, "Should be two pending installs");
aInstalls[0].cancel();
@ -653,35 +617,23 @@ function test_renotify_blocked() {
function test_renotify_installed() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
wait_for_notification("addon-install-complete-notification", function(aPanel) {
// Dismiss the notification
wait_for_notification_close(function () {
// Install another
executeSoon(function () {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
wait_for_progress_notification(function(aPanel) {
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
info("Timeouts after this probably mean bug 589954 regressed");
// Wait for the complete notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the second install complete");
wait_for_notification("addon-install-complete-notification", function(aPanel) {
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 1, "Should be one pending installs");
aInstalls[0].cancel();
@ -719,11 +671,14 @@ function test_renotify_installed() {
},
function test_cancel_restart() {
// Wait for the progress notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
function complete_install(callback) {
let url = TESTROOT + "slowinstall.sjs?continue=true"
NetUtil.asyncFetch(url, callback || (() => {}));
}
// Wait for the progress notification
wait_for_notification(PROGRESS_NOTIFICATION, function(aPanel) {
let notification = aPanel.childNodes[0];
// Close the notification
let anchor = document.getElementById("addons-notification-icon");
anchor.click();
@ -737,52 +692,65 @@ function test_cancel_restart() {
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
let button = document.getAnonymousElementByAttribute(notification, "anonid", "cancel");
// Cancel the download
EventUtils.synthesizeMouse(button, 2, 2, {});
// Wait for the install to fully cancel
let install = notification.notification.options.installs[0];
install.addListener({
onDownloadCancelled: function() {
install.removeListener(this);
// Notification should have changed to cancelled
notification = aPanel.childNodes[0];
is(notification.id, "addon-install-cancelled-notification", "Should have seen the cancelled notification");
executeSoon(function() {
ok(PopupNotifications.isPanelOpen, "Notification should still be open");
is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
isnot(notification, aPanel.childNodes[0], "Should have reconstructed the notification UI");
notification = aPanel.childNodes[0];
is(notification.id, "addon-install-cancelled-notification", "Should have seen the cancelled notification");
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
is(notification.getAttribute("label"),
"XPI Test will be installed after you restart " + gApp + ".",
"Should have seen the right message");
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
// Wait for the complete notification
wait_for_notification("addon-install-complete-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.button.label, "Restart Now", "Should have seen the right button");
is(notification.getAttribute("label"),
"XPI Test will be installed after you restart " + gApp + ".",
"Should have seen the right message");
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 1, "Should be one pending install");
aInstalls[0].cancel();
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 1, "Should be one pending install");
aInstalls[0].cancel();
Services.perms.remove("example.com", "install");
wait_for_notification_close(runNextTest);
gBrowser.removeTab(gBrowser.selectedTab);
Services.perms.remove("example.com", "install");
wait_for_notification_close(runNextTest);
gBrowser.removeTab(gBrowser.selectedTab);
});
});
aWindow.document.documentElement.acceptDialog();
});
// Restart the download
EventUtils.synthesizeMouseAtCenter(notification.button, {});
// Should be back to a progress notification
ok(PopupNotifications.isPanelOpen, "Notification should still be open");
is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
complete_install();
});
});
aWindow.document.documentElement.acceptDialog();
}
});
// Restart the download
EventUtils.synthesizeMouse(notification.button, 20, 10, {});
// Should be back to a progress notification
ok(PopupNotifications.isPanelOpen, "Notification should still be open");
is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
notification = aPanel.childNodes[0];
is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
// Cancel the download
EventUtils.synthesizeMouseAtCenter(button, {});
});
var pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
var triggers = encodeURIComponent(JSON.stringify({
"XPI": "unsigned.xpi"
"XPI": "slowinstall.sjs?file=unsigned.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
@ -796,9 +764,8 @@ function test_failed_security() {
});
// Wait for the blocked notification
wait_for_notification(function(aPanel) {
wait_for_notification("addon-install-blocked-notification", function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-blocked-notification", "Should have seen the install blocked");
// Click on Allow
EventUtils.synthesizeMouse(notification.button, 20, 10, {});

View File

@ -513,8 +513,11 @@ loop.panel = (function(_, mozL10n) {
* FxA sign in/up link component.
*/
var AuthLink = React.createClass({displayName: "AuthLink",
mixins: [sharedMixins.WindowCloseMixin],
handleSignUpLinkClick: function() {
navigator.mozLoop.logInToFxA();
this.closeWindow();
},
render: function() {

View File

@ -513,8 +513,11 @@ loop.panel = (function(_, mozL10n) {
* FxA sign in/up link component.
*/
var AuthLink = React.createClass({
mixins: [sharedMixins.WindowCloseMixin],
handleSignUpLinkClick: function() {
navigator.mozLoop.logInToFxA();
this.closeWindow();
},
render: function() {

View File

@ -182,6 +182,13 @@ loop.shared.views.FeedbackView = (function(l10n) {
componentDidMount: function() {
this._timer = setInterval(function() {
if (this.state.countdown == 1) {
clearInterval(this._timer);
if (this.props.onAfterFeedbackReceived) {
this.props.onAfterFeedbackReceived();
}
return;
}
this.setState({countdown: this.state.countdown - 1});
}.bind(this), 1000);
},
@ -193,12 +200,6 @@ loop.shared.views.FeedbackView = (function(l10n) {
},
render: function() {
if (this.state.countdown < 1) {
clearInterval(this._timer);
if (this.props.onAfterFeedbackReceived) {
this.props.onAfterFeedbackReceived();
}
}
return (
React.createElement(FeedbackLayout, {title: l10n.get("feedback_thank_you_heading")},
React.createElement("p", {className: "info thank-you"},

View File

@ -182,6 +182,13 @@ loop.shared.views.FeedbackView = (function(l10n) {
componentDidMount: function() {
this._timer = setInterval(function() {
if (this.state.countdown == 1) {
clearInterval(this._timer);
if (this.props.onAfterFeedbackReceived) {
this.props.onAfterFeedbackReceived();
}
return;
}
this.setState({countdown: this.state.countdown - 1});
}.bind(this), 1000);
},
@ -193,12 +200,6 @@ loop.shared.views.FeedbackView = (function(l10n) {
},
render: function() {
if (this.state.countdown < 1) {
clearInterval(this._timer);
if (this.props.onAfterFeedbackReceived) {
this.props.onAfterFeedbackReceived();
}
}
return (
<FeedbackLayout title={l10n.get("feedback_thank_you_heading")}>
<p className="info thank-you">{

View File

@ -253,6 +253,17 @@ describe("loop.panel", function() {
});
describe("AuthLink", function() {
beforeEach(function() {
navigator.mozLoop.calls = { clearCallInProgress: function() {} };
});
afterEach(function() {
delete navigator.mozLoop.logInToFxA;
delete navigator.mozLoop.calls;
navigator.mozLoop.fxAEnabled = true;
});
it("should trigger the FxA sign in/up process when clicking the link",
function() {
navigator.mozLoop.loggedInToFxA = false;
@ -266,6 +277,19 @@ describe("loop.panel", function() {
sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
});
it("should close the panel after clicking the link",
function() {
navigator.mozLoop.loggedInToFxA = false;
navigator.mozLoop.logInToFxA = sandbox.stub();
var view = createTestPanelView();
TestUtils.Simulate.click(
view.getDOMNode().querySelector(".signin-link a"));
sinon.assert.calledOnce(fakeWindow.close);
});
it("should be hidden if FxA is not enabled",
function() {
navigator.mozLoop.fxAEnabled = false;
@ -273,10 +297,6 @@ describe("loop.panel", function() {
React.createElement(loop.panel.AuthLink));
expect(view.getDOMNode()).to.be.null;
});
afterEach(function() {
navigator.mozLoop.fxAEnabled = true;
});
});
describe("SettingsDropdown", function() {

View File

@ -88,21 +88,25 @@
label="&trackingProtection.label;" />
<image id="trackingProtectionImage"/>
</hbox>
<label id="trackingProtectionLearnMore"
class="text-link"
value="&trackingProtectionLearnMore.label;"/>
<separator/>
<hbox align="center"
class="indent">
<label id="trackingProtectionLearnMore"
class="text-link"
value="&trackingProtectionLearnMore.label;"/>
</hbox>
</vbox>
<checkbox id="privacyDoNotTrackCheckbox"
label="&dntTrackingNotOkay.label2;"
accesskey="&dntTrackingNotOkay.accesskey;"
preference="privacy.donottrackheader.enabled"/>
<separator class="thin"/>
<vbox>
<hbox pack="end">
<spacer flex="1"/>
<label class="text-link" id="doNotTrackInfo"
href="https://www.mozilla.org/dnt">
<hbox align="center">
<checkbox id="privacyDoNotTrackCheckbox"
label="&dntTrackingNotOkay.label2;"
accesskey="&dntTrackingNotOkay.accesskey;"
preference="privacy.donottrackheader.enabled"/>
</hbox>
<hbox align="center"
class="indent">
<label id="doNotTrackInfo"
class="text-link"
href="https://www.mozilla.org/dnt">
&doNotTrackInfo.label;
</label>
</hbox>

View File

@ -95,24 +95,30 @@
preference="privacy.trackingprotection.enabled"
accesskey="&trackingProtection.accesskey;"
label="&trackingProtection.label;" />
<image id="trackingProtectionImage" src="chrome://browser/skin/bad-content-blocked-16.png"/>
<image id="trackingProtectionImage"
src="chrome://browser/skin/bad-content-blocked-16.png"/>
</hbox>
<hbox align="center"
class="indent">
<label id="trackingProtectionLearnMore"
class="text-link"
value="&trackingProtectionLearnMore.label;"/>
</hbox>
<label id="trackingProtectionLearnMore"
class="text-link"
value="&trackingProtectionLearnMore.label;"/>
<separator/>
</vbox>
<checkbox id="privacyDoNotTrackCheckbox"
label="&dntTrackingNotOkay.label2;"
accesskey="&dntTrackingNotOkay.accesskey;"
preference="privacy.donottrackheader.enabled"/>
<separator class="thin"/>
<vbox>
<hbox pack="end">
<spacer flex="1"/>
<label class="text-link" id="doNotTrackInfo"
href="https://www.mozilla.org/dnt"
value="&doNotTrackInfo.label;"/>
<hbox align="center">
<checkbox id="privacyDoNotTrackCheckbox"
label="&dntTrackingNotOkay.label2;"
accesskey="&dntTrackingNotOkay.accesskey;"
preference="privacy.donottrackheader.enabled"/>
</hbox>
<hbox align="center"
class="indent">
<label id="doNotTrackInfo"
class="text-link"
href="https://www.mozilla.org/dnt">
&doNotTrackInfo.label;
</label>
</hbox>
</vbox>

View File

@ -9,6 +9,12 @@ XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
});
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "fxaMigrator",
"resource://services-sync/FxaMigrator.jsm");
const PAGE_NO_ACCOUNT = 0;
const PAGE_HAS_ACCOUNT = 1;
const PAGE_NEEDS_UPDATE = 2;
@ -25,7 +31,6 @@ const FXA_LOGIN_UNVERIFIED = 1;
const FXA_LOGIN_FAILED = 2;
let gSyncPane = {
_stringBundle: null,
prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
"engine.tabs", "engine.history"],
@ -89,6 +94,7 @@ let gSyncPane = {
"weave:service:setup-complete",
"weave:service:logout:finish",
FxAccountsCommon.ONVERIFIED_NOTIFICATION];
let migrateTopic = "fxa-migration:state-changed";
// Add the observers now and remove them on unload
//XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
@ -96,18 +102,34 @@ let gSyncPane = {
topics.forEach(function (topic) {
Weave.Svc.Obs.add(topic, this.updateWeavePrefs, this);
}, this);
window.addEventListener("unload", function() {
topics.forEach(function (topic) {
Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
}, gSyncPane);
}, false);
// The FxA migration observer is a special case.
Weave.Svc.Obs.add(migrateTopic, this.updateMigrationState, this);
window.addEventListener("unload", function() {
topics.forEach(topic => {
Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
});
Weave.Svc.Obs.remove(migrateTopic, this.updateMigrationState, this);
}.bind(this), false);
XPCOMUtils.defineLazyGetter(this, '_stringBundle', () => {
return Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
});
XPCOMUtils.defineLazyGetter(this, '_accountsStringBundle', () => {
return Services.strings.createBundle("chrome://browser/locale/accounts.properties");
});
this._stringBundle =
Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
this.updateWeavePrefs();
},
updateWeavePrefs: function () {
// ask the migration module to broadcast its current state (and nothing will
// happen if it's not loaded - which is good, as that means no migration
// is pending/necessary) - we don't want to suck that module in just to
// find there's nothing to do.
Services.obs.notifyObservers(null, "fxa-migration:state-request", null);
let service = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
@ -116,7 +138,6 @@ let gSyncPane = {
if (service.fxAccountsEnabled) {
// determine the fxa status...
this.page = PAGE_PLEASE_WAIT;
Components.utils.import("resource://gre/modules/FxAccounts.jsm");
fxAccounts.getSignedInUser().then(data => {
if (!data) {
this.page = FXA_PAGE_LOGGED_OUT;
@ -175,6 +196,78 @@ let gSyncPane = {
}
},
updateMigrationState: function(subject, state) {
let selIndex;
let container = document.getElementById("sync-migration");
switch (state) {
case fxaMigrator.STATE_USER_FXA: {
let sb = this._accountsStringBundle;
// There are 2 cases here - no email address means it is an offer on
// the first device (so the user is prompted to create an account).
// If there is an email address it is the "join the party" flow, so the
// user is prompted to sign in with the address they previously used.
let email = subject ? subject.QueryInterface(Components.interfaces.nsISupportsString).data : null;
let elt = document.getElementById("sync-migrate-upgrade-description");
elt.textContent = email ?
sb.formatStringFromName("signInAfterUpgradeOnOtherDevice.description",
[email], 1) :
sb.GetStringFromName("needUserLong");
// The "upgrade" button.
let button = document.getElementById("sync-migrate-upgrade");
button.setAttribute("label",
sb.GetStringFromName(email
? "signInAfterUpgradeOnOtherDevice.label"
: "upgradeToFxA.label"));
button.setAttribute("accesskey",
sb.GetStringFromName(email
? "signInAfterUpgradeOnOtherDevice.accessKey"
: "upgradeToFxA.accessKey"));
// The "unlink" button - this is only shown for first migration
button = document.getElementById("sync-migrate-unlink");
if (email) {
button.hidden = true;
} else {
button.setAttribute("label", sb.GetStringFromName("unlinkMigration.label"));
button.setAttribute("accesskey", sb.GetStringFromName("unlinkMigration.accessKey"));
}
selIndex = 0;
break;
}
case fxaMigrator.STATE_USER_FXA_VERIFIED: {
let sb = this._accountsStringBundle;
let email = subject.QueryInterface(Components.interfaces.nsISupportsString).data;
let label = sb.formatStringFromName("needVerifiedUserLong", [email], 1);
let elt = document.getElementById("sync-migrate-verify-description");
elt.textContent = label;
// The "resend" button.
let button = document.getElementById("sync-migrate-resend");
button.setAttribute("label", sb.GetStringFromName("resendVerificationEmail.label"));
button.setAttribute("accesskey", sb.GetStringFromName("resendVerificationEmail.accessKey"));
// The "forget" button.
button = document.getElementById("sync-migrate-forget");
button.setAttribute("label", sb.GetStringFromName("forgetMigration.label"));
button.setAttribute("accesskey", sb.GetStringFromName("forgetMigration.accessKey"));
selIndex = 1;
break;
}
default:
if (state) { // |null| is expected, but everything else is not.
Cu.reportError("updateMigrationState has unknown state: " + state);
}
if (!container.hidden) {
window.innerHeight -= container.clientHeight;
container.hidden = true;
}
return;
}
document.getElementById("sync-migration-deck").selectedIndex = selIndex;
if (container.hidden) {
container.hidden = false;
window.innerHeight += container.clientHeight;
}
},
startOver: function (showDialog) {
if (showDialog) {
let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
@ -282,7 +375,6 @@ let gSyncPane = {
},
verifyFirefoxAccount: function() {
Components.utils.import("resource://gre/modules/FxAccounts.jsm");
fxAccounts.resendVerificationEmail().then(() => {
fxAccounts.getSignedInUser().then(data => {
let sb = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
@ -323,7 +415,6 @@ let gSyncPane = {
return;
}
}
Components.utils.import('resource://gre/modules/FxAccounts.jsm');
fxAccounts.signOut().then(() => {
this.updateWeavePrefs();
});
@ -356,5 +447,43 @@ let gSyncPane = {
resetSync: function () {
this.openSetup("reset");
},
// click handlers for the FxA migration.
migrateUpgrade: function() {
fxaMigrator.getFxAccountCreationOptions().then(({url, options}) => {
this.openContentInBrowser(url, options);
});
},
migrateForget: function() {
fxaMigrator.forgetFxAccount();
},
migrateResend: function() {
fxaMigrator.resendVerificationMail(window);
},
// When the "Unlink" button in the migration header is selected we display
// a slightly different message.
startOverMigration: function () {
let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
Services.prompt.BUTTON_POS_1_DEFAULT;
let sb = this._accountsStringBundle;
let buttonChoice =
Services.prompt.confirmEx(window,
sb.GetStringFromName("unlinkVerificationTitle"),
sb.GetStringFromName("unlinkVerificationDescription"),
flags,
sb.GetStringFromName("unlinkVerificationConfirm"),
null, null, null, {});
// If the user selects cancel, just bail
if (buttonChoice == 1)
return;
Weave.Service.startOver();
this.updateWeavePrefs();
},
};

View File

@ -36,6 +36,30 @@
<script type="application/javascript"
src="chrome://browser/content/sync/utils.js"/>
<vbox id="sync-migration" flex="1" hidden="true">
<deck id="sync-migration-deck">
<!-- When we are in the "need FxA user" state -->
<hbox align="center">
<description id="sync-migrate-upgrade-description" flex="1"/>
<spacer flex="1"/>
<button id="sync-migrate-unlink"
onclick="event.stopPropagation(); gSyncPane.startOverMigration();"/>
<button id="sync-migrate-upgrade"
onclick="event.stopPropagation(); gSyncPane.migrateUpgrade();"/>
</hbox>
<!-- When we are in the "need the user to be verified" state -->
<hbox align="center">
<description id="sync-migrate-verify-description" flex="1"/>
<spacer flex="1"/>
<button id="sync-migrate-forget"
onclick="event.stopPropagation(); gSyncPane.migrateForget();"/>
<button id="sync-migrate-resend"
onclick="event.stopPropagation(); gSyncPane.migrateResend();"/>
</hbox>
</deck>
</vbox>
<deck id="weavePrefsDeck">

View File

@ -98,8 +98,9 @@ FontInspector.prototype = {
this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
let fillStyle = (Services.prefs.getCharPref("devtools.theme") == "light") ?
"black" : "white";
// Assume light theme colors as the default (see also bug 1118179).
let fillStyle = (Services.prefs.getCharPref("devtools.theme") == "dark") ?
"white" : "black";
let options = {
includePreviews: true,
previewFillStyle: fillStyle

View File

@ -33,6 +33,8 @@ devtools.lazyRequireGetter(this, "CallView",
"devtools/profiler/tree-view", true);
devtools.lazyRequireGetter(this, "ThreadNode",
"devtools/profiler/tree-model", true);
devtools.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
"devtools/timeline/global", true);
devtools.lazyImporter(this, "CanvasGraphUtils",
"resource:///modules/devtools/Graphs.jsm");

View File

@ -16,7 +16,7 @@ let WaterfallView = {
this._onMarkerSelected = this._onMarkerSelected.bind(this);
this._onResize = this._onResize.bind(this);
this.graph = new Waterfall($("#waterfall-graph"), $("#details-pane"));
this.graph = new Waterfall($("#waterfall-graph"), $("#details-pane"), TIMELINE_BLUEPRINT);
this.markerDetails = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
this.graph.on("selected", this._onMarkerSelected);

View File

@ -80,7 +80,7 @@ let OverviewView = {
* Sets up the markers overivew graph.
*/
_showMarkersGraph: Task.async(function *() {
this.markersOverview = new MarkersOverview($("#markers-overview"));
this.markersOverview = new MarkersOverview($("#markers-overview"), TIMELINE_BLUEPRINT);
this.markersOverview.headerHeight = MARKERS_GRAPH_HEADER_HEIGHT;
this.markersOverview.bodyHeight = MARKERS_GRAPH_BODY_HEIGHT;
this.markersOverview.groupPadding = MARKERS_GROUP_VERTICAL_PADDING;

View File

@ -75,7 +75,7 @@ function Waterfall(parent, container, blueprint) {
// Lazy require is a bit slow, and these are hot objects.
this._l10n = L10N;
this._blueprint = blueprint
this._blueprint = blueprint;
this._setNamedTimeout = setNamedTimeout;
this._clearNamedTimeout = clearNamedTimeout;

View File

@ -189,4 +189,25 @@ label.small {
margin-bottom: 0.6em;
}
/**
* Sync migration
*/
#sync-migration {
border: 1px solid rgba(0, 0, 0, 0.32);
background-color: InfoBackground;
color: InfoText;
text-shadow: none;
margin: 5px 0 0 0;
animation: fadein 3000ms;
}
#sync-migration description {
margin: 8px;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
%endif

View File

@ -250,4 +250,25 @@ html|a.inline-link:-moz-focusring {
margin-bottom: 0.6em;
}
/**
* Sync migration
*/
#sync-migration {
border: 1px solid rgba(0, 0, 0, 0.32);
background-color: InfoBackground;
color: InfoText;
text-shadow: none;
margin: 5px 0 0 0;
animation: fadein 3000ms;
}
#sync-migration description {
margin: 8px;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
%endif

View File

@ -176,4 +176,25 @@ label.small {
margin-bottom: 0.6em;
}
/**
* Sync migration
*/
#sync-migration {
border: 1px solid rgba(0, 0, 0, 0.32);
background-color: InfoBackground;
color: InfoText;
text-shadow: none;
margin: 5px 0 0 0;
animation: fadein 3000ms;
}
#sync-migration description {
margin: 8px;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
%endif

View File

@ -11,6 +11,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const UPDATE_URL_PREF = "browser.webapps.updateCheckUrl";
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/Downloads.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
@ -137,30 +138,29 @@ this.WebappManager = {
[p + "=" + encodeURIComponent(params[p]) for (p in params)].join("&");
debug("downloading APK from " + generatorUrl.spec);
let file = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager).
defaultDownloadsDirectory.
clone();
file.append(aManifestUrl.replace(/[^a-zA-Z0-9]/gi, "") + ".apk");
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
debug("downloading APK to " + file.path);
Downloads.getSystemDownloadsDirectory().then(function(downloadsDir) {
let file = new FileUtils.File(downloadsDir);
file.append(aManifestUrl.replace(/[^a-zA-Z0-9]/gi, "") + ".apk");
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
debug("downloading APK to " + file.path);
let worker = new ChromeWorker("resource://gre/modules/WebappManagerWorker.js");
worker.onmessage = function(event) {
let { type, message } = event.data;
let worker = new ChromeWorker("resource://gre/modules/WebappManagerWorker.js");
worker.onmessage = function(event) {
let { type, message } = event.data;
worker.terminate();
worker.terminate();
if (type == "success") {
deferred.resolve(file.path);
} else { // type == "failure"
debug("error downloading APK: " + message);
deferred.reject(message);
if (type == "success") {
deferred.resolve(file.path);
} else { // type == "failure"
debug("error downloading APK: " + message);
deferred.reject(message);
}
}
}
// Trigger the download.
worker.postMessage({ url: generatorUrl.spec, path: file.path });
// Trigger the download.
worker.postMessage({ url: generatorUrl.spec, path: file.path });
});
return deferred.promise;
},

View File

@ -4,7 +4,9 @@
package org.mozilla.search;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.R;
@ -40,6 +42,8 @@ public class PostSearchFragment extends Fragment {
private WebView webview;
private View errorView;
private String resultsPageHost;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@ -72,6 +76,7 @@ public class PostSearchFragment extends Fragment {
final String url = engine.resultsUriForQuery(query);
// Only load urls if the url is different than the webview's current url.
if (!TextUtils.equals(webview.getUrl(), url)) {
resultsPageHost = null;
webview.loadUrl(Constants.ABOUT_BLANK);
webview.loadUrl(url);
}
@ -95,14 +100,22 @@ public class PostSearchFragment extends Fragment {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Ignore about:blank URL loads.
if (TextUtils.equals(url, Constants.ABOUT_BLANK)) {
// Ignore about:blank URL loads and the first results page we try to load.
if (TextUtils.equals(url, Constants.ABOUT_BLANK) || resultsPageHost == null) {
return false;
}
// If the URL is a results page, don't override the URL load, but
String host = null;
try {
host = new URL(url).getHost();
} catch (MalformedURLException e) {
Log.e(LOG_TAG, "Error getting host from URL loading in webview", e);
}
// If the host name is the same as the results page, don't override the URL load, but
// do update the query in the search bar if possible.
if (engine.isSearchResultsPage(url)) {
if (TextUtils.equals(resultsPageHost, host)) {
// This won't work for results pages that redirect (e.g. Google in different country)
final String query = engine.queryForResultsUrl(url);
if (!TextUtils.isEmpty(query)) {
((AcceptsSearchQuery) getActivity()).onQueryChange(query);
@ -132,7 +145,7 @@ public class PostSearchFragment extends Fragment {
}
return false;
}
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
@ -166,6 +179,14 @@ public class PostSearchFragment extends Fragment {
errorView.setVisibility(networkError ? View.VISIBLE : View.GONE);
webview.setVisibility(networkError ? View.GONE : View.VISIBLE);
}
if (!TextUtils.equals(url, Constants.ABOUT_BLANK) && resultsPageHost == null) {
try {
resultsPageHost = new URL(url).getHost();
} catch (MalformedURLException e) {
Log.e(LOG_TAG, "Error getting host from results page URL", e);
}
}
}
}

View File

@ -219,14 +219,6 @@ public class SearchEngine {
return iconURL;
}
/**
* Determine whether a particular url belongs to this search engine. If not,
* the url will be sent to Fennec.
*/
public boolean isSearchResultsPage(String url) {
return getResultsUri().getAuthority().equalsIgnoreCase(Uri.parse(url).getAuthority());
}
/**
* Finds the search query encoded in a given results URL.
*

View File

@ -370,9 +370,26 @@ Migrator.prototype = {
// we'll move to either the STATE_USER_FXA_VERIFIED state or we'll just
// complete the migration if they login as an already verified user.
createFxAccount: Task.async(function* (win) {
let {url, options} = yield this.getFxAccountCreationOptions();
win.switchToTabHavingURI(url, true, options);
// An FxA observer will fire when the user completes this, which will
// cause us to move to the next "user blocked" state and notify via our
// observer notification.
}),
// Returns an object with properties "url" and "options", suitable for
// opening FxAccounts to create/signin to FxA suitable for the migration
// state. The caller of this is responsible for the actual opening of the
// page.
// This should only be called while we are in the STATE_USER_FXA state. When
// the user completes the creation we'll see an ONLOGIN_NOTIFICATION
// notification from FxA and we'll move to either the STATE_USER_FXA_VERIFIED
// state or we'll just complete the migration if they login as an already
// verified user.
getFxAccountCreationOptions: Task.async(function* (win) {
// warn if we aren't in the expected state - but go ahead anyway!
if (this._state != this.STATE_USER_FXA) {
this.log.warn("createFxAccount called in an unexpected state: ${}", this._state);
this.log.warn("getFxAccountCreationOptions called in an unexpected state: ${}", this._state);
}
// We need to obtain the sentinel and apply any prefs that might be
// specified *before* attempting to setup FxA as the prefs might
@ -387,16 +404,17 @@ Migrator.prototype = {
// See if we can find a default account name to use.
let email = yield this._getDefaultAccountName(sentinel);
let tail = email ? "&email=" + encodeURIComponent(email) : "";
// A special flag so server-side metrics can tell this is part of migration.
tail += "&migration=sync11";
// We want to ask FxA to offer a "Customize Sync" checkbox iff any engines
// are disabled.
let customize = !this._allEnginesEnabled();
tail += "&customizeSync=" + customize;
win.switchToTabHavingURI("about:accounts?action=" + action + tail, true,
{ignoreFragment: true, replaceQueryString: true});
// An FxA observer will fire when the user completes this, which will
// cause us to move to the next "user blocked" state and notify via our
// observer notification.
return {
url: "about:accounts?action=" + action + tail,
options: {ignoreFragment: true, replaceQueryString: true}
};
}),
// Ask the FxA servers to re-send a verification mail for the currently
@ -407,7 +425,7 @@ Migrator.prototype = {
resendVerificationMail: Task.async(function * (win) {
// warn if we aren't in the expected state - but go ahead anyway!
if (this._state != this.STATE_USER_FXA_VERIFIED) {
this.log.warn("createFxAccount called in an unexpected state: ${}", this._state);
this.log.warn("resendVerificationMail called in an unexpected state: ${}", this._state);
}
let ok = true;
try {
@ -442,7 +460,7 @@ Migrator.prototype = {
forgetFxAccount: Task.async(function * () {
// warn if we aren't in the expected state - but go ahead anyway!
if (this._state != this.STATE_USER_FXA_VERIFIED) {
this.log.warn("createFxAccount called in an unexpected state: ${}", this._state);
this.log.warn("forgetFxAccount called in an unexpected state: ${}", this._state);
}
return fxAccounts.signOut();
}),

View File

@ -111,6 +111,30 @@ try {
}
catch (e) { }
// Configure a console listener so messages sent to it are logged as part
// of the test.
try {
let levelNames = {}
for (let level of ["debug", "info", "warn", "error"]) {
levelNames[Components.interfaces.nsIConsoleMessage[level]] = level;
}
let listener = {
QueryInterface : function(iid) {
if (!iid.equals(Components.interfaces.nsISupports) &&
!iid.equals(Components.interfaces.nsIConsoleListener)) {
throw Components.results.NS_NOINTERFACE;
}
return this;
},
observe : function (msg) {
do_print("CONSOLE_MESSAGE: (" + levelNames[msg.logLevel] + ") " + msg.toString());
}
};
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService)
.registerListener(listener);
} catch (e) {}
/**
* Date.now() is not necessarily monotonically increasing (insert sob story
* about times not being the right tool to use for measuring intervals of time,

View File

@ -1286,7 +1286,9 @@ Search.prototype = {
// search or because of the user's preferences), so only set it if we
// haven't already done so.
if (showTags) {
match.style = "tag";
// If we're not suggesting bookmarks, then this shouldn't
// display as one.
match.style = this.hasBehavior("bookmark") ? "bookmark-tag" : "tag";
}
else if (bookmarked) {
match.style = "bookmark";

View File

@ -171,7 +171,7 @@ function* check_autocomplete(test) {
// Got a match on both uri and title?
if (stripPrefix(uri.spec) == stripPrefix(value) && title == comment) {
do_log_info("Got a match at index " + j + "!");
let actualStyle = controller.getStyleAt(i).split(/\W+/).sort();
let actualStyle = controller.getStyleAt(i).split(/\s+/).sort();
if (style)
Assert.equal(actualStyle.toString(), style.toString(), "Match should have expected style");

View File

@ -16,7 +16,7 @@ add_task(function* test_tag_match_has_bookmark_title() {
tags: [ "superTag" ]});
yield check_autocomplete({
search: "superTag",
matches: [ { uri: uri, title: "Bookmark title", tags: [ "superTag" ], style: [ "tag" ] } ]
matches: [ { uri: uri, title: "Bookmark title", tags: [ "superTag" ], style: [ "bookmark-tag" ] } ]
});
yield cleanup();
});

View File

@ -23,15 +23,15 @@ add_task(function* test_tag_match_url() {
addBookmark({ uri: uri1,
title: "title",
tags: [ "superTag" ],
style: [ "tag" ] });
style: [ "bookmark-tag" ] });
addBookmark({ uri: uri2,
title: "title",
tags: [ "superTag" ],
style: [ "tag" ] });
style: [ "bookmark-tag" ] });
yield check_autocomplete({
search: "superTag",
matches: [ { uri: uri1, title: "title", tags: [ "superTag" ], style: [ "tag" ] },
{ uri: uri2, title: "title", tags: [ "superTag" ], style: [ "tag" ] } ]
matches: [ { uri: uri1, title: "title", tags: [ "superTag" ], style: [ "bookmark-tag" ] },
{ uri: uri2, title: "title", tags: [ "superTag" ], style: [ "bookmark-tag" ] } ]
});
yield cleanup();
});

View File

@ -33,32 +33,32 @@ add_task(function* test_javascript_match() {
do_log_info("Make sure tags come back in the title when matching tags");
yield check_autocomplete({
search: "page1 tag",
matches: [ { uri: uri1, title: "tagged", tags: [ "tag1" ], style: [ "tag" ] } ]
matches: [ { uri: uri1, title: "tagged", tags: [ "tag1" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("Check tags in title for page2");
yield check_autocomplete({
search: "page2 tag",
matches: [ { uri: uri2, title: "tagged", tags: [ "tag1", "tag2" ], style: [ "tag" ] } ]
matches: [ { uri: uri2, title: "tagged", tags: [ "tag1", "tag2" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("Make sure tags appear even when not matching the tag");
yield check_autocomplete({
search: "page3",
matches: [ { uri: uri3, title: "tagged", tags: [ "tag1", "tag3" ], style: [ "tag" ] } ]
matches: [ { uri: uri3, title: "tagged", tags: [ "tag1", "tag3" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("Multiple tags come in commas for page4");
yield check_autocomplete({
search: "page4",
matches: [ { uri: uri4, title: "tagged", tags: [ "tag1", "tag2", "tag3" ], style: [ "tag" ] } ]
matches: [ { uri: uri4, title: "tagged", tags: [ "tag1", "tag2", "tag3" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("Extra test just to make sure we match the title");
yield check_autocomplete({
search: "tag2",
matches: [ { uri: uri2, title: "tagged", tags: [ "tag1", "tag2" ], style: [ "tag" ] },
{ uri: uri4, title: "tagged", tags: [ "tag1", "tag2", "tag3" ], style: [ "tag" ] } ]
matches: [ { uri: uri2, title: "tagged", tags: [ "tag1", "tag2" ], style: [ "bookmark-tag" ] },
{ uri: uri4, title: "tagged", tags: [ "tag1", "tag2", "tag3" ], style: [ "bookmark-tag" ] } ]
});
yield cleanup();

View File

@ -63,10 +63,10 @@ add_task(function* test_special_searches() {
{ uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri7, title: "title", style: [ "bookmark" ] },
{ uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar"], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar"], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("Tag restrict");
@ -138,10 +138,10 @@ add_task(function* test_special_searches() {
matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri7, title: "title", style: [ "bookmark" ] },
{ uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo | -> is star (change pref)");
@ -151,10 +151,10 @@ add_task(function* test_special_searches() {
matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri7, title: "title", style: [ "bookmark" ] },
{ uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo # -> in title");
@ -251,7 +251,7 @@ add_task(function* test_special_searches() {
yield check_autocomplete({
search: "foo ^ *",
matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo ^ # -> history, in title");
@ -289,10 +289,10 @@ add_task(function* test_special_searches() {
search: "foo * #",
matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo * @ -> is star, in url");
@ -300,23 +300,23 @@ add_task(function* test_special_searches() {
search: "foo * @",
matches: [ { uri: uri7, title: "title", style: [ "bookmark" ] },
{ uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo * + -> same as +");
yield check_autocomplete({
search: "foo * +",
matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo * ~ -> is star, is typed");
yield check_autocomplete({
search: "foo * ~",
matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ]
matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo # @ -> in title, in url");
@ -393,10 +393,10 @@ add_task(function* test_special_searches() {
{ uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri7, title: "title", style: [ "bookmark" ] },
{ uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar"], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar"], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo -> default history, is star, is typed");
@ -407,7 +407,7 @@ add_task(function* test_special_searches() {
yield check_autocomplete({
search: "foo",
matches: [ { uri: uri4, title: "foo.bar" },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo -> is star");
@ -419,10 +419,10 @@ add_task(function* test_special_searches() {
matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri7, title: "title", style: [ "bookmark" ] },
{ uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("foo -> is star, is typed");
@ -435,10 +435,10 @@ add_task(function* test_special_searches() {
matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri7, title: "title", style: [ "bookmark" ] },
{ uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ]
{ uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
{ uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ]
});
yield cleanup();

View File

@ -52,7 +52,7 @@ add_task(function* test_escape() {
search: "match",
matches: [ { uri: uri1, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "tag" ] },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "title1" } ]
});
@ -61,7 +61,7 @@ add_task(function* test_escape() {
search: "dont",
matches: [ { uri: uri2, title: "title1" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "tag" ] } ]
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("Match 'match' at the beginning or after / or on a CamelCase");
@ -69,8 +69,8 @@ add_task(function* test_escape() {
search: "2",
matches: [ { uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "tag" ] } ]
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] } ]
});
do_log_info("Match 't' at the beginning or after /");
@ -80,8 +80,8 @@ add_task(function* test_escape() {
{ uri: uri2, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "tag" ] },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "title1" } ]
});
@ -98,8 +98,8 @@ add_task(function* test_escape() {
{ uri: uri2, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "tag" ] },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" },
{ uri: uri8, title: katakana.join("") },
{ uri: uri9, title: ideograph.join("") },
@ -164,8 +164,8 @@ add_task(function* test_escape() {
{ uri: uri2, title: "title1" },
{ uri: uri3, title: "matchme2" },
{ uri: uri4, title: "dontmatchme3" },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "tag" ] },
{ uri: uri5, title: "title1", tags: [ "matchme2" ], style: [ "bookmark-tag" ] },
{ uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] },
{ uri: uri10, title: "title1" } ]
});

View File

@ -451,6 +451,15 @@ var PrintUtils = {
let onEntered = (message) => {
mm.removeMessageListener("Printing:PrintPreview:Entered", onEntered);
if (message.data.failed) {
// Something went wrong while putting the document into print preview
// mode. Bail out.
this._listener.onEnter();
this._listener.onExit();
return;
}
// Stash the focused element so that we can return to it after exiting
// print preview.
gFocusedElement = document.commandDispatcher.focusedElement;
@ -520,7 +529,7 @@ var PrintUtils = {
if (gFocusedElement)
fm.setFocus(gFocusedElement, fm.FLAG_NOSCROLL);
else
window.content.focus();
this._sourceBrowser.focus();
gFocusedElement = null;
this._listener.onExit();

View File

@ -25,6 +25,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SearchStaticData",
"resource://gre/modules/SearchStaticData.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
"resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gTextToSubURI",
"@mozilla.org/intl/texttosuburi;1",
@ -463,61 +467,113 @@ let ensureKnownCountryCode = Task.async(function* () {
Services.prefs.getCharPref("browser.search.countryCode");
return; // pref exists, so we've done this before.
} catch(e) {}
// we don't have it cached, so fetch it.
let cc = yield fetchCountryCode();
if (cc) {
// we got one - stash it away
Services.prefs.setCharPref("browser.search.countryCode", cc);
// and update our "isUS" cache pref if it is US - that will prevent a
// fallback to the timezone check.
// However, only do this if the locale also matches.
if (getLocale() == "en-US") {
Services.prefs.setBoolPref("browser.search.isUS", (cc == "US"));
}
// and telemetry...
let isTimezoneUS = isUSTimezone();
if (cc == "US" && !isTimezoneUS) {
Services.telemetry.getHistogramById("SEARCH_SERVICE_US_COUNTRY_MISMATCHED_TIMEZONE").add(1);
}
if (cc != "US" && isTimezoneUS) {
Services.telemetry.getHistogramById("SEARCH_SERVICE_US_TIMEZONE_MISMATCHED_COUNTRY").add(1);
}
}
// We don't have it cached, so fetch it. fetchCountryCode() will call
// storeCountryCode if it gets a result (even if that happens after the
// promise resolves)
yield fetchCountryCode();
// If gInitialized is true then the search service was forced to perform
// a sync initialization during our XHR - capture this via telemetry.
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT").add(gInitialized);
});
// Store the result of the geoip request as well as any other values and
// telemetry which depend on it.
function storeCountryCode(cc) {
// Set the country-code itself.
Services.prefs.setCharPref("browser.search.countryCode", cc);
// and update our "isUS" cache pref if it is US - that will prevent a
// fallback to the timezone check.
// However, only do this if the locale also matches.
if (getLocale() == "en-US") {
Services.prefs.setBoolPref("browser.search.isUS", (cc == "US"));
}
// and telemetry...
let isTimezoneUS = isUSTimezone();
if (cc == "US" && !isTimezoneUS) {
Services.telemetry.getHistogramById("SEARCH_SERVICE_US_COUNTRY_MISMATCHED_TIMEZONE").add(1);
}
if (cc != "US" && isTimezoneUS) {
Services.telemetry.getHistogramById("SEARCH_SERVICE_US_TIMEZONE_MISMATCHED_COUNTRY").add(1);
}
}
// Get the country we are in via a XHR geoip request.
function fetchCountryCode() {
// values for the SEARCH_SERVICE_COUNTRY_FETCH_RESULT 'enum' telemetry probe.
const TELEMETRY_RESULT_ENUM = {
SUCCESS: 0,
SUCCESS_WITHOUT_DATA: 1,
XHRTIMEOUT: 2,
ERROR: 3,
// Note that we expect to add finer-grained error types here later (eg,
// dns error, network error, ssl error, etc) with .ERROR remaining as the
// generic catch-all that doesn't fit into other categories.
};
let endpoint = Services.urlFormatter.formatURLPref("browser.search.geoip.url");
// As an escape hatch, no endpoint means no geoip.
if (!endpoint) {
return Promise.resolve(null);
return Promise.resolve();
}
let startTime = Date.now();
return new Promise(resolve => {
// Instead of using a timeout on the xhr object itself, we simulate one
// using a timer and let the XHR request complete. This allows us to
// capture reliable telemetry on what timeout value should actually be
// used to ensure most users don't see one while not making it so large
// that many users end up doing a sync init of the search service and thus
// would see the jank that implies.
// (Note we do actually use a timeout on the XHR, but that's set to be a
// large value just incase the request never completes - we don't want the
// XHR object to live forever)
let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
let timerId = setTimeout(() => {
LOG("_fetchCountryCode: timeout fetching country information");
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(1);
timerId = null;
resolve();
}, timeoutMS);
let resolveAndReportSuccess = (result, reason) => {
// Even if we timed out, we want to save the country code and everything
// related so next startup sees the value and doesn't retry this dance.
if (result) {
storeCountryCode(result);
}
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_RESULT").add(reason);
// This notification is just for tests...
Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "geoip-lookup-xhr-complete");
// If we've already timed out then we've already resolved the promise,
// so there's nothing else to do.
if (timerId == null) {
return;
}
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(0);
clearTimeout(timerId);
resolve();
};
let request = new XMLHttpRequest();
request.timeout = Services.prefs.getIntPref("browser.search.geoip.timeout");
// This notification is just for tests...
Services.obs.notifyObservers(request, SEARCH_SERVICE_TOPIC, "geoip-lookup-xhr-starting");
request.timeout = 100000; // 100 seconds as the last-chance fallback
request.onload = function(event) {
let took = Date.now() - startTime;
let cc = event.target.response && event.target.response.country_code;
LOG("_fetchCountryCode got success response in " + took + "ms: " + cc);
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_MS").add(took);
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS").add(cc ? 1 : 0);
if (!cc) {
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS_WITHOUT_DATA").add(1);
}
resolve(cc);
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS").add(took);
let reason = cc ? TELEMETRY_RESULT_ENUM.SUCCESS : TELEMETRY_RESULT_ENUM.SUCCESS_WITHOUT_DATA;
resolveAndReportSuccess(cc, reason);
};
request.ontimeout = function(event) {
LOG("_fetchCountryCode: XHR finally timed-out fetching country information");
resolveAndReportSuccess(null, TELEMETRY_RESULT_ENUM.XHRTIMEOUT);
};
request.onerror = function(event) {
LOG("_fetchCountryCode: failed to retrieve country information");
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS").add(0);
resolve(null);
resolveAndReportSuccess(null, TELEMETRY_RESULT_ENUM.ERROR);
};
request.ontimeout = function(event) {
LOG("_fetchCountryCode: timeout fetching country information");
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_TIMEOUT").add(1);
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS").add(0);
resolve(null);
}
request.open("POST", endpoint, true);
request.setRequestHeader("Content-Type", "application/json");
request.responseType = "json";

View File

@ -302,3 +302,51 @@ let addTestEngines = Task.async(function* (aItems) {
return engines;
});
/**
* Returns a promise that is resolved when an observer notification from the
* search service fires with the specified data.
*
* @param aExpectedData
* The value the observer notification sends that causes us to resolve
* the promise.
*/
function waitForSearchNotification(aExpectedData) {
return new Promise(resolve => {
const SEARCH_SERVICE_TOPIC = "browser-search-service";
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
if (aData != aExpectedData)
return;
Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC);
resolve(aSubject);
}, SEARCH_SERVICE_TOPIC, false);
});
}
// This "enum" from nsSearchService.js
const TELEMETRY_RESULT_ENUM = {
SUCCESS: 0,
SUCCESS_WITHOUT_DATA: 1,
XHRTIMEOUT: 2,
ERROR: 3,
};
/**
* Checks the value of the SEARCH_SERVICE_COUNTRY_FETCH_RESULT probe.
*
* @param aExpectedValue
* If a value from TELEMETRY_RESULT_ENUM, we expect to see this value
* recorded exactly once in the probe. If |null|, we expect to see
* nothing recorded in the probe at all.
*/
function checkCountryResultTelemetry(aExpectedValue) {
let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_RESULT");
let snapshot = histogram.snapshot();
// The probe is declared with 8 values, but we get 9 back from .counts
let expectedCounts = [0,0,0,0,0,0,0,0,0];
if (aExpectedValue != null) {
expectedCounts[aExpectedValue] = 1;
}
deepEqual(snapshot.counts, expectedCounts);
}

View File

@ -25,9 +25,14 @@ function run_test() {
equal(Services.prefs.getCharPref("browser.search.countryCode"), "AU", "got the correct country code.");
equal(Services.prefs.getBoolPref("browser.search.isUS"), false, "AU is not in the US.")
// check we have "success" recorded in telemetry
let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS");
let snapshot = histogram.snapshot();
equal(snapshot.sum, 1)
checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.SUCCESS);
// a false value for each of SEARCH_SERVICE_COUNTRY_TIMEOUT and SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT
for (let hid of ["SEARCH_SERVICE_COUNTRY_TIMEOUT",
"SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT"]) {
let histogram = Services.telemetry.getHistogramById(hid);
let snapshot = histogram.snapshot();
deepEqual(snapshot.counts, [1,0,0]); // boolean probe so 3 buckets, expect 1 result for |0|.
}
do_test_finished();
run_next_test();
});

View File

@ -20,7 +20,9 @@ function run_test() {
removeCacheFile();
});
// from server-locations.txt, we choose a URL without a cert.
// this will cause an "unknown host" error, but not report an external
// network connection in the tests (note that the hosts listed in
// server-locations.txt are *not* loaded for xpcshell tests...)
let url = "https://nocert.example.com:443";
Services.prefs.setCharPref("browser.search.geoip.url", url);
Services.search.init(() => {
@ -28,15 +30,15 @@ function run_test() {
Services.prefs.getCharPref("browser.search.countryCode");
ok(false, "not expecting countryCode to be set");
} catch (ex) {}
// should be no success recorded.
let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS");
let snapshot = histogram.snapshot();
equal(snapshot.sum, 0);
// should be no timeout either.
histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_TIMEOUT");
snapshot = histogram.snapshot();
equal(snapshot.sum, 0);
// should have an error recorded.
checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.ERROR);
// but false values for timeout and forced-sync-init.
for (let hid of ["SEARCH_SERVICE_COUNTRY_TIMEOUT",
"SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT"]) {
let histogram = Services.telemetry.getHistogramById(hid);
let snapshot = histogram.snapshot();
deepEqual(snapshot.counts, [1,0,0]); // boolean probe so 3 buckets, expect 1 result for |0|.
}
do_test_finished();
run_next_test();

View File

@ -36,15 +36,15 @@ function run_test() {
equal(Services.prefs.getBoolPref("browser.search.isUS"),
isUSTimezone(),
"should have set isUS based on current timezone.");
// should have a false value for success.
let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS");
let snapshot = histogram.snapshot();
equal(snapshot.sum, 0);
// and a flag for SEARCH_SERVICE_COUNTRY_SUCCESS_WITHOUT_DATA
histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS_WITHOUT_DATA");
snapshot = histogram.snapshot();
equal(snapshot.sum, 1);
// should have recorded SUCCESS_WITHOUT_DATA
checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.SUCCESS_WITHOUT_DATA);
// and false values for timeout and forced-sync-init.
for (let hid of ["SEARCH_SERVICE_COUNTRY_TIMEOUT",
"SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT"]) {
let histogram = Services.telemetry.getHistogramById(hid);
let snapshot = histogram.snapshot();
deepEqual(snapshot.counts, [1,0,0]); // boolean probe so 3 buckets, expect 1 result for |0|.
}
do_test_finished();
run_next_test();

View File

@ -64,15 +64,32 @@ add_task(function* test_simple() {
deepEqual(getCountryCodePref(), undefined, "didn't do the geoip xhr");
// and no telemetry evidence of geoip.
for (let hid of [
"SEARCH_SERVICE_COUNTRY_FETCH_MS",
"SEARCH_SERVICE_COUNTRY_SUCCESS",
"SEARCH_SERVICE_COUNTRY_SUCCESS_WITHOUT_DATA",
"SEARCH_SERVICE_COUNTRY_FETCH_TIMEOUT",
"SEARCH_SERVICE_COUNTRY_FETCH_RESULT",
"SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS",
"SEARCH_SERVICE_COUNTRY_TIMEOUT",
"SEARCH_SERVICE_US_COUNTRY_MISMATCHED_TIMEZONE",
"SEARCH_SERVICE_US_TIMEZONE_MISMATCHED_COUNTRY",
"SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT",
]) {
let histogram = Services.telemetry.getHistogramById(hid);
let snapshot = histogram.snapshot();
equal(snapshot.sum, 0);
equal(snapshot.sum, 0, hid);
switch (snapshot.histogram_type) {
case Ci.nsITelemetry.HISTOGRAM_FLAG:
// flags are a special case in that they are initialized with a default
// of one |0|.
deepEqual(snapshot.counts, [1,0,0], hid);
break;
case Ci.nsITelemetry.HISTOGRAM_BOOLEAN:
// booleans aren't initialized at all, so should have all zeros.
deepEqual(snapshot.counts, [0,0,0], hid);
break;
case Ci.nsITelemetry.HISTOGRAM_EXPONENTIAL:
case Ci.nsITelemetry.HISTOGRAM_LINEAR:
equal(snapshot.counts.reduce((a, b) => a+b), 0, hid);
break;
default:
ok(false, "unknown histogram type " + snapshot.histogram_type + " for " + hid);
}
}
});

View File

@ -1,16 +1,20 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function startServer() {
// This is testing the "normal" timer-based timeout for the location search.
function startServer(continuePromise) {
let srv = new HttpServer();
function lookupCountry(metadata, response) {
response.processAsync();
// wait 200 ms before writing a valid response - the search service
// should timeout before the response is written so the response should
// be ignored.
do_timeout(200, () => {
// wait for our continuePromise to resolve before writing a valid
// response.
// This will be resolved after the timeout period, so we can check
// the behaviour in that case.
continuePromise.then(() => {
response.setStatusLine("1.1", 200, "OK");
response.write('{"country_code" : "AU"}');
response.finish();
});
}
srv.registerPathHandler("/lookup_country", lookupCountry);
@ -18,6 +22,11 @@ function startServer() {
return srv;
}
function getProbeSum(probe, sum) {
let histogram = Services.telemetry.getHistogramById(probe);
return histogram.snapshot().sum;
}
function run_test() {
removeMetadata();
removeCacheFile();
@ -37,7 +46,12 @@ function run_test() {
removeCacheFile();
});
let server = startServer();
let resolveContinuePromise;
let continuePromise = new Promise(resolve => {
resolveContinuePromise = resolve;
});
let server = startServer(continuePromise);
let url = "http://localhost:" + server.identity.primaryPort + "/lookup_country";
Services.prefs.setCharPref("browser.search.geoip.url", url);
Services.prefs.setIntPref("browser.search.geoip.timeout", 50);
@ -46,18 +60,36 @@ function run_test() {
Services.prefs.getCharPref("browser.search.countryCode");
ok(false, "not expecting countryCode to be set");
} catch (ex) {}
// should be no success recorded.
let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_SUCCESS");
// should be no result recorded at all.
checkCountryResultTelemetry(null);
// should have set the flag indicating we saw a timeout.
let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT");
let snapshot = histogram.snapshot();
equal(snapshot.sum, 0);
deepEqual(snapshot.counts, [0,1,0]);
// should not yet have SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS recorded as our
// test server is still blocked on our promise.
equal(getProbeSum("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS"), 0);
// should be a timeout.
histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_TIMEOUT");
snapshot = histogram.snapshot();
equal(snapshot.sum, 1);
waitForSearchNotification("geoip-lookup-xhr-complete").then(() => {
// now we *should* have a report of how long the response took even though
// it timed out.
// The telemetry "sum" will be the actual time in ms - just check it's non-zero.
ok(getProbeSum("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS") != 0);
// should have reported the fetch ended up being successful
checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.SUCCESS);
do_test_finished();
server.stop(run_next_test);
// and should have the result of the response that finally came in, and
// everything dependent should also be updated.
equal(Services.prefs.getCharPref("browser.search.countryCode"), "AU");
equal(Services.prefs.getBoolPref("browser.search.isUS"), false);
do_test_finished();
server.stop(run_next_test);
});
// now tell the server to send its response. That will end up causing the
// search service to notify of that the response was received.
resolveContinuePromise();
});
do_test_pending();
}

View File

@ -0,0 +1,105 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// This is testing the long, last-resort XHR-based timeout for the location
// search.
function startServer(continuePromise) {
let srv = new HttpServer();
function lookupCountry(metadata, response) {
response.processAsync();
// wait for our continuePromise to resolve before writing a valid
// response.
// This will be resolved after the timeout period, so we can check
// the behaviour in that case.
continuePromise.then(() => {
response.setStatusLine("1.1", 200, "OK");
response.write('{"country_code" : "AU"}');
response.finish();
});
}
srv.registerPathHandler("/lookup_country", lookupCountry);
srv.start(-1);
return srv;
}
function verifyProbeSum(probe, sum) {
let histogram = Services.telemetry.getHistogramById(probe);
let snapshot = histogram.snapshot();
equal(snapshot.sum, sum, probe);
}
function run_test() {
removeMetadata();
removeCacheFile();
do_check_false(Services.search.isInitialized);
let engineDummyFile = gProfD.clone();
engineDummyFile.append("searchplugins");
engineDummyFile.append("test-search-engine.xml");
let engineDir = engineDummyFile.parent;
engineDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
do_get_file("data/engine.xml").copyTo(engineDir, "engine.xml");
do_register_cleanup(function() {
removeMetadata();
removeCacheFile();
});
let resolveContinuePromise;
let continuePromise = new Promise(resolve => {
resolveContinuePromise = resolve;
});
let server = startServer(continuePromise);
let url = "http://localhost:" + server.identity.primaryPort + "/lookup_country";
Services.prefs.setCharPref("browser.search.geoip.url", url);
// The timeout for the timer.
Services.prefs.setIntPref("browser.search.geoip.timeout", 10);
let promiseXHRStarted = waitForSearchNotification("geoip-lookup-xhr-starting");
Services.search.init(() => {
try {
Services.prefs.getCharPref("browser.search.countryCode");
ok(false, "not expecting countryCode to be set");
} catch (ex) {}
// should be no result recorded at all.
checkCountryResultTelemetry(null);
// should have set the flag indicating we saw a timeout.
let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT");
let snapshot = histogram.snapshot();
deepEqual(snapshot.counts, [0,1,0]);
// should not have SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS recorded as our
// test server is still blocked on our promise.
verifyProbeSum("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS", 0);
promiseXHRStarted.then(xhr => {
// Set the timeout on the xhr object to an extremely low value, so it
// should timeout immediately.
xhr.timeout = 10;
// wait for the xhr timeout to fire.
waitForSearchNotification("geoip-lookup-xhr-complete").then(() => {
// should have the XHR timeout recorded.
checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.XHRTIMEOUT);
// still should not have a report of how long the response took as we
// only record that on success responses.
verifyProbeSum("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS", 0);
// and we don't know the country code.
try {
Services.prefs.getCharPref("browser.search.countryCode");
ok(false, "not expecting countryCode to be set");
} catch (ex) {}
// unblock the server even though nothing is listening.
resolveContinuePromise();
do_test_finished();
server.stop(run_next_test);
});
});
});
do_test_pending();
}

View File

@ -47,20 +47,8 @@ function getDefaultEngineName() {
return Services.prefs.getComplexValue(pref, nsIPLS).data;
}
function waitForNotification(aExpectedData) {
let deferred = Promise.defer();
const SEARCH_SERVICE_TOPIC = "browser-search-service";
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
if (aData != aExpectedData)
return;
Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC);
deferred.resolve();
}, SEARCH_SERVICE_TOPIC, false);
return deferred.promise;
}
// waitForSearchNotification is in head_search.js
let waitForNotification = waitForSearchNotification;
function asyncInit() {
let deferred = Promise.defer();

View File

@ -35,6 +35,7 @@ support-files =
[test_location_malformed_json.js]
[test_location_sync.js]
[test_location_timeout.js]
[test_location_timeout_xhr.js]
[test_nodb.js]
[test_nodb_pluschanges.js]
[test_save_sorted_engines.js]

View File

@ -4577,31 +4577,32 @@
"extended_statistics_ok": true,
"description": "Time (ms) it takes to build the cache of the search service"
},
"SEARCH_SERVICE_COUNTRY_FETCH_MS": {
"SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS": {
"alert_emails": ["mhammond@mozilla.com", "gavin@mozilla.com"],
"expires_in_version": "never",
"kind": "linear",
"n_buckets": 20,
"high": 2000,
"kind": "exponential",
"n_buckets": 30,
"high": 100000,
"description": "Time (ms) it takes to fetch the country code"
},
"SEARCH_SERVICE_COUNTRY_FETCH_TIMEOUT": {
"SEARCH_SERVICE_COUNTRY_FETCH_RESULT": {
"alert_emails": ["mhammond@mozilla.com", "gavin@mozilla.com"],
"expires_in_version": "never",
"kind": "flag",
"description": "If we saw a timeout fetching the country-code"
"kind": "enumerated",
"n_values": 8,
"description": "Result of XHR request fetching the country-code. 0=SUCCESS, 1=SUCCESS_WITHOUT_DATA, 2=XHRTIMEOUT, 3=ERROR (rest reserved for finer-grained error codes later)"
},
"SEARCH_SERVICE_COUNTRY_SUCCESS": {
"SEARCH_SERVICE_COUNTRY_TIMEOUT": {
"alert_emails": ["mhammond@mozilla.com", "gavin@mozilla.com"],
"expires_in_version": "never",
"kind": "boolean",
"description": "If we successfully fetched the country-code."
"description": "True if we stopped waiting for the XHR response before it completed"
},
"SEARCH_SERVICE_COUNTRY_SUCCESS_WITHOUT_DATA": {
"SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT": {
"alert_emails": ["mhammond@mozilla.com", "gavin@mozilla.com"],
"expires_in_version": "never",
"kind": "flag",
"description": "If we got a success response but no country-code"
"kind": "boolean",
"description": "True if the search service was synchronously initialized while we were waiting for the XHR response"
},
"SEARCH_SERVICE_US_COUNTRY_MISMATCHED_TIMEZONE": {
"alert_emails": ["mhammond@mozilla.com", "gavin@mozilla.com"],

View File

@ -416,17 +416,35 @@ let Printing = {
printSettings = null;
}
// We'll call this whenever we've finished reflowing the document, or if
// we errored out while attempting to print preview (in which case, we'll
// notify the parent that we've failed).
let notifyEntered = (error) => {
removeEventListener("printPreviewUpdate", onPrintPreviewReady);
sendAsyncMessage("Printing:Preview:Entered", {
failed: !!error,
});
};
let onPrintPreviewReady = () => {
notifyEntered();
};
// We have to wait for the print engine to finish reflowing all of the
// documents and subdocuments before we can tell the parent to flip to
// the print preview UI - otherwise, the print preview UI might ask for
// information (like the number of pages in the document) before we have
// our PresShells set up.
addEventListener("printPreviewUpdate", function onPrintPreviewReady() {
removeEventListener("printPreviewUpdate", onPrintPreviewReady);
sendAsyncMessage("Printing:Preview:Entered");
});
addEventListener("printPreviewUpdate", onPrintPreviewReady);
docShell.printPreview.printPreview(printSettings, contentWindow, this);
try {
docShell.printPreview.printPreview(printSettings, contentWindow, this);
} catch(error) {
// This might fail if we, for example, attempt to print a XUL document.
// In that case, we inform the parent to bail out of print preview.
Components.utils.reportError(error);
notifyEntered(error);
}
},
exitPrintPreview() {

View File

@ -1596,6 +1596,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
// we need extra stuff.
this._extraBox.hidden = true;
this._titleBox.flex = 1;
this._typeImage.hidden = false;
this.removeAttribute("actiontype");
this.classList.remove("overridable-action");
@ -1681,7 +1682,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
type = [...types].join(" ");
// If we have a tag match, show the tags and icon
if (type == "tag") {
if (type == "tag" || type == "bookmark-tag") {
// Configure the extra box for tags display
this._extraBox.hidden = false;
this._extraBox.childNodes[0].hidden = false;
@ -1699,8 +1700,13 @@ extends="chrome://global/content/bindings/popup.xml#popup">
// Emphasize the matching text in the tags
this._setUpDescription(this._extra, sortedTags);
// Treat tagged matches as bookmarks for the star
type = "bookmark";
// If we're suggesting bookmarks, then treat tagged matches as
// bookmarks for the star.
if (type == "bookmark-tag") {
type = "bookmark";
} else {
this._typeImage.hidden = true;
}
// keyword and favicon type results for search engines
// have an extra magnifying glass icon after them
} else if (type == "keyword" || (initialTypes.has("search") &&

View File

@ -19,6 +19,11 @@
readonly="true">
<getter><![CDATA[
if (!this._securityUI) {
// Don't attempt to create the remote web progress if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
let RemoteSecurityUI = Components.utils.import(jsm, {}).RemoteSecurityUI;
this._securityUI = new RemoteSecurityUI();
@ -40,6 +45,8 @@
]]></body>
</method>
<field name="_controller">null</field>
<field name="_remoteWebNavigation">null</field>
<property name="webNavigation"
@ -52,8 +59,13 @@
<getter>
<![CDATA[
if (!this._remoteWebProgress) {
// Don't attempt to create the remote web progress if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
let RemoteWebProgressManager = Cu.import(jsm, {}).RemoteWebProgressManager;
let { RemoteWebProgressManager } = Cu.import(jsm, {});
this._remoteWebProgressManager = new RemoteWebProgressManager(this);
this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress;
}
@ -67,8 +79,13 @@
<property name="finder" readonly="true">
<getter><![CDATA[
if (!this._remoteFinder) {
// Don't attempt to create the remote web progress if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteFinder.jsm";
let RemoteFinder = Cu.import(jsm, {}).RemoteFinder;
let { RemoteFinder } = Cu.import(jsm, {});
this._remoteFinder = new RemoteFinder(this);
}
return this._remoteFinder;
@ -206,6 +223,8 @@
</setter>
</property>
<field name="mDestroyed">false</field>
<constructor>
<![CDATA[
/*
@ -246,10 +265,24 @@
<destructor>
<![CDATA[
Services.obs.removeObserver(this, "ask-children-to-exit-fullscreen");
this.destroy();
]]>
</destructor>
<!-- This is necessary because the destructor doesn't always get called when
we are removed from a tabbrowser. This will be explicitly called by tabbrowser -->
<method name="destroy">
<body><![CDATA[
if (this.mDestroyed)
return;
this.mDestroyed = true;
this.controllers.removeController(this._controller);
Services.obs.removeObserver(this, "ask-children-to-exit-fullscreen");
]]></body>
</method>
<method name="receiveMessage">
<parameter name="aMessage"/>
<body><![CDATA[

View File

@ -24,6 +24,7 @@ support-files =
signed-untrusted.xpi
signed.xpi
signed2.xpi
slowinstall.sjs
startsoftwareupdate.html
theme.xpi
triggerredirect.html

View File

@ -0,0 +1,101 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
const RELATIVE_PATH = "browser/toolkit/mozapps/extensions/test/xpinstall"
const NOTIFICATION_TOPIC = "slowinstall-complete";
/**
* Helper function to create a JS object representing the url parameters from
* the request's queryString.
*
* @param aQueryString
* The request's query string.
* @return A JS object representing the url parameters from the request's
* queryString.
*/
function parseQueryString(aQueryString) {
var paramArray = aQueryString.split("&");
var regex = /^([^=]+)=(.*)$/;
var params = {};
for (var i = 0, sz = paramArray.length; i < sz; i++) {
var match = regex.exec(paramArray[i]);
if (!match)
throw "Bad parameter in queryString! '" + paramArray[i] + "'";
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
}
return params;
}
function handleRequest(aRequest, aResponse) {
let id = +getState("ID");
setState("ID", "" + (id + 1));
function LOG(str) {
dump("slowinstall.sjs[" + id + "]: " + str + "\n");
}
aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
var params = { };
if (aRequest.queryString)
params = parseQueryString(aRequest.queryString);
if (params.file) {
let xpiFile = "";
function complete_download() {
LOG("Completing download");
downloadPaused = false;
try {
// Doesn't seem to be a sane way to read using OS.File and write to an
// nsIOutputStream so here we are.
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(xpiFile);
let stream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
stream.init(file, -1, -1, stream.DEFER_OPEN + stream.CLOSE_ON_EOF);
NetUtil.asyncCopy(stream, aResponse.bodyOutputStream, () => {
LOG("Download complete");
aResponse.finish();
});
}
catch (e) {
LOG("Exception " + e);
}
}
let waitForComplete = new Promise(resolve => {
function complete() {
Services.obs.removeObserver(complete, NOTIFICATION_TOPIC);
resolve();
}
Services.obs.addObserver(complete, NOTIFICATION_TOPIC, false);
});
aResponse.processAsync();
OS.File.getCurrentDirectory().then(dir => {
xpiFile = OS.Path.join(dir, ...RELATIVE_PATH.split("/"), params.file);
LOG("Starting slow download of " + xpiFile);
OS.File.stat(xpiFile).then(info => {
aResponse.setHeader("Content-Type", "binary/octet-stream");
aResponse.setHeader("Content-Length", info.size.toString());
LOG("Download paused");
waitForComplete.then(complete_download);
});
});
}
else if (params.continue) {
dump("slowinstall.sjs: Received signal to complete all current downloads.\n");
Services.obs.notifyObservers(null, NOTIFICATION_TOPIC, null);
}
}