summaryrefslogtreecommitdiffstats
path: root/toolkit/components/viewsource/ViewSourceBrowser.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/viewsource/ViewSourceBrowser.jsm')
-rw-r--r--toolkit/components/viewsource/ViewSourceBrowser.jsm331
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"));
+};