Merge fx-team to m-c

This commit is contained in:
Wes Kocher 2014-05-23 17:04:56 -07:00
commit 98152d91df
77 changed files with 899 additions and 137 deletions

View File

@ -76,9 +76,6 @@ XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
'nsICaptivePortalDetector');
#endif
let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
let { RootActor } = devtools.require("devtools/server/actors/root");
function getContentWindow() {
return shell.contentBrowser.contentWindow;
}
@ -971,6 +968,8 @@ let RemoteDebugger = {
deviceActor: DebuggerServer.globalActorFactories.deviceActor,
} : DebuggerServer.globalActorFactories
};
let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
let { RootActor } = devtools.require("devtools/server/actors/root");
let root = new RootActor(connection, parameters);
root.applicationType = "operating-system";
return root;

View File

@ -278,7 +278,7 @@ pref("browser.startup.page", 1);
pref("browser.startup.homepage", "chrome://branding/locale/browserconfig.properties");
pref("browser.slowStartup.notificationDisabled", false);
pref("browser.slowStartup.timeThreshold", 60000);
pref("browser.slowStartup.timeThreshold", 45000);
pref("browser.slowStartup.maxSamples", 5);
// This url, if changed, MUST continue to point to an https url. Pulling arbitrary content to inject into
@ -1568,6 +1568,8 @@ pref("browser.cache.frecency_experiment", 0);
pref("browser.translation.detectLanguage", false);
pref("browser.translation.neverForLanguages", "");
// Show the translation UI bits, like the info bar, notification icon and preferences.
pref("browser.translation.ui.show", false);
// Telemetry experiments settings.
pref("experiments.enabled", true);

View File

@ -4428,7 +4428,8 @@ var TabsInTitlebar = {
// Get the full height of the tabs toolbar:
let tabsToolbar = $("TabsToolbar");
let fullTabsHeight = rect(tabsToolbar).height;
let tabsStyles = window.getComputedStyle(tabsToolbar);
let fullTabsHeight = rect(tabsToolbar).height + verticalMargins(tabsStyles);
// Buttons first:
let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width;
@ -4437,15 +4438,11 @@ var TabsInTitlebar = {
// No need to look up the menubar stuff on OS X:
let menuHeight = 0;
let fullMenuHeight = 0;
// Instead, look up the titlebar padding:
let titlebarPadding = parseInt(window.getComputedStyle(titlebar).paddingTop, 10);
#else
// Otherwise, get the height and margins separately for the menubar
let menuHeight = rect(menubar).height;
let menuStyles = window.getComputedStyle(menubar);
let fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
let tabsStyles = window.getComputedStyle(tabsToolbar);
fullTabsHeight += verticalMargins(tabsStyles);
#endif
// And get the height of what's in the titlebar:
@ -4488,7 +4485,6 @@ var TabsInTitlebar = {
// We need to increase the titlebar content's outer height (ie including margins)
// to match the tab and menu height:
let extraMargin = tabAndMenuHeight - titlebarContentHeight;
// On non-OSX, we can just use bottom margin:
#ifndef XP_MACOSX
titlebarContent.style.marginBottom = extraMargin + "px";
#endif

View File

@ -3704,6 +3704,7 @@ function XULWidgetSingleWrapper(aWidgetId, aNode, aDocument) {
}
const LAZY_RESIZE_INTERVAL_MS = 200;
const OVERFLOW_PANEL_HIDE_DELAY_MS = 500
function OverflowableToolbar(aToolbarNode) {
this._toolbar = aToolbarNode;
@ -3747,6 +3748,8 @@ OverflowableToolbar.prototype = {
let chevronId = this._toolbar.getAttribute("overflowbutton");
this._chevron = doc.getElementById(chevronId);
this._chevron.addEventListener("command", this);
this._chevron.addEventListener("dragover", this);
this._chevron.addEventListener("dragend", this);
let panelId = this._toolbar.getAttribute("overflowpanel");
this._panel = doc.getElementById(panelId);
@ -3781,6 +3784,8 @@ OverflowableToolbar.prototype = {
window.gNavToolbox.removeEventListener("customizationstarting", this);
window.gNavToolbox.removeEventListener("aftercustomization", this);
this._chevron.removeEventListener("command", this);
this._chevron.removeEventListener("dragover", this);
this._chevron.removeEventListener("dragend", this);
this._panel.removeEventListener("popuphiding", this);
CustomizableUI.removeListener(this);
CustomizableUIInternal.removePanelCloseListeners(this._panel);
@ -3788,8 +3793,8 @@ OverflowableToolbar.prototype = {
handleEvent: function(aEvent) {
switch(aEvent.type) {
case "resize":
this._onResize(aEvent);
case "aftercustomization":
this._enable();
break;
case "command":
if (aEvent.target == this._chevron) {
@ -3798,15 +3803,20 @@ OverflowableToolbar.prototype = {
this._panel.hidePopup();
}
break;
case "popuphiding":
this._onPanelHiding(aEvent);
break;
case "customizationstarting":
this._disable();
break;
case "aftercustomization":
this._enable();
case "dragover":
this._showWithTimeout();
break;
case "dragend":
this._panel.hidePopup();
break;
case "popuphiding":
this._onPanelHiding(aEvent);
break;
case "resize":
this._onResize(aEvent);
}
},
@ -3824,8 +3834,11 @@ OverflowableToolbar.prototype = {
this._panel.openPopup(anchor || this._chevron);
this._chevron.open = true;
this._panel.addEventListener("popupshown", function onPopupShown() {
let overflowableToolbarInstance = this;
this._panel.addEventListener("popupshown", function onPopupShown(aEvent) {
this.removeEventListener("popupshown", onPopupShown);
this.addEventListener("dragover", overflowableToolbarInstance);
this.addEventListener("dragend", overflowableToolbarInstance);
deferred.resolve();
});
@ -3843,6 +3856,8 @@ OverflowableToolbar.prototype = {
_onPanelHiding: function(aEvent) {
this._chevron.open = false;
this._panel.removeEventListener("dragover", this);
this._panel.removeEventListener("dragend", this);
let doc = aEvent.target.ownerDocument;
let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
gELS.removeSystemEventListener(contextMenu, 'command', this, true);
@ -4077,6 +4092,21 @@ OverflowableToolbar.prototype = {
}
return this._target;
},
_hideTimeoutId: null,
_showWithTimeout: function() {
this.show();
let window = this._toolbar.ownerDocument.defaultView;
if (this._hideTimeoutId) {
window.clearTimeout(this._hideTimeoutId);
this._hideTimeoutId = null;
}
this._hideTimeoutId = window.setTimeout(() => {
if (!this._panel.firstChild.mozMatchesSelector(":hover")) {
this._panel.hidePopup();
}
}, OVERFLOW_PANEL_HIDE_DELAY_MS);
},
};
CustomizableUIInternal.initialize();

View File

@ -69,6 +69,7 @@ skip-if = e10s # Bug ?????? - test uses promiseTabLoadEvent() which isn't e10s f
[browser_948985_non_removable_defaultArea.js]
[browser_952963_areaType_getter_no_area.js]
[browser_956602_remove_special_widget.js]
[browser_962069_drag_to_overflow_chevron.js]
[browser_962884_opt_in_disable_hyphens.js]
[browser_963639_customizing_attribute_non_customizable_toolbar.js]
[browser_967000_button_charEncoding.js]

View File

@ -31,7 +31,7 @@ add_task(function() {
setToolbarVisibility(bookmarksToolbar, true);
setToolbarVisibility(navbar, false);
isnot(bookmarksToolbar.getBoundingClientRect().height, 0, "bookmarksToolbar should be visible now");
is(navbar.getBoundingClientRect().height, 1, "navbar should have a height=1 (due to border)");
ok(navbar.getBoundingClientRect().height <= 1, "navbar should have height=0 or 1 (due to border)");
is(CustomizableUI.inDefaultState, false, "Should no longer be in default state");
yield startCustomizing();

View File

@ -0,0 +1,39 @@
/* 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 originalWindowWidth;
// Drag to overflow chevron should open the overflow panel.
add_task(function*() {
originalWindowWidth = window.outerWidth;
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
let oldChildCount = navbar.customizationTarget.childElementCount;
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
let widgetOverflowPanel = document.getElementById("widget-overflow");
let panelShownPromise = promisePanelElementShown(window, widgetOverflowPanel);
let identityBox = document.getElementById("identity-box");
let overflowChevron = document.getElementById("nav-bar-overflow-button");
ChromeUtils.synthesizeDrop(identityBox, overflowChevron, [], null);
yield panelShownPromise;
ok(true, "Overflow panel is shown.");
let panelHiddenPromise = promisePanelElementHidden(window, widgetOverflowPanel);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield panelHiddenPromise;
});
add_task(function*() {
window.resizeTo(originalWindowWidth, window.outerHeight);
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
});

View File

@ -16,6 +16,13 @@ var gContentPane = {
menulist.insertItemAt(0, "", "", "");
menulist.selectedIndex = 0;
}
// Show translation preferences if we may:
const prefName = "browser.translation.ui.show";
if (Services.prefs.getBoolPref(prefName)) {
let row = document.getElementById("translationBox");
row.removeAttribute("hidden");
}
},
// UTILITY FUNCTIONS

View File

@ -143,7 +143,7 @@
accesskey="&chooseButton.accesskey;"
oncommand="gContentPane.showLanguages();"/>
</row>
<row hidden="true">
<row id="translationBox" hidden="true">
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
onsyncfrompreference="return gContentPane.updateButtons('translateButton',

View File

@ -437,9 +437,6 @@
<separator/>
#ifdef XP_MACOSX
<vbox>
#endif
<hbox>
<button id="viewCertificatesButton"
label="&viewCerts.label;" accesskey="&viewCerts.accesskey;"
@ -448,18 +445,11 @@
<button id="verificationButton"
label="&verify2.label;" accesskey="&verify2.accesskey;"
oncommand="gAdvancedPane.showOCSP();"/>
#ifdef XP_MACOSX
</hbox>
<hbox>
#endif
<button id="viewSecurityDevicesButton"
label="&viewSecurityDevices.label;" accesskey="&viewSecurityDevices.accesskey;"
oncommand="gAdvancedPane.showSecurityDevices();"
preference="security.disable_button.openDeviceManager"/>
</hbox>
#ifdef XP_MACOSX
</vbox>
#endif
</tabpanel>
</tabpanels>
</tabbox>

View File

@ -15,6 +15,13 @@ var gContentPane = {
menulist.insertItemAt(0, "", "", "");
menulist.selectedIndex = 0;
}
// Show translation preferences if we may:
const prefName = "browser.translation.ui.show";
if (Services.prefs.getBoolPref(prefName)) {
let row = document.getElementById("translationBox");
row.removeAttribute("hidden");
}
},
// UTILITY FUNCTIONS

View File

@ -8,6 +8,8 @@ this.EXPORTED_SYMBOLS = ["Translation"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const TRANSLATION_PREF_SHOWUI = "browser.translation.ui.show";
Cu.import("resource://gre/modules/Services.jsm");
this.Translation = {
@ -26,6 +28,9 @@ this.Translation = {
},
languageDetected: function(aBrowser, aDetectedLanguage) {
if (!Services.prefs.getBoolPref(TRANSLATION_PREF_SHOWUI))
return;
if (this.supportedSourceLanguages.indexOf(aDetectedLanguage) != -1 &&
aDetectedLanguage != this.defaultTargetLanguage) {
if (!aBrowser.translationUI)

View File

@ -67,7 +67,12 @@ TranslationContentHandler.prototype = {
Cu.import("resource:///modules/translation/TranslationDocument.jsm");
Cu.import("resource:///modules/translation/BingTranslator.jsm");
let translationDocument = new TranslationDocument(this.global.content.document);
// If a TranslationDocument already exists for this document, it should
// be used instead of creating a new one so that we can use the original
// content of the page for the new translation instead of the newly
// translated text.
let translationDocument = this.global.content.translationDocument ||
new TranslationDocument(this.global.content.document);
let bingTranslation = new BingTranslation(translationDocument,
msg.data.from,
msg.data.to);

View File

@ -115,21 +115,27 @@ this.TranslationDocument.prototype = {
* Besides generating the string, it's also stored in the "original"
* field of the TranslationItem object, which needs to be stored for
* later to be used in the "Show Original" functionality.
* If this function had already been called for the given item (determined
* by the presence of the "original" array in the item), the text will
* be regenerated from the "original" data instead of from the related
* DOM nodes (because the nodes might contain translated data).
*
* @param item A TranslationItem object
*
* @returns A string representation of the TranslationItem.
*/
generateTextForItem: function(item) {
if (item.original) {
return regenerateTextFromOriginalHelper(item);
}
if (item.isSimpleRoot) {
let text = item.nodeRef.firstChild.nodeValue.trim();
item.original = [text];
return text;
}
let localName = item.isRoot ? "div" : "b";
let str = '<' + localName + ' id="n' + item.id + '">';
let str = "";
item.original = [];
for (let child of item.nodeRef.childNodes) {
@ -141,15 +147,18 @@ this.TranslationDocument.prototype = {
}
let objInMap = this.itemsMap.get(child);
if (objInMap) {
if (objInMap && !objInMap.isRoot) {
// If this childNode is present in the itemsMap, it means
// it's a translation node: it has useful content for translation.
// In this case, we need to stringify this node.
// However, if this item is a root, we should skip it here in this
// object's child list (and just add a placeholder for it), because
// it will be stringfied separately for being a root.
item.original.push(objInMap);
str += this.generateTextForItem(objInMap);
} else {
// Otherwise, if this node doesn't contain any useful content,
// we can simply replace it by a placeholder node.
// or if it is a root itself, we can replace it with a placeholder node.
// We can't simply eliminate this node from our string representation
// because that could change the HTML structure (e.g., it would
// probably merge two separate text nodes).
@ -157,8 +166,7 @@ this.TranslationDocument.prototype = {
}
}
str += '</' + localName + '>';
return str;
return generateTranslationHtmlForItem(item, str);
},
/**
@ -316,6 +324,58 @@ TranslationItem.prototype = {
}
};
/**
* Generate the outer HTML representation for a given item.
*
* @param item A TranslationItem object.
* param content The inner content for this item.
* @returns string The outer HTML needed for translation
* of this item.
*/
function generateTranslationHtmlForItem(item, content) {
let localName = item.isRoot ? "div" : "b";
return '<' + localName + ' id="n' + item.id + '">' +
content +
"</" + localName + ">";
}
/**
* Regenerate the text string that represents a TranslationItem object,
* with data from its "original" array. The array must have already
* been created by TranslationDocument.generateTextForItem().
*
* @param item A TranslationItem object
*
* @returns A string representation of the TranslationItem.
*/
function regenerateTextFromOriginalHelper(item) {
if (item.isSimpleRoot) {
return item.original[0];
}
let str = "";
let wasLastItemText = false;
for (let child of item.original) {
if (child instanceof TranslationItem) {
str += regenerateTextFromOriginalHelper(child);
wasLastItemText = false;
} else {
// The non-significant elements (which were replaced with <br/>
// during the first call to generateTextForItem) are not stored
// in the original array. If they are not properly re-generated,
// two adjacent text nodes would be merged into one.
if (wasLastItemText) {
str += "<br/>";
}
str += child;
wasLastItemText = true;
}
}
return generateTranslationHtmlForItem(item, str);
}
/**
* Helper function to parse a HTML doc result.
* How it works:

View File

@ -52,7 +52,6 @@
}
#TabsToolbar:not([collapsed="true"]) + #nav-bar {
margin-top: -@tabToolbarNavbarOverlap@; /* Move up into the TabsToolbar */
/* Position the toolbar above the bottom of background tabs */
position: relative;
z-index: 1;
@ -1768,6 +1767,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
min-height: 0;
padding: 0;
position: relative;
margin-bottom: -@tabToolbarNavbarOverlap@;
}
/*

View File

@ -244,7 +244,6 @@ browser.jar:
skin/classic/browser/devtools/itemArrow-dark-ltr.svg (../shared/devtools/images/itemArrow-dark-ltr.svg)
skin/classic/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg)
skin/classic/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg)
skin/classic/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
skin/classic/browser/devtools/noise.png (../shared/devtools/images/noise.png)
skin/classic/browser/devtools/dropmarker.png (../shared/devtools/images/dropmarker.png)
skin/classic/browser/devtools/layoutview.css (../shared/devtools/layoutview.css)

View File

@ -110,7 +110,6 @@ toolbarseparator {
}
#TabsToolbar:not([collapsed="true"]) + #nav-bar {
margin-top: -@tabToolbarNavbarOverlap@; /* Move up into the TabsToolbar */
/* Position the toolbar above the bottom of background tabs */
position: relative;
z-index: 1;
@ -2797,6 +2796,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
position: relative;
-moz-appearance: none;
background-repeat: repeat-x;
margin-bottom: -@tabToolbarNavbarOverlap@;
}
#TabsToolbar:not(:-moz-lwtheme) {

View File

@ -364,7 +364,6 @@ browser.jar:
skin/classic/browser/devtools/itemArrow-dark-ltr.svg (../shared/devtools/images/itemArrow-dark-ltr.svg)
skin/classic/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg)
skin/classic/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg)
skin/classic/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
skin/classic/browser/devtools/noise.png (../shared/devtools/images/noise.png)
skin/classic/browser/devtools/dropmarker.png (../shared/devtools/images/dropmarker.png)
skin/classic/browser/devtools/layoutview.css (../shared/devtools/layoutview.css)

View File

@ -18,12 +18,12 @@
}
.theme-dark .notice-container {
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
color: #f5f7fa; /* Light foreground text */
}
.theme-light .notice-container {
background: url(background-noise-toolbar.png), #f0f1f2; /* Toolbars */
background-color: #f0f1f2; /* Toolbars */
color: #585959; /* Grey foreground text */
}
@ -439,13 +439,11 @@
.theme-dark #snapshot-filmstrip {
border-top: 1px solid #000;
background-image: url(background-noise-toolbar.png);
color: #f5f7fa; /* Light foreground text */
}
.theme-light #snapshot-filmstrip {
border-top: 1px solid #aaa;
background-image: url(background-noise-toolbar.png);
color: #585959; /* Grey foreground text */
}

View File

@ -10,7 +10,7 @@
-moz-appearance: none;
padding: 0;
min-height: 32px;
background-image: url(devtools/background-noise-toolbar.png), linear-gradient(#303840, #2d3640);
background-color: #343C45; /* Toolbars */
border-top: 1px solid #060a0d;
box-shadow: 0 1px 0 hsla(204,45%,98%,.05) inset, 0 -1px 0 hsla(206,37%,4%,.1) inset;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -417,7 +417,7 @@ label.requests-menu-status-code {
/* Network request details tabpanels */
.theme-dark .tabpanel-content {
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
color: #f5f7fa; /* Light foreground text */
}
@ -461,12 +461,12 @@ label.requests-menu-status-code {
}
.theme-dark #response-content-info-header {
background: url(background-noise-toolbar.png), #eb5368; /* Red highlight */
background-color: #eb5368; /* Red highlight */
color: #f5f7fa; /* Light foreground text */
}
.theme-light #response-content-info-header {
background: url(background-noise-toolbar.png), #ed2655; /* Red highlight */
background-color: #ed2655; /* Red highlight */
color: #f5f7fa; /* Light foreground text */
}
@ -531,13 +531,13 @@ label.requests-menu-status-code {
.theme-dark #requests-menu-footer {
border-top: 1px solid @table_itemDarkStartBorder@;
box-shadow: 0 1px 0 @table_itemDarkEndBorder@ inset;
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
}
.theme-light #requests-menu-footer {
border-top: 1px solid @table_itemLightStartBorder@;
box-shadow: 0 1px 0 @table_itemLightEndBorder@ inset;
background: url(background-noise-toolbar.png), #f0f1f2; /* Toolbars */
background-color: #f0f1f2; /* Toolbars */
}
.requests-menu-footer-button,
@ -656,11 +656,11 @@ label.requests-menu-status-code {
}
.theme-dark #network-statistics-charts {
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
}
.theme-light #network-statistics-charts {
background: url(background-noise-toolbar.png), #f0f1f2; /* Toolbars */
background-color: #f0f1f2; /* Toolbars */
}
#network-statistics-charts .pie-chart-container {

View File

@ -9,12 +9,12 @@
}
.theme-dark .notice-container {
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
color: #f5f7fa; /* Light foreground text */
}
.theme-light .notice-container {
background: url(background-noise-toolbar.png), #f0f1f2; /* Toolbars */
background-color: #f0f1f2; /* Toolbars */
color: #585959; /* Grey foreground text */
}

View File

@ -18,7 +18,7 @@
}
.theme-dark .splitview-nav-container {
background: url(background-noise-toolbar.png), #343c45;
background-color: #343c45; /* Toolbars */
}
.splitview-nav {

View File

@ -8,12 +8,12 @@
}
.theme-dark .notice-container {
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
color: #f5f7fa; /* Light foreground text */
}
.theme-light .notice-container {
background: url(background-noise-toolbar.png), #f0f1f2; /* Toolbars */
background-color: #f0f1f2; /* Toolbars */
color: #585959; /* Grey foreground text */
}

View File

@ -372,7 +372,7 @@
.theme-dark .side-menu-widget-container,
.theme-dark .side-menu-widget-empty-text {
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
}
.theme-dark .side-menu-widget-container:-moz-locale-dir(ltr),
@ -522,11 +522,11 @@
}
.theme-dark .side-menu-widget-item-other {
background: url(background-noise-toolbar.png), rgba(0,0,0,.1);
background-color: rgba(0,0,0,.1);
}
.theme-light .side-menu-widget-item-other {
background: url(background-noise-toolbar.png), rgba(128,128,128,.1);
background-color: rgba(128,128,128,.1);
}
.theme-dark .side-menu-widget-item.selected .side-menu-widget-item-other {
@ -568,7 +568,7 @@
}
.theme-dark .side-menu-widget-empty-text {
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
color: #b6babf; /* Foreground (Text) - Grey */
}
@ -1067,7 +1067,7 @@
.theme-dark .table-widget-body,
.theme-dark .table-widget-empty-text {
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
background-color: #343c45; /* Toolbars */
}
.theme-dark .table-widget-body:-moz-locale-dir(ltr) {

View File

@ -282,7 +282,6 @@ browser.jar:
skin/classic/browser/devtools/itemArrow-dark-ltr.svg (../shared/devtools/images/itemArrow-dark-ltr.svg)
skin/classic/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg)
skin/classic/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg)
skin/classic/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
skin/classic/browser/devtools/noise.png (../shared/devtools/images/noise.png)
skin/classic/browser/devtools/dropmarker.png (../shared/devtools/images/dropmarker.png)
skin/classic/browser/devtools/layoutview.css (../shared/devtools/layoutview.css)
@ -674,7 +673,6 @@ browser.jar:
skin/classic/aero/browser/devtools/itemArrow-dark-ltr.svg (../shared/devtools/images/itemArrow-dark-ltr.svg)
skin/classic/aero/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg)
skin/classic/aero/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg)
skin/classic/aero/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
skin/classic/aero/browser/devtools/noise.png (../shared/devtools/images/noise.png)
skin/classic/aero/browser/devtools/dropmarker.png (../shared/devtools/images/dropmarker.png)
skin/classic/aero/browser/devtools/layoutview.css (../shared/devtools/layoutview.css)

View File

@ -4051,6 +4051,19 @@ if test -z "$MOZ_GOOGLE_API_KEY"; then
fi
AC_SUBST(MOZ_GOOGLE_API_KEY)
# Allow specifying a Bing API key file that contains the client ID and the
# secret key to be used for the Bing Translation API requests.
MOZ_ARG_WITH_STRING(bing-api-keyfile,
[ --with-bing-api-keyfile=file Use the client id and secret key contained in the given keyfile for Bing API requests],
[MOZ_BING_API_CLIENTID=`cat $withval | cut -f 1 -d " "`
MOZ_BING_API_KEY=`cat $withval | cut -f 2 -d " "`])
if test -z "$MOZ_BING_API_CLIENTID"; then
MOZ_BING_API_CLIENTID=no-bing-api-clientid
MOZ_BING_API_KEY=no-bing-api-key
fi
AC_SUBST(MOZ_BING_API_CLIENTID)
AC_SUBST(MOZ_BING_API_KEY)
# Allow the application to influence configure with a confvars.sh script.
AC_MSG_CHECKING([if app-specific confvars.sh exists])
if test -f "${srcdir}/${MOZ_BUILD_APP}/confvars.sh" ; then

View File

@ -798,7 +798,7 @@ public abstract class GeckoApp
});
}
protected ButtonToast getButtonToast() {
public ButtonToast getButtonToast() {
if (mToast != null) {
return mToast;
}

View File

@ -438,6 +438,8 @@ public class BrowserContract {
public static final class SuggestedSites implements CommonColumns, URLColumns {
private SuggestedSites() {}
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "suggestedsites");
public static final String IMAGE_URL = "image_url";
public static final String BG_COLOR = "bg_color";
}

View File

@ -6,6 +6,7 @@
package org.mozilla.gecko.db;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
@ -22,9 +23,11 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.util.RawResource;
/**
@ -150,6 +153,11 @@ public class SuggestedSites {
return sites;
}
private boolean isEnabled() {
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
return prefs.getBoolean(GeckoPreferences.PREFS_SUGGESTED_SITES, true);
}
/**
* Returns a {@code Cursor} with the list of suggested websites.
*
@ -166,14 +174,20 @@ public class SuggestedSites {
* @param excludeUrls list of URLs to be excluded from the list.
*/
public Cursor get(int limit, List<String> excludeUrls) {
final MatrixCursor cursor = new MatrixCursor(COLUMNS);
// Return an empty cursor if suggested sites have been
// disabled by the user.
if (!isEnabled()) {
return cursor;
}
List<Site> sites = cachedSites.get();
if (sites == null) {
Log.d(LOGTAG, "No cached sites, refreshing.");
sites = refresh();
}
final MatrixCursor cursor = new MatrixCursor(COLUMNS);
// Return empty cursor if there was an error when
// loading the suggested sites or the list is empty.
if (sites == null || sites.isEmpty()) {
@ -199,6 +213,9 @@ public class SuggestedSites {
row.add(site.bgColor);
}
cursor.setNotificationUri(context.getContentResolver(),
BrowserContract.SuggestedSites.CONTENT_URI);
return cursor;
}
}

View File

@ -6,11 +6,13 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.EditBookmarkDialog;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.ReaderModeUtils;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
@ -21,6 +23,7 @@ import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
import org.mozilla.gecko.widget.ButtonToast;
import android.content.ContentResolver;
import android.content.Context;
@ -176,8 +179,10 @@ abstract class HomeFragment extends Fragment {
}
int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
if (item.getItemId() == R.id.home_open_private_tab)
final boolean isPrivate = (item.getItemId() == R.id.home_open_private_tab);
if (isPrivate) {
flags |= Tabs.LOADURL_PRIVATE;
}
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
@ -185,8 +190,26 @@ abstract class HomeFragment extends Fragment {
// Some pinned site items have "user-entered" urls. URLs entered in the PinSiteDialog are wrapped in
// a special URI until we can get a valid URL. If the url is a user-entered url, decode the URL before loading it.
Tabs.getInstance().loadUrl(decodeUserEnteredUrl(url), flags);
Toast.makeText(context, R.string.new_tab_opened, Toast.LENGTH_SHORT).show();
final Tab newTab = Tabs.getInstance().loadUrl(decodeUserEnteredUrl(url), flags);
final int newTabId = newTab.getId(); // We don't want to hold a reference to the Tab.
final String message = isPrivate ?
getResources().getString(R.string.new_private_tab_opened) :
getResources().getString(R.string.new_tab_opened);
final GeckoApp geckoApp = (GeckoApp) context;
geckoApp.getButtonToast().show(false,
message,
null,
R.drawable.select_opened_tab,
new ButtonToast.ToastListener() {
@Override
public void onButtonClicked() {
Tabs.getInstance().selectTab(newTabId);
}
@Override
public void onToastHidden(ButtonToast.ReasonHidden reason) { }
});
return true;
}

View File

@ -89,7 +89,7 @@ $(dir-strings-xml)/strings.xml: $(strings-xml-preqs)
$< \
-o $@)
suggestedsites-srcdir := $(dir $(abspath $(call MERGE_FILE,suggestedsites/list.txt)))
suggestedsites-srcdir := $(if $(filter en-US,$(AB_CD)),,$(or $(realpath $(L10NBASEDIR)),$(abspath $(L10NBASEDIR)))/$(AB_CD)/mobile/chrome)
# Determine the ../res/raw[-*] path. This can be ../res/raw when no
# locale is explicitly specified.
@ -105,5 +105,5 @@ $(suggestedsites-dstdir-raw)/suggestedsites.json: FORCE
--android-package-name=$(ANDROID_PACKAGE_NAME) \
--resources=$(srcdir)/../resources \
$(if $(filter en-US,$(AB_CD)),,--srcdir=$(suggestedsites-srcdir)) \
--srcdir=$(addprefix $(srcdir)/,en-US/suggestedsites) \
--srcdir=$(topsrcdir)/mobile/locales/en-US/chrome \
$@)

View File

@ -62,6 +62,7 @@
We can't use android plural forms, sadly. See bug #753859. -->
<!ENTITY num_tabs2 "&formatD; tabs">
<!ENTITY new_tab_opened "New tab opened">
<!ENTITY new_private_tab_opened "New private tab opened">
<!ENTITY settings "Settings">
<!ENTITY settings_title "Settings">
@ -115,6 +116,8 @@
<!ENTITY pref_home_updates "Automatic updates">
<!ENTITY pref_home_updates_enabled "Enabled">
<!ENTITY pref_home_updates_wifi "Only over Wi-Fi">
<!ENTITY pref_home_suggested_sites "Show site suggestions">
<!ENTITY pref_home_suggested_sites_summary "Display shortcuts to sites on your homepage that we think you might find interesting">
<!-- Localization note: These are shown in the left sidebar on tablets -->
<!ENTITY pref_header_customize "Customize">

View File

@ -1,3 +0,0 @@
{ "title": "Firefox Marketplace",
"url": "https://marketplace.firefox.com/",
"bgcolor": "#0095dd" }

View File

@ -1,2 +0,0 @@
mozilla
fxmarketplace

View File

@ -1,3 +0,0 @@
{ "title": "Mozilla",
"url": "http://www.mozilla.org/en-US/",
"bgcolor": "#c13832" }

View File

@ -415,6 +415,7 @@ gbjar.sources += [
'widget/DoorHanger.java',
'widget/EllipsisTextView.java',
'widget/FaviconView.java',
'widget/FloatingHintEditText.java',
'widget/FlowLayout.java',
'widget/GeckoActionProvider.java',
'widget/GeckoPopupMenu.java',

View File

@ -29,9 +29,11 @@ import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.background.announcements.AnnouncementsConstants;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.healthreport.HealthReportConstants;
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
import org.mozilla.gecko.home.HomePanelPicker;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.FloatingHintEditText;
import android.app.ActionBar;
import android.app.Activity;
@ -40,6 +42,7 @@ import android.app.Dialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -112,6 +115,7 @@ OnSharedPreferenceChangeListener
private static final String PREFS_BROWSER_LOCALE = "locale";
public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
public static final String PREFS_SUGGESTED_SITES = NON_PREF_PREFIX + "home_suggested_sites";
// These values are chosen to be distinct from other Activity constants.
private static final int REQUEST_CODE_PREF_SCREEN = 5;
@ -901,6 +905,12 @@ OnSharedPreferenceChangeListener
if (PREFS_BROWSER_LOCALE.equals(key)) {
onLocaleSelected(BrowserLocaleManager.getLanguageTag(lastLocale),
sharedPreferences.getString(key, null));
} else if (PREFS_SUGGESTED_SITES.equals(key)) {
final ContentResolver cr = getApplicationContext().getContentResolver();
// This will force all active suggested sites cursors
// to request a refresh (e.g. cursor loaders).
cr.notifyChange(SuggestedSites.CONTENT_URI, null);
}
}
@ -962,7 +972,7 @@ OnSharedPreferenceChangeListener
}
private EditText getTextBox(int aHintText) {
EditText input = new EditText(GeckoAppShell.getContext());
EditText input = new FloatingHintEditText(GeckoAppShell.getContext());
int inputtype = InputType.TYPE_CLASS_TEXT;
inputtype |= InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
input.setInputType(inputtype);

View File

@ -12,6 +12,7 @@ import java.util.GregorianCalendar;
import org.json.JSONObject;
import org.mozilla.gecko.widget.AllCapsTextView;
import org.mozilla.gecko.widget.DateTimePicker;
import org.mozilla.gecko.widget.FloatingHintEditText;
import android.content.Context;
import android.content.res.Configuration;
@ -62,7 +63,7 @@ public class PromptInput {
}
public View getView(final Context context) throws UnsupportedOperationException {
EditText input = new EditText(context);
EditText input = new FloatingHintEditText(context);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(mValue);

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Holo blue for focus -->
<item android:state_focused="true" android:color="@color/text_color_hint_floating_focused" />
<!-- Default gray for all other states -->
<item android:color="@color/text_color_hint"/>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="30"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="30" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="60"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="60" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="90"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="90" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="120"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="120" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="150"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="150" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="180"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="180" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="210"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="210" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="240"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="240" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="270"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="270" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="300"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="300" />

View File

@ -0,0 +1,6 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/tabs_synced"
android:fromDegrees="330"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="330" />

View File

@ -0,0 +1,44 @@
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false" >
<!-- Forgive me, but this is actually a reasonable way to get a
frame-by-frame rotation animation without adding lots of small
resources. The inner drawables rotate the master drawable. -->
<item
android:drawable="@drawable/tabs_synced"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_030"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_060"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_090"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_120"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_150"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_180"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_210"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_240"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_270"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_300"
android:duration="100"/>
<item
android:drawable="@drawable/tabs_synced_330"
android:duration="100"/>
</animation-list>

View File

@ -9,20 +9,23 @@
android:orientation="vertical"
android:layout_height="fill_parent">
<EditText android:id="@+id/edit_bookmark_name"
<org.mozilla.gecko.widget.FloatingHintEditText
android:id="@+id/edit_bookmark_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:hint="@string/bookmark_edit_name"/>
<EditText android:id="@+id/edit_bookmark_location"
<org.mozilla.gecko.widget.FloatingHintEditText
android:id="@+id/edit_bookmark_location"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:hint="@string/bookmark_edit_location"
android:inputType="textNoSuggestions"/>
<EditText android:id="@+id/edit_bookmark_keyword"
<org.mozilla.gecko.widget.FloatingHintEditText
android:id="@+id/edit_bookmark_keyword"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"

View File

@ -60,6 +60,7 @@
<item name="android:actionModeCopyDrawable">@drawable/ab_copy</item>
<item name="android:actionModePasteDrawable">@drawable/ab_paste</item>
<item name="android:actionModeSelectAllDrawable">@drawable/ab_select_all</item>
<item name="floatingHintEditTextStyle">@style/FloatingHintEditText</item>
</style>
</resources>

View File

@ -244,5 +244,9 @@
<attr name="ellipsizeAtLine" format="integer"/>
</declare-styleable>
<declare-styleable name="FloatingHintEditText">
<attr name="floatingHintEditTextStyle" format="reference" />
</declare-styleable>
</resources>

View File

@ -50,6 +50,7 @@
<!-- Hint colors -->
<color name="text_color_hint">#666666</color>
<color name="text_color_hint_inverse">#7F828A</color>
<color name="text_color_hint_floating_focused">#33b5e5</color>
<!-- Highlight colors -->
<color name="text_color_highlight">#FF9500</color>

View File

@ -684,4 +684,8 @@
<item name="android:minHeight">@dimen/menu_item_row_height</item>
</style>
<style name="FloatingHintEditText" parent="android:style/Widget.EditText">
<item name="android:paddingTop">0dp</item>
</style>
</resources>

View File

@ -92,6 +92,7 @@
<item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
<item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
<item name="menuItemShareActionButtonStyle">@style/Widget.MenuItemSecondaryActionBar</item>
<item name="floatingHintEditTextStyle">@style/FloatingHintEditText</item>
</style>
<style name="Gecko.Preferences" parent="GeckoPreferencesBase"/>

View File

@ -13,6 +13,11 @@
<PreferenceCategory android:title="@string/pref_category_home_content_settings">
<CheckBoxPreference android:key="android.not_a_preference.home_suggested_sites"
android:title="@string/pref_home_suggested_sites"
android:summary="@string/pref_home_suggested_sites_summary"
android:defaultValue="true" />
<ListPreference android:key="home.sync.updateMode"
android:title="@string/pref_home_updates"
android:entries="@array/pref_home_updates_entries"

View File

@ -130,6 +130,8 @@
<string name="pref_home_updates">&pref_home_updates;</string>
<string name="pref_home_updates_enabled">&pref_home_updates_enabled;</string>
<string name="pref_home_updates_wifi">&pref_home_updates_wifi;</string>
<string name="pref_home_suggested_sites">&pref_home_suggested_sites;</string>
<string name="pref_home_suggested_sites_summary">&pref_home_suggested_sites_summary;</string>
<string name="pref_header_customize">&pref_header_customize;</string>
<string name="pref_header_display">&pref_header_display;</string>
@ -234,6 +236,7 @@
<string name="site_security">&site_security;</string>
<string name="close_tab">&close_tab;</string>
<string name="new_tab_opened">&new_tab_opened;</string>
<string name="new_private_tab_opened">&new_private_tab_opened;</string>
<string name="one_tab">&one_tab;</string>
<string name="num_tabs">&num_tabs2;</string>
<string name="addons">&addons;</string>

View File

@ -5,28 +5,41 @@
package org.mozilla.gecko.tabspanel;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.TabsAccessor;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.tabspanel.TabsPanel.Panel;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.GeckoSwipeRefreshLayout;
import android.accounts.Account;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* Provides a container to wrap the list of synced tabs and provide swipe-to-refresh support. The
* only child view should be an instance of {@link RemoteTabsList}.
* Provides a container to wrap the list of synced tabs and provide
* swipe-to-refresh support. The only child view should be an instance of
* {@link RemoteTabsList}.
*/
public class RemoteTabsContainerPanel extends GeckoSwipeRefreshLayout
implements TabsPanel.PanelView {
private static final String[] STAGES_TO_SYNC_ON_REFRESH = new String[] { "tabs" };
/**
* Refresh indicators (the swipe-to-refresh "laser show" and the spinning
* icon) will never be shown for less than the following duration, in
* milliseconds.
*/
private static final long MINIMUM_REFRESH_INDICATOR_DURATION_IN_MS = 12 * 100; // 12 frames, 100 ms each.
private final Context context;
private final RemoteTabsSyncObserver syncListener;
private TabsPanel panel;
private RemoteTabsList list;
// Whether or not a sync status listener is attached.
@ -54,8 +67,8 @@ public class RemoteTabsContainerPanel extends GeckoSwipeRefreshLayout
@Override
public boolean canChildScrollUp() {
// We are not supporting swipe-to-refresh for old sync. This disables the swipe gesture if
// no FxA are detected.
// We are not supporting swipe-to-refresh for old sync. This disables
// the swipe gesture if no FxA are detected.
if (FirefoxAccounts.firefoxAccountsExist(getContext())) {
return super.canChildScrollUp();
} else {
@ -65,12 +78,18 @@ public class RemoteTabsContainerPanel extends GeckoSwipeRefreshLayout
@Override
public void setTabsPanel(TabsPanel panel) {
this.panel = panel;
list.setTabsPanel(panel);
}
@Override
public void show() {
// Start fetching remote tabs.
TabsAccessor.getTabs(context, list);
// The user can trigger a tabs sync, so we want to be very certain the
// locally-persisted tabs are fresh (tab writes are batched and delayed).
Tabs.getInstance().persistAllTabs();
if (!isListening) {
isListening = true;
FirefoxAccounts.addSyncStatusListener(syncListener);
@ -103,6 +122,10 @@ public class RemoteTabsContainerPanel extends GeckoSwipeRefreshLayout
}
private class RemoteTabsSyncObserver implements FirefoxAccounts.SyncStatusListener {
// Written on the main thread, and read off the main thread, but no need
// to synchronize.
protected volatile long lastSyncStarted = 0;
@Override
public Context getContext() {
return RemoteTabsContainerPanel.this.getContext();
@ -113,16 +136,50 @@ public class RemoteTabsContainerPanel extends GeckoSwipeRefreshLayout
return FirefoxAccounts.getFirefoxAccount(getContext());
}
public void onSyncFinished() {
public void onSyncStarted() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
TabsAccessor.getTabs(context, list);
setRefreshing(false);
lastSyncStarted = System.currentTimeMillis();
// Get the sync icon and start the drawable's animation.
final Drawable iconDrawable = panel.getIconDrawable(Panel.REMOTE_TABS);
if (iconDrawable instanceof AnimationDrawable) {
((AnimationDrawable) iconDrawable).start();
}
}
});
}
public void onSyncStarted() {}
public void onSyncFinished() {
final Handler uiHandler = ThreadUtils.getUiHandler();
// We want to update the list immediately ...
uiHandler.post(new Runnable() {
@Override
public void run() {
TabsAccessor.getTabs(context, list);
}
});
// ... but we want the refresh indicators to persist for long enough
// to be visible.
final long last = lastSyncStarted;
final long now = System.currentTimeMillis();
final long delay = Math.max(0, MINIMUM_REFRESH_INDICATOR_DURATION_IN_MS - (now - last));
uiHandler.postDelayed(new Runnable() {
@Override
public void run() {
setRefreshing(false);
// Get the sync icon and stop the drawable's animation.
final Drawable iconDrawable = panel.getIconDrawable(Panel.REMOTE_TABS);
if (iconDrawable instanceof AnimationDrawable) {
((AnimationDrawable) iconDrawable).stop();
}
}
}, delay);
}
}
}

View File

@ -20,6 +20,7 @@ import org.mozilla.gecko.widget.IconTabWidget;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@ -140,7 +141,8 @@ public class TabsPanel extends LinearLayout
mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private);
if (!GeckoProfile.get(mContext).inGuestMode()) {
mTabWidget.addTab(R.drawable.tabs_synced, R.string.tabs_synced);
// n.b.: the animation does not start automatically.
mTabWidget.addTab(R.drawable.tabs_synced_animation, R.string.tabs_synced);
}
mTabWidget.setTabSelectionListener(this);
@ -461,4 +463,13 @@ public class TabsPanel extends LinearLayout
if (mLayoutChangeListener != null)
mLayoutChangeListener.onTabsLayoutChange(width, height);
}
/**
* Fetch the Drawable icon corresponding to the given panel.
* @param panel to fetch icon for.
* @return Drawable instance, or null if no icon is being displayed, or the icon does not exist.
*/
public Drawable getIconDrawable(Panel panel) {
return mTabWidget.getIconDrawable(panel.ordinal());
}
}

View File

@ -29,7 +29,9 @@ import android.widget.Button;
import android.widget.TextView;
public class ButtonToast {
@SuppressWarnings("unused")
private final static String LOGTAG = "GeckoButtonToast";
private final static int TOAST_DURATION = 5000;
private final View mView;
@ -37,7 +39,6 @@ public class ButtonToast {
private final Button mButton;
private final Handler mHideHandler = new Handler();
private final ToastListener mListener;
private final LinkedList<Toast> mQueue = new LinkedList<Toast>();
private Toast mCurrentToast;
@ -70,7 +71,6 @@ public class ButtonToast {
public ButtonToast(View view) {
mView = view;
mListener = null;
mMessageView = (TextView) mView.findViewById(R.id.toast_message);
mButton = (Button) mView.findViewById(R.id.toast_button);
mButton.setOnClickListener(new View.OnClickListener() {
@ -90,6 +90,13 @@ public class ButtonToast {
hide(true, ReasonHidden.STARTUP);
}
public void show(boolean immediate, CharSequence message,
CharSequence buttonMessage, int buttonDrawableId,
ToastListener listener) {
final Drawable d = mView.getContext().getResources().getDrawable(buttonDrawableId);
show(false, message, buttonMessage, d, listener);
}
public void show(boolean immediate, CharSequence message,
CharSequence buttonMessage, Drawable buttonDrawable,
ToastListener listener) {
@ -106,10 +113,16 @@ public class ButtonToast {
mCurrentToast = t;
mButton.setEnabled(true);
mMessageView.setText(t.message);
mButton.setText(t.buttonMessage);
// Our toast is re-used, so we update all fields to clear any old values.
mMessageView.setText(null != t.message ? t.message : "");
mButton.setText(null != t.buttonMessage ? t.buttonMessage : "");
if (null != t.buttonDrawable) {
mButton.setCompoundDrawablePadding(mView.getContext().getResources().getDimensionPixelSize(R.dimen.toast_button_padding));
mButton.setCompoundDrawablesWithIntrinsicBounds(t.buttonDrawable, null, null, null);
} else {
mButton.setCompoundDrawablePadding(0);
mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
}
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, TOAST_DURATION);

View File

@ -0,0 +1,168 @@
/* 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.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.EditText;
import org.mozilla.gecko.R;
public class FloatingHintEditText extends EditText {
private static enum Animation { NONE, SHRINK, GROW }
private static final float HINT_SCALE = 0.6f;
private static final int ANIMATION_STEPS = 6;
private final Paint floatingHintPaint = new Paint();
private final ColorStateList floatingHintColors;
private final ColorStateList normalHintColors;
private final int defaultFloatingHintColor;
private final int defaultNormalHintColor;
private boolean wasEmpty;
private int animationFrame;
private Animation animation = Animation.NONE;
public FloatingHintEditText(Context context) {
this(context, null);
}
public FloatingHintEditText(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.floatingHintEditTextStyle);
}
public FloatingHintEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
floatingHintColors = getResources().getColorStateList(R.color.floating_hint_text);
normalHintColors = getHintTextColors();
defaultFloatingHintColor = floatingHintColors.getDefaultColor();
defaultNormalHintColor = normalHintColors.getDefaultColor();
wasEmpty = TextUtils.isEmpty(getText());
}
@Override
public int getCompoundPaddingTop() {
final FontMetricsInt metrics = getPaint().getFontMetricsInt();
final int floatingHintHeight = (int) ((metrics.bottom - metrics.top) * HINT_SCALE);
return super.getCompoundPaddingTop() + floatingHintHeight;
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
final boolean isEmpty = TextUtils.isEmpty(getText());
// The empty state hasn't changed, so the hint stays the same.
if (wasEmpty == isEmpty) {
return;
}
wasEmpty = isEmpty;
// Don't animate if we aren't visible.
if (!isShown()) {
return;
}
if (isEmpty) {
animation = Animation.GROW;
// The TextView will show a hint since the field is empty, but since we're animating
// from the floating hint, we don't want the normal hint to appear yet. We set it to
// transparent here, then restore the hint color after the animation has finished.
setHintTextColor(Color.TRANSPARENT);
} else {
animation = Animation.SHRINK;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (TextUtils.isEmpty(getHint())) {
return;
}
final boolean isAnimating = (animation != Animation.NONE);
// The large hint is drawn by Android, so do nothing.
if (!isAnimating && TextUtils.isEmpty(getText())) {
return;
}
final Paint paint = getPaint();
final float hintPosX = getCompoundPaddingLeft() + getScrollX();
final float normalHintPosY = getBaseline();
final float floatingHintPosY = normalHintPosY + paint.getFontMetricsInt().top + getScrollY();
final float normalHintSize = getTextSize();
final float floatingHintSize = normalHintSize * HINT_SCALE;
final int[] stateSet = getDrawableState();
final int floatingHintColor = floatingHintColors.getColorForState(stateSet, defaultFloatingHintColor);
floatingHintPaint.set(paint);
// If we're not animating, we're showing the floating hint, so draw it and bail.
if (!isAnimating) {
drawHint(canvas, floatingHintSize, floatingHintColor, hintPosX, floatingHintPosY);
return;
}
// We are animating, so draw the linearly interpolated frame.
final int normalHintColor = normalHintColors.getColorForState(stateSet, defaultNormalHintColor);
if (animation == Animation.SHRINK) {
drawAnimationFrame(canvas, normalHintSize, floatingHintSize,
hintPosX, normalHintPosY, floatingHintPosY, normalHintColor, floatingHintColor);
} else {
drawAnimationFrame(canvas, floatingHintSize, normalHintSize,
hintPosX, floatingHintPosY, normalHintPosY, floatingHintColor, normalHintColor);
}
animationFrame++;
if (animationFrame == ANIMATION_STEPS) {
// After the grow animation has finished, restore the normal TextView hint color that we
// removed in our onTextChanged listener.
if (animation == Animation.GROW) {
setHintTextColor(normalHintColors);
}
animation = Animation.NONE;
animationFrame = 0;
}
invalidate();
}
private void drawAnimationFrame(Canvas canvas, float fromSize, float toSize,
float hintPosX, float fromY, float toY, int fromColor, int toColor) {
final float textSize = lerp(fromSize, toSize);
final float hintPosY = lerp(fromY, toY);
final int color = Color.rgb((int) lerp(Color.red(fromColor), Color.red(toColor)),
(int) lerp(Color.green(fromColor), Color.green(toColor)),
(int) lerp(Color.blue(fromColor), Color.blue(toColor)));
drawHint(canvas, textSize, color, hintPosX, hintPosY);
}
private void drawHint(Canvas canvas, float textSize, int color, float x, float y) {
floatingHintPaint.setTextSize(textSize);
floatingHintPaint.setColor(color);
canvas.drawText(getHint().toString(), x, y, floatingHintPaint);
}
private float lerp(float from, float to) {
final float alpha = (float) animationFrame / (ANIMATION_STEPS - 1);
return from * (1 - alpha) + to * alpha;
}
}

View File

@ -8,12 +8,13 @@ import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.TabWidget;
import android.widget.TextView;
public class IconTabWidget extends TabWidget {
private OnTabChangedListener mListener;
@ -72,4 +73,24 @@ public class IconTabWidget extends TabWidget {
mListener.onTabChanged(mIndex);
}
}
/**
* Fetch the Drawable icon corresponding to the given panel.
* @param panel to fetch icon for.
* @return Drawable instance, or null if no icon is being displayed, or the icon does not exist.
*/
public Drawable getIconDrawable(int index) {
if (!mIsIcon) {
return null;
}
// We can have multiple views in the tray for each child. This finds the
// first view corresponding to the given tab. This varies by Android
// version. The first view should always be our ImageButton, but let's
// be safe.
final View view = getChildTabViewAt(index);
if (view instanceof ImageButton) {
return ((ImageButton) view).getDrawable();
}
return null;
}
}

View File

@ -36,6 +36,11 @@ var Devices = {
this.updateDeviceList();
}, false);
let manual = document.getElementById("connect");
manual.addEventListener("click", (evt) => {
this.connectManually(evt);
}, false);
this._savedSearchInterval = SimpleServiceDiscovery.search(SEARCH_INTERVAL_IN_MILLISECONDS);
this.updateDeviceList();
@ -87,6 +92,46 @@ var Devices = {
this.updateDeviceList();
}
},
_fixedTargetForType: function(type, ip) {
let fixedTarget = {};
if (type == "roku") {
fixedTarget.target = "roku:ecp";
fixedTarget.location = "http://" + ip + ":8060";
} else if (type == "chromecast") {
fixedTarget.target = "urn:dial-multiscreen-org:service:dial:1";
fixedTarget.location = "http://" + ip + ":8008";
}
return fixedTarget;
},
connectManually: function(evt) {
// Since there is no form submit event, this is not validated. However,
// after we process this event, the element's validation state is updated.
let ip = document.getElementById("ip");
if (!ip.checkValidity()) {
dump("Manually entered IP address is not valid!");
return;
}
let fixedTargets = [];
try {
fixedTargets = JSON.parse(Services.prefs.getCharPref("browser.casting.fixedTargets"));
} catch (e) {}
let type = document.getElementById("type").value;
let fixedTarget = this._fixedTargetForType(type, ip.value);
// Early abort if we're already looking for this target.
if (fixedTargets.indexOf(fixedTarget) > -1)
return;
fixedTargets.push(fixedTarget);
Services.prefs.setCharPref("browser.casting.fixedTargets", JSON.stringify(fixedTargets));
// The backend does not yet listen for pref changes, so we trigger a scan.
this.updateDeviceList();
},
};
window.addEventListener("load", Devices.init.bind(Devices), false);

View File

@ -30,6 +30,20 @@
<button id="refresh">&aboutDevices.refresh;</button>
<h1>&aboutDevices.addDeviceHeader;</h1>
<div id="manual">
<select id="type">
<option value="roku">&aboutDevices.roku;</option>
<option value="chromecast">&aboutDevices.chromecast;</option>
</select>
<input id="ip" type="text" required="required"
pattern="((^|\.)((25[0-5])|(2[0-4]\d)|(1\d\d)|([1-9]?\d))){4}$"
placeholder="&aboutDevices.placeholder;" />
<button id="connect">&aboutDevices.connectManually;</button>
</div>
<script type="text/javascript;version=1.8" src="chrome://browser/content/aboutDevices.js"></script>
</body>
</html>

View File

@ -485,11 +485,16 @@ var BrowserApp = {
function(aTarget) {
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id });
let tab = BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id });
let newtabStrings = Strings.browser.GetStringFromName("newtabpopup.opened");
let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
NativeWindow.toast.show(label, "short");
NativeWindow.toast.show(label, "long", {
button: {
icon: "drawable://select_opened_tab",
callback: () => { BrowserApp.selectTab(tab); },
}
});
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.openInPrivateTab"),
@ -497,11 +502,16 @@ var BrowserApp = {
function(aTarget) {
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true });
let tab = BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true });
let newtabStrings = Strings.browser.GetStringFromName("newprivatetabpopup.opened");
let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
NativeWindow.toast.show(label, "short");
NativeWindow.toast.show(label, "long", {
button: {
icon: "drawable://select_opened_tab",
callback: () => { BrowserApp.selectTab(tab); },
}
});
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyLink"),
@ -1764,12 +1774,20 @@ var NativeWindow = {
if (aOptions && aOptions.button) {
msg.button = {
label: aOptions.button.label,
id: uuidgen.generateUUID().toString(),
};
// null is badly handled by the receiver, so try to avoid including nulls.
if (aOptions.button.label) {
msg.button.label = aOptions.button.label;
}
if (aOptions.button.icon) {
// If the caller specified a button, make sure we convert any chrome urls
// to jar:jar urls so that the frontend can show them
icon: aOptions.button.icon ? resolveGeckoURI(aOptions.button.icon) : null,
msg.button.icon = resolveGeckoURI(aOptions.button.icon);
};
this._callbacks[msg.button.id] = aOptions.button.callback;
}

View File

@ -5,3 +5,10 @@
<!ENTITY aboutDevices.title "Devices">
<!ENTITY aboutDevices.header "Your devices">
<!ENTITY aboutDevices.refresh "Refresh">
<!ENTITY aboutDevices.addDeviceHeader "Add a device">
<!ENTITY aboutDevices.roku "Roku">
<!ENTITY aboutDevices.chromecast "Chromecast">
<!-- Localization note (aboutDevices.placeholder): this is the hint shown to the
user prompting them to input the IP address of a casting device. -->
<!ENTITY aboutDevices.placeholder "IP address">
<!ENTITY aboutDevices.connectManually "Connect manually">

View File

@ -31,3 +31,21 @@ gecko.handlerService.schemes.mailto.1.uriTemplate=https://mail.google.com/mail/?
# selection UI
browser.contentHandlers.types.0.title=My Yahoo!
browser.contentHandlers.types.0.uri=https://add.my.yahoo.com/rss?url=%s
# Order of suggested websites displayed in the Top Sites panel.
# Values for these keys must correspond to the name used in the keys that
# define each suggested website's details. For example:
# browser.suggestedsites.list.0=NAME
# browser.suggestedsites.NAME.title=Displayed name
# browser.suggestedsites.NAME.url=Website URL
# browser.suggestedsites.NAME.bgcolor= Color (hex format)
browser.suggestedsites.list.0=mozilla
browser.suggestedsites.list.1=fxmarketplace
browser.suggestedsites.mozilla.title=Mozilla
browser.suggestedsites.mozilla.url=https://mozilla.org/en-US/
browser.suggestedsites.mozilla.bgcolor=#c13832
browser.suggestedsites.fxmarketplace.title=Firefox Marketplace
browser.suggestedsites.fxmarketplace.url=https://marketplace.firefox.com/
browser.suggestedsites.fxmarketplace.bgcolor=#0095dd

View File

@ -7,18 +7,21 @@
This script follows these steps:
1. Looks for a 'list.txt' file in one of the given source directories
(see srcdir). The list.txt contains the list of site names, one per line.
1. Read the region.properties file in all the given source directories
(see srcdir option). Merge all properties into a single dict accounting for
the priority of source directories.
2. For each site name found in 'list.txt', it tries to find a matching
.json file in one of the source directories.
2. Read the list of sites from the 'browser.suggestedsites.list.INDEX'
properties with value of these keys being an identifier for each suggested site
e.g. browser.suggestedsites.list.0=mozilla, browser.suggestedsites.list.1=fxmarketplace.
3. For each json file, load it and define the respective imageurl
based on a image URL template composed by the target Android package name
and the site name.
3. For each site identifier defined by the list keys, look for matching branches
containing the respective properties i.e. url, title, etc. For example,
for a 'mozilla' identifier, we'll look for keys like:
browser.suggestedsites.mozilla.url, browser.suggestedsites.mozilla.title, etc.
4. Join the JSON representation of each site into a JSON array and write
the result to suggestedsites.json on the locale-specific raw resource
4. Generate a JSON representation of each site, join them in a JSON array, and
write the result to suggestedsites.json on the locale-specific raw resource
directory e.g. raw/suggestedsites.json, raw-pt-rBR/suggestedsites.json.
'''
@ -26,6 +29,7 @@ from __future__ import print_function
import argparse
import json
import re
import sys
import os
@ -38,6 +42,59 @@ from mozpack.files import (
import mozpack.path as mozpath
def read_properties_file(filename):
"""Reads a properties file into a dict.
Ignores empty, comment lines, and keys not starting with the prefix for
suggested sites ('browser.suggestedsites'). Removes the prefix from all
matching keys i.e. turns 'browser.suggestedsites.foo' into simply 'foo'
"""
prefix = 'browser.suggestedsites.'
properties = {}
for l in open(filename, 'rt').readlines():
line = l.strip()
if not line.startswith(prefix):
continue
(k, v) = re.split('\s*=\s*', line, 1)
properties[k[len(prefix):]] = v
return properties
def merge_properties(filename, srcdirs):
"""Merges properties from the given file in the given source directories."""
properties = {}
for srcdir in srcdirs:
path = mozpath.join(srcdir, filename)
try:
properties.update(read_properties_file(path))
except IOError, e:
# Ignore non-existing files
continue
return properties
def get_site_list_from_properties(properties):
"""Turns {'list.0':'foo', 'list.1':'bar'} into ['foo', 'bar']."""
prefix = 'list.'
indexes = []
for k, v in properties.iteritems():
if not k.startswith(prefix):
continue
indexes.append(int(k[len(prefix):]))
return [properties[prefix + str(index)] for index in sorted(indexes)]
def get_site_from_properties(name, properties):
"""Turns {'foo.title':'title', ...} into {'title':'title', ...}."""
prefix = '{name}.'.format(name=name)
try:
site = dict((k, properties[prefix + k]) for k in ('title', 'url', 'bgcolor'))
except IndexError, e:
raise Exception("Could not find required property for '{name}: {error}'"
.format(name=name, error=str(e)))
return site
def main(args):
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', '-v', default=False, action='store_true',
@ -57,15 +114,9 @@ def main(args):
help='output')
opts = parser.parse_args(args)
def resolve_filename(filename):
for srcdir in opts.srcdir:
path = mozpath.join(srcdir, filename)
if os.path.exists(path):
return path
return None
# The list.txt file has one site name per line.
names = [s.strip() for s in open(resolve_filename('list.txt'), 'rt').readlines()]
# Use reversed order so that the first srcdir has higher priority to override keys.
all_properties = merge_properties('region.properties', reversed(opts.srcdir))
names = get_site_list_from_properties(all_properties)
if opts.verbose:
print('Reading {len} suggested sites: {names}'.format(len=len(names), names=names))
@ -73,15 +124,11 @@ def main(args):
image_url_template = 'android.resource://%s/drawable/suggestedsites_{name}' % opts.android_package_name
drawables_template = 'drawable*/suggestedsites_{name}.*'
# Load json files corresponding to each site name and define their
# Load properties corresponding to each site name and define their
# respective image URL.
sites = []
for name in names:
filename = resolve_filename(name + '.json')
if opts.verbose:
print("Reading '{name}' from {filename}"
.format(name=name, filename=filename))
site = json.load(open(filename, 'rt'))
site = get_site_from_properties(name, all_properties)
site['imageurl'] = image_url_template.format(name=name)
sites.append(site)

View File

@ -3,9 +3,9 @@
# 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/.
export:: mozilla_api_key google_api_key
export:: mozilla_api_key google_api_key bing_api_key
EXTRA_PP_COMPONENTS_FLAGS = -I mozilla_api_key -I google_api_key
EXTRA_PP_COMPONENTS_FLAGS = -I mozilla_api_key -I google_api_key -I bing_api_key
include $(topsrcdir)/config/rules.mk
@ -15,4 +15,8 @@ mozilla_api_key:
google_api_key:
@echo '#define MOZ_GOOGLE_API_KEY $(MOZ_GOOGLE_API_KEY)' > $@
GARBAGE += google_api_key moz_google_api_key
bing_api_key:
@echo '#define MOZ_BING_API_KEY $(MOZ_BING_API_KEY)' > $@
@echo '#define MOZ_BING_API_CLIENTID $(MOZ_BING_API_CLIENTID)' >> $@
GARBAGE += mozilla_api_key google_api_key bing_api_key

View File

@ -105,6 +105,8 @@ nsURLFormatterService.prototype = {
CHANNEL: function() UpdateChannel.get(),
MOZILLA_API_KEY: function() "@MOZ_MOZILLA_API_KEY@",
GOOGLE_API_KEY: function() "@MOZ_GOOGLE_API_KEY@",
BING_API_CLIENTID:function() "@MOZ_BING_API_CLIENTID@",
BING_API_KEY: function() "@MOZ_BING_API_KEY@",
DISTRIBUTION: function() this.distribution.id,
DISTRIBUTION_VERSION: function() this.distribution.version
},