Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2014-01-22 15:32:22 -05:00
commit 67913e8980
71 changed files with 2757 additions and 980 deletions

View File

@ -527,6 +527,8 @@
#endif
@BINPATH@/components/TelemetryPing.js
@BINPATH@/components/TelemetryPing.manifest
@BINPATH@/components/TelemetryStartup.js
@BINPATH@/components/TelemetryStartup.manifest
@BINPATH@/components/Webapps.js
@BINPATH@/components/Webapps.manifest
@BINPATH@/components/AppsService.js

View File

@ -14,6 +14,9 @@ let CustomizationHandler = {
case "customizationstarting":
this._customizationStarting();
break;
case "customizationchange":
this._customizationChange();
break;
case "customizationending":
this._customizationEnding(aEvent.detail);
break;
@ -53,6 +56,12 @@ let CustomizationHandler = {
}
},
_customizationChange: function() {
gHomeButton.updatePersonalToolbarStyle();
BookmarkingUI.customizeChange();
PlacesToolbarHelper.customizeChange();
},
_customizationEnding: function(aDetails) {
// Update global UI elements that may have been added or removed
if (aDetails.changed) {

View File

@ -863,6 +863,10 @@ let PlacesToolbarHelper = {
return document.getElementById("PlacesToolbar");
},
get _placeholder() {
return document.getElementById("bookmarks-toolbar-placeholder");
},
init: function PTH_init(forceToolbarOverflowCheck) {
let viewElt = this._viewElt;
if (!viewElt || viewElt._placesView)
@ -883,6 +887,7 @@ let PlacesToolbarHelper = {
if (forceToolbarOverflowCheck) {
viewElt._placesView.updateOverflowStatus();
}
this.customizeChange();
},
customizeStart: function PTH_customizeStart() {
@ -893,6 +898,24 @@ let PlacesToolbarHelper = {
} finally {
this._isCustomizing = true;
}
this._shouldWrap = this._getShouldWrap();
},
customizeChange: function PTH_customizeChange() {
let placeholder = this._placeholder;
if (!placeholder) {
return;
}
let shouldWrapNow = this._getShouldWrap();
if (this._shouldWrap != shouldWrapNow) {
if (shouldWrapNow) {
placeholder.setAttribute("wrap", "true");
} else {
placeholder.removeAttribute("wrap");
}
placeholder.classList.toggle("toolbarbutton-1", shouldWrapNow);
this._shouldWrap = shouldWrapNow;
}
},
customizeDone: function PTH_customizeDone() {
@ -900,6 +923,13 @@ let PlacesToolbarHelper = {
this.init(true);
},
_getShouldWrap: function PTH_getShouldWrap() {
let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
let area = placement && placement.area;
let areaType = area && CustomizableUI.getAreaType(area);
return !area || CustomizableUI.TYPE_MENU_PANEL == areaType;
},
onPlaceholderCommand: function () {
let widgetGroup = CustomizableUI.getWidget("personal-bookmarks");
let widget = widgetGroup.forWindow(window);
@ -1082,8 +1112,14 @@ let BookmarkingUI = {
this._updateToolbarStyle();
},
init: function() {
CustomizableUI.addListener(this);
},
_hasBookmarksObserver: false,
uninit: function BUI_uninit() {
CustomizableUI.removeListener(this);
this._uninitView();
if (this._hasBookmarksObserver) {
@ -1279,6 +1315,41 @@ let BookmarkingUI = {
onItemVisited: function () {},
onItemMoved: function () {},
// CustomizableUI events:
_starButtonLabel: null,
_starButtonOverflowedLabel: null,
onWidgetOverflow: function(aNode, aContainer) {
let win = aNode.ownerDocument.defaultView;
if (aNode.id != "bookmarks-menu-button" || win != window)
return;
if (!this._starButtonOverflowedLabel) {
this._starButtonOverflowedLabel = gNavigatorBundle.getString(
"starButtonOverflowed.label");
}
let currentLabel = aNode.getAttribute("label");
if (!this._starButtonLabel)
this._starButtonLabel = currentLabel;
if (currentLabel == this._starButtonLabel)
aNode.setAttribute("label", this._starButtonOverflowedLabel);
},
onWidgetUnderflow: function(aNode, aContainer) {
let win = aNode.ownerDocument.defaultView;
if (aNode.id != "bookmarks-menu-button" || win != window)
return;
// If the button hasn't been in the overflow panel before, we may ignore
// this event.
if (!this._starButtonOverflowedLabel || !this._starButtonLabel)
return;
if (aNode.getAttribute("label") == this._starButtonOverflowedLabel)
aNode.setAttribute("label", this._starButtonLabel);
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsINavBookmarkObserver
])

View File

@ -747,6 +747,14 @@ toolbarpaletteitem[place="palette"] > toolbarbutton[type="badged"] > .toolbarbut
max-height: 32px;
}
toolbarbutton[sdk-button="true"] > .toolbarbutton-icon {
max-height: 32px;
}
toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
max-height: 18px;
}
panelview > .social-panel-frame {
width: auto;
height: auto;

View File

@ -1044,19 +1044,17 @@ var gBrowserInit = {
if (!isLoadingBlank || !focusAndSelectUrlBar())
gBrowser.selectedBrowser.focus();
gNavToolbox.customizeDone = BrowserToolboxCustomizeDone;
gNavToolbox.customizeChange = BrowserToolboxCustomizeChange;
// Set up Sanitize Item
this._initializeSanitizer();
// Enable/Disable auto-hide tabbar
gBrowser.tabContainer.updateVisibility();
BookmarkingUI.init();
gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
var homeButton = document.getElementById("home-button");
gHomeButton.init();
gHomeButton.updateTooltip(homeButton);
gHomeButton.updatePersonalToolbarStyle(homeButton);
@ -1151,6 +1149,7 @@ var gBrowserInit = {
window.addEventListener("dragover", MousePosTracker, false);
gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
gNavToolbox.addEventListener("customizationchange", CustomizationHandler);
gNavToolbox.addEventListener("customizationending", CustomizationHandler);
// End startup crash tracking after a delay to catch crashes while restoring
@ -1248,7 +1247,6 @@ var gBrowserInit = {
}
BookmarkingUI.uninit();
gHomeButton.uninit();
TabsInTitlebar.uninit();
@ -3278,20 +3276,11 @@ function OpenBrowserWindow(options)
return win;
}
//XXXunf Are these still useful to keep around?
// Only here for backwards compat, we should remove this soon
function BrowserCustomizeToolbar() {
gCustomizeMode.enter();
}
function BrowserToolboxCustomizeDone(aToolboxChanged) {
gCustomizeMode.exit(aToolboxChanged);
}
function BrowserToolboxCustomizeChange(aType) {
gHomeButton.updatePersonalToolbarStyle();
BookmarksMenuButton.customizeChange();
}
/**
* Update the global flag that tracks whether or not any edit UI (the Edit menu,
* edit-related items in the context menu, and edit-related toolbar buttons
@ -4762,16 +4751,6 @@ function fireSidebarFocusedEvent() {
var gHomeButton = {
init: function() {
gNavToolbox.addEventListener("customizationchange",
this.onCustomizationChange);
},
uninit: function() {
gNavToolbox.removeEventListener("customizationchange",
this.onCustomizationChange);
},
prefDomain: "browser.startup.homepage",
observe: function (aSubject, aTopic, aPrefName)
{
@ -4824,10 +4803,6 @@ var gHomeButton = {
homeButton.className.replace("toolbarbutton-1", "bookmark-item") :
homeButton.className.replace("bookmark-item", "toolbarbutton-1");
},
onCustomizationChange: function(aEvent) {
gHomeButton.updatePersonalToolbarStyle();
},
};
/**

View File

@ -858,7 +858,6 @@
cui-areatype="toolbar"
removable="true">
<toolbarbutton id="bookmarks-toolbar-placeholder"
type="wrap"
mousethrough="never"
label="&bookmarksToolbarItem.label;"
oncommand="PlacesToolbarHelper.onPlaceholderCommand();"/>

View File

@ -11,7 +11,7 @@
<panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
<panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
<vbox id="PanelUI-contents-scroller">
<vbox id="PanelUI-contents"/>
<vbox id="PanelUI-contents" class="panelUI-grid"/>
</vbox>
<footer id="PanelUI-footer">
@ -20,9 +20,11 @@
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
exitLabel="&appMenuCustomizeExit.label;" tabindex="0"
oncommand="gCustomizeMode.toggle();"/>
<toolbarseparator/>
<toolbarbutton id="PanelUI-help" label="&helpMenu.label;" tabindex="0"
tooltiptext="&helpMenu.label;"
oncommand="PanelUI.showHelpView(this.parentNode);"/>
<toolbarseparator/>
<toolbarbutton id="PanelUI-quit" tabindex="0"
#ifdef XP_WIN
label="&quitApplicationCmdWin.label;"
@ -178,6 +180,6 @@
level="top"
hidden="true">
<vbox id="widget-overflow-scroller">
<vbox id="widget-overflow-list"/>
<vbox id="widget-overflow-list" class="widget-overflow-list"/>
</vbox>
</panel>

View File

@ -207,6 +207,8 @@ const PanelUI = {
return this._readyPromise;
}
this._readyPromise = Task.spawn(function() {
this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang",
getLocale());
if (!this._scrollWidth) {
// In order to properly center the contents of the panel, while ensuring
// that we have enough space on either side to show a scrollbar, we have to
@ -418,3 +420,25 @@ const PanelUI = {
this.removeEventListener("command", PanelUI.onCommandHandler);
}
};
/**
* Gets the currently selected locale for display.
* @return the selected locale or "en-US" if none is selected
*/
function getLocale() {
try {
let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
Ci.nsIPrefLocalizedString);
if (locale)
return locale;
}
catch (e) { }
try {
return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
}
catch (e) { }
return "en-US";
}

View File

@ -479,9 +479,7 @@ let CustomizableUIInternal = {
this.ensureButtonContextMenu(node, aAreaNode);
if (node.localName == "toolbarbutton" && aArea == CustomizableUI.AREA_PANEL) {
node.setAttribute("tabindex", "0");
if (!node.hasAttribute("type")) {
node.setAttribute("type", "wrap");
}
node.setAttribute("wrap", "true");
}
this.insertWidgetBefore(node, currentNode, container, aArea);
@ -658,9 +656,7 @@ let CustomizableUIInternal = {
}
this.ensureButtonContextMenu(child, aPanel);
child.setAttribute("tabindex", "0");
if (!child.hasAttribute("type")) {
child.setAttribute("type", "wrap");
}
child.setAttribute("wrap", "true");
}
this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanel);
@ -709,9 +705,7 @@ let CustomizableUIInternal = {
container.removeChild(widgetNode);
} else {
widgetNode.removeAttribute("tabindex");
if (widgetNode.getAttribute("type") == "wrap") {
widgetNode.removeAttribute("type");
}
widgetNode.removeAttribute("wrap");
areaNode.toolbox.palette.appendChild(widgetNode);
}
this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
@ -861,9 +855,7 @@ let CustomizableUIInternal = {
this.ensureButtonContextMenu(widgetNode, aAreaNode);
if (widgetNode.localName == "toolbarbutton" && areaId == CustomizableUI.AREA_PANEL) {
widgetNode.setAttribute("tabindex", "0");
if (!widgetNode.hasAttribute("type")) {
widgetNode.setAttribute("type", "wrap");
}
widgetNode.setAttribute("wrap", "true");
}
}
@ -1200,19 +1192,92 @@ let CustomizableUIInternal = {
* part of the menu.
*/
_isOnInteractiveElement: function(aEvent) {
function getMenuPopupForDescendant(aNode) {
let lastPopup = null;
while (aNode && aNode.parentNode &&
aNode.parentNode.localName.startsWith("menu")) {
lastPopup = aNode.localName == "menupopup" ? aNode : lastPopup;
aNode = aNode.parentNode;
}
return lastPopup;
}
let target = aEvent.originalTarget;
let panel = this._getPanelForNode(aEvent.currentTarget);
// We keep track of:
// whether we're in an input container (text field)
let inInput = false;
// whether we're in a popup/context menu
let inMenu = false;
// whether we're in a toolbarbutton/toolbaritem
let inItem = false;
while (!inInput && !inMenu && !inItem && target != panel) {
// whether the current menuitem has a valid closemenu attribute
let menuitemCloseMenu = "auto";
// whether the toolbarbutton/item has a valid closemenu attribute.
let closemenu = "auto";
// While keeping track of that, we go from the original target back up,
// to the panel if we have to. We bail as soon as we find an input,
// a toolbarbutton/item, or the panel:
while (true) {
let tagName = target.localName;
inInput = tagName == "input";
inMenu = target.type == "menu";
inInput = tagName == "input" || tagName == "textbox";
inItem = tagName == "toolbaritem" || tagName == "toolbarbutton";
target = target.parentNode;
let isMenuItem = tagName == "menuitem";
inMenu = inMenu || isMenuItem;
if (inItem && target.hasAttribute("closemenu")) {
let closemenuVal = target.getAttribute("closemenu");
closemenu = (closemenuVal == "single" || closemenuVal == "none") ?
closemenuVal : "auto";
}
if (isMenuItem && target.hasAttribute("closemenu")) {
let closemenuVal = target.getAttribute("closemenu");
menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ?
closemenuVal : "auto";
}
// This isn't in the loop condition because we want to break before
// changing |target| if any of these conditions are true
if (inInput || inItem || target == panel) {
break;
}
// We need specific code for popups: the item on which they were invoked
// isn't necessarily in their parentNode chain:
if (isMenuItem) {
let topmostMenuPopup = getMenuPopupForDescendant(target);
target = (topmostMenuPopup && topmostMenuPopup.triggerNode) ||
target.parentNode;
} else {
target = target.parentNode;
}
}
return inMenu || inInput || !inItem;
// If the user clicked a menu item...
if (inMenu) {
// We care if we're in an input also,
// or if the user specified closemenu!="auto":
if (inInput || menuitemCloseMenu != "auto") {
return true;
}
// Otherwise, we're probably fine to close the panel
return false;
}
// If we're not in a menu, and we *are* in a type="menu" toolbarbutton,
// we'll now interact with the menu
if (inItem && target.getAttribute("type") == "menu") {
return true;
}
// If we're not in a menu, and we *are* in a type="menu-button" toolbarbutton,
// it depends whether we're in the dropmarker or the 'real' button:
if (inItem && target.getAttribute("type") == "menu-button") {
// 'real' button (which has a single action):
if (target.getAttribute("anonid") == "button") {
return closemenu != "none";
}
// otherwise, this is the outer button, and the user will now
// interact with the menu:
return true;
}
return inInput || !inItem;
},
hidePanelForNode: function(aNode) {
@ -2228,6 +2293,13 @@ this.CustomizableUI = {
* Fired when opening customize mode in aWindow.
* - onCustomizeEnd(aWindow)
* Fired when exiting customize mode in aWindow.
*
* - onWidgetOverflow(aNode, aContainer)
* Fired when a widget's DOM node is overflowing its container, a toolbar,
* and will be displayed in the overflow panel.
* - onWidgetUnderflow(aNode, aContainer)
* Fired when a widget's DOM node is *not* overflowing its container, a
* toolbar, anymore.
*/
addListener: function(aListener) {
CustomizableUIInternal.addListener(aListener);
@ -3235,6 +3307,7 @@ OverflowableToolbar.prototype = {
this._collapsed.set(child.id, this._target.clientWidth);
child.classList.add("overflowedItem");
child.setAttribute("cui-anchorid", this._chevron.id);
CustomizableUIInternal.notifyListeners("onWidgetOverflow", child, this._target);
this._list.insertBefore(child, this._list.firstChild);
if (!this._toolbar.hasAttribute("overflowing")) {
@ -3291,6 +3364,7 @@ OverflowableToolbar.prototype = {
}
child.removeAttribute("cui-anchorid");
child.classList.remove("overflowedItem");
CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target);
}
let win = this._target.ownerDocument.defaultView;
@ -3365,6 +3439,7 @@ OverflowableToolbar.prototype = {
this._collapsed.set(aNode.id, minSize);
aNode.setAttribute("cui-anchorid", this._chevron.id);
aNode.classList.add("overflowedItem");
CustomizableUIInternal.notifyListeners("onWidgetOverflow", aNode, this._target);
}
// If it is not overflowed and not in the toolbar, and was not overflowed
// either, it moved out of the toolbar. That means there's now space in there!
@ -3382,6 +3457,7 @@ OverflowableToolbar.prototype = {
this._collapsed.delete(aNode.id);
aNode.removeAttribute("cui-anchorid");
aNode.classList.remove("overflowedItem");
CustomizableUIInternal.notifyListeners("onWidgetUnderflow", aNode, this._target);
if (!this._collapsed.size) {
this._toolbar.removeAttribute("overflowing");

View File

@ -51,6 +51,7 @@ skip-if = os == "linux"
[browser_938995_indefaultstate_nonremovable.js]
[browser_940013_registerToolbarNode_calls_registerArea.js]
[browser_940107_home_button_in_bookmarks_toolbar.js]
[browser_940307_panel_click_closure_handling.js]
[browser_940946_removable_from_navbar_customizemode.js]
[browser_941083_invalidate_wrapper_cache_createWidget.js]
[browser_942581_unregisterArea_keeps_placements.js]

View File

@ -0,0 +1,108 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
let button, menuButton;
/* Clicking a button should close the panel */
add_task(function() {
button = document.createElement("toolbarbutton");
button.id = "browser_940307_button";
button.setAttribute("label", "Button");
PanelUI.contents.appendChild(button);
yield PanelUI.show();
let hiddenAgain = promisePanelHidden(window);
EventUtils.synthesizeMouseAtCenter(button, {});
yield hiddenAgain;
button.remove();
});
/* Clicking a menu button should close the panel, opening the popup shouldn't. */
add_task(function() {
menuButton = document.createElement("toolbarbutton");
menuButton.setAttribute("type", "menu-button");
menuButton.id = "browser_940307_menubutton";
menuButton.setAttribute("label", "Menu button");
let menuPopup = document.createElement("menupopup");
menuPopup.id = "browser_940307_menupopup";
let menuItem = document.createElement("menuitem");
menuItem.setAttribute("label", "Menu item");
menuItem.id = "browser_940307_menuitem";
menuPopup.appendChild(menuItem);
menuButton.appendChild(menuPopup);
PanelUI.contents.appendChild(menuButton);
yield PanelUI.show();
let hiddenAgain = promisePanelHidden(window);
let innerButton = document.getAnonymousElementByAttribute(menuButton, "anonid", "button");
EventUtils.synthesizeMouseAtCenter(innerButton, {});
yield hiddenAgain;
// Now click the dropmarker to show the menu
yield PanelUI.show();
hiddenAgain = promisePanelHidden(window);
let menuShown = promisePanelElementShown(window, menuPopup);
let dropmarker = document.getAnonymousElementByAttribute(menuButton, "type", "menu-button");
EventUtils.synthesizeMouseAtCenter(dropmarker, {});
yield menuShown;
// Panel should stay open:
ok(isPanelUIOpen(), "Panel should still be open");
let menuHidden = promisePanelElementHidden(window, menuPopup);
// Then click the menu item to close all the things
EventUtils.synthesizeMouseAtCenter(menuItem, {});
yield menuHidden;
yield hiddenAgain;
menuButton.remove();
});
add_task(function() {
let searchbar = document.getElementById("searchbar");
gCustomizeMode.addToPanel(searchbar);
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
yield PanelUI.show();
yield waitForCondition(() => "value" in searchbar && searchbar.value === "");
searchbar.value = "foo";
searchbar.focus();
// Reaching into this context menu is pretty evil, but hey... it's a test.
let textbox = document.getAnonymousElementByAttribute(searchbar.textbox, "anonid", "textbox-input-box");
let contextmenu = document.getAnonymousElementByAttribute(textbox, "anonid", "input-box-contextmenu");
let contextMenuShown = promisePanelElementShown(window, contextmenu);
EventUtils.synthesizeMouseAtCenter(searchbar, {type: "contextmenu", button: 2});
yield contextMenuShown;
ok(isPanelUIOpen(), "Panel should still be open");
let selectAll = contextmenu.querySelector("[cmd='cmd_selectAll']");
let contextMenuHidden = promisePanelElementHidden(window, contextmenu);
EventUtils.synthesizeMouseAtCenter(selectAll, {});
yield contextMenuHidden;
ok(isPanelUIOpen(), "Panel should still be open");
let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
ok(!isPanelUIOpen(), "Panel should no longer be open");
});
registerCleanupFunction(function() {
if (button && button.parentNode) {
button.remove();
}
if (menuButton && menuButton.parentNode) {
menuButton.remove();
}
// Sadly this isn't task.jsm-enabled, so we can't wait for this to happen. But we should
// definitely close it here and hope it won't interfere with other tests.
// Of course, all the tests are meant to do this themselves, but if they fail...
if (isPanelUIOpen()) {
PanelUI.hide();
}
});

View File

@ -250,6 +250,10 @@ function promisePanelElementHidden(win, aPanel) {
return deferred.promise;
}
function isPanelUIOpen() {
return PanelUI.panel.state == "open" || PanelUI.panel.state == "showing";
}
function subviewShown(aSubview) {
let deferred = Promise.defer();
let win = aSubview.ownerDocument.defaultView;

View File

@ -114,6 +114,8 @@
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey"/>
</content>
</binding>
</bindings>

View File

@ -846,6 +846,12 @@ let TabItems = {
_isComplete: function TabItems__isComplete(tab, callback) {
Utils.assertThrow(tab, "tab");
// A pending tab can't be complete, yet.
if (tab.hasAttribute("pending")) {
setTimeout(() => callback(false));
return;
}
let mm = tab.linkedBrowser.messageManager;
let message = "Panorama:isDocumentLoaded";

View File

@ -164,6 +164,7 @@ skip-if = true # Bug 736425
skip-if = os == "mac" || os == "win" # Bug 945687
[browser_tabview_launch.js]
[browser_tabview_multiwindow_search.js]
[browser_tabview_pending_tabs.js]
[browser_tabview_privatebrowsing_perwindowpb.js]
skip-if = os == 'linux' # Bug 944300
[browser_tabview_rtl.js]

View File

@ -0,0 +1,119 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const STATE = {
windows: [{
tabs: [{
entries: [{ url: "about:mozilla" }],
hidden: true,
extData: {"tabview-tab": '{"url":"about:mozilla","groupID":1}'}
},{
entries: [{ url: "about:robots" }],
hidden: false,
extData: {"tabview-tab": '{"url":"about:robots","groupID":1}'},
}],
selected: 1,
extData: {
"tabview-groups": '{"nextID":2,"activeGroupId":1, "totalNumber":1}',
"tabview-group":
'{"1":{"bounds":{"left":15,"top":5,"width":280,"height":232},"id":1}}'
}
}]
};
/**
* Make sure that tabs are restored on demand as otherwise the tab will start
* loading immediately and we can't check whether it shows cached data.
*/
add_task(function setup() {
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
});
});
/**
* Ensure that a pending tab shows cached data.
*/
add_task(function () {
// Open a new window.
let win = OpenBrowserWindow();
yield promiseDelayedStartupFinished(win);
// Set the window to a specific state.
let ss = Cc["@mozilla.org/browser/sessionstore;1"]
.getService(Ci.nsISessionStore)
.setWindowState(win, JSON.stringify(STATE), true);
// Open Panorama.
yield promiseTabViewShown(win);
let [tab1, tab2] = win.gBrowser.tabs;
let cw = win.TabView.getContentWindow();
// Update the two tabs in reverse order. Panorama will first try to update
// the second tab but will put it back onto the queue once it detects that
// it hasn't loaded yet. It will then try to update the first tab.
cw.TabItems.update(tab2);
cw.TabItems.update(tab1);
let tabItem1 = tab1._tabViewTabItem;
let tabItem2 = tab2._tabViewTabItem;
// Wait for the first tabItem to be updated. Calling update() on the second
// tabItem won't send a notification as that is pushed back onto the queue.
yield promiseTabItemUpdated(tabItem1);
// Check that the first tab doesn't show cached data, the second one does.
ok(!tabItem1.isShowingCachedData(), "doesn't show cached data");
ok(tabItem2.isShowingCachedData(), "shows cached data");
// Cleanup.
yield promiseWindowClosed(win);
});
function promiseTabItemUpdated(tabItem) {
let deferred = Promise.defer();
tabItem.addSubscriber("updated", function onUpdated() {
tabItem.removeSubscriber("updated", onUpdated);
deferred.resolve();
});
return deferred.promise;
}
function promiseAllTabItemsUpdated(win) {
let deferred = Promise.defer();
afterAllTabItemsUpdated(deferred.resolve, win);
return deferred.promise;
}
function promiseDelayedStartupFinished(win) {
let deferred = Promise.defer();
whenDelayedStartupFinished(win, deferred.resolve);
return deferred.promise;
}
function promiseTabViewShown(win) {
let deferred = Promise.defer();
showTabView(deferred.resolve, win);
return deferred.promise;
}
function promiseWindowClosed(win) {
let deferred = Promise.defer();
Services.obs.addObserver(function obs(subject, topic) {
if (subject == win) {
Services.obs.removeObserver(obs, topic);
deferred.resolve();
}
}, "domwindowclosed", false);
win.close();
return deferred.promise;
}

View File

@ -37,6 +37,10 @@ window.addEventListener("DOMContentLoaded", function onDOMReady() {
document.getElementById("port").value = port;
}
let form = document.querySelector("#connection-form form");
form.addEventListener("submit", function() {
window.submit();
});
}, true);
/**

View File

@ -19,7 +19,7 @@
<body>
<h1>&header;</h1>
<section id="connection-form">
<form validate="validate" onsubmit="window.submit()" action="#">
<form validate="validate" action="#">
<label>
<span>&host;</span>
<input required="required" class="devtools-textinput" id="host" type="text"></input>

View File

@ -512,6 +512,8 @@
@BINPATH@/components/cryptoComponents.manifest
@BINPATH@/components/TelemetryPing.js
@BINPATH@/components/TelemetryPing.manifest
@BINPATH@/components/TelemetryStartup.js
@BINPATH@/components/TelemetryStartup.manifest
@BINPATH@/components/messageWakeupService.js
@BINPATH@/components/messageWakeupService.manifest
@BINPATH@/components/SettingsManager.js

View File

@ -229,6 +229,7 @@ refreshBlocked.redirectLabel=%S prevented this page from automatically redirecti
# Star button
starButtonOn.tooltip=Edit this bookmark
starButtonOff.tooltip=Bookmark this page
starButtonOverflowed.label=Bookmark This Page
# Offline web applications
offlineApps.available=This website (%S) is asking to store data on your computer for offline use.

View File

@ -1,407 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/Services.jsm");
var ViewConfig = {
get _main() {
delete this._main;
return this._main = document.getElementById("main-container");
},
get _container() {
delete this._container;
return this._container = document.getElementById("prefs-container");
},
get _editor() {
delete this._editor;
return this._editor = document.getElementById("editor");
},
init: function init() {
this._main.addEventListener("click", this, false);
window.addEventListener("resize", this, false);
window.addEventListener("prefchange", this, false);
window.addEventListener("prefnew", this, false);
this._handleWindowResize();
this.filter("");
document.getElementById("textbox").focus();
},
uninit: function uninit() {
this._main.removeEventListener("click", this, false);
window.removeEventListener("resize", this, false);
window.removeEventListener("prefchange", this, false);
window.removeEventListener("prefnew", this, false);
},
filter: function filter(aValue) {
let row = document.getElementById("editor-row");
let container = this._container;
container.scrollBoxObject.scrollTo(0, 0);
// Clear the list by replacing with a shallow copy
let empty = container.cloneNode(false);
empty.appendChild(row);
container.parentNode.replaceChild(empty, container);
this._container = empty;
let result = Utils.getPrefs(aValue);
this._container.setItems(result.map(this._createItem, this));
},
open: function open(aType) {
let buttons = document.getElementById("editor-buttons-add");
buttons.setAttribute("hidden", "true");
let shouldFocus = false;
let setting = document.getElementById("editor-setting");
switch (aType) {
case Ci.nsIPrefBranch.PREF_INT:
setting.setAttribute("type", "integer");
setting.setAttribute("min", -Infinity);
break;
case Ci.nsIPrefBranch.PREF_BOOL:
setting.setAttribute("type", "bool");
break;
case Ci.nsIPrefBranch.PREF_STRING:
setting.setAttribute("type", "string");
break;
}
setting.removeAttribute("title");
setting.removeAttribute("pref");
if (setting.input)
setting.input.value = "";
document.getElementById("editor-container").appendChild(this._editor);
let nameField = document.getElementById("editor-name");
nameField.value = "";
this._editor.setAttribute("hidden", "false");
this._currentItem = null;
nameField.focus();
},
close: function close(aValid) {
this._editor.setAttribute("hidden", "true");
let buttons = document.getElementById("editor-buttons-add");
buttons.setAttribute("hidden", "false");
if (aValid) {
let name = document.getElementById("editor-name").inputField.value;
if (name != "") {
let setting = document.getElementById("editor-setting");
setting.setAttribute("pref", name);
setting.valueToPreference();
}
}
document.getElementById("editor-container").appendChild(this._editor);
},
_currentItem: null,
delayEdit: function(aItem) {
setTimeout(this.edit.bind(this), 0, aItem);
},
edit: function(aItem) {
if (!aItem)
return;
let pref = Utils.getPref(aItem.getAttribute("name"));
if (pref.lock || !pref.name || aItem == this._currentItem)
return;
this.close(false);
this._currentItem = aItem;
let setting = document.getElementById("editor-setting");
let shouldFocus = false;
switch (pref.type) {
case Ci.nsIPrefBranch.PREF_BOOL:
setting.setAttribute("type", "bool");
break;
case Ci.nsIPrefBranch.PREF_INT:
setting.setAttribute("type", "integer");
setting.setAttribute("increment", this.getIncrementForValue(pref.value));
setting.setAttribute("min", -Infinity);
shouldFocus = true;
break;
case Ci.nsIPrefBranch.PREF_STRING:
setting.setAttribute("type", "string");
shouldFocus = true;
break;
}
setting.setAttribute("title", pref.name);
setting.setAttribute("pref", pref.name);
this._container.insertBefore(this._editor, aItem);
let resetButton = document.getElementById("editor-reset");
resetButton.setAttribute("disabled", pref.default);
this._editor.setAttribute("default", pref.default);
this._editor.setAttribute("hidden", "false");
if (shouldFocus && setting.input)
setting.input.focus();
},
reset: function reset(aItem) {
let setting = document.getElementById("editor-setting");
let pref = Utils.getPref(setting.getAttribute("pref"));
if (!pref.default)
Utils.resetPref(pref.name);
},
handleEvent: function handleEvent(aEvent) {
switch (aEvent.type) {
case "resize":
this._handleWindowResize();
break;
case "prefchange":
case "prefnew":
this._handlePrefChange(aEvent.detail, aEvent.type == "prefnew");
break;
case "click":
this._onClick();
break;
}
},
_handleWindowResize: function _handleWindowResize() {
let mainBox = document.getElementById("main-container");
let textbox = document.getElementById("textbox");
let height = window.innerHeight - textbox.getBoundingClientRect().height;
mainBox.setAttribute("height", height);
},
_onClick: function () {
// Blur the search box when tapping anywhere else in the content
// in order to close the soft keyboard.
document.getElementById("textbox").blur();
},
_handlePrefChange: function _handlePrefChange(aIndex, aNew) {
let isEditing = !this._editor.hidden;
let shouldUpdateEditor = false;
if (isEditing) {
let setting = document.getElementById("editor-setting");
let editorIndex = Utils.getPrefIndex(setting.getAttribute("pref"));
shouldUpdateEditor = (aIndex == editorIndex);
if(shouldUpdateEditor || aIndex > editorIndex)
aIndex += 1;
}
// XXX An item display value will probably fail if a pref is changed in the
// background while there is a filter on the pref
let item = shouldUpdateEditor ? this._editor.nextSibling
: this._container.childNodes[aIndex + 1];// add 1 because of the new pref row
if (!item) // the pref is not viewable
return;
if (aNew) {
let pref = Utils.getPrefByIndex(aIndex);
let row = this._createItem(pref);
this._container.insertBefore(row, item);
return;
}
let pref = Utils.getPref(item.getAttribute("name"));
if (shouldUpdateEditor) {
this._editor.setAttribute("default", pref.default);
let resetButton = document.getElementById("editor-reset");
resetButton.disabled = pref.default;
}
item.setAttribute("default", pref.default);
item.lastChild.setAttribute("value", pref.value);
},
_createItem: function _createItem(aPref) {
let row = document.createElement("richlistitem");
row.setAttribute("name", aPref.name);
row.setAttribute("type", aPref.type);
row.setAttribute("role", "button");
row.setAttribute("default", aPref.default);
let label = document.createElement("label");
label.setAttribute("class", "preferences-title");
label.setAttribute("value", aPref.name);
label.setAttribute("crop", "end");
row.appendChild(label);
label = document.createElement("label");
label.setAttribute("class", "preferences-value");
label.setAttribute("value", aPref.value);
label.setAttribute("crop", "end");
row.appendChild(label);
return row;
},
getIncrementForValue: function getIncrementForValue(aValue) {
let count = 1;
while (aValue >= 100) {
aValue /= 10;
count *= 10;
}
return count;
}
};
var Utils = {
QueryInterface: function(aIID) {
if (!aIID.equals(Ci.nsIObserver) && !aIID.equals(Ci.nsISupportsWeakReference))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
get _branch() {
delete this._branch;
this._branch = Services.prefs.getBranch(null);
this._branch.addObserver("", this, true);
return this._branch;
},
get _preferences() {
delete this._preferences;
let list = this._branch.getChildList("", {}).filter(function(element) {
return !(/^capability\./.test(element));
});
return this._preferences = list.sort().map(this.getPref, this);
},
getPrefs: function getPrefs(aValue) {
let result = this._preferences.slice();;
if (aValue != "") {
let reg = this._generateRegexp(aValue);
if (!reg)
return [];
result = this._preferences.filter(function(element, index, array) {
return reg.test(element.name + ";" + element.value);
});
}
return result;
},
getPref: function getPref(aPrefName) {
let branch = this._branch;
let pref = {
name: aPrefName,
value: "",
default: !branch.prefHasUserValue(aPrefName),
lock: branch.prefIsLocked(aPrefName),
type: branch.getPrefType(aPrefName)
};
try {
switch (pref.type) {
case Ci.nsIPrefBranch.PREF_BOOL:
pref.value = branch.getBoolPref(aPrefName).toString();
break;
case Ci.nsIPrefBranch.PREF_INT:
pref.value = branch.getIntPref(aPrefName).toString();
break;
default:
case Ci.nsIPrefBranch.PREF_STRING:
pref.value = branch.getComplexValue(aPrefName, Ci.nsISupportsString).data;
// Try in case it's a localized string (will throw an exception if not)
if (pref.default && /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.value))
pref.value = branch.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
break;
}
} catch (e) {}
return pref;
},
getPrefByIndex: function getPrefByIndex(aIndex) {
return this._preferences[aIndex];
},
getPrefIndex: function getPrefIndex(aPrefName) {
let prefs = this._preferences;
let high = prefs.length - 1;
let low = 0, middle, element;
while (low <= high) {
middle = parseInt((low + high) / 2);
element = prefs[middle];
if (element.name > aPrefName)
high = middle - 1;
else if (element.name < aPrefName)
low = middle + 1;
else
return middle;
}
return -1;
},
resetPref: function resetPref(aPrefName) {
this._branch.clearUserPref(aPrefName);
},
observe: function observe(aSubject, aTopic, aPrefName) {
if (aTopic != "nsPref:changed" || /^capability\./.test(aPrefName)) // avoid displaying "private" preferences
return;
let type = "prefchange";
let index = this.getPrefIndex(aPrefName);
if (index != - 1) {
// update the inner array
let pref = this.getPref(aPrefName);
this._preferences[index].value = pref.value;
}
else {
// XXX we could do better here
let list = this._branch.getChildList("", {}).filter(function(element, index, array) {
return !(/^capability\./.test(element));
});
this._preferences = list.sort().map(this.getPref, this);
type = "prefnew";
index = this.getPrefIndex(aPrefName);
}
let evt = document.createEvent("UIEvents");
evt.initUIEvent(type, true, true, window, index);
window.dispatchEvent(evt);
},
_generateRegexp: function _generateRegexp(aValue) {
if (aValue.charAt(0) == "/") {
try {
let rv = aValue.match(/^\/(.*)\/(i?)$/);
return RegExp(rv[1], rv[2]);
}
catch (e) {
return null; // Do nothing on incomplete or bad RegExp
}
}
return RegExp(aValue.replace(/([^* \w])/g, "\\$1").replace(/^\*+/, "")
.replace(/\*+/g, ".*"), "i");
}
};

View File

@ -1,65 +0,0 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://browser/skin/platform.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/config.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % configDTD SYSTEM "chrome://browser/locale/config.dtd">
%configDTD;
]>
<window id="about:config"
onload="ViewConfig.init();"
onunload="ViewConfig.uninit();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript" src="chrome://browser/content/config.js"/>
<vbox class="panel-dark" flex="1">
<textbox id="textbox"
oncommand="ViewConfig.filter(this.value)"
type="search"
timeout="400"
emptytext="&empty.label;"/>
<hbox id="main-container" class="panel-dark">
<richlistbox id="prefs-container" flex="1" onselect="ViewConfig.delayEdit(this.selectedItem)" batch="25">
<richlistitem id="editor-row">
<vbox id="editor-container" flex="1">
<hbox align="center" flex="1">
<label value="&newpref.label;" flex="1"/>
<spacer flex="1" />
<hbox id="editor-buttons-add">
<button label="&integer.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_INT)"/>
<button label="&boolean.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_BOOL)"/>
<button label="&string.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_STRING)"/>
</hbox>
</hbox>
<vbox id="editor" hidden="true">
<hbox align="center">
<textbox id="editor-name" emptytext="&addpref.name;" flex="1"/>
<setting id="editor-setting" emptytext="&addpref.value;" onlabel="true" offlabel="false" flex="1"/>
</hbox>
<hbox id="editor-buttons">
<button id="editor-cancel" label="&cancel.label;" oncommand="ViewConfig.close(false)"/>
<spacer flex="1"/>
<button id="editor-reset" label="&reset.label;" oncommand="ViewConfig.reset(this.parentNode.parentNode.nextSibling)"/>
<button id="editor-done" label="&done.label;" oncommand="ViewConfig.close(true)"/>
</hbox>
</vbox>
</vbox>
</richlistitem>
</richlistbox>
</hbox>
</vbox>
</window>

View File

@ -0,0 +1,642 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, manager: Cm, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
const PRIVATE_PREF_PREFIX = "capability."; // Tag to prevent exposing private preferences
const INITIAL_PAGE_DELAY = 500; // Initial pause on program start for scroll alignment
const PREFS_BUFFER_MAX = 100; // Max prefs buffer size for getPrefsBuffer()
const PAGE_SCROLL_TRIGGER = 200; // Triggers additional getPrefsBuffer() on user scroll-to-bottom
const FILTER_CHANGE_TRIGGER = 200; // Delay between responses to filterInput changes
const INNERHTML_VALUE_DELAY = 100; // Delay before providing prefs innerHTML value
let gStringBundle = Services.strings.createBundle("chrome://browser/locale/config.properties");
let gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
/* ============================== NewPrefDialog ==============================
*
* New Preference Dialog Object and methods
*
* Implements User Interfaces for creation of a single(new) Preference setting
*
*/
var NewPrefDialog = {
_prefsShield: null,
_newPrefsDialog: null,
_newPrefItem: null,
_prefNameInputElt: null,
_prefTypeSelectElt: null,
_booleanValue: null,
_booleanToggle: null,
_stringValue: null,
_intValue: null,
_positiveButton: null,
get type() {
return this._prefTypeSelectElt.value;
},
set type(aType) {
this._prefTypeSelectElt.value = aType;
switch(this._prefTypeSelectElt.value) {
case "boolean":
this._prefTypeSelectElt.selectedIndex = 0;
break;
case "string":
this._prefTypeSelectElt.selectedIndex = 1;
break;
case "int":
this._prefTypeSelectElt.selectedIndex = 2;
break;
}
this._newPrefItem.setAttribute("typestyle", aType);
},
// Init the NewPrefDialog
init: function AC_init() {
this._prefsShield = document.getElementById("prefs-shield");
this._newPrefsDialog = document.getElementById("new-pref-container");
this._newPrefItem = document.getElementById("new-pref-item");
this._prefNameInputElt = document.getElementById("new-pref-name");
this._prefTypeSelectElt = document.getElementById("new-pref-type");
this._booleanValue = document.getElementById("new-pref-value-boolean");
this._stringValue = document.getElementById("new-pref-value-string");
this._intValue = document.getElementById("new-pref-value-int");
this._positiveButton = document.getElementById("positive-button");
},
// Called to update positive button to display text ("Create"/"Change), and enabled/disabled status
// As new pref name is initially displayed, re-focused, or modifed during user input
_updatePositiveButton: function AC_updatePositiveButton(aPrefName) {
this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.createButton");
this._positiveButton.setAttribute("disabled", true);
if (aPrefName == "") {
return;
}
// Prevent addition of new "private" preferences
if (aPrefName.startsWith(PRIVATE_PREF_PREFIX)) {
this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.privateButton");
return;
}
// If item already in list, it's being changed, else added
let item = document.querySelector(".pref-item[name=" + aPrefName.quote() + "]");
if (item) {
this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.changeButton");
} else {
this._positiveButton.removeAttribute("disabled");
}
},
// When we want to cancel/hide an existing, or show a new pref dialog
toggleShowHide: function AC_toggleShowHide() {
if (this._newPrefsDialog.classList.contains("show")) {
this.hide();
} else {
this._show();
}
},
// When we want to show the new pref dialog / shield the prefs list
_show: function AC_show() {
this._newPrefsDialog.classList.add("show");
this._prefsShield.setAttribute("shown", true);
// Initial default field values
this._prefNameInputElt.value = "";
this._updatePositiveButton(this._prefNameInputElt.value);
this.type = "boolean";
this._booleanValue.value = "false";
this._stringValue.value = "";
this._intValue.value = "";
this._prefNameInputElt.focus();
window.addEventListener("keypress", this.handleKeypress, false);
},
// When we want to cancel/hide the new pref dialog / un-shield the prefs list
hide: function AC_hide() {
this._newPrefsDialog.classList.remove("show");
this._prefsShield.removeAttribute("shown");
window.removeEventListener("keypress", this.handleKeypress, false);
},
// Watch user key input so we can provide Enter key action, commit input values
handleKeypress: function AC_handleKeypress(aEvent) {
// Close our VKB on new pref enter key press
if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
aEvent.target.blur();
},
// New prefs create dialog only allows creating a non-existing preference, doesn't allow for
// Changing an existing one on-the-fly, tap existing/displayed line item pref for that
create: function AC_create(aEvent) {
if (this._positiveButton.getAttribute("disabled") == "true") {
return;
}
switch(this.type) {
case "boolean":
Services.prefs.setBoolPref(this._prefNameInputElt.value, (this._booleanValue.value == "true") ? true : false);
break;
case "string":
Services.prefs.setCharPref(this._prefNameInputElt.value, this._stringValue.value);
break;
case "int":
Services.prefs.setIntPref(this._prefNameInputElt.value, this._intValue.value);
break;
}
this.hide();
},
// Display proper positive button text/state on new prefs name input focus
focusName: function AC_focusName(aEvent) {
this._updatePositiveButton(aEvent.target.value);
},
// Display proper positive button text/state as user changes new prefs name
updateName: function AC_updateName(aEvent) {
this._updatePositiveButton(aEvent.target.value);
},
// In new prefs dialog, bool prefs are <input type="text">, as they aren't yet tied to an
// Actual Services.prefs.*etBoolPref()
toggleBoolValue: function AC_toggleBoolValue() {
this._booleanValue.value = (this._booleanValue.value == "true" ? "false" : "true");
}
}
/* ============================== AboutConfig ==============================
*
* Main AboutConfig object and methods
*
* Implements User Interfaces for maintenance of a list of Preference settings
*
*/
var AboutConfig = {
filterInput: null,
_filterPrevInput: null,
_filterChangeTimer: null,
_prefsContainer: null,
_loadingContainer: null,
_list: null,
// Init the main AboutConfig dialog
init: function AC_init() {
this.filterInput = document.getElementById("filter-input");
this._prefsContainer = document.getElementById("prefs-container");
this._loadingContainer = document.getElementById("loading-container");
let list = Services.prefs.getChildList("", {}).filter(function(aElement) {
// Prevent display of "private" preferences
return !aElement.startsWith(PRIVATE_PREF_PREFIX);
});
this._list = list.sort().map( function AC_getMapPref(aPref) {
return new Pref(aPref);
}, this);
// Display the current prefs list (retains searchFilter value)
this.bufferFilterInput();
// Setup the prefs observers
Services.prefs.addObserver("", this, false);
},
// Uninit the main AboutConfig dialog
uninit: function AC_uninit() {
// Remove the prefs observer
Services.prefs.removeObserver("", this);
},
// Clear the filterInput value, to display the entire list
clearFilterInput: function AC_clearFilterInput() {
this.filterInput.value = "";
this.bufferFilterInput();
},
// Buffer down rapid changes in filterInput value from keyboard
bufferFilterInput: function AC_bufferFilterInput() {
if (this._filterChangeTimer) {
clearTimeout(this._filterChangeTimer);
}
this._filterChangeTimer = setTimeout((function() {
this._filterChangeTimer = null;
// Display updated prefs list when filterInput value settles
this._displayNewList();
}).bind(this), FILTER_CHANGE_TRIGGER);
},
// Update displayed list when filterInput value changes
_displayNewList: function AC_displayNewList() {
// This survives the search filter value past a page refresh
this.filterInput.setAttribute("value", this.filterInput.value);
// Don't start new filter search if same as last
if (this.filterInput.value == this._filterPrevInput) {
return;
}
this._filterPrevInput = this.filterInput.value;
// Clear list item selection and prefs list, get first buffer, set scrolling on
this.selected = "";
this._clearPrefsContainer();
this._addMorePrefsToContainer();
window.onscroll = this.onScroll.bind(this);
// Pause for screen to settle, then ensure at top
setTimeout((function() {
window.scrollTo(0, 0);
}).bind(this), INITIAL_PAGE_DELAY);
},
// Clear the displayed preferences list
_clearPrefsContainer: function AC_clearPrefsContainer() {
// Quick clear the prefsContainer list
let empty = this._prefsContainer.cloneNode(false);
this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer);
this._prefsContainer = empty;
// Quick clear the prefs li.HTML list
this._list.forEach(function(item) {
delete item.li;
});
},
// Get a small manageable block of prefs items, and add them to the displayed list
_addMorePrefsToContainer: function AC_addMorePrefsToContainer() {
// Create filter regex
let filterExp = this.filterInput.value ?
new RegExp(this.filterInput.value, "i") : null;
// Get a new block for the display list
let prefsBuffer = [];
for (let i = 0; i < this._list.length && prefsBuffer.length < PREFS_BUFFER_MAX; i++) {
if (!this._list[i].li && this._list[i].test(filterExp)) {
prefsBuffer.push(this._list[i]);
}
}
// Add the new block to the displayed list
for (let i = 0; i < prefsBuffer.length; i++) {
this._prefsContainer.appendChild(prefsBuffer[i].getOrCreateNewLINode());
}
// Determine if anything left to add later by scrolling
let anotherPrefsBufferRemains = false;
for (let i = 0; i < this._list.length; i++) {
if (!this._list[i].li && this._list[i].test(filterExp)) {
anotherPrefsBufferRemains = true;
break;
}
}
if (anotherPrefsBufferRemains) {
// If still more could be displayed, show the throbber
this._loadingContainer.style.display = "block";
} else {
// If no more could be displayed, hide the throbber, and stop noticing scroll events
this._loadingContainer.style.display = "none";
window.onscroll = null;
}
},
// If scrolling at the bottom, maybe add some more entries
onScroll: function AC_onScroll(aEvent) {
if (this._prefsContainer.scrollHeight - (window.pageYOffset + window.innerHeight) < PAGE_SCROLL_TRIGGER) {
if (!this._filterChangeTimer) {
this._addMorePrefsToContainer();
}
}
},
// Return currently selected list item node
get selected() {
return document.querySelector(".pref-item.selected");
},
// Set list item node as selected
set selected(aSelection) {
let currentSelection = this.selected;
if (aSelection == currentSelection) {
return;
}
// Clear any previous selection
if (currentSelection) {
currentSelection.classList.remove("selected");
currentSelection.removeEventListener("keypress", this.handleKeypress, false);
}
// Set any current selection
if (aSelection) {
aSelection.classList.add("selected");
aSelection.addEventListener("keypress", this.handleKeypress, false);
}
},
// Watch user key input so we can provide Enter key action, commit input values
handleKeypress: function AC_handleKeypress(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
aEvent.target.blur();
},
// Return the target list item node of an action event
getLINodeForEvent: function AC_getLINodeForEvent(aEvent) {
let node = aEvent.target;
while (node && node.nodeName != "li") {
node = node.parentNode;
}
return node;
},
// Return a pref of a list item node
_getPrefForNode: function AC_getPrefForNode(aNode) {
let pref = aNode.getAttribute("name");
return new Pref(pref);
},
// When list item name or value are tapped
selectOrToggleBoolPref: function AC_selectOrToggleBoolPref(aEvent) {
let node = this.getLINodeForEvent(aEvent);
// If not already selected, just do so
if (this.selected != node) {
this.selected = node;
return;
}
// If already selected, and value is boolean, toggle it
let pref = this._getPrefForNode(node);
if (pref.type != Services.prefs.PREF_BOOL) {
return;
}
this.toggleBoolPref(aEvent);
},
// When finalizing list input values due to blur
setIntOrStringPref: function AC_setIntOrStringPref(aEvent) {
let node = this.getLINodeForEvent(aEvent);
// Skip if locked
let pref = this._getPrefForNode(node);
if (pref.locked) {
return;
}
// Boolean inputs blur to remove focus from "button"
if (pref.type == Services.prefs.PREF_BOOL) {
return;
}
// String and Int inputs change / commit on blur
pref.value = aEvent.target.value;
},
// When we reset a pref to it's default value (note resetting a user created pref will delete it)
resetDefaultPref: function AC_resetDefaultPref(aEvent) {
let node = this.getLINodeForEvent(aEvent);
// If not already selected, do so
if (this.selected != node) {
this.selected = node;
}
// Reset will handle any locked condition
let pref = this._getPrefForNode(node);
pref.reset();
},
// When we want to toggle a bool pref
toggleBoolPref: function AC_toggleBoolPref(aEvent) {
let node = this.getLINodeForEvent(aEvent);
// Skip if locked, or not boolean
let pref = this._getPrefForNode(node);
if (pref.locked) {
return;
}
// Toggle, and blur to remove field focus
pref.value = !pref.value;
aEvent.target.blur();
},
// When Int inputs have their Up or Down arrows toggled
incrOrDecrIntPref: function AC_incrOrDecrIntPref(aEvent, aInt) {
let node = this.getLINodeForEvent(aEvent);
// Skip if locked
let pref = this._getPrefForNode(node);
if (pref.locked) {
return;
}
pref.value += aInt;
},
// Observe preference changes
observe: function AC_observe(aSubject, aTopic, aPrefName) {
let pref = new Pref(aPrefName);
// Ignore uninteresting preference changes, and external changes to "private" preferences
if ((aTopic != "nsPref:changed") || pref.name.startsWith(PRIVATE_PREF_PREFIX)) {
return;
}
// If pref type invalid, refresh display as user reset/removed an item from the list
if (pref.type == Services.prefs.PREF_INVALID) {
document.location.reload();
return;
}
// If pref not already in list, refresh display as it's being added
let item = document.querySelector(".pref-item[name=" + pref.name.quote() + "]");
if (!item) {
document.location.reload();
return;
}
// Else we're modifying a pref
item.setAttribute("value", pref.value);
let input = item.querySelector("input");
input.setAttribute("value", pref.value);
input.value = pref.value;
pref.default ?
item.querySelector(".reset").setAttribute("disabled", "true") :
item.querySelector(".reset").removeAttribute("disabled");
}
}
/* ============================== Pref ==============================
*
* Individual Preference object / methods
*
* Defines a Pref object, a document list item tied to Preferences Services
* And the methods by which they interact.
*
*/
function Pref(aName) {
this.name = aName;
}
Pref.prototype = {
get type() {
return Services.prefs.getPrefType(this.name);
},
get value() {
switch (this.type) {
case Services.prefs.PREF_BOOL:
return Services.prefs.getBoolPref(this.name);
case Services.prefs.PREF_INT:
return Services.prefs.getIntPref(this.name);
case Services.prefs.PREF_STRING:
default:
return Services.prefs.getCharPref(this.name);
}
},
set value(aPrefValue) {
switch (this.type) {
case Services.prefs.PREF_BOOL:
Services.prefs.setBoolPref(this.name, aPrefValue);
break;
case Services.prefs.PREF_INT:
Services.prefs.setIntPref(this.name, aPrefValue);
break;
case Services.prefs.PREF_STRING:
default:
Services.prefs.setCharPref(this.name, aPrefValue);
}
},
get default() {
return !Services.prefs.prefHasUserValue(this.name);
},
get locked() {
return Services.prefs.prefIsLocked(this.name);
},
reset: function AC_reset() {
Services.prefs.clearUserPref(this.name);
},
test: function AC_test(aValue) {
return aValue ? aValue.test(this.name) : true;
},
// Get existing or create new LI node for the pref
getOrCreateNewLINode: function AC_getOrCreateNewLINode() {
if (!this.li) {
this.li = document.createElement("li");
this.li.className = "pref-item";
this.li.setAttribute("name", this.name);
// Click callback to ensure list item selected even on no-action tap events
this.li.addEventListener("click",
function(aEvent) {
AboutConfig.selected = AboutConfig.getLINodeForEvent(aEvent);
},
false
);
// Create list item outline, bind to object actions
this.li.innerHTML =
"<div class='pref-name' " +
"onclick='AboutConfig.selectOrToggleBoolPref(event);'>" +
this.name +
"</div>" +
"<div class='pref-item-line'>" +
"<input class='pref-value' value='' " +
"onblur='AboutConfig.setIntOrStringPref(event);' " +
"onclick='AboutConfig.selectOrToggleBoolPref(event);'>" +
"</input>" +
"<div class='pref-button reset' " +
"onclick='AboutConfig.resetDefaultPref(event);'>" +
gStringBundle.GetStringFromName("pref.resetButton") +
"</div>" +
"<div class='pref-button toggle' " +
"onclick='AboutConfig.toggleBoolPref(event);'>" +
gStringBundle.GetStringFromName("pref.toggleButton") +
"</div>" +
"<div class='pref-button up' " +
"onclick='AboutConfig.incrOrDecrIntPref(event, 1);'>" +
"</div>" +
"<div class='pref-button down' " +
"onclick='AboutConfig.incrOrDecrIntPref(event, -1);'>" +
"</div>" +
"</div>";
// Delay providing the list item values, until the LI is returned and added to the document
setTimeout(this._valueSetup.bind(this), INNERHTML_VALUE_DELAY);
}
return this.li;
},
// Initialize list item object values
_valueSetup: function AC_valueSetup() {
this.li.setAttribute("type", this.type);
this.li.setAttribute("value", this.value);
let valDiv = this.li.querySelector(".pref-value");
valDiv.value = this.value;
switch(this.type) {
case Services.prefs.PREF_BOOL:
valDiv.setAttribute("type", "button");
this.li.querySelector(".up").setAttribute("disabled", true);
this.li.querySelector(".down").setAttribute("disabled", true);
break;
case Services.prefs.PREF_STRING:
valDiv.setAttribute("type", "text");
this.li.querySelector(".up").setAttribute("disabled", true);
this.li.querySelector(".down").setAttribute("disabled", true);
this.li.querySelector(".toggle").setAttribute("disabled", true);
break;
case Services.prefs.PREF_INT:
valDiv.setAttribute("type", "number");
this.li.querySelector(".toggle").setAttribute("disabled", true);
break;
}
this.li.setAttribute("default", this.default);
if (this.default) {
this.li.querySelector(".reset").setAttribute("disabled", true);
}
if (this.locked) {
valDiv.setAttribute("disabled", this.locked);
this.li.querySelector(".pref-name").setAttribute("locked", true);
}
}
}

View File

@ -0,0 +1,81 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
<!ENTITY % configDTD SYSTEM "chrome://browser/locale/config.dtd">
%configDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width; user-scalable=false" />
<link rel="stylesheet" href="chrome://browser/skin/config.css" type="text/css"/>
<script type="text/javascript;version=1.8" src="chrome://browser/content/pages/config.js"/>
</head>
<body dir="&locale.dir;" onload="NewPrefDialog.init(); AboutConfig.init();"
onunload="AboutConfig.uninit();">
<div class="toolbar">
<div class="toolbar-container">
<div id="new-pref-toggle-button" onclick="NewPrefDialog.toggleShowHide();"/>
<div class="toolbar-item" id="filter-container">
<div id="filter-search-button"/>
<input id="filter-input" type="search" placeholder="&empty.label;" value=""
oninput="AboutConfig.bufferFilterInput();"/>
<div id="filter-input-clear-button" onclick="AboutConfig.clearFilterInput();"/>
</div>
</div>
</div>
<div id="content" ontouchstart="AboutConfig.filterInput.blur();">
<div id="new-pref-container">
<li class="pref-item" id="new-pref-item">
<div class="pref-item-line">
<input class="pref-name" id="new-pref-name" type="text" placeholder="&addpref.name;"
onfocus="NewPrefDialog.focusName(event);"
oninput="NewPrefDialog.updateName(event);"/>
<select class="pref-value" id="new-pref-type" onchange="NewPrefDialog.type = event.target.value;">
<option value="boolean">&boolean.label;</option>
<option value="string">&string.label;</option>
<option value="int">&integer.label;</option>
</select>
</div>
<div class="pref-item-line" id="new-pref-line-boolean">
<input class="pref-value" id="new-pref-value-boolean" disabled="disabled"/>
<div class="pref-button toggle" onclick="NewPrefDialog.toggleBoolValue();">&toggle.label;</div>
</div>
<div class="pref-item-line">
<input class="pref-value" id="new-pref-value-string" placeholder="&string.placeholder;"/>
<input class="pref-value" id="new-pref-value-int" placeholder="&number.placeholder;" type="number"/>
</div>
<div class="pref-item-line">
<div class="pref-button cancel" id="negative-button" onclick="NewPrefDialog.hide();">&cancel.label;</div>
<div class="pref-button create" id="positive-button" onclick="NewPrefDialog.create(event);"></div>
</div>
</li>
</div>
<div id="prefs-shield"></div>
<ul id="prefs-container"/>
<ul id="loading-container"><li></li></ul>
</div>
</body>
</html>

View File

@ -6,11 +6,12 @@
chrome.jar:
% content browser %content/
content/aboutAddons.xhtml (content/pages/aboutAddons.xhtml)
content/aboutCertError.xhtml (content/pages/aboutCertError.xhtml)
content/aboutRights.xhtml (content/pages/aboutRights.xhtml)
content/blockedSite.xhtml (content/pages/blockedSite.xhtml)
content/config.xhtml (content/pages/config.xhtml)
content/netError.xhtml (content/pages/netError.xhtml)
content/aboutAddons.xhtml (content/pages/aboutAddons.xhtml)
#ifdef MOZ_CRASHREPORTER
content/crashprompt.xhtml (content/pages/crashprompt.xhtml)
#endif
@ -64,8 +65,7 @@ chrome.jar:
content/ContentAreaObserver.js (content/ContentAreaObserver.js)
content/BrowserTouchHandler.js (content/BrowserTouchHandler.js)
* content/WebProgress.js (content/WebProgress.js)
content/config.xul (content/config.xul)
content/config.js (content/config.js)
content/pages/config.js (content/pages/config.js)
* content/browser.xul (content/browser.xul)
content/browser.js (content/browser.js)
* content/browser-ui.js (content/browser-ui.js)
@ -103,6 +103,6 @@ chrome.jar:
content/RemoteTabsView.js (content/startui/RemoteTabsView.js)
#endif
% override chrome://global/content/config.xul chrome://browser/content/config.xul
% override chrome://global/content/config.xul chrome://browser/content/config.xhtml
% override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
% override chrome://mozapps/content/extensions/extensions.xul chrome://browser/content/aboutAddons.xhtml

View File

@ -3,14 +3,14 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY empty.label "Search">
<!ENTITY newpref.label "Add a New Preference">
<!ENTITY addpref.name "Name">
<!ENTITY addpref.value "Value">
<!ENTITY cancel.label "Cancel">
<!ENTITY reset.label "Reset">
<!ENTITY done.label "Done">
<!ENTITY toggle.label "Toggle">
<!ENTITY integer.label "Integer">
<!ENTITY string.label "String">
<!ENTITY boolean.label "Boolean">
<!ENTITY string.placeholder "Enter a string">
<!ENTITY number.placeholder "Enter a number">

View File

@ -0,0 +1,10 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
newPref.createButton=Create
newPref.privateButton=Private
newPref.changeButton=Change
pref.toggleButton=Toggle
pref.resetButton=Reset

View File

@ -12,8 +12,9 @@
locale/browser/aboutCertError.dtd (%chrome/aboutCertError.dtd)
locale/browser/browser.dtd (%chrome/browser.dtd)
locale/browser/browser.properties (%chrome/browser.properties)
locale/browser/region.properties (%chrome/region.properties)
locale/browser/config.dtd (%chrome/config.dtd)
locale/browser/config.properties (%chrome/config.properties)
locale/browser/region.properties (%chrome/region.properties)
locale/browser/preferences.dtd (%chrome/preferences.dtd)
locale/browser/aboutPanel.dtd (%chrome/aboutPanel.dtd)
locale/browser/searchPanel.dtd (%chrome/searchPanel.dtd)

View File

@ -2,96 +2,344 @@
* 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/. */
@media (max-width: 499px) {
#editor-container > hbox {
-moz-box-orient: vertical;
}
html,
body {
margin: 0;
padding: 0;
background-color: #ced7de;
-moz-user-select: none;
font-family: "Segoe UI", sans-serif;
-moz-text-size-adjust: none;
}
richlistitem {
-moz-box-align: center;
.toolbar {
width: 100%;
height: 3em;
position: fixed;
top: 0;
left: 0;
z-index: 10;
box-shadow: 0 0 3px #444;
background-color: #ced7de;
color: #000000;
font-weight: bold;
border-bottom: 2px solid;
-moz-border-bottom-colors: #ff9100 #f27900;
}
richlistitem .preferences-title {
pointer-events: none;
min-width: 200px;
-moz-box-flex: 1;
margin-right: 8px;
.toolbar-container {
max-width: 40em;
margin-left: auto;
margin-right: auto;
}
/* XXX look + sync */
richlistitem[default="false"] .preferences-title {
font-weight: bold;
#filter-container {
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-right: 0.5em;
height: 2em;
border: 1px solid transparent;
border-image-source: url("chrome://browser/skin/images/textfield.png");
border-image-slice: 1 1 3 1;
border-image-width: 1px 1px 3px 1px;
overflow: hidden;
display: flex;
flex-direction: row;
}
richlistitem .preferences-value {
min-width: 200px;
pointer-events: none;
-moz-box-flex: 4;
text-align: end;
color: grey;
#filter-input {
-moz-appearance: none;
border: none;
background-image: none;
background-color: transparent;
display: inline-block;
width: 12em;
min-width: 0;
color: #000000;
opacity: 1;
flex: 1 1 auto;
}
/* Editor */
#editor-row {
padding: 0;
background: #E9E9E9;
#filter-input:-moz-placeholder {
color: rgba(255,255,255,0.5);
}
#editor {
border-bottom: 1px solid rgb(207,207,207);
.toolbar input {
display: inline-block;
height: 100%;
min-width: 3em;
-moz-box-sizing: border-box;
opacity: 0.75;
}
#editor > hbox > #editor-name,
#editor > hbox > #editor-cancel,
#editor > hbox > #editor-done {
display: none;
#new-pref-toggle-button {
background-position: center center;
background-image: url("chrome://browser/skin/images/reader-plus-icon-xhdpi.png");
background-size: 48px 48px;
height: 48px;
width: 48px;
display: inline-block;
outline-style: none;
}
#editor-container > #editor > hbox > #editor-name,
#editor-container > #editor > hbox > #editor-cancel,
#editor-container > #editor > hbox > #editor-done {
display: -moz-box;
#filter-search-button {
background-image: url("chrome://browser/skin/images/search.png");
background-size: 32px 32px;
height: 32px;
width: 32px;
display: inline-block;
outline-style: none;
}
#editor-container > #editor > hbox > #editor-reset {
display: none;
#filter-input-clear-button {
background-image: url("chrome://browser/skin/images/search-clear-30.png");
background-size: 32px 32px;
height: 32px;
width: 32px;
display: inline-block;
outline-style: none;
}
#editor-container > hbox > label {
pointer-events: none;
color: black;
#filter-input[value=""] + #filter-input-clear-button {
display: none;
}
#editor + richlistitem {
display: none;
.toolbar-item {
display: inline-block;
height: 3em;
min-width: 3em;
float: right;
}
#editor[default="false"] .preferences-title {
font-weight: bold;
#content {
position: relative;
margin: 0;
margin-left: auto;
margin-right: auto;
padding-top: 3em;
padding-left: 0;
padding-right: 0;
min-height: 100%;
max-width: 40em;
}
#editor-setting setting {
border-color: transparent !important;
ul {
list-style-position: inside;
border: 1px solid #808080;
background-color: #ffffff;
min-height: 100%;
width: 100%;
padding-top: 0;
margin: 0;
padding-left: 0;
-moz-box-sizing: border-box;
box-shadow: 0 0 5px #000000;
overflow-x: hidden;
}
#editor-setting[type="string"] .setting-input {
-moz-box-flex: 4;
#new-pref-container {
width: 100%;
margin: 0;
background-color: #ffffff;
-moz-box-sizing: border-box;
box-shadow: 0 0 5px #000000;
overflow-x: hidden;
max-width: 40em;
max-height: 100%;
position: fixed;
top: 3em;
left: auto;
display: none;
z-index: 5;
}
#editor-setting[type="string"] .setting-input > textbox {
-moz-box-flex: 1;
#new-pref-container input,
#new-pref-container select {
border: none;
background-image: none;
}
/* bug 647650: keep 'text-align: right' here instead of using start/end since
* the field should looks like ltr as much as possible
*/
#editor-setting[type="string"] .setting-input > textbox:-moz-locale-dir(rtl) {
direction: ltr;
text-align: right;
#new-pref-container.show {
display: block;
}
#editor-buttons {
margin: 2px;
li {
list-style-type: none;
border-bottom: 1px solid #d3d3d3;
opacity: 1;
background-color: #ffffff;
cursor: pointer;
}
#new-pref-line-boolean,
#new-pref-value-string,
#new-pref-value-int {
display: none;
}
#new-pref-item[typestyle="boolean"] #new-pref-line-boolean,
#new-pref-item[typestyle="string"] #new-pref-value-string,
#new-pref-item[typestyle="int"] #new-pref-value-int {
display: block;
}
.pref-name,
.pref-value {
padding: 15px 10px;
text-align: left;
text-overflow: ellipsis;
overflow: hidden;
background-image: none;
}
.pref-value {
color: rgba(0,0,0,0.5);
flex: 1 1 auto;
border: none;
-moz-appearance: none;
background-image: none;
background-color: transparent;
}
.pref-name[locked] {
padding-right: 20px;
background-image: url("chrome://browser/skin/images/lock.png");
background-repeat: no-repeat;
background-position: right 50%;
background-size: auto 60%;
}
#new-pref-name {
width: 30em;
}
#new-pref-type {
display: inline-block !important;
border-left: 1px solid #d3d3d3;
width: 10em;
text-align: right;
}
.pref-item-line {
border-top: 1px solid rgba(0,0,0,0.05);
color: rgba(0,0,0,0.5);
display: flex;
flex-direction: row;
}
#new-pref-value-boolean {
flex: 1 1 auto;
}
/* Disable newPref dialog spinbuttons, use custom version from Android */
/* Filed Bug 962359 to enhance the default spinbutton style to be touch-friendly */
#new-pref-value-int {
-moz-appearance: textfield;
}
#new-pref-container .pref-button.toggle {
display: inline-block;
opacity: 1;
flex: 0 1 auto;
float: right;
}
#new-pref-container .pref-button.cancel,
#new-pref-container .pref-button.create {
display: inline-block;
opacity: 1;
flex: 1 1 auto;
}
.pref-item-line {
pointer-events: none;
}
#new-pref-container .pref-item-line,
.pref-item.selected .pref-item-line,
.pref-item:not(.selected) .pref-button.reset {
pointer-events: auto;
}
#new-pref-container .pref-button.create[disabled] {
color: #d3d3d3;
}
.pref-item.selected {
background-color: rgba(0,0,255,0.05);
}
.pref-button {
display: inline-block;
-moz-box-sizing: border-box;
text-align: center;
padding: 10px 1em;
border-left: 1px solid rgba(0,0,0,0.1);
opacity: 0;
transition-property: opacity;
transition-duration: 500ms;
}
.pref-item.selected .pref-item-line .pref-button {
opacity: 1;
}
.pref-item:not(.selected) .pref-item-line .pref-button:not(.reset) {
display: none;
}
.pref-item:not(.selected) .pref-button.reset {
opacity: 1;
}
/* Disable detail list item spinbuttons, use custom version from Android */
/* Filed Bug 962359 to enhance the default spinbutton style to be touch-friendly */
.pref-item input[type="number"] {
-moz-appearance: textfield;
}
.pref-button:active {
background-color: rgba(0,0,255,0.2);
}
.pref-button[disabled] {
display: none;
}
.pref-button.up {
background-image: url("chrome://browser/skin/images/arrowup-16.png");
background-position: center center;
background-repeat: no-repeat;
}
.pref-button.down {
background-image: url("chrome://browser/skin/images/arrowdown-16.png");
background-position: center center;
background-repeat: no-repeat;
}
#prefs-shield {
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
position: fixed;
top: 0;
left: 0;
opacity: 0;
transition-property: opacity;
transition-duration: 500ms;
display: none;
}
#prefs-shield[shown] {
display: block;
opacity: 1;
}
#loading-container > li {
background-image: url(chrome://global/skin/media/throbber.png);
background-position: center center;
background-repeat: no-repeat;
padding-left: 40px;
height: 3em;
width: 100%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

View File

@ -150,3 +150,10 @@ chrome.jar:
skin/images/arrow-left.png (images/arrow-left.png)
skin/images/arrow-left@1.4x.png (images/arrow-left@1.4x.png)
skin/images/arrow-left@1.8x.png (images/arrow-left@1.8x.png)
# AboutConfig specific:
skin/images/textfield.png (images/textfield.png)
skin/images/reader-plus-icon-xhdpi.png (images/reader-plus-icon-xhdpi.png)
skin/images/search.png (images/search.png)
skin/images/search-clear-30.png (images/search-clear-30.png)
skin/images/lock.png (images/lock.png)

View File

@ -7,3 +7,12 @@
#BMB_bookmarksPopup > menuitem[type="checkbox"] {
-moz-appearance: none !important; /* important, to override toolkit rule */
}
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button {
-moz-appearance: none;
border: 0;
}
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-moz-margin-start: 0;
}

View File

@ -45,8 +45,7 @@
}
}
.panel-wide-item[cui-areatype="menu-panel"] > toolbarbutton,
toolbarbutton[cui-areatype="menu-panel"] {
.panelUI-grid .toolbarbutton-1 {
margin-right: 0;
margin-left: 0;
margin-bottom: 0;
@ -63,3 +62,7 @@ toolbarbutton[cui-areatype="menu-panel"] {
#BMB_bookmarksPopup > menu > .menu-right {
-moz-margin-end: 0;
}
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-moz-margin-start: 4px;
}

View File

@ -3,6 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Customization mode */
#main-window:-moz-any([customize-entering],[customize-exiting]) #tab-view-deck {
pointer-events: none;
}
#nav-bar[customize-entered] > #nav-bar-customization-target {
margin: 1px 3px;
}

View File

@ -68,14 +68,20 @@
}
toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > .toolbarbutton-text,
#bookmarks-menu-button > toolbarbutton > .toolbarbutton-text,
:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarpaletteitem > toolbaritem > toolbarbutton > .toolbarbutton-text,
:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbaritem > toolbarbutton > .toolbarbutton-text,
:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarpaletteitem > toolbarbutton > .toolbarbutton-text,
:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarbutton > .toolbarbutton-text {
.panelUI-grid .panel-combined-button > .toolbarbutton-text,
.widget-overflow-list .toolbarbutton-menubutton-button > .toolbarbutton-text,
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-text {
font-size: @panelTextSize@;
}
.panelUI-grid .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text,
.panelUI-grid .toolbarbutton-1 > .toolbarbutton-multiline-text {
font-size: @panelTextSize@;
margin: 2px 0 0;
text-align: center;
-moz-hyphens: auto;
}
#wrapper-edit-controls:-moz-any([place="palette"],[place="panel"]) > #edit-controls,
#wrapper-zoom-controls:-moz-any([place="palette"],[place="panel"]) > #zoom-controls {
-moz-margin-start: 0;
@ -197,16 +203,26 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
#PanelUI-footer {
display: flex;
background-color: rgba(0, 0, 0, 0.05);
border-top: 1px solid rgba(0,0,0,.1);
box-shadow: 0 -1px 0 rgba(0,0,0,.15);
padding: 0;
margin: 0;
min-height: 4em;
}
#PanelUI-footer > toolbarseparator {
border: 0;
border-left: 1px solid rgba(0,0,0,0.1);
margin: 7px 0 7px;
}
#PanelUI-footer:hover > toolbarseparator {
margin: 0;
}
#PanelUI-help,
#PanelUI-customize,
#PanelUI-quit {
margin: -1px 0 0;
margin: 0;
padding: 10px 0;
-moz-appearance: none;
box-shadow: none;
@ -279,14 +295,14 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
#PanelUI-help:not([disabled]):hover,
#PanelUI-customize:hover,
#PanelUI-quit:not([disabled]):hover {
border-color: rgba(8,25,42,0.2);
border-top-color: rgba(8,25,42,0.1);
outline: 1px solid rgba(0,0,0,0.1);
background-color: rgba(0,0,0,0.1);
box-shadow: none;
}
#PanelUI-quit:not([disabled]):hover {
background-color: #d94141;
outline-color: #c23a3a;
}
#PanelUI-quit:not([disabled]):hover:active {
@ -305,6 +321,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
background-image: linear-gradient(rgb(38,115,191), rgb(38,125,191));
}
#customization-palette .toolbarbutton-multiline-text,
#customization-palette .toolbarbutton-text {
display: none;
}
@ -532,6 +549,20 @@ toolbarpaletteitem[place="palette"] > #search-container {
min-height: 28px;
}
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button::after {
content: "";
display: -moz-box;
width: 1px;
height: 18px;
-moz-margin-end: -1px;
background-image: linear-gradient(hsla(210,54%,20%,.2) 0, hsla(210,54%,20%,.2) 18px);
background-clip: padding-box;
background-position: center;
background-repeat: no-repeat;
background-size: 1px 18px;
box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
}
#PanelUI-developerItems > toolbarbutton[checked="true"],
#PanelUI-bookmarks > toolbarbutton[checked="true"],
#PanelUI-history > toolbarbutton[checked="true"],

View File

@ -1375,7 +1375,7 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
-moz-margin-start: 5px;
}
#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
#bookmarks-menu-button[cui-areatype="toolbar"]:not(.bookmark-item):not(.overflowedItem) > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
padding-top: 2px;
padding-bottom: 2px;
}

View File

@ -28,3 +28,19 @@
padding-top: 0;
padding-bottom: 0;
}
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button {
-moz-appearance: none;
border: 0;
-moz-margin-start: 3px;
}
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
padding: 0 2px;
-moz-padding-start: 0;
height: 18px;
}
.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
padding: 0 6px;
}

View File

@ -18,7 +18,7 @@ import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
final class HomeConfig {
public final class HomeConfig {
/**
* Used to determine what type of HomeFragment subclass to use when creating
* a given panel. With the exception of DYNAMIC, all of these types correspond
@ -100,11 +100,14 @@ final class HomeConfig {
private static final String JSON_KEY_LAYOUT = "layout";
private static final String JSON_KEY_VIEWS = "views";
private static final String JSON_KEY_DEFAULT = "default";
private static final String JSON_KEY_DISABLED = "disabled";
private static final int IS_DEFAULT = 1;
private static final int IS_DISABLED = 1;
public enum Flags {
DEFAULT_PANEL
DEFAULT_PANEL,
DISABLED_PANEL
}
public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
@ -140,6 +143,11 @@ final class HomeConfig {
mFlags.add(Flags.DEFAULT_PANEL);
}
final boolean isDisabled = (json.optInt(JSON_KEY_DISABLED, -1) == IS_DISABLED);
if (isDisabled) {
mFlags.add(Flags.DISABLED_PANEL);
}
validate();
}
@ -158,6 +166,21 @@ final class HomeConfig {
validate();
}
public PanelConfig(PanelConfig panelConfig) {
mType = panelConfig.mType;
mTitle = panelConfig.mTitle;
mId = panelConfig.mId;
mLayoutType = panelConfig.mLayoutType;
mViews = new ArrayList<ViewConfig>();
for (ViewConfig viewConfig : panelConfig.mViews) {
mViews.add(new ViewConfig(viewConfig));
}
mFlags = panelConfig.mFlags.clone();
validate();
}
public PanelConfig(PanelType type, String title, String id) {
this(type, title, id, EnumSet.noneOf(Flags.class));
}
@ -171,9 +194,9 @@ final class HomeConfig {
mType = type;
mTitle = title;
mId = id;
mFlags = flags;
mLayoutType = layoutType;
mViews = views;
mFlags = flags;
validate();
}
@ -232,6 +255,26 @@ final class HomeConfig {
return mFlags.contains(Flags.DEFAULT_PANEL);
}
public void setIsDefault(boolean isDefault) {
if (isDefault) {
mFlags.add(Flags.DEFAULT_PANEL);
} else {
mFlags.remove(Flags.DEFAULT_PANEL);
}
}
public boolean isDisabled() {
return mFlags.contains(Flags.DISABLED_PANEL);
}
public void setIsDisabled(boolean isDisabled) {
if (isDisabled) {
mFlags.add(Flags.DISABLED_PANEL);
} else {
mFlags.remove(Flags.DISABLED_PANEL);
}
}
public JSONObject toJSON() throws JSONException {
final JSONObject json = new JSONObject();
@ -260,6 +303,10 @@ final class HomeConfig {
json.put(JSON_KEY_DEFAULT, IS_DEFAULT);
}
if (mFlags.contains(Flags.DISABLED_PANEL)) {
json.put(JSON_KEY_DISABLED, IS_DISABLED);
}
return json;
}
@ -415,6 +462,13 @@ final class HomeConfig {
validate();
}
public ViewConfig(ViewConfig viewConfig) {
mType = viewConfig.mType;
mDatasetId = viewConfig.mDatasetId;
validate();
}
public ViewConfig(ViewType type, String datasetId) {
mType = type;
mDatasetId = datasetId;

View File

@ -28,6 +28,7 @@ import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.View;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
@ -296,14 +297,21 @@ public class HomePager extends ViewPager {
// in the pager.
setAdapter(null);
// Only keep enabled panels.
final List<PanelConfig> enabledPanels = new ArrayList<PanelConfig>();
for (PanelConfig panelConfig : panelConfigs) {
if (!panelConfig.isDisabled()) {
enabledPanels.add(panelConfig);
}
}
// Update the adapter with the new panel configs
adapter.update(panelConfigs);
adapter.update(enabledPanels);
// Hide the tab strip if the new configuration contains
// no panels for some reason.
final int count = (panelConfigs != null ? panelConfigs.size() : 0);
// Hide the tab strip if the new configuration contains no panels.
final int count = enabledPanels.size();
mTabStrip.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE);
// Re-install the adapter with the final state
// in the pager.
setAdapter(adapter);
@ -316,7 +324,7 @@ public class HomePager extends ViewPager {
mInitialPanelId = null;
} else {
for (int i = 0; i < count; i++) {
final PanelConfig panelConfig = panelConfigs.get(i);
final PanelConfig panelConfig = enabledPanels.get(i);
if (panelConfig.isDefault()) {
setCurrentItem(i, false);
break;

View File

@ -85,6 +85,9 @@
<!ENTITY pref_developer_remotedebugging_docs "Learn more">
<!ENTITY pref_remember_signons "Remember passwords">
<!ENTITY pref_category_home "Home">
<!ENTITY pref_category_home_panels "Panels">
<!-- Localization note: These are shown in the left sidebar on tablets -->
<!ENTITY pref_header_customize "Customize">
<!ENTITY pref_header_display "Display">
@ -151,11 +154,16 @@ size. -->
<!ENTITY pref_about_firefox "About &brandShortName;">
<!ENTITY pref_vendor_faqs "FAQs">
<!ENTITY pref_vendor_feedback "Give feedback">
<!ENTITY pref_search_set_default "Set as default">
<!ENTITY pref_search_default "Default">
<!ENTITY pref_search_remove "Remove">
<!ENTITY pref_dialog_set_default "Set as default">
<!ENTITY pref_dialog_default "Default">
<!ENTITY pref_dialog_remove "Remove">
<!ENTITY pref_search_last_toast "You can\'t remove or disable your last search engine.">
<!ENTITY pref_panels_show "Show">
<!ENTITY pref_panels_hide "Hide">
<!ENTITY datareporting_notification_title "&brandShortName; stats &amp; data">
<!ENTITY datareporting_notification_action_long "Choose what information to share">
<!ENTITY datareporting_notification_action "Choose what to share">

View File

@ -269,11 +269,15 @@ gbjar.sources += [
'preferences/AlignRightLinkPreference.java',
'preferences/AndroidImport.java',
'preferences/AndroidImportPreference.java',
'preferences/CustomListCategory.java',
'preferences/CustomListPreference.java',
'preferences/FontSizePreference.java',
'preferences/GeckoPreferenceFragment.java',
'preferences/GeckoPreferences.java',
'preferences/LinkPreference.java',
'preferences/MultiChoicePreference.java',
'preferences/PanelsPreference.java',
'preferences/PanelsPreferenceCategory.java',
'preferences/PrivateDataPreference.java',
'preferences/SearchEnginePreference.java',
'preferences/SearchPreferenceCategory.java',

View File

@ -0,0 +1,72 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.preferences;
import android.content.Context;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
public abstract class CustomListCategory extends PreferenceCategory {
protected CustomListPreference mDefaultReference;
public CustomListCategory(Context context) {
super(context);
}
public CustomListCategory(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomListCategory(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onAttachedToActivity() {
super.onAttachedToActivity();
setOrderingAsAdded(true);
}
/**
* Set the default to some available list item. Used if the current default is removed or
* disabled.
*/
protected void setFallbackDefault() {
if (getPreferenceCount() > 0) {
CustomListPreference aItem = (CustomListPreference) getPreference(0);
setDefault(aItem);
}
}
/**
* Removes the given item from the set of available list items.
* This only updates the UI, so callers are responsible for persisting any state.
*
* @param item The given item to remove.
*/
public void uninstall(CustomListPreference item) {
removePreference(item);
if (item == mDefaultReference) {
// If the default is being deleted, set a new default.
setFallbackDefault();
}
}
/**
* Sets the given item as the current default.
* This only updates the UI, so callers are responsible for persisting any state.
*
* @param item The intended new default.
*/
public void setDefault(CustomListPreference item) {
if (mDefaultReference != null) {
mDefaultReference.setIsDefault(false);
}
item.setIsDefault(true);
mDefaultReference = item;
}
}

View File

@ -0,0 +1,173 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.preferences;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.preference.Preference;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ThreadUtils;
/**
* Represents an element in a <code>CustomListCategory</code> preference menu.
* This preference con display a dialog when clicked, and also supports
* being set as a default item within the preference list category.
*/
public abstract class CustomListPreference extends Preference implements View.OnLongClickListener {
protected String LOGTAG = "CustomListPreference";
// Indices of the buttons of the Dialog.
public static final int INDEX_SET_DEFAULT_BUTTON = 0;
// Dialog item labels.
protected final String[] mDialogItems;
// Dialog displayed when this element is tapped.
protected AlertDialog mDialog;
// Cache label to avoid repeated use of the resource system.
public final String LABEL_IS_DEFAULT;
public final String LABEL_SET_AS_DEFAULT;
protected boolean mIsDefault;
// Enclosing parent category that contains this preference.
protected final CustomListCategory mParentCategory;
/**
* Create a preference object to represent a list preference that is attached to
* a category.
*
* @param context The activity context we operate under.
* @param parentCategory The PreferenceCategory this object exists within.
*/
public CustomListPreference(Context context, CustomListCategory parentCategory) {
super(context);
mParentCategory = parentCategory;
setLayoutResource(getPreferenceLayoutResource());
setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
CustomListPreference sPref = (CustomListPreference) preference;
sPref.showDialog();
return true;
}
});
Resources res = getContext().getResources();
// Fetch these strings now, instead of every time we ever want to relabel a button.
LABEL_IS_DEFAULT = res.getString(R.string.pref_default);
LABEL_SET_AS_DEFAULT = res.getString(R.string.pref_dialog_set_default);
mDialogItems = getDialogStrings();
}
/**
* Returns the Android resource id for the layout.
*/
protected abstract int getPreferenceLayoutResource();
/**
* Set whether this object's UI should display this as the default item. To ensure proper ordering,
* this method should only be called after this Preference is added to the PreferenceCategory.
* @param isDefault Flag indicating if this represents the default list item.
*/
public void setIsDefault(boolean isDefault) {
mIsDefault = isDefault;
if (isDefault) {
setOrder(0);
setSummary(LABEL_IS_DEFAULT);
} else {
setOrder(1);
setSummary("");
}
}
/**
* Returns the strings to be displayed in the dialog.
*/
abstract protected String[] getDialogStrings();
/**
* Display a dialog for this preference, when the preference is clicked.
*/
public void showDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(getTitle().toString());
builder.setItems(mDialogItems, new DialogInterface.OnClickListener() {
// Forward relevant events to the container class for handling.
@Override
public void onClick(DialogInterface dialog, int indexClicked) {
hideDialog();
onDialogIndexClicked(indexClicked);
}
});
configureDialogBuilder(builder);
// We have to construct the dialog itself on the UI thread.
mDialog = builder.create();
mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
// Called when the dialog is shown (so we're finally able to manipulate button enabledness).
@Override
public void onShow(DialogInterface dialog) {
configureShownDialog();
}
});
mDialog.show();
}
/**
* (Optional) Configure the AlertDialog builder.
*/
protected void configureDialogBuilder(AlertDialog.Builder builder) {
return;
}
abstract protected void onDialogIndexClicked(int index);
/**
* Disables buttons in the shown AlertDialog as required. The button elements are not created
* until after show is called, so this method has to be called from the onShowListener above.
* @see this.showDialog
*/
protected void configureShownDialog() {
// If this is already the default list item, disable the button for setting this as the default.
final TextView defaultButton = (TextView) mDialog.getListView().getChildAt(INDEX_SET_DEFAULT_BUTTON);
if (mIsDefault) {
defaultButton.setEnabled(false);
// Failure to unregister this listener leads to tapping the button dismissing the dialog
// without doing anything.
defaultButton.setOnClickListener(null);
}
}
/**
* Hide the dialog we previously created, if any.
*/
public void hideDialog() {
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
@Override
public boolean onLongClick(View view) {
// Show the preference dialog on long-press.
showDialog();
return true;
}
}

View File

@ -153,8 +153,8 @@ public class GeckoPreferences
final ListAdapter listAdapter = ((ListView) parent).getAdapter();
final Object listItem = listAdapter.getItem(position);
// Only SearchEnginePreference handles long clicks.
if (listItem instanceof SearchEnginePreference && listItem instanceof View.OnLongClickListener) {
// Only CustomListPreference handles long clicks.
if (listItem instanceof CustomListPreference && listItem instanceof View.OnLongClickListener) {
final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem;
return longClickListener.onLongClick(view);
}

View File

@ -0,0 +1,122 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.preferences;
import android.content.Context;
import android.content.res.Resources;
import android.preference.Preference;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.mozilla.gecko.R;
public class PanelsPreference extends CustomListPreference {
protected String LOGTAG = "PanelsPreference";
private static final int INDEX_SHOW_BUTTON = 1;
private static final int INDEX_REMOVE_BUTTON = 2;
private final String LABEL_HIDE;
private final String LABEL_SHOW;
protected boolean mIsHidden = false;
public PanelsPreference(Context context, CustomListCategory parentCategory) {
super(context, parentCategory);
Resources res = getContext().getResources();
LABEL_HIDE = res.getString(R.string.pref_panels_hide);
LABEL_SHOW = res.getString(R.string.pref_panels_show);
}
@Override
protected int getPreferenceLayoutResource() {
return R.layout.preference_panels;
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
// Override view handling so we can grey out "hidden" PanelPreferences.
view.setEnabled(!mIsHidden);
if (view instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) view;
for (int i = 0; i < group.getChildCount(); i++) {
group.getChildAt(i).setEnabled(!mIsHidden);
}
}
}
@Override
protected String[] getDialogStrings() {
Resources res = getContext().getResources();
// XXX: Don't provide the "Remove" string for now, because we only support built-in
// panels, which can only be disabled.
return new String[] { LABEL_SET_AS_DEFAULT,
LABEL_HIDE };
}
@Override
public void setIsDefault(boolean isDefault) {
mIsDefault = isDefault;
if (isDefault) {
setSummary(LABEL_IS_DEFAULT);
if (mIsHidden) {
// Unhide the panel if it's being set as the default.
setHidden(false);
}
} else {
setSummary("");
}
}
@Override
protected void onDialogIndexClicked(int index) {
switch(index) {
case INDEX_SET_DEFAULT_BUTTON:
mParentCategory.setDefault(this);
break;
case INDEX_SHOW_BUTTON:
((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
break;
case INDEX_REMOVE_BUTTON:
mParentCategory.uninstall(this);
break;
default:
Log.w(LOGTAG, "Selected index out of range: " + index);
}
}
@Override
protected void configureShownDialog() {
super.configureShownDialog();
// Handle Show/Hide buttons.
final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_SHOW_BUTTON);
hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
}
public void setHidden(boolean toHide) {
if (toHide) {
setIsDefault(false);
}
if (mIsHidden != toHide) {
mIsHidden = toHide;
notifyChanged();
}
}
public boolean isHidden() {
return mIsHidden;
}
}

View File

@ -0,0 +1,242 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.preferences;
import android.content.Context;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.gecko.home.HomeConfig;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
public class PanelsPreferenceCategory extends CustomListCategory {
public static final String LOGTAG = "PanelsPrefCategory";
protected HomeConfig mHomeConfig;
protected final List<PanelConfig> mPanelConfigs = new ArrayList<PanelConfig>();
protected UiAsyncTask<Void, Void, List<PanelConfig>> mLoadTask;
protected UiAsyncTask<Void, Void, Void> mSaveTask;
public PanelsPreferenceCategory(Context context) {
super(context);
initConfig(context);
}
public PanelsPreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
initConfig(context);
}
public PanelsPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initConfig(context);
}
protected void initConfig(Context context) {
mHomeConfig = HomeConfig.getDefault(context);
}
@Override
public void onAttachedToActivity() {
super.onAttachedToActivity();
loadHomeConfig();
}
/**
* Load the Home Panels config and populate the preferences screen and maintain local state.
*/
private void loadHomeConfig() {
mLoadTask = new UiAsyncTask<Void, Void, List<PanelConfig>>(ThreadUtils.getBackgroundHandler()) {
@Override
public List<PanelConfig> doInBackground(Void... params) {
return mHomeConfig.load();
}
@Override
public void onPostExecute(List<PanelConfig> panelConfigs) {
displayPanelConfig(panelConfigs);
}
};
mLoadTask.execute();
}
private void displayPanelConfig(List<PanelConfig> panelConfigs) {
for (PanelConfig panelConfig: panelConfigs) {
// Populate our local copy of the panels.
mPanelConfigs.add(panelConfig);
// Create and add the pref.
final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this);
pref.setTitle(panelConfig.getTitle());
pref.setKey(panelConfig.getId());
// XXX: Pull icon from PanelInfo.
addPreference(pref);
if (panelConfig.isDefault()) {
mDefaultReference = pref;
pref.setIsDefault(true);
}
if (panelConfig.isDisabled()) {
pref.setHidden(true);
}
}
}
/**
* Update HomeConfig off the main thread.
*
* @param panelConfigs Configuration to be saved
*/
private void saveHomeConfig() {
final List<PanelConfig> panelConfigs = makeConfigListDeepCopy();
mSaveTask = new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) {
@Override
public Void doInBackground(Void... params) {
mHomeConfig.save(panelConfigs);
return null;
}
};
mSaveTask.execute();
}
private List<PanelConfig> makeConfigListDeepCopy() {
List<PanelConfig> copiedList = new ArrayList<PanelConfig>();
for (PanelConfig panelConfig : mPanelConfigs) {
copiedList.add(new PanelConfig(panelConfig));
}
return copiedList;
}
@Override
public void setDefault(CustomListPreference pref) {
super.setDefault(pref);
updateConfigDefault();
saveHomeConfig();
}
@Override
protected void onPrepareForRemoval() {
if (mLoadTask != null) {
mLoadTask.cancel(true);
}
if (mSaveTask != null) {
mSaveTask.cancel(true);
}
}
/**
* Update the local HomeConfig default state from mDefaultReference.
*/
private void updateConfigDefault() {
String mId = null;
if (mDefaultReference != null) {
mId = mDefaultReference.getKey();
}
for (PanelConfig panelConfig : mPanelConfigs) {
if (TextUtils.equals(panelConfig.getId(), mId)) {
panelConfig.setIsDefault(true);
panelConfig.setIsDisabled(false);
} else {
panelConfig.setIsDefault(false);
}
}
}
@Override
public void uninstall(CustomListPreference pref) {
super.uninstall(pref);
// This could change the default, so update the local version of the config.
updateConfigDefault();
final String mId = pref.getKey();
PanelConfig toRemove = null;
for (PanelConfig panelConfig : mPanelConfigs) {
if (TextUtils.equals(panelConfig.getId(), mId)) {
toRemove = panelConfig;
break;
}
}
mPanelConfigs.remove(toRemove);
saveHomeConfig();
}
/**
* Update the hide/show state of the preference and save the HomeConfig
* changes.
*
* @param pref Preference to update
* @param toHide New hidden state of the preference
*/
protected void setHidden(PanelsPreference pref, boolean toHide) {
pref.setHidden(toHide);
ensureDefaultForHide(pref, toHide);
final String mId = pref.getKey();
for (PanelConfig panelConfig : mPanelConfigs) {
if (TextUtils.equals(panelConfig.getId(), mId)) {
panelConfig.setIsDisabled(toHide);
break;
}
}
saveHomeConfig();
}
/**
* Ensure a default is set (if possible) for hiding/showing a pref.
* If hiding, try to find an enabled pref to set as the default.
* If showing, set it as the default if there is no default currently.
*
* This updates the local HomeConfig state.
*
* @param pref Preference getting updated
* @param toHide Boolean of the new hidden state
*/
private void ensureDefaultForHide(PanelsPreference pref, boolean toHide) {
if (toHide) {
// Set a default if there is an enabled panel left.
if (pref == mDefaultReference) {
setFallbackDefault();
updateConfigDefault();
}
} else {
if (mDefaultReference == null) {
super.setDefault(pref);
updateConfigDefault();
}
}
}
/**
* When the default panel is removed or disabled, find an enabled panel
* if possible and set it as mDefaultReference.
*/
@Override
protected void setFallbackDefault() {
for (int i = 0; i < getPreferenceCount(); i++) {
final PanelsPreference pref = (PanelsPreference) getPreference(i);
if (!pref.isHidden()) {
super.setDefault(pref);
return;
}
}
mDefaultReference = null;
}
}

View File

@ -6,15 +6,12 @@ package org.mozilla.gecko.preferences;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.preference.Preference;
import android.text.SpannableString;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.util.Iterator;
@ -32,66 +29,21 @@ import org.mozilla.gecko.widget.FaviconView;
/**
* Represents an element in the list of search engines on the preferences menu.
*/
public class SearchEnginePreference extends Preference implements View.OnLongClickListener {
private static final String LOGTAG = "SearchEnginePreference";
public class SearchEnginePreference extends CustomListPreference {
protected String LOGTAG = "SearchEnginePreference";
// Indices in button array of the AlertDialog of the three buttons.
public static final int INDEX_SET_DEFAULT_BUTTON = 0;
public static final int INDEX_REMOVE_BUTTON = 1;
// Cache label to avoid repeated use of the resource system.
public final String LABEL_IS_DEFAULT;
// Specifies if this engine is configured as the default search engine.
private boolean mIsDefaultEngine;
// Dialog element labels.
private String[] mDialogItems;
// The popup displayed when this element is tapped.
private AlertDialog mDialog;
private final SearchPreferenceCategory mParentCategory;
protected static final int INDEX_REMOVE_BUTTON = 1;
// The icon to display in the prompt when clicked.
private BitmapDrawable mPromptIcon;
// The bitmap backing the drawable above - needed separately for the FaviconView.
private Bitmap mIconBitmap;
private FaviconView mFaviconView;
/**
* Create a preference object to represent a search engine that is attached to category
* containingCategory.
* @param context The activity context we operate under.
* @param parentCategory The PreferenceCategory this object exists within.
* @see this.setSearchEngine
*/
public SearchEnginePreference(Context context, SearchPreferenceCategory parentCategory) {
super(context);
mParentCategory = parentCategory;
Resources res = getContext().getResources();
// Set the layout resource for this preference - includes a FaviconView.
setLayoutResource(R.layout.preference_search_engine);
setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
SearchEnginePreference sPref = (SearchEnginePreference) preference;
sPref.showDialog();
return true;
}
});
// Fetch this resource now, instead of every time we ever want to relabel a button.
LABEL_IS_DEFAULT = res.getString(R.string.pref_search_default);
// Set up default dialog items.
mDialogItems = new String[] { res.getString(R.string.pref_search_set_default),
res.getString(R.string.pref_search_remove) };
super(context, parentCategory);
}
/**
@ -103,18 +55,72 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
@Override
protected void onBindView(View view) {
super.onBindView(view);
// Set the icon in the FaviconView.
mFaviconView = ((FaviconView) view.findViewById(R.id.search_engine_icon));
mFaviconView.updateAndScaleImage(mIconBitmap, getTitle().toString());
}
@Override
public boolean onLongClick(View view) {
// Show the preference dialog on long-press.
showDialog();
return true;
protected int getPreferenceLayoutResource() {
return R.layout.preference_search_engine;
}
/**
* Returns the strings to be displayed in the dialog.
*/
@Override
protected String[] getDialogStrings() {
Resources res = getContext().getResources();
return new String[] { LABEL_SET_AS_DEFAULT,
res.getString(R.string.pref_dialog_remove) };
}
@Override
public void showDialog() {
// If this is the last engine, then we are the default, and none of the options
// on this menu can do anything.
if (mParentCategory.getPreferenceCount() == 1) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getContext(), R.string.pref_search_last_toast, Toast.LENGTH_SHORT).show();
}
});
return;
}
super.showDialog();
}
@Override
protected void configureDialogBuilder(AlertDialog.Builder builder) {
// Copy the icon from this object to the prompt we produce. We lazily create the drawable,
// as the user may not ever actually tap this object.
if (mPromptIcon == null && mIconBitmap != null) {
mPromptIcon = new BitmapDrawable(getContext().getResources(), mFaviconView.getBitmap());
}
builder.setIcon(mPromptIcon);
}
@Override
protected void onDialogIndexClicked(int index) {
switch (index) {
case INDEX_SET_DEFAULT_BUTTON:
mParentCategory.setDefault(this);
break;
case INDEX_REMOVE_BUTTON:
mParentCategory.uninstall(this);
break;
default:
Log.w(LOGTAG, "Selected index out of range.");
break;
}
}
/**
* Configure this Preference object from the Gecko search engine JSON object.
* @param geckoEngineJSON The Gecko-formatted JSON object representing the search engine.
@ -184,121 +190,4 @@ public class SearchEnginePreference extends Preference implements View.OnLongCli
Log.e(LOGTAG, "NullPointerException creating Bitmap. Most likely a zero-length bitmap.", e);
}
}
/**
* Set if this object's UI should show that this is the default engine. To ensure proper ordering,
* this method should only be called after this Preference is added to the PreferenceCategory.
* @param isDefault Flag indicating if this represents the default engine.
*/
public void setIsDefaultEngine(boolean isDefault) {
mIsDefaultEngine = isDefault;
if (isDefault) {
setOrder(0);
setSummary(LABEL_IS_DEFAULT);
} else {
setOrder(1);
setSummary("");
}
}
/**
* Display the AlertDialog providing options to reconfigure this search engine. Sets an event
* listener to disable buttons in the dialog as appropriate after they have been constructed by
* Android.
* @see this.configureShownDialog
* @see this.hideDialog
*/
public void showDialog() {
// If we are the only engine left, then we are the default engine, and none of the options
// on this menu can do anything.
if (mParentCategory.getPreferenceCount() == 1) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getContext(), R.string.pref_search_last_toast, Toast.LENGTH_SHORT).show();
}
});
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(getTitle().toString());
builder.setItems(mDialogItems, new DialogInterface.OnClickListener() {
// Forward the various events that we care about to the container class for handling.
@Override
public void onClick(DialogInterface dialog, int indexClicked) {
hideDialog();
switch (indexClicked) {
case INDEX_SET_DEFAULT_BUTTON:
mParentCategory.setDefault(SearchEnginePreference.this);
break;
case INDEX_REMOVE_BUTTON:
mParentCategory.uninstall(SearchEnginePreference.this);
break;
default:
Log.w(LOGTAG, "Selected index out of range.");
break;
}
}
});
// Copy the icon from this object to the prompt we produce. We lazily create the drawable,
// as the user may not ever actually tap this object.
if (mPromptIcon == null && mIconBitmap != null) {
mPromptIcon = new BitmapDrawable(getContext().getResources(), mFaviconView.getBitmap());
}
builder.setIcon(mPromptIcon);
// Icons are hidden until Bug 926711 is fixed.
//builder.setIcon(mPromptIcon);
// We have to construct the dialog itself on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
mDialog = builder.create();
mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
// Called when the dialog is shown (so we're finally able to manipulate button enabledness).
@Override
public void onShow(DialogInterface dialog) {
configureShownDialog();
}
});
mDialog.show();
}
});
}
/**
* Hide the dialog we previously created, if any.
*/
public void hideDialog() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
// Null check so we can chain engine-mutating methods up in SearchPreferenceCategory
// without consequence.
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
});
}
/**
* Disables buttons in the shown AlertDialog as required. The button elements are not created
* until after we call show, so this method has to be called from the onShowListener above.
* @see this.showDialog
*/
private void configureShownDialog() {
// If we are the default engine, disable the "Set as default" button.
final TextView defaultButton = (TextView) mDialog.getListView().getChildAt(INDEX_SET_DEFAULT_BUTTON);
// Disable "Set as default" button if we are already the default.
if (mIsDefaultEngine) {
defaultButton.setEnabled(false);
// Failure to unregister this listener leads to tapping the button dismissing the dialog
// without doing anything.
defaultButton.setOnClickListener(null);
}
}
}

View File

@ -6,43 +6,36 @@ package org.mozilla.gecko.preferences;
import android.content.Context;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.util.GeckoEventListener;
public class SearchPreferenceCategory extends PreferenceCategory implements GeckoEventListener {
public class SearchPreferenceCategory extends CustomListCategory implements GeckoEventListener {
public static final String LOGTAG = "SearchPrefCategory";
private SearchEnginePreference mDefaultEngineReference;
// These seemingly redundant constructors are mandated by the Android system, else it fails to
// inflate this object.
public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
public SearchPreferenceCategory(Context context) {
super(context);
}
public SearchPreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SearchPreferenceCategory(Context context) {
super(context);
public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onAttachedToActivity() {
super.onAttachedToActivity();
// Ensures default engine remains at top of list.
setOrderingAsAdded(true);
// Register for SearchEngines messages and request list of search engines from Gecko.
GeckoAppShell.registerEventListener("SearchEngines:Data", this);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
@ -53,6 +46,20 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
GeckoAppShell.unregisterEventListener("SearchEngines:Data", this);
}
@Override
public void setDefault(CustomListPreference item) {
super.setDefault(item);
sendGeckoEngineEvent("SearchEngines:SetDefault", item.getTitle().toString());
}
@Override
public void uninstall(CustomListPreference item) {
super.uninstall(item);
sendGeckoEngineEvent("SearchEngines:Remove", item.getTitle().toString());
}
@Override
public void handleMessage(String event, final JSONObject data) {
if (event.equals("SearchEngines:Data")) {
@ -93,8 +100,8 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
// We set this here, not in setSearchEngineFromJSON, because it allows us to
// keep a reference to the default engine to use when the AlertDialog
// callbacks are used.
enginePreference.setIsDefaultEngine(true);
mDefaultEngineReference = enginePreference;
enginePreference.setIsDefault(true);
mDefaultReference = enginePreference;
}
} catch (JSONException e) {
Log.e(LOGTAG, "JSONException parsing engine at index " + i, e);
@ -103,58 +110,19 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
}
}
/**
* Set the default engine to any available engine. Used if the current default is removed or
* disabled.
*/
private void setFallbackDefaultEngine() {
if (getPreferenceCount() > 0) {
SearchEnginePreference aEngine = (SearchEnginePreference) getPreference(0);
setDefault(aEngine);
}
}
/**
* Helper method to send a particular event string to Gecko with an associated engine name.
* @param event The type of event to send.
* @param engine The engine to which the event relates.
*/
private void sendGeckoEngineEvent(String event, SearchEnginePreference engine) {
private void sendGeckoEngineEvent(String event, String engineName) {
JSONObject json = new JSONObject();
try {
json.put("engine", engine.getTitle());
json.put("engine", engineName);
} catch (JSONException e) {
Log.e(LOGTAG, "JSONException creating search engine configuration change message for Gecko.", e);
return;
}
GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent(event, json.toString()));
}
// Methods called by tapping items on the submenus for each search engine are below.
/**
* Removes the given engine from the set of available engines.
* @param engine The engine to remove.
*/
public void uninstall(SearchEnginePreference engine) {
removePreference(engine);
if (engine == mDefaultEngineReference) {
// If they're deleting their default engine, get them a new default engine.
setFallbackDefaultEngine();
}
sendGeckoEngineEvent("SearchEngines:Remove", engine);
}
/**
* Sets the given engine as the current default engine.
* @param engine The intended new default engine.
*/
public void setDefault(SearchEnginePreference engine) {
engine.setIsDefaultEngine(true);
mDefaultEngineReference.setIsDefaultEngine(false);
mDefaultEngineReference = engine;
sendGeckoEngineEvent("SearchEngines:SetDefault", engine);
}
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="?android:attr/scrollbarSize">
<TextView android:id="@+android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="@+android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:maxLines="2" />
</LinearLayout>

View File

@ -15,6 +15,13 @@
android:value="preferences_search"/>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_category_home"
android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
<extra android:name="resource"
android:value="preferences_home" />
</PreferenceScreen>
<org.mozilla.gecko.preferences.AndroidImportPreference
android:key="android.not_a_preference.import_android"
gecko:entries="@array/pref_import_android_entries"

View File

@ -22,6 +22,12 @@
android:value="preferences_search"/>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_category_home"
android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
<extra android:name="resource"
android:value="preferences_home" />
</PreferenceScreen>
<org.mozilla.gecko.preferences.AndroidImportPreference
android:key="android.not_a_preference.import_android"
gecko:entries="@array/pref_import_android_entries"

View File

@ -6,6 +6,7 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:enabled="false">
<PreferenceScreen android:title="@string/pref_category_search" >
<intent android:action="android.intent.action.VIEW"
android:targetPackage="@string/android_package_name"
@ -16,6 +17,16 @@
</intent>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_category_home" >
<intent android:action="android.intent.action.VIEW"
android:targetPackage="@string/android_package_name"
android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" >
<extra
android:name="resource"
android:value="preferences_home" />
</intent>
</PreferenceScreen>
<org.mozilla.gecko.preferences.AndroidImportPreference
android:key="android.not_a_preference.import_android"
gecko:entries="@array/pref_import_android_entries"

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:title="@string/pref_category_home"
android:enabled="false">
<org.mozilla.gecko.preferences.PanelsPreferenceCategory
android:title="@string/pref_category_home_panels"/>
</PreferenceScreen>

View File

@ -108,6 +108,9 @@
<string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
<string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>
<string name="pref_category_home">&pref_category_home;</string>
<string name="pref_category_home_panels">&pref_category_home_panels;</string>
<string name="pref_header_customize">&pref_header_customize;</string>
<string name="pref_header_display">&pref_header_display;</string>
<string name="pref_header_privacy_short">&pref_header_privacy_short;</string>
@ -169,12 +172,15 @@
<string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
<string name="pref_vendor_feedback">&pref_vendor_feedback;</string>
<!-- Strings used in default search provider config preferences menu -->
<string name="pref_search_set_default">&pref_search_set_default;</string>
<string name="pref_search_default">&pref_search_default;</string>
<string name="pref_search_remove">&pref_search_remove;</string>
<string name="pref_dialog_set_default">&pref_dialog_set_default;</string>
<string name="pref_default">&pref_dialog_default;</string>
<string name="pref_dialog_remove">&pref_dialog_remove;</string>
<string name="pref_search_last_toast">&pref_search_last_toast;</string>
<string name="pref_panels_show">&pref_panels_show;</string>
<string name="pref_panels_hide">&pref_panels_hide;</string>
<string name="datareporting_notification_title">&datareporting_notification_title;</string>
<string name="datareporting_notification_action_long">&datareporting_notification_action_long;</string>
<string name="datareporting_notification_action">&datareporting_notification_action;</string>

View File

@ -32,6 +32,7 @@ public class testSettingsMenuItems extends PixelTest {
// Customize menu items.
String[][] OPTIONS_CUSTOMIZE = {
{ "Search settings", "", "Show search suggestions", "Installed search engines"},
{ "Home", "", "Panels" },
{ "Import from Android", "", "Bookmarks", "History", "Import" },
{ "Tabs", "Don't restore after quitting " + BRAND_NAME, "Always restore", "Don't restore after quitting " + BRAND_NAME },
};

View File

@ -386,6 +386,8 @@
@BINPATH@/components/nsPrompter.js
@BINPATH@/components/TelemetryPing.js
@BINPATH@/components/TelemetryPing.manifest
@BINPATH@/components/TelemetryStartup.js
@BINPATH@/components/TelemetryStartup.manifest
@BINPATH@/components/Webapps.js
@BINPATH@/components/Webapps.manifest
@BINPATH@/components/AppsService.js

View File

@ -72,6 +72,16 @@ function PromiseWorker(url, log) {
* Used for debugging purposes.
*/
this._id = 0;
/**
* The instant at which the worker was launched.
*/
this.launchTimeStamp = null;
/**
* Timestamps provided by the worker for statistics purposes.
*/
this.workerTimeStamps = null;
}
PromiseWorker.prototype = {
/**
@ -85,6 +95,10 @@ PromiseWorker.prototype = {
worker
});
// We assume that we call to _worker for the purpose of calling
// postMessage().
this.launchTimeStamp = Date.now();
/**
* Receive errors that are not instances of OS.File.Error, propagate
* them to the listeners.
@ -129,6 +143,9 @@ PromiseWorker.prototype = {
throw new Error("Internal error: expecting msg " + handler.id + ", " +
" got " + data.id + ": " + JSON.stringify(msg.data));
}
if ("timeStamps" in data) {
self.workerTimeStamps = data.timeStamps;
}
if ("ok" in data) {
// Pass the data to the listeners.
deferred.resolve(data);

View File

@ -55,7 +55,7 @@ Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
// If profileDir is not available, osfile.jsm has been imported before the
@ -138,9 +138,10 @@ let Scheduler = {
this.resetTimer = setTimeout(File.resetWorker, delay);
},
post: function post(...args) {
post: function post(method, ...args) {
if (this.shutdown) {
LOG("OS.File is not available anymore. The following request has been rejected.", args);
LOG("OS.File is not available anymore. The following request has been rejected.",
method, args);
return Promise.reject(new Error("OS.File has been shut down."));
}
if (!worker) {
@ -148,21 +149,30 @@ let Scheduler = {
worker = new PromiseWorker(
"resource://gre/modules/osfile/osfile_async_worker.js", LOG);
}
if (!this.launched && SharedAll.Config.DEBUG) {
let firstLaunch = !this.launched;
this.launched = true;
if (firstLaunch && SharedAll.Config.DEBUG) {
// If we have delayed sending SET_DEBUG, do it now.
worker.post("SET_DEBUG", [true]);
}
this.launched = true;
// By convention, the last argument of any message may be an |options| object.
let methodArgs = args[1];
let options = methodArgs ? methodArgs[methodArgs.length - 1] : null;
let promise = worker.post.apply(worker, args);
let options;
let methodArgs = args[0];
if (methodArgs) {
options = methodArgs[methodArgs.length - 1];
}
let promise = worker.post(method,...args);
return this.latestPromise = promise.then(
function onSuccess(data) {
if (firstLaunch) {
Scheduler._updateTelemetry();
}
// Don't restart the timer when reseting the worker, since that will
// lead to an endless "resetWorker()" loop.
if (args[0] != "Meta_reset") {
if (method != "Meta_reset") {
Scheduler.restartTimer();
}
@ -194,9 +204,13 @@ let Scheduler = {
return data.ok;
},
function onError(error) {
if (firstLaunch) {
Scheduler._updateTelemetry();
}
// Don't restart the timer when reseting the worker, since that will
// lead to an endless "resetWorker()" loop.
if (args[0] != "Meta_reset") {
if (method != "Meta_reset") {
Scheduler.restartTimer();
}
@ -215,6 +229,26 @@ let Scheduler = {
throw error;
}
);
},
/**
* Post Telemetry statistics.
*
* This is only useful on first launch.
*/
_updateTelemetry: function() {
let workerTimeStamps = worker.workerTimeStamps;
if (!workerTimeStamps) {
// If the first call to OS.File results in an uncaught errors,
// the timestamps are absent. As this case is a developer error,
// let's not waste time attempting to extract telemetry from it.
return;
}
let HISTOGRAM_LAUNCH = Services.telemetry.getHistogramById("OSFILE_WORKER_LAUNCH_MS");
HISTOGRAM_LAUNCH.add(worker.workerTimeStamps.entered - worker.launchTimeStamp);
let HISTOGRAM_READY = Services.telemetry.getHistogramById("OSFILE_WORKER_READY_MS");
HISTOGRAM_READY.add(worker.workerTimeStamps.loaded - worker.launchTimeStamp);
}
};
@ -852,10 +886,14 @@ File.writeAtomic = function writeAtomic(path, buffer, options = {}) {
// - the buffer is effectively shared (not neutered) between both
// threads;
// - we take care of any |byteOffset|.
return Scheduler.post("writeAtomic",
let refObj = {};
TelemetryStopwatch.start("OSFILE_WRITEATOMIC_JANK_MS", refObj);
let promise = Scheduler.post("writeAtomic",
[Type.path.toMsg(path),
Type.void_t.in_ptr.toMsg(buffer),
options], [options, buffer]);
TelemetryStopwatch.finish("OSFILE_WRITEATOMIC_JANK_MS", refObj);
return promise;
};
File.removeDir = function(path, options = {}) {

View File

@ -12,11 +12,29 @@ if (this.Components) {
(function(exports) {
"use strict";
// Timestamps, for use in Telemetry.
// The object is set to |null| once it has been sent
// to the main thread.
let timeStamps = {
entered: Date.now(),
loaded: null
};
importScripts("resource://gre/modules/osfile.jsm");
let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
let LOG = SharedAll.LOG.bind(SharedAll, "Agent");
// Post a message to the parent, decorate it with statistics if
// necessary. Use this instead of self.postMessage.
function post(message, ...transfers) {
if (timeStamps) {
message.timeStamps = timeStamps;
timeStamps = null;
}
self.postMessage(message, ...transfers);
}
/**
* Communications with the controller.
*
@ -70,33 +88,33 @@ if (this.Components) {
if (result instanceof Meta) {
if ("transfers" in result.meta) {
// Take advantage of zero-copy transfers
self.postMessage({ok: result.data, id: id, durationMs: durationMs},
post({ok: result.data, id: id, durationMs: durationMs},
result.meta.transfers);
} else {
self.postMessage({ok: result.data, id:id, durationMs: durationMs});
post({ok: result.data, id:id, durationMs: durationMs});
}
if (result.meta.shutdown || false) {
// Time to close the worker
self.close();
}
} else {
self.postMessage({ok: result, id:id, durationMs: durationMs});
post({ok: result, id:id, durationMs: durationMs});
}
} else if (exn == StopIteration) {
// StopIteration cannot be serialized automatically
LOG("Sending back StopIteration");
self.postMessage({StopIteration: true, id: id, durationMs: durationMs});
post({StopIteration: true, id: id, durationMs: durationMs});
} else if (exn instanceof exports.OS.File.Error) {
LOG("Sending back OS.File error", exn, "id is", id);
// Instances of OS.File.Error know how to serialize themselves
// (deserialization ensures that we end up with OS-specific
// instances of |OS.File.Error|)
self.postMessage({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs});
post({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs});
} else {
// Other exceptions do not, and should be propagated through DOM's
// built-in mechanism for uncaught errors, although this mechanism
// may lose interesting information.
LOG("Sending back regular error", exn, exn.stack, "id is", id);
LOG("Sending back regular error", exn, exn.moduleStack || exn.stack, "id is", id);
throw exn;
}
@ -466,4 +484,6 @@ if (this.Components) {
});
}
};
timeStamps.loaded = Date.now();
})(this);

View File

@ -160,7 +160,6 @@ let test = maketest("Main", function main(test) {
yield test_iter();
yield test_exists();
yield test_debug_test();
yield test_duration();
info("Test is over");
SimpleTest.finish();
});
@ -616,90 +615,3 @@ let test_debug_test = maketest("debug_test", function debug_test(test) {
});
/**
* Test optional duration reporting that can be used for telemetry.
*/
let test_duration = maketest("duration", function duration(test) {
return Task.spawn(function() {
Services.prefs.setBoolPref("toolkit.osfile.log", true);
// Options structure passed to a OS.File copy method.
let copyOptions = {
// This field should be overridden with the actual duration
// measurement.
outExecutionDuration: null
};
let currentDir = yield OS.File.getCurrentDirectory();
let pathSource = OS.Path.join(currentDir, EXISTING_FILE);
let copyFile = pathSource + ".bak";
let testOptions = function testOptions(options, name) {
test.info("Checking outExecutionDuration for operation: " + name);
test.info(name + ": Gathered method duration time: " +
options.outExecutionDuration + "ms");
// Making sure that duration was updated.
test.ok(typeof options.outExecutionDuration === "number",
name + ": Operation duration is a number");
test.ok(options.outExecutionDuration >= 0,
name + ": Operation duration time is non-negative.");
};
// Testing duration of OS.File.copy.
yield OS.File.copy(pathSource, copyFile, copyOptions);
testOptions(copyOptions, "OS.File.copy");
yield OS.File.remove(copyFile);
// Trying an operation where options are cloned.
let pathDest = OS.Path.join(OS.Constants.Path.tmpDir,
"osfile async test read writeAtomic.tmp");
let tmpPath = pathDest + ".tmp";
let readOptions = {
outExecutionDuration: null
};
let contents = yield OS.File.read(pathSource, undefined, readOptions);
testOptions(readOptions, "OS.File.read");
// Options structure passed to a OS.File writeAtomic method.
let writeAtomicOptions = {
// This field should be first initialized with the actual
// duration measurement then progressively incremented.
outExecutionDuration: null,
tmpPath: tmpPath
};
yield OS.File.writeAtomic(pathDest, contents, writeAtomicOptions);
testOptions(writeAtomicOptions, "OS.File.writeAtomic");
yield OS.File.remove(pathDest);
test.info("Ensuring that we can use outExecutionDuration to accumulate durations");
let ARBITRARY_BASE_DURATION = 5;
copyOptions = {
// This field should now be incremented with the actual duration
// measurement.
outExecutionDuration: ARBITRARY_BASE_DURATION
};
let backupDuration = ARBITRARY_BASE_DURATION;
// Testing duration of OS.File.copy.
yield OS.File.copy(pathSource, copyFile, copyOptions);
test.ok(copyOptions.outExecutionDuration >= backupDuration, "duration has increased 1");
backupDuration = copyOptions.outExecutionDuration;
yield OS.File.remove(copyFile, copyOptions);
test.ok(copyOptions.outExecutionDuration >= backupDuration, "duration has increased 2");
// Trying an operation where options are cloned.
// Options structure passed to a OS.File writeAtomic method.
writeAtomicOptions = {
// This field should be overridden with the actual duration
// measurement.
outExecutionDuration: copyOptions.outExecutionDuration,
tmpPath: tmpPath
};
backupDuration = writeAtomicOptions.outExecutionDuration;
yield OS.File.writeAtomic(pathDest, contents, writeAtomicOptions);
test.ok(copyOptions.outExecutionDuration >= backupDuration, "duration has increased 3");
OS.File.remove(pathDest);
// Testing an operation that doesn't take arguments at all
let file = yield OS.File.open(pathSource);
yield file.stat();
yield file.close();
});
});

View File

@ -0,0 +1,91 @@
let {OS} = Components.utils.import("resource://gre/modules/osfile.jsm", {});
let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
/**
* Test optional duration reporting that can be used for telemetry.
*/
add_task(function* duration() {
Services.prefs.setBoolPref("toolkit.osfile.log", true);
// Options structure passed to a OS.File copy method.
let copyOptions = {
// This field should be overridden with the actual duration
// measurement.
outExecutionDuration: null
};
let currentDir = yield OS.File.getCurrentDirectory();
let pathSource = OS.Path.join(currentDir, "test_duration.js");
let copyFile = pathSource + ".bak";
function testOptions(options, name) {
do_print("Checking outExecutionDuration for operation: " + name);
do_print(name + ": Gathered method duration time: " +
options.outExecutionDuration + "ms");
// Making sure that duration was updated.
do_check_eq(typeof options.outExecutionDuration, "number");
do_check_true(options.outExecutionDuration >= 0);
};
// Testing duration of OS.File.copy.
yield OS.File.copy(pathSource, copyFile, copyOptions);
testOptions(copyOptions, "OS.File.copy");
yield OS.File.remove(copyFile);
// Trying an operation where options are cloned.
let pathDest = OS.Path.join(OS.Constants.Path.tmpDir,
"osfile async test read writeAtomic.tmp");
let tmpPath = pathDest + ".tmp";
let readOptions = {
outExecutionDuration: null
};
let contents = yield OS.File.read(pathSource, undefined, readOptions);
testOptions(readOptions, "OS.File.read");
// Options structure passed to a OS.File writeAtomic method.
let writeAtomicOptions = {
// This field should be first initialized with the actual
// duration measurement then progressively incremented.
outExecutionDuration: null,
tmpPath: tmpPath
};
yield OS.File.writeAtomic(pathDest, contents, writeAtomicOptions);
testOptions(writeAtomicOptions, "OS.File.writeAtomic");
yield OS.File.remove(pathDest);
do_print("Ensuring that we can use outExecutionDuration to accumulate durations");
let ARBITRARY_BASE_DURATION = 5;
copyOptions = {
// This field should now be incremented with the actual duration
// measurement.
outExecutionDuration: ARBITRARY_BASE_DURATION
};
let backupDuration = ARBITRARY_BASE_DURATION;
// Testing duration of OS.File.copy.
yield OS.File.copy(pathSource, copyFile, copyOptions);
do_check_true(copyOptions.outExecutionDuration >= backupDuration);
backupDuration = copyOptions.outExecutionDuration;
yield OS.File.remove(copyFile, copyOptions);
do_check_true(copyOptions.outExecutionDuration >= backupDuration);
// Trying an operation where options are cloned.
// Options structure passed to a OS.File writeAtomic method.
writeAtomicOptions = {
// This field should be overridden with the actual duration
// measurement.
outExecutionDuration: copyOptions.outExecutionDuration,
tmpPath: tmpPath
};
backupDuration = writeAtomicOptions.outExecutionDuration;
yield OS.File.writeAtomic(pathDest, contents, writeAtomicOptions);
do_check_true(copyOptions.outExecutionDuration >= backupDuration);
OS.File.remove(pathDest);
// Testing an operation that doesn't take arguments at all
let file = yield OS.File.open(pathSource);
yield file.stat();
yield file.close();
});
function run_test() {
run_next_test();
}

View File

@ -0,0 +1,63 @@
"use strict";
let {OS: {File, Path, Constants}} = Components.utils.import("resource://gre/modules/osfile.jsm", {});
let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
// Ensure that we have a profile but that the OS.File worker is not launched
add_task(function* init() {
do_get_profile();
yield File.resetWorker();
});
function getCount(histogram) {
if (histogram == null) {
return 0;
}
let total = 0;
for (let i of histogram.counts) {
total += i;
}
return total;
}
// Ensure that launching the OS.File worker adds data to the relevant
// histograms
add_task(function* test_startup() {
let LAUNCH = "OSFILE_WORKER_LAUNCH_MS";
let READY = "OSFILE_WORKER_READY_MS";
let before = Services.telemetry.histogramSnapshots;
// Launch the OS.File worker
yield File.getCurrentDirectory();
let after = Services.telemetry.histogramSnapshots;
do_print("Ensuring that we have recorded measures for histograms");
do_check_eq(getCount(after[LAUNCH]), getCount(before[LAUNCH]) + 1);
do_check_eq(getCount(after[READY]), getCount(before[READY]) + 1);
do_print("Ensuring that launh <= ready");
do_check_true(after[LAUNCH].sum <= after[READY].sum);
});
// Ensure that calling writeAtomic adds data to the relevant histograms
add_task(function* test_writeAtomic() {
let LABEL = "OSFILE_WRITEATOMIC_JANK_MS";
let before = Services.telemetry.histogramSnapshots;
// Perform a write.
let path = Path.join(Constants.Path.profileDir, "test_osfile_telemetry.tmp");
yield File.writeAtomic(path, LABEL, { tmpPath: path + ".tmp" } );
let after = Services.telemetry.histogramSnapshots;
do_check_eq(getCount(after[LABEL]), getCount(before[LABEL]) + 1);
});
function run_test() {
run_next_test();
}

View File

@ -23,4 +23,6 @@ tail =
[test_shutdown.js]
[test_unique.js]
[test_open.js]
[test_telemetry.js]
[test_duration.js]
[test_compression.js]

View File

@ -5620,5 +5620,29 @@
"expires_in_version": "never",
"kind": "boolean",
"description": "Is OCSP required when the cert has an OCSP URI? (pref security.OCSP.require)"
},
"OSFILE_WORKER_LAUNCH_MS": {
"expires_in_version": "never",
"kind": "exponential",
"description": "The duration between the instant the first message is sent to OS.File and the moment the OS.File worker starts executing JavaScript, in milliseconds",
"high": "5000",
"n_buckets": 10,
"extended_statistics_ok": true
},
"OSFILE_WORKER_READY_MS": {
"expires_in_version": "never",
"kind": "exponential",
"description": "The duration between the instant the first message is sent to OS.File and the moment the OS.File worker has finished executing its startup JavaScript and is ready to receive requests, in milliseconds",
"high": "5000",
"n_buckets": 10,
"extended_statistics_ok": true
},
"OSFILE_WRITEATOMIC_JANK_MS": {
"expires_in_version": "never",
"kind": "exponential",
"description": "The duration during which the main thread is blocked during a call to OS.File.writeAtomic, in milliseconds",
"high": "5000",
"n_buckets": 10,
"extended_statistics_ok": true
}
}

View File

@ -20,6 +20,8 @@
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey"/>
</content>
</binding>
@ -30,6 +32,8 @@
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type"/>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop,dragover-top"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey"/>
<xul:dropmarker type="menu" class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
</content>
</binding>
@ -42,7 +46,9 @@
<xul:vbox flex="1" align="center">
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop,dragover-top"/>
xbl:inherits="value=label,accesskey,crop,dragover-top"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey"/>
</xul:vbox>
<xul:dropmarker type="menu" class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
</xul:hbox>
@ -59,7 +65,7 @@
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:toolbarbutton class="box-inherit toolbarbutton-menubutton-button"
anonid="button" flex="1" allowevents="true"
xbl:inherits="disabled,crop,image,label,accesskey,command,
xbl:inherits="disabled,crop,image,label,accesskey,command,wrap,
align,dir,pack,orient,tooltiptext=buttontooltiptext"/>
<xul:dropmarker type="menu-button" class="toolbarbutton-menubutton-dropmarker"
xbl:inherits="align,dir,pack,orient,disabled,label,open"/>
@ -72,15 +78,4 @@
<xul:image class="toolbarbutton-icon" xbl:inherits="src=image"/>
</content>
</binding>
<binding id="toolbarbutton-wrapping-label"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
<content>
<children includes="observes|template|menupopup|panel|tooltip"/>
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
<xul:label class="toolbarbutton-text toolbarbutton-label" flex="1"
xbl:inherits="xbl:text=label,accesskey"/>
</content>
</binding>
</bindings>

View File

@ -157,15 +157,16 @@ toolbarbutton[type="menu-button"] {
-moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu-button");
}
toolbarbutton[type="wrap"] {
-moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-wrapping-label");
}
toolbar[mode="icons"] .toolbarbutton-text,
toolbar[mode="text"] .toolbarbutton-icon {
display: none;
}
toolbarbutton:not([wrap="true"]) > .toolbarbutton-multiline-text,
toolbarbutton[wrap="true"] > .toolbarbutton-text {
display: none;
}
/******** browser, editor, iframe ********/
browser,
@ -694,7 +695,7 @@ label.text-link, label[onclick] {
-moz-user-focus: normal;
}
label[control], label.radio-label, label.checkbox-label, label.toolbarbutton-label {
label[control], label.radio-label, label.checkbox-label, label.toolbarbutton-multiline-text {
-moz-binding: url("chrome://global/content/bindings/text.xml#label-control");
}

View File

@ -793,22 +793,26 @@ BrowserTabActor.prototype = {
let enable = Ci.nsIRequest.LOAD_NORMAL;
let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING;
let docShell = this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
if (this.window) {
let docShell = this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
docShell.defaultLoadFlags = allow ? enable : disable;
docShell.defaultLoadFlags = allow ? enable : disable;
}
},
/**
* Disable or enable JS via docShell.
*/
_setJavascriptEnabled: function(allow) {
let docShell = this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
if (this.window) {
let docShell = this.window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
docShell.allowJavascript = allow;
docShell.allowJavascript = allow;
}
},
/**