Bug 935815 - [Australis] UITour: Allow adding a button with an action to the info panel. r=MattN

This commit is contained in:
Blair McBride 2013-12-19 12:36:29 +13:00
parent 892809e3fa
commit cd86b382d5
8 changed files with 307 additions and 20 deletions

View File

@ -202,8 +202,16 @@
align="start"
orient="vertical"
role="alert">
<label id="UITourTooltipTitle" flex="1"/>
<description id="UITourTooltipDescription" flex="1"/>
<hbox>
<vbox>
<image id="UITourTooltipIcon"/>
</vbox>
<vbox flex="1">
<label id="UITourTooltipTitle" flex="1"/>
<description id="UITourTooltipDescription" flex="1"/>
<hbox id="UITourTooltipButtons" flex="1" align="end"/>
</vbox>
</hbox>
</panel>
<panel id="UITourHighlightContainer"
hidden="true"

View File

@ -22,6 +22,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
const UITOUR_PERMISSION = "uitour";
const PREF_PERM_BRANCH = "browser.uitour.";
const MAX_BUTTONS = 4;
this.UITour = {
@ -125,7 +126,34 @@ this.UITour = {
Cu.reportError("UITour: Target could not be resolved: " + data.target);
return;
}
this.showInfo(target, data.title, data.text);
let iconURL = null;
if (typeof data.icon == "string")
iconURL = this.resolveURL(contentDocument, data.icon);
let buttons = [];
if (Array.isArray(data.buttons) && data.buttons.length > 0) {
for (let buttonData of data.buttons) {
if (typeof buttonData == "object" &&
typeof buttonData.label == "string" &&
typeof buttonData.callbackID == "string") {
let button = {
label: buttonData.label,
callbackID: buttonData.callbackID,
};
if (typeof buttonData.icon == "string")
button.iconURL = this.resolveURL(contentDocument, buttonData.icon);
buttons.push(button);
if (buttons.length == MAX_BUTTONS)
break;
}
}
}
this.showInfo(contentDocument, target, data.title, data.text, iconURL, buttons);
}).then(null, Cu.reportError);
break;
}
@ -303,11 +331,7 @@ this.UITour = {
if (uri.schemeIs("chrome"))
return true;
let allowedSchemes = new Set(["https"]);
if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
allowedSchemes.add("http");
if (!allowedSchemes.has(uri.scheme))
if (!this.isSafeScheme(uri))
return false;
this.importPermissions();
@ -315,6 +339,50 @@ this.UITour = {
return permission == Services.perms.ALLOW_ACTION;
},
isSafeScheme: function(aURI) {
let allowedSchemes = new Set(["https"]);
if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
allowedSchemes.add("http");
if (!allowedSchemes.has(aURI.scheme))
return false;
return true;
},
resolveURL: function(aDocument, aURL) {
try {
let uri = Services.io.newURI(aURL, null, aDocument.documentURIObject);
if (!this.isSafeScheme(uri))
return null;
return uri.spec;
} catch (e) {}
return null;
},
sendPageCallback: function(aDocument, aCallbackID, aData = {}) {
let detail = Cu.createObjectIn(aDocument.defaultView);
detail.data = Cu.createObjectIn(detail);
for (let key of Object.keys(aData))
detail.data[key] = aData[key];
Cu.makeObjectPropsNormal(detail.data);
Cu.makeObjectPropsNormal(detail);
detail.callbackID = aCallbackID;
let event = new aDocument.defaultView.CustomEvent("mozUITourResponse", {
bubbles: true,
detail: detail
});
aDocument.dispatchEvent(event);
},
getTarget: function(aWindow, aTargetName, aSticky = false) {
let deferred = Promise.defer();
if (typeof aTargetName != "string" || !aTargetName) {
@ -506,7 +574,7 @@ this.UITour = {
this._setAppMenuStateForAnnotation(aWindow, "highlight", false);
},
showInfo: function(aAnchor, aTitle, aDescription) {
showInfo: function(aContentDocument, aAnchor, aTitle = "", aDescription = "", aIconURL = "", aButtons = []) {
function showInfoPanel(aAnchorEl) {
aAnchorEl.focus();
@ -514,6 +582,8 @@ this.UITour = {
let tooltip = document.getElementById("UITourTooltip");
let tooltipTitle = document.getElementById("UITourTooltipTitle");
let tooltipDesc = document.getElementById("UITourTooltipDescription");
let tooltipIcon = document.getElementById("UITourTooltipIcon");
let tooltipButtons = document.getElementById("UITourTooltipButtons");
if (tooltip.state == "open") {
tooltip.hidePopup();
@ -521,6 +591,28 @@ this.UITour = {
tooltipTitle.textContent = aTitle;
tooltipDesc.textContent = aDescription;
tooltipIcon.src = aIconURL;
tooltipIcon.hidden = !aIconURL;
while (tooltipButtons.firstChild)
tooltipButtons.firstChild.remove();
for (let button of aButtons) {
let el = document.createElement("button");
el.setAttribute("label", button.label);
if (button.iconURL)
el.setAttribute("image", button.iconURL);
let callbackID = button.callbackID;
el.addEventListener("command", event => {
tooltip.hidePopup();
this.sendPageCallback(aContentDocument, callbackID);
});
tooltipButtons.appendChild(el);
}
tooltipButtons.hidden = !aButtons.length;
tooltip.hidden = false;
let alignment = "bottomcenter topright";
@ -533,9 +625,15 @@ this.UITour = {
},
hideInfo: function(aWindow) {
let tooltip = aWindow.document.getElementById("UITourTooltip");
let document = aWindow.document;
let tooltip = document.getElementById("UITourTooltip");
tooltip.hidePopup();
this._setAppMenuStateForAnnotation(aWindow, "info", false);
let tooltipButtons = document.getElementById("UITourTooltipButtons");
while (tooltipButtons.firstChild)
tooltipButtons.firstChild.remove();
},
showMenu: function(aWindow, aMenuName, aOpenCallback = null) {

View File

@ -5,6 +5,6 @@ support-files =
[browser_NetworkPrioritizer.js]
[browser_SignInToWebsite.js]
[browser_UITour.js]
support-files = uitour.*
support-files = uitour.* image.png
[browser_taskbar_preview.js]
run-if = os == "win"

View File

@ -4,6 +4,7 @@
"use strict";
let gTestTab;
let gContentWindow;
let gContentAPI;
Components.utils.import("resource:///modules/UITour.jsm");
@ -66,10 +67,10 @@ function loadTestPage(callback, host = "https://example.com/") {
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
let contentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
gContentAPI = contentWindow.Mozilla.UITour;
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
gContentAPI = gContentWindow.Mozilla.UITour;
waitForFocus(callback, contentWindow);
waitForFocus(callback, gContentWindow);
}, true);
}
@ -82,6 +83,7 @@ function test() {
registerCleanupFunction(function() {
delete window.UITour;
delete window.gContentWindow;
delete window.gContentAPI;
if (gTestTab)
gBrowser.removeTab(gTestTab);
@ -285,11 +287,16 @@ let tests = [
let popup = document.getElementById("UITourTooltip");
let title = document.getElementById("UITourTooltipTitle");
let desc = document.getElementById("UITourTooltipDescription");
let icon = document.getElementById("UITourTooltipIcon");
let buttons = document.getElementById("UITourTooltipButtons");
popup.addEventListener("popupshown", function onPopupShown() {
popup.removeEventListener("popupshown", onPopupShown);
is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
is(title.textContent, "test title", "Popup should have correct title");
is(desc.textContent, "test text", "Popup should have correct description text");
is(icon.src, "", "Popup should have no icon");
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
popup.addEventListener("popuphidden", function onPopupHidden() {
popup.removeEventListener("popuphidden", onPopupHidden);
@ -311,11 +318,16 @@ let tests = [
let popup = document.getElementById("UITourTooltip");
let title = document.getElementById("UITourTooltipTitle");
let desc = document.getElementById("UITourTooltipDescription");
let icon = document.getElementById("UITourTooltipIcon");
let buttons = document.getElementById("UITourTooltipButtons");
popup.addEventListener("popupshown", function onPopupShown() {
popup.removeEventListener("popupshown", onPopupShown);
is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
is(title.textContent, "urlbar title", "Popup should have correct title");
is(desc.textContent, "urlbar text", "Popup should have correct description text");
is(icon.src, "", "Popup should have no icon");
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
gContentAPI.showInfo("search", "search title", "search text");
executeSoon(function() {
@ -377,6 +389,114 @@ let tests = [
}, "Info should be shown after showInfo() for fixed menu panel items");
});
},
function test_info_icon(done) {
let popup = document.getElementById("UITourTooltip");
let title = document.getElementById("UITourTooltipTitle");
let desc = document.getElementById("UITourTooltipDescription");
let icon = document.getElementById("UITourTooltipIcon");
let buttons = document.getElementById("UITourTooltipButtons");
popup.addEventListener("popupshown", function onPopupShown() {
popup.removeEventListener("popupshown", onPopupShown);
is(title.textContent, "a title", "Popup should have correct title");
is(desc.textContent, "some text", "Popup should have correct description text");
let imageURL = getRootDirectory(gTestPath) + "image.png";
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
is(icon.src, imageURL, "Popup should have correct icon shown");
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
done();
});
gContentAPI.showInfo("urlbar", "a title", "some text", "image.png");
},
function test_info_buttons_1(done) {
let popup = document.getElementById("UITourTooltip");
let title = document.getElementById("UITourTooltipTitle");
let desc = document.getElementById("UITourTooltipDescription");
let icon = document.getElementById("UITourTooltipIcon");
popup.addEventListener("popupshown", function onPopupShown() {
popup.removeEventListener("popupshown", onPopupShown);
is(title.textContent, "another title", "Popup should have correct title");
is(desc.textContent, "moar text", "Popup should have correct description text");
let imageURL = getRootDirectory(gTestPath) + "image.png";
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
is(icon.src, imageURL, "Popup should have correct icon shown");
let buttons = document.getElementById("UITourTooltipButtons");
is(buttons.childElementCount, 2, "Popup should have two buttons");
is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
popup.addEventListener("popuphidden", function onPopupHidden() {
popup.removeEventListener("popuphidden", onPopupHidden);
ok(true, "Popup should close automatically");
executeSoon(function() {
is(gContentWindow.callbackResult, "button1", "Correct callback should have been called");
done();
});
});
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[0], {}, window);
});
let buttons = gContentWindow.makeButtons();
gContentAPI.showInfo("urlbar", "another title", "moar text", "./image.png", buttons);
},
function test_info_buttons_2(done) {
let popup = document.getElementById("UITourTooltip");
let title = document.getElementById("UITourTooltipTitle");
let desc = document.getElementById("UITourTooltipDescription");
let icon = document.getElementById("UITourTooltipIcon");
popup.addEventListener("popupshown", function onPopupShown() {
popup.removeEventListener("popupshown", onPopupShown);
is(title.textContent, "another title", "Popup should have correct title");
is(desc.textContent, "moar text", "Popup should have correct description text");
let imageURL = getRootDirectory(gTestPath) + "image.png";
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
is(icon.src, imageURL, "Popup should have correct icon shown");
let buttons = document.getElementById("UITourTooltipButtons");
is(buttons.childElementCount, 2, "Popup should have two buttons");
is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
popup.addEventListener("popuphidden", function onPopupHidden() {
popup.removeEventListener("popuphidden", onPopupHidden);
ok(true, "Popup should close automatically");
executeSoon(function() {
is(gContentWindow.callbackResult, "button2", "Correct callback should have been called");
done();
});
});
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[1], {}, window);
});
let buttons = gContentWindow.makeButtons();
gContentAPI.showInfo("urlbar", "another title", "moar text", "./image.png", buttons);
},
function test_pinnedTab(done) {
is(UITour.pinnedTabs.get(window), null, "Should not already have a pinned tab");

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -5,6 +5,22 @@
<title>UITour test</title>
<script type="application/javascript" src="uitour.js">
</script>
<script type="application/javascript">
var callbackResult;
function makeCallback(name) {
return (function() {
callbackResult = name;
});
}
// Defined in content to avoid weird issues when crossing between chrome/content.
function makeButtons() {
return [
{label: "Button 1", callback: makeCallback("button1")},
{label: "Button 2", callback: makeCallback("button2"), icon: "image.png"}
];
}
</script>
</head>
<body>
<h1>UITour tests</h1>

View File

@ -25,7 +25,6 @@ if (typeof Mozilla == 'undefined') {
}
}
function _sendEvent(action, data) {
var event = new CustomEvent('mozUITour', {
bubbles: true,
@ -34,10 +33,31 @@ if (typeof Mozilla == 'undefined') {
data: data || {}
}
});
console.log("Sending mozUITour event: ", event);
document.dispatchEvent(event);
}
function _generateCallbackID() {
return Math.random().toString(36).replace(/[^a-z]+/g, '');
}
function _waitForCallback(callback) {
var id = _generateCallbackID();
function listener(event) {
if (typeof event.detail != "object")
return;
if (event.detail.callbackID != id)
return;
document.removeEventListener("mozUITourResponse", listener);
callback(event.detail.data);
}
document.addEventListener("mozUITourResponse", listener);
return id;
}
Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY = 10 * 1000;
Mozilla.UITour.showHighlight = function(target, effect) {
@ -51,11 +71,24 @@ if (typeof Mozilla == 'undefined') {
_sendEvent('hideHighlight');
};
Mozilla.UITour.showInfo = function(target, title, text) {
Mozilla.UITour.showInfo = function(target, title, text, icon, buttons) {
var buttonData = [];
if (Array.isArray(buttons)) {
for (var i = 0; i < buttons.length; i++) {
buttonData.push({
label: buttons[i].label,
icon: buttons[i].icon,
callbackID: _waitForCallback(buttons[i].callback)
});
}
}
_sendEvent('showInfo', {
target: target,
title: title,
text: text
text: text,
icon: icon,
buttons: buttonData
});
};

View File

@ -22,8 +22,10 @@
min-width: 32px;
}
#UITourTooltip {
max-width: 20em;
#UITourTooltipIcon {
width: 48px;
height: 48px;
padding: 8px;
}
#UITourTooltipTitle {
@ -35,3 +37,13 @@
#UITourTooltipDescription {
max-width: 20em;
}
#UITourTooltipButtons {
height: 5em;
}
#UITourTooltipButtons > button[image] > .button-box > .button-icon {
width: 16px;
height: 16px;
-moz-margin-end: 5px;
}