diff --git a/CLOBBER b/CLOBBER index 4bb16a505313..45331ae0d3b9 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 957865 - Non-clobbered ASAN builds were failing all mochitests after the clang upgrade +Bug 989137 - /experiments needed clobber to build on OSX diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index f7b8cee5db43..b7c296e7b253 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index e2443e11c4b6..f2d9c10e89dd 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 972ec30fa4d2..2a7578e28989 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index f7b8cee5db43..b7c296e7b253 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 36530b90a5f8..ddd5c1a005a3 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "6c593455e3d1292120a6f3d41ec5d06bc91019f1", + "revision": "f3575a1613e6c94fbc6b2ae01fd00130ee1b3f8a", "repo_path": "/integration/gaia-central" } diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index e9ccd1391aa1..a46e93397af2 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 9bf5e0fbd710..88c7a676f985 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index 307d4393cf9e..fbdb0d184b9c 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index 7b27d398a221..f319bfd38c4e 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index fb6187a4609c..69308a7d3f6d 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index b2c86ac773eb..998b1fa121fc 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 27b6b036ed17..33c37b23f78a 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1187,22 +1187,20 @@ var gBrowserInit = { let windows8WindowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}).Windows8WindowFrameColor; let windowFrameColor = windows8WindowFrameColor.get(); - // Formula from W3C Techniques For Accessibility Evaluation And - // Repair Tools, Section 2.2 http://www.w3.org/TR/AERT#color - let brightnessThreshold = 125; - let colorThreshold = 500; - let bY = windowFrameColor[0] * .299 + - windowFrameColor[1] * .587 + - windowFrameColor[2] * .114; - let fY = 0; // Default to black for foreground text. - let brightnessDifference = Math.abs(bY - fY); - // Color difference calculation is simplified because black is 0 for R,G,B. - let colorDifference = windowFrameColor[0] + windowFrameColor[1] + windowFrameColor[2]; - - // Brightness is defined within {0, 255}. Set an attribute - // if the window frame color doesn't reach these thresholds - // so the theme can be adjusted for readability. - if (brightnessDifference < brightnessThreshold && colorDifference < colorThreshold) { + // Formula from W3C's WCAG 2.0 spec's color ratio and relative luminance, + // section 1.3.4, http://www.w3.org/TR/WCAG20/ . + windowFrameColor = windowFrameColor.map((color) => { + if (color <= 10) { + return color / 255 / 12.92; + } + return Math.pow(((color / 255) + 0.055) / 1.055, 2.4); + }); + let backgroundLuminance = windowFrameColor[0] * 0.2126 + + windowFrameColor[1] * 0.7152 + + windowFrameColor[2] * 0.0722; + let foregroundLuminance = 0; // Default to black for foreground text. + let contrastRatio = (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05); + if (contrastRatio < 3) { document.documentElement.setAttribute("darkwindowframe", "true"); } } @@ -1882,55 +1880,47 @@ function loadURI(uri, referrer, postData, allowThirdPartyFixup) { } catch (e) {} } -function getShortcutOrURIAndPostData(aURL) { - return Task.spawn(function() { - let mayInheritPrincipal = false; - let postData = null; - let shortcutURL = null; - let keyword = aURL; - let param = ""; +function getShortcutOrURIAndPostData(aURL, aCallback) { + let mayInheritPrincipal = false; + let postData = null; + let shortcutURL = null; + let keyword = aURL; + let param = ""; - let offset = aURL.indexOf(" "); - if (offset > 0) { - keyword = aURL.substr(0, offset); - param = aURL.substr(offset + 1); - } + let offset = aURL.indexOf(" "); + if (offset > 0) { + keyword = aURL.substr(0, offset); + param = aURL.substr(offset + 1); + } - let engine = Services.search.getEngineByAlias(keyword); - if (engine) { - let submission = engine.getSubmission(param); - postData = submission.postData; - throw new Task.Result({ postData: submission.postData, - url: submission.uri.spec, - mayInheritPrincipal: mayInheritPrincipal }); - } + let engine = Services.search.getEngineByAlias(keyword); + if (engine) { + let submission = engine.getSubmission(param); + postData = submission.postData; + aCallback({ postData: submission.postData, url: submission.uri.spec, + mayInheritPrincipal: mayInheritPrincipal }); + return; + } - [shortcutURL, postData] = - PlacesUtils.getURLAndPostDataForKeyword(keyword); + [shortcutURL, postData] = + PlacesUtils.getURLAndPostDataForKeyword(keyword); - if (!shortcutURL) - throw new Task.Result({ postData: postData, url: aURL, - mayInheritPrincipal: mayInheritPrincipal }); + if (!shortcutURL) { + aCallback({ postData: postData, url: aURL, + mayInheritPrincipal: mayInheritPrincipal }); + return; + } - let escapedPostData = ""; - if (postData) - escapedPostData = unescape(postData); + let escapedPostData = ""; + if (postData) + escapedPostData = unescape(postData); - if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { - let charset = ""; - const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; - let matches = shortcutURL.match(re); - if (matches) - [, shortcutURL, charset] = matches; - else { - // Try to get the saved character-set. - try { - // makeURI throws if URI is invalid. - // Will return an empty string if character-set is not found. - charset = yield PlacesUtils.getCharsetForURI(makeURI(shortcutURL)); - } catch (e) {} - } + if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { + let charset = ""; + const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; + let matches = shortcutURL.match(re); + let continueOperation = function () { // encodeURIComponent produces UTF-8, and cannot be used for other charsets. // escape() works in those cases, but it doesn't uri-encode +, @, and /. // Therefore we need to manually replace these ASCII characters by their @@ -1948,23 +1938,45 @@ function getShortcutOrURIAndPostData(aURL) { if (/%s/i.test(escapedPostData)) // POST keyword postData = getPostDataStream(escapedPostData, param, encodedParam, "application/x-www-form-urlencoded"); - } - else if (param) { - // This keyword doesn't take a parameter, but one was provided. Just return - // the original URL. - postData = null; - throw new Task.Result({ postData: postData, url: aURL, - mayInheritPrincipal: mayInheritPrincipal }); + // This URL came from a bookmark, so it's safe to let it inherit the current + // document's principal. + mayInheritPrincipal = true; + + aCallback({ postData: postData, url: shortcutURL, + mayInheritPrincipal: mayInheritPrincipal }); } + if (matches) { + [, shortcutURL, charset] = matches; + continueOperation(); + } else { + // Try to get the saved character-set. + // makeURI throws if URI is invalid. + // Will return an empty string if character-set is not found. + try { + PlacesUtils.getCharsetForURI(makeURI(shortcutURL)) + .then(c => { charset = c; continueOperation(); }); + } catch (ex) { + continueOperation(); + } + } + } + else if (param) { + // This keyword doesn't take a parameter, but one was provided. Just return + // the original URL. + postData = null; + + aCallback({ postData: postData, url: aURL, + mayInheritPrincipal: mayInheritPrincipal }); + } else { // This URL came from a bookmark, so it's safe to let it inherit the current // document's principal. mayInheritPrincipal = true; - throw new Task.Result({ postData: postData, url: shortcutURL, - mayInheritPrincipal: mayInheritPrincipal }); - }); + aCallback({ postData: postData, url: shortcutURL, + mayInheritPrincipal: mayInheritPrincipal }); + } } function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) { @@ -2765,8 +2777,7 @@ var newTabButtonObserver = { onDrop: function (aEvent) { let url = browserDragAndDrop.drop(aEvent, { }); - Task.spawn(function() { - let data = yield getShortcutOrURIAndPostData(url); + getShortcutOrURIAndPostData(url, data => { if (data.url) { // allow third-party services to fixup this URL openNewTabWith(data.url, null, data.postData, aEvent, true); @@ -2786,8 +2797,7 @@ var newWindowButtonObserver = { onDrop: function (aEvent) { let url = browserDragAndDrop.drop(aEvent, { }); - Task.spawn(function() { - let data = yield getShortcutOrURIAndPostData(url); + getShortcutOrURIAndPostData(url, data => { if (data.url) { // allow third-party services to fixup this URL openNewWindowWith(data.url, null, data.postData, true); @@ -5129,8 +5139,7 @@ function middleMousePaste(event) { lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; } - Task.spawn(function() { - let data = yield getShortcutOrURIAndPostData(clipboard); + getShortcutOrURIAndPostData(clipboard, data => { try { makeURI(data.url); } catch (ex) { @@ -5161,8 +5170,7 @@ function handleDroppedLink(event, url, name) { let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; - Task.spawn(function() { - let data = yield getShortcutOrURIAndPostData(url); + getShortcutOrURIAndPostData(url, data => { if (data.url && lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) loadURI(data.url, null, data.postData, false); diff --git a/browser/base/content/newtab/grid.js b/browser/base/content/newtab/grid.js index ec1235f4bf38..514432af122f 100644 --- a/browser/base/content/newtab/grid.js +++ b/browser/base/content/newtab/grid.js @@ -162,7 +162,9 @@ let gGrid = { '' + ''; + ' class="newtab-control newtab-control-block"/>' + + ''; this._siteFragment = document.createDocumentFragment(); this._siteFragment.appendChild(site); diff --git a/browser/base/content/newtab/newTab.css b/browser/base/content/newtab/newTab.css index fe13d67fb107..e30483c8f2e3 100644 --- a/browser/base/content/newtab/newTab.css +++ b/browser/base/content/newtab/newTab.css @@ -129,16 +129,16 @@ input[type=button] { .newtab-thumbnail[dragged], .newtab-link:-moz-focusring > .newtab-thumbnail, -.newtab-site:hover > .newtab-link > .newtab-thumbnail { +.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-link > .newtab-thumbnail { opacity: 1; } /* TITLES */ .newtab-title { - bottom: -20px; + bottom: -21px; position: absolute; left: 0; - line-height: 20px; + line-height: 21px; right: 0; text-align: start; white-space: nowrap; @@ -155,7 +155,7 @@ input[type=button] { } .newtab-control:-moz-focusring, -.newtab-site:hover > .newtab-control { +.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-control { opacity: 1; } @@ -169,16 +169,31 @@ input[type=button] { } } +.newtab-control-sponsored:-moz-locale-dir(rtl), .newtab-control-pin:-moz-locale-dir(ltr), .newtab-control-block:-moz-locale-dir(rtl) { left: 4px; } +.newtab-control-sponsored:-moz-locale-dir(ltr), .newtab-control-block:-moz-locale-dir(ltr), .newtab-control-pin:-moz-locale-dir(rtl) { right: 4px; } +.newtab-control.newtab-control-sponsored { + bottom: -20px; + height: 14px; + -moz-margin-end: -5px; + opacity: 1; + top: auto; + width: 14px; +} + +.newtab-site:not([type=sponsored]) .newtab-control-sponsored { + display: none; +} + /* DRAG & DROP */ /* diff --git a/browser/base/content/newtab/newTab.js b/browser/base/content/newtab/newTab.js index 2ca2ff433c01..af2f6a52ffb4 100644 --- a/browser/base/content/newtab/newTab.js +++ b/browser/base/content/newtab/newTab.js @@ -11,6 +11,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/PageThumbs.jsm"); Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm"); +Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm"); Cu.import("resource://gre/modules/NewTabUtils.jsm"); Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js"); diff --git a/browser/base/content/newtab/page.js b/browser/base/content/newtab/page.js index 112f7b8e9ea4..41326ceb52ee 100644 --- a/browser/base/content/newtab/page.js +++ b/browser/base/content/newtab/page.js @@ -36,7 +36,11 @@ let gPage = { * thumbnail service. */ get allowBackgroundCaptures() { - return document.documentElement.getAttribute("allow-background-captures") == + // The preloader is bypassed altogether for private browsing windows, and + // therefore allow-background-captures will not be set. In that case, the + // page is not preloaded and so it's visible, so allow background captures. + return inPrivateBrowsingMode() || + document.documentElement.getAttribute("allow-background-captures") == "true"; }, @@ -65,10 +69,13 @@ let gPage = { /** * Updates the whole page and the grid when the storage has changed. + * @param aOnlyIfHidden If true, the page is updated only if it's hidden in + * the preloader. */ - update: function Page_update() { + update: function Page_update(aOnlyIfHidden=false) { + let skipUpdate = aOnlyIfHidden && this.allowBackgroundCaptures; // The grid might not be ready yet as we initialize it asynchronously. - if (gGrid.ready) { + if (gGrid.ready && !skipUpdate) { gGrid.refresh(); } }, @@ -87,11 +94,29 @@ let gPage = { if (this.allowBackgroundCaptures) { Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true); + // Initialize type counting with the types we want to count + let directoryCount = {}; + for (let type of DirectoryLinksProvider.linkTypes) { + directoryCount[type] = 0; + } + for (let site of gGrid.sites) { if (site) { site.captureIfMissing(); + let {type} = site.link; + if (type in directoryCount) { + directoryCount[type]++; + } } } + + // Record how many directory sites were shown, but place counts over the + // default 9 in the same bucket + for (let [type, count] of Iterator(directoryCount)) { + let shownId = "NEWTAB_PAGE_DIRECTORY_" + type.toUpperCase() + "_SHOWN"; + let shownCount = Math.min(10, count); + Services.telemetry.getHistogramById(shownId).add(shownCount); + } } }); this._mutationObserver.observe(document.documentElement, { diff --git a/browser/base/content/newtab/sites.js b/browser/base/content/newtab/sites.js index e91706fd0e8c..1a8f7ff4d8ee 100644 --- a/browser/base/content/newtab/sites.js +++ b/browser/base/content/newtab/sites.js @@ -128,6 +128,7 @@ Site.prototype = { link.setAttribute("title", tooltip); link.setAttribute("href", url); this._querySelector(".newtab-title").textContent = title; + this.node.setAttribute("type", this.link.type); if (this.isPinned()) this._updateAttributes(true); @@ -143,17 +144,19 @@ Site.prototype = { * existing thumbnail and the page allows background captures. */ captureIfMissing: function Site_captureIfMissing() { - if (gPage.allowBackgroundCaptures) + if (gPage.allowBackgroundCaptures && !this.link.imageURISpec) { BackgroundPageThumbs.captureIfMissing(this.url); + } }, /** * Refreshes the thumbnail for the site. */ refreshThumbnail: function Site_refreshThumbnail() { - let thumbnailURL = PageThumbs.getThumbnailURL(this.url); let thumbnail = this._querySelector(".newtab-thumbnail"); - thumbnail.style.backgroundImage = "url(" + thumbnailURL + ")"; + thumbnail.style.backgroundColor = this.link.bgColor; + let uri = this.link.imageURISpec || PageThumbs.getThumbnailURL(this.url); + thumbnail.style.backgroundImage = "url(" + uri + ")"; }, /** @@ -165,6 +168,15 @@ Site.prototype = { this._node.addEventListener("dragend", this, false); this._node.addEventListener("mouseover", this, false); this._node.addEventListener("click", this, false); + + // Specially treat the sponsored icon to prevent regular hover effects + let sponsored = this._querySelector(".newtab-control-sponsored"); + sponsored.addEventListener("mouseover", () => { + this.cell.node.setAttribute("ignorehover", "true"); + }); + sponsored.addEventListener("mouseout", () => { + this.cell.node.removeAttribute("ignorehover"); + }); }, /** @@ -189,6 +201,13 @@ Site.prototype = { } Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED") .add(aIndex); + + // Specially count clicks on directory tiles + let typeIndex = DirectoryLinksProvider.linkTypes.indexOf(this.link.type); + if (typeIndex != -1) { + Services.telemetry.getHistogramById("NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED") + .add(typeIndex); + } }, /** diff --git a/browser/base/content/test/general/browser_getshortcutoruri.js b/browser/base/content/test/general/browser_getshortcutoruri.js index bfc4b73595d4..dd47b84d4a14 100644 --- a/browser/base/content/test/general/browser_getshortcutoruri.js +++ b/browser/base/content/test/general/browser_getshortcutoruri.js @@ -103,7 +103,8 @@ function test() { let query = data.keyword; if (data.searchWord) query += " " + data.searchWord; - let returnedData = yield getShortcutOrURIAndPostData(query); + let returnedData = yield new Promise( + resolve => getShortcutOrURIAndPostData(query, resolve)); // null result.url means we should expect the same query we sent in let expected = result.url || query; is(returnedData.url, expected, "got correct URL for " + data.keyword); diff --git a/browser/base/content/test/general/browser_tabopen_reflows.js b/browser/base/content/test/general/browser_tabopen_reflows.js index 5597db65c0cc..aeaf168019ed 100644 --- a/browser/base/content/test/general/browser_tabopen_reflows.js +++ b/browser/base/content/test/general/browser_tabopen_reflows.js @@ -56,6 +56,7 @@ const EXPECTED_REFLOWS = [ ]; const PREF_PRELOAD = "browser.newtab.preload"; +const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource"; /* * This test ensures that there are no unexpected @@ -65,7 +66,11 @@ function test() { waitForExplicitFinish(); Services.prefs.setBoolPref(PREF_PRELOAD, false); - registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_PRELOAD)); + Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}"); + registerCleanupFunction(() => { + Services.prefs.clearUserPref(PREF_PRELOAD); + Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE); + }); // Add a reflow observer and open a new tab. docShell.addWeakReflowObserver(observer); diff --git a/browser/base/content/test/newtab/browser.ini b/browser/base/content/test/newtab/browser.ini index c7b1c7f9449d..01ec5ab570da 100644 --- a/browser/base/content/test/newtab/browser.ini +++ b/browser/base/content/test/newtab/browser.ini @@ -24,3 +24,4 @@ skip-if = os == "mac" # Intermittent failures, bug 898317 [browser_newtab_tabsync.js] [browser_newtab_undo.js] [browser_newtab_unpin.js] +[browser_newtab_update.js] diff --git a/browser/base/content/test/newtab/browser_newtab_update.js b/browser/base/content/test/newtab/browser_newtab_update.js new file mode 100644 index 000000000000..8c63f61c4b47 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_update.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Checks that newtab is updated as its links change. + */ + +function runTests() { + if (NewTabUtils.allPages.updateScheduledForHiddenPages) { + // Wait for dynamic updates triggered by the previous test to finish. + yield whenPagesUpdated(null, true); + } + + // First, start with an empty page. setLinks will trigger a hidden page + // update because it calls clearHistory. We need to wait for that update to + // happen so that the next time we wait for a page update below, we catch the + // right update and not the one triggered by setLinks. + // + // Why this weird way of yielding? First, these two functions don't return + // promises, they call TestRunner.next when done. Second, the point at which + // setLinks is done is independent of when the page update will happen, so + // calling whenPagesUpdated cannot wait until that time. + setLinks([]); + whenPagesUpdated(null, true); + yield null; + yield null; + + // Strategy: Add some visits, open a new page, check the grid, repeat. + fillHistory([link(1)]); + yield whenPagesUpdated(null, true); + yield addNewTabPageTab(); + checkGrid("1,,,,,,,,"); + + fillHistory([link(2)]); + yield whenPagesUpdated(null, true); + yield addNewTabPageTab(); + checkGrid("2,1,,,,,,,"); + + fillHistory([link(1)]); + yield whenPagesUpdated(null, true); + yield addNewTabPageTab(); + checkGrid("1,2,,,,,,,"); + + // Wait for fillHistory to add all links before waiting for an update + yield fillHistory([link(2), link(3), link(4)], TestRunner.next); + yield whenPagesUpdated(null, true); + yield addNewTabPageTab(); + checkGrid("2,1,3,4,,,,,"); +} + +function link(id) { + return { url: "http://example.com/#" + id, title: "site#" + id }; +} diff --git a/browser/base/content/test/newtab/head.js b/browser/base/content/test/newtab/head.js index 6df4bff9c620..8f823cf9738d 100644 --- a/browser/base/content/test/newtab/head.js +++ b/browser/base/content/test/newtab/head.js @@ -2,8 +2,11 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled"; +const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource"; Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true); +// start with no directory links by default +Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}"); let tmp = {}; Cu.import("resource://gre/modules/Promise.jsm", tmp); @@ -26,6 +29,7 @@ registerCleanupFunction(function () { gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]); Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED); + Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE); }); /** @@ -159,20 +163,34 @@ function clearHistory(aCallback) { function fillHistory(aLinks, aCallback) { let numLinks = aLinks.length; + if (!numLinks) { + if (aCallback) + executeSoon(aCallback); + return; + } + let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK; - for (let link of aLinks.reverse()) { + // Important: To avoid test failures due to clock jitter on Windows XP, call + // Date.now() once here, not each time through the loop. + let now = Date.now() * 1000; + + for (let i = 0; i < aLinks.length; i++) { + let link = aLinks[i]; let place = { uri: makeURI(link.url), title: link.title, - visits: [{visitDate: Date.now() * 1000, transitionType: transitionLink}] + // Links are secondarily sorted by visit date descending, so decrease the + // visit date as we progress through the array so that links appear in the + // grid in the order they're present in the array. + visits: [{visitDate: now - i, transitionType: transitionLink}] }; PlacesUtils.asyncHistory.updatePlaces(place, { handleError: function () ok(false, "couldn't add visit to history"), handleResult: function () {}, handleCompletion: function () { - if (--numLinks == 0) + if (--numLinks == 0 && aCallback) aCallback(); } }); @@ -503,12 +521,18 @@ function createDragEvent(aEventType, aData) { /** * Resumes testing when all pages have been updated. + * @param aCallback Called when done. If not specified, TestRunner.next is used. + * @param aOnlyIfHidden If true, this resumes testing only when an update that + * applies to pre-loaded, hidden pages is observed. If + * false, this resumes testing when any update is observed. */ -function whenPagesUpdated(aCallback) { +function whenPagesUpdated(aCallback, aOnlyIfHidden=false) { let page = { - update: function () { - NewTabUtils.allPages.unregister(this); - executeSoon(aCallback || TestRunner.next); + update: function (onlyIfHidden=false) { + if (onlyIfHidden == aOnlyIfHidden) { + NewTabUtils.allPages.unregister(this); + executeSoon(aCallback || TestRunner.next); + } } }; diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml index f218cbe9edef..1468efb592cd 100644 --- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -260,29 +260,35 @@ var action = this._parseActionUrl(url); let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; - Task.spawn(function() { - let matchLastLocationChange = true; - if (action) { - url = action.param; - if (this.hasAttribute("actiontype")) { - if (action.type == "switchtab") { - this.handleRevert(); - let prevTab = gBrowser.selectedTab; - if (switchToTabHavingURI(url) && - isTabEmpty(prevTab)) - gBrowser.removeTab(prevTab); - } - return; - } - } - else { - [url, postData, mayInheritPrincipal] = yield this._canonizeURL(aTriggeringEvent); - matchLastLocationChange = (lastLocationChange == - gBrowser.selectedBrowser.lastLocationChange); - if (!url) - return; - } + let matchLastLocationChange = true; + if (action) { + url = action.param; + if (this.hasAttribute("actiontype")) { + if (action.type == "switchtab") { + this.handleRevert(); + let prevTab = gBrowser.selectedTab; + if (switchToTabHavingURI(url) && + isTabEmpty(prevTab)) + gBrowser.removeTab(prevTab); + } + return; + } + continueOperation.call(this); + } + else { + this._canonizeURL(aTriggeringEvent, response => { + [url, postData, mayInheritPrincipal] = response; + if (url) { + matchLastLocationChange = (lastLocationChange == + gBrowser.selectedBrowser.lastLocationChange); + continueOperation.call(this); + } + }); + } + + function continueOperation() + { this.value = url; gBrowser.userTypedValue = url; try { @@ -347,73 +353,74 @@ loadCurrent(); } } - }.bind(this)); + } ]]> + = 0) { - url = url.substring(0, firstSlash) + suffix + - url.substring(firstSlash + 1); - } else { - url = url + suffix; + switch (true) { + case (accel && shift): + suffix = ".org/"; + break; + case (shift): + suffix = ".net/"; + break; + case (accel): + try { + suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix"); + if (suffix.charAt(suffix.length - 1) != "/") + suffix += "/"; + } catch(e) { + suffix = ".com/"; } - - url = "http://www." + url; - } + break; } - let data = yield getShortcutOrURIAndPostData(url); + if (suffix) { + // trim leading/trailing spaces (bug 233205) + url = url.trim(); - throw new Task.Result([data.url, data.postData, data.mayInheritPrincipal]); - }.bind(this)); + // Tack www. and suffix on. If user has appended directories, insert + // suffix before them (bug 279035). Be careful not to get two slashes. + + let firstSlash = url.indexOf("/"); + + if (firstSlash >= 0) { + url = url.substring(0, firstSlash) + suffix + + url.substring(firstSlash + 1); + } else { + url = url + suffix; + } + + url = "http://www." + url; + } + } + + getShortcutOrURIAndPostData(url, data => { + aCallback([data.url, data.postData, data.mayInheritPrincipal]); + }); ]]> diff --git a/browser/components/customizableui/content/toolbar.xml b/browser/components/customizableui/content/toolbar.xml index 55c0a12ef2b1..8dd8fc01b4a1 100644 --- a/browser/components/customizableui/content/toolbar.xml +++ b/browser/components/customizableui/content/toolbar.xml @@ -45,6 +45,11 @@ Cu.import("resource:///modules/CustomizableUI.jsm", scope); let CustomizableUI = scope.CustomizableUI; + // Bug 989289: Forcibly set the now unsupported "mode" attribute, just + // in case it gets accidentally restored from persistence from a user + // that's been upgrading and downgrading. + this.setAttribute("mode", "icons"); + // Searching for the toolbox palette in the toolbar binding because // toolbars are constructed first. let toolbox = this.toolbox; diff --git a/browser/components/customizableui/test/browser.ini b/browser/components/customizableui/test/browser.ini index 67b41d17f3a8..c5c3f709feba 100644 --- a/browser/components/customizableui/test/browser.ini +++ b/browser/components/customizableui/test/browser.ini @@ -94,4 +94,5 @@ skip-if = os == "linux" [browser_987177_destroyWidget_xul.js] [browser_987177_xul_wrapper_updating.js] [browser_987492_window_api.js] +[browser_989289_force_icons_mode_attribute.js] [browser_panel_toggle.js] diff --git a/browser/components/customizableui/test/browser_989289_force_icons_mode_attribute.js b/browser/components/customizableui/test/browser_989289_force_icons_mode_attribute.js new file mode 100644 index 000000000000..928157786b38 --- /dev/null +++ b/browser/components/customizableui/test/browser_989289_force_icons_mode_attribute.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbarID = "test-toolbar"; + +/** + * Tests that customizable toolbars are forced to have their mode + * attribute set to "icons". + */ +add_task(function* testAddingToolbar() { + let toolbar = document.createElement("toolbar"); + toolbar.setAttribute("mode", "full"); + toolbar.setAttribute("customizable", "true"); + toolbar.setAttribute("id", kToolbarID); + + CustomizableUI.registerArea(kToolbarID, { + type: CustomizableUI.TYPE_TOOLBAR, + legacy: false, + }) + + gNavToolbox.appendChild(toolbar); + + is(toolbar.getAttribute("mode"), "icons", + "Toolbar should have its mode attribute set to icons.") + + toolbar.remove(); + CustomizableUI.unregisterArea(kToolbarID); +}); \ No newline at end of file diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index bca11c903050..65353e3279aa 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -22,6 +22,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", XPCOMUtils.defineLazyModuleGetter(this, "ContentClick", "resource:///modules/ContentClick.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider", + "resource://gre/modules/DirectoryLinksProvider.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); @@ -475,6 +478,8 @@ BrowserGlue.prototype = { WebappManager.init(); PageThumbs.init(); NewTabUtils.init(); + DirectoryLinksProvider.init(); + NewTabUtils.links.addProvider(DirectoryLinksProvider); BrowserNewTabPreloader.init(); #ifdef NIGHTLY_BUILD if (Services.prefs.getBoolPref("dom.identity.enabled")) { diff --git a/browser/components/preferences/in-content/sync.js b/browser/components/preferences/in-content/sync.js index c077a7bf3bb9..cea1ec3fc433 100644 --- a/browser/components/preferences/in-content/sync.js +++ b/browser/components/preferences/in-content/sync.js @@ -3,10 +3,26 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ Components.utils.import("resource://services-sync/main.js"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () { + return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {}); +}); const PAGE_NO_ACCOUNT = 0; const PAGE_HAS_ACCOUNT = 1; const PAGE_NEEDS_UPDATE = 2; +const PAGE_PLEASE_WAIT = 3; +const FXA_PAGE_LOGGED_OUT = 4; +const FXA_PAGE_LOGGED_IN = 5; + +// Indexes into the "login status" deck. +// We are in a successful verified state - everything should work! +const FXA_LOGIN_VERIFIED = 0; +// We have logged in to an unverified account. +const FXA_LOGIN_UNVERIFIED = 1; +// We are logged in locally, but the server rejected our credentials. +const FXA_LOGIN_FAILED = 2; let gSyncPane = { _stringBundle: null, @@ -43,6 +59,10 @@ let gSyncPane = { return; } + // it may take some time before we can determine what provider to use + // and the state of that provider, so show the "please wait" page. + this.page = PAGE_PLEASE_WAIT; + let onUnload = function () { window.removeEventListener("unload", onUnload, false); try { @@ -56,7 +76,6 @@ let gSyncPane = { this._init(); }.bind(this); - Services.obs.addObserver(onReady, "weave:service:ready", false); window.addEventListener("unload", onUnload, false); @@ -66,9 +85,10 @@ let gSyncPane = { _init: function () { let topics = ["weave:service:login:error", "weave:service:login:finish", - "weave:service:start-over", + "weave:service:start-over:finish", "weave:service:setup-complete", - "weave:service:logout:finish"]; + "weave:service:logout:finish", + FxAccountsCommon.ONVERIFIED_NOTIFICATION]; // Add the observers now and remove them on unload //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling @@ -88,16 +108,73 @@ let gSyncPane = { }, updateWeavePrefs: function () { - if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED || - Weave.Svc.Prefs.get("firstSync", "") == "notReady") { + let service = Components.classes["@mozilla.org/weave/service;1"] + .getService(Components.interfaces.nsISupports) + .wrappedJSObject; + // service.fxAccountsEnabled is false iff sync is already configured for + // the legacy provider. + if (service.fxAccountsEnabled) { + // determine the fxa status... + this.page = PAGE_PLEASE_WAIT; + Components.utils.import("resource://gre/modules/FxAccounts.jsm"); + fxAccounts.getSignedInUser().then(data => { + if (!data) { + this.page = FXA_PAGE_LOGGED_OUT; + return; + } + this.page = FXA_PAGE_LOGGED_IN; + // We are logged in locally, but maybe we are in a state where the + // server rejected our credentials (eg, password changed on the server) + let fxaLoginStatus = document.getElementById("fxaLoginStatus"); + let enginesListDisabled; + // Not Verfied implies login error state, so check that first. + if (!data.verified) { + fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED; + enginesListDisabled = true; + // So we think we are logged in, so login problems are next. + // (Although if the Sync identity manager is still initializing, we + // ignore login errors and assume all will eventually be good.) + // LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in". + // All other login failures are assumed to be transient and should go + // away by themselves, so aren't reflected here. + } else if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) { + fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED; + enginesListDisabled = true; + // Else we must be golden (or in an error state we expect to magically + // resolve itself) + } else { + fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED; + enginesListDisabled = false; + } + document.getElementById("fxaEmailAddress1").textContent = data.email; + document.getElementById("fxaEmailAddress2").textContent = data.email; + document.getElementById("fxaEmailAddress3").textContent = data.email; + document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName; + let engines = document.getElementById("fxaSyncEngines") + for (let checkbox of engines.querySelectorAll("checkbox")) { + checkbox.disabled = enginesListDisabled; + } + + let checkbox = document.getElementById("fxa-pweng-chk"); + let help = document.getElementById("fxa-pweng-help"); + let allowPasswordsEngine = service.allowPasswordsEngine; + + if (!allowPasswordsEngine) { + checkbox.checked = false; + } + + checkbox.disabled = !allowPasswordsEngine; + help.hidden = allowPasswordsEngine; + }); + // If fxAccountEnabled is false and we are in a "not configured" state, + // then fxAccounts is probably fully disabled rather than just unconfigured, + // so handle this case. This block can be removed once we remove support + // for fxAccounts being disabled. + } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED || + Weave.Svc.Prefs.get("firstSync", "") == "notReady") { this.page = PAGE_NO_ACCOUNT; - let service = Components.classes["@mozilla.org/weave/service;1"] - .getService(Components.interfaces.nsISupports) - .wrappedJSObject; - // no concept of "pair" in an fxAccounts world. - if (service.fxAccountsEnabled) { - document.getElementById("pairDevice").hidden = true; - } + // else: sync was previously configured for the legacy provider, so we + // make the "old" panels available. } else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE || Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) { this.needsUpdate(); @@ -147,7 +224,7 @@ let gSyncPane = { /** * Invoke the Sync setup wizard. - * + * * @param wizardType * Indicates type of wizard to launch: * null -- regular set up wizard @@ -160,8 +237,7 @@ let gSyncPane = { .wrappedJSObject; if (service.fxAccountsEnabled) { - let win = Services.wm.getMostRecentWindow("navigator:browser"); - win.switchToTabHavingURI("about:accounts", true); + this.openContentInBrowser("about:accounts"); } else { let win = Services.wm.getMostRecentWindow("Weave:AccountSetup"); if (win) @@ -174,6 +250,85 @@ let gSyncPane = { } }, + openContentInBrowser: function(url) { + let win = Services.wm.getMostRecentWindow("navigator:browser"); + if (!win) { + // no window to use, so use _openLink to create a new one. We don't + // always use that as it prefers to open a new window rather than use + // an existing one. + gSyncUtils._openLink(url); + return; + } + win.switchToTabHavingURI(url, true); + // seeing as we are doing this in a tab we close the prefs dialog. + window.close(); + }, + + signUp: function() { + this.openContentInBrowser("about:accounts?action=signup"); + }, + + signIn: function() { + this.openContentInBrowser("about:accounts?action=signin"); + }, + + reSignIn: function() { + this.openContentInBrowser("about:accounts?action=reauth"); + }, + + manageFirefoxAccount: function() { + let url = Services.prefs.getCharPref("identity.fxaccounts.settings.uri"); + this.openContentInBrowser(url); + }, + + verifyFirefoxAccount: function() { + Components.utils.import("resource://gre/modules/FxAccounts.jsm"); + fxAccounts.resendVerificationEmail().then(() => { + fxAccounts.getSignedInUser().then(data => { + let sb = this._stringBundle; + let title = sb.GetStringFromName("firefoxAccountsVerificationSentTitle"); + let heading = sb.formatStringFromName("firefoxAccountsVerificationSentHeading", + [data.email], 1); + let description = sb.GetStringFromName("firefoxAccountVerificationSentDescription"); + + Services.prompt.alert(window, title, heading + "\n\n" + description); + }); + }); + }, + + openOldSyncSupportPage: function() { + let url = Services.urlFormatter.formatURLPref('app.support.baseURL') + "old-sync" + this.openContentInBrowser(url); + }, + + unlinkFirefoxAccount: function(confirm) { + if (confirm) { + // We use a string bundle shared with aboutAccounts. + let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties"); + let continueLabel = sb.GetStringFromName("continue.label"); + let title = sb.GetStringFromName("disconnect.verify.title"); + let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); + let brandShortName = brandBundle.GetStringFromName("brandShortName"); + let body = sb.GetStringFromName("disconnect.verify.heading") + + "\n\n" + + sb.formatStringFromName("disconnect.verify.description", + [brandShortName], 1); + let ps = Services.prompt; + let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) + + (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) + + ps.BUTTON_POS_1_DEFAULT; + let pressed = Services.prompt.confirmEx(window, title, body, buttonFlags, + continueLabel, null, null, null, {}); + if (pressed != 0) { // 0 is the "continue" button + return; + } + } + Components.utils.import('resource://gre/modules/FxAccounts.jsm'); + fxAccounts.signOut().then(() => { + this.updateWeavePrefs(); + }); + }, + openQuotaDialog: function () { let win = Services.wm.getMostRecentWindow("Sync:ViewQuota"); if (win) diff --git a/browser/components/preferences/in-content/sync.xul b/browser/components/preferences/in-content/sync.xul index 34c0aad0e7d8..97165341c536 100644 --- a/browser/components/preferences/in-content/sync.xul +++ b/browser/components/preferences/in-content/sync.xul @@ -38,6 +38,7 @@