--- a/devtools/client/shared/components/NotificationBox.js
+++ b/devtools/client/shared/components/NotificationBox.js
@@ -28,22 +28,32 @@ const PriorityLevels = {
};
/**
* This component represents Notification Box - HTML alternative for
* <xul:notificationbox> binding.
*
* See also MDN for more info about <xul:notificationbox>:
* https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox
+ *
+ * This component can maintain its own state (list of notifications)
+ * as well as consume list of notifications provided as a prop
+ * (coming e.g. from Redux store).
*/
class NotificationBox extends Component {
static get propTypes() {
return {
+ // Optional box ID (used for mounted node ID attribute)
+ id: PropTypes.string,
+
// List of notifications appended into the box.
- notifications: PropTypes.arrayOf(PropTypes.shape({
+ // Use `PropTypes.arrayOf` validation (see below) as soon as
+ // ImmutableJS is removed. See bug 1461678 for more details.
+ notifications: PropTypes.object,
+ /* notifications: PropTypes.arrayOf(PropTypes.shape({
// label to appear on the notification.
label: PropTypes.string.isRequired,
// Value used to identify the notification
value: PropTypes.string.isRequired,
// URL of image to appear on the notification. If "" then an icon
// appropriate for the priority level is used.
@@ -69,17 +79,17 @@ class NotificationBox extends Component
// The accesskey attribute set on the <button> element.
accesskey: PropTypes.string,
})),
// A function to call to notify you of interesting things that happen
// with the notification box.
eventCallback: PropTypes.func,
- })),
+ })),*/
// Message that should be shown when hovering over the close button
closeButtonTooltip: PropTypes.string
};
}
static get defaultProps() {
return {
@@ -104,53 +114,26 @@ class NotificationBox extends Component
}
/**
* Create a new notification and display it. If another notification is
* already present with a higher priority, the new notification will be
* added behind it. See `propTypes` for arguments description.
*/
appendNotification(label, value, image, priority, buttons = [], eventCallback) {
- // Priority level must be within expected interval
- // (see priority levels at the top of this file).
- if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
- priority > PriorityLevels.PRIORITY_CRITICAL_BLOCK) {
- throw new Error("Invalid notification priority " + priority);
- }
-
- // Custom image URL is not supported yet.
- if (image) {
- throw new Error("Custom image URL is not supported yet");
- }
-
- let type = "warning";
- if (priority >= PriorityLevels.PRIORITY_CRITICAL_LOW) {
- type = "critical";
- } else if (priority <= PriorityLevels.PRIORITY_INFO_HIGH) {
- type = "info";
- }
-
- let notifications = this.state.notifications.set(value, {
- label: label,
- value: value,
- image: image,
- priority: priority,
- type: type,
- buttons: Array.isArray(buttons) ? buttons : [],
- eventCallback: eventCallback,
+ const newState = appendNotification(this.state, {
+ label,
+ value,
+ image,
+ priority,
+ buttons,
+ eventCallback,
});
- // High priorities must be on top.
- notifications = notifications.sortBy((val, key) => {
- return -val.priority;
- });
-
- this.setState({
- notifications: notifications
- });
+ this.setState(newState);
}
/**
* Remove specific notification from the list.
*/
removeNotification(notification) {
if (notification) {
this.close(this.state.notifications.get(notification.value));
@@ -187,16 +170,20 @@ class NotificationBox extends Component
if (!notification) {
return;
}
if (notification.eventCallback) {
notification.eventCallback("removed");
}
+ if (!this.state.notifications.get(notification.value)) {
+ return;
+ }
+
this.setState({
notifications: this.state.notifications.remove(notification.value)
});
}
/**
* Render a button. A notification can have a set of custom buttons.
* These are used to execute custom callback.
@@ -255,21 +242,97 @@ class NotificationBox extends Component
);
}
/**
* Render the top (highest priority) notification. Only one
* notification is rendered at a time.
*/
render() {
- let notification = this.state.notifications.first();
- let content = notification ?
+ const notifications = this.props.notifications || this.state.notifications;
+ const notification = notifications ? notifications.first() : null;
+ const content = notification ?
this.renderNotification(notification) :
null;
- return div({className: "notificationbox"},
+ return div({
+ className: "notificationbox",
+ id: this.props.id},
content
);
}
}
+// Helpers
+
+/**
+ * Create a new notification. If another notification is already present with
+ * a higher priority, the new notification will be added behind it.
+ * See `propTypes` for arguments description.
+ */
+function appendNotification(state, props) {
+ const {
+ label,
+ value,
+ image,
+ priority,
+ buttons,
+ eventCallback
+ } = props;
+
+ // Priority level must be within expected interval
+ // (see priority levels at the top of this file).
+ if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
+ priority > PriorityLevels.PRIORITY_CRITICAL_BLOCK) {
+ throw new Error("Invalid notification priority " + priority);
+ }
+
+ // Custom image URL is not supported yet.
+ if (image) {
+ throw new Error("Custom image URL is not supported yet");
+ }
+
+ let type = "warning";
+ if (priority >= PriorityLevels.PRIORITY_CRITICAL_LOW) {
+ type = "critical";
+ } else if (priority <= PriorityLevels.PRIORITY_INFO_HIGH) {
+ type = "info";
+ }
+
+ if (!state.notifications) {
+ state.notifications = new Immutable.OrderedMap();
+ }
+
+ let notifications = state.notifications.set(value, {
+ label: label,
+ value: value,
+ image: image,
+ priority: priority,
+ type: type,
+ buttons: Array.isArray(buttons) ? buttons : [],
+ eventCallback: eventCallback,
+ });
+
+ // High priorities must be on top.
+ notifications = notifications.sortBy((val, key) => {
+ return -val.priority;
+ });
+
+ return {
+ notifications: notifications
+ };
+}
+
+function getNotificationWithValue(notifications, value) {
+ return notifications ? notifications.get(value) : null;
+}
+
+function removeNotificationWithValue(notifications, value) {
+ return {
+ notifications: notifications.remove(value)
+ };
+}
+
module.exports.NotificationBox = NotificationBox;
module.exports.PriorityLevels = PriorityLevels;
+module.exports.appendNotification = appendNotification;
+module.exports.getNotificationWithValue = getNotificationWithValue;
+module.exports.removeNotificationWithValue = removeNotificationWithValue;
--- a/devtools/client/webconsole/actions/index.js
+++ b/devtools/client/webconsole/actions/index.js
@@ -5,13 +5,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const actionModules = [
require("./filters"),
require("./messages"),
require("./ui"),
+ require("./notifications"),
];
const actions = Object.assign({}, ...actionModules);
module.exports = actions;
--- a/devtools/client/webconsole/actions/moz.build
+++ b/devtools/client/webconsole/actions/moz.build
@@ -2,10 +2,11 @@
# 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/.
DevToolsModules(
'filters.js',
'index.js',
'messages.js',
+ 'notifications.js',
'ui.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/actions/notifications.js
@@ -0,0 +1,43 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 {
+ APPEND_NOTIFICATION,
+ REMOVE_NOTIFICATION,
+} = require("devtools/client/webconsole/constants");
+
+/**
+ * Append a notification into JSTerm notification list.
+ */
+function appendNotification(label, value, image, priority, buttons = [], eventCallback) {
+ return {
+ type: APPEND_NOTIFICATION,
+ label,
+ value,
+ image,
+ priority,
+ buttons,
+ eventCallback,
+ };
+}
+
+/**
+ * Remove notification with specified value from JSTerm
+ * notification list.
+ */
+function removeNotification(value) {
+ return {
+ type: REMOVE_NOTIFICATION,
+ value,
+ };
+}
+
+module.exports = {
+ appendNotification,
+ removeNotification,
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/components/App.js
@@ -0,0 +1,172 @@
+/* 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 { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const { connect } = require("devtools/client/shared/redux/visibility-handler-connect");
+
+const actions = require("devtools/client/webconsole/actions/index");
+const ConsoleOutput = createFactory(require("devtools/client/webconsole/components/ConsoleOutput"));
+const FilterBar = createFactory(require("devtools/client/webconsole/components/FilterBar"));
+const SideBar = createFactory(require("devtools/client/webconsole/components/SideBar"));
+const JSTerm = createFactory(require("devtools/client/webconsole/components/JSTerm"));
+const NotificationBox = createFactory(require("devtools/client/shared/components/NotificationBox").NotificationBox);
+
+const l10n = require("devtools/client/webconsole/webconsole-l10n");
+const { Utils: WebConsoleUtils } = require("devtools/client/webconsole/utils");
+
+const SELF_XSS_OK = l10n.getStr("selfxss.okstring");
+const SELF_XSS_MSG = l10n.getFormatStr("selfxss.msg", [SELF_XSS_OK]);
+
+const {
+ getNotificationWithValue,
+ PriorityLevels,
+} = require("devtools/client/shared/components/NotificationBox");
+
+const { getAllNotifications } = require("devtools/client/webconsole/selectors/notifications");
+
+const { div } = dom;
+
+/**
+ * Console root Application component.
+ */
+class App extends Component {
+ static get propTypes() {
+ return {
+ attachRefToHud: PropTypes.func.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ hud: PropTypes.object.isRequired,
+ notifications: PropTypes.object,
+ onFirstMeaningfulPaint: PropTypes.func.isRequired,
+ serviceContainer: PropTypes.object.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.onPaste = this.onPaste.bind(this);
+ }
+
+ onPaste(event) {
+ const {
+ dispatch,
+ hud,
+ notifications,
+ } = this.props;
+
+ const {
+ usageCount,
+ CONSOLE_ENTRY_THRESHOLD
+ } = WebConsoleUtils;
+
+ // Bail out if self-xss notification is suppressed.
+ if (hud.isBrowserConsole || usageCount >= CONSOLE_ENTRY_THRESHOLD) {
+ return;
+ }
+
+ // Stop event propagation, so the clipboard content is *not* inserted.
+ event.preventDefault();
+ event.stopPropagation();
+
+ // Bail out if self-xss notification is already there.
+ if (getNotificationWithValue(notifications, "selfxss-notification")) {
+ return;
+ }
+
+ const inputField = this.node.querySelector(".jsterm-input-node");
+
+ // Cleanup function if notification is closed by the user.
+ const removeCallback = (eventType) => {
+ if (eventType == "removed") {
+ inputField.removeEventListener("keyup", pasteKeyUpHandler);
+ dispatch(actions.removeNotification("selfxss-notification"));
+ }
+ };
+
+ // Create self-xss notification
+ dispatch(actions.appendNotification(
+ SELF_XSS_MSG,
+ "selfxss-notification",
+ null,
+ PriorityLevels.PRIORITY_WARNING_HIGH,
+ null,
+ removeCallback
+ ));
+
+ // Remove notification automatically when the user
+ // types "allow pasting".
+ function pasteKeyUpHandler() {
+ let value = inputField.value || inputField.textContent;
+ if (value.includes(SELF_XSS_OK)) {
+ dispatch(actions.removeNotification("selfxss-notification"));
+ inputField.removeEventListener("keyup", pasteKeyUpHandler);
+ WebConsoleUtils.usageCount = WebConsoleUtils.CONSOLE_ENTRY_THRESHOLD;
+ }
+ }
+
+ inputField.addEventListener("keyup", pasteKeyUpHandler);
+ }
+
+ // Rendering
+
+ render() {
+ const {
+ attachRefToHud,
+ hud,
+ notifications,
+ onFirstMeaningfulPaint,
+ serviceContainer,
+ } = this.props;
+
+ // Render the entire Console panel. The panel consists
+ // from the following parts:
+ // * FilterBar - Buttons & free text for content filtering
+ // * Content - List of logs & messages
+ // * SideBar - Object inspector
+ // * NotificationBox - Notifications for JSTerm (self-xss warning at the moment)
+ // * JSTerm - Input command line.
+ return (
+ div({
+ className: "webconsole-output-wrapper",
+ ref: node => {
+ this.node = node;
+ }},
+ FilterBar({
+ hidePersistLogsCheckbox: hud.isBrowserConsole,
+ serviceContainer: {
+ attachRefToHud
+ }
+ }),
+ ConsoleOutput({
+ serviceContainer,
+ onFirstMeaningfulPaint,
+ }),
+ SideBar({
+ serviceContainer,
+ }),
+ NotificationBox({
+ id: "webconsole-notificationbox",
+ notifications,
+ }),
+ JSTerm({
+ hud,
+ onPaste: this.onPaste,
+ }),
+ )
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ notifications: getAllNotifications(state),
+});
+
+const mapDispatchToProps = dispatch => ({
+ dispatch,
+});
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(App);
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -13,18 +13,16 @@ const PropTypes = require("devtools/clie
loader.lazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
-loader.lazyRequireGetter(this, "NotificationBox", "devtools/client/shared/components/NotificationBox", true);
-loader.lazyRequireGetter(this, "PriorityLevels", "devtools/client/shared/components/NotificationBox", true);
const l10n = require("devtools/client/webconsole/webconsole-l10n");
// Constants used for defining the direction of JSTerm input history navigation.
const HISTORY_BACK = -1;
const HISTORY_FORWARD = 1;
const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
@@ -48,25 +46,28 @@ const dom = require("devtools/client/sha
* @constructor
* @param object webConsoleFrame
* The WebConsoleFrame object that owns this JSTerm instance.
*/
class JSTerm extends Component {
static get propTypes() {
return {
hud: PropTypes.object.isRequired,
+ // Handler for clipboard 'paste' event (also used for 'drop' event).
+ onPaste: PropTypes.func,
};
}
constructor(props) {
super(props);
const {
hud,
} = props;
+
this.hud = hud;
this.hudId = this.hud.hudId;
this.inputHistoryCount = Services.prefs.getIntPref(PREF_INPUT_HISTORY_COUNT);
this._loadHistory();
/**
* Stores the data for the last completion.
* @type object
@@ -171,26 +172,16 @@ class JSTerm extends Component {
// Update the character width and height needed for the popup offset
// calculations.
this._updateCharSize();
this.inputNode.addEventListener("keypress", this._keyPress);
this.inputNode.addEventListener("input", this._inputEventHandler);
this.inputNode.addEventListener("keyup", this._inputEventHandler);
this.inputNode.addEventListener("focus", this._focusEventHandler);
-
- if (!this.hud.isBrowserConsole) {
- let okstring = l10n.getStr("selfxss.okstring");
- let msg = l10n.getFormatStr("selfxss.msg", [okstring]);
- this._onPaste = WebConsoleUtils.pasteHandlerGen(this.inputNode,
- this.getNotificationBox(), msg, okstring);
- this.inputNode.addEventListener("paste", this._onPaste);
- this.inputNode.addEventListener("drop", this._onPaste);
- }
-
this.hud.window.addEventListener("blur", this._blurEventHandler);
this.lastInputValue && this.setInputValue(this.lastInputValue);
this.focus();
}
shouldComponentUpdate() {
// XXX: For now, everything is handled in an imperative way and we only want React
@@ -1274,79 +1265,58 @@ class JSTerm extends Component {
style.width = "auto";
style.color = "transparent";
WebConsoleUtils.copyTextStyles(this.inputNode, tempLabel);
tempLabel.textContent = "x";
doc.documentElement.appendChild(tempLabel);
this._inputCharWidth = tempLabel.offsetWidth;
tempLabel.remove();
// Calculate the width of the chevron placed at the beginning of the input
- // box. Remove 4 more pixels to accomodate the padding of the popup.
+ // box. Remove 4 more pixels to accommodate the padding of the popup.
this._chevronWidth = +doc.defaultView.getComputedStyle(this.inputNode)
.paddingLeft.replace(/[^0-9.]/g, "") - 4;
}
- /**
- * Build the notification box as soon as needed.
- */
- getNotificationBox() {
- if (this._notificationBox) {
- return this._notificationBox;
- }
-
- let box = this.hud.document.getElementById("webconsole-notificationbox");
- let toolbox = gDevTools.getToolbox(this.hud.owner.target);
-
- // Render NotificationBox and assign priority levels to it.
- this._notificationBox = Object.assign(
- toolbox.ReactDOM.render(toolbox.React.createElement(NotificationBox), box),
- PriorityLevels);
- return this._notificationBox;
- }
-
destroy() {
this.clearCompletion();
this.webConsoleClient.clearNetworkRequests();
if (this.hud.outputNode) {
// We do this because it's much faster than letting React handle the ConsoleOutput
// unmounting.
this.hud.outputNode.innerHTML = "";
}
if (this.autocompletePopup) {
this.autocompletePopup.destroy();
this.autocompletePopup = null;
}
if (this.inputNode) {
- if (this._onPaste) {
- this.inputNode.removeEventListener("paste", this._onPaste);
- this.inputNode.removeEventListener("drop", this._onPaste);
- this._onPaste = null;
- }
-
this.inputNode.removeEventListener("keypress", this._keyPress);
this.inputNode.removeEventListener("input", this._inputEventHandler);
this.inputNode.removeEventListener("keyup", this._inputEventHandler);
this.inputNode.removeEventListener("focus", this._focusEventHandler);
this.hud.window.removeEventListener("blur", this._blurEventHandler);
}
this.hud = null;
}
render() {
if (this.props.hud.isBrowserConsole &&
!Services.prefs.getBoolPref("devtools.chrome.enabled")) {
return null;
}
- return [
- dom.div({id: "webconsole-notificationbox", key: "notification"}),
+ let {
+ onPaste
+ } = this.props;
+
+ return (
dom.div({
className: "jsterm-input-container",
key: "jsterm-container",
style: {direction: "ltr"},
"aria-live": "off",
},
dom.textarea({
className: "jsterm-complete-node devtools-monospace",
@@ -1360,15 +1330,17 @@ class JSTerm extends Component {
className: "jsterm-input-node devtools-monospace",
key: "input",
tabIndex: "0",
rows: "1",
"aria-autocomplete": "list",
ref: node => {
this.inputNode = node;
},
+ onPaste: onPaste,
+ onDrop: onPaste,
})
- ),
- ];
+ )
+ );
}
}
module.exports = JSTerm;
--- a/devtools/client/webconsole/components/moz.build
+++ b/devtools/client/webconsole/components/moz.build
@@ -3,16 +3,17 @@
# 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/.
DIRS += [
'message-types'
]
DevToolsModules(
+ 'App.js',
'CollapseButton.js',
'ConsoleOutput.js',
'ConsoleTable.js',
'FilterBar.js',
'FilterButton.js',
'FilterCheckbox.js',
'GripMessageBody.js',
'JSTerm.js',
--- a/devtools/client/webconsole/constants.js
+++ b/devtools/client/webconsole/constants.js
@@ -22,16 +22,18 @@ const actionTypes = {
NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
PERSIST_TOGGLE: "PERSIST_TOGGLE",
PRIVATE_MESSAGES_CLEAR: "PRIVATE_MESSAGES_CLEAR",
REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
SELECT_NETWORK_MESSAGE_TAB: "SELECT_NETWORK_MESSAGE_TAB",
SIDEBAR_CLOSE: "SIDEBAR_CLOSE",
SHOW_OBJECT_IN_SIDEBAR: "SHOW_OBJECT_IN_SIDEBAR",
TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
+ APPEND_NOTIFICATION: "APPEND_NOTIFICATION",
+ REMOVE_NOTIFICATION: "REMOVE_NOTIFICATION",
};
const prefs = {
PREFS: {
// Filter preferences only have the suffix since they can be used either for the
// webconsole or the browser console.
FILTER: {
ERROR: "filter.error",
--- a/devtools/client/webconsole/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output-wrapper.js
@@ -1,30 +1,26 @@
/* 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 { createElement, createFactory } = require("devtools/client/shared/vendor/react");
-const dom = require("devtools/client/shared/vendor/react-dom-factories");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const actions = require("devtools/client/webconsole/actions/index");
const { createContextMenu } = require("devtools/client/webconsole/utils/context-menu");
const { configureStore } = require("devtools/client/webconsole/store");
const { isPacketPrivate } = require("devtools/client/webconsole/utils/messages");
const { getAllMessagesById, getMessage } = require("devtools/client/webconsole/selectors/messages");
const Telemetry = require("devtools/client/shared/telemetry");
const EventEmitter = require("devtools/shared/event-emitter");
-const ConsoleOutput = createFactory(require("devtools/client/webconsole/components/ConsoleOutput"));
-const FilterBar = createFactory(require("devtools/client/webconsole/components/FilterBar"));
-const SideBar = createFactory(require("devtools/client/webconsole/components/SideBar"));
-const JSTerm = createFactory(require("devtools/client/webconsole/components/JSTerm"));
+const App = createFactory(require("devtools/client/webconsole/components/App"));
let store = null;
function NewConsoleOutputWrapper(parentNode, hud, toolbox, owner, document) {
EventEmitter.decorate(this);
this.parentNode = parentNode;
this.hud = hud;
@@ -38,16 +34,17 @@ function NewConsoleOutputWrapper(parentN
this.queuedMessageUpdates = [];
this.queuedRequestUpdates = [];
this.throttledDispatchPromise = null;
this._telemetry = new Telemetry();
store = configureStore(this.hud);
}
+
NewConsoleOutputWrapper.prototype = {
init: function() {
return new Promise((resolve) => {
const attachRefToHud = (id, node) => {
this.hud[id] = node;
};
// Focus the input line whenever the output area is clicked.
this.parentNode.addEventListener("click", (event) => {
@@ -206,38 +203,25 @@ NewConsoleOutputWrapper.prototype = {
let onNodeFrontSet = this.toolbox.selection
.setNodeFront(front, { reason: "console" });
return Promise.all([onNodeFrontSet, onInspectorUpdated]);
}
});
}
- let provider = createElement(
- Provider,
- { store },
- dom.div(
- {className: "webconsole-output-wrapper"},
- FilterBar({
- hidePersistLogsCheckbox: this.hud.isBrowserConsole,
- serviceContainer: {
- attachRefToHud
- }
- }),
- ConsoleOutput({
- serviceContainer,
- onFirstMeaningfulPaint: resolve
- }),
- SideBar({
- serviceContainer,
- }),
- JSTerm({
- hud: this.hud,
- }),
- ));
+ const app = App({
+ attachRefToHud,
+ serviceContainer,
+ hud,
+ onFirstMeaningfulPaint: resolve,
+ });
+
+ // Render the root Application component.
+ let provider = createElement(Provider, { store }, app);
this.body = ReactDOM.render(provider, this.parentNode);
});
},
dispatchMessageAdd: function(packet, waitForResponse) {
// Wait for the message to render to resolve with the DOM node.
// This is just for backwards compatibility with old tests, and should
// be removed once it's not needed anymore.
--- a/devtools/client/webconsole/reducers/index.js
+++ b/devtools/client/webconsole/reducers/index.js
@@ -4,15 +4,17 @@
* 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 { filters } = require("./filters");
const { messages } = require("./messages");
const { prefs } = require("./prefs");
const { ui } = require("./ui");
+const { notifications } = require("./notifications");
exports.reducers = {
filters,
messages,
prefs,
ui,
+ notifications,
};
--- a/devtools/client/webconsole/reducers/moz.build
+++ b/devtools/client/webconsole/reducers/moz.build
@@ -2,11 +2,12 @@
# 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/.
DevToolsModules(
'filters.js',
'index.js',
'messages.js',
+ 'notifications.js',
'prefs.js',
'ui.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/reducers/notifications.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 {
+ APPEND_NOTIFICATION,
+ REMOVE_NOTIFICATION,
+} = require("devtools/client/webconsole/constants");
+
+const {
+ appendNotification,
+ removeNotificationWithValue
+} = require("devtools/client/shared/components/NotificationBox");
+
+/**
+ * Create default initial state for this reducer. The state is composed
+ * from list of notifications.
+ */
+function getInitialState() {
+ return {
+ notifications: undefined,
+ };
+}
+
+/**
+ * Reducer function implementation. This reducers is responsible
+ * for maintaining list of notifications. It's consumed by
+ * `NotificationBox` component.
+ */
+function notifications(state = getInitialState(), action) {
+ switch (action.type) {
+ case APPEND_NOTIFICATION:
+ return append(state, action);
+ case REMOVE_NOTIFICATION:
+ return remove(state, action);
+ }
+
+ return state;
+}
+
+// Helpers
+
+function append(state, action) {
+ return appendNotification(state, action);
+}
+
+function remove(state, action) {
+ return removeNotificationWithValue(state.notifications, action.value);
+}
+
+// Exports
+
+module.exports = {
+ notifications,
+};
--- a/devtools/client/webconsole/selectors/moz.build
+++ b/devtools/client/webconsole/selectors/moz.build
@@ -1,11 +1,12 @@
# vim: set filetype=python:
# 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/.
DevToolsModules(
'filters.js',
'messages.js',
+ 'notifications.js',
'prefs.js',
'ui.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/selectors/notifications.js
@@ -0,0 +1,14 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
+
+function getAllNotifications(state) {
+ return state.notifications.notifications;
+}
+
+module.exports = {
+ getAllNotifications,
+};
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_selfxss.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_selfxss.js
@@ -12,16 +12,18 @@ XPCOMUtils.defineLazyServiceGetter(
"clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper"
);
const WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
const stringToCopy = "foobazbarBug642615";
add_task(async function() {
+ await pushPref("devtools.selfxss.count", 0);
+
let {jsterm} = await openNewTabAndConsole(TEST_URI);
jsterm.clearOutput();
ok(!jsterm.completeNode.value, "no completeNode.value");
jsterm.setInputValue("doc");
info("wait for completion value after typing 'docu'");
let onAutocompleteUpdated = jsterm.once("autocomplete-updated");
--- a/devtools/client/webconsole/utils.js
+++ b/devtools/client/webconsole/utils.js
@@ -23,16 +23,18 @@ const CONSOLE_ENTRY_THRESHOLD = 5;
exports.CONSOLE_WORKER_IDS = [
"SharedWorker",
"ServiceWorker",
"Worker"
];
var WebConsoleUtils = {
+ CONSOLE_ENTRY_THRESHOLD,
+
/**
* Wrap a string in an nsISupportsString object.
*
* @param string string
* @return nsISupportsString
*/
supportsString: function(string) {
let str = Cc["@mozilla.org/supports-string;1"]
@@ -120,17 +122,17 @@ var WebConsoleUtils = {
} catch (ex) {
return false;
}
},
/**
* Helper function to deduce the name of the provided function.
*
- * @param funtion function
+ * @param function function
* The function whose name will be returned.
* @return string
* Function name.
*/
getFunctionName: function(func) {
let name = null;
if (func.name) {
name = func.name;