/* 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/. */
/** * A WebConsoleUI instance is an interactive console initialized *per target* * that displays console log data as well as provides an interactive terminal to * manipulate the target's document content. * * The WebConsoleUI is responsible for the actual Web Console UI * implementation.
*/ class WebConsoleUI { /* * @param {WebConsole} hud: The WebConsole owner object.
*/
constructor(hud) { this.hud = hud; this.hudId = this.hud.hudId; this.isBrowserConsole = this.hud.isBrowserConsole;
if (this.isBrowserConsole) {
Services.prefs.addObserver(
PREF_BROWSERTOOLBOX_SCOPE, this._onScopePrefChanged
);
}
EventEmitter.decorate(this);
}
/** * Initialize the WebConsoleUI instance. * @return object * A promise object that resolves once the frame is ready to use.
*/
init() { if (this._initializer) { returnthis._initializer;
}
if (this.isBrowserConsole) { // Bug 1605763: // TargetCommand.startListening will start fetching additional targets // and may overload the Browser Console with loads of targets and resources. // We can call it from here, as `_attachTargets` is called after the UI is initialized. // Bug 1642599: // TargetCommand.startListening has to be called before: // - `_attachTargets`, in order to set TargetCommand.watcherFront which is used by ResourceWatcher.watchResources. // - `ConsoleCommands`, in order to set TargetCommand.targetFront which is wrapped by hud.currentTarget
await this.hud.commands.targetCommand.startListening(); if (this._destroyed) { return;
}
}
await this.wrapper.init(); if (this._destroyed) { return;
}
// Bug 1605763: It's important to call _attachTargets once the UI is initialized, as // it may overload the Browser Console with many updates. // It is also important to do it only after the wrapper is initialized, // otherwise its `store` will be null while we already call a few dispatch methods // from onResourceAvailable
await this._attachTargets(); if (this._destroyed) { return;
}
// `_attachTargets` will process resources and throttle some actions // Wait for these actions to be dispatched before reporting that the // console is initialized. Otherwise `showToolbox` will resolve before // all already existing console messages are displayed.
await this.wrapper.waitAsyncDispatches(); this._initNotifications();
})();
if (this.networkDataProvider) { this.networkDataProvider.destroy(); this.networkDataProvider = null;
}
// Nullify `hud` last as it nullify also target which is used on destroy this.window = this.hud = this.wrapper = null;
}
/** * Clear the Web Console output. * * This method emits the "messages-cleared" notification. * * @param boolean clearStorage * True if you want to clear the console messages storage associated to * this Web Console. * @param object event * If the event exists, calls preventDefault on it.
*/
async clearOutput(clearStorage, event) { if (event) {
event.preventDefault();
} if (this.wrapper) { this.wrapper.dispatchMessagesClear();
}
if (clearStorage) {
await this.clearMessagesCache();
} this.emitForTests("messages-cleared");
}
async clearMessagesCache() { if (this._destroyed) { return;
}
// This can be called during console destruction and getAllFronts would reject in such case. try { const consoleFronts = await this.hud.commands.targetCommand.getAllFronts( this.hud.commands.targetCommand.ALL_TYPES, "console"
); const promises = []; for (const consoleFront of consoleFronts) {
promises.push(consoleFront.clearMessagesCacheAsync());
}
await Promise.all(promises); this.emitForTests("messages-cache-cleared");
} catch (e) {
console.warn("Exception in clearMessagesCache", e);
}
}
/** * Remove all of the private messages from the Web Console output. * * This method emits the "private-messages-cleared" notification.
*/
clearPrivateMessages() { if (this._destroyed) { return;
}
/** * Connect to the server using the remote debugging protocol. * * @private * @return object * A promise object that is resolved/reject based on the proxies connections.
*/
async _attachTargets() { const { commands, resourceCommand } = this.hud; this.networkDataProvider = new FirefoxDataProvider({
commands,
actions: {
updateRequest: (id, data) => this.wrapper.batchedRequestUpdates({ id, data }),
},
owner: this,
});
// Listen for all target types, including: // - frames, in order to get the parent process target // which is considered as a frame rather than a process. // - workers, for similar reason. When we open a toolbox // for just a worker, the top level target is a worker target. // - processes, as we want to spawn additional proxies for them.
await commands.targetCommand.watchTargets({
types: this.hud.commands.targetCommand.ALL_TYPES,
onAvailable: this._onTargetAvailable,
onDestroyed: this._onTargetDestroyed,
});
// CSS Warnings are only enabled when the user explicitely requested to show them // as it can slow down page load. const shouldShowCssWarnings = this.wrapper.getFilterState(FILTERS.CSS); if (shouldShowCssWarnings) { this._watchedResources.push(resourceCommand.TYPES.CSS_MESSAGE);
}
// When opening a worker toolbox from about:debugging, // we do not instantiate any Watcher actor yet and would throw here. // But even once we do, we wouldn't support network inspection anyway. if (commands.targetCommand.hasTargetWatcherSupport()) { const networkFront = await commands.watcherFront.getNetworkParentActor(); // There is no way to view response bodies from the Browser Console, so do // not waste the memory. const saveBodies =
!this.isBrowserConsole &&
Services.prefs.getBoolPref( "devtools.netmonitor.saveRequestAndResponseBodies"
);
await networkFront.setSaveRequestAndResponseBodies(saveBodies);
}
}
async stopWatchingNetworkResources() { if (this._destroyed) { return;
}
handleDocumentEvent(resource) { // Only consider top level document, and ignore remote iframes top document if (!resource.targetFront.isTopLevel) { return;
}
if (resource.name == "will-navigate") { this.handleWillNavigate({
timeStamp: resource.time,
url: resource.newURI,
});
} elseif (resource.name == "dom-complete") { this.handleNavigated({
hasNativeConsoleAPI: resource.hasNativeConsoleAPI,
});
} // For now, ignore all other DOCUMENT_EVENT's.
}
/** * Handler for when the page is done loading. * * @param Boolean hasNativeConsoleAPI * True if the `console` object is the native one and hasn't been overloaded by a custom * object by the page itself.
*/
async handleNavigated({ hasNativeConsoleAPI }) { // Updates instant evaluation on page navigation this.wrapper.dispatchUpdateInstantEvaluationResultForCurrentExpression();
// Wait for completion of any async dispatch before notifying that the console // is fully updated after a page reload
await this.wrapper.waitAsyncDispatches();
if (!hasNativeConsoleAPI) { this.logWarningAboutReplacedAPI();
}
/** * Called when the CSS Warning filter is enabled, in order to start observing for them in the backend.
*/
async watchCssMessages() { const { resourceCommand } = this.hud; if (this._watchedResources.includes(resourceCommand.TYPES.CSS_MESSAGE)) { return;
}
await resourceCommand.watchResources([resourceCommand.TYPES.CSS_MESSAGE], {
onAvailable: this._onResourceAvailable,
}); this._watchedResources.push(resourceCommand.TYPES.CSS_MESSAGE);
}
// eslint-disable-next-line complexity
_onResourceAvailable(resources) { if (this._destroyed) { return;
}
const messages = []; for (const resource of resources) { const { TYPES } = this.hud.resourceCommand; if (resource.resourceType === TYPES.DOCUMENT_EVENT) { this.handleDocumentEvent(resource); continue;
} if (resource.resourceType == TYPES.LAST_PRIVATE_CONTEXT_EXIT) { // Private messages only need to be removed from the output in Browser Console/Browser Toolbox // (but in theory this resource should only be send from parent process watchers) if (this.isBrowserConsole || this.isBrowserToolboxConsole) { this.clearPrivateMessages();
} continue;
} // Ignore messages forwarded from content processes if we're in fission browser toolbox. if (
!this.wrapper ||
((resource.resourceType === TYPES.ERROR_MESSAGE ||
resource.resourceType === TYPES.CSS_MESSAGE) &&
resource.pageError?.isForwardedFromContentProcess &&
(this.isBrowserToolboxConsole || this.isBrowserConsole))
) { continue;
}
// Don't show messages emitted from a private window before the Browser Console was // opened to avoid leaking data from past usage of the browser (e.g. content message // from now closed private tabs) if (
(this.isBrowserToolboxConsole || this.isBrowserConsole) &&
resource.isAlreadyExistingResource &&
(resource.pageError?.private || resource.private)
) { continue;
}
_onNetworkResourceUpdated(updates) { if (this._destroyed) { return;
}
const messageUpdates = []; for (const { resource } of updates) { if (
resource.resourceType == this.hud.resourceCommand.TYPES.NETWORK_EVENT
) { this.networkDataProvider?.onNetworkResourceUpdated(resource);
messageUpdates.push(resource);
}
} this.wrapper.dispatchMessagesUpdate(messageUpdates);
}
/** * Called any time a new target is available. * i.e. it was already existing or has just been created. * * @private
*/
async _onTargetAvailable() { // onTargetAvailable is a mandatory argument for watchTargets, // we still define it solely for being able to use onTargetDestroyed.
}
_onTargetDestroyed({ targetFront, isModeSwitching }) { // Don't try to do anything if the WebConsole is being destroyed if (this._destroyed) { return;
}
// We only want to remove messages from a target destroyed when we're switching mode // in the Browser Console/Browser Toolbox Console. // For regular cases, we want to keep the message history (the output will still be // cleared when the top level target navigates, if "Persist Logs" isn't true, via handleWillNavigate) if (isModeSwitching) { this.wrapper.dispatchTargetMessagesRemove(targetFront);
}
}
// Initialize module loader and load all the WebConsoleWrapper. The entire code-base // doesn't need any extra privileges and runs entirely in content scope. const WebConsoleWrapper = BrowserLoader({
baseURI: "resource://devtools/client/webconsole/",
window: this.window,
}).require("resource://devtools/client/webconsole/webconsole-wrapper.js");
this.wrapper = new WebConsoleWrapper( this.outputNode, this,
toolbox, this.document
);
_initOutputSyntaxHighlighting() { // Given a DOM node, we syntax highlight identically to how the input field // looks. See https://codemirror.net/demo/runmode.html; const syntaxHighlightNode = node => { const editor = this.jsterm && this.jsterm.editor; if (node && editor) {
node.classList.add("cm-s-mozilla");
editor.CodeMirror.runMode(
node.textContent, "application/javascript",
node
);
}
};
// Use a Custom Element to handle syntax highlighting to avoid // dealing with refs or innerHTML from React. const win = this.window;
win.customElements.define( "syntax-highlighted", classextends win.HTMLElement {
connectedCallback() { if (!this.connected) { this.connected = true;
syntaxHighlightNode(this);
// Highlight Again when the innerText changes // We remove the listener before running codemirror mode and add // it again to capture text changes this.observer = new win.MutationObserver((mutations, observer) => {
observer.disconnect();
syntaxHighlightNode(this);
observer.observe(this, { childList: true });
});
if (this.isBrowserConsole) { // Make sure keyboard shortcuts work immediately after opening // the Browser Console (Bug 1461366). this.window.focus();
shortcuts.on(
l10n.getStr("webconsole.close.key"), this.window.close.bind(this.window)
);
ZoomKeys.register(this.window, shortcuts);
/* This is the same as DevelopmentHelpers.quickRestart, but it runs in all * builds (even official). This allows a user to do a restart + session restore * with Ctrl+Shift+J (open Browser Console) and then Ctrl+Alt+R (restart).
*/
shortcuts.on("CmdOrCtrl+Alt+R", () => { this.hud.commands.targetCommand.reloadTopLevelTarget();
});
} elseif (Services.prefs.getBoolPref(PREF_SIDEBAR_ENABLED)) {
shortcuts.on("Esc", () => { this.wrapper.dispatchSidebarClose(); if (this.jsterm) { this.jsterm.focus();
}
});
}
}
/** * Sets the focus to JavaScript input field when the web console tab is * selected or when there is a split console present. * @private
*/
_onPanelSelected() { // We can only focus when we have the jsterm reference. This is fine because if the // jsterm is not mounted yet, it will be focused in JSTerm's componentDidMount. if (this.jsterm) { this.jsterm.focus();
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.22Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.