author | Ryan VanderMeulen <ryanvm@gmail.com> |
Thu, 04 Jun 2015 09:24:15 -0400 | |
changeset 247139 | 5b4c240e1a36ed27db67f21dc35c1ca2e8934d0c |
parent 247118 | f21fac50a8cca34cf708d6acffca39e1d4d4e6d8 (current diff) |
parent 247138 | 4b69a62d1905679020c20fc3f06645140532eb3e (diff) |
child 247143 | dbc89e025b5f37be29eb360f6338a667ac4d6f26 |
child 247193 | d421de06735de2701094b38db71280badd584ed4 |
child 247214 | b538d26f89743b9a0ce5d1a3d268a24b2468fffc |
push id | 28854 |
push user | ryanvm@gmail.com |
push date | Thu, 04 Jun 2015 13:24:20 +0000 |
treeherder | mozilla-central@5b4c240e1a36 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 41.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -173,19 +173,17 @@ pref("app.update.mode", 1); pref("app.update.silent", false); // If set to true, the hamburger button will show badges for update events. #ifndef RELEASE_BUILD pref("app.update.badge", true); #else pref("app.update.badge", false); #endif -// Give the user x seconds to reboot before showing a badge on the hamburger -// button. default=4 days -pref("app.update.badgeWaitTime", 345600); +// app.update.badgeWaitTime is in branding section // If set to true, the Update Service will apply updates in the background // when it finishes downloading them. pref("app.update.staging.enabled", true); // Update service URL: pref("app.update.url", "https://aus4.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml"); // app.update.url.manual is in branding section
--- a/browser/base/content/browser-fxaccounts.js +++ b/browser/base/content/browser-fxaccounts.js @@ -26,16 +26,17 @@ let gFxAccounts = { get topics() { // Do all this dance to lazy-load FxAccountsCommon. delete this.topics; return this.topics = [ "weave:service:ready", "weave:service:sync:start", "weave:service:login:error", "weave:service:setup-complete", + "weave:ui:login:error", "fxa-migration:state-changed", this.FxAccountsCommon.ONVERIFIED_NOTIFICATION, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION, "weave:notification:removed", ]; }, get button() {
--- a/browser/base/content/browser-syncui.js +++ b/browser/base/content/browser-syncui.js @@ -222,16 +222,17 @@ let gSyncUI = { this.clearError(title); }, onSetupComplete: function SUI_onSetupComplete() { this.onLoginFinish(); }, onLoginError: function SUI_onLoginError() { + this.log.debug("onLoginError: login=${login}, sync=${sync}", Weave.Status); // Note: This is used for *both* Sync and ReadingList login errors. // if login fails, any other notifications are essentially moot Weave.Notifications.removeAll(); // if we haven't set up the client, don't show errors if (this._needsSetup()) { this.updateUI(); return; @@ -457,16 +458,24 @@ let gSyncUI = { // Clear out sync failures on a successful sync this.clearError(title); }, // Return true if the reading-list is in a "prolonged" error state. That // engine doesn't impose what that means, so calculate it here. For // consistency, we just use the sync prefs. isProlongedReadingListError() { + // If the readinglist scheduler is disabled we don't treat it as prolonged. + let enabled = false; + try { + enabled = Services.prefs.getBoolPref("readinglist.scheduler.enabled"); + } catch (_) {} + if (!enabled) { + return false; + } let lastSync, threshold, prolonged; try { lastSync = new Date(Services.prefs.getCharPref("readinglist.scheduler.lastSync")); threshold = new Date(Date.now() - Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") * 1000); prolonged = lastSync <= threshold; } catch (ex) { // no pref, assume not prolonged. prolonged = false; @@ -515,17 +524,17 @@ let gSyncUI = { let notification = new Weave.Notification(title, description, null, priority, buttons); Weave.Notifications.replaceTitle(notification); this.updateUI(); }, onSyncError: function SUI_onSyncError() { - this.log.debug("onSyncError"); + this.log.debug("onSyncError: login=${login}, sync=${sync}", Weave.Status); let title = this._stringBundle.GetStringFromName("error.sync.title"); if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) { this.onLoginError(); return; } let description;
--- a/browser/base/content/test/general/browser_syncui.js +++ b/browser/base/content/test/general/browser_syncui.js @@ -32,16 +32,33 @@ add_task(function* prepare() { // mock out the "_needsSetup()" function so we don't short-circuit. let oldNeedsSetup = window.gSyncUI._needsSetup; window.gSyncUI._needsSetup = () => false; registerCleanupFunction(() => { window.gSyncUI._needsSetup = oldNeedsSetup; }); }); +add_task(function* testNotProlongedRLErrorWhenDisabled() { + // Here we arrange for the (dead?) readinglist scheduler to have a last-synced + // date of long ago and the RL scheduler is disabled. + // gSyncUI.isProlongedReadingListError() should return false. + // Pretend the reading-list is in the "prolonged error" state. + let longAgo = new Date(Date.now() - 100 * 24 * 60 * 60 * 1000); // 100 days ago. + Services.prefs.setCharPref("readinglist.scheduler.lastSync", longAgo.toString()); + + // It's prolonged while it's enabled. + Services.prefs.setBoolPref("readinglist.scheduler.enabled", true); + Assert.equal(gSyncUI.isProlongedReadingListError(), true); + + // But false when disabled. + Services.prefs.setBoolPref("readinglist.scheduler.enabled", false); + Assert.equal(gSyncUI.isProlongedReadingListError(), false); +}); + add_task(function* testProlongedSyncError() { let promiseNotificationAdded = promiseObserver("weave:notification:added"); Assert.equal(Notifications.notifications.length, 0, "start with no notifications"); // Pretend we are in the "prolonged error" state. Weave.Status.sync = Weave.PROLONGED_SYNC_FAILURE; Weave.Status.login = Weave.LOGIN_SUCCEEDED; Services.obs.notifyObservers(null, "weave:ui:sync:error", null); @@ -55,16 +72,17 @@ add_task(function* testProlongedSyncErro let promiseNotificationRemoved = promiseObserver("weave:notification:removed"); Weave.Status.sync = Weave.STATUS_OK; Services.obs.notifyObservers(null, "weave:ui:sync:finish", null); yield promiseNotificationRemoved; Assert.equal(Notifications.notifications.length, 0, "no notifications left"); }); add_task(function* testProlongedRLError() { + Services.prefs.setBoolPref("readinglist.scheduler.enabled", true); let promiseNotificationAdded = promiseObserver("weave:notification:added"); Assert.equal(Notifications.notifications.length, 0, "start with no notifications"); // Pretend the reading-list is in the "prolonged error" state. let longAgo = new Date(Date.now() - 100 * 24 * 60 * 60 * 1000); // 100 days ago. Services.prefs.setCharPref("readinglist.scheduler.lastSync", longAgo.toString()); getInternalScheduler().state = ReadingListScheduler.STATE_ERROR_OTHER; Services.obs.notifyObservers(null, "readinglist:sync:start", null);
--- a/browser/branding/aurora/pref/firefox-branding.js +++ b/browser/branding/aurora/pref/firefox-branding.js @@ -21,11 +21,15 @@ pref("app.update.url.manual", "https://w // supplied in the "An update is available" page of the update wizard. pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/"); // The number of days a binary is permitted to be old // without checking for an update. This assumes that // app.update.checkInstallTime is true. pref("app.update.checkInstallTime.days", 2); +// Give the user x seconds to reboot before showing a badge on the hamburger +// button. default=4 days +pref("app.update.badgeWaitTime", 345600); + // Number of usages of the web console or scratchpad. // If this is less than 5, then pasting code into the web console or scratchpad is disabled -pref("devtools.selfxss.count", 5); \ No newline at end of file +pref("devtools.selfxss.count", 5);
--- a/browser/branding/nightly/pref/firefox-branding.js +++ b/browser/branding/nightly/pref/firefox-branding.js @@ -19,11 +19,15 @@ pref("app.update.url.manual", "https://n // supplied in the "An update is available" page of the update wizard. pref("app.update.url.details", "https://nightly.mozilla.org"); // The number of days a binary is permitted to be old // without checking for an update. This assumes that // app.update.checkInstallTime is true. pref("app.update.checkInstallTime.days", 2); +// Give the user x seconds to reboot before showing a badge on the hamburger +// button. default=immediately +pref("app.update.badgeWaitTime", 0); + // Number of usages of the web console or scratchpad. // If this is less than 5, then pasting code into the web console or scratchpad is disabled -pref("devtools.selfxss.count", 5); \ No newline at end of file +pref("devtools.selfxss.count", 5);
--- a/browser/branding/official/pref/firefox-branding.js +++ b/browser/branding/official/pref/firefox-branding.js @@ -18,11 +18,15 @@ pref("app.update.url.manual", "https://w // supplied in the "An update is available" page of the update wizard. pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes"); // The number of days a binary is permitted to be old // without checking for an update. This assumes that // app.update.checkInstallTime is true. pref("app.update.checkInstallTime.days", 63); +// Give the user x seconds to reboot before showing a badge on the hamburger +// button. default=immediately +pref("app.update.badgeWaitTime", 0); + // Number of usages of the web console or scratchpad. // If this is less than 5, then pasting code into the web console or scratchpad is disabled -pref("devtools.selfxss.count", 0); \ No newline at end of file +pref("devtools.selfxss.count", 0);
--- a/browser/branding/unofficial/pref/firefox-branding.js +++ b/browser/branding/unofficial/pref/firefox-branding.js @@ -18,11 +18,15 @@ pref("app.update.url.manual", "https://n // supplied in the "An update is available" page of the update wizard. pref("app.update.url.details", "https://nightly.mozilla.org"); // The number of days a binary is permitted to be old // without checking for an update. This assumes that // app.update.checkInstallTime is true. pref("app.update.checkInstallTime.days", 2); +// Give the user x seconds to reboot before showing a badge on the hamburger +// button. default=immediately +pref("app.update.badgeWaitTime", 0); + // Number of usages of the web console or scratchpad. // If this is less than 5, then pasting code into the web console or scratchpad is disabled -pref("devtools.selfxss.count", 0); \ No newline at end of file +pref("devtools.selfxss.count", 0);
--- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -2623,19 +2623,19 @@ let DefaultBrowserCheck = { this._notification.close(); } }, setAsDefault: function() { let claimAllTypes = true; #ifdef XP_WIN try { - // In Windows 8, the UI for selecting default protocol is much + // In Windows 8+, the UI for selecting default protocol is much // nicer than the UI for setting file type associations. So we - // only show the protocol association screen on Windows 8. + // only show the protocol association screen on Windows 8+. // Windows 8 is version 6.2. let version = Services.sysinfo.getProperty("version"); claimAllTypes = (parseFloat(version) < 6.2); } catch (ex) { } #endif try { ShellService.setDefaultBrowser(claimAllTypes, false); } catch (ex) {
--- a/browser/components/places/PlacesUIUtils.jsm +++ b/browser/components/places/PlacesUIUtils.jsm @@ -1466,19 +1466,16 @@ XPCOMUtils.defineLazyGetter(PlacesUIUtil new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject), setPageAnnotation: function(aURI, aAnnotationObject) new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject), editBookmarkKeyword: function(aItemId, aNewKeyword) new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword), - editBookmarkPostData: function(aItemId, aPostData) - new PlacesEditBookmarkPostDataTransaction(aItemId, aPostData), - editLivemarkSiteURI: function(aLivemarkId, aSiteURI) new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI), editLivemarkFeedURI: function(aLivemarkId, aFeedURI) new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI), editItemDateAdded: function(aItemId, aNewDateAdded) new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded),
--- a/browser/components/places/content/bookmarkProperties.js +++ b/browser/components/places/content/bookmarkProperties.js @@ -278,17 +278,18 @@ var BookmarkPropertiesPanel = { gEditItemOverlay.initPanel({ node: this._node , hiddenRows: this._hiddenRows }); acceptButton.disabled = gEditItemOverlay.readOnly; break; case ACTION_ADD: this._node = yield this._promiseNewItem(); // Edit the new item gEditItemOverlay.initPanel({ node: this._node - , hiddenRows: this._hiddenRows }); + , hiddenRows: this._hiddenRows + , postData: this._postData }); // Empty location field if the uri is about:blank, this way inserting a new // url will be easier for the user, Accept button will be automatically // disabled by the input listener until the user fills the field. let locationField = this._element("locationField"); if (locationField.value == "about:blank") locationField.value = ""; @@ -520,32 +521,28 @@ var BookmarkPropertiesPanel = { if (this._loadInSidebar) { let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO, value : true }; let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj); childTransactions.push(setLoadTxn); } - if (this._postData) { - let postDataTxn = new PlacesEditBookmarkPostDataTransaction(-1, this._postData); - childTransactions.push(postDataTxn); - } - //XXX TODO: this should be in a transaction! if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window)) PlacesUtils.setCharsetForURI(this._uri, this._charSet); let createTxn = new PlacesCreateBookmarkTransaction(this._uri, aContainer, aIndex, this._title, this._keyword, annotations, - childTransactions); + childTransactions, + this._postData); return new PlacesAggregatedTransaction(this._getDialogTitle(), [createTxn]); }, /** * Returns a childItems-transactions array representing the URIList with * which the dialog has been opened.
--- a/browser/components/places/content/editBookmarkOverlay.js +++ b/browser/components/places/content/editBookmarkOverlay.js @@ -37,29 +37,30 @@ let gEditItemOverlay = { let isURI = node && PlacesUtils.nodeIsURI(node); let uri = isURI ? NetUtil.newURI(node.uri) : null; let title = node ? node.title : null; let isBookmark = isItem && isURI; let bulkTagging = !node; let uris = bulkTagging ? aInitInfo.uris : null; let visibleRows = new Set(); let isParentReadOnly = false; + let postData = aInitInfo.postData; if (node && "parent" in node) { let parent = node.parent; if (parent) { isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) || PlacesUIUtils.isContentsReadOnly(parent); } } return this._paneInfo = { itemId, itemGuid, isItem, isURI, uri, title, isBookmark, isFolderShortcut, isParentReadOnly, bulkTagging, uris, - visibleRows }; + visibleRows, postData }; }, get initialized() { return this._paneInfo != null; }, // Backwards-compatibility getters get itemId() { @@ -591,17 +592,17 @@ let gEditItemOverlay = { onKeywordFieldChange() { if (this.readOnly || !this._paneInfo.isBookmark) return; let itemId = this._paneInfo.itemId; let newKeyword = this._keywordField.value; if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditBookmarkKeywordTransaction(itemId, newKeyword); + let txn = new PlacesEditBookmarkKeywordTransaction(itemId, newKeyword, this._paneInfo.postData); PlacesUtils.transactionManager.doTransaction(txn); return; } let guid = this._paneInfo.itemGuid; PlacesTransactions.EditKeyword({ guid, keyword: newKeyword }) .transact().catch(Components.utils.reportError); },
--- a/browser/components/places/tests/browser/browser.ini +++ b/browser/components/places/tests/browser/browser.ini @@ -5,47 +5,45 @@ [DEFAULT] skip-if = buildapp == "mulet" support-files = head.js framedPage.html frameLeft.html frameRight.html sidebarpanels_click_test_page.html + keyword_form.html [browser_0_library_left_pane_migration.js] -[browser_library_left_pane_fixnames.js] -[browser_425884.js] -[browser_475045.js] +[browser_410196_paste_into_tags.js] +[browser_416459_cut.js] [browser_423515.js] -[browser_410196_paste_into_tags.js] -[browser_sort_in_library.js] -[browser_library_open_leak.js] -[browser_library_panel_leak.js] -[browser_library_search.js] -[browser_history_sidebar_search.js] -skip-if = e10s && (os == 'linux' || os == 'mac') # Bug 1116457 +[browser_425884.js] +[browser_435851_copy_query.js] +[browser_475045.js] +[browser_555547.js] +[browser_bookmarkProperties_addKeywordForThisSearch.js] [browser_bookmarksProperties.js] - -[browser_forgetthissite_single.js] -[browser_library_commands.js] [browser_drag_bookmarks_on_toolbar.js] skip-if = e10s # Bug ?????? - test fails - "Number of dragged items should be the same. - Got 0, expected 1" +[browser_forgetthissite_single.js] +[browser_history_sidebar_search.js] +skip-if = e10s && (os == 'linux' || os == 'mac') # Bug 1116457 +[browser_library_batch_delete.js] +[browser_library_commands.js] +[browser_library_downloads.js] +[browser_library_infoBox.js] +[browser_library_left_pane_fixnames.js] +[browser_library_left_pane_select_hierarchy.js] [browser_library_middleclick.js] +[browser_library_open_leak.js] +[browser_library_openFlatContainer.js] +[browser_library_panel_leak.js] +[browser_library_search.js] [browser_library_views_liveupdate.js] -[browser_views_liveupdate.js] - -[browser_sidebarpanels_click.js] -# temporarily disabled for breaking the treeview - bug 658744 -skip-if = true - -[browser_library_infoBox.js] [browser_markPageAsFollowedLink.js] skip-if = e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly (test does EventUtils.sendMouseEvent...) +[browser_sidebarpanels_click.js] +skip-if = true # temporarily disabled for breaking the treeview - bug 658744 +[browser_sort_in_library.js] [browser_toolbar_migration.js] -[browser_library_batch_delete.js] -[browser_555547.js] -[browser_416459_cut.js] -[browser_library_downloads.js] -[browser_library_left_pane_select_hierarchy.js] -[browser_435851_copy_query.js] [browser_toolbarbutton_menu_context.js] -[browser_library_openFlatContainer.js] +[browser_views_liveupdate.js]
new file mode 100644 --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js @@ -0,0 +1,57 @@ +"use strict" + +const TEST_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser/keyword_form.html"; + +add_task(function* () { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: TEST_URL, + }, function* (browser) { + // We must wait for the context menu code to build metadata. + yield openContextMenuForContentSelector(browser, 'form > input[name="search"]'); + + yield withBookmarksDialog(function*() { + AddKeywordForSearchField(); + }, function* (dialogWin) { + let acceptBtn = dialogWin.document.documentElement.getButton("accept"); + ok(acceptBtn.disabled, "Accept button is disabled"); + + let promiseKeywordNotification = promiseBookmarksNotification( + "onItemChanged", (itemId, prop, isAnno, val) => prop == "keyword" && val =="kw"); + + fillBookmarkTextField("editBMPanel_keywordField", "kw", dialogWin); + + ok(!acceptBtn.disabled, "Accept button is enabled"); + + // The dialog is instant apply. + yield promiseKeywordNotification; + + // After the notification, the keywords cache will update asynchronously. + info("Check the keyword entry has been created"); + let entry; + yield waitForCondition(function* () { + entry = yield PlacesUtils.keywords.fetch("kw"); + return !!entry; + },"Unable to find the expected keyword"); + is(entry.keyword, "kw", "keyword is correct"); + is(entry.url.href, TEST_URL, "URL is correct"); + is(entry.postData, "accenti%3D%E0%E8%EC%F2%F9&search%3D%25s", "POST data is correct"); + + info("Check the charset has been saved"); + let charset = yield PlacesUtils.getCharsetForURI(NetUtil.newURI(TEST_URL)); + is(charset, "windows-1252", "charset is correct"); + + // Now check getShortcutOrURI. + let data = yield getShortcutOrURIAndPostData("kw test"); + is(getPostDataString(data.postData), "accenti=\u00E0\u00E8\u00EC\u00F2\u00F9&search=test", "getShortcutOrURI POST data is correct"); + is(data.url, TEST_URL, "getShortcutOrURI URL is correct"); + }); + }); +}); + +function getPostDataString(stream) { + let sis = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + sis.init(stream); + return sis.read(stream.available()).split("\n").pop(); +}
--- a/browser/components/places/tests/browser/head.js +++ b/browser/components/places/tests/browser/head.js @@ -157,28 +157,31 @@ function promiseIsURIVisited(aURI) { PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) { deferred.resolve(aIsVisited); }); return deferred.promise; } function promiseBookmarksNotification(notification, conditionFn) { - info(`Waiting for ${notification}`); + info(`promiseBookmarksNotification: waiting for ${notification}`); return new Promise((resolve, reject) => { let proxifiedObserver = new Proxy({}, { get: (target, name) => { if (name == "QueryInterface") return XPCOMUtils.generateQI([ Ci.nsINavBookmarkObserver ]); + info(`promiseBookmarksNotification: got ${name} notification`); if (name == notification) return () => { if (conditionFn.apply(this, arguments)) { clearTimeout(timeout); PlacesUtils.bookmarks.removeObserver(proxifiedObserver, false); executeSoon(resolve); + } else { + info(`promiseBookmarksNotification: skip cause condition doesn't apply to ${JSON.stringify(arguments)}`); } } return () => {}; } }); PlacesUtils.bookmarks.addObserver(proxifiedObserver, false); let timeout = setTimeout(() => { PlacesUtils.bookmarks.removeObserver(proxifiedObserver, false); @@ -273,8 +276,136 @@ function promiseSetToolbarVisibility(aTo function isToolbarVisible(aToolbar) { let hidingAttribute = aToolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed"; let hidingValue = aToolbar.getAttribute(hidingAttribute).toLowerCase(); // Check for both collapsed="true" and collapsed="collapsed" return hidingValue !== "true" && hidingValue !== hidingAttribute; } + +/** + * Executes a task after opening the bookmarks dialog, then cancels the dialog. + * + * @param openFn + * generator function causing the dialog to open + * @param task + * the task to execute once the dialog is open + */ +let withBookmarksDialog = Task.async(function* (openFn, taskFn) { + let dialogPromise = new Promise(resolve => { + Services.ww.registerNotification(function winObserver(subject, topic, data) { + if (topic != "domwindowopened") + return; + let win = subject.QueryInterface(Ci.nsIDOMWindow); + win.addEventListener("load", function load() { + win.removeEventListener("load", load); + ok(win.location.href.startsWith("chrome://browser/content/places/bookmarkProperties"), + "The bookmark properties dialog is ready"); + Services.ww.unregisterNotification(winObserver); + // This is needed for the overlay. + waitForFocus(() => { + resolve(win); + }, win); + }); + }); + }); + + info("withBookmarksDialog: opening the dialog"); + yield openFn(); + info("withBookmarksDialog: waiting for the dialog"); + let dialogWin = yield dialogPromise; + + // Ensure overlay is loaded + ok(dialogWin.gEditItemOverlay.initialized, "EditItemOverlay is initialized"); + + info("withBookmarksDialog: executing the task"); + try { + yield taskFn(dialogWin); + } finally { + info("withBookmarksDialog: canceling the dialog"); + dialogWin.document.documentElement.cancelDialog(); + } +}); + +/** + * Opens the contextual menu on the element pointed by the given selector. + * + * @param selector + * Valid selector syntax + * @return the target DOM node. + */ +let openContextMenuForContentSelector = Task.async(function* (browser, selector) { + info("wait for the context menu"); + let contextPromise = BrowserTestUtils.waitForEvent(document.getElementById("contentAreaContextMenu"), + "popupshown"); + yield ContentTask.spawn(browser, { selector }, function* (args) { + let doc = content.document; + let elt = doc.querySelector(args.selector) + dump(`openContextMenuForContentSelector: found ${elt}\n`); + + /* Open context menu so chrome can access the element */ + const domWindowUtils = + content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + let rect = elt.getBoundingClientRect(); + let left = rect.left + rect.width / 2; + let top = rect.top + rect.height / 2; + domWindowUtils.sendMouseEvent("contextmenu", left, top, 2, + 1, 0, false, 0, 0, true); + }); + yield contextPromise; + + return gContextMenuContentData.popupNode; +}); + +/** + * Waits for a specified condition to happen. + * + * @param conditionFn + * a Function or a generator function, returning a boolean for whether + * the condition is fulfilled. + * @param errorMsg + * Error message to use if the condition has not been satisfied after a + * meaningful amount of tries. + */ +let waitForCondition = Task.async(function* (conditionFn, errorMsg) { + for (let tries = 0; tries < 100; ++tries) { + if ((yield conditionFn())) + return; + yield new Promise(resolve => { + if (!waitForCondition._timers) { + waitForCondition._timers = new Set(); + registerCleanupFunction(() => { + is(waitForCondition._timers.size, 0, "All the wait timers have been removed"); + delete waitForCondition._timers; + }); + } + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + waitForCondition._timers.add(timer); + timer.init(() => { + waitForCondition._timers.delete(timer); + resolve(); + }, 100, Ci.nsITimer.TYPE_ONE_SHOT); + }); + } + ok(false, errorMsg); +}); + +/** + * Fills a bookmarks dialog text field ensuring to cause expected edit events. + * + * @param id + * id of the text field + * @param text + * text to fill in + * @param win + * dialog window + */ +function fillBookmarkTextField(id, text, win) { + let elt = win.document.getElementById(id); + elt.focus(); + elt.select(); + for (let c of text.split("")) { + EventUtils.synthesizeKey(c, {}, win); + } + elt.blur(); +}
new file mode 100644 --- /dev/null +++ b/browser/components/places/tests/browser/keyword_form.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> + +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html;charset=windows-1252"> +</head> +<body> + <form method="POST" action="keyword_form.html"> + <input type="hidden" name="accenti" value="àèìòù"> + <input type="text" name="search"> + </form> +</body> +</html>
--- a/browser/components/preferences/in-content/main.js +++ b/browser/components/preferences/in-content/main.js @@ -682,43 +682,67 @@ var gMainPane = { /* * Preferences: * * browser.shell.checkDefault * - true if a default-browser check (and prompt to make it so if necessary) * occurs at startup, false otherwise */ + /** + * Firefox can attempt to set itself as the default application + * for all related filetypes or just for HTML. Some platforms + * such as Windows have terrible UIs for all filetypes. In those + * platforms, Firefox only attempts to associate itself with HTML. + */ + shouldClaimAllTypes: function() + { + let claimAllTypes = true; + try { + if (AppConstants.platform == "win") { + // In Windows 8+, the UI for selecting default protocol is much + // nicer than the UI for setting file type associations. So we + // only show the protocol association screen on Windows 8+. + // Windows 8 is version 6.2. + let version = Services.sysinfo.getProperty("version"); + claimAllTypes = (parseFloat(version) < 6.2); + } + } catch (ex) {} + return claimAllTypes; + }, + /** * Show button for setting browser as default browser or information that * browser is already the default browser. */ updateSetDefaultBrowser: function() { let shellSvc = getShellService(); let defaultBrowserBox = document.getElementById("defaultBrowserBox"); if (!shellSvc) { defaultBrowserBox.hidden = true; return; } let setDefaultPane = document.getElementById("setDefaultPane"); - let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0; + let claimAllTypes = gMainPane.shouldClaimAllTypes(); + let selectedIndex = shellSvc.isDefaultBrowser(false, claimAllTypes) ? 1 : 0; setDefaultPane.selectedIndex = selectedIndex; }, /** * Set browser as the operating system default browser. */ setDefaultBrowser: function() { let shellSvc = getShellService(); if (!shellSvc) return; try { - shellSvc.setDefaultBrowser(true, false); + let claimAllTypes = gMainPane.shouldClaimAllTypes(); + shellSvc.setDefaultBrowser(claimAllTypes, false); } catch (ex) { Cu.reportError(ex); return; } let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0; document.getElementById("setDefaultPane").selectedIndex = selectedIndex; }
--- a/browser/components/shell/nsWindowsShellService.cpp +++ b/browser/components/shell/nsWindowsShellService.cpp @@ -34,16 +34,19 @@ #include "windows.h" #include "shellapi.h" #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0600 #define INITGUID +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_WIN8 +// Needed for access to IApplicationActivationManager #include <shlobj.h> #include <mbstring.h> #include <shlwapi.h> #ifndef MAX_BUF #define MAX_BUF 4096 #endif @@ -658,16 +661,38 @@ nsWindowsShellService::LaunchControlPane } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return NS_OK; } nsresult +nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() +{ + IApplicationActivationManager* pActivator; + HRESULT hr = CoCreateInstance(CLSID_ApplicationActivationManager, + nullptr, + CLSCTX_INPROC, + IID_IApplicationActivationManager, + (void**)&pActivator); + + if (SUCCEEDED(hr)) { + DWORD pid; + hr = pActivator->ActivateApplication( + L"windows.immersivecontrolpanel_cw5n1h2txyewy" + L"!microsoft.windows.immersivecontrolpanel", + L"page=SettingsPageAppsDefaults", AO_NONE, &pid); + pActivator->Release(); + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult nsWindowsShellService::LaunchHTTPHandlerPane() { OPENASINFO info; info.pcszFile = L"http"; info.pcszClass = nullptr; info.oaifInFlags = OAIF_FORCE_REGISTRATION | OAIF_URL_PROTOCOL | OAIF_REGISTER_EXT; @@ -692,19 +717,27 @@ nsWindowsShellService::SetDefaultBrowser if (aClaimAllTypes) { rv = LaunchControlPanelDefaultPrograms(); // The above call should never really fail, but just in case // fall back to showing the HTTP association screen only. if (NS_FAILED(rv)) { rv = LaunchHTTPHandlerPane(); } } else { - rv = LaunchHTTPHandlerPane(); - // The above calls hould never really fail, but just in case - // fallb ack to showing control panel for all defaults + // Windows 10 blocks attempts to load the HTTP Handler + // association dialog, so the modern Settings dialog + // is opened with the Default Apps view loaded. + if (IsWin10OrLater()) { + rv = LaunchModernSettingsDialogDefaultApps(); + } else { + rv = LaunchHTTPHandlerPane(); + } + + // The above call should never really fail, but just in case + // fall back to showing control panel for all defaults if (NS_FAILED(rv)) { rv = LaunchControlPanelDefaultPrograms(); } } } nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefs) {
--- a/browser/components/shell/nsWindowsShellService.h +++ b/browser/components/shell/nsWindowsShellService.h @@ -23,15 +23,16 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSISHELLSERVICE NS_DECL_NSIWINDOWSSHELLSERVICE protected: bool IsDefaultBrowserVista(bool aCheckAllTypes, bool* aIsDefaultBrowser); nsresult LaunchControlPanelDefaultPrograms(); + nsresult LaunchModernSettingsDialogDefaultApps(); nsresult LaunchHTTPHandlerPane(); private: bool mCheckedThisSession; }; #endif // nswindowsshellservice_h____
--- a/build/mobile/remoteautomation.py +++ b/build/mobile/remoteautomation.py @@ -19,16 +19,20 @@ import mozcrash # signatures for logcat messages that we don't care about much fennecLogcatFilters = [ "The character encoding of the HTML document was not declared", "Use of Mutation Events is deprecated. Use MutationObserver instead.", "Unexpected value from nativeGetEnabledTags: 0" ] class RemoteAutomation(Automation): _devicemanager = None + # Part of a hack for Robocop: "am COMMAND" is handled specially if COMMAND + # is in this set. See usages below. + _specialAmCommands = ('instrument', 'start') + def __init__(self, deviceManager, appName = '', remoteLog = None, processArgs=None): self._devicemanager = deviceManager self._appName = appName self._remoteProfile = None self._remoteLog = remoteLog self._processArgs = processArgs or {}; @@ -232,17 +236,17 @@ class RemoteAutomation(Automation): def buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs): # If remote profile is specified, use that instead if (self._remoteProfile): profileDir = self._remoteProfile # Hack for robocop, if app & testURL == None and extraArgs contains the rest of the stuff, lets # assume extraArgs is all we need - if app == "am" and extraArgs[0] == "instrument": + if app == "am" and extraArgs[0] in RemoteAutomation._specialAmCommands: return app, extraArgs cmd, args = Automation.buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs) # Remove -foreground if it exists, if it doesn't this just returns try: args.remove('-foreground') except: pass @@ -270,17 +274,17 @@ class RemoteAutomation(Automation): self.messageLogger = messageLogger if (self.proc is None): if cmd[0] == 'am': self.proc = stdout else: raise Exception("unable to launch process") self.procName = cmd[0].split('/')[-1] - if cmd[0] == 'am' and cmd[1] == "instrument": + if cmd[0] == 'am' and cmd[1] in RemoteAutomation._specialAmCommands: self.procName = app print "Robocop process name: "+self.procName # Setting timeout at 1 hour since on a remote device this takes much longer. # Temporarily increased to 75 minutes because no more chunks can be created. self.timeout = 4500 # The benefit of the following sleep is unclear; it was formerly 15 seconds time.sleep(1)
--- a/build/mobile/robocop/AndroidManifest.xml.in +++ b/build/mobile/robocop/AndroidManifest.xml.in @@ -3,17 +3,21 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.mozilla.roboexample.test" #ifdef MOZ_ANDROID_SHARED_ID android:sharedUserId="@MOZ_ANDROID_SHARED_ID@" #endif android:versionCode="1" android:versionName="1.0" > - <uses-sdk android:minSdkVersion="8" /> + <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@" +#ifdef MOZ_ANDROID_MAX_SDK_VERSION + android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@" +#endif + android:targetSdkVersion="@ANDROID_TARGET_SDK@"/> <instrumentation android:name="org.mozilla.gecko.FennecInstrumentationTestRunner" android:targetPackage="@ANDROID_PACKAGE_NAME@" /> <application android:label="@string/app_name" > <uses-library android:name="android.test.runner" /> @@ -38,11 +42,19 @@ <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/*" /> <data android:mimeType="image/*" /> </intent-filter> </activity> + <activity android:name="org.mozilla.gecko.LaunchFennecWithConfigurationActivity" + android:label="Robocop Fennec"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> </manifest>
new file mode 100644 --- /dev/null +++ b/build/mobile/robocop/LaunchFennecWithConfigurationActivity.java @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko; + +import java.util.Map; + +import org.mozilla.gecko.tests.BaseRobocopTest; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +/** + * An Activity that extracts Robocop settings from robotium.config, launches + * Fennec with the Robocop testing parameters, and finishes itself. + * <p> + * This is intended to be used by local testers using |mach robocop --serve|. + */ +public class LaunchFennecWithConfigurationActivity extends Activity { + @Override + public void onCreate(Bundle arguments) { + super.onCreate(arguments); + } + + @Override + public void onResume() { + super.onResume(); + + final String configFile = FennecNativeDriver.getFile(BaseRobocopTest.DEFAULT_ROOT_PATH + "/robotium.config"); + final Map<String, String> config = FennecNativeDriver.convertTextToTable(configFile); + final Intent intent = BaseRobocopTest.createActivityIntent(config); + + intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS); + + this.finish(); + this.startActivity(intent); + } +}
--- a/build/mobile/robocop/Makefile.in +++ b/build/mobile/robocop/Makefile.in @@ -19,16 +19,17 @@ ANDROID_ASSETS_DIR := $(TESTPATH)/assets Driver.java \ Element.java \ FennecInstrumentationTestRunner.java \ FennecNativeActions.java \ FennecMochitestAssert.java \ FennecTalosAssert.java \ FennecNativeDriver.java \ FennecNativeElement.java \ + LaunchFennecWithConfigurationActivity.java \ RoboCopException.java \ RobocopShare1.java \ RobocopShare2.java \ RobocopUtils.java \ PaintedSurface.java \ StructuredLogger.java \ $(NULL)
--- a/mobile/android/base/GeckoInputConnection.java +++ b/mobile/android/base/GeckoInputConnection.java @@ -12,17 +12,16 @@ import java.util.concurrent.SynchronousQ import org.mozilla.gecko.AppConstants.Versions; import org.mozilla.gecko.gfx.InputConnectionHandler; import org.mozilla.gecko.util.Clipboard; import org.mozilla.gecko.util.GamepadUtils; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.util.ThreadUtils.AssertBehavior; -import android.R; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.text.Editable; import android.text.InputType; import android.text.Selection; import android.text.SpannableString; @@ -286,37 +285,37 @@ class GeckoInputConnection Editable editable = getEditable(); if (editable == null) { return false; } int selStart = Selection.getSelectionStart(editable); int selEnd = Selection.getSelectionEnd(editable); switch (id) { - case R.id.selectAll: + case android.R.id.selectAll: setSelection(0, editable.length()); break; - case R.id.cut: + case android.R.id.cut: // If selection is empty, we'll select everything if (selStart == selEnd) { // Fill the clipboard Clipboard.setText(editable); editable.clear(); } else { Clipboard.setText( editable.toString().substring( Math.min(selStart, selEnd), Math.max(selStart, selEnd))); editable.delete(selStart, selEnd); } break; - case R.id.paste: + case android.R.id.paste: commitText(Clipboard.getText(), 1); break; - case R.id.copy: + case android.R.id.copy: // Copy the current selection or the empty string if nothing is selected. String copiedText = selStart == selEnd ? "" : editable.toString().substring( Math.min(selStart, selEnd), Math.max(selStart, selEnd)); Clipboard.setText(copiedText); break; }
--- a/mobile/android/base/toolbar/ToolbarDisplayLayout.java +++ b/mobile/android/base/toolbar/ToolbarDisplayLayout.java @@ -224,16 +224,17 @@ public class ToolbarDisplayLayout extend final int lockAnimDuration = 300; mLockFadeIn.setDuration(lockAnimDuration); mTitleSlideLeft.setDuration(lockAnimDuration); mTitleSlideRight.setDuration(lockAnimDuration); } @Override public void onDetachedFromWindow() { + super.onDetachedFromWindow(); mIsAttached = false; } @Override public void onAnimationStart(Animation animation) { if (animation.equals(mLockFadeIn)) { if (mSiteSecurityVisible) mSiteSecurity.setVisibility(View.VISIBLE);
new file mode 100644 --- /dev/null +++ b/mobile/android/gradle/base/lint.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<lint> + <!-- Enable relevant checks disabled by default --> + <issue id="NegativeMargin" severity="warning" /> +</lint>
--- a/mobile/android/mach_commands.py +++ b/mobile/android/mach_commands.py @@ -125,16 +125,17 @@ class MachCommands(MachCommandBase): srcdir('app/src/robocop_harness/org/mozilla/gecko', 'build/mobile/robocop') srcdir('app/src/robocop/org/mozilla/gecko/tests', 'mobile/android/tests/browser/robocop') srcdir('app/src/background/org/mozilla/gecko', 'mobile/android/tests/background/junit3/src') srcdir('app/src/browser/org/mozilla/gecko', 'mobile/android/tests/browser/junit3/src') # Test libraries. srcdir('app/libs', 'build/mobile/robocop') srcdir('base/build.gradle', 'mobile/android/gradle/base/build.gradle') + srcdir('base/lint.xml', 'mobile/android/gradle/base/lint.xml') srcdir('base/src/main/AndroidManifest.xml', 'mobile/android/gradle/base/AndroidManifest.xml') srcdir('base/src/main/java/org/mozilla/gecko', 'mobile/android/base') srcdir('base/src/main/java/org/mozilla/mozstumbler', 'mobile/android/stumbler/java/org/mozilla/mozstumbler') srcdir('base/src/main/java/org/mozilla/search', 'mobile/android/search/java/org/mozilla/search') srcdir('base/src/main/res', 'mobile/android/base/resources') srcdir('base/src/crashreporter/res', 'mobile/android/base/crashreporter/res') manifest_path = os.path.join(self.topobjdir, 'mobile', 'android', 'gradle.manifest')
--- a/mobile/android/tests/browser/robocop/BaseRobocopTest.java +++ b/mobile/android/tests/browser/robocop/BaseRobocopTest.java @@ -1,49 +1,57 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.tests; -import java.util.Map; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.test.ActivityInstrumentationTestCase2; +import android.text.TextUtils; +import android.util.Log; + +import com.jayway.android.robotium.solo.Solo; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.mozilla.gecko.Actions; import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.Assert; +import org.mozilla.gecko.BrowserApp; import org.mozilla.gecko.Driver; import org.mozilla.gecko.FennecInstrumentationTestRunner; import org.mozilla.gecko.FennecMochitestAssert; import org.mozilla.gecko.FennecNativeActions; import org.mozilla.gecko.FennecNativeDriver; import org.mozilla.gecko.FennecTalosAssert; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.updater.UpdateServiceHelper; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.PowerManager; -import android.test.ActivityInstrumentationTestCase2; -import android.util.Log; - -import com.jayway.android.robotium.solo.Solo; +import java.util.Map; @SuppressWarnings("unchecked") public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<Activity> { + public static final String LOGTAG = "BaseTest"; + public enum Type { MOCHITEST, TALOS } - private static final String DEFAULT_ROOT_PATH = "/mnt/sdcard/tests"; + public static final String DEFAULT_ROOT_PATH = "/mnt/sdcard/tests"; + + // How long to wait for a Robocop:Quit message to actually kill Fennec. + private static final int ROBOCOP_QUIT_WAIT_MS = 180000; /** * The Java Class instance that launches the browser. * <p> * This should always agree with {@link AppConstants#MOZ_ANDROID_BROWSER_INTENT_CLASS}. */ public static final Class<? extends Activity> BROWSER_INTENT_CLASS; @@ -71,18 +79,16 @@ public abstract class BaseRobocopTest ex protected Solo mSolo; protected Driver mDriver; protected Actions mActions; protected String mProfile; protected StringHelper mStringHelper; - protected abstract Intent createActivityIntent(); - /** * The browser is started at the beginning of this test. A single test is a * class inheriting from <code>BaseRobocopTest</code> that contains test * methods. * <p> * If a test should not start the browser at the beginning of a test, * specify a different activity class to the one-argument constructor. To do * as little as possible, specify <code>Activity.class</code>. @@ -107,16 +113,40 @@ public abstract class BaseRobocopTest ex * <p> * By default tests are mochitests, but a test can override this method in * order to change its type. Most Robocop tests are mochitests. */ protected Type getTestType() { return Type.MOCHITEST; } + // Member function to allow specialization. + protected Intent createActivityIntent() { + return BaseRobocopTest.createActivityIntent(mConfig); + } + + // Static function to allow re-use. + public static Intent createActivityIntent(Map<String, String> config) { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.putExtra("args", "-no-remote -profile " + config.get("profile")); + // Don't show the first run experience. + intent.putExtra(BrowserApp.EXTRA_SKIP_STARTPANE, true); + + final String envString = config.get("envvars"); + if (!TextUtils.isEmpty(envString)) { + final String[] envStrings = envString.split(","); + + for (int iter = 0; iter < envStrings.length; iter++) { + intent.putExtra("env" + iter, envStrings[iter]); + } + } + + return intent; + } + @Override protected void setUp() throws Exception { // Disable the updater. UpdateServiceHelper.setEnabled(false); // Load config file from root path (set up by Python script). mRootPath = FennecInstrumentationTestRunner.getFennecArguments().getString("deviceroot"); if (mRootPath == null) { @@ -147,17 +177,53 @@ public abstract class BaseRobocopTest ex Activity tempActivity = getActivity(); StringHelper.initialize(tempActivity.getResources()); mStringHelper = StringHelper.get(); mSolo = new Solo(getInstrumentation(), tempActivity); mDriver = new FennecNativeDriver(tempActivity, mSolo, mRootPath); mActions = new FennecNativeActions(tempActivity, mSolo, getInstrumentation(), mAsserter); + } + @Override + public void tearDown() throws Exception { + try { + mAsserter.endTest(); + + // By default, we don't quit Fennec on finish, and we don't finish + // all opened activities. Not quiting Fennec entirely is intended to + // make life better for local testers, who might want to alter a + // test that is under development rather than Fennec itself. Not + // finishing activities is intended to allow local testers to + // manually inspect an activity's state after a test + // run. runtestsremote.py sets this to "1". Testers running via an + // IDE will not have this set at all. + final String quitAndFinish = FennecInstrumentationTestRunner.getFennecArguments() + .getString("quit_and_finish"); // null means not specified. + if ("1".equals(quitAndFinish)) { + // Request the browser force quit and wait for it to take effect. + Log.i(LOGTAG, "Requesting force quit."); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Robocop:Quit", null)); + mSolo.sleep(ROBOCOP_QUIT_WAIT_MS); + + // If still running, finish activities as recommended by Robotium. + Log.i(LOGTAG, "Finishing all opened activities."); + mSolo.finishOpenedActivities(); + } else { + // This has the effect of keeping the activity-under-test + // around; if we don't set it to null, it is killed, either by + // finishOpenedActivities above or super.tearDown below. + Log.i(LOGTAG, "Not requesting force quit and trying to keep started activity alive."); + setActivity(null); + } + } catch (Throwable e) { + e.printStackTrace(); + } + super.tearDown(); } /** * Function to early abort if we can't reach the given HTTP server. Provides local testers * with diagnostic information. Not currently available for TALOS tests, which are rarely run * locally in any case. */ public void throwIfHttpGetFails() {
--- a/mobile/android/tests/browser/robocop/BaseTest.java +++ b/mobile/android/tests/browser/robocop/BaseTest.java @@ -139,48 +139,16 @@ abstract class BaseTest extends BaseRobo mAsserter.dumpLog("Exception caught during test!", t); mAsserter.ok(false, "Exception caught", t.toString()); } // re-throw to continue bail-out throw t; } } - @Override - public void tearDown() throws Exception { - try { - mAsserter.endTest(); - // request a force quit of the browser and wait for it to take effect - GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Robocop:Quit", null)); - mSolo.sleep(120000); - // if still running, finish activities as recommended by Robotium - mSolo.finishOpenedActivities(); - } catch (Throwable e) { - e.printStackTrace(); - } - super.tearDown(); - } - - @Override - protected Intent createActivityIntent() { - final Intent intent = new Intent(Intent.ACTION_MAIN); - intent.putExtra("args", "-no-remote -profile " + mProfile); - - final String envString = mConfig.get("envvars"); - if (!TextUtils.isEmpty(envString)) { - final String[] envStrings = envString.split(","); - - for (int iter = 0; iter < envStrings.length; iter++) { - intent.putExtra("env" + iter, envStrings[iter]); - } - } - - return intent; - } - public void assertMatches(String value, String regex, String name) { if (value == null) { mAsserter.ok(false, name, "Expected /" + regex + "/, got null"); return; } mAsserter.ok(value.matches(regex), name, "Expected /" + regex +"/, got \"" + value + "\""); }
--- a/mobile/android/tests/browser/robocop/UITest.java +++ b/mobile/android/tests/browser/robocop/UITest.java @@ -54,32 +54,16 @@ abstract class UITest extends BaseRoboco initHelpers(); // Ensure Robocop tests have access to network, and are run with Display powered on. throwIfHttpGetFails(); throwIfScreenNotOn(); } @Override - public void tearDown() throws Exception { - try { - mAsserter.endTest(); - // request a force quit of the browser and wait for it to take effect - GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Robocop:Quit", null)); - mSolo.sleep(120000); - // if still running, finish activities as recommended by Robotium - mSolo.finishOpenedActivities(); - } catch (Throwable e) { - e.printStackTrace(); - } - - super.tearDown(); - } - - @Override protected void runTest() throws Throwable { try { super.runTest(); } catch (Throwable t) { // save screenshot -- written to /mnt/sdcard/Robotium-Screenshots // as <filename>.jpg mSolo.takeScreenshot("robocop-screenshot"); if (mAsserter != null) { @@ -177,36 +161,16 @@ abstract class UITest extends BaseRoboco public String getAbsoluteIpUrl(final String url) { return getAbsoluteUrl(mBaseIpUrl, url); } private String getAbsoluteUrl(final String baseUrl, final String url) { return baseUrl + "/" + url.replaceAll("(^/)", ""); } - @Override - protected Intent createActivityIntent() { - final Intent intent = new Intent(Intent.ACTION_MAIN); - - // Don't show the first run experience. - intent.putExtra(BrowserApp.EXTRA_SKIP_STARTPANE, true); - intent.putExtra("args", "-no-remote -profile " + mProfile); - - final String envString = mConfig.get("envvars"); - if (!TextUtils.isEmpty(envString)) { - final String[] envStrings = envString.split(","); - - for (int iter = 0; iter < envStrings.length; iter++) { - intent.putExtra("env" + iter, envStrings[iter]); - } - } - - return intent; - } - /** * Throws an Exception. Called from overridden JUnit methods to ensure JUnit assertions * are not accidentally used over AssertionHelper assertions (the latter of which contains * additional logging facilities for use in our test harnesses). */ private static void junit() { throw new UnsupportedOperationException(JUNIT_FAILURE_MSG); }
--- a/testing/mochitest/mach_commands.py +++ b/testing/mochitest/mach_commands.py @@ -575,17 +575,23 @@ class RobocopCommands(MachCommandBase): @Command('robocop', category='testing', conditions=[conditions.is_android], description='Run a Robocop test.', parser=setup_argument_parser) @CommandArgument('test_paths', nargs='*', metavar='TEST', default=None, help='Test to run. Can be a single Robocop test file (like "testLoad.java") ' ' or a directory of tests ' '(to run recursively). If omitted, the entire Robocop suite is run.') - def run_robocop(self, test_paths, **kwargs): + @CommandArgument('--serve', default=False, action='store_true', + help='Run no tests but start the mochi.test web server and launch ' + 'Fennec with a test profile.') + def run_robocop(self, test_paths, serve=False, **kwargs): + if serve: + kwargs['autorun'] = False + if not kwargs.get('robocopIni'): kwargs['robocopIni'] = os.path.join(self.topobjdir, '_tests', 'testing', 'mochitest', 'robocop.ini') if not kwargs.get('robocopApk'): kwargs['robocopApk'] = os.path.join(self.topobjdir, 'build', 'mobile', 'robocop', 'robocop-debug.apk')
--- a/testing/mochitest/runtestsremote.py +++ b/testing/mochitest/runtestsremote.py @@ -538,16 +538,21 @@ def run_test_harness(options): options.extraPrefs.append('layout.css.devPixelsPerPx=1.0') options.extraPrefs.append('browser.chrome.dynamictoolbar=false') options.extraPrefs.append('browser.snippets.enabled=false') options.extraPrefs.append('browser.casting.enabled=true') if (options.dm_trans == 'adb' and options.robocopApk): dm._checkCmd(["install", "-r", options.robocopApk]) + if not options.autorun: + # Force a single loop iteration. The iteration will start Fennec and + # the httpd server, but not actually run a test. + options.testPath = robocop_tests[0]['name'] + retVal = None # Filtering tests active_tests = [] for test in robocop_tests: if options.testPath and options.testPath != test['name']: continue if 'disabled' in test: @@ -565,30 +570,46 @@ def run_test_harness(options): # each cycle if mochitest.localProfile: options.profilePath = mochitest.localProfile os.system("rm -Rf %s" % options.profilePath) options.profilePath = None mochitest.localProfile = options.profilePath options.app = "am" - options.browserArgs = [ - "instrument", - "-w", - "-e", - "deviceroot", - deviceRoot, - "-e", - "class"] - options.browserArgs.append( - "org.mozilla.gecko.tests.%s" % - test['name'].split('.java')[0]) - options.browserArgs.append( - "org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner") mochitest.nsprLogName = "nspr-%s.log" % test['name'] + if options.autorun: + # This launches a test (using "am instrument") and instructs + # Fennec to /quit/ the browser (using Robocop:Quit) and to + # /finish/ all opened activities. + options.browserArgs = [ + "instrument", + "-w", + "-e", "quit_and_finish", "1", + "-e", "deviceroot", deviceRoot, + "-e", + "class"] + options.browserArgs.append( + "org.mozilla.gecko.tests.%s" % + test['name'].split('.java')[0]) + options.browserArgs.append( + "org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner") + else: + # This does not launch a test at all. It launches an activity + # that starts Fennec and then waits indefinitely, since cat + # never returns. + options.browserArgs = ["start", + "-n", "org.mozilla.roboexample.test/org.mozilla.gecko.LaunchFennecWithConfigurationActivity", + "&&", "cat"] + dm.default_timeout = sys.maxint # Forever. + + mochitest.log.info("") + mochitest.log.info("Serving mochi.test Robocop root at http://%s:%s/tests/robocop/" % + (options.remoteWebServer, options.httpPort)) + mochitest.log.info("") # If the test is for checking the import from bookmarks then make # sure there is data to import if test['name'] == "testImportFromAndroid": # Get the OS so we can run the insert in the apropriate # database and following the correct table schema osInfo = dm.getInfo("os")
--- a/toolkit/components/places/PlacesUtils.jsm +++ b/toolkit/components/places/PlacesUtils.jsm @@ -2753,46 +2753,53 @@ PlacesCreateFolderTransaction.prototype * array of annotations to set for the new bookmark * @param [optional] aChildTransactions * child transactions to commit after creating the bookmark. Prefer * using any of the arguments above if possible. In general, a child * transations should be used only if the change it does has to be * reverted manually when removing the bookmark item. * a child transaction must support setting its bookmark-item * identifier via an "id" js setter. + * @param [optional] aPostData + * keyword's POST data, if available. * * @return nsITransaction object */ this.PlacesCreateBookmarkTransaction = function PlacesCreateBookmarkTransaction(aURI, aParentId, aIndex, aTitle, aKeyword, aAnnotations, - aChildTransactions) + aChildTransactions, aPostData) { this.item = new TransactionItemCache(); this.item.uri = aURI; this.item.parentId = aParentId; this.item.index = aIndex; this.item.title = aTitle; this.item.keyword = aKeyword; + this.item.postData = aPostData; this.item.annotations = aAnnotations; this.childTransactions = aChildTransactions; } PlacesCreateBookmarkTransaction.prototype = { __proto__: BaseTransaction.prototype, doTransaction: function CITXN_doTransaction() { this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId, this.item.uri, this.item.index, this.item.title); if (this.item.keyword) { PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, this.item.keyword); + if (this.item.postData) { + PlacesUtils.setPostDataForBookmark(this.item.id, + this.item.postData); + } } if (this.item.annotations && this.item.annotations.length > 0) PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations); if (this.childTransactions && this.childTransactions.length > 0) { // Set the new item id into child transactions. for (let i = 0; i < this.childTransactions.length; ++i) { this.childTransactions[i].item.id = this.item.id; @@ -3069,16 +3076,18 @@ this.PlacesRemoveItemTransaction = // Remove this folder itself. let txn = PlacesUtils.bookmarks.getRemoveFolderTransaction(this.item.id); this.childTransactions.push(txn); } else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) { this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id); this.item.keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this.item.id); + if (this.item.keyword) + this.item.postData = PlacesUtils.getPostDataForBookmark(this.item.id); } if (this.item.itemType != Ci.nsINavBookmarksService.TYPE_SEPARATOR) this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id); this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id); this.item.annotations = PlacesUtils.getAnnotationsForItem(this.item.id); this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id); @@ -3120,16 +3129,19 @@ PlacesRemoveItemTransaction.prototype = this.item.uri, this.item.index, this.item.title); if (this.item.tags && this.item.tags.length > 0) PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags); if (this.item.keyword) { PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, this.item.keyword); + if (this.item.postData) { + PlacesUtils.bookmarks.setPostDataForBookmark(this.item.id); + } } } else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) { let txn = new PlacesAggregatedTransaction("Remove item childTxn", this.childTransactions); txn.undoTransaction(); } else { // TYPE_SEPARATOR @@ -3368,40 +3380,52 @@ PlacesSetPageAnnotationTransaction.proto /** * Transaction for editing a bookmark's keyword. * * @param aItemId * id of the bookmark to edit * @param aNewKeyword * new keyword for the bookmark + * @param aNewPostData [optional] + * new keyword's POST data, if available * * @return nsITransaction object */ this.PlacesEditBookmarkKeywordTransaction = - function PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword) + function PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword, aNewPostData) { this.item = new TransactionItemCache(); this.item.id = aItemId; this.new = new TransactionItemCache(); this.new.keyword = aNewKeyword; + this.new.postData = aNewPostData } PlacesEditBookmarkKeywordTransaction.prototype = { __proto__: BaseTransaction.prototype, doTransaction: function EBKTXN_doTransaction() { + // Store the current values. this.item.keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this.item.id); + if (this.item.keyword) + this.item.postData = PlacesUtils.getPostDataForBookmark(this.item.id); + + // Update the keyword. PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, this.new.keyword); + if (this.new.keyword && this.new.postData) + PlacesUtils.setPostDataForBookmark(this.item.id, this.new.postData); }, undoTransaction: function EBKTXN_undoTransaction() { PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, this.item.keyword); + if (this.item.postData) + PlacesUtils.setPostDataForBookmark(this.item.id, this.item.postData); } }; /** * Transaction for editing the post data associated with a bookmark. * * @param aItemId
--- a/toolkit/components/places/UnifiedComplete.js +++ b/toolkit/components/places/UnifiedComplete.js @@ -153,17 +153,22 @@ const SQL_ADAPTIVE_QUERY = :matchBehavior, :searchBehavior) ORDER BY rank DESC, h.frecency DESC`; function hostQuery(conditions = "") { let query = `/* do not warn (bug NA): not worth to index on (typed, frecency) */ SELECT :query_type, host || '/', IFNULL(prefix, '') || host || '/', - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, frecency + ( SELECT f.url FROM moz_favicons f + JOIN moz_places h ON h.favicon_id = f.id + WHERE rev_host = get_unreversed_host(host || '.') || '.' + OR rev_host = get_unreversed_host(host || '.') || '.www.' + ) AS favicon_url, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, frecency FROM moz_hosts WHERE host BETWEEN :searchString AND :searchString || X'FFFF' AND frecency <> 0 ${conditions} ORDER BY frecency DESC LIMIT 1`; return query; } @@ -171,18 +176,22 @@ function hostQuery(conditions = "") { const SQL_HOST_QUERY = hostQuery(); const SQL_TYPED_HOST_QUERY = hostQuery("AND typed = 1"); function bookmarkedHostQuery(conditions = "") { let query = `/* do not warn (bug NA): not worth to index on (typed, frecency) */ SELECT :query_type, host || '/', IFNULL(prefix, '') || host || '/', - NULL, ( - SELECT foreign_count > 0 FROM moz_places + ( SELECT f.url FROM moz_favicons f + JOIN moz_places h ON h.favicon_id = f.id + WHERE rev_host = get_unreversed_host(host || '.') || '.' + OR rev_host = get_unreversed_host(host || '.') || '.www.' + ) AS favicon_url, + ( SELECT foreign_count > 0 FROM moz_places WHERE rev_host = get_unreversed_host(host || '.') || '.' OR rev_host = get_unreversed_host(host || '.') || '.www.' ) AS bookmarked, NULL, NULL, NULL, NULL, NULL, NULL, frecency FROM moz_hosts WHERE host BETWEEN :searchString AND :searchString || X'FFFF' AND bookmarked AND frecency <> 0 ${conditions} @@ -193,19 +202,21 @@ function bookmarkedHostQuery(conditions const SQL_BOOKMARKED_HOST_QUERY = bookmarkedHostQuery(); const SQL_BOOKMARKED_TYPED_HOST_QUERY = bookmarkedHostQuery("AND typed = 1"); function urlQuery(conditions = "") { let query = `/* do not warn (bug no): cannot use an index */ - SELECT :query_type, h.url, NULL, - NULL, foreign_count > 0 AS bookmarked, NULL, NULL, NULL, NULL, NULL, NULL, h.frecency + SELECT :query_type, h.url, NULL, f.url AS favicon_url, + foreign_count > 0 AS bookmarked, + NULL, NULL, NULL, NULL, NULL, NULL, h.frecency FROM moz_places h + LEFT JOIN moz_favicons f ON h.favicon_id = f.id WHERE h.frecency <> 0 ${conditions} AND AUTOCOMPLETE_MATCH(:searchString, h.url, h.title, '', h.visit_count, h.typed, bookmarked, 0, :matchBehavior, :searchBehavior) ORDER BY h.frecency DESC, h.id DESC LIMIT 1`; @@ -1171,49 +1182,43 @@ Search.prototype = { } }, _processHostRow: function (row) { let match = {}; let trimmedHost = row.getResultByIndex(QUERYINDEX_URL); let untrimmedHost = row.getResultByIndex(QUERYINDEX_TITLE); let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY); + let faviconUrl = row.getResultByIndex(QUERYINDEX_ICONURL); // If the untrimmed value doesn't preserve the user's input just // ignore it and complete to the found host. if (untrimmedHost && !untrimmedHost.toLowerCase().includes(this._trimmedOriginalSearchString.toLowerCase())) { untrimmedHost = null; } match.value = this._strippedPrefix + trimmedHost; // Remove the trailing slash. match.comment = stripHttpAndTrim(trimmedHost); match.finalCompleteValue = untrimmedHost; - - try { - let iconURI = NetUtil.newURI(untrimmedHost); - iconURI.path = "/favicon.ico"; - match.icon = PlacesUtils.favicons.getFaviconLinkForIcon(iconURI).spec; - } catch (e) { - // This can fail, which is ok. - } - + match.icon = faviconUrl; // Although this has a frecency, this query is executed before any other // queries that would result in frecency matches. match.frecency = frecency; match.style = "autofill"; return match; }, _processUrlRow: function (row) { let match = {}; let value = row.getResultByIndex(QUERYINDEX_URL); let url = fixupSearchText(value); let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY); + let faviconUrl = row.getResultByIndex(QUERYINDEX_ICONURL); let prefix = value.slice(0, value.length - stripPrefix(value).length); // We must complete the URL up to the next separator (which is /, ? or #). let separatorIndex = url.slice(this._searchString.length) .search(/[\/\?\#]/); if (separatorIndex != -1) { separatorIndex += this._searchString.length; @@ -1229,16 +1234,17 @@ Search.prototype = { if (untrimmedURL && !untrimmedURL.toLowerCase().includes(this._trimmedOriginalSearchString.toLowerCase())) { untrimmedURL = null; } match.value = this._strippedPrefix + url; match.comment = url; match.finalCompleteValue = untrimmedURL; + match.icon = faviconUrl; // Although this has a frecency, this query is executed before any other // queries that would result in frecency matches. match.frecency = frecency; match.style = "autofill"; return match; }, _processRow: function (row) {
--- a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js +++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js @@ -94,39 +94,43 @@ AutoCompleteInput.prototype = { onTextEntered: function() false, onTextReverted: function() false, QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteInput]) } // A helper for check_autocomplete to check a specific match against data from // the controller. -function _check_autocomplete_matches(match, controllerInfo) { +function _check_autocomplete_matches(match, result) { let { uri, title, tags, searchEngine, style } = match; - let { controllerValue, controllerComment, controllerStyle } = controllerInfo; if (tags) title += " \u2013 " + tags.sort().join(", "); if (searchEngine) title += TITLE_SEARCH_ENGINE_SEPARATOR + searchEngine; if (style) style = style.sort(); else style = ["favicon"]; do_print("Checking against expected '" + uri.spec + "', '" + title + "'..."); // Got a match on both uri and title? - if (stripPrefix(uri.spec) != stripPrefix(controllerValue) || title != controllerComment) { + if (stripPrefix(uri.spec) != stripPrefix(result.value) || title != result.comment) { return false; } - let actualStyle = controllerStyle.split(/\s+/).sort(); + + let actualStyle = result.style.split(/\s+/).sort(); if (style) Assert.equal(actualStyle.toString(), style.toString(), "Match should have expected style"); if (uri.spec.startsWith("moz-action:")) { Assert.ok(actualStyle.indexOf("action") != -1, "moz-action results should always have 'action' in their style"); } + + if (match.icon) + Assert.equal(result.image, match.icon, "Match should have expected image"); + return true; } function* check_autocomplete(test) { // At this point frecency could still be updating due to latest pages // updates. // This is not a problem in real life, but autocomplete tests should // return reliable resultsets, thus we have to wait. @@ -179,47 +183,51 @@ function* check_autocomplete(test) { if (test.matches) { // Do not modify the test original matches. let matches = test.matches.slice(); let firstIndexToCheck = 0; if (test.searchParam && test.searchParam == "enable-actions") { firstIndexToCheck = 1; do_print("Checking first match is first autocomplete entry") - let controllerValue = controller.getValueAt(0); - let controllerComment = controller.getCommentAt(0); - let controllerStyle = controller.getStyleAt(0); - do_print("First match is '" + controllerValue + "', '" + controllerComment + ""); - let controllerInfo = { controllerValue, controllerComment, controllerStyle }; - Assert.ok(_check_autocomplete_matches(matches[0], controllerInfo), "first item is correct"); + let result = { + value: controller.getValueAt(0), + comment: controller.getCommentAt(0), + style: controller.getStyleAt(0), + image: controller.getImageAt(0), + } + do_print(`First match is "${result.value}", " ${result.comment}"`); + Assert.ok(_check_autocomplete_matches(matches[0], result), "first item is correct"); do_print("Checking rest of the matches"); } for (let i = firstIndexToCheck; i < controller.matchCount; i++) { - let controllerValue = controller.getValueAt(i); - let controllerComment = controller.getCommentAt(i); - let controllerStyle = controller.getStyleAt(i); - let controllerInfo = { controllerValue, controllerComment, controllerStyle }; - do_print("Looking for '" + controllerValue + "', '" + controllerComment + "' in expected results..."); + let result = { + value: controller.getValueAt(i), + comment: controller.getCommentAt(i), + style: controller.getStyleAt(i), + image: controller.getImageAt(i), + } + do_print(`Looking for "${result.value}", "${result.comment}" in expected results...`); let j; for (j = firstIndexToCheck; j < matches.length; j++) { // Skip processed expected results if (matches[j] == undefined) continue; - if (_check_autocomplete_matches(matches[j], controllerInfo)) { + if (_check_autocomplete_matches(matches[j], result)) { do_print("Got a match at index " + j + "!"); // Make it undefined so we don't process it again matches[j] = undefined; break; } } // We didn't hit the break, so we must have not found it if (j == matches.length) - do_throw("Didn't find the current result ('" + controllerValue + "', '" + controllerComment + "') in matches"); + do_throw(`Didn't find the current result ("${result.value}", "${result.comment}") in matches`); } Assert.equal(controller.matchCount, matches.length, "Got as many results as expected"); // If we expect results, make sure we got matches. do_check_eq(controller.searchStatus, matches.length ? Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH : @@ -368,16 +376,28 @@ function makeVisitMatch(input, url, extr function makeSwitchToTabMatch(url, extra = {}) { return { uri: makeActionURI("switchtab", {url}), title: extra.title || url, style: [ "action", "switchtab" ], } } +function setFaviconForHref(href, iconHref) { + return new Promise(resolve => { + PlacesUtils.favicons.setAndFetchFaviconForPage( + NetUtil.newURI(href), + NetUtil.newURI(iconHref), + true, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + resolve + ); + }); +} + // Ensure we have a default search engine and the keyword.enabled preference // set. add_task(function ensure_search_engine() { // keyword.enabled is necessary for the tests to see keyword searches. Services.prefs.setBoolPref("keyword.enabled", true); // Remove any existing engines before adding ours. for (let engine of Services.search.getEngines()) {
--- a/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js @@ -17,33 +17,37 @@ add_task(function* test_default_behavior { uri: uri1, title: "typed", transition: TRANSITION_TYPED }, { uri: uri2, title: "visited" }, { uri: uri4, title: "tpbk", transition: TRANSITION_TYPED }, ]); yield addBookmark( { uri: uri3, title: "bookmarked" } ); yield addBookmark( { uri: uri4, title: "tpbk" } ); yield addBookmark( { uri: uri5, title: "title", tags: ["foo"] } ); + yield setFaviconForHref(uri1.spec, "chrome://global/skin/icons/information-16.png"); + yield setFaviconForHref(uri3.spec, "chrome://global/skin/icons/error-16.png"); + // RESTRICT TO HISTORY. Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", false); Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false); do_print("Restrict history, common visit, should not autoFill"); yield check_autocomplete({ search: "vi", matches: [ { uri: uri2, title: "visited" } ], autofilled: "vi", completed: "vi" }); do_print("Restrict history, typed visit, should autoFill"); yield check_autocomplete({ search: "ty", - matches: [ { uri: uri1, title: "typed", style: [ "autofill" ] } ], + matches: [ { uri: uri1, title: "typed", style: [ "autofill" ], + icon: "chrome://global/skin/icons/information-16.png" } ], autofilled: "typed/", completed: "typed/" }); // Don't autoFill this one cause it's not typed. do_print("Restrict history, bookmark, should not autoFill"); yield check_autocomplete({ search: "bo", @@ -64,17 +68,18 @@ add_task(function* test_default_behavior Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); // We are not restricting on typed, so we autoFill the bookmark even if we // are restricted to history. We accept that cause not doing that // would be a perf hit and the privacy implications are very weak. do_print("Restrict history, bookmark, autoFill.typed = false, should autoFill"); yield check_autocomplete({ search: "bo", - matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ], style: [ "autofill" ] } ], + matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ], style: [ "autofill" ], + icon: "chrome://global/skin/icons/error-16.png" } ], autofilled: "bookmarked/", completed: "bookmarked/" }); do_print("Restrict history, common visit, autoFill.typed = false, should autoFill"); yield check_autocomplete({ search: "vi", matches: [ { uri: uri2, title: "visited", style: [ "autofill" ] } ], @@ -93,17 +98,18 @@ add_task(function* test_default_behavior matches: [ ], autofilled: "vi", completed: "vi" }); do_print("Restrict typed, typed visit, autofill.typed = false, should autoFill"); yield check_autocomplete({ search: "ty", - matches: [ { uri: uri1, title: "typed", style: [ "autofill" ] } ], + matches: [ { uri: uri1, title: "typed", style: [ "autofill" ], + icon: "chrome://global/skin/icons/information-16.png"} ], autofilled: "typed/", completed: "typed/" }); do_print("Restrict typed, bookmark, autofill.typed = false, should not autoFill"); yield check_autocomplete({ search: "bo", matches: [ ], @@ -139,17 +145,18 @@ add_task(function* test_default_behavior autofilled: "ty", completed: "ty" }); // Don't autoFill this one cause it's not typed. do_print("Restrict bookmarks, bookmark, should not autoFill"); yield check_autocomplete({ search: "bo", - matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ] } ], + matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ], + icon: "chrome://global/skin/icons/error-16.png"} ], autofilled: "bo", completed: "bo" }); // Note we don't show this one cause it's not typed. do_print("Restrict bookmarks, typed bookmark, should autoFill"); yield check_autocomplete({ search: "tp", @@ -158,17 +165,18 @@ add_task(function* test_default_behavior completed: "tpbk/" }); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); do_print("Restrict bookmarks, bookmark, autofill.typed = false, should autoFill"); yield check_autocomplete({ search: "bo", - matches: [ { uri: uri3, title: "bookmarked", style: [ "autofill" ] } ], + matches: [ { uri: uri3, title: "bookmarked", style: [ "autofill" ], + icon: "chrome://global/skin/icons/error-16.png" } ], autofilled: "bookmarked/", completed: "bookmarked/" }); // Don't autofill because it's a title. do_print("Restrict bookmarks, title, autofill.typed = false, should not autoFill"); yield check_autocomplete({ search: "# ta", @@ -198,16 +206,19 @@ add_task(function* test_default_behavior yield PlacesTestUtils.addVisits([ { uri: uri1, title: "typed", transition: TRANSITION_TYPED }, { uri: uri2, title: "visited" }, { uri: uri4, title: "tpbk", transition: TRANSITION_TYPED }, ]); yield addBookmark( { uri: uri3, title: "bookmarked" } ); yield addBookmark( { uri: uri4, title: "tpbk" } ); + yield setFaviconForHref(uri1.spec, "chrome://global/skin/icons/information-16.png"); + yield setFaviconForHref(uri3.spec, "chrome://global/skin/icons/error-16.png"); + // RESTRICT TO HISTORY. Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", false); Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", false); do_print("URL: Restrict history, common visit, should not autoFill"); @@ -216,17 +227,18 @@ add_task(function* test_default_behavior matches: [ { uri: uri2, title: "visited" } ], autofilled: "visited/v", completed: "visited/v" }); do_print("URL: Restrict history, typed visit, should autoFill"); yield check_autocomplete({ search: "typed/t", - matches: [ { uri: uri1, title: "typed/ty/", style: [ "autofill" ] } ], + matches: [ { uri: uri1, title: "typed/ty/", style: [ "autofill" ], + icon: "chrome://global/skin/icons/information-16.png"} ], autofilled: "typed/ty/", completed: "http://typed/ty/" }); // Don't autoFill this one cause it's not typed. do_print("URL: Restrict history, bookmark, should not autoFill"); yield check_autocomplete({ search: "bookmarked/b", @@ -263,17 +275,18 @@ add_task(function* test_default_behavior autofilled: "typed/t", completed: "typed/t" }); // Don't autoFill this one cause it's not typed. do_print("URL: Restrict bookmarks, bookmark, should not autoFill"); yield check_autocomplete({ search: "bookmarked/b", - matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ] } ], + matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ], + icon: "chrome://global/skin/icons/error-16.png" } ], autofilled: "bookmarked/b", completed: "bookmarked/b" }); // Note we don't show this one cause it's not typed. do_print("URL: Restrict bookmarks, typed bookmark, should autoFill"); yield check_autocomplete({ search: "tpbk/t", @@ -282,15 +295,16 @@ add_task(function* test_default_behavior completed: "http://tpbk/tp/" }); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); do_print("URL: Restrict bookmarks, bookmark, autofill.typed = false, should autoFill"); yield check_autocomplete({ search: "bookmarked/b", - matches: [ { uri: uri3, title: "bookmarked/bo/", style: [ "autofill" ] } ], + matches: [ { uri: uri3, title: "bookmarked/bo/", style: [ "autofill" ], + icon: "chrome://global/skin/icons/error-16.png" } ], autofilled: "bookmarked/bo/", completed: "http://bookmarked/bo/" }); yield cleanup(); });
--- a/toolkit/components/places/tests/unit/test_placesTxn.js +++ b/toolkit/components/places/tests/unit/test_placesTxn.js @@ -535,27 +535,29 @@ add_task(function* test_edit_description }); add_task(function* test_edit_keyword() { const KEYWORD = "keyword-test_edit_keyword"; let testURI = NetUtil.newURI("http://test_edit_keyword.com"); let testBkmId = bmsvc.insertBookmark(root, testURI, bmsvc.DEFAULT_INDEX, "Test edit keyword"); - let txn = new PlacesEditBookmarkKeywordTransaction(testBkmId, KEYWORD); + let txn = new PlacesEditBookmarkKeywordTransaction(testBkmId, KEYWORD, "postData"); txn.doTransaction(); do_check_eq(observer._itemChangedId, testBkmId); do_check_eq(observer._itemChangedProperty, "keyword"); do_check_eq(observer._itemChangedValue, KEYWORD); + do_check_eq(PlacesUtils.getPostDataForBookmark(testBkmId), "postData"); txn.undoTransaction(); do_check_eq(observer._itemChangedId, testBkmId); do_check_eq(observer._itemChangedProperty, "keyword"); do_check_eq(observer._itemChangedValue, ""); + do_check_eq(PlacesUtils.getPostDataForBookmark(testBkmId), null); }); add_task(function* test_LoadInSidebar_transaction() { let testURI = NetUtil.newURI("http://test_LoadInSidebar_transaction.com"); let testBkmId = bmsvc.insertBookmark(root, testURI, bmsvc.DEFAULT_INDEX, "Test LoadInSidebar transaction"); const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar"; let anno = { name: LOAD_IN_SIDEBAR_ANNO, @@ -695,44 +697,16 @@ add_task(function* test_sort_folder_by_n do_check_eq(0, bmsvc.getItemIndex(b3)); txn.undoTransaction(); do_check_eq(0, bmsvc.getItemIndex(b1)); do_check_eq(1, bmsvc.getItemIndex(b2)); do_check_eq(2, bmsvc.getItemIndex(b3)); }); -add_task(function* test_edit_postData() { - let postData = "post-test_edit_postData"; - let testURI = NetUtil.newURI("http://test_edit_postData.com"); - - let testBkm = yield PlacesUtils.bookmarks.insert({ - parentGuid: PlacesUtils.bookmarks.menuGuid, - url: "http://test_edit_postData.com", - title: "Test edit Post Data" - }); - - yield PlacesUtils.keywords.insert({ - keyword: "kw", - url: "http://test_edit_postData.com" - }); - - let testBkmId = yield PlacesUtils.promiseItemId(testBkm.guid); - let txn = new PlacesEditBookmarkPostDataTransaction(testBkmId, postData); - - txn.doTransaction(); - yield promiseKeyword("kw", testURI.spec, postData); - - txn.undoTransaction(); - entry = yield PlacesUtils.keywords.fetch("kw"); - Assert.equal(entry.url.href, testURI.spec); - // We don't allow anymore to set a null post data. - //Assert.equal(null, post_data); -}); - add_task(function* test_tagURI_untagURI() { const TAG_1 = "tag-test_tagURI_untagURI-bar"; const TAG_2 = "tag-test_tagURI_untagURI-foo"; let tagURI = NetUtil.newURI("http://test_tagURI_untagURI.com"); // Test tagURI let tagTxn = new PlacesTagURITransaction(tagURI, [TAG_1, TAG_2]);
--- a/toolkit/mozapps/update/tests/chrome/chrome.ini +++ b/toolkit/mozapps/update/tests/chrome/chrome.ini @@ -13,18 +13,18 @@ support-files = ; order and not in the order specified in the manifest. [test_0011_check_basic.xul] [test_0012_check_basic_license.xul] [test_0013_check_incompat_basic.xul] [test_0014_check_incompat_basic_license.xul] [test_0015_check_incompat_basic_addons.xul] [test_0016_check_incompat_basic_license_addons.xul] [test_0017_check_staging_basic.xul] -skip-if = os != 'win' -reason = Bug 918029 and bug 1164560 - timeout caused by copying too many files. +skip-if = asan +reason = Bug 1168003 [test_0021_check_billboard.xul] [test_0022_check_billboard_license.xul] [test_0023_check_incompat_billboard.xul] [test_0024_check_incompat_billboard_license.xul] [test_0025_check_incompat_billboard_addons.xul] [test_0026_check_incompat_billboard_license_addons.xul] [test_0031_available_basic.xul] [test_0032_available_basic_license.xul] @@ -55,21 +55,25 @@ reason = Bug 918029 and bug 1164560 - ti [test_0083_error_patchApplyFailure_partial_complete.xul] [test_0084_error_patchApplyFailure_verify_failed.xul] [test_0091_installed.xul] [test_0092_finishedBackground.xul] [test_0093_restartNotification.xul] [test_0094_restartNotification_remote.xul] [test_0095_restartNotification_remoteInvalidNumber.xul] [test_0096_restartNotification_stagedBackground.xul] +skip-if = asan +reason = Bug 1168003 [test_0097_restartNotification_stagedServiceBackground.xul] +skip-if = os != 'win' +reason = only Windows has the maintenance service. [test_0101_background_restartNotification.xul] [test_0102_background_restartNotification_staging.xul] -skip-if = os == 'linux' -reason = Bug 918029 - timeout caused by copying too many files. +skip-if = asan +reason = Bug 1168003 [test_0103_background_restartNotification_stagingService.xul] skip-if = os != 'win' reason = only Windows has the maintenance service. [test_0104_background_restartNotification_NoIncompatAddons.xul] [test_0105_background_restartNotification_VersionCompatAddons.xul] [test_0111_neverButton_basic.xul] [test_0112_neverButton_billboard.xul] [test_0113_showNeverForVersionRemovedWithPref.xul]
--- a/toolkit/mozapps/update/tests/chrome/test_0017_check_staging_basic.xul +++ b/toolkit/mozapps/update/tests/chrome/test_0017_check_staging_basic.xul @@ -26,16 +26,18 @@ const TESTS = [ { buttonClick: "next" }, { pageid: PAGEID_DOWNLOADING }, { pageid: PAGEID_FINISHED, buttonClick: "extra1" } ]; +gUseTestUpdater = true; + function runTest() { debugDump("entering"); Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true); let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams(); setUpdateURLOverride(url);
--- a/toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul +++ b/toolkit/mozapps/update/tests/chrome/test_0096_restartNotification_stagedBackground.xul @@ -19,16 +19,18 @@ <script type="application/javascript"> <![CDATA[ const TESTS = [ { pageid: PAGEID_FINISHED_BKGRD, buttonClick: "extra1" } ]; +gUseTestUpdater = true; + function runTest() { debugDump("entering"); let patches = getLocalPatchString("complete", null, null, null, null, null, STATE_APPLIED); let updates = getLocalUpdateString(patches, null, null, null, Services.appinfo.version, Services.appinfo.platformVersion);
--- a/toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul +++ b/toolkit/mozapps/update/tests/chrome/test_0097_restartNotification_stagedServiceBackground.xul @@ -19,16 +19,18 @@ <script type="application/javascript"> <![CDATA[ const TESTS = [ { pageid: PAGEID_FINISHED_BKGRD, buttonClick: "extra1" } ]; +gUseTestUpdater = true; + function runTest() { debugDump("entering"); let patches = getLocalPatchString("complete", null, null, null, null, null, STATE_APPLIED_SVC); let updates = getLocalUpdateString(patches, null, null, null, Services.appinfo.version, Services.appinfo.platformVersion);
--- a/toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul +++ b/toolkit/mozapps/update/tests/chrome/test_0102_background_restartNotification_staging.xul @@ -19,16 +19,18 @@ <script type="application/javascript"> <![CDATA[ const TESTS = [ { pageid: PAGEID_FINISHED_BKGRD, buttonClick: "extra1" } ]; +gUseTestUpdater = true; + function runTest() { debugDump("entering"); Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true); Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1); let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams(); setUpdateURLOverride(url);
--- a/toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul +++ b/toolkit/mozapps/update/tests/chrome/test_0103_background_restartNotification_stagingService.xul @@ -19,16 +19,18 @@ <script type="application/javascript"> <![CDATA[ const TESTS = [ { pageid: PAGEID_FINISHED_BKGRD, buttonClick: "extra1" } ]; +gUseTestUpdater = true; + function runTest() { debugDump("entering"); Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true); Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, true); Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 1); let url = URL_HTTP_UPDATE_XML + "?showDetails=1" + getVersionParams();
--- a/toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul +++ b/toolkit/mozapps/update/tests/chrome/test_9999_cleanup.xul @@ -76,17 +76,39 @@ function runTest() { try { removeDirRecursive(addonPrepDir); } catch (e) { logTestInfo("Unable to remove directory. Path: " + addonPrepDir.path + ", Exception: " + e); } - resetAddons(finishTest); + resetAddons(cleanupRestoreUpdaterBackup); +} + +/** + * After all tests finish this will repeatedly attempt to restore the real + * updater if it exists and then call finishTest after the restore is + * successful. + */ +function cleanupRestoreUpdaterBackup() { + debugDump("entering"); + + try { + // Windows debug builds keep the updater file in use for a short period of + // time after the updater process exits. + restoreUpdaterBackup(); + } catch (e) { + logTestInfo("Attempt to restore the backed up updater failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(cleanupRestoreUpdaterBackup); + return; + } + + SimpleTest.executeSoon(finishTest); } function finishTest() { debugDump("entering"); if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LOG)) { Services.prefs.clearUserPref(PREF_APP_UPDATE_LOG); }
--- a/toolkit/mozapps/update/tests/chrome/utils.js +++ b/toolkit/mozapps/update/tests/chrome/utils.js @@ -211,16 +211,17 @@ var gExtUpdateURL; // ext var gTestCounter = -1; var gWin; var gDocElem; var gPrefToCheck; var gDisableNoUpdateAddon = false; var gDisableUpdateCompatibilityAddon = false; var gDisableUpdateVersionAddon = false; +var gUseTestUpdater = false; // Set to true to log additional information for debugging. To log additional // information for an individual test set DEBUG_AUS_TEST to true in the test's // onload function. var DEBUG_AUS_TEST = false; const DATA_URI_SPEC = "chrome://mochitests/content/chrome/toolkit/mozapps/update/tests/data/"; Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this); @@ -325,19 +326,20 @@ function runTestDefaultWaitForWindowClos SimpleTest.executeSoon(runTestDefaultWaitForWindowClosed); } else { Services.ww.registerNotification(gWindowObserver); gCloseWindowTimeoutCounter = 0; setupFiles(); setupPrefs(); + gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1"); removeUpdateDirsAndFiles(); reloadUpdateManagerData(); - setupAddons(runTest); + setupAddons(setupTestUpdater); } } /** * Default test finish function that can be used by most tests. This function * uses protective measures to prevent the next test from failing provided by * |finishTestDefaultWaitForWindowClosed| helper functions to prevent failure * due to an update window being left open. @@ -353,26 +355,27 @@ function finishTestDefault() { debugDump("channel = " + gChannel); gChannel = null; gPrefRoot.removeObserver(PREF_APP_UPDATE_CHANNEL, observer); } verifyTestsRan(); resetPrefs(); + gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", ""); resetFiles(); removeUpdateDirsAndFiles(); reloadUpdateManagerData(); Services.ww.unregisterNotification(gWindowObserver); if (gDocElem) { gDocElem.removeEventListener("pageshow", onPageShowDefault, false); } - finishTestDefaultWaitForWindowClosed(); + finishTestRestoreUpdaterBackup(); } /** * nsITimerCallback for the timeout timer to cleanly finish a test if the Update * Window doesn't close for a test. This allows the next test to run properly if * a previous test fails. * * @param aTimer @@ -386,25 +389,48 @@ function finishTestTimeout(aTimer) { finishTest(); } catch (e) { finishTestDefault(); } } /** + * When a test finishes this will repeatedly attempt to restore the real updater + * for tests that use the test updater and then call + * finishTestDefaultWaitForWindowClosed after the restore is successful. + */ +function finishTestRestoreUpdaterBackup() { + if (gUseTestUpdater) { + try { + // Windows debug builds keep the updater file in use for a short period of + // time after the updater process exits. + restoreUpdaterBackup(); + } catch (e) { + logTestInfo("Attempt to restore the backed up updater failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(finishTestRestoreUpdaterBackup); + return; + } + } + + finishTestDefaultWaitForWindowClosed(); +} + +/** * If an update window is found SimpleTest.executeSoon can callback before the * update window is fully closed especially with debug builds. If an update * window is found this function will call itself using SimpleTest.executeSoon * up to the amount declared in CLOSE_WINDOW_TIMEOUT_MAXCOUNT until the update * window has closed before finishing the test. */ function finishTestDefaultWaitForWindowClosed() { gCloseWindowTimeoutCounter++; if (gCloseWindowTimeoutCounter > CLOSE_WINDOW_TIMEOUT_MAXCOUNT) { + SimpleTest.requestCompleteLog(); SimpleTest.finish(); return; } // The update window should not be open at this time. If it is the call to // |closeUpdateWindow| will close it and cause the test to fail. if (closeUpdateWindow()) { SimpleTest.executeSoon(finishTestDefaultWaitForWindowClosed); @@ -747,17 +773,17 @@ function checkRadioGroupSelectedIndex() /** * Checks that only incompatible add-ons (e.g. noupdate_X add-ons) that don't * have an update are listed in the add-ons incompatible list. */ function checkIncompatbleList() { for (let i = 0; i < gIncompatibleListbox.itemCount; i++) { let label = gIncompatibleListbox.getItemAtIndex(i).label; // Use indexOf since locales can change the text displayed - ok(label.indexOf("noupdate") != -1, "Checking that only incompatible " + + ok(label.indexOf("noupdate") != -1, "Checking that only incompatible " + "add-ons that don't have an update are listed in the incompatible list"); } } /** * Compares the return value of prefHasUserValue for the preference specified in * gPrefToCheck with the value passed in the aPrefHasValue parameter or the * value specified in the current test's prefHasUserValue property if @@ -871,74 +897,125 @@ function verifyTestsRan() { let msg = "Checking if TESTS[" + i + "] test was performed... " + "callback function name = " + gCallback.name + ", " + "pageid = " + test.pageid; ok(test.ranTest, msg); } } /** - * Restore the updater that was backed up. This is called both in setupFiles - * and resetFiles. It is called in setupFiles before the backup is done in - * case the previous test failed. It is called in resetFiles to put things - * back to its original state. - */ -function resetUpdaterBackup() { - let baseAppDir = getAppBaseDir(); - let updater = baseAppDir.clone(); - let updaterBackup = baseAppDir.clone(); - updater.append(FILE_UPDATER_BIN); - updaterBackup.append(FILE_UPDATER_BIN_BAK); - if (updaterBackup.exists()) { - if (updater.exists()) { - updater.remove(true); - } - updaterBackup.moveTo(baseAppDir, FILE_UPDATER_BIN); - } -} - -/** * Creates a backup of files the tests need to modify so they can be restored to * the original file when the test has finished and then modifies the files. */ function setupFiles() { // Backup the updater-settings.ini file if it exists by moving it. let baseAppDir = getAppBaseDir(); let updateSettingsIni = baseAppDir.clone(); updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI); if (updateSettingsIni.exists()) { updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI_BAK); } updateSettingsIni = baseAppDir.clone(); updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI); writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS); - - // Just in case the last test failed, try to reset. - resetUpdaterBackup(); +} - // Move away the real updater - let updater = baseAppDir.clone(); - updater.append(FILE_UPDATER_BIN); - updater.moveTo(baseAppDir, FILE_UPDATER_BIN_BAK); +/** + * For tests that use the test updater restores the backed up real updater if + * it exists and tries again on failure since Windows debug builds at times + * leave the file in use. After success moveRealUpdater is called to continue + * the setup of the test updater. For tests that don't use the test updater + * runTest will be called. + */ +function setupTestUpdater() { + if (!gUseTestUpdater) { + runTest(); + return; + } - // Move in the test only updater - let testUpdaterDir = Cc["@mozilla.org/file/directory_service;1"]. - getService(Ci.nsIProperties). - get("CurWorkD", Ci.nsILocalFile); + try { + restoreUpdaterBackup(); + } catch (e) { + logTestInfo("Attempt to restore the backed up updater failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(setupTestUpdater); + return; + } + moveRealUpdater(); +} - let relPath = REL_PATH_DATA; - let pathParts = relPath.split("/"); - for (let i = 0; i < pathParts.length; ++i) { - testUpdaterDir.append(pathParts[i]); +/** + * Backs up the real updater and tries again on failure since Windows debug + * builds at times leave the file in use. After success it will call + * copyTestUpdater to continue the setup of the test updater. + */ +function moveRealUpdater() { + try { + // Move away the real updater + let baseAppDir = getAppBaseDir(); + let updater = baseAppDir.clone(); + updater.append(FILE_UPDATER_BIN); + updater.moveTo(baseAppDir, FILE_UPDATER_BIN_BAK); + } catch (e) { + logTestInfo("Attempt to move the real updater out of the way failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(moveRealUpdater); + return; } - let testUpdater = testUpdaterDir.clone(); - testUpdater.append(FILE_UPDATER_BIN); - if (testUpdater.exists()) { + copyTestUpdater(); +} + +/** + * Copies the test updater so it can be used by tests and tries again on failure + * since Windows debug builds at times leave the file in use. After success it + * will call runTest to continue the test. + */ +function copyTestUpdater() { + try { + // Copy the test updater + let baseAppDir = getAppBaseDir(); + let testUpdaterDir = Services.dirsvc.get("CurWorkD", Ci.nsILocalFile); + let relPath = REL_PATH_DATA; + let pathParts = relPath.split("/"); + for (let i = 0; i < pathParts.length; ++i) { + testUpdaterDir.append(pathParts[i]); + } + + let testUpdater = testUpdaterDir.clone(); + testUpdater.append(FILE_UPDATER_BIN); testUpdater.copyToFollowingLinks(baseAppDir, FILE_UPDATER_BIN); + } catch (e) { + logTestInfo("Attempt to copy the test updater failed... " + + "will try again, Exception: " + e); + SimpleTest.executeSoon(copyTestUpdater); + return; + } + + runTest(); +} + +/** + * Restores the updater that was backed up. This is called in setupTestUpdater + * before the backup of the real updater is done in case the previous test + * failed to restore the updater, in finishTestDefaultWaitForWindowClosed when + * the test has finished, and in test_9999_cleanup.xul after all tests have + * finished. + */ +function restoreUpdaterBackup() { + let baseAppDir = getAppBaseDir(); + let updater = baseAppDir.clone(); + let updaterBackup = baseAppDir.clone(); + updater.append(FILE_UPDATER_BIN); + updaterBackup.append(FILE_UPDATER_BIN_BAK); + if (updaterBackup.exists()) { + if (updater.exists()) { + updater.remove(true); + } + updaterBackup.moveTo(baseAppDir, FILE_UPDATER_BIN); } } /** * Sets the most common preferences used by tests to values used by the majority * of the tests and when necessary saves the preference's original values if * present so they can be set back to the original values when the test has * finished. @@ -1017,17 +1094,16 @@ function resetFiles() { try { removeDirRecursive(updatedDir); } catch (e) { logTestInfo("Unable to remove directory. Path: " + updatedDir.path + ", Exception: " + e); } } - resetUpdaterBackup(); } /** * Resets the most common preferences used by tests to their original values. */ function resetPrefs() { if (gAppUpdateURL !== undefined) { Services.prefs.setCharPref(PREF_APP_UPDATE_URL_OVERRIDE, gAppUpdateURL);
--- a/toolkit/mozapps/update/tests/data/shared.js +++ b/toolkit/mozapps/update/tests/data/shared.js @@ -125,16 +125,20 @@ XPCOMUtils.defineLazyGetter(this, "gUP", XPCOMUtils.defineLazyGetter(this, "gDefaultPrefBranch", function test_gDPB() { return Services.prefs.getDefaultBranch(null); }); XPCOMUtils.defineLazyGetter(this, "gPrefRoot", function test_gPR() { return Services.prefs.getBranch(null); }); +XPCOMUtils.defineLazyServiceGetter(this, "gEnv", + "@mozilla.org/process/environment;1", + "nsIEnvironment"); + XPCOMUtils.defineLazyGetter(this, "gZipW", function test_gZipW() { return Cc["@mozilla.org/zipwriter;1"]. createInstance(Ci.nsIZipWriter); }); /* Initializes the update service stub */ function initUpdateServiceStub() { Cc["@mozilla.org/updates/update-service-stub;1"].
--- a/toolkit/mozapps/update/updater/archivereader.cpp +++ b/toolkit/mozapps/update/updater/archivereader.cpp @@ -13,17 +13,17 @@ #ifdef XP_WIN #include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp #include "updatehelper.h" #endif // These are generated at compile time based on the DER file for the channel // being used #ifdef MOZ_VERIFY_MAR_SIGNATURE -#ifdef UPDATER_XPCSHELL_CERT +#ifdef TEST_UPDATER #include "../xpcshellCert.h" #else #include "primaryCert.h" #include "secondaryCert.h" #endif #endif #define UPDATER_NO_STRING_GLUE_STL @@ -80,17 +80,17 @@ ArchiveReader::VerifySignature() { if (!mArchive) { return ARCHIVE_NOT_OPEN; } #ifndef MOZ_VERIFY_MAR_SIGNATURE return OK; #else -#ifdef UPDATER_XPCSHELL_CERT +#ifdef TEST_UPDATER int rv = VerifyLoadedCert(mArchive, xpcshellCertData); #else int rv = VerifyLoadedCert(mArchive, primaryCertData); if (rv != OK) { rv = VerifyLoadedCert(mArchive, secondaryCertData); } #endif return rv;
--- a/toolkit/mozapps/update/updater/updater-xpcshell/moz.build +++ b/toolkit/mozapps/update/updater/updater-xpcshell/moz.build @@ -3,11 +3,11 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. Program('updater-xpcshell') updater_rel_path = '../' DIST_INSTALL = False -DEFINES['UPDATER_XPCSHELL_CERT'] = True +DEFINES['TEST_UPDATER'] = True include('../updater-common.build') FAIL_ON_WARNINGS = True
--- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -2257,17 +2257,28 @@ UpdateThreadFunc(void *param) rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID, MOZ_APP_VERSION); } } #endif if (rv == OK && sStagedUpdate && !sIsOSUpdate) { +#ifdef TEST_UPDATER + // The MOZ_TEST_SKIP_UPDATE_STAGE environment variable prevents copying + // the files in dist/bin in the test updater when staging an update since + // this can cause tests to timeout. + if (getenv("MOZ_TEST_SKIP_UPDATE_STAGE")) { + rv = OK; + } else { + rv = CopyInstallDirToDestDir(); + } +#else rv = CopyInstallDirToDestDir(); +#endif } if (rv == OK) { rv = DoUpdate(); gArchiveReader.Close(); NS_tchar updatingDir[MAXPATHLEN]; NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]), NS_T("%s/updating"), gWorkingDirPath);
--- a/toolkit/mozapps/update/updater/updater.rc +++ b/toolkit/mozapps/update/updater/updater.rc @@ -1,15 +1,15 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Microsoft Visual C++ generated resource script. // -#ifdef UPDATER_XPCSHELL_CERT +#ifdef TEST_UPDATER #include "../resource.h" #define MANIFEST_PATH "../updater.exe.manifest" #define COMCTL32_MANIFEST_PATH "../updater.exe.comctl32.manifest" #define ICON_PATH "../updater.ico" #else #include "resource.h" #define MANIFEST_PATH "updater.exe.manifest" #define COMCTL32_MANIFEST_PATH "updater.exe.comctl32.manifest"