Merge m-c to inbound, a=merge

MozReview-Commit-ID: Ah48RzFU8Mt
This commit is contained in:
Wes Kocher 2017-07-21 18:20:46 -07:00
commit d45eb771f0
534 changed files with 41730 additions and 35951 deletions

View File

@ -44,7 +44,7 @@ function simulateCommand(ele) {
let { document } = window;
var evt = document.createEvent('XULCommandEvent');
evt.initCommandEvent('command', true, true, window,
0, false, false, false, false, null);
0, false, false, false, false, null, 0);
ele.dispatchEvent(evt);
}
exports.simulateCommand = simulateCommand;

View File

@ -51,7 +51,7 @@ function simulateCommand(ele) {
let { document } = window;
var evt = document.createEvent('XULCommandEvent');
evt.initCommandEvent('command', true, true, window,
0, false, false, false, false, null);
0, false, false, false, false, null, 0);
ele.dispatchEvent(evt);
}
exports.simulateCommand = simulateCommand;

View File

@ -334,7 +334,8 @@ var gGestureSupport = {
let cmdEvent = document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, window, 0,
aEvent.ctrlKey, aEvent.altKey,
aEvent.shiftKey, aEvent.metaKey, aEvent);
aEvent.shiftKey, aEvent.metaKey,
aEvent, aEvent.mozInputSource);
node.dispatchEvent(cmdEvent);
}

View File

@ -1860,16 +1860,16 @@ var BookmarkingUI = {
},
showSubView(anchor) {
this._showSubView(anchor);
this._showSubView(null, anchor);
},
_showSubView(anchor = document.getElementById(this.BOOKMARK_BUTTON_ID)) {
_showSubView(event, anchor = document.getElementById(this.BOOKMARK_BUTTON_ID)) {
let view = document.getElementById("PanelUI-bookmarks");
view.addEventListener("ViewShowing", this);
view.addEventListener("ViewHiding", this);
anchor.setAttribute("closemenu", "none");
PanelUI.showSubView("PanelUI-bookmarks", anchor,
CustomizableUI.AREA_PANEL);
CustomizableUI.AREA_PANEL, event);
},
onCommand: function BUI_onCommand(aEvent) {
@ -1879,7 +1879,7 @@ var BookmarkingUI = {
// Handle special case when the button is in the panel.
if (this.button.getAttribute("cui-areatype") == CustomizableUI.TYPE_MENU_PANEL) {
this._showSubView();
this._showSubView(aEvent);
return;
}
let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)

View File

@ -380,7 +380,7 @@ const gClickAndHoldListenersOnElement = {
let cmdEvent = document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, window, 0,
aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
aEvent.metaKey, null);
aEvent.metaKey, null, aEvent.mozInputSource);
aEvent.currentTarget.dispatchEvent(cmdEvent);
// This is here to cancel the XUL default event
@ -1631,7 +1631,7 @@ var gBrowserInit = {
PointerLock.init();
if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
ContextMenuTouchModeObserver.init();
MenuTouchModeObserver.init();
}
// initialize the sync UI
@ -1846,7 +1846,7 @@ var gBrowserInit = {
}
if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
ContextMenuTouchModeObserver.uninit();
MenuTouchModeObserver.uninit();
}
BrowserOffline.uninit();
IndexedDBPromptHelper.uninit();
@ -7960,7 +7960,10 @@ var gPageActionButton = {
this._preparePanelToBeShown();
this.panel.hidden = false;
this.panel.openPopup(this.button, "bottomcenter topright");
this.panel.openPopup(this.button, {
position: "bottomcenter topright",
triggerEvent: event,
});
},
_preparePanelToBeShown() {
@ -8328,16 +8331,17 @@ function restoreLastSession() {
SessionStore.restoreLastSession();
}
/* Observes context menus and adjusts their size for better
/* Observes menus and adjusts their size for better
* usability when opened via a touch screen. */
var ContextMenuTouchModeObserver = {
var MenuTouchModeObserver = {
init() {
window.addEventListener("popupshowing", this, true);
},
handleEvent(event) {
let target = event.originalTarget;
if (target.localName != "menupopup") {
// Only resize non-context menus in Photon.
if (target.localName != "menupopup" && !gPhotonStructure) {
return;
}

View File

@ -1218,7 +1218,7 @@
tooltip="dynamic-shortcut-tooltip"/>
#ifdef MOZ_PHOTON_THEME
<toolbarbutton id="library-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
oncommand="PanelUI.showSubView('appMenu-libraryView', this, null, true);"
oncommand="PanelUI.showSubView('appMenu-libraryView', this, null, event);"
closemenu="none"
label="&places.library.title;"/>
#endif

View File

@ -1980,6 +1980,22 @@
</body>
</method>
<method name="removePreloadedBrowser">
<body>
<![CDATA[
if (!this._isPreloadingEnabled()) {
return;
}
let browser = this._getPreloadedBrowser();
if (browser) {
browser.remove();
}
]]>
</body>
</method>
<field name="_preloadedBrowser">null</field>
<method name="_getPreloadedBrowser">
<body>
@ -5579,6 +5595,7 @@
</getter>
</property>
<field name="_soundPlayingAttrRemovalTimer">0</field>
<field name="_hoverTabTimer">null</field>
</implementation>
<handlers>
@ -5694,9 +5711,7 @@
// Preloaded browsers do not actually have any tabs. If one crashes,
// it should be released and removed.
if (browser === this._preloadedBrowser) {
// Calling _getPreloadedBrowser is necessary to actually consume the preloaded browser
let preloaded = this._getPreloadedBrowser();
preloaded.remove();
this.removePreloadedBrowser();
return;
}
@ -7427,6 +7442,11 @@
this._setPositionAttributes(val);
// Tab becomes visible, it's not unselected anymore.
if (val) {
this.finishUnselectedTabHoverTimer();
}
return val;
]]>
</setter>
@ -7570,6 +7590,10 @@
}
tabContainer._hoveredTab = this;
if (this.linkedPanel && !this.selected) {
this.linkedBrowser.unselectedTabHover(true);
this.startUnselectedTabHoverTimer();
}
]]></body>
</method>
@ -7586,6 +7610,55 @@
}
tabContainer._hoveredTab = null;
if (this.linkedPanel && !this.selected) {
this.linkedBrowser.unselectedTabHover(false);
this.cancelUnselectedTabHoverTimer();
}
]]></body>
</method>
<method name="startUnselectedTabHoverTimer">
<body><![CDATA[
// Only record data when we need to.
if (!this.linkedBrowser.shouldHandleUnselectedTabHover) {
return;
}
if (!TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.start("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
if (this._hoverTabTimer) {
clearTimeout(this._hoverTabTimer);
this._hoverTabTimer = null;
}
]]></body>
</method>
<method name="cancelUnselectedTabHoverTimer">
<body><![CDATA[
// Since we're listening "mouseout" event, instead of "mouseleave".
// Every time the cursor is moving from the tab to its child node (icon),
// it would dispatch "mouseout"(for tab) first and then dispatch
// "mouseover" (for icon, eg: close button, speaker icon) soon.
// It causes we would cancel present TelemetryStopwatch immediately
// when cursor is moving on the icon, and then start a new one.
// In order to avoid this situation, we could delay cancellation and
// remove it if we get "mouseover" within very short period.
this._hoverTabTimer = setTimeout(() => {
if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
}, 100);
]]></body>
</method>
<method name="finishUnselectedTabHoverTimer">
<body><![CDATA[
// Stop timer when the tab is opened.
if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.finish("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
]]></body>
</method>

View File

@ -99,6 +99,9 @@ var whitelist = [
{file: "resource://gre/modules/ClusterLib.js"},
{file: "resource://gre/modules/ColorConversion.js"},
// List of built-in locales. See bug 1362617 for details.
{file: "resource://gre/res/multilocale.json"},
// The l10n build system can't package string files only for some platforms.
{file: "resource://gre/chrome/en-US/locale/en-US/global-platform/mac/accessible.properties",
platforms: ["linux", "win"]},

View File

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test"
]
};

View File

@ -0,0 +1,4 @@
[DEFAULT]
[browser_menu_touch.js]
skip-if = !(os == 'win' && os_version == '10.0')

View File

@ -0,0 +1,109 @@
/* 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/. */
/* This test checks that toolbar menus are in touchmode
* when opened through a touch event. */
async function openAndCheckMenu(menu, target) {
is(menu.state, "closed", "Menu panel is initally closed.");
let popupshown = BrowserTestUtils.waitForEvent(menu, "popupshown");
EventUtils.synthesizeNativeTapAtCenter(target);
await popupshown;
is(menu.state, "open", "Menu panel is open.");
is(menu.getAttribute("touchmode"), "true", "Menu panel is in touchmode.");
menu.hidePopup();
popupshown = BrowserTestUtils.waitForEvent(menu, "popupshown");
EventUtils.synthesizeMouseAtCenter(target, {});
await popupshown;
is(menu.state, "open", "Menu panel is open.");
ok(!menu.hasAttribute("touchmode"), "Menu panel is not in touchmode.");
menu.hidePopup();
}
// The customization UI menu is not attached to the document when it is
// closed and hence requires special attention.
async function openAndCheckCustomizationUIMenu(target) {
EventUtils.synthesizeNativeTapAtCenter(target);
await BrowserTestUtils.waitForCondition(() =>
document.getElementById("customizationui-widget-panel") != null);
let menu = document.getElementById("customizationui-widget-panel");
if (menu.state != "open") {
await BrowserTestUtils.waitForEvent(menu, "popupshown");
is(menu.state, "open", "Menu is open");
}
is(menu.getAttribute("touchmode"), "true", "Menu is in touchmode.");
menu.hidePopup();
EventUtils.synthesizeMouseAtCenter(target, {});
await BrowserTestUtils.waitForCondition(() =>
document.getElementById("customizationui-widget-panel") != null);
menu = document.getElementById("customizationui-widget-panel");
if (menu.state != "open") {
await BrowserTestUtils.waitForEvent(menu, "popupshown");
is(menu.state, "open", "Menu is open");
}
ok(!menu.hasAttribute("touchmode"), "Menu is not in touchmode.");
menu.hidePopup();
}
// Test main ("hamburger") menu.
add_task(async function test_main_menu_touch() {
if (!gPhotonStructure) {
ok(true, "Skipping test because we're not in Photon mode");
return;
}
let mainMenu = document.getElementById("appMenu-popup");
let target = document.getElementById("PanelUI-menu-button");
await openAndCheckMenu(mainMenu, target);
});
// Test the page action menu.
add_task(async function test_page_action_panel_touch() {
if (!gPhotonStructure) {
ok(true, "Skipping test because we're not in Photon mode");
return;
}
let pageActionPanel = document.getElementById("page-action-panel");
let target = document.getElementById("urlbar-page-action-button");
await openAndCheckMenu(pageActionPanel, target);
});
// Test the customizationUI panel, which is used for various menus
// such as library, history, sync, developer and encoding.
add_task(async function test_customizationui_panel_touch() {
if (!gPhotonStructure) {
ok(true, "Skipping test because we're not in Photon mode");
return;
}
CustomizableUI.addWidgetToArea("library-button", CustomizableUI.AREA_NAVBAR);
CustomizableUI.addWidgetToArea("history-panelmenu", CustomizableUI.AREA_NAVBAR);
await BrowserTestUtils.waitForCondition(() =>
CustomizableUI.getPlacementOfWidget("library-button").area == "nav-bar");
let target = document.getElementById("library-button");
await openAndCheckCustomizationUIMenu(target);
target = document.getElementById("history-panelmenu");
await openAndCheckCustomizationUIMenu(target);
CustomizableUI.reset();
});

View File

@ -37,6 +37,7 @@ BROWSER_CHROME_MANIFESTS += [
'content/test/tabcrashed/browser.ini',
'content/test/tabPrompts/browser.ini',
'content/test/tabs/browser.ini',
'content/test/touch/browser.ini',
'content/test/urlbar/browser.ini',
'content/test/webextensions/browser.ini',
'content/test/webrtc/browser.ini',

View File

@ -11,4 +11,4 @@ BROWSER_CHROME_MANIFESTS += [
JAR_MANIFESTS += ['jar.mn']
with Files('**'):
BUG_COMPONENT = ('DOM', 'Security')
BUG_COMPONENT = ('Core', 'DOM: Security')

View File

@ -1521,6 +1521,14 @@ var CustomizableUIInternal = {
handleWidgetCommand(aWidget, aNode, aEvent) {
log.debug("handleWidgetCommand");
if (aWidget.onBeforeCommand) {
try {
aWidget.onBeforeCommand.call(null, aEvent);
} catch (e) {
log.error(e);
}
}
if (aWidget.type == "button") {
if (aWidget.onCommand) {
try {
@ -1548,7 +1556,8 @@ var CustomizableUIInternal = {
anchor = wrapper.anchor;
}
}
ownerWindow.PanelUI.showSubView(aWidget.viewId, anchor, area);
ownerWindow.PanelUI.showSubView(aWidget.viewId, anchor, area, aEvent);
}
},
@ -2414,6 +2423,10 @@ var CustomizableUIInternal = {
this.wrapWidgetEventHandler("onCreated", widget);
this.wrapWidgetEventHandler("onDestroyed", widget);
if (typeof aData.onBeforeCommand == "function") {
widget.onBeforeCommand = aData.onBeforeCommand;
}
if (widget.type == "button") {
widget.onCommand = typeof aData.onCommand == "function" ?
aData.onCommand :
@ -3291,6 +3304,12 @@ this.CustomizableUI = {
* passing the document from which it was removed. This is
* useful especially for 'view' type widgets that need to
* cleanup after views that were constructed on the fly.
* - onBeforeCommand(aEvt): A function that will be invoked when the user
* activates the button but before the command
* is evaluated. Useful if code needs to run to
* change the button's icon in preparation to the
* pending command action. Called for both type=button
* and type=view.
* - onCommand(aEvt): Only useful for button widgets; a function that will be
* invoked when the user activates the button.
* - onClick(aEvt): Attached to all widgets; a function that will be invoked
@ -4242,7 +4261,6 @@ OverflowableToolbar.prototype = {
this._chevron.open = false;
} else if (this._panel.state != "hiding") {
this.show();
this._chevron.removeAttribute("animate");
}
},

View File

@ -133,7 +133,7 @@ function fillSubviewFromMenuItems(aMenuItems, aSubview) {
newEvent.initCommandEvent(
event.type, event.bubbles, event.cancelable, event.view,
event.detail, event.ctrlKey, event.altKey, event.shiftKey,
event.metaKey, event.sourceEvent);
event.metaKey, event.sourceEvent, 0);
item.dispatchEvent(newEvent);
});
}

View File

@ -819,6 +819,15 @@ CustomizeMode.prototype = {
Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
let overflowButton = this.document.getElementById("nav-bar-overflow-button");
overflowButton.setAttribute("animate", "true");
overflowButton.addEventListener("animationend", function onAnimationEnd(event) {
if (event.animationName.startsWith("overflow-animation")) {
this.setAttribute("fade", "true");
} else if (event.animationName == "overflow-fade") {
this.removeEventListener("animationend", onAnimationEnd);
this.removeAttribute("animate");
this.removeAttribute("fade");
}
});
}
},

View File

@ -942,6 +942,7 @@ this.PanelMultiView = class {
}
for (let panelView of this._viewStack.children) {
if (panelView.nodeName != "children") {
panelView.__lastKnownBoundingRect = null;
panelView.style.removeProperty("min-width");
panelView.style.removeProperty("max-width");
}
@ -949,8 +950,17 @@ this.PanelMultiView = class {
this.window.removeEventListener("keydown", this);
this._panel.removeEventListener("mousemove", this);
this._resetKeyNavigation();
// Clear the main view size caches. The dimensions could be different
// when the popup is opened again, e.g. through touch mode sizing.
this._mainViewHeight = 0;
this._mainViewWidth = 0;
this._viewContainer.style.removeProperty("min-height");
this._viewStack.style.removeProperty("max-height");
this._viewContainer.style.removeProperty("min-width");
this._viewContainer.style.removeProperty("max-width");
}
// Always try to layout the panel normally when reopening it. This is
// also the layout that will be used in customize mode.
if (this._mainView.hasAttribute("blockinboxworkaround")) {

View File

@ -273,10 +273,12 @@ const PanelUI = {
}
let anchor;
let domEvent = null;
if (!aEvent ||
aEvent.type == "command") {
anchor = this.menuButton;
} else {
domEvent = aEvent;
anchor = aEvent.target;
}
@ -285,7 +287,7 @@ const PanelUI = {
}, {once: true});
anchor = this._getPanelAnchor(anchor);
this.panel.openPopup(anchor);
this.panel.openPopup(anchor, { triggerEvent: domEvent });
}, (reason) => {
console.error("Error showing the PanelUI menu", reason);
});
@ -475,8 +477,22 @@ const PanelUI = {
* @param aViewId the ID of the subview to show.
* @param aAnchor the element that spawned the subview.
* @param aPlacementArea the CustomizableUI area that aAnchor is in.
* @param aEvent the event triggering the view showing.
*/
async showSubView(aViewId, aAnchor, aPlacementArea) {
async showSubView(aViewId, aAnchor, aPlacementArea, aEvent) {
let domEvent = null;
if (aEvent) {
if (aEvent.type == "command" && aEvent.inputSource != null) {
// Synthesize a new DOM mouse event to pass on the inputSource.
domEvent = document.createEvent("MouseEvent");
domEvent.initNSMouseEvent("click", true, true, null, 0, aEvent.screenX, aEvent.screenY,
0, 0, false, false, false, false, 0, aEvent.target, 0, aEvent.inputSource);
} else if (aEvent.mozInputSource != null) {
domEvent = aEvent;
}
}
this._ensureEventListenersAdded();
let viewNode = document.getElementById(aViewId);
if (!viewNode) {
@ -584,7 +600,10 @@ const PanelUI = {
anchor.setAttribute("consumeanchor", aAnchor.id);
}
tempPanel.openPopup(anchor, "bottomcenter topright");
tempPanel.openPopup(anchor, {
position: "bottomcenter topright",
triggerEvent: domEvent,
});
}
},

View File

@ -123,15 +123,19 @@ add_task(async function test_devtools_inspectedWindow_eval_bindings() {
await toolbox.once("split-console");
let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
const options = await new Promise(resolve => {
jsterm.once("variablesview-open", (evt, view, options) => resolve(options));
// Wait for the message to appear on the console.
const messageNode = await new Promise(resolve => {
jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
for (let m of messages) {
resolve(m.node);
jsterm.hud.off("new-messages", onThisMessage);
return;
}
});
});
const objectType = options.objectActor.type;
const objectPreviewProperties = options.objectActor.preview.ownProperties;
is(objectType, "object", "The inspected object has the expected type");
Assert.deepEqual(Object.keys(objectPreviewProperties), ["testkey"],
"The inspected object has the expected preview properties");
let objectInspectors = [...messageNode.querySelectorAll(".tree")];
is(objectInspectors.length, 1, "There is the expected number of object inspectors");
})();
const inspectJSObjectPromise = extension.awaitMessage(`inspectedWindow-eval-result`);

View File

@ -38,7 +38,7 @@ add_task(async function selectInternalOptionForFeed() {
// Select the option.
let cmdEvent = win.document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null, 0);
chooseItems[0].dispatchEvent(cmdEvent);
// Check that we display the correct result.

View File

@ -34,7 +34,7 @@ add_task(async function() {
let chooseItem = list.firstChild.querySelector(".choose-app-item");
let dialogLoadedPromise = promiseLoadSubDialog("chrome://global/content/appPicker.xul");
let cmdEvent = win.document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null, 0);
chooseItem.dispatchEvent(cmdEvent);
let dialog = await dialogLoadedPromise;
@ -63,7 +63,7 @@ add_task(async function() {
let manageItem = list.firstChild.querySelector(".manage-app-item");
cmdEvent = win.document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null, 0);
manageItem.dispatchEvent(cmdEvent);
dialog = await dialogLoadedPromise;

View File

@ -38,7 +38,7 @@ add_task(async function selectInternalOptionForFeed() {
// Select the option.
let cmdEvent = win.document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null, 0);
chooseItems[0].dispatchEvent(cmdEvent);
// Check that we display the correct result.

View File

@ -33,7 +33,7 @@ add_task(async function() {
let chooseItem = list.firstChild.querySelector(".choose-app-item");
let dialogLoadedPromise = promiseLoadSubDialog("chrome://global/content/appPicker.xul");
let cmdEvent = win.document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null, 0);
chooseItem.dispatchEvent(cmdEvent);
let dialog = await dialogLoadedPromise;
@ -62,7 +62,7 @@ add_task(async function() {
let manageItem = list.firstChild.querySelector(".manage-app-item");
cmdEvent = win.document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null, 0);
manageItem.dispatchEvent(cmdEvent);
dialog = await dialogLoadedPromise;

View File

@ -234,10 +234,7 @@ add_task(async function test_preload_crash() {
});
// Release any existing preloaded browser
let preloaded = gBrowser._getPreloadedBrowser();
if (preloaded) {
preloaded.remove();
}
gBrowser.removePreloadedBrowser();
// Create a fresh preloaded browser
gBrowser._createPreloadBrowser();

View File

@ -1,8 +1,6 @@
# This file is sourced by the nightly, beta, and release mozconfigs.
# TODO remove once configure defaults to stylo once stylo enabled
# on all platforms.
ac_add_options --enable-stylo=build
. $topsrcdir/build/mozconfig.stylo
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --with-google-api-keyfile=/builds/gapi.data

View File

@ -2,8 +2,7 @@ ac_add_options --enable-debug
ac_add_options --enable-dmd
ac_add_options --enable-verify-mar
# TODO remove once configure defaults to stylo once stylo enabled
ac_add_options --enable-stylo=build
. $topsrcdir/build/mozconfig.stylo
MOZ_AUTOMATION_L10N_CHECK=0

View File

@ -9,6 +9,7 @@ ac_add_options --enable-dmd
# Disable stylo until bug 1356926 is fixed and we have >= llvm39 on centos.
ac_add_options --disable-stylo
unset LLVM_CONFIG
# Use Clang as specified in manifest
export CC="$topsrcdir/clang/bin/clang"

View File

@ -2,9 +2,7 @@
ac_add_options --disable-debug
ac_add_options --enable-optimize="-O2 -gline-tables-only"
# TODO remove once configure defaults to stylo once stylo enabled
# on all platforms.
ac_add_options --enable-stylo=build
. $topsrcdir/build/mozconfig.stylo
# ASan specific options on Linux
ac_add_options --enable-valgrind

View File

@ -2,9 +2,7 @@
ac_add_options --disable-debug
ac_add_options --enable-optimize="-O2 -gline-tables-only"
# TODO remove once configure defaults to stylo once stylo enabled
# on all platforms.
ac_add_options --enable-stylo=build
. $topsrcdir/build/mozconfig.stylo
# ASan specific options on Linux
ac_add_options --enable-valgrind

View File

@ -8,6 +8,7 @@ ac_add_options --enable-dmd
# Disable stylo until bug 1356926 is fixed and we have >= llvm39 on centos.
ac_add_options --disable-stylo
unset LLVM_CONFIG
# Use Clang as specified in manifest
CC="$topsrcdir/clang/bin/clang"

View File

@ -8,6 +8,7 @@ ac_add_options --disable-gtest-in-build
# Rust code gives false positives that we have not entirely suppressed yet.
# Bug 1365915 tracks fixing these.
ac_add_options --disable-stylo
unset LLVM_CONFIG
# Include the override mozconfig again (even though the above includes it)
# since it's supposed to override everything.

View File

@ -1,8 +1,6 @@
# This file is sourced by the nightly, beta, and release mozconfigs.
# TODO remove once configure defaults to stylo once stylo enabled
# on all platforms.
ac_add_options --enable-stylo=build
. "$topsrcdir/build/mozconfig.stylo"
. "$topsrcdir/browser/config/mozconfigs/common"

View File

@ -2,9 +2,7 @@
MOZ_AUTOMATION_L10N_CHECK=0
. "$topsrcdir/browser/config/mozconfigs/common"
# TODO remove once configure defaults to stylo once stylo enabled
# on all platforms.
ac_add_options --enable-stylo=build
. "$topsrcdir/build/mozconfig.stylo"
ac_add_options --enable-debug
ac_add_options --enable-dmd

View File

@ -1,8 +1,6 @@
# This file is sourced by the nightly, beta, and release mozconfigs.
# TODO remove once configure defaults to stylo once stylo enabled
# on all platforms.
ac_add_options --enable-stylo=build
. "$topsrcdir/build/mozconfig.stylo"
. "$topsrcdir/browser/config/mozconfigs/common"

View File

@ -5,9 +5,7 @@ MOZ_AUTOMATION_L10N_CHECK=0
ac_add_options --target=x86_64-pc-mingw32
ac_add_options --host=x86_64-pc-mingw32
# TODO remove once configure defaults to stylo once stylo enabled
# on all platforms.
ac_add_options --enable-stylo=build
. "$topsrcdir/build/mozconfig.stylo"
ac_add_options --enable-debug
ac_add_options --enable-dmd

View File

@ -31,6 +31,7 @@ for (const type of [
"DELETE_HISTORY_URL_CONFIRM",
"DIALOG_CANCEL",
"DIALOG_OPEN",
"FEED_INIT",
"INIT",
"LOCALE_UPDATED",
"NEW_TAB_INITIAL_STATE",
@ -50,7 +51,13 @@ for (const type of [
"PREF_CHANGED",
"SAVE_TO_POCKET",
"SCREENSHOT_UPDATED",
"SECTION_DEREGISTER",
"SECTION_REGISTER",
"SECTION_ROWS_UPDATE",
"SET_PREF",
"SNIPPETS_DATA",
"SNIPPETS_RESET",
"SYSTEM_TICK",
"TELEMETRY_PERFORMANCE_EVENT",
"TELEMETRY_UNDESIRED_EVENT",
"TELEMETRY_USER_EVENT",

View File

@ -16,6 +16,7 @@ const INITIAL_STATE = {
// The version of the system-addon
version: null
},
Snippets: {initialized: false},
TopSites: {
// Have we received real data from history yet?
initialized: false,
@ -29,7 +30,8 @@ const INITIAL_STATE = {
Dialog: {
visible: false,
data: {}
}
},
Sections: []
};
function App(prevState = INITIAL_STATE.App, action) {
@ -105,6 +107,9 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
});
return hasMatch ? Object.assign({}, prevState, {rows: newRows}) : prevState;
case at.PLACES_BOOKMARK_ADDED:
if (!action.data) {
return prevState;
}
newRows = prevState.rows.map(site => {
if (site && site.url === action.data.url) {
const {bookmarkGuid, bookmarkTitle, lastModified} = action.data;
@ -114,6 +119,9 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
});
return Object.assign({}, prevState, {rows: newRows});
case at.PLACES_BOOKMARK_REMOVED:
if (!action.data) {
return prevState;
}
newRows = prevState.rows.map(site => {
if (site && site.url === action.data.url) {
const newSite = Object.assign({}, site);
@ -165,8 +173,58 @@ function Prefs(prevState = INITIAL_STATE.Prefs, action) {
}
}
function Sections(prevState = INITIAL_STATE.Sections, action) {
let hasMatch;
let newState;
switch (action.type) {
case at.SECTION_DEREGISTER:
return prevState.filter(section => section.id !== action.data);
case at.SECTION_REGISTER:
// If section exists in prevState, update it
newState = prevState.map(section => {
if (section && section.id === action.data.id) {
hasMatch = true;
return Object.assign({}, section, action.data);
}
return section;
});
// If section doesn't exist in prevState, create a new section object and
// append it to the sections state
if (!hasMatch) {
const initialized = action.data.rows && action.data.rows.length > 0;
newState.push(Object.assign({title: "", initialized, rows: []}, action.data));
}
return newState;
case at.SECTION_ROWS_UPDATE:
return prevState.map(section => {
if (section && section.id === action.data.id) {
return Object.assign({}, section, action.data);
}
return section;
});
case at.PLACES_LINK_DELETED:
case at.PLACES_LINK_BLOCKED:
return prevState.map(section =>
Object.assign({}, section, {rows: section.rows.filter(site => site.url !== action.data.url)}));
default:
return prevState;
}
}
function Snippets(prevState = INITIAL_STATE.Snippets, action) {
switch (action.type) {
case at.SNIPPETS_DATA:
return Object.assign({}, prevState, {initialized: true}, action.data);
case at.SNIPPETS_RESET:
return INITIAL_STATE.Snippets;
default:
return prevState;
}
}
this.INITIAL_STATE = INITIAL_STATE;
this.reducers = {TopSites, App, Prefs, Dialog};
this.reducers = {TopSites, App, Snippets, Prefs, Dialog, Sections};
this.insertPinned = insertPinned;
this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE", "insertPinned"];

View File

@ -1,3 +1,4 @@
@charset "UTF-8";
html {
box-sizing: border-box; }
@ -30,6 +31,8 @@ input {
vertical-align: middle; }
.icon.icon-spacer {
margin-inline-end: 8px; }
.icon.icon-small-spacer {
margin-inline-end: 6px; }
.icon.icon-bookmark {
background-image: url("assets/glyph-bookmark-16.svg"); }
.icon.icon-bookmark-remove {
@ -50,11 +53,19 @@ input {
background-image: url("assets/glyph-unpin-16.svg"); }
.icon.icon-pocket {
background-image: url("assets/glyph-pocket-16.svg"); }
.icon.icon-historyItem {
background-image: url("assets/glyph-historyItem-16.svg"); }
.icon.icon-trending {
background-image: url("assets/glyph-trending-16.svg"); }
.icon.icon-now {
background-image: url("assets/glyph-now-16.svg"); }
.icon.icon-pin-small {
background-image: url("assets/glyph-pin-12.svg");
background-size: 12px;
height: 12px;
width: 12px; }
.icon.icon-check {
background-image: url("chrome://browser/skin/check.svg"); }
html,
body,
@ -134,6 +145,19 @@ a {
color: #FFF;
margin-inline-start: auto; }
#snippets-container {
display: none;
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
height: 122px; }
#snippets {
max-width: 736px;
margin: 0 auto; }
.outer-wrapper {
display: flex;
flex-grow: 1;
@ -149,7 +173,7 @@ main {
main {
width: 736px; } }
main section {
margin-bottom: 41px; }
margin-bottom: 40px; }
.section-title {
color: #6E707E;
@ -205,10 +229,10 @@ main {
.top-sites-list .top-site-outer .context-menu-button:focus, .top-sites-list .top-site-outer .context-menu-button:active {
transform: scale(1);
opacity: 1; }
.top-sites-list .top-site-outer:hover .tile, .top-sites-list .top-site-outer:active .tile, .top-sites-list .top-site-outer:focus .tile, .top-sites-list .top-site-outer.active .tile {
.top-sites-list .top-site-outer:hover .tile, .top-sites-list .top-site-outer:focus .tile, .top-sites-list .top-site-outer.active .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1);
transition: box-shadow 150ms; }
.top-sites-list .top-site-outer:hover .context-menu-button, .top-sites-list .top-site-outer:active .context-menu-button, .top-sites-list .top-site-outer:focus .context-menu-button, .top-sites-list .top-site-outer.active .context-menu-button {
.top-sites-list .top-site-outer:hover .context-menu-button, .top-sites-list .top-site-outer:focus .context-menu-button, .top-sites-list .top-site-outer.active .context-menu-button {
transform: scale(1);
opacity: 1; }
.top-sites-list .top-site-outer .tile {
@ -258,6 +282,117 @@ main {
.top-sites-list .top-site-outer .title.pinned span {
padding: 0 13px; }
.sections-list .section-top-bar {
position: relative;
height: 16px;
margin-bottom: 18px; }
.sections-list .section-top-bar .section-title {
float: left; }
.sections-list .section-top-bar .section-info-option {
float: right; }
.sections-list .section-top-bar .info-option-icon {
background-image: url("assets/glyph-info-option-12.svg");
background-size: 12px 12px;
background-repeat: no-repeat;
background-position: center;
height: 16px;
width: 16px;
display: inline-block; }
.sections-list .section-top-bar .section-info-option div {
visibility: hidden;
opacity: 0;
transition: visibility 0.2s, opacity 0.2s ease-out;
transition-delay: 0.5s; }
.sections-list .section-top-bar .section-info-option:hover div {
visibility: visible;
opacity: 1;
transition: visibility 0.2s, opacity 0.2s ease-out; }
.sections-list .section-top-bar .info-option {
z-index: 9999;
position: absolute;
background: #FFF;
border: solid 1px rgba(0, 0, 0, 0.1);
border-radius: 3px;
font-size: 13px;
color: #0C0C0D;
line-height: 120%;
width: 320px;
right: 0;
top: 34px;
margin-top: -4px;
margin-right: -4px;
padding: 24px;
-moz-user-select: none; }
.sections-list .section-top-bar .info-option-header {
font-size: 15px;
font-weight: 600; }
.sections-list .section-top-bar .info-option-body {
margin: 0;
margin-top: 12px; }
.sections-list .section-top-bar .info-option-link {
display: block;
margin-top: 12px;
color: #0A84FF; }
.sections-list .section-list {
width: 768px;
clear: both;
margin: 0; }
.sections-list .section-empty-state {
width: 100%;
height: 266px;
display: flex;
border: solid 1px rgba(0, 0, 0, 0.1);
border-radius: 3px; }
.sections-list .section-empty-state .empty-state {
margin: auto;
max-width: 350px; }
.sections-list .section-empty-state .empty-state .empty-state-icon {
background-size: 50px 50px;
background-repeat: no-repeat;
background-position: center;
fill: rgba(160, 160, 160, 0.4);
-moz-context-properties: fill;
height: 50px;
width: 50px;
margin: 0 auto;
display: block; }
.sections-list .section-empty-state .empty-state .empty-state-message {
margin-bottom: 0;
font-size: 13px;
font-weight: 300;
color: #A0A0A0;
text-align: center; }
.topic {
font-size: 13px;
color: #BFC0C7;
min-width: 780px; }
.topic ul {
display: inline;
padding-left: 12px; }
.topic ul li {
display: inline; }
.topic ul li::after {
content: '•';
padding-left: 8px;
padding-right: 8px; }
.topic ul li:last-child::after {
content: none; }
.topic .topic-link {
color: #008EA4; }
.topic .topic-read-more {
float: right;
margin-right: 40px;
color: #008EA4; }
.topic .topic-read-more-logo {
padding-right: 10px;
margin-left: 5px;
background-image: url("assets/topic-show-more-12.svg");
background-repeat: no-repeat;
background-position-y: 2px; }
.search-wrapper {
cursor: default;
display: flex;
@ -516,3 +651,109 @@ main {
border-radius: 3px;
font-size: 14px;
z-index: 11002; }
.card-outer {
background: #FFF;
display: inline-block;
margin-inline-end: 32px;
margin-bottom: 16px;
width: 224px;
border-radius: 3px;
border-color: rgba(0, 0, 0, 0.1);
height: 266px;
position: relative; }
.card-outer .context-menu-button {
cursor: pointer;
position: absolute;
top: -13.5px;
offset-inline-end: -13.5px;
width: 27px;
height: 27px;
background-color: #FFF;
background-image: url("assets/glyph-more-16.svg");
background-position: 65%;
background-repeat: no-repeat;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 100%;
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1);
transform: scale(0.25);
opacity: 0;
transition-property: transform, opacity;
transition-duration: 200ms;
z-index: 399; }
.card-outer .context-menu-button:focus, .card-outer .context-menu-button:active {
transform: scale(1);
opacity: 1; }
.card-outer .card {
height: 100%;
border-radius: 3px; }
.card-outer > a {
display: block;
color: inherit;
height: 100%;
outline: none;
position: absolute; }
.card-outer > a.active .card, .card-outer > a:focus .card {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1);
transition: box-shadow 150ms; }
.card-outer:hover, .card-outer:focus, .card-outer.active {
outline: none;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1);
transition: box-shadow 150ms; }
.card-outer:hover .context-menu-button, .card-outer:focus .context-menu-button, .card-outer.active .context-menu-button {
transform: scale(1);
opacity: 1; }
.card-outer .card-preview-image {
position: relative;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
height: 122px;
border-bottom-color: rgba(0, 0, 0, 0.1);
border-bottom-style: solid;
border-bottom-width: 1px;
border-radius: 3px 3px 0 0; }
.card-outer .card-details {
padding: 10px 16px 12px; }
.card-outer .card-text {
overflow: hidden;
max-height: 78px; }
.card-outer .card-text.full-height {
max-height: 200px; }
.card-outer .card-host-name {
color: #858585;
font-size: 10px;
padding-bottom: 4px;
text-transform: uppercase; }
.card-outer .card-title {
margin: 0 0 2px;
font-size: inherit;
word-wrap: break-word; }
.card-outer .card-description {
font-size: 12px;
margin: 0;
word-wrap: break-word;
overflow: hidden;
line-height: 18px;
max-height: 34px; }
.card-outer .card-context {
padding: 16px 16px 14px 14px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
color: #A0A0A0;
font-size: 11px;
display: flex;
align-items: center; }
.card-outer .card-context-icon {
opacity: 0.5;
font-size: 13px;
margin-inline-end: 6px;
display: block; }
.card-outer .card-context-label {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }

View File

@ -8,6 +8,10 @@
</head>
<body class="activity-stream">
<div id="root"></div>
<div id="snippets-container">
<div id="topSection"></div> <!-- TODO: placeholder for v4 snippets. It should be removed when we switch to v5 -->
<div id="snippets"></div>
</div>
<script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script>
<script src="resource://activity-stream/vendor/react-dom.js"></script>

View File

@ -0,0 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#4d4d4d" d="M365,190a4,4,0,1,1,4-4A4,4,0,0,1,365,190Zm0-6a2,2,0,1,0,2,2A2,2,0,0,0,365,184Z" transform="translate(-357 -178)"/>
</svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="#999" d="M6 0a6 6 0 1 0 6 6 6 6 0 0 0-6-6zm.7 10.26a1.13 1.13 0 0 1-.78.28 1.13 1.13 0 0 1-.78-.28 1 1 0 0 1 0-1.42 1.13 1.13 0 0 1 .78-.28 1.13 1.13 0 0 1 .78.28 1 1 0 0 1 0 1.42zM8.55 5a3 3 0 0 1-.62.81l-.67.63a1.58 1.58 0 0 0-.4.57 2.24 2.24 0 0 0-.12.74H5.06a3.82 3.82 0 0 1 .19-1.35 2.11 2.11 0 0 1 .63-.86 4.17 4.17 0 0 0 .66-.67 1.09 1.09 0 0 0 .23-.67.73.73 0 0 0-.77-.86.71.71 0 0 0-.57.26 1.1 1.1 0 0 0-.23.7h-2A2.36 2.36 0 0 1 4 2.47a2.94 2.94 0 0 1 2-.65 3.06 3.06 0 0 1 2 .6 2.12 2.12 0 0 1 .72 1.72 2 2 0 0 1-.17.86z"/></svg>

After

Width:  |  Height:  |  Size: 612 B

View File

@ -0,0 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#4d4d4d" d="M8 0a8 8 0 1 0 8 8 8.009 8.009 0 0 0-8-8zm0 14a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zm3.5-6H8V4.5a.5.5 0 0 0-1 0v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 0-1z"/>
</svg>

After

Width:  |  Height:  |  Size: 479 B

View File

@ -2,5 +2,5 @@
- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M8 15a8 8 0 0 1-8-8V3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4a8 8 0 0 1-8 8zm3.985-10.032a.99.99 0 0 0-.725.319L7.978 8.57 4.755 5.336A.984.984 0 0 0 4 4.968a1 1 0 0 0-.714 1.7l-.016.011 3.293 3.306.707.707a1 1 0 0 0 1.414 0l.707-.707L12.7 6.679a1 1 0 0 0-.715-1.711z"/>
</svg>
<path fill="#4d4d4d" d="M8 15a8 8 0 0 1-8-8V3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4a8 8 0 0 1-8 8zm3.985-10.032a.99.99 0 0 0-.725.319L7.978 8.57 4.755 5.336A.984.984 0 0 0 4 4.968a1 1 0 0 0-.714 1.7l-.016.011 3.293 3.306.707.707a1 1 0 0 0 1.414 0l.707-.707L12.7 6.679a1 1 0 0 0-.715-1.711z"/>
</svg>

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 593 B

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Context-/-Pocket-Trending" fill="#999999">
<path d="M12.164765,5.74981818 C12.4404792,5.74981818 12.5976221,6.06981818 12.4233364,6.28509091 C10.7404792,8.37236364 4.26619353,15.6829091 4.15905067,15.744 C5.70047924,12.3301818 7.1276221,8.976 7.1276221,8.976 L4.3276221,8.976 C4.09905067,8.976 3.9376221,8.74472727 4.02333638,8.52654545 C4.70047924,6.77672727 6.86190781,1.32945455 7.30476495,0.216727273 C7.35333638,0.0916363636 7.46190781,0.0174545455 7.59476495,0.016 C8.32476495,0.0130909091 10.7904792,0.00290909091 12.5790507,0 C12.844765,0 12.9976221,0.305454545 12.8433364,0.525090909 L9.17190781,5.74981818 L12.164765,5.74981818 Z" id="Fill-1"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 985 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>Icon / &gt;</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="Icon-/-&gt;" stroke-width="2" stroke="#008EA4">
<polyline id="Path-2" points="4 2 8 6 4 10"></polyline>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@ -1025,6 +1025,7 @@
"header_stories": "Top Stories",
"header_visit_again": "Visit Again",
"header_bookmarks": "Recent Bookmarks",
"header_recommended_by": "Recommended by {provider}",
"header_bookmarks_placeholder": "You dont have any bookmarks yet.",
"header_stories_from": "from",
"type_label_visited": "Visited",
@ -1051,6 +1052,7 @@
"search_header": "{search_engine_name} Search",
"search_web_placeholder": "Search the Web",
"search_settings": "Change Search Settings",
"section_info_option": "Info",
"welcome_title": "Welcome to new tab",
"welcome_body": "Firefox will use this space to show your most relevant bookmarks, articles, videos, and pages youve recently visited, so you can get back to them easily.",
"welcome_label": "Identifying your Highlights",
@ -1095,7 +1097,8 @@
"pocket_read_even_more": "View More Stories",
"pocket_feedback_header": "The best of the web, curated by over 25 million people.",
"pocket_feedback_body": "Pocket, a part of the Mozilla family, will help connect you to high-quality content that you may not have found otherwise.",
"pocket_send_feedback": "Send Feedback"
"pocket_send_feedback": "Send Feedback",
"empty_state_topstories": "Youve caught up. Check back later for more top stories from Pocket. Cant wait? Select a popular topic to find more great stories from around the web."
},
"en-ZA": {},
"eo": {

View File

@ -14,11 +14,34 @@ const {NewTabInit} = Cu.import("resource://activity-stream/lib/NewTabInit.jsm",
const {PlacesFeed} = Cu.import("resource://activity-stream/lib/PlacesFeed.jsm", {});
const {PrefsFeed} = Cu.import("resource://activity-stream/lib/PrefsFeed.jsm", {});
const {Store} = Cu.import("resource://activity-stream/lib/Store.jsm", {});
const {SnippetsFeed} = Cu.import("resource://activity-stream/lib/SnippetsFeed.jsm", {});
const {SystemTickFeed} = Cu.import("resource://activity-stream/lib/SystemTickFeed.jsm", {});
const {TelemetryFeed} = Cu.import("resource://activity-stream/lib/TelemetryFeed.jsm", {});
const {TopSitesFeed} = Cu.import("resource://activity-stream/lib/TopSitesFeed.jsm", {});
const {TopStoriesFeed} = Cu.import("resource://activity-stream/lib/TopStoriesFeed.jsm", {});
const REASON_ADDON_UNINSTALL = 6;
// Sections, keyed by section id
const SECTIONS = new Map([
["topstories", {
feed: TopStoriesFeed,
prefTitle: "Fetches content recommendations from a configurable content provider",
showByDefault: false
}]
]);
const SECTION_FEEDS_CONFIG = Array.from(SECTIONS.entries()).map(entry => {
const id = entry[0];
const {feed: Feed, prefTitle, showByDefault: value} = entry[1];
return {
name: `section.${id}`,
factory: () => new Feed(),
title: prefTitle || `${id} section feed`,
value
};
});
const PREFS_CONFIG = new Map([
["default.sites", {
title: "Comma-separated list of default top sites to fill in behind visited sites",
@ -45,11 +68,24 @@ const PREFS_CONFIG = new Map([
["telemetry.ping.endpoint", {
title: "Telemetry server endpoint",
value: "https://tiles.services.mozilla.com/v4/links/activity-stream"
}],
["feeds.section.topstories.options", {
title: "Configuration options for top stories feed",
value: `{
"stories_endpoint": "https://getpocket.com/v3/firefox/global-recs?consumer_key=$apiKey",
"topics_endpoint": "https://getpocket.com/v3/firefox/trending-topics?consumer_key=$apiKey",
"read_more_endpoint": "https://getpocket.com/explore/trending?src=ff_new_tab",
"learn_more_endpoint": "https://getpocket.com/firefox_learnmore?src=ff_newtab",
"survey_link": "https://www.surveymonkey.com/r/newtabffx",
"api_key_pref": "extensions.pocket.oAuthConsumerKey",
"provider_name": "Pocket",
"provider_icon": "pocket"
}`
}]
]);
const FEEDS_CONFIG = new Map();
for (const {name, factory, title, value} of [
for (const {name, factory, title, value} of SECTION_FEEDS_CONFIG.concat([
{
name: "localization",
factory: () => new LocalizationFeed(),
@ -74,6 +110,18 @@ for (const {name, factory, title, value} of [
title: "Preferences",
value: true
},
{
name: "snippets",
factory: () => new SnippetsFeed(),
title: "Gets snippets data",
value: false
},
{
name: "systemtick",
factory: () => new SystemTickFeed(),
title: "Produces system tick events to periodically check for data expiry",
value: true
},
{
name: "telemetry",
factory: () => new TelemetryFeed(),
@ -86,7 +134,7 @@ for (const {name, factory, title, value} of [
title: "Queries places and gets metadata for Top Sites section",
value: true
}
]) {
])) {
const pref = `feeds.${name}`;
FEEDS_CONFIG.set(pref, factory);
PREFS_CONFIG.set(pref, {title, value});
@ -135,4 +183,4 @@ this.ActivityStream = class ActivityStream {
};
this.PREFS_CONFIG = PREFS_CONFIG;
this.EXPORTED_SYMBOLS = ["ActivityStream"];
this.EXPORTED_SYMBOLS = ["ActivityStream", "SECTIONS"];

View File

@ -13,6 +13,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
"chrome://pocket/content/Pocket.jsm");
const LINK_BLOCKED_EVENT = "newtab-linkBlocked";
@ -205,6 +207,9 @@ class PlacesFeed {
case at.DELETE_HISTORY_URL:
NewTabUtils.activityStreamLinks.deleteHistoryEntry(action.data);
break;
case at.SAVE_TO_POCKET:
Pocket.savePage(action._target.browser, action.data.site.url, action.data.site.title);
break;
}
}
}

View File

@ -0,0 +1,58 @@
/* 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 {utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Console.jsm");
const {actionTypes: at, actionCreators: ac} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
// Url to fetch snippets, in the urlFormatter service format.
const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl";
// Should be bumped up if the snippets content format changes.
const STARTPAGE_VERSION = 4;
this.SnippetsFeed = class SnippetsFeed {
constructor() {
this._onUrlChange = this._onUrlChange.bind(this);
}
get snippetsURL() {
const updateURL = Services
.prefs.getStringPref(SNIPPETS_URL_PREF)
.replace("%STARTPAGE_VERSION%", STARTPAGE_VERSION);
return Services.urlFormatter.formatURL(updateURL);
}
init() {
const data = {
snippetsURL: this.snippetsURL,
version: STARTPAGE_VERSION
};
this.store.dispatch(ac.BroadcastToContent({type: at.SNIPPETS_DATA, data}));
Services.prefs.addObserver(SNIPPETS_URL_PREF, this._onUrlChange);
}
uninit() {
this.store.dispatch({type: at.SNIPPETS_RESET});
Services.prefs.removeObserver(SNIPPETS_URL_PREF, this._onUrlChange);
}
_onUrlChange() {
this.store.dispatch(ac.BroadcastToContent({
type: at.SNIPPETS_DATA,
data: {snippetsURL: this.snippetsURL}
}));
}
onAction(action) {
switch (action.type) {
case at.INIT:
this.init();
break;
case at.FEED_INIT:
if (action.data === "feeds.snippets") { this.init(); }
break;
}
}
};
this.EXPORTED_SYMBOLS = ["SnippetsFeed"];

View File

@ -9,6 +9,7 @@ const {ActivityStreamMessageChannel} = Cu.import("resource://activity-stream/lib
const {Prefs} = Cu.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
const {reducers} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
const {redux} = Cu.import("resource://activity-stream/vendor/Redux.jsm", {});
const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
/**
* Store - This has a similar structure to a redux store, but includes some extra
@ -91,6 +92,7 @@ this.Store = class Store {
if (this._feedFactories.has(name)) {
if (value) {
this.initFeed(name);
this.dispatch({type: at.FEED_INIT, data: name});
} else {
this.uninitFeed(name);
}

View File

@ -0,0 +1,35 @@
/* 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 {utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "setInterval", "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearInterval", "resource://gre/modules/Timer.jsm");
// Frequency at which SYSTEM_TICK events are fired
const SYSTEM_TICK_INTERVAL = 5 * 60 * 1000;
this.SystemTickFeed = class SystemTickFeed {
init() {
this.intervalId = setInterval(() => this.store.dispatch({type: at.SYSTEM_TICK}), SYSTEM_TICK_INTERVAL);
}
onAction(action) {
switch (action.type) {
case at.INIT:
this.init();
break;
case at.UNINIT:
clearInterval(this.intervalId);
break;
}
}
};
this.SYSTEM_TICK_INTERVAL = SYSTEM_TICK_INTERVAL;
this.EXPORTED_SYMBOLS = ["SystemTickFeed", "SYSTEM_TICK_INTERVAL"];

View File

@ -0,0 +1,187 @@
/* 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 {utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NewTabUtils.jsm");
Cu.importGlobalProperties(["fetch"]);
const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
const {Prefs} = Cu.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
const STORIES_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
const TOPICS_UPDATE_TIME = 3 * 60 * 60 * 1000; // 3 hours
const SECTION_ID = "TopStories";
this.TopStoriesFeed = class TopStoriesFeed {
constructor() {
this.storiesLastUpdated = 0;
this.topicsLastUpdated = 0;
}
init() {
try {
const prefs = new Prefs();
const options = JSON.parse(prefs.get("feeds.section.topstories.options"));
const apiKey = this._getApiKeyFromPref(options.api_key_pref);
this.stories_endpoint = this._produceUrlWithApiKey(options.stories_endpoint, apiKey);
this.topics_endpoint = this._produceUrlWithApiKey(options.topics_endpoint, apiKey);
this.read_more_endpoint = options.read_more_endpoint;
// TODO https://github.com/mozilla/activity-stream/issues/2902
const sectionOptions = {
id: SECTION_ID,
icon: options.provider_icon,
title: {id: "header_recommended_by", values: {provider: options.provider_name}},
rows: [],
maxCards: 3,
contextMenuOptions: ["SaveToPocket", "Separator", "CheckBookmark", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
infoOption: {
header: {id: "pocket_feedback_header"},
body: {id: "pocket_feedback_body"},
link: {
href: options.survey_link,
id: "pocket_send_feedback"
}
},
emptyState: {
message: {id: "empty_state_topstories"},
icon: "check"
}
};
this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_REGISTER, data: sectionOptions}));
this.fetchStories();
this.fetchTopics();
} catch (e) {
Cu.reportError(`Problem initializing top stories feed: ${e.message}`);
}
}
uninit() {
this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_DEREGISTER, data: SECTION_ID}));
}
async fetchStories() {
if (this.stories_endpoint) {
const stories = await fetch(this.stories_endpoint)
.then(response => {
if (response.ok) {
return response.text();
}
throw new Error(`Stories endpoint returned unexpected status: ${response.status}`);
})
.then(body => {
let items = JSON.parse(body).list;
items = items
.filter(s => !NewTabUtils.blockedLinks.isBlocked(s.dedupe_url))
.map(s => ({
"guid": s.id,
"type": "trending",
"title": s.title,
"description": s.excerpt,
"image": this._normalizeUrl(s.image_src),
"url": s.dedupe_url,
"lastVisitDate": s.published_timestamp
}));
return items;
})
.catch(error => Cu.reportError(`Failed to fetch content: ${error.message}`));
if (stories) {
this.dispatchUpdateEvent(this.storiesLastUpdated,
{"type": at.SECTION_ROWS_UPDATE, "data": {"id": SECTION_ID, "rows": stories}});
this.storiesLastUpdated = Date.now();
}
}
}
async fetchTopics() {
if (this.topics_endpoint) {
const topics = await fetch(this.topics_endpoint)
.then(response => {
if (response.ok) {
return response.text();
}
throw new Error(`Topics endpoint returned unexpected status: ${response.status}`);
})
.then(body => JSON.parse(body).topics)
.catch(error => Cu.reportError(`Failed to fetch topics: ${error.message}`));
if (topics) {
this.dispatchUpdateEvent(this.topicsLastUpdated,
{"type": at.SECTION_ROWS_UPDATE, "data": {"id": SECTION_ID, "topics": topics, "read_more_endpoint": this.read_more_endpoint}});
this.topicsLastUpdated = Date.now();
}
}
}
dispatchUpdateEvent(lastUpdated, evt) {
if (lastUpdated === 0) {
this.store.dispatch(ac.BroadcastToContent(evt));
} else {
this.store.dispatch(evt);
}
}
_getApiKeyFromPref(apiKeyPref) {
if (!apiKeyPref) {
return apiKeyPref;
}
return new Prefs().get(apiKeyPref) || Services.prefs.getCharPref(apiKeyPref);
}
_produceUrlWithApiKey(url, apiKey) {
if (!url) {
return url;
}
if (url.includes("$apiKey") && !apiKey) {
throw new Error(`An API key was specified but none configured: ${url}`);
}
return url.replace("$apiKey", apiKey);
}
// Need to remove parenthesis from image URLs as React will otherwise
// fail to render them properly as part of the card template.
_normalizeUrl(url) {
if (url) {
return url.replace(/\(/g, "%28").replace(/\)/g, "%29");
}
return url;
}
onAction(action) {
switch (action.type) {
case at.INIT:
this.init();
break;
case at.SYSTEM_TICK:
if (Date.now() - this.storiesLastUpdated >= STORIES_UPDATE_TIME) {
this.fetchStories();
}
if (Date.now() - this.topicsLastUpdated >= TOPICS_UPDATE_TIME) {
this.fetchTopics();
}
break;
case at.UNINIT:
this.uninit();
break;
case at.FEED_INIT:
if (action.data === "feeds.section.topstories") {
this.init();
}
break;
}
}
};
this.STORIES_UPDATE_TIME = STORIES_UPDATE_TIME;
this.TOPICS_UPDATE_TIME = TOPICS_UPDATE_TIME;
this.SECTION_ID = SECTION_ID;
this.EXPORTED_SYMBOLS = ["TopStoriesFeed", "STORIES_UPDATE_TIME", "TOPICS_UPDATE_TIME", "SECTION_ID"];

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if=!nightly_build
support-files =
blue_page.html

View File

@ -1,3 +0,0 @@
{
"activity_stream": true
}

View File

@ -1,5 +1,6 @@
const {reducers, INITIAL_STATE, insertPinned} = require("common/Reducers.jsm");
const {TopSites, App, Prefs, Dialog} = reducers;
const {TopSites, App, Snippets, Prefs, Dialog, Sections} = reducers;
const {actionTypes: at} = require("common/Actions.jsm");
describe("Reducers", () => {
@ -77,6 +78,10 @@ describe("Reducers", () => {
// old row is unchanged
assert.equal(nextState.rows[0], oldState.rows[0]);
});
it("should not update state for empty action.data on PLACES_BOOKMARK_ADDED", () => {
const nextState = TopSites(undefined, {type: at.PLACES_BOOKMARK_ADDED});
assert.equal(nextState, INITIAL_STATE.TopSites);
});
it("should remove a bookmark on PLACES_BOOKMARK_REMOVED", () => {
const oldState = {
rows: [{url: "foo.com"}, {
@ -98,6 +103,10 @@ describe("Reducers", () => {
// old row is unchanged
assert.deepEqual(nextState.rows[0], oldState.rows[0]);
});
it("should not update state for empty action.data on PLACES_BOOKMARK_REMOVED", () => {
const nextState = TopSites(undefined, {type: at.PLACES_BOOKMARK_REMOVED});
assert.equal(nextState, INITIAL_STATE.TopSites);
});
it("should remove a link on PLACES_LINK_BLOCKED and PLACES_LINK_DELETED", () => {
const events = [at.PLACES_LINK_BLOCKED, at.PLACES_LINK_DELETED];
events.forEach(event => {
@ -179,6 +188,70 @@ describe("Reducers", () => {
assert.deepEqual(INITIAL_STATE.Dialog, nextState);
});
});
describe("Sections", () => {
let oldState;
beforeEach(() => {
oldState = new Array(5).fill(null).map((v, i) => ({
id: `foo_bar_${i}`,
title: `Foo Bar ${i}`,
initialized: false,
rows: [{url: "www.foo.bar"}, {url: "www.other.url"}]
}));
});
it("should return INITIAL_STATE by default", () => {
assert.equal(INITIAL_STATE.Sections, Sections(undefined, {type: "non_existent"}));
});
it("should remove the correct section on SECTION_DEREGISTER", () => {
const newState = Sections(oldState, {type: at.SECTION_DEREGISTER, data: "foo_bar_2"});
assert.lengthOf(newState, 4);
const expectedNewState = oldState.splice(2, 1) && oldState;
assert.deepEqual(newState, expectedNewState);
});
it("should add a section on SECTION_REGISTER if it doesn't already exist", () => {
const action = {type: at.SECTION_REGISTER, data: {id: "foo_bar_5", title: "Foo Bar 5"}};
const newState = Sections(oldState, action);
assert.lengthOf(newState, 6);
const insertedSection = newState.find(section => section.id === "foo_bar_5");
assert.propertyVal(insertedSection, "title", action.data.title);
});
it("should set newSection.rows === [] if no rows are provided on SECTION_REGISTER", () => {
const action = {type: at.SECTION_REGISTER, data: {id: "foo_bar_5", title: "Foo Bar 5"}};
const newState = Sections(oldState, action);
const insertedSection = newState.find(section => section.id === "foo_bar_5");
assert.deepEqual(insertedSection.rows, []);
});
it("should update a section on SECTION_REGISTER if it already exists", () => {
const NEW_TITLE = "New Title";
const action = {type: at.SECTION_REGISTER, data: {id: "foo_bar_2", title: NEW_TITLE}};
const newState = Sections(oldState, action);
assert.lengthOf(newState, 5);
const updatedSection = newState.find(section => section.id === "foo_bar_2");
assert.ok(updatedSection && updatedSection.title === NEW_TITLE);
});
it("should have no effect on SECTION_ROWS_UPDATE if the id doesn't exist", () => {
const action = {type: at.SECTION_ROWS_UPDATE, data: {id: "fake_id", data: "fake_data"}};
const newState = Sections(oldState, action);
assert.deepEqual(oldState, newState);
});
it("should update the section rows with the correct data on SECTION_ROWS_UPDATE", () => {
const FAKE_DATA = ["some", "fake", "data"];
const action = {type: at.SECTION_ROWS_UPDATE, data: {id: "foo_bar_2", rows: FAKE_DATA}};
const newState = Sections(oldState, action);
const updatedSection = newState.find(section => section.id === "foo_bar_2");
assert.equal(updatedSection.rows, FAKE_DATA);
});
it("should remove blocked and deleted urls from all rows in all sections", () => {
const blockAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "www.foo.bar"}};
const deleteAction = {type: at.PLACES_LINK_DELETED, data: {url: "www.foo.bar"}};
const newBlockState = Sections(oldState, blockAction);
const newDeleteState = Sections(oldState, deleteAction);
newBlockState.concat(newDeleteState).forEach(section => {
assert.deepEqual(section.rows, [{url: "www.other.url"}]);
});
});
});
describe("#insertPinned", () => {
let links;
@ -244,4 +317,23 @@ describe("Reducers", () => {
assert.equal(links.length, result.length);
});
});
describe("Snippets", () => {
it("should return INITIAL_STATE by default", () => {
assert.equal(Snippets(undefined, {type: "some_action"}), INITIAL_STATE.Snippets);
});
it("should set initialized to true on a SNIPPETS_DATA action", () => {
const state = Snippets(undefined, {type: at.SNIPPETS_DATA, data: {}});
assert.isTrue(state.initialized);
});
it("should set the snippet data on a SNIPPETS_DATA action", () => {
const data = {snippetsURL: "foo.com", version: 4};
const state = Snippets(undefined, {type: at.SNIPPETS_DATA, data});
assert.propertyVal(state, "snippetsURL", data.snippetsURL);
assert.propertyVal(state, "version", data.version);
});
it("should reset to the initial state on a SNIPPETS_RESET action", () => {
const state = Snippets({initalized: true, foo: "bar"}, {type: at.SNIPPETS_RESET});
assert.equal(state, INITIAL_STATE.Snippets);
});
});
});

View File

@ -6,17 +6,21 @@ describe("ActivityStream", () => {
let sandbox;
let as;
let ActivityStream;
let SECTIONS;
function Fake() {}
beforeEach(() => {
sandbox = sinon.sandbox.create();
({ActivityStream} = injector({
({ActivityStream, SECTIONS} = injector({
"lib/LocalizationFeed.jsm": {LocalizationFeed: Fake},
"lib/NewTabInit.jsm": {NewTabInit: Fake},
"lib/PlacesFeed.jsm": {PlacesFeed: Fake},
"lib/TelemetryFeed.jsm": {TelemetryFeed: Fake},
"lib/TopSitesFeed.jsm": {TopSitesFeed: Fake},
"lib/PrefsFeed.jsm": {PrefsFeed: Fake}
"lib/PrefsFeed.jsm": {PrefsFeed: Fake},
"lib/SnippetsFeed.jsm": {SnippetsFeed: Fake},
"lib/TopStoriesFeed.jsm": {TopStoriesFeed: Fake},
"lib/SystemTickFeed.jsm": {SystemTickFeed: Fake}
}));
as = new ActivityStream();
sandbox.stub(as.store, "init");
@ -106,5 +110,21 @@ describe("ActivityStream", () => {
const feed = as.feeds.get("feeds.prefs")();
assert.instanceOf(feed, Fake);
});
it("should create a section feed for each section in SECTIONS", () => {
// If new sections are added, their feeds will have to be added to the
// list of injected feeds above for this test to pass
SECTIONS.forEach((value, key) => {
const feed = as.feeds.get(`feeds.section.${key}`)();
assert.instanceOf(feed, Fake);
});
});
it("should create a Snippets feed", () => {
const feed = as.feeds.get("feeds.snippets")();
assert.instanceOf(feed, Fake);
});
it("should create a SystemTick feed", () => {
const feed = as.feeds.get("feeds.systemtick")();
assert.instanceOf(feed, Fake);
});
});
});

View File

@ -28,6 +28,7 @@ describe("PlacesFeed", () => {
history: {addObserver: sandbox.spy(), removeObserver: sandbox.spy()},
bookmarks: {TYPE_BOOKMARK, addObserver: sandbox.spy(), removeObserver: sandbox.spy()}
});
globals.set("Pocket", {savePage: sandbox.spy()});
global.Components.classes["@mozilla.org/browser/nav-history-service;1"] = {
getService() {
return global.PlacesUtils.history;
@ -98,6 +99,10 @@ describe("PlacesFeed", () => {
feed.onAction({type: at.DELETE_HISTORY_URL, data: "guava.com"});
assert.calledWith(global.NewTabUtils.activityStreamLinks.deleteHistoryEntry, "guava.com");
});
it("should save to Pocket on SAVE_TO_POCKET", () => {
feed.onAction({type: at.SAVE_TO_POCKET, data: {site: {url: "raspberry.com", title: "raspberry"}}, _target: {browser: {}}});
assert.calledWith(global.Pocket.savePage, {}, "raspberry.com", "raspberry");
});
});
describe("#observe", () => {

View File

@ -0,0 +1,60 @@
const {SnippetsFeed} = require("lib/SnippetsFeed.jsm");
const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
describe("SnippetsFeed", () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
it("should dispatch the right version and snippetsURL on INIT", () => {
const url = "foo.com/%STARTPAGE_VERSION%";
sandbox.stub(global.Services.prefs, "getStringPref").returns(url);
const feed = new SnippetsFeed();
feed.store = {dispatch: sandbox.stub()};
feed.onAction({type: at.INIT});
assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({
type: at.SNIPPETS_DATA,
data: {
snippetsURL: "foo.com/4",
version: 4
}
}));
});
it("should call .init when a FEED_INIT happens for feeds.snippets", () => {
const feed = new SnippetsFeed();
sandbox.stub(feed, "init");
feed.store = {dispatch: sandbox.stub()};
feed.onAction({type: at.FEED_INIT, data: "feeds.snippets"});
assert.calledOnce(feed.init);
});
it("should dispatch a SNIPPETS_RESET on uninit", () => {
const feed = new SnippetsFeed();
feed.store = {dispatch: sandbox.stub()};
feed.uninit();
assert.calledWith(feed.store.dispatch, {type: at.SNIPPETS_RESET});
});
describe("_onUrlChange", () => {
it("should dispatch a new snippetsURL", () => {
const url = "boo.com/%STARTPAGE_VERSION%";
sandbox.stub(global.Services.prefs, "getStringPref").returns(url);
const feed = new SnippetsFeed();
feed.store = {dispatch: sandbox.stub()};
feed._onUrlChange();
assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({
type: at.SNIPPETS_DATA,
data: {snippetsURL: "boo.com/4"}
}));
});
});
});

View File

@ -0,0 +1,41 @@
"use strict";
const injector = require("inject!lib/SystemTickFeed.jsm");
const {actionTypes: at} = require("common/Actions.jsm");
describe("System Tick Feed", () => {
let SystemTickFeed;
let SYSTEM_TICK_INTERVAL;
let instance;
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
({SystemTickFeed, SYSTEM_TICK_INTERVAL} = injector({}));
instance = new SystemTickFeed();
instance.store = {getState() { return {}; }, dispatch() {}};
});
afterEach(() => {
clock.restore();
});
it("should create a SystemTickFeed", () => {
assert.instanceOf(instance, SystemTickFeed);
});
it("should fire SYSTEM_TICK events at configured interval", () => {
let expectation = sinon.mock(instance.store).expects("dispatch")
.twice()
.withExactArgs({type: at.SYSTEM_TICK});
instance.onAction({type: at.INIT});
clock.tick(SYSTEM_TICK_INTERVAL * 2);
expectation.verify();
});
it("should not fire SYSTEM_TICK events after UNINIT", () => {
let expectation = sinon.mock(instance.store).expects("dispatch")
.never();
instance.onAction({type: at.UNINIT});
clock.tick(SYSTEM_TICK_INTERVAL * 2);
expectation.verify();
});
});

View File

@ -0,0 +1,257 @@
"use strict";
const injector = require("inject!lib/TopStoriesFeed.jsm");
const {FakePrefs} = require("test/unit/utils");
const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm");
const {GlobalOverrider} = require("test/unit/utils");
describe("Top Stories Feed", () => {
let TopStoriesFeed;
let STORIES_UPDATE_TIME;
let TOPICS_UPDATE_TIME;
let SECTION_ID;
let instance;
let clock;
let globals;
beforeEach(() => {
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = `{
"stories_endpoint": "https://somedomain.org/stories?key=$apiKey",
"topics_endpoint": "https://somedomain.org/topics?key=$apiKey",
"survey_link": "https://www.surveymonkey.com/r/newtabffx",
"api_key_pref": "apiKeyPref",
"provider_name": "test-provider",
"provider_icon": "provider-icon"
}`;
FakePrefs.prototype.prefs.apiKeyPref = "test-api-key";
globals = new GlobalOverrider();
clock = sinon.useFakeTimers();
({TopStoriesFeed, STORIES_UPDATE_TIME, TOPICS_UPDATE_TIME, SECTION_ID} = injector({"lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs}}));
instance = new TopStoriesFeed();
instance.store = {getState() { return {}; }, dispatch: sinon.spy()};
});
afterEach(() => {
globals.restore();
clock.restore();
});
describe("#init", () => {
it("should create a TopStoriesFeed", () => {
assert.instanceOf(instance, TopStoriesFeed);
});
it("should initialize endpoints based on prefs", () => {
instance.onAction({type: at.INIT});
assert.equal("https://somedomain.org/stories?key=test-api-key", instance.stories_endpoint);
assert.equal("https://somedomain.org/topics?key=test-api-key", instance.topics_endpoint);
});
it("should register section", () => {
const expectedSectionOptions = {
id: SECTION_ID,
icon: "provider-icon",
title: {id: "header_recommended_by", values: {provider: "test-provider"}},
rows: [],
maxCards: 3,
contextMenuOptions: ["SaveToPocket", "Separator", "CheckBookmark", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
infoOption: {
header: {id: "pocket_feedback_header"},
body: {id: "pocket_feedback_body"},
link: {
href: "https://www.surveymonkey.com/r/newtabffx",
id: "pocket_send_feedback"
}
},
emptyState: {
message: {id: "empty_state_topstories"},
icon: "check"
}
};
instance.onAction({type: at.INIT});
assert.calledOnce(instance.store.dispatch);
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_REGISTER);
assert.calledWith(instance.store.dispatch, ac.BroadcastToContent({
type: at.SECTION_REGISTER,
data: expectedSectionOptions
}));
});
it("should fetch stories on init", () => {
instance.fetchStories = sinon.spy();
instance.fetchTopics = sinon.spy();
instance.onAction({type: at.INIT});
assert.calledOnce(instance.fetchStories);
});
it("should fetch topics on init", () => {
instance.fetchStories = sinon.spy();
instance.fetchTopics = sinon.spy();
instance.onAction({type: at.INIT});
assert.calledOnce(instance.fetchTopics);
});
it("should not fetch if endpoint not configured", () => {
let fetchStub = globals.sandbox.stub();
globals.set("fetch", fetchStub);
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = "{}";
instance.init();
assert.notCalled(fetchStub);
});
it("should report error for invalid configuration", () => {
globals.sandbox.spy(global.Components.utils, "reportError");
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = "invalid";
instance.init();
assert.called(Components.utils.reportError);
});
it("should report error for missing api key", () => {
let fakeServices = {prefs: {getCharPref: sinon.spy()}};
globals.set("Services", fakeServices);
globals.sandbox.spy(global.Components.utils, "reportError");
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = `{
"stories_endpoint": "https://somedomain.org/stories?key=$apiKey",
"topics_endpoint": "https://somedomain.org/topics?key=$apiKey"
}`;
instance.init();
assert.called(Components.utils.reportError);
});
it("should deregister section", () => {
instance.onAction({type: at.UNINIT});
assert.calledOnce(instance.store.dispatch);
assert.calledWith(instance.store.dispatch, ac.BroadcastToContent({
type: at.SECTION_DEREGISTER,
data: SECTION_ID
}));
});
it("should initialize on FEED_INIT", () => {
instance.init = sinon.spy();
instance.onAction({type: at.FEED_INIT, data: "feeds.section.topstories"});
assert.calledOnce(instance.init);
});
});
describe("#fetch", () => {
it("should fetch stories and send event", async () => {
let fetchStub = globals.sandbox.stub();
globals.set("fetch", fetchStub);
globals.set("NewTabUtils", {blockedLinks: {isBlocked: globals.sandbox.spy()}});
const response = `{"list": [{"id" : "1",
"title": "title",
"excerpt": "description",
"image_src": "image-url",
"dedupe_url": "rec-url",
"published_timestamp" : "123"
}]}`;
const stories = [{
"guid": "1",
"type": "trending",
"title": "title",
"description": "description",
"image": "image-url",
"url": "rec-url",
"lastVisitDate": "123"
}];
instance.stories_endpoint = "stories-endpoint";
fetchStub.resolves({ok: true, status: 200, text: () => response});
await instance.fetchStories();
assert.calledOnce(fetchStub);
assert.calledWithExactly(fetchStub, instance.stories_endpoint);
assert.calledOnce(instance.store.dispatch);
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE);
assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.id, SECTION_ID);
assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.rows, stories);
});
it("should dispatch events", () => {
instance.dispatchUpdateEvent(123, {});
assert.calledOnce(instance.store.dispatch);
});
it("should report error for unexpected stories response", async () => {
let fetchStub = globals.sandbox.stub();
globals.set("fetch", fetchStub);
globals.sandbox.spy(global.Components.utils, "reportError");
instance.stories_endpoint = "stories-endpoint";
fetchStub.resolves({ok: false, status: 400});
await instance.fetchStories();
assert.calledOnce(fetchStub);
assert.calledWithExactly(fetchStub, instance.stories_endpoint);
assert.notCalled(instance.store.dispatch);
assert.called(Components.utils.reportError);
});
it("should exclude blocked (dismissed) URLs", async () => {
let fetchStub = globals.sandbox.stub();
globals.set("fetch", fetchStub);
globals.set("NewTabUtils", {blockedLinks: {isBlocked: url => url === "blocked"}});
const response = `{"list": [{"dedupe_url" : "blocked"}, {"dedupe_url" : "not_blocked"}]}`;
instance.stories_endpoint = "stories-endpoint";
fetchStub.resolves({ok: true, status: 200, text: () => response});
await instance.fetchStories();
assert.calledOnce(instance.store.dispatch);
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE);
assert.equal(instance.store.dispatch.firstCall.args[0].data.rows.length, 1);
assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[0].url, "not_blocked");
});
it("should fetch topics and send event", async () => {
let fetchStub = globals.sandbox.stub();
globals.set("fetch", fetchStub);
const response = `{"topics": [{"name" : "topic1", "url" : "url-topic1"}, {"name" : "topic2", "url" : "url-topic2"}]}`;
const topics = [{
"name": "topic1",
"url": "url-topic1"
}, {
"name": "topic2",
"url": "url-topic2"
}];
instance.topics_endpoint = "topics-endpoint";
fetchStub.resolves({ok: true, status: 200, text: () => response});
await instance.fetchTopics();
assert.calledOnce(fetchStub);
assert.calledWithExactly(fetchStub, instance.topics_endpoint);
assert.calledOnce(instance.store.dispatch);
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE);
assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.id, SECTION_ID);
assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.topics, topics);
});
it("should report error for unexpected topics response", async () => {
let fetchStub = globals.sandbox.stub();
globals.set("fetch", fetchStub);
globals.sandbox.spy(global.Components.utils, "reportError");
instance.topics_endpoint = "topics-endpoint";
fetchStub.resolves({ok: false, status: 400});
await instance.fetchTopics();
assert.calledOnce(fetchStub);
assert.calledWithExactly(fetchStub, instance.topics_endpoint);
assert.notCalled(instance.store.dispatch);
assert.called(Components.utils.reportError);
});
});
describe("#update", () => {
it("should fetch stories after update interval", () => {
instance.fetchStories = sinon.spy();
instance.fetchTopics = sinon.spy();
instance.onAction({type: at.SYSTEM_TICK});
assert.notCalled(instance.fetchStories);
clock.tick(STORIES_UPDATE_TIME);
instance.onAction({type: at.SYSTEM_TICK});
assert.calledOnce(instance.fetchStories);
});
it("should fetch topics after update interval", () => {
instance.fetchStories = sinon.spy();
instance.fetchTopics = sinon.spy();
instance.onAction({type: at.SYSTEM_TICK});
assert.notCalled(instance.fetchTopics);
clock.tick(TOPICS_UPDATE_TIME);
instance.onAction({type: at.SYSTEM_TICK});
assert.calledOnce(instance.fetchTopics);
});
});
});

View File

@ -24,6 +24,16 @@ describe("initStore", () => {
callback(message);
assert.calledWith(store.dispatch, message.data);
});
it("should log errors from failed messages", () => {
const callback = global.addMessageListener.firstCall.args[1];
globals.sandbox.stub(global.console, "error");
globals.sandbox.stub(store, "dispatch").throws(Error("failed"));
const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: "FOO"}};
callback(message);
assert.calledOnce(global.console.error);
});
it("should replace the state if a MERGE_STORE_ACTION is dispatched", () => {
store.dispatch({type: initStore.MERGE_STORE_ACTION, data: {number: 42}});
assert.deepEqual(store.getState(), {number: 42});

View File

@ -29,6 +29,7 @@ overrider.set({
Preferences: FakePrefs,
Services: {
locale: {getRequestedLocale() {}},
urlFormatter: {formatURL: str => str},
mm: {
addMessageListener: (msg, cb) => cb(),
removeMessageListener() {}
@ -39,6 +40,8 @@ overrider.set({
removeObserver() {}
},
prefs: {
addObserver() {},
removeObserver() {},
getStringPref() {},
getDefaultBranch() {
return {

View File

@ -536,6 +536,7 @@
padding: 10px 20px;
font-size: 14px;
color: #fff;
min-width: 130px;
}
@media all and (max-width: 960px) {

View File

@ -12,6 +12,8 @@ MOZ_PKG_REMOVALS = $(srcdir)/removed-files.in
MOZ_PKG_MANIFEST = $(srcdir)/package-manifest.in
MOZ_PKG_DUPEFLAGS = -f $(srcdir)/allowed-dupes.mn
DEFINES += -DPKG_LOCALE_MANIFEST=$(topobjdir)/toolkit/locales/locale-manifest.in
# Some files have been already bundled with xulrunner
ifndef MOZ_MULET
MOZ_PKG_FATAL_WARNINGS = 1

View File

@ -47,8 +47,6 @@
[@AB_CD@]
@RESPATH@/browser/chrome/@AB_CD@@JAREXT@
@RESPATH@/browser/chrome/@AB_CD@.manifest
@RESPATH@/chrome/@AB_CD@@JAREXT@
@RESPATH@/chrome/@AB_CD@.manifest
@RESPATH@/dictionaries/*
#if defined(XP_WIN) || defined(XP_LINUX)
@RESPATH@/fonts/*
@ -837,3 +835,7 @@ bin/libfreebl_32int64_3.so
@RESPATH@/fix_linux_stack.py
#endif
#endif
#ifdef PKG_LOCALE_MANIFEST
#include @PKG_LOCALE_MANIFEST@
#endif

View File

@ -356,6 +356,11 @@ photonpanelmultiview panelview {
max-width: 30em;
}
/* Add 2 * 12px extra width for touch mode button padding. */
#appMenu-popup[touchmode] panelview {
min-width: calc(@menuPanelWidth@ + 24px);
}
photonpanelmultiview .panel-subview-body {
margin: 4px 0;
}
@ -1298,6 +1303,10 @@ photonpanelmultiview .panel-banner-item > .toolbarbutton-multiline-text {
padding-inline-start: 8px; /* See '.subviewbutton-iconic > .toolbarbutton-text' rule above. */
}
photonpanelmultiview .subviewbutton-iconic > .toolbarbutton-icon {
width: 16px;
}
photonpanelmultiview .subviewbutton {
-moz-context-properties: fill;
fill: currentColor;

View File

@ -349,6 +349,21 @@ toolbar:not([brighttext]) #bookmarks-menu-button@attributeSelectorForToolbar@[st
}
}
/* The animation is supposed to show the blue fill color for 520ms, then the
fade to the toolbarbutton-fill color for the remaining 210ms. Thus with an
animation-duration of 730ms, 71% is the point where we start the fade out. */
@keyframes overflow-fade {
from {
fill: #30A3FF;
}
71% {
fill: #30A3FF;
}
to {
fill: inherit;
}
}
#nav-bar-overflow-button > .toolbarbutton-animatable-box {
position: fixed;
overflow: hidden;
@ -381,6 +396,12 @@ toolbar:not([brighttext]) #bookmarks-menu-button@attributeSelectorForToolbar@[st
#nav-bar-overflow-button[animate]:-moz-locale-dir(rtl) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
animation-name: overflow-animation-rtl;
}
#nav-bar-overflow-button[animate][fade] > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
animation-name: overflow-fade;
animation-timing-function: ease-out;
animation-duration: 730ms;
}
%endif
#email-link-button@attributeSelectorForToolbar@ {

View File

@ -1961,13 +1961,20 @@ notification.pluginVulnerable > .notification-inner > .messageCloseButton {
%include ../shared/contextmenu.inc.css
/* Make context menu items larger when opened through touch. */
/* Make menu items larger when opened through touch. */
panel[touchmode] .PanelUI-subView .subviewbutton,
menupopup[touchmode] menu,
menupopup[touchmode] menuitem {
padding-top: 12px;
padding-bottom: 12px;
}
panel[touchmode] .PanelUI-subView #appMenu-edit-controls > .subviewbutton,
panel[touchmode] .PanelUI-subView #appMenu-zoom-controls > .subviewbutton-iconic {
padding-inline-start: 12px;
padding-inline-end: 12px;
}
#contentAreaContextMenu[touchmode] > #context-navigation > menuitem {
padding-top: 7px;
padding-bottom: 7px;

View File

@ -4,6 +4,4 @@ else
. $topsrcdir/build/macosx/local-mozconfig.common
fi
# Enable stylo in automation builds.
# Can be removed after bug 1375774 is resolved.
ac_add_options --enable-stylo=build
. $topsrcdir/build/mozconfig.stylo

View File

@ -97,7 +97,7 @@ def check_prog(var, progs, what=None, input=None, allow_missing=False,
paths=None, when=None):
if input is not None:
# Wrap input with type checking and normalization.
@depends(input)
@depends(input, when=when)
def input(value):
if not value:
return
@ -108,7 +108,7 @@ def check_prog(var, progs, what=None, input=None, allow_missing=False,
configure_error('input must resolve to a tuple or a list with a '
'single element, or a string')
else:
option(env=var, nargs=1,
option(env=var, nargs=1, when=when,
help='Path to %s' % (what or 'the %s program' % var.lower()))
input = var
what = what or var.lower()
@ -135,7 +135,7 @@ def check_prog(var, progs, what=None, input=None, allow_missing=False,
if not allow_missing or value:
raise FatalCheckError('Cannot find %s' % what)
@depends_if(check, progs)
@depends_if(check, progs, when=when)
def normalized_for_config(value, progs):
return ':' if value is None else value

View File

@ -309,6 +309,88 @@ def shell(value, mozillabuild):
return find_program(shell)
# Source checkout and version control integration.
# ================================================
@depends(check_build_environment, 'MOZ_AUTOMATION', '--help')
@checking('for vcs source checkout')
@imports('os')
def vcs_checkout_type(build_env, automation, _):
if os.path.exists(os.path.join(build_env.topsrcdir, '.hg')):
return 'hg'
elif os.path.exists(os.path.join(build_env.topsrcdir, '.git')):
return 'git'
elif automation:
raise FatalCheckError('unable to resolve VCS type; must run '
'from a source checkout when MOZ_AUTOMATION '
'is set')
# Resolve VCS binary for detected repository type.
# TODO remove hg.exe once bug 1382940 addresses ambiguous executables case.
hg = check_prog('HG', ('hg.exe', 'hg',), allow_missing=True,
when=depends(vcs_checkout_type)(lambda x: x == 'hg'))
git = check_prog('GIT', ('git',), allow_missing=True,
when=depends(vcs_checkout_type)(lambda x: x == 'git'))
@depends_if(hg)
@checking('for Mercurial version')
@imports('os')
@imports('re')
def hg_version(hg):
# HGPLAIN in Mercurial 1.5+ forces stable output, regardless of set
# locale or encoding.
env = dict(os.environ)
env['HGPLAIN'] = '1'
out = check_cmd_output(hg, '--version', env=env)
match = re.search(r'Mercurial Distributed SCM \(version ([^\)]+)', out)
if not match:
raise FatalCheckError('unable to determine Mercurial version: %s' % out)
# The version string may be "unknown" for Mercurial run out of its own
# source checkout or for bad builds. But LooseVersion handles it.
return Version(match.group(1))
@depends_if(git)
@checking('for Git version')
@imports('re')
def git_version(git):
out = check_cmd_output(git, '--version').rstrip()
match = re.search('git version (.*)$', out)
if not match:
raise FatalCheckError('unable to determine Git version: %s' % out)
return Version(match.group(1))
# Only set VCS_CHECKOUT_TYPE if we resolved the VCS binary.
# Require resolved VCS info when running in automation so automation's
# environment is more well-defined.
@depends(vcs_checkout_type, hg_version, git_version, 'MOZ_AUTOMATION')
def exposed_vcs_checkout_type(vcs_checkout_type, hg, git, automation):
if vcs_checkout_type == 'hg':
if hg:
return 'hg'
if automation:
raise FatalCheckError('could not resolve Mercurial binary info')
elif vcs_checkout_type == 'git':
if git:
return 'git'
if automation:
raise FatalCheckError('could not resolve Git binary info')
elif vcs_checkout_type:
raise FatalCheckError('unhandled VCS type: %s' % vcs_checkout_type)
set_config('VCS_CHECKOUT_TYPE', exposed_vcs_checkout_type)
# Host and target systems
# ==============================================================
option('--host', nargs=1, help='Define the system type performing the build')

View File

@ -1105,18 +1105,19 @@ def enable_gold(enable_gold_option, c_compiler, developer_options, build_env):
# Used to check the kind of linker
version_check = ['-Wl,--version']
cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags
if enable_gold_option or developer_options:
def resolve_gold():
# Try to force the usage of gold
targetDir = os.path.join(build_env.topobjdir, 'build', 'unix', 'gold')
gold_detection_arg = '-print-prog-name=ld.gold'
gold = check_cmd_output(c_compiler.compiler, gold_detection_arg).strip()
if not gold:
die('Could not find gold')
return
goldFullPath = find_program(gold)
if goldFullPath is None:
die('Could not find gold')
return
if os.path.exists(targetDir):
shutil.rmtree(targetDir)
@ -1135,6 +1136,16 @@ def enable_gold(enable_gold_option, c_compiler, developer_options, build_env):
# The -B trick didn't work, removing the directory
shutil.rmtree(targetDir)
if enable_gold_option or developer_options:
result = resolve_gold()
if result:
return result
# gold is only required if --enable-gold is used.
elif enable_gold_option:
die('Could not find gold')
# Else fallthrough.
cmd = cmd_base + version_check
cmd_output = check_cmd_output(*cmd).decode('utf-8')
# using decode because ld can be localized and python will

View File

@ -22,6 +22,7 @@ def configure_error(message):
# by running the given command if it exits normally, and streams that
# output to log.debug and calls die or the given error callback if it
# does not.
@imports(_from='__builtin__', _import='unicode')
@imports('subprocess')
@imports('sys')
@imports(_from='mozbuild.configure.util', _import='LineIO')
@ -29,10 +30,26 @@ def configure_error(message):
def check_cmd_output(*args, **kwargs):
onerror = kwargs.pop('onerror', None)
# subprocess on older Pythons can't handle unicode keys or values in
# environment dicts. Normalize automagically so callers don't have to
# deal with this.
if 'env' in kwargs:
normalized_env = {}
for k, v in kwargs['env'].items():
if isinstance(k, unicode):
k = k.encode('utf-8', 'strict')
if isinstance(v, unicode):
v = v.encode('utf-8', 'strict')
normalized_env[k] = v
kwargs['env'] = normalized_env
with log.queue_debug():
log.debug('Executing: `%s`', quote(*args))
proc = subprocess.Popen(args, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stderr=subprocess.PIPE, **kwargs)
stdout, stderr = proc.communicate()
retcode = proc.wait()
if retcode == 0:

View File

@ -14,9 +14,6 @@ mk_add_options AUTOCLOBBER=1
ac_add_options --enable-crashreporter
# Tell the build system where to find llvm-config for builds on automation.
export LLVM_CONFIG="${TOOLTOOL_DIR:-$topsrcdir}/clang/bin/llvm-config"
# Enable checking that add-ons are signed by the trusted root
MOZ_ADDON_SIGNING=${MOZ_ADDON_SIGNING-1}
# Disable enforcing that add-ons are signed by the trusted root

View File

@ -10,3 +10,4 @@ unset CARGO
unset MAKECAB
unset TOOLCHAIN_PREFIX
unset BINDGEN_CFLAGS
unset LLVM_CONFIG

6
build/mozconfig.stylo Normal file
View File

@ -0,0 +1,6 @@
# Tell the build system where to find llvm-config for builds on automation.
export LLVM_CONFIG="${TOOLTOOL_DIR:-$topsrcdir}/clang/bin/llvm-config"
# TODO remove once configure defaults to stylo once stylo enabled
# on all platforms.
ac_add_options --enable-stylo=build

View File

@ -93,16 +93,16 @@ Tools.inspector = {
return new InspectorPanel(iframeWindow, toolbox);
}
};
Tools.webConsole = {
id: "webconsole",
key: l10n("cmd.commandkey"),
accesskey: l10n("webConsoleCmd.accesskey"),
modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
ordinal: 2,
oldWebConsoleURL: "chrome://devtools/content/webconsole/webconsole.xul",
newWebConsoleURL: "chrome://devtools/content/webconsole/webconsole.xhtml",
icon: "chrome://devtools/skin/images/tool-webconsole.svg",
invertIconForDarkTheme: true,
url: "chrome://devtools/content/webconsole/webconsole.xul",
label: l10n("ToolboxTabWebconsole.label"),
menuLabel: l10n("MenuWebconsole.label"),
panelLabel: l10n("ToolboxWebConsole.panelLabel"),
@ -126,11 +126,23 @@ Tools.webConsole = {
isTargetSupported: function () {
return true;
},
build: function (iframeWindow, toolbox) {
return new WebConsolePanel(iframeWindow, toolbox);
}
};
function switchWebconsole() {
if (Services.prefs.getBoolPref("devtools.webconsole.new-frontend-enabled")) {
Tools.webConsole.url = Tools.webConsole.newWebConsoleURL;
} else {
Tools.webConsole.url = Tools.webConsole.oldWebConsoleURL;
}
}
switchWebconsole();
Services.prefs.addObserver(
"devtools.webconsole.new-frontend-enabled",
{ observe: switchWebconsole }
);
Tools.jsdebugger = {
id: "jsdebugger",

View File

@ -202,11 +202,6 @@
data-pref="devtools.debugger.remote-enabled"/>
<span>&options.enableRemote.label3;</span>
</label>
<label title="&options.enableWorkers.tooltip;">
<input type="checkbox"
data-pref="devtools.debugger.workers"/>
<span>&options.enableWorkers.label;</span>
</label>
<span class="options-citation-label theme-comment"
>&options.context.triggersPageRefresh;</span>
</fieldset>

View File

@ -66,8 +66,8 @@ function setPrefDefaults() {
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
Services.prefs.setBoolPref("devtools.debugger.client-source-maps-enabled", true);
Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false);
}
window.addEventListener("load", function () {
let cmdClose = document.getElementById("toolbox-cmd-close");
cmdClose.addEventListener("command", onCloseCommand);

View File

@ -10,6 +10,7 @@ devtools.jar:
content/netmonitor/src/assets/styles/netmonitor.css (netmonitor/src/assets/styles/netmonitor.css)
content/shared/widgets/VariablesView.xul (shared/widgets/VariablesView.xul)
content/netmonitor/index.html (netmonitor/index.html)
content/webconsole/webconsole.xhtml (webconsole/webconsole.xhtml)
content/webconsole/webconsole.xul (webconsole/webconsole.xul)
content/scratchpad/scratchpad.xul (scratchpad/scratchpad.xul)
content/scratchpad/scratchpad.js (scratchpad/scratchpad.js)

View File

@ -82,12 +82,6 @@
<!ENTITY options.enableRemote.label3 "Enable remote debugging">
<!ENTITY options.enableRemote.tooltip2 "Turning this option on will allow the developer tools to debug a remote instance like Firefox OS">
<!-- LOCALIZATION NOTE (options.enableWorkers.label): This is the label for the
- checkbox that toggles worker debugging, i.e. devtools.debugger.workers
- boolean preference in about:config, in the options panel. -->
<!ENTITY options.enableWorkers.label "Enable worker debugging (in development)">
<!ENTITY options.enableWorkers.tooltip "Turning this option on will allow the developer tools to debug workers">
<!-- LOCALIZATION NOTE (options.disableJavaScript.label,
- options.disableJavaScript.tooltip): This is the options panel label and
- tooltip for the checkbox that toggles JavaScript on or off. -->

View File

@ -1,21 +1,16 @@
<!-- 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/. -->
<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
- keep it in English, or another language commonly spoken among web developers.
- You want to make that choice consistent across the developer tools.
- A good criteria is the language in which you'd find the best
- documentation on web development on the web. -->
<!ENTITY window.title "Web Console">
<!ENTITY browserConsole.title "Browser Console">
<!-- LOCALIZATION NOTE (openURL.label): You can see this string in the Web
- Console context menu. -->
<!ENTITY openURL.label "Open URL in New Tab">
<!ENTITY openURL.accesskey "T">
<!-- LOCALIZATION NOTE (btnPageNet.label): This string is used for the menu
- button that allows users to toggle the network logging output.
- This string and the following strings toggle various kinds of output

View File

@ -1,21 +1,19 @@
# 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/.
# LOCALIZATION NOTE
# The correct localization of this file might be to keep it in
# English, or another language commonly spoken among web developers.
# You want to make that choice consistent across the developer tools.
# A good criteria is the language in which you'd find the best
# documentation on web development on the web.
# LOCALIZATION NOTE (browserConsole.title): shown as the
# title when opening the browser console popup
browserConsole.title=Browser Console
# LOCALIZATION NOTE (timestampFormat): %1$02S = hours (24-hour clock),
# %2$02S = minutes, %3$02S = seconds, %4$03S = milliseconds.
timestampFormat=%02S:%02S:%02S.%03S
helperFuncUnsupportedTypeError=Cant call pprint on this type of object.
# LOCALIZATION NOTE (NetworkPanel.deltaDurationMS): this string is used to
# show the duration between two network events (e.g request and response
# header or response header and response body). Parameters: %S is the duration.

View File

@ -15,7 +15,7 @@
* view of the visualization is drawn onto this canvas, providing a crisp zoomed
* in view of the tree map.
*/
const { debounce } = require("sdk/lang/functional");
const { debounce } = require("devtools/shared/debounce");
const EventEmitter = require("devtools/shared/event-emitter");
const HTML_NS = "http://www.w3.org/1999/xhtml";

View File

@ -4,7 +4,7 @@
"use strict";
const { debounce } = require("sdk/lang/functional");
const { debounce } = require("devtools/shared/debounce");
const { lerp } = require("devtools/client/memory/utils");
const EventEmitter = require("devtools/shared/event-emitter");

View File

@ -11,7 +11,7 @@ exports.VERTICAL_AXIS = 2;
exports.command = (node) => {
let ev = node.ownerDocument.createEvent("XULCommandEvent");
ev.initCommandEvent("command", true, true, node.ownerDocument.defaultView, 0, false,
false, false, false, null);
false, false, false, null, 0);
node.dispatchEvent(ev);
};

View File

@ -151,10 +151,12 @@
let doc = node.ownerDocument;
const inspectorUrl = "chrome://devtools/content/inspector/inspector.xhtml";
const netMonitorUrl = "chrome://devtools/content/netmonitor/netmonitor.xhtml";
const webConsoleUrl = "chrome://devtools/content/webconsole/webconsole.xhtml";
while (doc instanceof XULDocument ||
doc.location.href === inspectorUrl ||
doc.location.href === netMonitorUrl) {
doc.location.href === netMonitorUrl ||
doc.location.href === webConsoleUrl) {
const {frameElement} = doc.defaultView;
if (!frameElement) {

View File

@ -15,7 +15,7 @@
box-sizing: border-box;
}
.display-wrap {
.cubic-bezier-container .display-wrap {
width: 50%;
height: 100%;
text-align: center;
@ -24,14 +24,14 @@
/* Coordinate Plane */
.coordinate-plane {
.cubic-bezier-container .coordinate-plane {
width: 150px;
height: 370px;
margin: 0 auto;
position: relative;
}
.control-point {
.cubic-bezier-container .control-point {
position: absolute;
z-index: 1;
height: 10px;
@ -46,7 +46,7 @@
cursor: pointer;
}
.display-wrap {
.cubic-bezier-container .display-wrap {
background:
repeating-linear-gradient(0deg,
transparent,
@ -66,7 +66,7 @@
-moz-user-select: none;
}
canvas.curve {
.cubic-bezier-container canvas.curve {
background:
linear-gradient(-45deg,
transparent 49.7%,
@ -79,14 +79,14 @@ canvas.curve {
/* Timing Function Preview Widget */
.timing-function-preview {
.cubic-bezier-container .timing-function-preview {
position: absolute;
bottom: 20px;
right: 45px;
width: 150px;
}
.timing-function-preview .scale {
.cubic-bezier-container .timing-function-preview .scale {
position: absolute;
top: 6px;
left: 0;
@ -98,7 +98,7 @@ canvas.curve {
background: #ccc;
}
.timing-function-preview .dot {
.cubic-bezier-container .timing-function-preview .dot {
position: absolute;
top: 0;
left: -7px;
@ -114,7 +114,7 @@ canvas.curve {
/* Preset Widget */
.preset-pane {
.cubic-bezier-container .preset-pane {
width: 50%;
height: 100%;
border-right: 1px solid var(--theme-splitter-color);
@ -134,7 +134,7 @@ canvas.curve {
border-right: none;
}
.category {
.cubic-bezier-container .category {
padding: 5px 0px;
width: 33.33%;
text-align: center;
@ -146,16 +146,16 @@ canvas.curve {
overflow: hidden;
}
.category:hover {
.cubic-bezier-container .category:hover {
background-color: var(--theme-tab-toolbar-background);
}
.active-category {
.cubic-bezier-container .active-category {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}
.active-category:hover {
.cubic-bezier-container .active-category:hover {
background-color: var(--theme-selection-background);
}
@ -166,25 +166,25 @@ canvas.curve {
overflow-y: auto;
}
.preset-list {
.cubic-bezier-container .preset-list {
display: none;
padding-top: 6px;
}
.active-preset-list {
.cubic-bezier-container .active-preset-list {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.preset {
.cubic-bezier-container .preset {
cursor: pointer;
width: 33.33%;
margin: 5px 0px;
text-align: center;
}
.preset canvas {
.cubic-bezier-container .preset canvas {
display: block;
border: 1px solid var(--theme-splitter-color);
border-radius: 3px;
@ -192,7 +192,7 @@ canvas.curve {
margin: 0 auto;
}
.preset p {
.cubic-bezier-container .preset p {
font-size: 80%;
margin: 2px auto 0px auto;
color: var(--theme-body-color-alt);
@ -201,16 +201,17 @@ canvas.curve {
overflow: hidden;
}
.active-preset p, .active-preset:hover p {
.cubic-bezier-container .active-preset p,
.cubic-bezier-container .active-preset:hover p {
color: var(--theme-body-color);
}
.preset:hover canvas {
.cubic-bezier-container .preset:hover canvas {
border-color: var(--theme-selection-background);
}
.active-preset canvas,
.active-preset:hover canvas {
.cubic-bezier-container .active-preset canvas,
.cubic-bezier-container .active-preset:hover canvas {
background-color: var(--theme-selection-background-semitransparent);
border-color: var(--theme-selection-background);
}

View File

@ -19,21 +19,21 @@
-moz-user-select: none;
}
.filters-list,
.presets-list {
#filter-container .filters-list,
#filter-container .presets-list {
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.filters-list {
#filter-container .filters-list {
/* Allow the filters list to take the full width when the presets list is
hidden */
flex-grow: 1;
padding: 0 6px;
}
.presets-list {
#filter-container .presets-list {
/* Make sure that when the presets list is shown, it has a fixed width */
width: 200px;
padding-left: 6px;
@ -55,8 +55,8 @@
/* The list of filters and list of presets should push their footers to the
bottom, so they can take as much space as there is */
#filters,
#presets {
#filter-container #filters,
#filter-container #presets {
flex-grow: 1;
/* Avoid pushing below the tooltip's area */
overflow-y: auto;
@ -66,47 +66,47 @@
These footers have some input (taking up as much space as possible) and an
add button next */
.footer {
#filter-container .footer {
display: flex;
margin: 10px 3px;
align-items: center;
}
.footer :not(button) {
#filter-container .footer :not(button) {
flex-grow: 1;
margin-right: 3px;
}
/* Styles for 1 filter function item */
.filter,
.filter-name,
.filter-value {
#filter-container .filter,
#filter-container .filter-name,
#filter-container .filter-value {
display: flex;
align-items: center;
}
.filter {
#filter-container .filter {
margin: 5px 0;
}
.filter-name {
#filter-container .filter-name {
width: 120px;
margin-right: 10px;
}
.filter-name label {
#filter-container .filter-name label {
-moz-user-select: none;
flex-grow: 1;
}
.filter-name label.devtools-draglabel {
#filter-container .filter-name label.devtools-draglabel {
cursor: ew-resize;
}
/* drag/drop handle */
.filter-name i {
#filter-container .filter-name i {
width: 10px;
height: 10px;
margin-right: 10px;
@ -121,23 +121,23 @@
background-position: 0 1px;
}
.filter-value {
#filter-container .filter-value {
min-width: 150px;
margin-right: 10px;
flex: 1;
}
.filter-value input {
#filter-container .filter-value input {
flex-grow: 1;
}
/* Fix the size of inputs */
/* Especially needed on Linux where input are bigger */
input {
#filter-container input {
width: 8em;
}
.preset {
#filter-container .preset {
display: flex;
margin-bottom: 10px;
cursor: pointer;
@ -147,31 +147,32 @@ input {
flex-wrap: wrap;
}
.preset label,
.preset span {
#filter-container .preset label,
#filter-container .preset span {
display: flex;
align-items: center;
}
.preset label {
#filter-container .preset label {
flex: 1 0;
cursor: pointer;
color: var(--theme-body-color);
}
.preset:hover {
#filter-container .preset:hover {
background: var(--theme-selection-background);
}
.preset:hover label, .preset:hover span {
#filter-container .preset:hover label,
#filter-container .preset:hover span {
color: var(--theme-selection-color);
}
.preset .remove-button {
#filter-container .preset .remove-button {
order: 2;
}
.preset span {
#filter-container .preset span {
flex: 2 100%;
white-space: nowrap;
overflow: hidden;
@ -181,7 +182,7 @@ input {
color: var(--theme-body-color-alt);
}
.remove-button {
#filter-container .remove-button {
width: 16px;
height: 16px;
background: url(chrome://devtools/skin/images/close.svg);
@ -191,7 +192,7 @@ input {
cursor: pointer;
}
.hidden {
#filter-container .hidden {
display: none !important;
}
@ -207,7 +208,7 @@ input {
line-height: 20px;
}
.add,
#filter-container .add,
#toggle-presets {
background-size: cover;
border: none;
@ -219,7 +220,7 @@ input {
margin: 0 5px;
}
.add {
#filter-container .add {
background: url(chrome://devtools/skin/images/add.svg);
}
@ -227,8 +228,8 @@ input {
background: url(chrome://devtools/skin/images/pseudo-class.svg);
}
.add,
.remove-button,
#filter-container .add,
#filter-container .remove-button,
#toggle-presets {
filter: var(--icon-filter);
}

View File

@ -28,7 +28,7 @@
margin-top: 1em;
}
.devtools-throbber {
.mdn-container .devtools-throbber {
align-self: center;
opacity: 0;
}

View File

@ -23,8 +23,7 @@ function CssDocsTooltip(toolboxDoc) {
type: "arrow",
consumeOutsideClicks: true,
autofocus: true,
useXulWrapper: true,
stylesheet: "chrome://devtools/content/shared/widgets/mdn-docs.css",
useXulWrapper: true
});
this.widget = this.setMdnDocsContent();
this._onVisitLink = this._onVisitLink.bind(this);

View File

@ -213,15 +213,12 @@ const getRelativeRect = function (node, relativeTo) {
* - {Boolean} useXulWrapper
* Defaults to false. If the tooltip is hosted in a XUL document, use a XUL panel
* in order to use all the screen viewport available.
* - {String} stylesheet
* Style sheet URL to apply to the tooltip content.
*/
function HTMLTooltip(toolboxDoc, {
type = "normal",
autofocus = false,
consumeOutsideClicks = true,
useXulWrapper = false,
stylesheet = "",
} = {}) {
EventEmitter.decorate(this);
@ -246,9 +243,6 @@ function HTMLTooltip(toolboxDoc, {
this.container = this._createContainer();
if (stylesheet) {
this._applyStylesheet(stylesheet);
}
if (this.useXulWrapper) {
// When using a XUL panel as the wrapper, the actual markup for the tooltip is as
// follows :
@ -634,16 +628,4 @@ HTMLTooltip.prototype = {
top += this.doc.defaultView.mozInnerScreenY;
return {top, right: left + width, bottom: top + height, left, width, height};
},
/**
* Apply a scoped stylesheet to the container so that this css file only
* applies to it.
*/
_applyStylesheet: function (url) {
let style = this.doc.createElementNS(XHTML_NS, "style");
style.setAttribute("scoped", "true");
url = url.replace(/"/g, "\\\"");
style.textContent = `@import url("${url}");`;
this.container.appendChild(style);
}
};

View File

@ -19,12 +19,10 @@ const INLINE_TOOLTIP_CLASS = "inline-tooltip-container";
* The document to attach the SwatchBasedEditorTooltip. This is either the toolbox
* document if the tooltip is a popup tooltip or the panel's document if it is an
* inline editor.
* @param {String} stylesheet
* The stylesheet to be used for the HTMLTooltip.
* @param {Boolean} useInline
* A boolean flag representing whether or not the InlineTooltip should be used.
*/
function SwatchBasedEditorTooltip(document, stylesheet, useInline) {
function SwatchBasedEditorTooltip(document, useInline) {
EventEmitter.decorate(this);
this.useInline = useInline;
@ -40,7 +38,6 @@ function SwatchBasedEditorTooltip(document, stylesheet, useInline) {
type: "arrow",
consumeOutsideClicks: true,
useXulWrapper: true,
stylesheet
});
}

View File

@ -38,10 +38,7 @@ const XHTML_NS = "http://www.w3.org/1999/xhtml";
function SwatchColorPickerTooltip(document,
inspector,
{supportsCssColor4ColorFunction}) {
let stylesheet = NEW_COLOR_WIDGET ?
"chrome://devtools/content/shared/widgets/color-widget.css" :
"chrome://devtools/content/shared/widgets/spectrum.css";
SwatchBasedEditorTooltip.call(this, document, stylesheet);
SwatchBasedEditorTooltip.call(this, document);
this.inspector = inspector;

View File

@ -26,8 +26,7 @@ const XHTML_NS = "http://www.w3.org/1999/xhtml";
* inline editor.
*/
function SwatchCubicBezierTooltip(document) {
let stylesheet = "chrome://devtools/content/shared/widgets/cubic-bezier.css";
SwatchBasedEditorTooltip.call(this, document, stylesheet);
SwatchBasedEditorTooltip.call(this, document);
// Creating a cubic-bezier instance.
// this.widget will always be a promise that resolves to the widget instance

View File

@ -28,8 +28,7 @@ const XHTML_NS = "http://www.w3.org/1999/xhtml";
* This can be obtained from "shared/fronts/css-properties.js".
*/
function SwatchFilterTooltip(document, cssIsValid) {
let stylesheet = "chrome://devtools/content/shared/widgets/filter-widget.css";
SwatchBasedEditorTooltip.call(this, document, stylesheet);
SwatchBasedEditorTooltip.call(this, document);
this._cssIsValid = cssIsValid;
// Creating a filter editor instance.

View File

@ -1,600 +0,0 @@
@import "chrome://devtools/skin/widgets.css";
@import "resource://devtools/client/themes/light-theme.css";
/* Webconsole specific theme variables */
.theme-light,
.theme-firebug {
--error-color: #FF0000;
--error-background-color: #FFEBEB;
--warning-background-color: #FFFFC8;
}
/* General output styles */
a {
-moz-user-focus: normal;
-moz-user-input: enabled;
cursor: pointer;
text-decoration: underline;
}
/* Workaround for Bug 575675 - FindChildWithRules aRelevantLinkVisited
* assertion when loading HTML page with links in XUL iframe */
*:visited { }
.webconsole-filterbar-wrapper {
flex-grow: 0;
}
.webconsole-filterbar-primary {
display: flex;
}
.devtools-toolbar.webconsole-filterbar-secondary {
height: initial;
}
.webconsole-filterbar-primary .devtools-plaininput {
flex: 1 1 100%;
}
.webconsole-output.hideTimestamps > .message > .timestamp {
display: none;
}
.message.startGroup .message-body > .objectBox-string,
.message.startGroupCollapsed .message-body > .objectBox-string {
color: var(--theme-body-color);
font-weight: bold;
}
.webconsole-output-wrapper .message > .icon {
margin: 3px 0 0 0;
padding: 0 0 0 6px;
}
.message.error > .icon::before {
background-position: -12px -36px;
}
.message.warn > .icon::before {
background-position: -24px -36px;
}
.message.info > .icon::before {
background-position: -36px -36px;
}
.message.network .method {
margin-inline-end: 5px;
}
.network .message-flex-body > .message-body {
display: flex;
}
.webconsole-output-wrapper .message .indent {
display: inline-block;
border-inline-end: solid 1px var(--theme-splitter-color);
}
.message.startGroup .indent,
.message.startGroupCollapsed .indent {
border-inline-end-color: transparent;
margin-inline-end: 5px;
}
.message.startGroup .icon,
.message.startGroupCollapsed .icon {
display: none;
}
/* console.table() */
.new-consoletable {
width: 100%;
border-collapse: collapse;
--consoletable-border: 1px solid var(--table-splitter-color);
}
.new-consoletable thead,
.new-consoletable tbody {
background-color: var(--theme-body-background);
}
.new-consoletable th {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
margin: 0;
padding: 5px 0 0;
font-weight: inherit;
border-inline-end: var(--consoletable-border);
border-bottom: var(--consoletable-border);
}
.new-consoletable tr:nth-of-type(even) {
background-color: var(--table-zebra-background);
}
.new-consoletable td {
padding: 3px 4px;
min-width: 100px;
-moz-user-focus: normal;
color: var(--theme-body-color);
border-inline-end: var(--consoletable-border);
height: 1.25em;
line-height: 1.25em;
}
/* Layout */
.webconsole-output {
flex: 1;
direction: ltr;
overflow: auto;
-moz-user-select: text;
position: relative;
}
:root,
body,
#app-wrapper {
height: 100%;
margin: 0;
padding: 0;
}
body {
overflow: hidden;
}
#app-wrapper {
display: flex;
flex-direction: column;
}
:root, body {
margin: 0;
padding: 0;
height: 100%;
}
#app-wrapper {
height: 100%;
display: flex;
flex-direction: column;
}
#left-wrapper {
flex: 1;
display: flex;
flex-direction: column;
}
#output-container {
flex: 1;
overflow: hidden;
}
.webconsole-output-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.message {
display: flex;
padding: 0 7px;
width: 100%;
box-sizing: border-box;
}
.message > .prefix,
.message > .timestamp {
flex: none;
color: var(--theme-comment);
margin: 3px 6px 0 0;
}
.message > .indent {
flex: none;
}
.message > .icon {
flex: none;
margin: 3px 6px 0 0;
padding: 0 4px;
height: 1em;
align-self: flex-start;
}
.theme-firebug .message > .icon {
margin: 0;
margin-inline-end: 6px;
}
.theme-firebug .message[severity="error"],
.theme-light .message.error,
.theme-firebug .message.error {
color: var(--error-color);
background-color: var(--error-background-color);
}
.theme-firebug .message[severity="warn"],
.theme-light .message.warn,
.theme-firebug .message.warn {
background-color: var(--warning-background-color);
}
.message > .icon::before {
content: "";
background-image: url(chrome://devtools/skin/images/webconsole.svg);
background-position: 12px 12px;
background-repeat: no-repeat;
background-size: 72px 60px;
width: 12px;
height: 12px;
display: inline-block;
}
.theme-light .message > .icon::before {
background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
}
.message > .message-body-wrapper {
flex: auto;
min-width: 0px;
margin: 3px;
}
/* The red bubble that shows the number of times a message is repeated */
.message-repeats {
-moz-user-select: none;
flex: none;
margin: 2px 6px;
padding: 0 6px;
height: 1.25em;
color: white;
background-color: red;
border-radius: 40px;
font: message-box;
font-size: 0.9em;
font-weight: 600;
}
.message-repeats[value="1"] {
display: none;
}
.message-location {
max-width: 40%;
}
.stack-trace {
/* The markup contains extra whitespace to improve formatting of clipboard text.
Make sure this whitespace doesn't affect the HTML rendering */
white-space: normal;
}
.stack-trace .frame-link-source,
.message-location .frame-link-source {
/* Makes the file name truncated (and ellipsis shown) on the left side */
direction: rtl;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stack-trace .frame-link-source-inner,
.message-location .frame-link-source-inner {
/* Enforce LTR direction for the file name - fixes bug 1290056 */
direction: ltr;
unicode-bidi: embed;
}
.stack-trace .frame-link-function-display-name {
max-width: 50%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.message-flex-body {
display: flex;
}
.message-body > * {
white-space: pre-wrap;
word-wrap: break-word;
}
.message-flex-body > .message-body {
display: block;
flex: auto;
}
#output-container.hideTimestamps > .message {
padding-inline-start: 0;
margin-inline-start: 7px;
width: calc(100% - 7px);
}
#output-container.hideTimestamps > .message > .timestamp {
display: none;
}
#output-container.hideTimestamps > .message > .indent {
background-color: var(--theme-body-background);
}
.message:hover {
background-color: var(--theme-selection-background-semitransparent) !important;
}
.theme-light .message.error {
background-color: rgba(255, 150, 150, 0.3);
}
.theme-dark .message.error {
background-color: rgba(235, 83, 104, 0.17);
}
.console-string {
color: var(--theme-highlight-lightorange);
}
.theme-selected .console-string,
.theme-selected .cm-number,
.theme-selected .cm-variable,
.theme-selected .kind-ArrayLike {
color: #f5f7fa !important; /* Selection Text Color */
}
.message.network.error > .icon::before {
background-position: -12px 0;
}
.message.network > .message-body {
display: flex;
flex-wrap: wrap;
}
.message.network .method {
flex: none;
}
.message.network:not(.navigation-marker) .url {
flex: 1 1 auto;
/* Make sure the URL is very small initially, let flex change width as needed. */
width: 100px;
min-width: 5em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.message.network .status {
flex: none;
margin-inline-start: 6px;
}
.message.network.mixed-content .url {
color: var(--theme-highlight-red);
}
.message .learn-more-link {
color: var(--theme-highlight-blue);
margin: 0 6px;
}
.message.network .xhr {
background-color: var(--theme-body-color-alt);
color: var(--theme-body-background);
border-radius: 3px;
font-weight: bold;
font-size: 10px;
padding: 2px;
line-height: 10px;
margin-inline-start: 3px;
margin-inline-end: 1ex;
}
.message.cssparser > .indent {
border-inline-end: solid #00b6f0 6px;
}
.message.cssparser.error > .icon::before {
background-position: -12px -12px;
}
.message.cssparser.warn > .icon::before {
background-position: -24px -12px;
}
.message.exception > .indent {
border-inline-end: solid #fb9500 6px;
}
.message.exception.error > .icon::before {
background-position: -12px -24px;
}
.message.exception.warn > .icon::before {
background-position: -24px -24px;
}
.message.console-api > .indent {
border-inline-end: solid #cbcbcb 6px;
}
.message.server > .indent {
border-inline-end: solid #90B090 6px;
}
/* Input and output styles */
.message.command > .indent,
.message.result > .indent {
border-inline-end: solid #808080 6px;
}
.message.command > .icon::before {
background-position: -48px -36px;
}
.message.result > .icon::before {
background-position: -60px -36px;
}
/* JSTerm Styles */
#jsterm-wrapper {
flex: 0;
}
.jsterm-input-container {
background-color: var(--theme-tab-toolbar-background);
border-top: 1px solid var(--theme-splitter-color);
}
.theme-light .jsterm-input-container {
/* For light theme use a white background for the input - it looks better
than off-white */
background-color: #fff;
border-top-color: #e0e0e0;
}
.theme-firebug .jsterm-input-container {
border-top: 1px solid #ccc;
}
.jsterm-input-node,
.jsterm-complete-node {
border: none;
padding: 0;
padding-inline-start: 20px;
margin: 0;
-moz-appearance: none;
background-color: transparent;
}
.jsterm-input-node[focused="true"] {
background-image: var(--theme-command-line-image-focus);
box-shadow: none;
}
.jsterm-complete-node {
color: var(--theme-comment);
}
.jsterm-input-node-html {
width: 100%;
}
.jsterm-input-node {
/* Always allow scrolling on input - it auto expands in js by setting height,
but don't want it to get bigger than the window. 24px = toolbar height. */
max-height: calc(90vh - 24px);
background-image: var(--theme-command-line-image);
background-repeat: no-repeat;
background-size: 16px 16px;
background-position: 4px 50%;
color: var(--theme-content-color1);
}
:-moz-any(.jsterm-input-node,
.jsterm-complete-node) > .textbox-input-box > .textbox-textarea {
overflow-x: hidden;
/* Set padding for console input on textbox to make sure it is inlcuded in
scrollHeight that is used when resizing JSTerminal's input. Note: textbox
default style has important already */
padding: 4px 0 !important;
}
#webconsole-notificationbox,
.jsterm-stack-node {
width: 100%;
}
.message.security > .indent {
border-inline-end: solid red 6px;
}
.message.security.error > .icon::before {
background-position: -12px -48px;
}
.message.security.warn > .icon::before {
background-position: -24px -48px;
}
.navigation-marker {
color: #aaa;
background: linear-gradient(#aaa, #aaa) no-repeat left 50%;
background-size: 100% 2px;
margin-top: 6px;
margin-bottom: 6px;
font-size: 0.9em;
}
.navigation-marker .url {
padding-inline-end: 9px;
text-decoration: none;
background: var(--theme-body-background);
}
.theme-light .navigation-marker .url {
background: #fff;
}
.stacktrace {
display: none;
padding: 5px 10px;
margin: 5px 0 0 0;
overflow-y: auto;
border: 1px solid var(--theme-splitter-color);
border-radius: 3px;
}
.theme-light .message.error .stacktrace {
background-color: rgba(255, 255, 255, 0.5);
}
.theme-dark .message.error .stacktrace {
background-color: rgba(0, 0, 0, 0.5);
}
.message.open .stacktrace {
display: block;
}
.message .theme-twisty {
display: inline-block;
vertical-align: middle;
margin: 3px 0 0 0;
flex-shrink: 0;
}
/*Do not mirror the twisty because container force to ltr */
.message .theme-twisty:dir(rtl),
.message .theme-twisty:-moz-locale-dir(rtl) {
transform: none;
}
.cm-s-mozilla a[class] {
font-style: italic;
text-decoration: none;
}
.cm-s-mozilla a[class]:hover,
.cm-s-mozilla a[class]:focus {
text-decoration: underline;
}
a.learn-more-link.webconsole-learn-more-link {
font-style: normal;
}
/* Open DOMNode in inspector button */
.open-inspector {
background: url(chrome://devtools/skin/images/vview-open-inspector.png) no-repeat 0 0;
padding-left: 16px;
margin-left: 5px;
cursor: pointer;
}
.elementNode:hover .open-inspector,
.open-inspector:hover {
filter: url(images/filters.svg#checked-icon-state);
}
.elementNode:hover .open-inspector:active,
.open-inspector:active {
filter: url(images/filters.svg#checked-icon-state) brightness(0.9);
}

View File

@ -3,6 +3,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Import stylesheets for specific tooltip widgets */
@import url(chrome://devtools/content/shared/widgets/color-widget.css);
@import url(chrome://devtools/content/shared/widgets/cubic-bezier.css);
@import url(chrome://devtools/content/shared/widgets/filter-widget.css);
@import url(chrome://devtools/content/shared/widgets/mdn-docs.css);
@import url(chrome://devtools/content/shared/widgets/spectrum.css);
/* Tooltip specific theme variables */
.theme-dark {

View File

@ -402,6 +402,15 @@ a {
}
/* JSTerm Styles */
html #jsterm-wrapper,
html .jsterm-stack-node,
html .jsterm-input-node-html,
html #webconsole-notificationbox {
flex: 0;
width: 100vw;
}
.jsterm-input-container {
background-color: var(--theme-tab-toolbar-background);
border-top: 1px solid var(--theme-splitter-color);
@ -860,3 +869,44 @@ a.learn-more-link.webconsole-learn-more-link {
height: 1.25em;
line-height: 1.25em;
}
/* Layout */
.webconsole-output {
flex: 1;
direction: ltr;
overflow: auto;
-moz-user-select: text;
position: relative;
}
html,
body,
#app-wrapper {
height: 100%;
margin: 0;
padding: 0;
}
body {
overflow: hidden;
}
#app-wrapper {
height: 100%;
display: flex;
flex-direction: column;
}
body #output-container {
flex: 1;
overflow: hidden;
}
.webconsole-output-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
/* Object Inspector */

Some files were not shown because too many files have changed in this diff Show More