merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2014-12-05 13:02:46 +01:00
commit ba58d43aea
77 changed files with 728 additions and 193 deletions

View File

@ -81,16 +81,14 @@ a {
-moz-margin-end: 5px;
height: 38px;
width: 38px;
background-image: url("chrome://browser/skin/magnifier.png");
background: url("chrome://browser/skin/magnifier.png") center center no-repeat;
background-size: 26px;
background-position: center center;
background-repeat: no-repeat;
}
#searchIcon[active],
#searchIcon:hover {
background-color: #e9e9e9;
border: 1px solid rgb(226, 227, 229);
border-color: rgb(226, 227, 229);
border-radius: 2.5px;
}

View File

@ -17,6 +17,22 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel
return this.toolbarButton = CustomizableUI.getWidget("loop-button").forWindow(window);
},
/**
* @return {Promise}
*/
promiseDocumentVisible(aDocument) {
if (!aDocument.hidden) {
return Promise.resolve();
}
return new Promise((resolve) => {
aDocument.addEventListener("visibilitychange", function onVisibilityChanged() {
aDocument.removeEventListener("visibilitychange", onVisibilityChanged);
resolve();
});
});
},
/**
* Opens the panel for Loop and sizes it appropriately.
*
@ -32,7 +48,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel
// Helper function to show a specific tab view in the panel.
function showTab() {
if (!tabId) {
resolve();
resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
return;
}
@ -44,7 +60,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel
}
}, win));
win.dispatchEvent(ev);
resolve();
resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
}
// If the panel has been opened and initialized before, we can skip waiting

View File

@ -12,6 +12,7 @@ let UITourListener = {
return;
}
addMessageListener("UITour:SendPageCallback", this);
addMessageListener("UITour:SendPageNotification", this);
sendAsyncMessage("UITour:onPageEvent", {detail: event.detail, type: event.type});
},
@ -68,14 +69,18 @@ let UITourListener = {
receiveMessage: function(aMessage) {
switch (aMessage.name) {
case "UITour:SendPageCallback":
this.sendPageCallback(aMessage.data);
this.sendPageEvent("Response", aMessage.data);
break;
}
case "UITour:SendPageNotification":
this.sendPageEvent("Notification", aMessage.data);
break;
}
},
sendPageCallback: function (detail) {
sendPageEvent: function (type, detail) {
let doc = content.document;
let event = new doc.defaultView.CustomEvent("mozUITourResponse", {
let eventName = "mozUITour" + type;
let event = new doc.defaultView.CustomEvent(eventName, {
bubbles: true,
detail: Cu.cloneInto(detail, doc.defaultView)
});

View File

@ -126,6 +126,8 @@ skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
[browser_autocomplete_autoselect.js]
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
[browser_autocomplete_oldschool_wrap.js]
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
[browser_backButtonFitts.js]
skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug 1099154: test touches content (attempts to add an event listener directly to the contentWindow)
[browser_blob-channelname.js]

View File

@ -0,0 +1,74 @@
function repeat(limit, func) {
for (let i = 0; i < limit; i++) {
func(i);
}
}
function* promiseAutoComplete(inputText) {
gURLBar.focus();
gURLBar.value = inputText.slice(0, -1);
EventUtils.synthesizeKey(inputText.slice(-1), {});
yield promiseSearchComplete();
}
function is_selected(index) {
is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
}
add_task(function*() {
// This test is only relevant if UnifiedComplete is *disabled*.
if (Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) {
todo(false, "Stop supporting old autocomplete components.");
return;
}
registerCleanupFunction(promiseClearHistory);
let visits = [];
repeat(10, i => {
visits.push({
uri: makeURI("http://example.com/autocomplete/?" + i),
});
});
yield PlacesTestUtils.addVisits(visits);
yield promiseAutoComplete("example.com/autocomplete");
let popup = gURLBar.popup;
let results = popup.richlistbox.children;
is(results.length, 10, "Should get 11 results");
is_selected(-1);
info("Key Down to select the next item");
EventUtils.synthesizeKey("VK_DOWN", {});
is_selected(0);
info("Key Up to select the previous item");
EventUtils.synthesizeKey("VK_UP", {});
is_selected(-1);
info("Key Down to select the next item");
EventUtils.synthesizeKey("VK_DOWN", {});
is_selected(0);
info("Key Down 11 times should wrap around all the way around");
repeat(11, () => EventUtils.synthesizeKey("VK_DOWN", {}));
is_selected(0);
info("Key Up 11 times should wrap around the other way");
repeat(11, () => EventUtils.synthesizeKey("VK_UP", {}));
is_selected(0);
info("Page Up will go up the list, but not wrap");
repeat(4, () => EventUtils.synthesizeKey("VK_DOWN", {}));
is_selected(4);
EventUtils.synthesizeKey("VK_PAGE_UP", {})
is_selected(0);
info("Page Up again will wrap around to the end of the list");
EventUtils.synthesizeKey("VK_PAGE_UP", {})
is_selected(-1);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield promisePopupHidden(gURLBar.popup);
});

View File

@ -1,33 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let originalTab;
let newTab;
// gBrowser.selectedTab.lastAccessed and Date.now() called from this test can't
// run concurrently, and therefore don't always match exactly.
const CURRENT_TIME_TOLERANCE_MS = 15;
function isCurrent(tab, msg) {
const tolerance = 5;
const difference = Math.abs(Date.now() - tab.lastAccessed);
ok(difference <= tolerance, msg + " (difference: " + difference + ")");
const DIFF = Math.abs(Date.now() - tab.lastAccessed);
ok(DIFF <= CURRENT_TIME_TOLERANCE_MS, msg + " (difference: " + DIFF + ")");
}
function nextStep(fn) {
setTimeout(fn, CURRENT_TIME_TOLERANCE_MS + 10);
}
let originalTab;
let newTab;
function test() {
waitForExplicitFinish();
originalTab = gBrowser.selectedTab;
setTimeout(step2, 10);
nextStep(step2);
}
function step2() {
isCurrent(originalTab, "selected tab has the current timestamp");
newTab = gBrowser.addTab("about:blank", {skipAnimation: true});
setTimeout(step3, 10);
nextStep(step3);
}
function step3() {
ok(newTab.lastAccessed < Date.now(), "new tab hasn't been selected so far");
gBrowser.selectedTab = newTab;
isCurrent(newTab, "new tab has the current timestamp after being selected");
setTimeout(step4, 10);
nextStep(step4);
}
function step4() {

View File

@ -1224,8 +1224,9 @@
createBundle("chrome://browser/locale/places/places.properties");
</field>
<!-- Override this so that navigating between items results in an item
always being selected. This is contrary to the normal behaviour where
<!-- Override this so that when UnifiedComplete is enabled, navigating
between items results in an item always being selected. This is
contrary to the old behaviour (UnifiedComplete disabled) where
if you navigate beyond either end of the list, no item will be
selected. -->
<method name="getNextIndex">
@ -1239,10 +1240,23 @@
let newIndex = index + (reverse ? -1 : 1) * amount;
// We don't want to wrap if navigation in any direction by one item.
// Otherwise we clamp to one end of the list.
// We only want to wrap if navigation is in any direction by one item,
// otherwise we clamp to one end of the list.
// ie, hitting page-down will only cause is to wrap if we're already
// at one end of the list.
if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) {
if (reverse && index == -1 || newIndex > maxRow && index != maxRow)
newIndex = maxRow;
else if (!reverse && index == -1 || newIndex < 0 && index != 0)
newIndex = 0;
if (newIndex < 0 && index == 0 || newIndex > maxRow && index == maxRow)
newIndex = -1;
return newIndex;
}
if (newIndex < 0) {
newIndex = index > 0 ? 0 : maxRow;
} else if (newIndex > maxRow) {

View File

@ -345,8 +345,8 @@ let LoopRoomsInternal = {
MozLoopService.hawkRequest(this.sessionType, url, "DELETE")
.then(response => {
this.rooms.delete(roomToken);
eventEmitter.emit("delete", room);
callback(null, room);
// We'll emit an update when the push notification is received.
}, error => callback(error)).catch(error => callback(error));
},

View File

@ -752,7 +752,7 @@ loop.panel = (function(_, mozL10n) {
}, this)
),
React.DOM.p(null,
React.DOM.button({className: "btn btn-info",
React.DOM.button({className: "btn btn-info new-room-button",
onClick: this.handleCreateButtonClick,
disabled: this._hasPendingOperation()},
mozL10n.get("rooms_new_room_button_label")

View File

@ -752,7 +752,7 @@ loop.panel = (function(_, mozL10n) {
}, this)
}</div>
<p>
<button className="btn btn-info"
<button className="btn btn-info new-room-button"
onClick={this.handleCreateButtonClick}
disabled={this._hasPendingOperation()}>
{mozL10n.get("rooms_new_room_button_label")}

View File

@ -41,7 +41,9 @@ loop.store.ActiveRoomStore = (function() {
// The room is full
FULL: "room-full",
// The room conversation has ended
ENDED: "room-ended"
ENDED: "room-ended",
// The window is closing
CLOSING: "room-closing"
};
/**
@ -386,7 +388,7 @@ loop.store.ActiveRoomStore = (function() {
* Handles the window being unloaded. Ensures the room is left.
*/
windowUnload: function() {
this._leaveRoom();
this._leaveRoom(ROOM_STATES.CLOSING);
// If we're closing the window, we can stop listening to updates.
this._mozLoop.rooms.off("update:" + this.getStoreState().roomToken,

View File

@ -22,7 +22,18 @@ body,
* Note: the is-standalone-room class is dynamically set by the StandaloneRoomView.
*/
.standalone.is-standalone-room {
background-color: #000;
background: #000;
}
.standalone .beta-logo {
position: fixed;
top: 0;
left: 0;
width: 50px;
height: 50px;
background: transparent url(../shared/img/beta-ribbon.svg) no-repeat;
background-size: 50px;
z-index: 1000;
}
.standalone-header {
@ -247,6 +258,9 @@ p.standalone-btn-label {
border-radius: 3px;
z-index: 1002; /* ensures the form is always on top of the control bar */
}
.standalone .room-conversation-wrapper .ended-conversation .feedback {
right: 35%;
}
.standalone .ended-conversation .local-stream {
/* Hide local media stream when feedback form is shown. */

View File

@ -351,6 +351,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
React.DOM.div({className: "room-conversation-wrapper"},
React.DOM.div({className: "beta-logo"}),
StandaloneRoomHeader(null),
StandaloneRoomInfoArea({roomState: this.state.roomState,
failureReason: this.state.failureReason,

View File

@ -351,6 +351,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
<div className="room-conversation-wrapper">
<div className="beta-logo" />
<StandaloneRoomHeader />
<StandaloneRoomInfoArea roomState={this.state.roomState}
failureReason={this.state.failureReason}

View File

@ -676,10 +676,10 @@ describe("loop.store.ActiveRoomStore", function () {
"fakeToken", "1627384950");
});
it("should set the state to ENDED", function() {
it("should set the state to CLOSING", function() {
store.windowUnload();
expect(store._storeState.roomState).eql(ROOM_STATES.ENDED);
expect(store._storeState.roomState).eql(ROOM_STATES.CLOSING);
});
});

View File

@ -290,15 +290,6 @@ add_task(function* test_createRoom() {
compareRooms(room, kCreateRoomProps);
});
// Test if deleting a room works as expected.
add_task(function* test_deleteRoom() {
let roomToken = "QzBbvGmIZWU";
let deletedRoom = yield LoopRooms.promise("delete", roomToken);
Assert.equal(deletedRoom.roomToken, roomToken);
let rooms = yield LoopRooms.promise("getAll");
Assert.ok(!rooms.some((room) => room.roomToken == roomToken));
});
// Test if opening a new room window works correctly.
add_task(function* test_openRoom() {
let openedUrl;
@ -403,6 +394,16 @@ add_task(function* test_roomDeleteNotifications() {
yield waitForCondition(() => gExpectedDeletes.length === 0);
});
// Test if deleting a room works as expected.
add_task(function* test_deleteRoom() {
let roomToken = "QzBbvGmIZWU";
gExpectedDeletes.push(roomToken);
let deletedRoom = yield LoopRooms.promise("delete", roomToken);
Assert.equal(deletedRoom.roomToken, roomToken);
let rooms = yield LoopRooms.promise("getAll");
Assert.ok(!rooms.some((room) => room.roomToken == roomToken));
});
// Test if the event emitter implementation doesn't leak and is working as expected.
add_task(function* () {
Assert.strictEqual(gExpectedAdds.length, 0, "No room additions should be expected anymore");

View File

@ -621,6 +621,17 @@
)
),
Example({summary: "Standalone room conversation (feedback)"},
React.DOM.div({className: "standalone"},
StandaloneRoomView({
dispatcher: dispatcher,
activeRoomStore: activeRoomStore,
feedbackStore: feedbackStore,
roomState: ROOM_STATES.ENDED,
helper: {isFirefox: returnFalse}})
)
),
Example({summary: "Standalone room conversation (failed)"},
React.DOM.div({className: "standalone"},
StandaloneRoomView({

View File

@ -621,6 +621,17 @@
</div>
</Example>
<Example summary="Standalone room conversation (feedback)">
<div className="standalone">
<StandaloneRoomView
dispatcher={dispatcher}
activeRoomStore={activeRoomStore}
feedbackStore={feedbackStore}
roomState={ROOM_STATES.ENDED}
helper={{isFirefox: returnFalse}} />
</div>
</Example>
<Example summary="Standalone room conversation (failed)">
<div className="standalone">
<StandaloneRoomView

View File

@ -531,8 +531,8 @@ Settings.prototype = {
yesNoToBoolean);
this._set("Software\\Microsoft\\Internet Explorer\\Settings",
"Always Use My Colors",
"browser.display.use_document_colors",
function (v) !Boolean(v));
"browser.display.document_color_use",
function (v) !Boolean(v) ? 0 : 2);
this._set("Software\\Microsoft\\Internet Explorer\\Settings",
"Always Use My Font Face",
"browser.display.use_document_fonts",

View File

@ -1459,7 +1459,7 @@ BrowserGlue.prototype = {
},
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 26;
const UI_VERSION = 27;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion = 0;
try {
@ -1769,6 +1769,15 @@ BrowserGlue.prototype = {
}
}
if (currentUIVersion < 27) {
// Fix up document color use:
const kOldColorPref = "browser.display.use_document_colors";
if (Services.prefs.prefHasUserValue(kOldColorPref) &&
!Services.prefs.getBoolPref(kOldColorPref)) {
Services.prefs.setIntPref("browser.display.document_color_use", 2);
}
}
// Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
},

View File

@ -28,7 +28,7 @@
helpTopic="prefs-fonts-and-colors">
<preferences>
<preference id="browser.display.use_document_colors" name="browser.display.use_document_colors" type="bool"/>
<preference id="browser.display.document_color_use" name="browser.display.document_color_use" type="int"/>
<preference id="browser.anchor_color" name="browser.anchor_color" type="string"/>
<preference id="browser.visited_color" name="browser.visited_color" type="string"/>
<preference id="browser.underline_anchors" name="browser.underline_anchors" type="bool"/>
@ -80,10 +80,16 @@
</hbox>
</groupbox>
</hbox>
<hbox>
<checkbox id="useDocumentColors"
label="&allowPagesToUse.label;" accesskey="&allowPagesToUse.accesskey;"
preference="browser.display.use_document_colors" flex="1"/>
</hbox>
<vbox align="start">
<label accesskey="&allowPagesToUseColors.accesskey;"
control="useDocumentColors">&allowPagesToUseColors.label;</label>
<menulist id="useDocumentColors" preference="browser.display.document_color_use">
<menupopup>
<menuitem label="&allowPagesToUseColors.automatic.label;" value="0" id="documentColorAutomatic"/>
<menuitem label="&allowPagesToUseColors.always.label;" value="1" id="documentColorAlways"/>
<menuitem label="&allowPagesToUseColors.never.label;" value="2" id="documentColorNever"/>
</menupopup>
</menulist>
</vbox>
</prefpane>
</prefwindow>

View File

@ -6,8 +6,12 @@
<!ENTITY window.width "38em">
<!ENTITY window.macWidth "41em">
<!ENTITY allowPagesToUse.label "Allow pages to choose their own colors, instead of my selections above">
<!ENTITY allowPagesToUse.accesskey "A">
<!ENTITY allowPagesToUseColors.label "Allow pages to choose their own colors, instead of my selections above:">
<!ENTITY allowPagesToUseColors.accesskey "A">
<!ENTITY allowPagesToUseColors.automatic.label "Automatic">
<!ENTITY allowPagesToUseColors.always.label "Always">
<!ENTITY allowPagesToUseColors.never.label "Never">
<!ENTITY color "Text and Background">
<!ENTITY textColor.label "Text:">

View File

@ -120,6 +120,33 @@ this.UITour = {
allowAdd: true,
}],
["loop", {query: "#loop-button"}],
["loop-newRoom", {
query: (aDocument) => {
let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop");
if (!loopBrowser) {
return null;
}
return loopBrowser.contentDocument.querySelector(".new-room-button");
},
}],
["loop-roomList", {
query: (aDocument) => {
let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop");
if (!loopBrowser) {
return null;
}
return loopBrowser.contentDocument.querySelector(".room-list");
},
}],
["loop-signInUpLink", {
query: (aDocument) => {
let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop");
if (!loopBrowser) {
return null;
}
return loopBrowser.contentDocument.querySelector(".signin-link");
},
}],
["privateWindow", {query: "#privatebrowsing-button"}],
["quit", {query: "#PanelUI-quit"}],
["search", {
@ -358,7 +385,7 @@ this.UITour = {
if (this.highlightEffects.indexOf(data.effect) !== -1) {
effect = data.effect;
}
this.showHighlight(target, effect);
this.showHighlight(window, target, effect);
}).catch(log.error);
break;
}
@ -414,7 +441,7 @@ this.UITour = {
if (typeof data.targetCallbackID == "string")
infoOptions.targetCallbackID = data.targetCallbackID;
this.showInfo(messageManager, target, data.title, data.text, iconURL, buttons, infoOptions);
this.showInfo(window, messageManager, target, data.title, data.text, iconURL, buttons, infoOptions);
}).catch(log.error);
break;
}
@ -581,6 +608,12 @@ this.UITour = {
}).then(null, Cu.reportError);
break;
}
case "ping": {
if (typeof data.callbackID == "string")
this.sendPageCallback(messageManager, data.callbackID);
break;
}
}
if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
@ -735,10 +768,12 @@ this.UITour = {
}
// Clean up panel listeners after we may have called hideMenu above.
aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hidePanelAnnotations);
aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hidePanelAnnotations);
aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hideAppMenuAnnotations);
aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hideAppMenuAnnotations);
aWindow.PanelUI.panel.removeEventListener("popuphidden", this.onPanelHidden);
let loopPanel = aWindow.document.getElementById("loop-notification-panel");
loopPanel.removeEventListener("popuphidden", this.onLoopPanelHidden);
loopPanel.removeEventListener("popuphidden", this.onPanelHidden);
loopPanel.removeEventListener("popuphiding", this.hideLoopPanelAnnotations);
this.endUrlbarCapture(aWindow);
this.removePinnedTab(aWindow);
@ -792,7 +827,9 @@ this.UITour = {
isElementVisible: function(aElement) {
let targetStyle = aElement.ownerDocument.defaultView.getComputedStyle(aElement);
return (targetStyle.display != "none" && targetStyle.visibility == "visible");
return !aElement.ownerDocument.hidden &&
targetStyle.display != "none" &&
targetStyle.visibility == "visible";
},
getTarget: function(aWindow, aTargetName, aSticky = false) {
@ -953,27 +990,28 @@ this.UITour = {
},
/**
* @param aChromeWindow The chrome window that the highlight is in. Necessary since some targets
* are in a sub-frame so the defaultView is not the same as the chrome
* window.
* @param aTarget The element to highlight.
* @param aEffect (optional) The effect to use from UITour.highlightEffects or "none".
* @see UITour.highlightEffects
*/
showHighlight: function(aTarget, aEffect = "none") {
let window = aTarget.node.ownerDocument.defaultView;
showHighlight: function(aChromeWindow, aTarget, aEffect = "none") {
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);
this.hideHighlight(aChromeWindow);
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);
this._hideSearchEngineHighlight(aChromeWindow);
let highlighter = aTarget.node.ownerDocument.getElementById("UITourHighlight");
let highlighter = aChromeWindow.document.getElementById("UITourHighlight");
let effect = aEffect;
if (effect == "random") {
@ -985,7 +1023,7 @@ this.UITour = {
}
// Toggle the effect attribute to "none" and flush layout before setting it so the effect plays.
highlighter.setAttribute("active", "none");
aTarget.node.ownerDocument.defaultView.getComputedStyle(highlighter).animationName;
aChromeWindow.getComputedStyle(highlighter).animationName;
highlighter.setAttribute("active", effect);
highlighter.parentElement.setAttribute("targetName", aTarget.targetName);
highlighter.parentElement.hidden = false;
@ -1025,7 +1063,7 @@ this.UITour = {
}
/* The "overlap" position anchors from the top-left but we want to centre highlights at their
minimum size. */
let highlightWindow = aTarget.node.ownerDocument.defaultView;
let highlightWindow = aChromeWindow;
let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
@ -1046,7 +1084,7 @@ this.UITour = {
return;
}
this._setAppMenuStateForAnnotation(aTarget.node.ownerDocument.defaultView, "highlight",
this._setAppMenuStateForAnnotation(aChromeWindow, "highlight",
this.targetIsInAppMenu(aTarget),
showHighlightPanel.bind(this));
},
@ -1085,6 +1123,7 @@ this.UITour = {
/**
* Show an info panel.
*
* @param {ChromeWindow} aChromeWindow
* @param {nsIMessageSender} aMessageManager
* @param {Node} aAnchor
* @param {String} [aTitle=""]
@ -1094,12 +1133,12 @@ this.UITour = {
* @param {Object} [aOptions={}]
* @param {String} [aOptions.closeButtonCallbackID]
*/
showInfo: function(aMessageManager, aAnchor, aTitle = "", aDescription = "", aIconURL = "",
showInfo: function(aChromeWindow, aMessageManager, aAnchor, aTitle = "", aDescription = "", aIconURL = "",
aButtons = [], aOptions = {}) {
function showInfoPanel(aAnchorEl) {
aAnchorEl.focus();
let document = aAnchorEl.ownerDocument;
let document = aChromeWindow.document;
let tooltip = document.getElementById("UITourTooltip");
let tooltipTitle = document.getElementById("UITourTooltipTitle");
let tooltipDesc = document.getElementById("UITourTooltipDescription");
@ -1187,15 +1226,17 @@ this.UITour = {
}
// Prevent showing a panel at an undefined position.
if (!this.isElementVisible(aAnchor.node))
if (!this.isElementVisible(aAnchor.node)) {
log.warn("showInfo: Not showing since the target isn't visible", aAnchor);
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._setAppMenuStateForAnnotation(aChromeWindow, "info",
this.targetIsInAppMenu(aAnchor),
showInfoPanel.bind(this, aAnchor.node));
},
@ -1235,8 +1276,9 @@ this.UITour = {
if (aWindow.PanelUI.panel.state != "open") {
this.recreatePopup(aWindow.PanelUI.panel);
}
aWindow.PanelUI.panel.addEventListener("popuphiding", this.hidePanelAnnotations);
aWindow.PanelUI.panel.addEventListener("ViewShowing", this.hidePanelAnnotations);
aWindow.PanelUI.panel.addEventListener("popuphiding", this.hideAppMenuAnnotations);
aWindow.PanelUI.panel.addEventListener("ViewShowing", this.hideAppMenuAnnotations);
aWindow.PanelUI.panel.addEventListener("popuphidden", this.onPanelHidden);
if (aOpenCallback) {
aWindow.PanelUI.panel.addEventListener("popupshown", onPopupShown);
}
@ -1254,6 +1296,7 @@ this.UITour = {
panel.setAttribute("noautohide", true);
if (panel.state != "open") {
this.recreatePopup(panel);
this.availableTargetsCache.clear();
}
// An event object is expected but we don't want to toggle the panel with a click if the panel
@ -1263,7 +1306,8 @@ this.UITour = {
aOpenCallback();
}
});
panel.addEventListener("popuphidden", this.onLoopPanelHidden);
panel.addEventListener("popuphidden", this.onPanelHidden);
panel.addEventListener("popuphiding", this.hideLoopPanelAnnotations);
} else if (aMenuName == "searchEngines") {
this.getTarget(aWindow, "searchProvider").then(target => {
openMenuButton(target.node);
@ -1278,9 +1322,7 @@ this.UITour = {
}
if (aMenuName == "appMenu") {
aWindow.PanelUI.panel.removeAttribute("noautohide");
aWindow.PanelUI.hide();
this.recreatePopup(aWindow.PanelUI.panel);
} else if (aMenuName == "bookmarks") {
let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
closeMenuButton(menuBtn);
@ -1293,7 +1335,7 @@ this.UITour = {
}
},
hidePanelAnnotations: function(aEvent) {
hideAnnotationsForPanel: function(aEvent, aTargetPositionCallback) {
let win = aEvent.target.ownerDocument.defaultView;
let annotationElements = new Map([
// [annotationElement (panel), method to hide the annotation]
@ -1308,7 +1350,7 @@ this.UITour = {
// changed since it may have just moved to somewhere outside of the app menu.
if (annotationElement.getAttribute("targetName") != aTarget.targetName ||
annotationElement.state == "closed" ||
!UITour.targetIsInAppMenu(aTarget)) {
!aTargetPositionCallback(aTarget)) {
return;
}
hideMethod(win);
@ -1318,7 +1360,18 @@ this.UITour = {
UITour.appMenuOpenForAnnotation.clear();
},
onLoopPanelHidden: function(aEvent) {
hideAppMenuAnnotations: function(aEvent) {
UITour.hideAnnotationsForPanel(aEvent, UITour.targetIsInAppMenu);
},
hideLoopPanelAnnotations: function(aEvent) {
UITour.hideAnnotationsForPanel(aEvent, (aTarget) => {
// TODO: Bug 1104927 - Handle the conversation targets separately.
return aTarget.targetName.startsWith("loop-");
});
},
onPanelHidden: function(aEvent) {
aEvent.target.removeAttribute("noautohide");
UITour.recreatePopup(aEvent.target);
},
@ -1575,7 +1628,29 @@ this.UITour = {
reject("Search engine not available");
});
});
}
},
notify(eventName, params) {
let winEnum = Services.wm.getEnumerator("navigator:browser");
while (winEnum.hasMoreElements()) {
let window = winEnum.getNext();
if (window.closed)
continue;
debugger;
let originTabs = this.originTabs.get(window);
if (!originTabs)
continue;
for (let tab of originTabs) {
let messageManager = tab.linkedBrowser.messageManager;
let detail = {
event: eventName,
params: params,
};
messageManager.sendAsyncMessage("UITour:SendPageNotification", detail);
}
}
},
};
this.UITour.init();

View File

@ -33,6 +33,8 @@ skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
[browser_UITour_modalDialog.js]
run-if = os == "mac" # modal dialog disabling only working on OS X
skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
[browser_UITour_observe.js]
skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
[browser_UITour_panel_close_annotation.js]
skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137
[browser_UITour_registerPageID.js]

View File

@ -50,6 +50,41 @@ let tests = [
// Test that the open menu from above was torn down fully.
checkLoopPanelIsHidden();
}),
function test_availableTargets(done) {
gContentAPI.showMenu("loop");
gContentAPI.getConfiguration("availableTargets", (data) => {
for (let targetName of ["loop-newRoom", "loop-roomList", "loop-signInUpLink"]) {
isnot(data.targets.indexOf(targetName), -1, targetName + " should exist");
}
done();
});
},
function test_hideMenuHidesAnnotations(done) {
let infoPanel = document.getElementById("UITourTooltip");
let highlightPanel = document.getElementById("UITourHighlightContainer");
gContentAPI.showMenu("loop", function menuCallback() {
gContentAPI.showHighlight("loop-roomList");
gContentAPI.showInfo("loop-newRoom", "Make a new room", "AKA. conversation");
UITour.getTarget(window, "loop-newRoom").then((target) => {
waitForPopupAtAnchor(infoPanel, target.node, Task.async(function* checkPanelIsOpen() {
isnot(loopPanel.state, "closed", "Loop panel should still be open");
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should still be on the loop panel");
is(highlightPanel.getAttribute("targetName"), "loop-roomList", "Check highlight @targetname");
is(infoPanel.getAttribute("targetName"), "loop-newRoom", "Check info panel @targetname");
info("Close the loop menu and make sure the annotations inside disappear");
let hiddenPromises = [promisePanelElementHidden(window, infoPanel),
promisePanelElementHidden(window, highlightPanel)];
gContentAPI.hideMenu("loop");
yield Promise.all(hiddenPromises);
isnot(infoPanel.state, "open", "Info panel should have automatically hid");
isnot(highlightPanel.state, "open", "Highlight panel should have automatically hid");
done();
}), "Info panel should be anchored to the new room button");
});
});
},
];
function checkLoopPanelIsHidden() {
@ -61,7 +96,16 @@ function checkLoopPanelIsHidden() {
if (Services.prefs.getBoolPref("loop.enabled")) {
loopButton = window.LoopUI.toolbarButton.node;
// The targets to highlight only appear after getting started is launched.
Services.prefs.setBoolPref("loop.gettingStarted.seen", true);
Services.prefs.setCharPref("loop.server", "http://localhost");
Services.prefs.setCharPref("services.push.serverURL", "ws://localhost/");
registerCleanupFunction(() => {
Services.prefs.clearUserPref("loop.gettingStarted.seen");
Services.prefs.clearUserPref("loop.server");
Services.prefs.clearUserPref("services.push.serverURL");
// Copied from browser/components/loop/test/mochitest/head.js
// Remove the iframe after each test. This also avoids mochitest complaining
// about leaks on shutdown as we intentionally hold the iframe open for the

View File

@ -0,0 +1,51 @@
"use strict";
let gTestTab;
let gContentAPI;
let gContentWindow;
Components.utils.import("resource:///modules/UITour.jsm");
function test() {
requestLongerTimeout(2);
UITourTest();
}
let tests = [
function test_no_params(done) {
function listener(event, params) {
is(event, "test-event-1", "Correct event name");
is(params, null, "No param object");
gContentAPI.observe(null);
done();
}
gContentAPI.observe(listener, () => {
UITour.notify("test-event-1");
});
},
function test_param_string(done) {
function listener(event, params) {
is(event, "test-event-2", "Correct event name");
is(params, "a param", "Correct param string");
gContentAPI.observe(null);
done();
}
gContentAPI.observe(listener, () => {
UITour.notify("test-event-2", "a param");
});
},
function test_param_object(done) {
function listener(event, params) {
is(event, "test-event-3", "Correct event name");
is(JSON.stringify(params), JSON.stringify({key: "something"}), "Correct param object");
gContentAPI.observe(null);
done();
}
gContentAPI.observe(listener, () => {
UITour.notify("test-event-3", {key: "something"});
});
},
];

View File

@ -43,23 +43,54 @@ if (typeof Mozilla == 'undefined') {
var id = _generateCallbackID();
function listener(event) {
if (typeof event.detail != "object")
if (typeof event.detail != 'object')
return;
if (event.detail.callbackID != id)
return;
document.removeEventListener("mozUITourResponse", listener);
document.removeEventListener('mozUITourResponse', listener);
callback(event.detail.data);
}
document.addEventListener("mozUITourResponse", listener);
document.addEventListener('mozUITourResponse', listener);
return id;
}
var notificationListener = null;
function _notificationListener(event) {
if (typeof event.detail != 'object')
return;
if (typeof notificationListener != 'function')
return;
notificationListener(event.detail.event, event.detail.params);
}
Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY = 10 * 1000;
Mozilla.UITour.CONFIGNAME_SYNC = "sync";
Mozilla.UITour.CONFIGNAME_AVAILABLETARGETS = "availableTargets";
Mozilla.UITour.CONFIGNAME_SYNC = 'sync';
Mozilla.UITour.CONFIGNAME_AVAILABLETARGETS = 'availableTargets';
Mozilla.UITour.ping = function(callback) {
var data = {};
if (callback) {
data.callbackID = _waitForCallback(callback);
}
_sendEvent('ping', data);
};
Mozilla.UITour.observe = function(listener, callback) {
notificationListener = listener;
if (listener) {
document.addEventListener('mozUITourNotification',
_notificationListener);
Mozilla.UITour.ping(callback);
} else {
document.removeEventListener('mozUITourNotification',
_notificationListener);
}
};
Mozilla.UITour.registerPageID = function(pageID) {
_sendEvent('registerPageID', {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

View File

@ -49,10 +49,11 @@ browser.jar:
skin/classic/browser/menuPanel-exit.png
skin/classic/browser/menuPanel-help.png
skin/classic/browser/menuPanel-small.png
skin/classic/browser/bad-content-blocked-16.png
skin/classic/browser/bad-content-blocked-64.png
skin/classic/browser/bad-content-unblocked-16.png
skin/classic/browser/bad-content-unblocked-64.png
skin/classic/browser/bad-content-blocked-16.png (../shared/bad-content-blocked-16.png)
skin/classic/browser/bad-content-blocked-16@2x.png (../shared/bad-content-blocked-16@2x.png)
skin/classic/browser/bad-content-blocked-64.png (../shared/bad-content-blocked-64.png)
skin/classic/browser/bad-content-unblocked-16.png (../shared/bad-content-unblocked-16.png)
skin/classic/browser/bad-content-unblocked-64.png (../shared/bad-content-unblocked-64.png)
skin/classic/browser/monitor.png
skin/classic/browser/monitor_16-10.png
skin/classic/browser/notification-16.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -72,14 +72,14 @@ browser.jar:
skin/classic/browser/menuPanel-help@2x.png
skin/classic/browser/menuPanel-small.png
skin/classic/browser/menuPanel-small@2x.png
skin/classic/browser/bad-content-blocked-16.png
skin/classic/browser/bad-content-blocked-16@2x.png
skin/classic/browser/bad-content-blocked-64.png
skin/classic/browser/bad-content-blocked-64@2x.png
skin/classic/browser/bad-content-unblocked-16.png
skin/classic/browser/bad-content-unblocked-16@2x.png
skin/classic/browser/bad-content-unblocked-64.png
skin/classic/browser/bad-content-unblocked-64@2x.png
skin/classic/browser/bad-content-blocked-16.png (../shared/bad-content-blocked-16.png)
skin/classic/browser/bad-content-blocked-16@2x.png (../shared/bad-content-blocked-16@2x.png)
skin/classic/browser/bad-content-blocked-64.png (../shared/bad-content-blocked-64.png)
skin/classic/browser/bad-content-blocked-64@2x.png (../shared/bad-content-blocked-64@2x.png)
skin/classic/browser/bad-content-unblocked-16.png (../shared/bad-content-unblocked-16.png)
skin/classic/browser/bad-content-unblocked-16@2x.png (../shared/bad-content-unblocked-16@2x.png)
skin/classic/browser/bad-content-unblocked-64.png (../shared/bad-content-unblocked-64.png)
skin/classic/browser/bad-content-unblocked-64@2x.png (../shared/bad-content-unblocked-64@2x.png)
skin/classic/browser/panel-expander-closed.png
skin/classic/browser/panel-expander-closed@2x.png
skin/classic/browser/panel-expander-open.png

View File

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 346 B

View File

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 691 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 462 B

View File

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 831 B

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -59,10 +59,11 @@ browser.jar:
skin/classic/browser/Metro_Glyph.png (Metro_Glyph-aero.png)
skin/classic/browser/Metro_Glyph-inverted.png
skin/classic/browser/Metro_Glyph-menuPanel.png
skin/classic/browser/bad-content-blocked-16.png
skin/classic/browser/bad-content-blocked-64.png
skin/classic/browser/bad-content-unblocked-16.png
skin/classic/browser/bad-content-unblocked-64.png
skin/classic/browser/bad-content-blocked-16.png (../shared/bad-content-blocked-16.png)
skin/classic/browser/bad-content-blocked-16@2x.png (../shared/bad-content-blocked-16@2x.png)
skin/classic/browser/bad-content-blocked-64.png (../shared/bad-content-blocked-64.png)
skin/classic/browser/bad-content-unblocked-16.png (../shared/bad-content-unblocked-16.png)
skin/classic/browser/bad-content-unblocked-64.png (../shared/bad-content-unblocked-64.png)
skin/classic/browser/monitor.png
skin/classic/browser/monitor_16-10.png
skin/classic/browser/notification-16.png
@ -502,10 +503,11 @@ browser.jar:
skin/classic/aero/browser/Metro_Glyph.png (Metro_Glyph-aero.png)
skin/classic/aero/browser/Metro_Glyph-inverted.png
skin/classic/aero/browser/Metro_Glyph-menuPanel.png
skin/classic/aero/browser/bad-content-blocked-16.png
skin/classic/aero/browser/bad-content-blocked-64.png
skin/classic/aero/browser/bad-content-unblocked-16.png
skin/classic/aero/browser/bad-content-unblocked-64.png
skin/classic/aero/browser/bad-content-blocked-16.png (../shared/bad-content-blocked-16.png)
skin/classic/aero/browser/bad-content-blocked-16@2x.png (../shared/bad-content-blocked-16@2x.png)
skin/classic/aero/browser/bad-content-blocked-64.png (../shared/bad-content-blocked-64.png)
skin/classic/aero/browser/bad-content-unblocked-16.png (../shared/bad-content-unblocked-16.png)
skin/classic/aero/browser/bad-content-unblocked-64.png (../shared/bad-content-unblocked-64.png)
skin/classic/aero/browser/monitor.png
skin/classic/aero/browser/monitor_16-10.png
skin/classic/aero/browser/notification-16.png

View File

@ -614,6 +614,14 @@ nsPresContext::GetDocumentColorPreferences()
int32_t useAccessibilityTheme = 0;
bool usePrefColors = true;
bool isChromeDocShell = false;
static int32_t sDocumentColorsSetting;
static bool sDocumentColorsSettingPrefCached = false;
if (!sDocumentColorsSettingPrefCached) {
sDocumentColorsSettingPrefCached = true;
Preferences::AddIntVarCache(&sDocumentColorsSetting,
"browser.display.document_color_use",
0);
}
nsIDocument* doc = mDocument->GetDisplayDocument();
if (doc && doc->GetDocShell()) {
@ -669,9 +677,21 @@ nsPresContext::GetDocumentColorPreferences()
mBackgroundColor = NS_ComposeColors(NS_RGB(0xFF, 0xFF, 0xFF),
mBackgroundColor);
mUseDocumentColors = !useAccessibilityTheme &&
Preferences::GetBool("browser.display.use_document_colors",
mUseDocumentColors);
// Now deal with the pref:
// 0 = default: always, except in high contrast mode
// 1 = always
// 2 = never
if (sDocumentColorsSetting == 1) {
mUseDocumentColors = true;
} else if (sDocumentColorsSetting == 2) {
mUseDocumentColors = isChromeDocShell || mIsChromeOriginImage;
} else {
MOZ_ASSERT(!useAccessibilityTheme ||
!(isChromeDocShell || mIsChromeOriginImage),
"The accessibility theme should only be on for non-chrome");
mUseDocumentColors = !useAccessibilityTheme;
}
}
void

View File

@ -80,8 +80,7 @@ class ContainerLayer;
// supported values for cached bool types
enum nsPresContext_CachedBoolPrefType {
kPresContext_UseDocumentColors = 1,
kPresContext_UseDocumentFonts,
kPresContext_UseDocumentFonts = 1,
kPresContext_UnderlineLinks
};
@ -378,8 +377,6 @@ public:
switch (aPrefType) {
case kPresContext_UseDocumentFonts:
return mUseDocumentFonts;
case kPresContext_UseDocumentColors:
return mUseDocumentColors;
case kPresContext_UnderlineLinks:
return mUnderlineLinks;
default:
@ -846,7 +843,9 @@ public:
// Is it OK to let the page specify colors and backgrounds?
bool UseDocumentColors() const {
return GetCachedBoolPref(kPresContext_UseDocumentColors) || IsChrome() || IsChromeOriginImage();
MOZ_ASSERT(mUseDocumentColors || !(IsChrome() || IsChromeOriginImage()),
"We should never have a chrome doc or image that can't use its colors.");
return mUseDocumentColors;
}
// Explicitly enable and disable paint flashing.

View File

@ -50,7 +50,7 @@ var cs5 = getComputedStyle(document.getElementById("five"), "");
var cs6 = getComputedStyle(document.getElementById("six"), "");
var cs7 = getComputedStyle(document.getElementById("seven"), "");
SpecialPowers.pushPrefEnv({'set': [['browser.display.use_document_colors', true]]}, part1);
SpecialPowers.pushPrefEnv({'set': [['browser.display.document_color_use', 1]]}, part1);
var transparentBackgroundColor;
var inputBackgroundColor, inputColor, inputBorderTopColor;
@ -121,7 +121,7 @@ function part1()
inputBorderRightColor = cs4.borderRightColor;
inputBorderLeftColor = cs4.borderLeftColor;
inputBorderBottomColor = cs4.borderBottomColor;
SpecialPowers.pushPrefEnv({'set': [['browser.display.use_document_colors', false]]}, part2);
SpecialPowers.pushPrefEnv({'set': [['browser.display.document_color_use', 2]]}, part2);
}
function part2()

View File

@ -531,7 +531,7 @@ public class BrowserApp extends GeckoApp
public void run() {
final TabHistoryFragment fragment = TabHistoryFragment.newInstance(historyPageList, toIndex);
final FragmentManager fragmentManager = getSupportFragmentManager();
GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getInteger(R.integer.long_press_vibrate_msec));
GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getIntArray(R.array.long_press_vibrate_msec));
fragment.show(R.id.tab_history_panel, fragmentManager.beginTransaction(), TAB_HISTORY_FRAGMENT_TAG);
}
});

View File

@ -1416,10 +1416,19 @@ public class GeckoAppShell
return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
}
// Helper method to convert integer array to long array.
private static long[] convertIntToLongArray(int[] input) {
long[] output = new long[input.length];
for (int i = 0; i < input.length; i++) {
output[i] = input[i];
}
return output;
}
// Vibrate only if haptic feedback is enabled.
public static void vibrateOnHapticFeedbackEnabled(long milliseconds) {
public static void vibrateOnHapticFeedbackEnabled(int[] milliseconds) {
if (Settings.System.getInt(getContext().getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) > 0) {
vibrate(milliseconds);
vibrate(convertIntToLongArray(milliseconds), -1);
}
}

View File

@ -21,8 +21,8 @@ import android.graphics.Shader;
import android.graphics.drawable.Drawable;
/**
* A special drawable used with lightweight themes. This draws a color
* (with an optional color-filter) and a bitmap (with a linear gradient
* A special drawable used with lightweight themes. This draws a color
* (with an optional color-filter) and a bitmap (with a linear gradient
* to specify the alpha) in order.
*/
public class LightweightThemeDrawable extends Drawable {
@ -81,6 +81,8 @@ public class LightweightThemeDrawable extends Drawable {
/**
* Creates a paint that paint a particular color.
*
* Note that the given color should include an alpha value.
*
* @param color The color to be painted.
*/
public void setColor(int color) {
@ -91,6 +93,8 @@ public class LightweightThemeDrawable extends Drawable {
/**
* Creates a paint that paint a particular color, and a filter for the color.
*
* Note that the given color should include an alpha value.
*
* @param color The color to be painted.
* @param filter The filter color to be applied using SRC_OVER mode.
*/

View File

@ -12,8 +12,10 @@ import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
@ -235,7 +237,9 @@ public class TopSitesPanel extends HomeFragment {
// Record tile click events on non-private tabs.
final Tab tab = Tabs.getInstance().getSelectedTab();
if (!tab.isPrivate()) {
mTilesRecorder.recordAction(tab, TilesRecorder.ACTION_CLICK, position, getTilesSnapshot());
final Locale locale = Locale.getDefault();
final String localeTag = BrowserLocaleManager.getLanguageTag(locale);
mTilesRecorder.recordAction(tab, TilesRecorder.ACTION_CLICK, position, getTilesSnapshot(), localeTag);
}
mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class));

View File

@ -18,7 +18,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView style="@style/TabsPanelItem.TextAppearance.Header.PrivateTabs"
<TextView android:id="@+id/private_tabs_empty_header"
style="@style/TabsPanelItem.TextAppearance.Header.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/private_browsing_title"/>

View File

@ -6,6 +6,9 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<!-- The layout_height value is used in TabsPanel.getTabsLayoutContainerHeight
and as an offset in PrivateTabsPanel: if you change it here,
change it there! -->
<RelativeLayout android:id="@+id/tabs_panel_header"
android:layout_width="match_parent"
android:layout_height="@dimen/browser_toolbar_height">

View File

@ -147,4 +147,11 @@
<item>0</item>
<item>1</item>
</string-array>
<!-- This value is similar to config_longPressVibePattern in android frameworks/base/core/res/res/values/config.xml-->
<integer-array name="long_press_vibrate_msec">
<item>0</item>
<item>1</item>
<item>20</item>
<item>21</item>
</integer-array>
</resources>

View File

@ -42,6 +42,7 @@
<dimen name="new_tablet_browser_toolbar_menu_item_inset_horizontal">3dp</dimen>
<dimen name="new_tablet_browser_toolbar_menu_item_corner_radius">5dp</dimen>
<dimen name="new_tablet_tab_strip_button_inset">5dp</dimen>
<dimen name="new_tablet_private_tabs_panel_empty_width">300dp</dimen>
<dimen name="forward_default_offset">-13dip</dimen>
<!-- Dimensions used by Favicons and FaviconView -->

View File

@ -8,6 +8,5 @@
<integer name="number_of_top_sites">6</integer>
<integer name="number_of_top_sites_cols">2</integer>
<integer name="max_icon_grid_columns">4</integer>
<integer name="long_press_vibrate_msec">100</integer>
</resources>

View File

@ -8,16 +8,20 @@ package org.mozilla.gecko.tabs;
import java.util.Locale;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.tabs.TabsPanel.CloseAllPanelView;
import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
/**
* A container that wraps the private tabs {@link android.widget.AdapterView} and empty
@ -27,16 +31,28 @@ import android.widget.FrameLayout;
*/
class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
private TabsPanel tabsPanel;
private final TabsLayout tabsLayout;
private final View emptyTabsHeader;
private final LinearLayout emptyTabsFrame;
private final int emptyTabsFrameWidth;
private final int emptyTabsFrameVerticalOffset;
public PrivateTabsPanel(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = getResources();
emptyTabsFrameVerticalOffset = res.getDimensionPixelOffset(R.dimen.browser_toolbar_height);
emptyTabsFrameWidth =
res.getDimensionPixelSize(R.dimen.new_tablet_private_tabs_panel_empty_width);
LayoutInflater.from(context).inflate(R.layout.private_tabs_panel, this);
tabsLayout = (TabsLayout) findViewById(R.id.private_tabs_layout);
emptyTabsHeader = findViewById(R.id.private_tabs_empty_header);
final View emptyView = findViewById(R.id.private_tabs_empty);
tabsLayout.setEmptyView(emptyView);
emptyTabsFrame = (LinearLayout) findViewById(R.id.private_tabs_empty);
tabsLayout.setEmptyView(emptyTabsFrame);
final View learnMore = findViewById(R.id.private_tabs_learn_more);
learnMore.setOnClickListener(new OnClickListener() {
@ -61,6 +77,8 @@ class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
@Override
public void show() {
updateStyleForNewTablet();
tabsLayout.show();
setVisibility(View.VISIBLE);
}
@ -80,4 +98,27 @@ class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
public void closeAll() {
tabsLayout.closeAll();
}
private void updateStyleForNewTablet() {
if (!NewTabletUI.isEnabled(getContext())) {
return;
}
// TODO: Move this to styles when removing old tablet.
// Delete the emptyTabsFrame & Header class vars too.
emptyTabsFrame.setOrientation(LinearLayout.VERTICAL);
final FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) emptyTabsFrame.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(
R.dimen.new_tablet_private_tabs_panel_empty_width);
lp.height = LayoutParams.WRAP_CONTENT;
// We want to center the content on the screen, not in the View,
// so add padding to compensate for the header.
lp.gravity = Gravity.CENTER;
emptyTabsFrame.setPadding(0, 0, 0, emptyTabsFrameVerticalOffset);
emptyTabsHeader.setVisibility(View.VISIBLE);
}
}

View File

@ -585,6 +585,28 @@ abstract class BaseTest extends BaseRobocopTest {
mAsserter.ok(success, "Top site item was pinned: " + isPinned, null);
}
public void pinTopSite(String gridItemTitle) {
verifyPinned(false, gridItemTitle);
mSolo.clickLongOnText(gridItemTitle);
boolean dialogOpened = mSolo.waitForDialogToOpen();
mAsserter.ok(dialogOpened, "Pin site dialog opened: " + gridItemTitle, null);
boolean pinSiteFound = waitForText(StringHelper.CONTEXT_MENU_PIN_SITE);
mAsserter.ok(pinSiteFound, "Found pin site menu item", null);
mSolo.clickOnText(StringHelper.CONTEXT_MENU_PIN_SITE);
verifyPinned(true, gridItemTitle);
}
public void unpinTopSite(String gridItemTitle) {
verifyPinned(true, gridItemTitle);
mSolo.clickLongOnText(gridItemTitle);
boolean dialogOpened = mSolo.waitForDialogToOpen();
mAsserter.ok(dialogOpened, "Pin site dialog opened: " + gridItemTitle, null);
boolean unpinSiteFound = waitForText(StringHelper.CONTEXT_MENU_UNPIN_SITE);
mAsserter.ok(unpinSiteFound, "Found unpin site menu item", null);
mSolo.clickOnText(StringHelper.CONTEXT_MENU_UNPIN_SITE);
verifyPinned(false, gridItemTitle);
}
// Used to perform clicks on pop-up buttons without having to close the virtual keyboard
public void clickOnButton(String label) {
final Button button = mSolo.getButton(label);

View File

@ -51,6 +51,7 @@ public class StringHelper {
public static final String CONTEXT_MENU_EDIT_SITE_SETTINGS = "Edit Site Settings";
public static final String CONTEXT_MENU_ADD_TO_HOME_SCREEN = "Add to Home Screen";
public static final String CONTEXT_MENU_PIN_SITE = "Pin Site";
public static final String CONTEXT_MENU_UNPIN_SITE = "Unpin Site";
// Context Menu menu items
public static final String[] CONTEXT_MENU_ITEMS_IN_PRIVATE_TAB = new String[] {

View File

@ -7,6 +7,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Locale;
import java.util.jar.JarInputStream;
import org.json.JSONArray;
@ -15,6 +16,8 @@ import org.json.JSONObject;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.SuggestedSites;
@ -129,7 +132,14 @@ public class testDistribution extends ContentProviderTest {
Distribution dist = initDistribution(mockPackagePath);
SuggestedSites suggestedSites = new SuggestedSites(mActivity, dist);
BrowserDB.setSuggestedSites(suggestedSites);
checkTilesReporting();
// Test tiles uploading for an en-US OS locale with no app locale.
setOSLocale(Locale.US);
checkTilesReporting("en-US");
// Test tiles uploading for an es-MX OS locale with no app locale.
setOSLocale(new Locale("es", "MX"));
checkTilesReporting("es-MX");
// Pre-clear distribution pref, run basic preferences and en-US localized preferences Tests
clearDistributionPref();
@ -154,6 +164,11 @@ public class testDistribution extends ContentProviderTest {
doTestInvalidReferrerIntent();
}
private void setOSLocale(Locale locale) {
Locale.setDefault(locale);
BrowserLocaleManager.storeAndNotifyOSLocale(GeckoSharedPrefs.forProfile(mActivity), locale);
}
private void doReferrerTest(String ref, final TestableDistribution distribution, final Runnable distributionReady) throws InterruptedException {
final Intent intent = new Intent(ACTION_INSTALL_REFERRER);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, CLASS_REFERRER_RECEIVER);
@ -330,37 +345,9 @@ public class testDistribution extends ContentProviderTest {
}
}
// Sets the distribution locale preference for the test
private void setTestLocale(String aLocale) {
String prefUseragentLocale = "general.useragent.locale";
JSONObject jsonPref = new JSONObject();
try {
// Request the pref change to the locale.
jsonPref.put("name", prefUseragentLocale);
jsonPref.put("type", "string");
jsonPref.put("value", aLocale);
mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
// Wait for confirmation of the pref change.
final String[] prefNames = { prefUseragentLocale };
Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
JSONObject data = null;
int requestId = -1;
// Wait until we get the correct "Preferences:Data" event
while (requestId != PREF_REQUEST_ID) {
data = new JSONObject(eventExpecter.blockForEventData());
requestId = data.getInt("requestId");
}
eventExpecter.unregisterListener();
} catch (Exception e) {
mAsserter.ok(false, "exception setting test locale", e.toString());
}
// Sets the distribution locale preference for the test.
private void setTestLocale(String locale) {
BrowserLocaleManager.getInstance().setSelectedLocale(mActivity, locale);
}
// Test localized distribution and preferences values stored in preferences.json
@ -454,32 +441,31 @@ public class testDistribution extends ContentProviderTest {
TestableDistribution.clearReferrerDescriptorForTesting();
}
public void checkTilesReporting() throws JSONException {
public void checkTilesReporting(String localeCode) throws JSONException {
// Slight hack: Force top sites grid to reload.
inputAndLoadUrl(StringHelper.ABOUT_BLANK_URL);
inputAndLoadUrl(StringHelper.ABOUT_HOME_URL);
// Click the first tracking tile and verify the posted data.
JSONObject response = clickTrackingTile(StringHelper.DISTRIBUTION1_LABEL);
mAsserter.is(0, response.getInt("click"), "JSON click index matched");
mAsserter.is("[{\"id\":123},{\"id\":456},{},{},{},{}]", response.getString("tiles"), "JSON tiles data matched");
mAsserter.is(response.getInt("click"), 0, "JSON click index matched");
mAsserter.is(response.getString("locale"), localeCode, "JSON locale code matched");
mAsserter.is(response.getString("tiles"), "[{\"id\":123},{\"id\":456},{},{},{},{}]", "JSON tiles data matched");
inputAndLoadUrl(StringHelper.ABOUT_HOME_URL);
// Pin the second tracking tile.
verifyPinned(false, StringHelper.DISTRIBUTION2_LABEL);
mSolo.clickLongOnText(StringHelper.DISTRIBUTION2_LABEL);
boolean dialogOpened = mSolo.waitForDialogToOpen();
mAsserter.ok(dialogOpened, "Pin site dialog opened", null);
boolean pinSiteFound = waitForText(StringHelper.CONTEXT_MENU_PIN_SITE);
mAsserter.ok(pinSiteFound, "Found pin site menu item", null);
mSolo.clickOnText(StringHelper.CONTEXT_MENU_PIN_SITE);
verifyPinned(true, StringHelper.DISTRIBUTION2_LABEL);
pinTopSite(StringHelper.DISTRIBUTION2_LABEL);
// Click the second tracking tile and verify the posted data.
response = clickTrackingTile(StringHelper.DISTRIBUTION2_LABEL);
mAsserter.is(1, response.getInt("click"), "JSON click index matched");
mAsserter.is("[{\"id\":123},{\"id\":456,\"pin\":true},{},{},{},{}]", response.getString("tiles"), "JSON tiles data matched");
mAsserter.is(response.getInt("click"), 1, "JSON click index matched");
mAsserter.is(response.getString("tiles"), "[{\"id\":123},{\"id\":456,\"pin\":true},{},{},{},{}]", "JSON tiles data matched");
inputAndLoadUrl(StringHelper.ABOUT_HOME_URL);
// Unpin the second tracking tile.
unpinTopSite(StringHelper.DISTRIBUTION2_LABEL);
}
private JSONObject clickTrackingTile(String text) throws JSONException {

View File

@ -22,7 +22,7 @@ public class TilesRecorder {
private static final String LOG_TAG = "GeckoTilesRecorder";
private static final String EVENT_TILES_CLICK = "Tiles:Click";
public void recordAction(Tab tab, String action, int index, List<Tile> tiles) {
public void recordAction(Tab tab, String action, int index, List<Tile> tiles, String locale) {
final Tile clickedTile = tiles.get(index);
if (tab == null || clickedTile == null) {
@ -67,6 +67,7 @@ public class TilesRecorder {
final JSONObject payload = new JSONObject();
payload.put(action, clickedTileIndex);
payload.put("tiles", tilesJSON);
payload.put("locale", locale);
final JSONObject data = new JSONObject();
data.put("tabId", tab.getId());

View File

@ -152,4 +152,8 @@ public class ThemedEditText extends android.widget.EditText
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -152,4 +152,8 @@ public class ThemedImageButton extends android.widget.ImageButton
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -152,4 +152,8 @@ public class ThemedImageView extends android.widget.ImageView
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -147,4 +147,8 @@ public class ThemedLinearLayout extends android.widget.LinearLayout
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -152,4 +152,8 @@ public class ThemedRelativeLayout extends android.widget.RelativeLayout
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -147,4 +147,8 @@ public class ThemedTextSwitcher extends android.widget.TextSwitcher
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -152,4 +152,8 @@ public class ThemedTextView extends android.widget.TextView
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -152,4 +152,8 @@ public class ThemedView extends android.view.View
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -155,4 +155,8 @@ public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
public ColorDrawable getColorDrawable(int id) {
return new ColorDrawable(getResources().getColor(id));
}
protected LightweightTheme getTheme() {
return mTheme;
}
}

View File

@ -358,7 +358,7 @@ this.WebappManager = {
try {
yield DOMApplicationRegistry.startDownload(aData.manifestURL);
} catch (ex if ex.message == "PACKAGE_UNCHANGED") {
} catch (ex if ex == "PACKAGE_UNCHANGED") {
debug("package unchanged");
// If the package is unchanged, then there's nothing more to do.
return;

View File

@ -177,7 +177,10 @@ pref("browser.sessionhistory.max_total_viewers", -1);
pref("ui.use_native_colors", true);
pref("ui.click_hold_context_menus", false);
pref("browser.display.use_document_fonts", 1); // 0 = never, 1 = quick, 2 = always
pref("browser.display.use_document_colors", true);
// 0 = default: always, except in high contrast mode
// 1 = always
// 2 = never
pref("browser.display.document_color_use", 0);
pref("browser.display.use_system_colors", false);
pref("browser.display.foreground_color", "#000000");
pref("browser.display.background_color", "#FFFFFF");

View File

@ -310,10 +310,12 @@ AboutProtocolInstance.prototype = {
let AboutProtocolChild = {
_classDescription: "Addon shim about: protocol handler",
_classID: Components.ID("8d56a310-0c80-11e4-9191-0800200c9a66"),
init: function() {
this._instances = {};
// Maps contractIDs to instances
this._instances = new Map();
// Maps contractIDs to classIDs
this._classIDs = new Map();
NotificationTracker.watch("about-protocol", this);
},
@ -322,11 +324,19 @@ let AboutProtocolChild = {
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
if (register) {
let instance = new AboutProtocolInstance(contractID);
this._instances[contractID] = instance;
registrar.registerFactory(this._classID, this._classDescription, contractID, instance);
let classID = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator)
.generateUUID();
this._instances.set(contractID, instance);
this._classIDs.set(contractID, classID);
registrar.registerFactory(classID, this._classDescription, contractID, instance);
} else {
registrar.unregisterFactory(this._classID, this._instances[contractID]);
delete this._instances[contractID];
let instance = this._instances.get(contractID);
let classID = this._classIDs.get(contractID);
registrar.unregisterFactory(classID, instance);
this._instances.delete(contractID);
this._classIDs.delete(contractID);
}
},
};

View File

@ -404,8 +404,13 @@ let Bookmarks = Object.freeze({
let info = guidOrInfo;
if (!info)
throw new Error("Input should be a valid object");
if (typeof(guidOrInfo) != "object") {
if (typeof(guidOrInfo) != "object")
info = { guid: guidOrInfo };
// Disallow removing the root folders.
if ([this.rootGuid, this.menuGuid, this.toolbarGuid, this.unfiledGuid,
this.tagsGuid].indexOf(info.guid) != -1) {
throw new Error("It's not possible to remove Places root folders.");
}
// Even if we ignore any other unneeded property, we still validate any
@ -417,10 +422,6 @@ let Bookmarks = Object.freeze({
if (!item)
throw new Error("No bookmarks found for the provided GUID.");
// Disallow removing the root folders.
if (!item._parentId || item._parentId == PlacesUtils.placesRootId)
throw new Error("It's not possible to remove Places root folders.");
item = yield removeBookmark(item);
// Notify onItemRemoved to listeners.

View File

@ -44,19 +44,24 @@ add_task(function* remove_nonexistent_guid() {
});
add_task(function* remove_roots_fail() {
try {
yield PlacesUtils.bookmarks.remove(PlacesUtils.bookmarks.unfiledGuid);
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/It's not possible to remove Places root folders/.test(ex));
let guids = [PlacesUtils.bookmarks.rootGuid,
PlacesUtils.bookmarks.unfiledGuid,
PlacesUtils.bookmarks.menuGuid,
PlacesUtils.bookmarks.toolbarGuid,
PlacesUtils.bookmarks.tagsGuid];
for (let guid of guids) {
Assert.throws(() => PlacesUtils.bookmarks.remove(guid),
/It's not possible to remove Places root folders/);
}
});
try {
yield PlacesUtils.bookmarks.remove(PlacesUtils.bookmarks.rootGuid);
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/It's not possible to remove Places root folders/.test(ex));
}
add_task(function* remove_normal_folder_undes_root_succeeds() {
let folder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.rootGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(folder);
let removed_folder = yield PlacesUtils.bookmarks.remove(folder);
Assert.deepEqual(folder, removed_folder);
Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder.guid)), null);
});
add_task(function* remove_bookmark() {