mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 20:05:49 +00:00
Bug 1508364 - New UI for Private Browsing with Search r=andreio
MozReview-Commit-ID: 4WSGpL5Gvde Differential Revision: https://phabricator.services.mozilla.com/D16854 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
11b304b442
commit
bbd0796d2c
@ -973,6 +973,9 @@ pref("security.certerrors.recordEventTelemetry", true);
|
||||
// Whether to start the private browsing mode at application startup
|
||||
pref("browser.privatebrowsing.autostart", false);
|
||||
|
||||
// Whether to show the new private browsing UI with in-content search box.
|
||||
pref("browser.privatebrowsing.searchUI", true);
|
||||
|
||||
// Whether the bookmark panel should be shown when bookmarking a page.
|
||||
pref("browser.bookmarks.editDialog.showForNewBookmarks", true);
|
||||
|
||||
|
@ -7,11 +7,13 @@
|
||||
var EXPORTED_SYMBOLS = ["AboutPrivateBrowsingHandler"];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var AboutPrivateBrowsingHandler = {
|
||||
_topics: [
|
||||
"DontShowIntroPanelAgain",
|
||||
"OpenPrivateWindow",
|
||||
"SearchHandoff",
|
||||
],
|
||||
|
||||
init() {
|
||||
@ -40,6 +42,67 @@ var AboutPrivateBrowsingHandler = {
|
||||
win.ContentBlocking.dontShowIntroPanelAgain();
|
||||
break;
|
||||
}
|
||||
case "SearchHandoff": {
|
||||
let searchAlias = "";
|
||||
let searchAliases = Services.search.defaultEngine.wrappedJSObject.__internalAliases;
|
||||
if (searchAliases && searchAliases.length > 0) {
|
||||
searchAlias = `${searchAliases[0]} `;
|
||||
}
|
||||
let urlBar = aMessage.target.browser.ownerGlobal.gURLBar;
|
||||
let isFirstChange = true;
|
||||
|
||||
if (!aMessage.data || !aMessage.data.text) {
|
||||
urlBar.hiddenFocus();
|
||||
} else {
|
||||
// Pass the provided text to the awesomebar. Prepend the @engine shortcut.
|
||||
urlBar.search(`${searchAlias}${aMessage.data.text}`);
|
||||
isFirstChange = false;
|
||||
}
|
||||
|
||||
let checkFirstChange = () => {
|
||||
// Check if this is the first change since we hidden focused. If it is,
|
||||
// remove hidden focus styles, prepend the search alias and hide the
|
||||
// in-content search.
|
||||
if (isFirstChange) {
|
||||
isFirstChange = false;
|
||||
urlBar.removeHiddenFocus();
|
||||
urlBar.search(searchAlias);
|
||||
aMessage.target.sendAsyncMessage("HideSearch");
|
||||
urlBar.removeEventListener("compositionstart", checkFirstChange);
|
||||
urlBar.removeEventListener("paste", checkFirstChange);
|
||||
}
|
||||
};
|
||||
|
||||
let onKeydown = ev => {
|
||||
// Check if the keydown will cause a value change.
|
||||
if (ev.key.length === 1 && !ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
||||
checkFirstChange();
|
||||
}
|
||||
// If the Esc button is pressed, we are done. Show in-content search and cleanup.
|
||||
if (ev.key === "Escape") {
|
||||
onDone();
|
||||
}
|
||||
};
|
||||
|
||||
let onDone = () => {
|
||||
// We are done. Show in-content search again and cleanup.
|
||||
aMessage.target.sendAsyncMessage("ShowSearch");
|
||||
urlBar.removeHiddenFocus();
|
||||
|
||||
urlBar.removeEventListener("keydown", onKeydown);
|
||||
urlBar.removeEventListener("mousedown", onDone);
|
||||
urlBar.removeEventListener("blur", onDone);
|
||||
urlBar.removeEventListener("compositionstart", checkFirstChange);
|
||||
urlBar.removeEventListener("paste", checkFirstChange);
|
||||
};
|
||||
|
||||
urlBar.addEventListener("keydown", onKeydown);
|
||||
urlBar.addEventListener("mousedown", onDone);
|
||||
urlBar.addEventListener("blur", onDone);
|
||||
urlBar.addEventListener("compositionstart", checkFirstChange);
|
||||
urlBar.addEventListener("paste", checkFirstChange);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -92,7 +92,7 @@ let ACTORS = {
|
||||
child: {
|
||||
module: "resource:///actors/ContentSearchChild.jsm",
|
||||
group: "browsers",
|
||||
matches: ["about:home", "about:newtab", "about:welcome",
|
||||
matches: ["about:home", "about:newtab", "about:welcome", "about:privatebrowsing",
|
||||
"chrome://mochitests/content/*"],
|
||||
events: {
|
||||
"ContentSearchClient": {capture: true, wantUntrusted: true},
|
||||
|
@ -1,5 +1,7 @@
|
||||
html.private .showNormal,
|
||||
html.normal .showPrivate,
|
||||
html.search-ui .dontShowSearch,
|
||||
html.no-search-ui .showSearch,
|
||||
body[tpEnabled] .showTpDisabled,
|
||||
body:not([tpEnabled]) .showTpEnabled {
|
||||
display: none !important;
|
||||
|
@ -5,6 +5,7 @@
|
||||
/* eslint-env mozilla/frame-script */
|
||||
|
||||
const TP_PB_ENABLED_PREF = "privacy.trackingprotection.pbmode.enabled";
|
||||
const PB_SEARCH_UI_ENABLED_PREF = "browser.privatebrowsing.searchUI";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if (!RPMIsWindowPrivate()) {
|
||||
@ -34,4 +35,73 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
document.getElementById("tpSubHeader").remove();
|
||||
document.getElementById("tpSection").remove();
|
||||
}
|
||||
|
||||
let searchUIEnabled = RPMGetBoolPref(PB_SEARCH_UI_ENABLED_PREF);
|
||||
if (searchUIEnabled) {
|
||||
setupSearchUI();
|
||||
}
|
||||
});
|
||||
|
||||
function setupSearchUI() {
|
||||
// Show the new search UI and hide the old one.
|
||||
document.documentElement.classList.remove("no-search-ui");
|
||||
document.documentElement.classList.add("search-ui");
|
||||
|
||||
// Setup the private browsing myths link.
|
||||
document.getElementById("private-browsing-myths").setAttribute("href",
|
||||
RPMGetFormatURLPref("app.support.baseURL") + "private-browsing-myths");
|
||||
|
||||
// Setup the search hand-off box.
|
||||
let btn = document.getElementById("search-handoff-button");
|
||||
let editable = document.getElementById("fake-editable");
|
||||
let HIDE_SEARCH_TOPIC = "HideSearch";
|
||||
let SHOW_SEARCH_TOPIC = "ShowSearch";
|
||||
let SEARCH_HANDOFF_TOPIC = "SearchHandoff";
|
||||
|
||||
function showSearch() {
|
||||
btn.classList.remove("focused");
|
||||
btn.classList.remove("hidden");
|
||||
RPMRemoveMessageListener(SHOW_SEARCH_TOPIC, showSearch);
|
||||
}
|
||||
|
||||
function hideSearch() {
|
||||
btn.classList.add("hidden");
|
||||
}
|
||||
|
||||
function handoffSearch(text) {
|
||||
RPMSendAsyncMessage(SEARCH_HANDOFF_TOPIC, {text});
|
||||
RPMAddMessageListener(SHOW_SEARCH_TOPIC, showSearch);
|
||||
if (text) {
|
||||
hideSearch();
|
||||
} else {
|
||||
btn.classList.add("focused");
|
||||
RPMAddMessageListener(HIDE_SEARCH_TOPIC, hideSearch);
|
||||
}
|
||||
}
|
||||
btn.addEventListener("focus", function() {
|
||||
handoffSearch();
|
||||
});
|
||||
btn.addEventListener("click", function() {
|
||||
handoffSearch();
|
||||
});
|
||||
|
||||
// Hand-off any text that gets dropped or pasted
|
||||
editable.addEventListener("drop", function(ev) {
|
||||
ev.preventDefault();
|
||||
let text = ev.dataTransfer.getData("text");
|
||||
if (text) {
|
||||
handoffSearch(text);
|
||||
}
|
||||
});
|
||||
editable.addEventListener("paste", function(ev) {
|
||||
ev.preventDefault();
|
||||
handoffSearch(ev.clipboardData.getData("Text"));
|
||||
});
|
||||
|
||||
// Load contentSearchUI so it sets the search engine icon for us.
|
||||
// TODO: FIXME. We should eventually refector contentSearchUI to do only what
|
||||
// we need and have it do the common search handoff work for
|
||||
// about:newtab and about:privatebrowsing.
|
||||
let input = document.getElementById("dummy-input");
|
||||
new window.ContentSearchUIController(input, input.parentNode, "aboutprivatebrowsing", "aboutprivatebrowsing");
|
||||
}
|
||||
|
@ -17,13 +17,14 @@
|
||||
%aboutPrivateBrowsingDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" class="private">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" class="private no-search-ui">
|
||||
<head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src chrome:"/>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src chrome: blob:"/>
|
||||
<link rel="icon" type="image/png" href="chrome://browser/skin/privatebrowsing/favicon.svg"/>
|
||||
<link rel="stylesheet" href="chrome://browser/content/aboutPrivateBrowsing.css" type="text/css" media="all"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/privatebrowsing/aboutPrivateBrowsing.css" type="text/css" media="all"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutPrivateBrowsing.js"></script>
|
||||
<script type="application/javascript" src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
@ -31,7 +32,7 @@
|
||||
<button id="startPrivateBrowsing"
|
||||
class="showNormal"
|
||||
accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;">&privatebrowsingpage.openPrivateWindow.label;</button>
|
||||
<div class="showPrivate container">
|
||||
<div class="showPrivate dontShowSearch container">
|
||||
<h1 class="title">
|
||||
<span id="title">&privateBrowsing.title;</span>
|
||||
</h1>
|
||||
@ -67,5 +68,27 @@
|
||||
<p class="about-info">&aboutPrivateBrowsing.learnMore3.before;<a id="learnMore" target="_blank">&aboutPrivateBrowsing.learnMore3.title;</a>&aboutPrivateBrowsing.learnMore3.after;</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="showPrivate showSearch container">
|
||||
<div class="logo-and-wordmark">
|
||||
<div class="logo" />
|
||||
<div class="wordmark" />
|
||||
</div>
|
||||
<div class="search-inner-wrapper">
|
||||
<button id="search-handoff-button" class="search-handoff-button" title="&aboutPrivateBrowsing.search.placeholder;" tabindex="-1">
|
||||
<div class="fake-textbox">&aboutPrivateBrowsing.search.placeholder;</div>
|
||||
<div id="fake-editable" class="fake-editable" tabindex="-1" aria-hidden="true" contenteditable="" />
|
||||
<div class="fake-caret" />
|
||||
</button>
|
||||
<input id="dummy-input" class="dummy-input" type="search" />
|
||||
</div>
|
||||
<div class="info">
|
||||
<h1>&aboutPrivateBrowsing.info.title;</h1>
|
||||
<p>
|
||||
&aboutPrivateBrowsing.info.description;
|
||||
<br/>
|
||||
<a id="private-browsing-myths">&aboutPrivateBrowsing.info.myths;</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -72,3 +72,95 @@ add_task(async function test_links() {
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests the private-browsing-myths link in "about:privatebrowsing".
|
||||
*/
|
||||
add_task(async function test_myths_link() {
|
||||
Services.prefs.setCharPref("app.support.baseURL", "https://example.com/");
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("app.support.baseURL");
|
||||
});
|
||||
|
||||
let { win, tab } = await openAboutPrivateBrowsing();
|
||||
|
||||
await testLinkOpensUrl({ win, tab,
|
||||
elementId: "private-browsing-myths",
|
||||
expectedUrl: "https://example.com/private-browsing-myths",
|
||||
});
|
||||
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
function urlBarHasHiddenFocus(win) {
|
||||
return win.gURLBar.hasAttribute("focused") && win.gURLBar.classList.contains("hidden-focus");
|
||||
}
|
||||
|
||||
function urlBarHasNormalFocus(win) {
|
||||
return win.gURLBar.hasAttribute("focused") && !win.gURLBar.classList.contains("hidden-focus");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the search hand-off on character keydown in "about:privatebrowsing".
|
||||
*/
|
||||
add_task(async function test_search_handoff_on_keydown() {
|
||||
let { win, tab } = await openAboutPrivateBrowsing();
|
||||
|
||||
await ContentTask.spawn(tab, null, async function() {
|
||||
let btn = content.document.getElementById("search-handoff-button");
|
||||
btn.click();
|
||||
ok(btn.classList.contains("focused"), "in-content search has focus styles");
|
||||
});
|
||||
ok(urlBarHasHiddenFocus(win), "url bar has hidden focused");
|
||||
await new Promise(r => EventUtils.synthesizeKey("f", {}, win, r));
|
||||
await ContentTask.spawn(tab, null, async function() {
|
||||
ok(content.document.getElementById("search-handoff-button").classList.contains("hidden"),
|
||||
"in-content search is hidden");
|
||||
});
|
||||
ok(urlBarHasNormalFocus(win), "url bar has normal focused");
|
||||
is(win.gURLBar.value, "@google f", "url bar has search text");
|
||||
|
||||
// Hitting ESC should reshow the in-content search
|
||||
await new Promise(r => EventUtils.synthesizeKey("KEY_Escape", {}, win, r));
|
||||
await ContentTask.spawn(tab, null, async function() {
|
||||
ok(!content.document.getElementById("search-handoff-button").classList.contains("hidden"),
|
||||
"in-content search is not");
|
||||
});
|
||||
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests the search hand-off on composition start in "about:privatebrowsing".
|
||||
*/
|
||||
add_task(async function test_search_handoff_on_composition_start() {
|
||||
let { win, tab } = await openAboutPrivateBrowsing();
|
||||
|
||||
await ContentTask.spawn(tab, null, async function() {
|
||||
content.document.getElementById("search-handoff-button").click();
|
||||
});
|
||||
ok(urlBarHasHiddenFocus(win), "url bar has hidden focused");
|
||||
await new Promise(r => EventUtils.synthesizeComposition({type: "compositionstart"}, win, r));
|
||||
ok(urlBarHasNormalFocus(win), "url bar has normal focused");
|
||||
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests the search hand-off on paste in "about:privatebrowsing".
|
||||
*/
|
||||
add_task(async function test_search_handoff_on_paste() {
|
||||
let { win, tab } = await openAboutPrivateBrowsing();
|
||||
|
||||
await ContentTask.spawn(tab, null, async function() {
|
||||
content.document.getElementById("search-handoff-button").click();
|
||||
});
|
||||
ok(urlBarHasHiddenFocus(win), "url bar has hidden focused");
|
||||
var helper = SpecialPowers.Cc["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(SpecialPowers.Ci.nsIClipboardHelper);
|
||||
helper.copyString("words");
|
||||
await new Promise(r => EventUtils.synthesizeKey("v", {accelKey: true}, win, r));
|
||||
ok(urlBarHasNormalFocus(win), "url bar has normal focused");
|
||||
is(win.gURLBar.value, "@google words", "url bar has search text");
|
||||
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
@ -103,3 +103,148 @@ a.button:hover:active {
|
||||
color: inherit;
|
||||
background-color: #6000a1;
|
||||
}
|
||||
|
||||
.logo-and-wordmark {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.search-ui .container {
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
background: url("chrome://branding/content/icon128.png") no-repeat center center;
|
||||
background-size: 97px;
|
||||
display: inline-block;
|
||||
height: 97px;
|
||||
width: 97px;
|
||||
}
|
||||
|
||||
.wordmark {
|
||||
background: url("resource://activity-stream/data/content/assets/firefox-wordmark.svg") no-repeat center center;
|
||||
background-size: 175px;
|
||||
-moz-context-properties: fill;
|
||||
display: inline-block;
|
||||
fill: #fff;
|
||||
height: 97px;
|
||||
margin-inline-start: 15px;
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
.search-inner-wrapper {
|
||||
display: flex;
|
||||
height: 48px;
|
||||
margin-bottom: 64px;
|
||||
padding: 0 22px;
|
||||
}
|
||||
|
||||
.search-handoff-button {
|
||||
background: #fff var(--newtab-search-icon) 12px center no-repeat;
|
||||
background-size: 24px;
|
||||
border: solid 1px rgba(249, 249, 250, 0.2);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.15);
|
||||
cursor: text;
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-inline-end: 48px;
|
||||
padding-inline-start: 46px;
|
||||
position: relative;
|
||||
opacity: 1;
|
||||
transition: opacity 500ms;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-handoff-button.focused {
|
||||
border: solid 1px #0060df;
|
||||
box-shadow: 0 0 0 1px #0060df, 0 0 0 4px rgba(0, 96, 223, 0.3);
|
||||
}
|
||||
|
||||
.search-handoff-button.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.search-handoff-button:dir(rtl) {
|
||||
background-position-x: right 12px;
|
||||
}
|
||||
|
||||
.search-inner-wrapper .search-handoff-button:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.search-handoff-button.focused .fake-caret {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fake-editable:focus {
|
||||
outline: none;
|
||||
caret-color: transparent;
|
||||
}
|
||||
|
||||
.fake-editable {
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.fake-textbox {
|
||||
color: rgb(12, 12, 13);
|
||||
opacity: 0.54;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
@keyframes caret-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.fake-caret {
|
||||
animation: caret-animation 1.3s steps(5, start) infinite;
|
||||
background: rgb(12, 12, 13);
|
||||
display: none;
|
||||
inset-inline-start: 47px;
|
||||
height: 17px;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.dummy-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.info {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
background-image: url("chrome://browser/skin/privatebrowsing/private-browsing.svg");
|
||||
background-position: left 32px top 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 32px;
|
||||
border-radius: 6px;
|
||||
letter-spacing: -0.2px;
|
||||
padding: 20px;
|
||||
padding-inline-start: 76px;
|
||||
}
|
||||
|
||||
.info:dir(rtl) {
|
||||
background-position: right 32px top 20px;
|
||||
}
|
||||
|
||||
.info h1 {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.info p {
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ class TestAboutPrivateBrowsing(PuppeteerMixin, MarionetteTestCase):
|
||||
# Use a fake local support URL
|
||||
support_url = 'about:blank?'
|
||||
self.marionette.set_pref('app.support.baseURL', support_url)
|
||||
self.marionette.set_pref('browser.privatebrowsing.searchUI', False)
|
||||
|
||||
self.pb_url = support_url + 'private-browsing'
|
||||
|
||||
|
@ -25,7 +25,8 @@ let RPMAccessManager = {
|
||||
// "sendAsyncMessage": handled within AboutPrivateBrowsingHandler.jsm
|
||||
// "setBoolPref": handled within AsyncPrefs.jsm and uses the pref
|
||||
// ["privacy.trackingprotection.pbmode.enabled"],
|
||||
"getBoolPref": ["privacy.trackingprotection.pbmode.enabled"],
|
||||
"getBoolPref": ["privacy.trackingprotection.pbmode.enabled",
|
||||
"browser.privatebrowsing.searchUI"],
|
||||
"getFormatURLPref": ["privacy.trackingprotection.introURL",
|
||||
"app.support.baseURL"],
|
||||
"isWindowPrivate": ["yes"],
|
||||
|
Loading…
Reference in New Issue
Block a user