зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1068284 - UI Tour: Add ability to highlight search provider in search menu. r=MattN
--HG-- extra : transplant_source : %AA%DA%ED%B6%24F%92%F0%D3k%F6%D34%2054%C4%23%D8%F0
This commit is contained in:
Родитель
ab7777e096
Коммит
1b1bee23f5
|
@ -11,6 +11,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
|
||||
"resource://gre/modules/LightweightThemeManager.jsm");
|
||||
|
@ -39,6 +40,9 @@ const BUCKET_TIMESTEPS = [
|
|||
// Time after which seen Page IDs expire.
|
||||
const SEENPAGEID_EXPIRY = 8 * 7 * 24 * 60 * 60 * 1000; // 8 weeks.
|
||||
|
||||
// Prefix for any target matching a search engine.
|
||||
const TARGET_SEARCHENGINE_PREFIX = "searchEngine-";
|
||||
|
||||
|
||||
this.UITour = {
|
||||
url: null,
|
||||
|
@ -375,7 +379,10 @@ this.UITour = {
|
|||
}
|
||||
|
||||
case "showMenu": {
|
||||
this.showMenu(window, data.name);
|
||||
this.showMenu(window, data.name, () => {
|
||||
if (typeof data.showCallbackID == "string")
|
||||
this.sendPageCallback(contentDocument, data.showCallbackID);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -685,6 +692,11 @@ this.UITour = {
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
if (aTargetName.startsWith(TARGET_SEARCHENGINE_PREFIX)) {
|
||||
let engineID = aTargetName.slice(TARGET_SEARCHENGINE_PREFIX.length);
|
||||
return this.getSearchEngineTarget(aWindow, engineID);
|
||||
}
|
||||
|
||||
let targetObject = this.targets.get(aTargetName);
|
||||
if (!targetObject) {
|
||||
deferred.reject("The specified target name is not in the allowed set");
|
||||
|
@ -817,8 +829,22 @@ this.UITour = {
|
|||
* @see UITour.highlightEffects
|
||||
*/
|
||||
showHighlight: function(aTarget, aEffect = "none") {
|
||||
function showHighlightPanel(aTargetEl) {
|
||||
let highlighter = aTargetEl.ownerDocument.getElementById("UITourHighlight");
|
||||
let window = aTarget.node.ownerDocument.defaultView;
|
||||
|
||||
function showHighlightPanel() {
|
||||
if (aTarget.targetName.startsWith(TARGET_SEARCHENGINE_PREFIX)) {
|
||||
// This won't affect normal higlights done via the panel, so we need to
|
||||
// manually hide those.
|
||||
this.hideHighlight(window);
|
||||
aTarget.node.setAttribute("_moz-menuactive", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Conversely, highlights for search engines are highlighted via CSS
|
||||
// rather than a panel, so need to be manually removed.
|
||||
this._hideSearchEngineHighlight(window);
|
||||
|
||||
let highlighter = aTarget.node.ownerDocument.getElementById("UITourHighlight");
|
||||
|
||||
let effect = aEffect;
|
||||
if (effect == "random") {
|
||||
|
@ -830,12 +856,12 @@ this.UITour = {
|
|||
}
|
||||
// Toggle the effect attribute to "none" and flush layout before setting it so the effect plays.
|
||||
highlighter.setAttribute("active", "none");
|
||||
aTargetEl.ownerDocument.defaultView.getComputedStyle(highlighter).animationName;
|
||||
aTarget.node.ownerDocument.defaultView.getComputedStyle(highlighter).animationName;
|
||||
highlighter.setAttribute("active", effect);
|
||||
highlighter.parentElement.setAttribute("targetName", aTarget.targetName);
|
||||
highlighter.parentElement.hidden = false;
|
||||
|
||||
let targetRect = aTargetEl.getBoundingClientRect();
|
||||
let targetRect = aTarget.node.getBoundingClientRect();
|
||||
let highlightHeight = targetRect.height;
|
||||
let highlightWidth = targetRect.width;
|
||||
let minDimension = Math.min(highlightHeight, highlightWidth);
|
||||
|
@ -859,7 +885,7 @@ this.UITour = {
|
|||
}
|
||||
/* The "overlap" position anchors from the top-left but we want to centre highlights at their
|
||||
minimum size. */
|
||||
let highlightWindow = aTargetEl.ownerDocument.defaultView;
|
||||
let highlightWindow = aTarget.node.ownerDocument.defaultView;
|
||||
let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
|
||||
let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
|
||||
let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
|
||||
|
@ -870,9 +896,8 @@ this.UITour = {
|
|||
- (Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
|
||||
let offsetY = paddingLeftPx
|
||||
- (Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
|
||||
|
||||
this._addAnnotationPanelMutationObserver(highlighter.parentElement);
|
||||
highlighter.parentElement.openPopup(aTargetEl, "overlap", offsetX, offsetY);
|
||||
highlighter.parentElement.openPopup(aTarget.node, "overlap", offsetX, offsetY);
|
||||
}
|
||||
|
||||
// Prevent showing a panel at an undefined position.
|
||||
|
@ -881,7 +906,7 @@ this.UITour = {
|
|||
|
||||
this._setAppMenuStateForAnnotation(aTarget.node.ownerDocument.defaultView, "highlight",
|
||||
this.targetIsInAppMenu(aTarget),
|
||||
showHighlightPanel.bind(this, aTarget.node));
|
||||
showHighlightPanel.bind(this));
|
||||
},
|
||||
|
||||
hideHighlight: function(aWindow) {
|
||||
|
@ -895,6 +920,24 @@ this.UITour = {
|
|||
highlighter.removeAttribute("active");
|
||||
|
||||
this._setAppMenuStateForAnnotation(aWindow, "highlight", false);
|
||||
this._hideSearchEngineHighlight(aWindow);
|
||||
},
|
||||
|
||||
_hideSearchEngineHighlight: function(aWindow) {
|
||||
// We special case highlighting items in the search engines dropdown,
|
||||
// so just blindly remove any highlight there.
|
||||
let searchMenuBtn = null;
|
||||
try {
|
||||
searchMenuBtn = this.targets.get("searchProvider").query(aWindow.document);
|
||||
} catch (e) { /* This is ok to fail. */ }
|
||||
if (searchMenuBtn) {
|
||||
let searchPopup = aWindow.document
|
||||
.getAnonymousElementByAttribute(searchMenuBtn,
|
||||
"anonid",
|
||||
"searchbar-popup");
|
||||
for (let menuItem of searchPopup.children)
|
||||
menuItem.removeAttribute("_moz-menuactive");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -994,6 +1037,11 @@ this.UITour = {
|
|||
if (!this.isElementVisible(aAnchor.node))
|
||||
return;
|
||||
|
||||
// Due to a platform limitation, we can't anchor a panel to an element in a
|
||||
// <menupopup>. So we can't support showing info panels for search engines.
|
||||
if (aAnchor.targetName.startsWith(TARGET_SEARCHENGINE_PREFIX))
|
||||
return;
|
||||
|
||||
this._setAppMenuStateForAnnotation(aAnchor.node.ownerDocument.defaultView, "info",
|
||||
this.targetIsInAppMenu(aAnchor),
|
||||
showInfoPanel.bind(this, aAnchor.node));
|
||||
|
@ -1013,15 +1061,15 @@ this.UITour = {
|
|||
},
|
||||
|
||||
showMenu: function(aWindow, aMenuName, aOpenCallback = null) {
|
||||
function openMenuButton(aID) {
|
||||
let menuBtn = aWindow.document.getElementById(aID);
|
||||
if (!menuBtn || !menuBtn.boxObject) {
|
||||
aOpenCallback();
|
||||
function openMenuButton(aMenuBtn) {
|
||||
if (!aMenuBtn || !aMenuBtn.boxObject || aMenuBtn.open) {
|
||||
if (aOpenCallback)
|
||||
aOpenCallback();
|
||||
return;
|
||||
}
|
||||
if (aOpenCallback)
|
||||
menuBtn.addEventListener("popupshown", onPopupShown);
|
||||
menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(true);
|
||||
aMenuBtn.addEventListener("popupshown", onPopupShown);
|
||||
aMenuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(true);
|
||||
}
|
||||
function onPopupShown(event) {
|
||||
this.removeEventListener("popupshown", onPopupShown);
|
||||
|
@ -1041,15 +1089,19 @@ this.UITour = {
|
|||
}
|
||||
aWindow.PanelUI.show();
|
||||
} else if (aMenuName == "bookmarks") {
|
||||
openMenuButton("bookmarks-menu-button");
|
||||
let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
|
||||
openMenuButton(menuBtn);
|
||||
} else if (aMenuName == "searchEngines") {
|
||||
this.getTarget(aWindow, "searchProvider").then(target => {
|
||||
openMenuButton(target.node);
|
||||
}).catch(Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
hideMenu: function(aWindow, aMenuName) {
|
||||
function closeMenuButton(aID) {
|
||||
let menuBtn = aWindow.document.getElementById(aID);
|
||||
if (menuBtn && menuBtn.boxObject)
|
||||
menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(false);
|
||||
function closeMenuButton(aMenuBtn) {
|
||||
if (aMenuBtn && aMenuBtn.boxObject)
|
||||
aMenuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(false);
|
||||
}
|
||||
|
||||
if (aMenuName == "appMenu") {
|
||||
|
@ -1057,7 +1109,11 @@ this.UITour = {
|
|||
aWindow.PanelUI.hide();
|
||||
this.recreatePopup(aWindow.PanelUI.panel);
|
||||
} else if (aMenuName == "bookmarks") {
|
||||
closeMenuButton("bookmarks-menu-button");
|
||||
let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
|
||||
closeMenuButton(menuBtn);
|
||||
} else if (aMenuName == "searchEngines") {
|
||||
let menuBtn = this.targets.get("searchProvider").query(aWindow.document);
|
||||
closeMenuButton(menuBtn);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1158,31 +1214,39 @@ this.UITour = {
|
|||
},
|
||||
|
||||
getAvailableTargets: function(aContentDocument, aCallbackID) {
|
||||
let window = this.getChromeWindow(aContentDocument);
|
||||
let data = this.availableTargetsCache.get(window);
|
||||
if (data) {
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, data);
|
||||
return;
|
||||
}
|
||||
Task.spawn(function*() {
|
||||
let window = this.getChromeWindow(aContentDocument);
|
||||
let data = this.availableTargetsCache.get(window);
|
||||
if (data) {
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, data);
|
||||
return;
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let targetName of this.targets.keys()) {
|
||||
promises.push(this.getTarget(window, targetName));
|
||||
}
|
||||
let targetObjects = yield Promise.all(promises);
|
||||
|
||||
let promises = [];
|
||||
for (let targetName of this.targets.keys()) {
|
||||
promises.push(this.getTarget(window, targetName));
|
||||
}
|
||||
Promise.all(promises).then((targetObjects) => {
|
||||
let targetNames = [
|
||||
"pinnedTab",
|
||||
];
|
||||
|
||||
for (let targetObject of targetObjects) {
|
||||
if (targetObject.node)
|
||||
targetNames.push(targetObject.targetName);
|
||||
}
|
||||
let data = {
|
||||
|
||||
targetNames = targetNames.concat(
|
||||
yield this.getAvailableSearchEngineTargets(window)
|
||||
);
|
||||
|
||||
data = {
|
||||
targets: targetNames,
|
||||
};
|
||||
this.availableTargetsCache.set(window, data);
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, data);
|
||||
}, (err) => {
|
||||
}.bind(this)).catch(err => {
|
||||
Cu.reportError(err);
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, {
|
||||
targets: [],
|
||||
|
@ -1248,6 +1312,55 @@ this.UITour = {
|
|||
return;
|
||||
}
|
||||
},
|
||||
|
||||
getAvailableSearchEngineTargets(aWindow) {
|
||||
return new Promise(resolve => {
|
||||
this.getTarget(aWindow, "search").then(searchTarget => {
|
||||
if (!searchTarget.node || this.targetIsInAppMenu(searchTarget))
|
||||
return resolve([]);
|
||||
|
||||
Services.search.init(() => {
|
||||
let engines = Services.search.getVisibleEngines();
|
||||
resolve([TARGET_SEARCHENGINE_PREFIX + engine.identifier
|
||||
for (engine of engines)
|
||||
if (engine.identifier)]);
|
||||
});
|
||||
}).catch(() => resolve([]));
|
||||
});
|
||||
},
|
||||
|
||||
// We only allow matching based on a search engine's identifier - this gives
|
||||
// us a non-changing ID and guarentees we only match against app-provided
|
||||
// engines.
|
||||
getSearchEngineTarget(aWindow, aIdentifier) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Task.spawn(function*() {
|
||||
let searchTarget = yield this.getTarget(aWindow, "search");
|
||||
// We're not supporting having the searchbar in the app-menu, because
|
||||
// popups within popups gets crazy. This restriction should be lifted
|
||||
// once bug 988151 is implemented, as the page can then be responsible
|
||||
// for opening each menu when appropriate.
|
||||
if (!searchTarget.node || this.targetIsInAppMenu(searchTarget))
|
||||
return reject("Search engine not available");
|
||||
|
||||
yield Services.search.init();
|
||||
|
||||
let searchPopup = searchTarget.node._popup;
|
||||
for (let engineNode of searchPopup.children) {
|
||||
let engine = engineNode.engine;
|
||||
if (engine && engine.identifier == aIdentifier) {
|
||||
return resolve({
|
||||
targetName: TARGET_SEARCHENGINE_PREFIX + engine.identifier,
|
||||
node: engineNode,
|
||||
});
|
||||
}
|
||||
}
|
||||
reject("Search engine not available");
|
||||
}.bind(this)).catch(() => {
|
||||
reject("Search engine not available");
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.UITour.init();
|
||||
|
|
|
@ -195,6 +195,53 @@ let tests = [
|
|||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, checkDefaultEffect, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_search_engine(done) {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, () => {
|
||||
|
||||
gContentAPI.showMenu("searchEngines", function() {
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
isnot(searchbar, null, "Should have found searchbar");
|
||||
let searchPopup = document.getAnonymousElementByAttribute(searchbar,
|
||||
"anonid",
|
||||
"searchbar-popup");
|
||||
isnot(searchPopup, null, "Should have found search popup");
|
||||
|
||||
function getEngineNode(identifier) {
|
||||
let engineNode = null;
|
||||
for (let node of searchPopup.children) {
|
||||
if (node.engine.identifier == identifier) {
|
||||
engineNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
isnot(engineNode, null, "Should have found search engine node in popup");
|
||||
return engineNode;
|
||||
}
|
||||
let googleEngineNode = getEngineNode("google");
|
||||
let bingEngineNode = getEngineNode("bing");
|
||||
|
||||
gContentAPI.showHighlight("searchEngine-google");
|
||||
waitForCondition(() => googleEngineNode.getAttribute("_moz-menuactive") == "true", function() {
|
||||
is_element_hidden(highlight, "Highlight panel should be hidden by highlighting search engine");
|
||||
|
||||
gContentAPI.showHighlight("searchEngine-bing");
|
||||
waitForCondition(() => bingEngineNode.getAttribute("_moz-menuactive") == "true", function() {
|
||||
isnot(googleEngineNode.getAttribute("_moz-menuactive"), "true", "Previous engine should no longer be highlighted");
|
||||
|
||||
gContentAPI.hideHighlight();
|
||||
waitForCondition(() => bingEngineNode.getAttribute("_moz-menuactive") != "true", function() {
|
||||
gContentAPI.hideMenu("searchEngines");
|
||||
waitForCondition(() => searchPopup.state == "closed", function() {
|
||||
done();
|
||||
}, "Search dropdown should close");
|
||||
}, "Menu item should get attribute removed");
|
||||
}, "Menu item should get attribute to make it look active");
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function test_highlight_effect_unsupported(done) {
|
||||
function checkUnsupportedEffect() {
|
||||
is(highlight.getAttribute("active"), "none", "No effect should be used when an unsupported effect is requested");
|
||||
|
|
|
@ -14,6 +14,13 @@ function test() {
|
|||
UITourTest();
|
||||
}
|
||||
|
||||
function searchEngineTargets() {
|
||||
let engines = Services.search.getVisibleEngines();
|
||||
return ["searchEngine-" + engine.identifier
|
||||
for (engine of engines)
|
||||
if (engine.identifier)];
|
||||
}
|
||||
|
||||
let tests = [
|
||||
function test_availableTargets(done) {
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
|
@ -33,7 +40,7 @@ let tests = [
|
|||
"search",
|
||||
"searchProvider",
|
||||
"urlbar",
|
||||
]);
|
||||
].concat(searchEngineTargets()));
|
||||
ok(UITour.availableTargetsCache.has(window),
|
||||
"Targets should now be cached");
|
||||
done();
|
||||
|
@ -60,7 +67,7 @@ let tests = [
|
|||
"search",
|
||||
"searchProvider",
|
||||
"urlbar",
|
||||
]);
|
||||
].concat(searchEngineTargets()));
|
||||
ok(UITour.availableTargetsCache.has(window),
|
||||
"Targets should now be cached again");
|
||||
CustomizableUI.reset();
|
||||
|
|
|
@ -2,15 +2,13 @@
|
|||
* 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/. */
|
||||
|
||||
// Copied from the proposed JS library for Bedrock (ie, www.mozilla.org).
|
||||
|
||||
// create namespace
|
||||
if (typeof Mozilla == 'undefined') {
|
||||
var Mozilla = {};
|
||||
}
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
;(function($) {
|
||||
'use strict';
|
||||
|
||||
// create namespace
|
||||
if (typeof Mozilla.UITour == 'undefined') {
|
||||
|
@ -60,6 +58,9 @@ if (typeof Mozilla == 'undefined') {
|
|||
|
||||
Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY = 10 * 1000;
|
||||
|
||||
Mozilla.UITour.CONFIGNAME_SYNC = "sync";
|
||||
Mozilla.UITour.CONFIGNAME_AVAILABLETARGETS = "availableTargets";
|
||||
|
||||
Mozilla.UITour.registerPageID = function(pageID) {
|
||||
_sendEvent('registerPageID', {
|
||||
pageID: pageID
|
||||
|
@ -86,7 +87,7 @@ if (typeof Mozilla == 'undefined') {
|
|||
icon: buttons[i].icon,
|
||||
style: buttons[i].style,
|
||||
callbackID: _waitForCallback(buttons[i].callback)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,9 +157,14 @@ if (typeof Mozilla == 'undefined') {
|
|||
_sendEvent('removePinnedTab');
|
||||
};
|
||||
|
||||
Mozilla.UITour.showMenu = function(name) {
|
||||
Mozilla.UITour.showMenu = function(name, callback) {
|
||||
var showCallbackID;
|
||||
if (callback)
|
||||
showCallbackID = _waitForCallback(callback);
|
||||
|
||||
_sendEvent('showMenu', {
|
||||
name: name
|
||||
name: name,
|
||||
showCallbackID: showCallbackID,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -168,6 +174,17 @@ if (typeof Mozilla == 'undefined') {
|
|||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.startUrlbarCapture = function(text, url) {
|
||||
_sendEvent('startUrlbarCapture', {
|
||||
text: text,
|
||||
url: url
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.endUrlbarCapture = function() {
|
||||
_sendEvent('endUrlbarCapture');
|
||||
};
|
||||
|
||||
Mozilla.UITour.getConfiguration = function(configName, callback) {
|
||||
_sendEvent('getConfiguration', {
|
||||
callbackID: _waitForCallback(callback),
|
||||
|
|
Загрузка…
Ссылка в новой задаче