diff options
Diffstat (limited to 'toolkit/components/viewsource/ViewSourceBrowser.jsm')
-rw-r--r-- | toolkit/components/viewsource/ViewSourceBrowser.jsm | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/toolkit/components/viewsource/ViewSourceBrowser.jsm b/toolkit/components/viewsource/ViewSourceBrowser.jsm new file mode 100644 index 000000000..0623e244a --- /dev/null +++ b/toolkit/components/viewsource/ViewSourceBrowser.jsm @@ -0,0 +1,331 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + +/* 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/. */ + +const { utils: Cu, interfaces: Ci, classes: Cc } = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Deprecated", + "resource://gre/modules/Deprecated.jsm"); + +const BUNDLE_URL = "chrome://global/locale/viewSource.properties"; + +const FRAME_SCRIPT = "chrome://global/content/viewSource-content.js"; + +this.EXPORTED_SYMBOLS = ["ViewSourceBrowser"]; + +// Keep a set of browsers we've seen before, so we can load our frame script as +// needed into any new ones. +var gKnownBrowsers = new WeakSet(); + +/** + * ViewSourceBrowser manages the view source <browser> from the chrome side. + * It's companion frame script, viewSource-content.js, needs to be loaded as a + * frame script into the browser being managed. + * + * For a view source window using viewSource.xul, the script viewSource.js in + * the window extends an instance of this with more window specific functions. + * The page script takes care of loading the companion frame script. + * + * For a view source tab (or some other non-window case), an instance of this is + * created by viewSourceUtils.js to wrap the <browser>. The frame script will + * be loaded by this module at construction time. + */ +this.ViewSourceBrowser = function ViewSourceBrowser(aBrowser) { + this._browser = aBrowser; + this.init(); +} + +ViewSourceBrowser.prototype = { + /** + * The <browser> that will be displaying the view source content. + */ + get browser() { + return this._browser; + }, + + /** + * Holds the value of the last line found via the "Go to line" + * command, to pre-populate the prompt the next time it is + * opened. + */ + lastLineFound: null, + + /** + * These are the messages that ViewSourceBrowser will listen for + * from the frame script it injects. Any message names added here + * will automatically have ViewSourceBrowser listen for those messages, + * and remove the listeners on teardown. + */ + messages: [ + "ViewSource:PromptAndGoToLine", + "ViewSource:GoToLine:Success", + "ViewSource:GoToLine:Failed", + "ViewSource:StoreWrapping", + "ViewSource:StoreSyntaxHighlighting", + ], + + /** + * This should be called as soon as the script loads. When this function + * executes, we can assume the DOM content has not yet loaded. + */ + init() { + this.messages.forEach((msgName) => { + this.mm.addMessageListener(msgName, this); + }); + + // If we have a known <browser> already, load the frame script here. This + // is not true for the window case, as the element does not exist until the + // XUL document loads. For that case, the frame script is loaded by + // viewSource.js. + if (this._browser) { + this.loadFrameScript(); + } + }, + + /** + * This should be called when the window is closing. This function should + * clean up event and message listeners. + */ + uninit() { + this.messages.forEach((msgName) => { + this.mm.removeMessageListener(msgName, this); + }); + }, + + /** + * For a new browser we've not seen before, load the frame script. + */ + loadFrameScript() { + if (!gKnownBrowsers.has(this.browser)) { + gKnownBrowsers.add(this.browser); + this.mm.loadFrameScript(FRAME_SCRIPT, false); + } + }, + + /** + * Anything added to the messages array will get handled here, and should + * get dispatched to a specific function for the message name. + */ + receiveMessage(message) { + let data = message.data; + + switch (message.name) { + case "ViewSource:PromptAndGoToLine": + this.promptAndGoToLine(); + break; + case "ViewSource:GoToLine:Success": + this.onGoToLineSuccess(data.lineNumber); + break; + case "ViewSource:GoToLine:Failed": + this.onGoToLineFailed(); + break; + case "ViewSource:StoreWrapping": + this.storeWrapping(data.state); + break; + case "ViewSource:StoreSyntaxHighlighting": + this.storeSyntaxHighlighting(data.state); + break; + } + }, + + /** + * Getter for the message manager of the view source browser. + */ + get mm() { + return this.browser.messageManager; + }, + + /** + * Send a message to the view source browser. + */ + sendAsyncMessage(...args) { + this.browser.messageManager.sendAsyncMessage(...args); + }, + + /** + * A getter for the view source string bundle. + */ + get bundle() { + if (this._bundle) { + return this._bundle; + } + return this._bundle = Services.strings.createBundle(BUNDLE_URL); + }, + + /** + * Loads the source for a URL while applying some optional features if + * enabled. + * + * For the viewSource.xul window, this is called by onXULLoaded above. + * For view source in a specific browser, this is manually called after + * this object is constructed. + * + * This takes a single object argument containing: + * + * URL (required): + * A string URL for the page we'd like to view the source of. + * browser: + * The browser containing the document that we would like to view the + * source of. This argument is optional if outerWindowID is not passed. + * outerWindowID (optional): + * The outerWindowID of the content window containing the document that + * we want to view the source of. This is the only way of attempting to + * load the source out of the network cache. + * lineNumber (optional): + * The line number to focus on once the source is loaded. + */ + loadViewSource({ URL, browser, outerWindowID, lineNumber }) { + if (!URL) { + throw new Error("Must supply a URL when opening view source."); + } + + if (browser) { + this.browser.relatedBrowser = browser; + + // If we're dealing with a remote browser, then the browser + // for view source needs to be remote as well. + this.updateBrowserRemoteness(browser.isRemoteBrowser); + } else if (outerWindowID) { + throw new Error("Must supply the browser if passing the outerWindowID"); + } + + this.sendAsyncMessage("ViewSource:LoadSource", + { URL, outerWindowID, lineNumber }); + }, + + /** + * Loads a view source selection showing the given view-source url and + * highlight the selection. + * + * @param uri view-source uri to show + * @param drawSelection true to highlight the selection + * @param baseURI base URI of the original document + */ + loadViewSourceFromSelection(URL, drawSelection, baseURI) { + this.sendAsyncMessage("ViewSource:LoadSourceWithSelection", + { URL, drawSelection, baseURI }); + }, + + /** + * Updates the "remote" attribute of the view source browser. This + * will remove the browser from the DOM, and then re-add it in the + * same place it was taken from. + * + * @param shouldBeRemote + * True if the browser should be made remote. If the browsers + * remoteness already matches this value, this function does + * nothing. + */ + updateBrowserRemoteness(shouldBeRemote) { + if (this.browser.isRemoteBrowser != shouldBeRemote) { + // In this base case, where we are handed a <browser> someone else is + // managing, we don't know for sure that it's safe to toggle remoteness. + // For view source in a window, this is overridden to actually do the + // flip if needed. + throw new Error("View source browser's remoteness mismatch"); + } + }, + + /** + * Opens the "Go to line" prompt for a user to hop to a particular line + * of the source code they're viewing. This will keep prompting until the + * user either cancels out of the prompt, or enters a valid line number. + */ + promptAndGoToLine() { + let input = { value: this.lastLineFound }; + let window = Services.wm.getMostRecentWindow(null); + + let ok = Services.prompt.prompt( + window, + this.bundle.GetStringFromName("goToLineTitle"), + this.bundle.GetStringFromName("goToLineText"), + input, + null, + {value:0}); + + if (!ok) + return; + + let line = parseInt(input.value, 10); + + if (!(line > 0)) { + Services.prompt.alert(window, + this.bundle.GetStringFromName("invalidInputTitle"), + this.bundle.GetStringFromName("invalidInputText")); + this.promptAndGoToLine(); + } else { + this.goToLine(line); + } + }, + + /** + * Go to a particular line of the source code. This act is asynchronous. + * + * @param lineNumber + * The line number to try to go to to. + */ + goToLine(lineNumber) { + this.sendAsyncMessage("ViewSource:GoToLine", { lineNumber }); + }, + + /** + * Called when the frame script reports that a line was successfully gotten + * to. + * + * @param lineNumber + * The line number that we successfully got to. + */ + onGoToLineSuccess(lineNumber) { + // We'll pre-populate the "Go to line" prompt with this value the next + // time it comes up. + this.lastLineFound = lineNumber; + }, + + /** + * Called when the frame script reports that we failed to go to a particular + * line. This informs the user that their selection was likely out of range, + * and then reprompts the user to try again. + */ + onGoToLineFailed() { + let window = Services.wm.getMostRecentWindow(null); + Services.prompt.alert(window, + this.bundle.GetStringFromName("outOfRangeTitle"), + this.bundle.GetStringFromName("outOfRangeText")); + this.promptAndGoToLine(); + }, + + /** + * Update the wrapping pref based on the child's current state. + * @param state + * Whether wrapping is currently enabled in the child. + */ + storeWrapping(state) { + Services.prefs.setBoolPref("view_source.wrap_long_lines", state); + }, + + /** + * Update the syntax highlighting pref based on the child's current state. + * @param state + * Whether syntax highlighting is currently enabled in the child. + */ + storeSyntaxHighlighting(state) { + Services.prefs.setBoolPref("view_source.syntax_highlight", state); + }, + +}; + +/** + * Helper to decide if a URI maps to view source content. + * @param uri + * String containing the URI + */ +ViewSourceBrowser.isViewSource = function(uri) { + return uri.startsWith("view-source:") || + (uri.startsWith("data:") && uri.includes("MathML")); +}; |