Merge m-c to inbound, a=merge
MozReview-Commit-ID: Ah48RzFU8Mt
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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"]},
|
||||
|
7
browser/base/content/test/touch/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"plugin:mozilla/browser-test"
|
||||
]
|
||||
};
|
4
browser/base/content/test/touch/browser.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[DEFAULT]
|
||||
|
||||
[browser_menu_touch.js]
|
||||
skip-if = !(os == 'win' && os_version == '10.0')
|
109
browser/base/content/test/touch/browser_menu_touch.js
Normal 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();
|
||||
});
|
@ -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',
|
||||
|
@ -11,4 +11,4 @@ BROWSER_CHROME_MANIFESTS += [
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('DOM', 'Security')
|
||||
BUG_COMPONENT = ('Core', 'DOM: Security')
|
||||
|
@ -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");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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")) {
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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`);
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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"];
|
||||
|
@ -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; }
|
||||
|
@ -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>
|
||||
|
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 / ></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-/->" 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 |
@ -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 don’t 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 you’ve 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": "You’ve caught up. Check back later for more top stories from Pocket. Can’t wait? Select a popular topic to find more great stories from around the web."
|
||||
},
|
||||
"en-ZA": {},
|
||||
"eo": {
|
||||
|
@ -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"];
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
58
browser/extensions/activity-stream/lib/SnippetsFeed.jsm
Normal 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"];
|
@ -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);
|
||||
}
|
||||
|
35
browser/extensions/activity-stream/lib/SystemTickFeed.jsm
Normal 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"];
|
187
browser/extensions/activity-stream/lib/TopStoriesFeed.jsm
Normal 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"];
|
@ -1,5 +1,4 @@
|
||||
[DEFAULT]
|
||||
skip-if=!nightly_build
|
||||
support-files =
|
||||
blue_page.html
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"activity_stream": true
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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", () => {
|
||||
|
@ -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"}
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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});
|
||||
|
@ -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 {
|
||||
|
@ -536,6 +536,7 @@
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 960px) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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@ {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -10,3 +10,4 @@ unset CARGO
|
||||
unset MAKECAB
|
||||
unset TOOLCHAIN_PREFIX
|
||||
unset BINDGEN_CFLAGS
|
||||
unset LLVM_CONFIG
|
||||
|
6
build/mozconfig.stylo
Normal 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
|
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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. -->
|
||||
|
@ -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
|
||||
|
@ -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=Can’t 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.
|
||||
|
@ -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";
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
4
devtools/client/shared/vendor/react-dom.js
vendored
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.devtools-throbber {
|
||||
.mdn-container .devtools-throbber {
|
||||
align-self: center;
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 */
|
||||
|