BrowserTabList.prototype.getTab = function ({ outerWindowID, tabId }) { if (typeof outerWindowID == "number") { // First look for in-process frames with this ID let window = Services.wm.getOuterWindowWithId(outerWindowID); // Safety check to prevent debugging top level window via getTab if (window instanceof Ci.nsIDOMChromeWindow) { return promise.reject({ error: "forbidden", message: "Window with outerWindowID '" + outerWindowID + "' is chrome" }); } if (window) { let iframe = window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils) .containerElement; if (iframe) { return this._getActorForBrowser(iframe); } } // Then also look on registered <xul:browsers> when using outerWindowID for // OOP tabs for (let browser of this._getBrowsers()) { if (browser.outerWindowID == outerWindowID) { return this._getActorForBrowser(browser); } } return promise.reject({ error: "noTab", message: "Unable to find tab with outerWindowID '" + outerWindowID + "'" }); } else if (typeof tabId == "number") { // Tabs OOP for (let browser of this._getBrowsers()) { if (browser.frameLoader.tabParent && browser.frameLoader.tabParent.tabId === tabId) { return this._getActorForBrowser(browser); } } return promise.reject({ error: "noTab", message: "Unable to find tab with tabId '" + tabId + "'" }); } let topXULWindow = Services.wm.getMostRecentWindow( DebuggerServer.chromeWindowType); if (topXULWindow) { let selectedBrowser = this._getSelectedBrowser(topXULWindow); return this._getActorForBrowser(selectedBrowser); } return promise.reject({ error: "noTab", message: "Unable to find any selected browser" }); };
/** * Retrieve the most recent chrome window. */ function _getTopWindow() { // Try the main application window, such as a browser window. let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); if (win && win.openWebLinkIn && win.openTrustedLinkIn) { return win; } // For non-browser cases like Browser Toolbox, try any chrome window. win = Services.wm.getMostRecentWindow(null); if (win && win.openWebLinkIn && win.openTrustedLinkIn) { return win; } return null; }
exports.viewSourceInScratchpad = Task.async(function* (sourceURL, sourceLine) { // Check for matching top level scratchpad window. let wins = Services.wm.getEnumerator("devtools:scratchpad"); while (wins.hasMoreElements()) { let win = wins.getNext(); if (!win.closed && win.Scratchpad.uniqueName === sourceURL) { win.focus(); win.Scratchpad.editor.setCursor({ line: sourceLine, ch: 0 }); return; } } // For scratchpads within toolbox for (let [, toolbox] of gDevTools) { let scratchpadPanel = toolbox.getPanel("scratchpad"); if (scratchpadPanel) { let { scratchpad } = scratchpadPanel; if (scratchpad.uniqueName === sourceURL) { toolbox.selectTool("scratchpad"); toolbox.raise(); scratchpad.editor.focus(); scratchpad.editor.setCursor({ line: sourceLine, ch: 0 }); return; } } } });
BrowserTabList.prototype._listenToMediatorIf = function (shouldListen) { if (!shouldListen !== !this._listeningToMediator) { let op = shouldListen ? "addListener" : "removeListener"; Services.wm[op](this); this._listeningToMediator = shouldListen; } };
win.addEventListener("load", function() { if (win.document.documentElement.getAttribute("id") != "commonDialog") { return; } // Found the window promptWindow = win; Services.wm.removeListener(windowListener); }, {once: true});
openInBrowser: function (url) { // Open a URL in a Firefox window let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); if (mainWindow) { mainWindow.openUILinkIn(url, "tab"); mainWindow.focus() } else { window.open(url); } },
_checkListening: function() { let shouldListenToMediator = ((this._onListChanged && this._mustNotify) || this._actorByBrowser.size > 0); if (this._listeningToMediator !== shouldListenToMediator) { let op = shouldListenToMediator ? "addListener" : "removeListener"; Services.wm[op](this); this._listeningToMediator = shouldListenToMediator; } },
_getTopWindow: function() { let winIter = Services.wm.getZOrderDOMWindowEnumerator(null, true); while (winIter.hasMoreElements()) { let win = winIter.getNext(); if (this._checkedWindows.has(appShellDOMWindowType(win))) { // This is one of our windows, return it return win; } } return null; },
ChromeActor.prototype.postNest = function (nestData) { // Enable events in all open windows. let e = Services.wm.getEnumerator(null); while (e.hasMoreElements()) { let win = e.getNext(); let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); windowUtils.resumeTimeouts(); windowUtils.suppressEventHandling(false); } };
/** * Send a debugger shutdown event to all main windows. */ function sendShutdownEvent() { let windowTypes = DebuggerServer.RemoteDebuggerServer.chromeWindowTypes; for (let type of windowTypes) { let enumerator = Services.wm.getEnumerator(type); while (enumerator.hasMoreElements()) { let win = enumerator.getNext(); let evt = win.document.createEvent("Event"); evt.initEvent("Debugger:Shutdown", true, false); win.document.documentElement.dispatchEvent(evt); } } }
/** * Creates a TabActor for debugging all the chrome content in the * current process. Most of the implementation is inherited from TabActor. * ChromeActor is a child of RootActor, it can be instanciated via * RootActor.getProcess request. * ChromeActor exposes all tab actors via its form() request, like TabActor. * * History lecture: * All tab actors used to also be registered as global actors, * so that the root actor was also exposing tab actors for the main process. * Tab actors ended up having RootActor as parent actor, * but more and more features of the tab actors were relying on TabActor. * So we are now exposing a process actor that offers the same API as TabActor * by inheriting its functionality. * Global actors are now only the actors that are meant to be global, * and are no longer related to any specific scope/document. * * @param connection DebuggerServerConnection * The connection to the client. */ function ChromeActor(connection) { TabActor.call(this, connection); // This creates a Debugger instance for chrome debugging all globals. this.makeDebugger = makeDebugger.bind(null, { findDebuggees: dbg => dbg.findAllGlobals(), shouldAddNewGlobalAsDebuggee: () => true }); // Ensure catching the creation of any new content docshell this.listenForNewDocShells = true; // Defines the default docshell selected for the tab actor let window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType); // Default to any available top level window if there is no expected window // (for example when we open firefox with -webide argument) if (!window) { window = Services.wm.getMostRecentWindow(null); } // We really want _some_ window at least, so fallback to the hidden window if // there's nothing else (such as during early startup). if (!window) { try { window = Services.appShell.hiddenDOMWindow; } catch (e) { // On XPCShell, the above line will throw. } } // On XPCShell, there is no window/docshell let docShell = window ? window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDocShell) : null; Object.defineProperty(this, "docShell", { value: docShell, configurable: true }); }
BrowserTabList.prototype.getList = function () { let topXULWindow = Services.wm.getMostRecentWindow( DebuggerServer.chromeWindowType); let selectedBrowser = null; if (topXULWindow) { selectedBrowser = this._getSelectedBrowser(topXULWindow); } // As a sanity check, make sure all the actors presently in our map get // picked up when we iterate over all windows' tabs. let initialMapSize = this._actorByBrowser.size; this._foundCount = 0; // To avoid mysterious behavior if tabs are closed or opened mid-iteration, // we update the map first, and then make a second pass over it to yield // the actors. Thus, the sequence yielded is always a snapshot of the // actors that were live when we began the iteration. let actorPromises = []; for (let browser of this._getBrowsers()) { let selected = browser === selectedBrowser; actorPromises.push( this._getActorForBrowser(browser) .then(actor => { // Set the 'selected' properties on all actors correctly. actor.selected = selected; return actor; }, e => { if (e.error === "tabDestroyed") { // Return null if a tab was destroyed while retrieving the tab list. return null; } // Forward unexpected errors. throw e; }) ); } if (this._testing && initialMapSize !== this._foundCount) { throw new Error("_actorByBrowser map contained actors for dead tabs"); } this._mustNotify = true; this._checkListening(); return promise.all(actorPromises).then(values => { // Filter out null values if we received a tabDestroyed error. return values.filter(value => value != null); }); };
reportError: function (l10nProperty, ...l10nArgs) { let win = Services.wm.getMostRecentWindow("devtools:webide"); if (win) { win.UI.reportError(l10nProperty, ...l10nArgs); } else { let text; if (l10nArgs.length > 0) { text = Strings.formatStringFromName(l10nProperty, l10nArgs, l10nArgs.length); } else { text = Strings.GetStringFromName(l10nProperty); } console.error(text); } },
exports.viewSource = Task.async(function* (toolbox, sourceURL, sourceLine) { // Attempt to access view source via a browser first, which may display it in // a tab, if enabled. let browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); if (browserWin && browserWin.BrowserViewSourceOfDocument) { return browserWin.BrowserViewSourceOfDocument({ URL: sourceURL, lineNumber: sourceLine }); } let utils = toolbox.gViewSourceUtils; utils.viewSource(sourceURL, null, toolbox.doc, sourceLine || 0); return null; });
Client.defaultSendOOB = ({ authResult, oob }) => { // Only show in the PENDING state if (authResult != AuthenticationResult.PENDING) { throw new Error("Expected PENDING result, got " + authResult); } let title = L10N.getStr("clientSendOOBTitle"); let header = L10N.getStr("clientSendOOBHeader"); let hashMsg = L10N.getFormatStr("clientSendOOBHash", oob.sha256); let token = oob.sha256.replace(/:/g, "").toLowerCase() + oob.k; let tokenMsg = L10N.getFormatStr("clientSendOOBToken", token); let msg = `${header}\n\n${hashMsg}\n${tokenMsg}`; let prompt = Services.prompt; let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_CANCEL; // Listen for the window our prompt opens, so we can close it programatically let promptWindow; let windowListener = { onOpenWindow(xulWindow) { let win = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow); win.addEventListener("load", function() { if (win.document.documentElement.getAttribute("id") != "commonDialog") { return; } // Found the window promptWindow = win; Services.wm.removeListener(windowListener); }, {once: true}); }, onCloseWindow() {}, }; Services.wm.addListener(windowListener); // nsIPrompt is typically a blocking API, so |executeSoon| to get around this DevToolsUtils.executeSoon(() => { prompt.confirmEx(null, title, msg, flags, null, null, null, null, { value: false }); }); return { close() { if (!promptWindow) { return; } promptWindow.document.documentElement.acceptDialog(); promptWindow = null; } }; };
/** * Opens given request in a new tab. */ function openRequestInTab(url, requestPostData) { let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); let rawData = requestPostData ? requestPostData.postData : null; let postData; if (rawData && rawData.text) { let stringStream = getInputStreamFromString(rawData.text); postData = Cc["@mozilla.org/network/mime-input-stream;1"] .createInstance(Ci.nsIMIMEInputStream); postData.addHeader("Content-Type", "application/x-www-form-urlencoded"); postData.setData(stringStream); } win.gBrowser.selectedTab = win.gBrowser.addTab(url, null, null, postData); }
screenshotToDataURL: method(function() { let window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType); let canvas = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); let width = window.innerWidth; let height = window.innerHeight; canvas.setAttribute('width', width); canvas.setAttribute('height', height); let context = canvas.getContext('2d'); let flags = context.DRAWWINDOW_DRAW_CARET | context.DRAWWINDOW_DRAW_VIEW | context.DRAWWINDOW_USE_WIDGET_LAYERS; context.drawWindow(window, 0, 0, width, height, 'rgb(255,255,255)', flags); let dataURL = canvas.toDataURL('image/png') return new LongStringActor(this.conn, dataURL); }, {request: {},response: { value: RetVal("longstring")}}),
isMessageRelevant: function (message) { let workerType = WebConsoleUtils.getWorkerType(message); if (this.window && workerType === "ServiceWorker") { // For messages from Service Workers, message.ID is the // scope, which can be used to determine whether it's controlling // a window. let scope = message.ID; if (!swm.shouldReportToWindow(this.window, scope)) { return false; } } if (this.window && !workerType) { let msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID); if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) { // Not the same window! return false; } } if (this.addonId) { // ConsoleAPI.jsm messages contains a consoleID, (and it is currently // used in Addon SDK add-ons), the standard 'console' object // (which is used in regular webpages and in WebExtensions pages) // contains the originAttributes of the source document principal. // Filtering based on the originAttributes used by // the Console API object. if (message.originAttributes && message.originAttributes.addonId == this.addonId) { return true; } // Filtering based on the old-style consoleID property used by // the legacy Console JSM module. if (message.consoleID && message.consoleID == `addon/${this.addonId}`) { return true; } return false; } return true; },
screenshotToDataURL: function () { let window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType); var devicePixelRatio = window.devicePixelRatio; let canvas = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); let width = window.innerWidth; let height = window.innerHeight; canvas.setAttribute("width", Math.round(width * devicePixelRatio)); canvas.setAttribute("height", Math.round(height * devicePixelRatio)); let context = canvas.getContext("2d"); let flags = context.DRAWWINDOW_DRAW_CARET | context.DRAWWINDOW_DRAW_VIEW | context.DRAWWINDOW_USE_WIDGET_LAYERS; context.scale(devicePixelRatio, devicePixelRatio); context.drawWindow(window, 0, 0, width, height, "rgb(255,255,255)", flags); let dataURL = canvas.toDataURL("image/png"); return new LongStringActor(this.conn, dataURL); },
addMessageListener("addFrame", function (aMessage) { let win = Services.wm.getMostRecentWindow("navigator:browser"); let doc = win.document; let frame = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); frame.setAttribute("mozbrowser", "true"); if (aMessage.mozapp) { frame.setAttribute("mozapp", aMessage.mozapp); } if (aMessage.remote) { frame.setAttribute("remote", aMessage.remote); } if (aMessage.src) { frame.setAttribute("src", aMessage.src); } doc.documentElement.appendChild(frame); Frames.push(frame); sendAsyncMessage("frameAdded"); });
getList: function() { let topWindow = this._getTopWindow(); // Look for all browser elements in all the windows we care about for (let winName of this._checkedWindows) { let winIter = Services.wm.getEnumerator(winName); while (winIter.hasMoreElements()) { let win = winIter.getNext(); let foundSelected = false; // Check for browser elements and create a tab actor for each. // This will catch content tabs, the message reader and the // multi-message reader. for (let browser of win.document.getElementsByTagName("browser")) { if (browser.currentURI.spec == "about:blank") { // about:blank is not particularly interesting. Don't // add it to the list. continue; } let actor = this._actorByBrowser.get(browser); if (!actor) { actor = new BrowserTabActor(this._connection, browser, null); this._actorByBrowser.set(browser, actor); } // Select the first visible browser in the top xul // window. let bo = browser.boxObject; actor.selected = foundSelected = win == topWindow && !foundSelected && bo.height > 0 && bo.width > 0; } } } this._mustNotify = true; this._checkListening(); return Promise.resolve([...this._actorByBrowser.values()]); },
observe: function(message) { if (!this.listener) { return; } if (this.window) { if (!(message instanceof Ci.nsIScriptError) || !message.outerWindowID || !this.isCategoryAllowed(message.category)) { return; } const errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID); if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) { return; } } this.listener.onConsoleServiceMessage(message); },
connect: function(connection) { const win = Services.wm.getMostRecentWindow("devtools:webide"); if (!win) { return promise.reject(new Error("No WebIDE window found")); } const ret = {value: connection.host + ":" + connection.port}; const title = Strings.GetStringFromName("remote_runtime_promptTitle"); const message = Strings.GetStringFromName("remote_runtime_promptMessage"); const ok = Services.prompt.prompt(win, title, message, ret, null, {}); const [host, port] = ret.value.split(":"); if (!ok) { return promise.reject({canceled: true}); } if (!host || !port) { return promise.reject(new Error("Invalid host or port")); } connection.host = host; connection.port = port; connection.connect(); return promise.resolve(); },
/** * This file contains all of the privileged browser-specific functionality. This helps * keep a clear separation between the privileged and non-privileged client code. It * is also helpful in being able to mock out browser behavior for tests, without * worrying about polluting the browser environment. */ /** * Once a profile is received from the actor, it needs to be opened up in perf.html * to be analyzed. This function opens up perf.html into a new browser tab, and injects * the profile via a frame script. * * @param {object} profile - The Gecko profile. * @param {function} getSymbolTableCallback - A callback function with the signature * (debugName, breakpadId) => Promise<SymbolTableAsTuple>, which will be invoked * when perf-html.io sends SYMBOL_TABLE_REQUEST_EVENT messages to us. This function * should obtain a symbol table for the requested binary and resolve the returned * promise with it. */ function receiveProfile(profile, getSymbolTableCallback) { // Find the most recently used window, as the DevTools client could be in a variety // of hosts. const win = Services.wm.getMostRecentWindow("navigator:browser"); if (!win) { throw new Error("No browser window"); } const browser = win.gBrowser; Services.focus.activeWindow = win; const baseUrl = Services.prefs.getStringPref(UI_BASE_URL_PREF, UI_BASE_URL_DEFAULT); const tab = browser.addWebTab(`${baseUrl}/from-addon`, { triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({ userContextId: browser.contentPrincipal.userContextId, }), }); browser.selectedTab = tab; const mm = tab.linkedBrowser.messageManager; mm.loadFrameScript( "chrome://devtools/content/performance-new/frame-script.js", false ); mm.sendAsyncMessage(TRANSFER_EVENT, profile); mm.addMessageListener(SYMBOL_TABLE_REQUEST_EVENT, e => { const { debugName, breakpadId } = e.data; getSymbolTableCallback(debugName, breakpadId).then(result => { const [addr, index, buffer] = result; mm.sendAsyncMessage(SYMBOL_TABLE_RESPONSE_EVENT, { status: "success", debugName, breakpadId, result: [addr, index, buffer], }); }, error => { mm.sendAsyncMessage(SYMBOL_TABLE_RESPONSE_EVENT, { status: "error", debugName, breakpadId, error: `${error}`, }); }); }); }
promise.then(folder => { // Create subfolder with fs-friendly name of project const subfolder = projectName.replace(/[\\/:*?"<>|]/g, "").toLowerCase(); const win = Services.wm.getMostRecentWindow("devtools:webide"); folder.append(subfolder); try { folder.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); } catch (e) { win.UI.reportError("error_folderCreationFailed"); window.close(); return; } // Download boilerplate zip const template = gTemplateList[templatelistNode.selectedIndex]; const source = template.file; const target = folder.clone(); target.append(subfolder + ".zip"); Downloads.fetch(source, target).then(() => { ZipUtils.extractFiles(target, folder); target.remove(false); AppProjects.addPackaged(folder).then((project) => { window.arguments[0].location = project.location; AppManager.validateAndUpdateProject(project).then(() => { if (project.manifest) { project.manifest.name = projectName; AppManager.writeManifest(project).then(() => { AppManager.validateAndUpdateProject(project).then( () => { window.close(); }, bail); }, bail); } else { bail("Manifest not found"); } }, bail); }, bail); }, bail); }, bail);
/** * The MdnDocsWidget is used by tooltip code that needs to display docs * from MDN in a tooltip. * * In the constructor, the widget does some general setup that's not * dependent on the particular item we need docs for. * * After that, when the tooltip code needs to display docs for an item, it * asks the widget to retrieve the docs and update the document with them. * * @param {Element} tooltipContainer * A DOM element where the MdnDocs widget markup should be created. */ function MdnDocsWidget(tooltipContainer) { EventEmitter.decorate(this); tooltipContainer.innerHTML = `<header> <h1 class="mdn-property-name theme-fg-color5"></h1> </header> <div class="mdn-property-info"> <div class="mdn-summary"></div> <pre class="mdn-syntax devtools-monospace"></pre> </div> <footer> <a class="mdn-visit-page theme-link" href="#">Visit MDN (placeholder)</a> </footer>`; // fetch all the bits of the document that we will manipulate later this.elements = { heading: tooltipContainer.querySelector(".mdn-property-name"), summary: tooltipContainer.querySelector(".mdn-summary"), syntax: tooltipContainer.querySelector(".mdn-syntax"), info: tooltipContainer.querySelector(".mdn-property-info"), linkToMdn: tooltipContainer.querySelector(".mdn-visit-page") }; // get the localized string for the link text this.elements.linkToMdn.textContent = L10N.getStr("docsTooltip.visitMDN"); // force using LTR because we use the en-US version of MDN tooltipContainer.setAttribute("dir", "ltr"); // listen for clicks and open in the browser window instead let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); this.elements.linkToMdn.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); mainWindow.openUILinkIn(e.target.href, "tab"); this.emit("visitlink"); }); }
/** * The MdnDocsWidget is used by tooltip code that needs to display docs * from MDN in a tooltip. The tooltip code loads a document that contains the * basic structure of a docs tooltip (loaded from mdn-docs-frame.xhtml), * and passes this document into the widget's constructor. * * In the constructor, the widget does some general setup that's not * dependent on the particular item we need docs for. * * After that, when the tooltip code needs to display docs for an item, it * asks the widget to retrieve the docs and update the document with them. * * @param {Document} tooltipDocument * A DOM document. The widget expects the document to have a particular * structure. */ function MdnDocsWidget(tooltipDocument) { // fetch all the bits of the document that we will manipulate later this.elements = { heading: tooltipDocument.getElementById("property-name"), summary: tooltipDocument.getElementById("summary"), syntax: tooltipDocument.getElementById("syntax"), info: tooltipDocument.getElementById("property-info"), linkToMdn: tooltipDocument.getElementById("visit-mdn-page") }; this.doc = tooltipDocument; // get the localized string for the link text this.elements.linkToMdn.textContent = l10n.strings.GetStringFromName("docsTooltip.visitMDN"); // listen for clicks and open in the browser window instead let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); this.elements.linkToMdn.addEventListener("click", function (e) { e.stopPropagation(); e.preventDefault(); mainWindow.openUILinkIn(e.target.href, "tab", { inBackground: true }); }); }
menu.push({ id: "request-list-context-perf", label: L10N.getStr("netmonitor.context.perfTools"), accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"), visible: this.sortedRequests.size > 0, click: () => this.openStatistics(true) }); return showMenu(event, menu); }, /** * Opens selected item in a new tab. */ openRequestInTab() { let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType); win.openUILinkIn(this.selectedRequest.url, "tab", { relatedToCurrent: true }); }, /** * Copy the request url from the currently selected item. */ copyUrl() { copyString(this.selectedRequest.url); }, /** * Copy the request url query string parameters from the currently * selected item. */ copyUrlParams() {
/** * Browser-specific actors. */ /** * Yield all windows of type |windowType|, from the oldest window to the * youngest, using nsIWindowMediator::getEnumerator. We're usually * interested in "navigator:browser" windows. */ function* allAppShellDOMWindows(windowType) { let e = Services.wm.getEnumerator(windowType); while (e.hasMoreElements()) { yield e.getNext(); } }
_getSettingsService: function () { let win = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType); return win.navigator.mozSettings; },