toolkit/components/viewsource/content/viewSource.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 // -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 Components.utils.import("resource://gre/modules/Services.jsm");
     8 Components.utils.import("resource://gre/modules/CharsetMenu.jsm");
    10 const Cc = Components.classes;
    11 const Ci = Components.interfaces;
    13 var gLastLineFound = '';
    14 var gGoToLine = 0;
    16 [
    17   ["gBrowser",          "content"],
    18   ["gViewSourceBundle", "viewSourceBundle"],
    19   ["gContextMenu",      "viewSourceContextMenu"]
    20 ].forEach(function ([name, id]) {
    21   window.__defineGetter__(name, function () {
    22     var element = document.getElementById(id);
    23     if (!element)
    24       return null;
    25     delete window[name];
    26     return window[name] = element;
    27   });
    28 });
    30 // viewZoomOverlay.js uses this
    31 function getBrowser() {
    32   return gBrowser;
    33 }
    35 this.__defineGetter__("gPageLoader", function () {
    36   var webnav = getWebNavigation();
    37   if (!webnav)
    38     return null;
    39   delete this.gPageLoader;
    40   return this.gPageLoader = webnav.QueryInterface(Ci.nsIWebPageDescriptor);
    41 });
    43 var gSelectionListener = {
    44   timeout: 0,
    45   attached: false,
    46   notifySelectionChanged: function(doc, sel, reason)
    47   {
    48     // Coalesce notifications within 100ms intervals.
    49     if (!this.timeout)
    50       this.timeout = setTimeout(updateStatusBar, 100);
    51   }
    52 }
    54 function onLoadViewSource() 
    55 {
    56   viewSource(window.arguments[0]);
    57   document.commandDispatcher.focusedWindow = content;
    58   gBrowser.droppedLinkHandler = function (event, url, name) {
    59     viewSource(url)
    60     event.preventDefault();
    61   }
    63   if (!isHistoryEnabled()) {
    64     // Disable the BACK and FORWARD commands and hide the related menu items.
    65     var viewSourceNavigation = document.getElementById("viewSourceNavigation");
    66     viewSourceNavigation.setAttribute("disabled", "true");
    67     viewSourceNavigation.setAttribute("hidden", "true");
    68   }
    69 }
    71 function isHistoryEnabled() {
    72   return !gBrowser.hasAttribute("disablehistory");
    73 }
    75 function getSelectionController() {
    76   return gBrowser.docShell
    77                  .QueryInterface(Ci.nsIInterfaceRequestor)
    78                  .getInterface(Ci.nsISelectionDisplay)
    79                  .QueryInterface(Ci.nsISelectionController);
    80 }
    82 function viewSource(url)
    83 {
    84   if (!url)
    85     return; // throw Components.results.NS_ERROR_FAILURE;
    87   var viewSrcUrl = "view-source:" + url;
    89   gBrowser.addEventListener("pagehide", onUnloadContent, true);
    90   gBrowser.addEventListener("pageshow", onLoadContent, true);
    91   gBrowser.addEventListener("click", onClickContent, false);
    93   var loadFromURL = true;
    95   // Parse the 'arguments' supplied with the dialog.
    96   //    arg[0] - URL string.
    97   //    arg[1] - Charset value in the form 'charset=xxx'.
    98   //    arg[2] - Page descriptor used to load content from the cache.
    99   //    arg[3] - Line number to go to.
   100   //    arg[4] - Whether charset was forced by the user
   102   if ("arguments" in window) {
   103     var arg;
   105     // Set the charset of the viewsource window...
   106     var charset;
   107     if (window.arguments.length >= 2) {
   108       arg = window.arguments[1];
   110       try {
   111         if (typeof(arg) == "string" && arg.indexOf('charset=') != -1) {
   112           var arrayArgComponents = arg.split('=');
   113           if (arrayArgComponents) {
   114             // Remember the charset here so that it can be used below in case
   115             // the document had a forced charset.
   116             charset = arrayArgComponents[1];
   117           }
   118         }
   119       } catch (ex) {
   120         // Ignore the failure and keep processing arguments...
   121       }
   122     }
   123     // If the document had a forced charset, set it here also
   124     if (window.arguments.length >= 5) {
   125       arg = window.arguments[4];
   127       try {
   128         if (arg === true) {
   129           gBrowser.docShell.charset = charset;
   130         }
   131       } catch (ex) {
   132         // Ignore the failure and keep processing arguments...
   133       }
   134     }
   136     // Get any specified line to jump to.
   137     if (window.arguments.length >= 4) {
   138       arg = window.arguments[3];
   139       gGoToLine = parseInt(arg);
   140     }
   142     // Use the page descriptor to load the content from the cache (if
   143     // available).
   144     if (window.arguments.length >= 3) {
   145       arg = window.arguments[2];
   147       try {
   148         if (typeof(arg) == "object" && arg != null) {
   149           // Load the page using the page descriptor rather than the URL.
   150           // This allows the content to be fetched from the cache (if
   151           // possible) rather than the network...
   152           gPageLoader.loadPage(arg, gPageLoader.DISPLAY_AS_SOURCE);
   154           // The content was successfully loaded.
   155           loadFromURL = false;
   157           // Record the page load in the session history so <back> will work.
   158           var shEntrySource = arg.QueryInterface(Ci.nsISHEntry);
   159           var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry);
   160           shEntry.setURI(makeURI(viewSrcUrl, null, null));
   161           shEntry.setTitle(viewSrcUrl);
   162           shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
   163           shEntry.cacheKey = shEntrySource.cacheKey;
   164           gBrowser.sessionHistory
   165                   .QueryInterface(Ci.nsISHistoryInternal)
   166                   .addEntry(shEntry, true);
   167         }
   168       } catch(ex) {
   169         // Ignore the failure.  The content will be loaded via the URL
   170         // that was supplied in arg[0].
   171       }
   172     }
   173   }
   175   if (loadFromURL) {
   176     // Currently, an exception is thrown if the URL load fails...
   177     var loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
   178     getWebNavigation().loadURI(viewSrcUrl, loadFlags, null, null, null);
   179   }
   181   // Check the view_source.wrap_long_lines pref and set the menuitem's checked
   182   // attribute accordingly.
   183   var wraplonglinesPrefValue = Services.prefs.getBoolPref("view_source.wrap_long_lines");
   185   if (wraplonglinesPrefValue)
   186     document.getElementById("menu_wrapLongLines").setAttribute("checked", "true");
   188   document.getElementById("menu_highlightSyntax")
   189           .setAttribute("checked",
   190                         Services.prefs.getBoolPref("view_source.syntax_highlight"));
   192   window.addEventListener("AppCommand", HandleAppCommandEvent, true);
   193   window.addEventListener("MozSwipeGesture", HandleSwipeGesture, true);
   194   window.content.focus();
   195 }
   197 function onLoadContent()
   198 {
   199   // If the view source was opened with a "go to line" argument.
   200   if (gGoToLine > 0) {
   201     goToLine(gGoToLine);
   202     gGoToLine = 0;
   203   }
   204   document.getElementById('cmd_goToLine').removeAttribute('disabled');
   206   // Register a listener so that we can show the caret position on the status bar.
   207   window.content.getSelection()
   208    .QueryInterface(Ci.nsISelectionPrivate)
   209    .addSelectionListener(gSelectionListener);
   210   gSelectionListener.attached = true;
   212   if (isHistoryEnabled())
   213     UpdateBackForwardCommands();
   214 }
   216 function onUnloadContent()
   217 {
   218   // Disable "go to line" while reloading due to e.g. change of charset
   219   // or toggling of syntax highlighting.
   220   document.getElementById('cmd_goToLine').setAttribute('disabled', 'true');
   222   // If we're not just unloading the initial "about:blank" which doesn't have
   223   // a selection listener, get rid of it so it doesn't try to fire after the
   224   // window has gone away.
   225   if (gSelectionListener.attached) {
   226     window.content.getSelection().QueryInterface(Ci.nsISelectionPrivate)
   227           .removeSelectionListener(gSelectionListener);
   228     gSelectionListener.attached = false;
   229   }
   230 }
   232 /**
   233  * Handle click events bubbling up from error page content
   234  */
   235 function onClickContent(event) {
   236   // Don't trust synthetic events
   237   if (!event.isTrusted || event.target.localName != "button")
   238     return;
   240   var target = event.originalTarget;
   241   var errorDoc = target.ownerDocument;
   243   if (/^about:blocked/.test(errorDoc.documentURI)) {
   244     // The event came from a button on a malware/phishing block page
   245     // First check whether it's malware or phishing, so that we can
   246     // use the right strings/links
   247     var isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
   249     if (target == errorDoc.getElementById('getMeOutButton')) {
   250       // Instead of loading some safe page, just close the window
   251       window.close();
   252     } else if (target == errorDoc.getElementById('reportButton')) {
   253       // This is the "Why is this site blocked" button.  For malware,
   254       // we can fetch a site-specific report, for phishing, we redirect
   255       // to the generic page describing phishing protection.
   257       if (isMalware) {
   258         // Get the stop badware "why is this blocked" report url,
   259         // append the current url, and go there.
   260         try {
   261           let reportURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.malware.reportURL", true);
   262           reportURL += errorDoc.location.href.slice(12);
   263           openURL(reportURL);
   264         } catch (e) {
   265           Components.utils.reportError("Couldn't get malware report URL: " + e);
   266         }
   267       } else {
   268         // It's a phishing site, just link to the generic information page
   269         let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
   270         openURL(url + "phishing-malware");
   271       }
   272     } else if (target == errorDoc.getElementById('ignoreWarningButton')) {
   273       // Allow users to override and continue through to the site
   274       gBrowser.loadURIWithFlags(content.location.href,
   275                                 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
   276                                 null, null, null);
   277     }
   278   }
   279 }
   281 function HandleAppCommandEvent(evt)
   282 {
   283   evt.stopPropagation();
   284   switch (evt.command) {
   285     case "Back":
   286       BrowserBack();
   287       break;
   288     case "Forward":
   289       BrowserForward();
   290       break;
   291   }
   292 }
   294 function HandleSwipeGesture(evt) {
   295   evt.stopPropagation();
   296   switch (evt.direction) {
   297     case SimpleGestureEvent.DIRECTION_LEFT:
   298       BrowserBack();
   299       break;
   300     case SimpleGestureEvent.DIRECTION_RIGHT:
   301       BrowserForward();
   302       break;
   303     case SimpleGestureEvent.DIRECTION_UP:
   304       goDoCommand("cmd_scrollTop");
   305       break;
   306     case SimpleGestureEvent.DIRECTION_DOWN:
   307       goDoCommand("cmd_scrollBottom");
   308       break;
   309   }
   310 }
   312 function ViewSourceClose()
   313 {
   314   window.close();
   315 }
   317 function ViewSourceReload()
   318 {
   319   gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
   320                            Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
   321 }
   323 // Strips the |view-source:| for internalSave()
   324 function ViewSourceSavePage()
   325 {
   326   internalSave(window.content.location.href.substring(12), 
   327                null, null, null, null, null, "SaveLinkTitle",
   328                null, null, window.content.document, null, gPageLoader);
   329 }
   331 var PrintPreviewListener = {
   332   getPrintPreviewBrowser: function () {
   333     var browser = document.getElementById("ppBrowser");
   334     if (!browser) {
   335       browser = document.createElement("browser");
   336       browser.setAttribute("id", "ppBrowser");
   337       browser.setAttribute("flex", "1");
   338       document.getElementById("appcontent").
   339         insertBefore(browser, document.getElementById("FindToolbar"));
   340     }
   341     return browser;
   342   },
   343   getSourceBrowser: function () {
   344     return gBrowser;
   345   },
   346   getNavToolbox: function () {
   347     return document.getElementById("appcontent");
   348   },
   349   onEnter: function () {
   350     var toolbox = document.getElementById("viewSource-toolbox");
   351     toolbox.hidden = true;
   352     gBrowser.collapsed = true;
   353   },
   354   onExit: function () {
   355     document.getElementById("ppBrowser").collapsed = true;
   356     gBrowser.collapsed = false;
   357     document.getElementById("viewSource-toolbox").hidden = false;
   358   }
   359 }
   361 function getWebNavigation()
   362 {
   363   try {
   364     return gBrowser.webNavigation;
   365   } catch (e) {
   366     return null;
   367   }
   368 }
   370 function ViewSourceGoToLine()
   371 {
   372   var input = {value:gLastLineFound};
   373   for (;;) {
   374     var ok = Services.prompt.prompt(
   375         window,
   376         gViewSourceBundle.getString("goToLineTitle"),
   377         gViewSourceBundle.getString("goToLineText"),
   378         input,
   379         null,
   380         {value:0});
   382     if (!ok)
   383       return;
   385     var line = parseInt(input.value, 10);
   387     if (!(line > 0)) {
   388       Services.prompt.alert(window,
   389                             gViewSourceBundle.getString("invalidInputTitle"),
   390                             gViewSourceBundle.getString("invalidInputText"));
   392       continue;
   393     }
   395     var found = goToLine(line);
   397     if (found)
   398       break;
   400     Services.prompt.alert(window,
   401                           gViewSourceBundle.getString("outOfRangeTitle"),
   402                           gViewSourceBundle.getString("outOfRangeText"));
   403   }
   404 }
   406 function goToLine(line)
   407 {
   408   var viewsource = window.content.document.body;
   410   // The source document is made up of a number of pre elements with
   411   // id attributes in the format <pre id="line123">, meaning that
   412   // the first line in the pre element is number 123.
   413   // Do binary search to find the pre element containing the line.
   414   // However, in the plain text case, we have only one pre without an
   415   // attribute, so assume it begins on line 1.
   417   var pre;
   418   for (var lbound = 0, ubound = viewsource.childNodes.length; ; ) {
   419     var middle = (lbound + ubound) >> 1;
   420     pre = viewsource.childNodes[middle];
   422     var firstLine = pre.id ? parseInt(pre.id.substring(4)) : 1;
   424     if (lbound == ubound - 1) {
   425       break;
   426     }
   428     if (line >= firstLine) {
   429       lbound = middle;
   430     } else {
   431       ubound = middle;
   432     }
   433   }
   435   var result = {};
   436   var found = findLocation(pre, line, null, -1, false, result);
   438   if (!found) {
   439     return false;
   440   }
   442   var selection = window.content.getSelection();
   443   selection.removeAllRanges();
   445   // In our case, the range's startOffset is after "\n" on the previous line.
   446   // Tune the selection at the beginning of the next line and do some tweaking
   447   // to position the focusNode and the caret at the beginning of the line.
   449   selection.QueryInterface(Ci.nsISelectionPrivate)
   450     .interlinePosition = true;
   452   selection.addRange(result.range);
   454   if (!selection.isCollapsed) {
   455     selection.collapseToEnd();
   457     var offset = result.range.startOffset;
   458     var node = result.range.startContainer;
   459     if (offset < node.data.length) {
   460       // The same text node spans across the "\n", just focus where we were.
   461       selection.extend(node, offset);
   462     }
   463     else {
   464       // There is another tag just after the "\n", hook there. We need
   465       // to focus a safe point because there are edgy cases such as
   466       // <span>...\n</span><span>...</span> vs.
   467       // <span>...\n<span>...</span></span><span>...</span>
   468       node = node.nextSibling ? node.nextSibling : node.parentNode.nextSibling;
   469       selection.extend(node, 0);
   470     }
   471   }
   473   var selCon = getSelectionController();
   474   selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON);
   475   selCon.setCaretVisibilityDuringSelection(true);
   477   // Scroll the beginning of the line into view.
   478   selCon.scrollSelectionIntoView(
   479     Ci.nsISelectionController.SELECTION_NORMAL,
   480     Ci.nsISelectionController.SELECTION_FOCUS_REGION,
   481     true);
   483   gLastLineFound = line;
   485   document.getElementById("statusbar-line-col").label =
   486     gViewSourceBundle.getFormattedString("statusBarLineCol", [line, 1]);
   488   return true;
   489 }
   491 function updateStatusBar()
   492 {
   493   // Reset the coalesce flag.
   494   gSelectionListener.timeout = 0;
   496   var statusBarField = document.getElementById("statusbar-line-col");
   498   var selection = window.content.getSelection();
   499   if (!selection.focusNode) {
   500     statusBarField.label = '';
   501     return;
   502   }
   503   if (selection.focusNode.nodeType != Node.TEXT_NODE) {
   504     return;
   505   }
   507   var selCon = getSelectionController();
   508   selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON);
   509   selCon.setCaretVisibilityDuringSelection(true);
   511   var interlinePosition = selection.QueryInterface(Ci.nsISelectionPrivate)
   512                                    .interlinePosition;
   514   var result = {};
   515   findLocation(null, -1, 
   516       selection.focusNode, selection.focusOffset, interlinePosition, result);
   518   statusBarField.label = gViewSourceBundle.getFormattedString(
   519                            "statusBarLineCol", [result.line, result.col]);
   520 }
   522 // Loops through the text lines in the pre element. The arguments are either
   523 // (pre, line) or (node, offset, interlinePosition). result is an out
   524 // argument. If (pre, line) are specified (and node == null), result.range is
   525 // a range spanning the specified line. If the (node, offset,
   526 // interlinePosition) are specified, result.line and result.col are the line
   527 // and column number of the specified offset in the specified node relative to
   528 // the whole file.
   529 function findLocation(pre, line, node, offset, interlinePosition, result)
   530 {
   531   if (node && !pre) {
   532     // Look upwards to find the current pre element.
   533     for (pre = node;
   534          pre.nodeName != "PRE";
   535          pre = pre.parentNode);
   536   }
   538   // The source document is made up of a number of pre elements with
   539   // id attributes in the format <pre id="line123">, meaning that
   540   // the first line in the pre element is number 123.
   541   // However, in the plain text case, there is only one <pre> without an id,
   542   // so assume line 1.
   543   var curLine = pre.id ? parseInt(pre.id.substring(4)) : 1;
   545   // Walk through each of the text nodes and count newlines.
   546   var treewalker = window.content.document
   547       .createTreeWalker(pre, NodeFilter.SHOW_TEXT, null);
   549   // The column number of the first character in the current text node.
   550   var firstCol = 1;
   552   var found = false;
   553   for (var textNode = treewalker.firstChild();
   554        textNode && !found;
   555        textNode = treewalker.nextNode()) {
   557     // \r is not a valid character in the DOM, so we only check for \n.
   558     var lineArray = textNode.data.split(/\n/);
   559     var lastLineInNode = curLine + lineArray.length - 1;
   561     // Check if we can skip the text node without further inspection.
   562     if (node ? (textNode != node) : (lastLineInNode < line)) {
   563       if (lineArray.length > 1) {
   564         firstCol = 1;
   565       }
   566       firstCol += lineArray[lineArray.length - 1].length;
   567       curLine = lastLineInNode;
   568       continue;
   569     }
   571     // curPos is the offset within the current text node of the first
   572     // character in the current line.
   573     for (var i = 0, curPos = 0;
   574          i < lineArray.length;
   575          curPos += lineArray[i++].length + 1) {
   577       if (i > 0) {
   578         curLine++;
   579       }
   581       if (node) {
   582         if (offset >= curPos && offset <= curPos + lineArray[i].length) {
   583           // If we are right after the \n of a line and interlinePosition is
   584           // false, the caret looks as if it were at the end of the previous
   585           // line, so we display that line and column instead.
   587           if (i > 0 && offset == curPos && !interlinePosition) {
   588             result.line = curLine - 1;
   589             var prevPos = curPos - lineArray[i - 1].length;
   590             result.col = (i == 1 ? firstCol : 1) + offset - prevPos;
   591           } else {
   592             result.line = curLine;
   593             result.col = (i == 0 ? firstCol : 1) + offset - curPos;
   594           }
   595           found = true;
   597           break;
   598         }
   600       } else {
   601         if (curLine == line && !("range" in result)) {
   602           result.range = document.createRange();
   603           result.range.setStart(textNode, curPos);
   605           // This will always be overridden later, except when we look for
   606           // the very last line in the file (this is the only line that does
   607           // not end with \n).
   608           result.range.setEndAfter(pre.lastChild);
   610         } else if (curLine == line + 1) {
   611           result.range.setEnd(textNode, curPos - 1);
   612           found = true;
   613           break;
   614         }
   615       }
   616     }
   617   }
   619   return found || ("range" in result);
   620 }
   622 // Toggle long-line wrapping and sets the view_source.wrap_long_lines
   623 // pref to persist the last state.
   624 function wrapLongLines()
   625 {
   626   var myWrap = window.content.document.body;
   627   myWrap.classList.toggle("wrap");
   629   // Since multiple viewsource windows are possible, another window could have
   630   // affected the pref, so instead of determining the new pref value via the current
   631   // pref value, we use myWrap.classList.
   632   Services.prefs.setBoolPref("view_source.wrap_long_lines", myWrap.classList.contains("wrap"));
   633 }
   635 // Toggles syntax highlighting and sets the view_source.syntax_highlight
   636 // pref to persist the last state.
   637 function highlightSyntax()
   638 {
   639   var highlightSyntaxMenu = document.getElementById("menu_highlightSyntax");
   640   var highlightSyntax = (highlightSyntaxMenu.getAttribute("checked") == "true");
   641   Services.prefs.setBoolPref("view_source.syntax_highlight", highlightSyntax);
   643   gPageLoader.loadPage(gPageLoader.currentDescriptor, gPageLoader.DISPLAY_NORMAL);
   644 }
   646 // Reload after change to character encoding or autodetection
   647 //
   648 // Fix for bug 136322: this function overrides the function in
   649 // browser.js to call PageLoader.loadPage() instead of BrowserReloadWithFlags()
   650 function BrowserCharsetReload()
   651 {
   652   if (isHistoryEnabled()) {
   653     gPageLoader.loadPage(gPageLoader.currentDescriptor,
   654                          gPageLoader.DISPLAY_NORMAL);
   655   } else {
   656     gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
   657   }
   658 }
   660 function BrowserSetCharacterSet(aEvent)
   661 {
   662   if (aEvent.target.hasAttribute("charset"))
   663     gBrowser.docShell.charset = aEvent.target.getAttribute("charset");
   664   BrowserCharsetReload();
   665 }
   667 function BrowserForward(aEvent) {
   668   try {
   669     gBrowser.goForward();
   670   }
   671   catch(ex) {
   672   }
   673 }
   675 function BrowserBack(aEvent) {
   676   try {
   677     gBrowser.goBack();
   678   }
   679   catch(ex) {
   680   }
   681 }
   683 function UpdateBackForwardCommands() {
   684   var backBroadcaster = document.getElementById("Browser:Back");
   685   var forwardBroadcaster = document.getElementById("Browser:Forward");
   687   if (getWebNavigation().canGoBack)
   688     backBroadcaster.removeAttribute("disabled");
   689   else
   690     backBroadcaster.setAttribute("disabled", "true");
   692   if (getWebNavigation().canGoForward)
   693     forwardBroadcaster.removeAttribute("disabled");
   694   else
   695     forwardBroadcaster.setAttribute("disabled", "true");
   696 }
   698 function contextMenuShowing() {
   699   var isLink = false;
   700   var isEmail = false;
   701   if (gContextMenu.triggerNode && gContextMenu.triggerNode.localName == 'a') {
   702     if (gContextMenu.triggerNode.href.indexOf('view-source:') == 0)
   703       isLink = true;
   704     if (gContextMenu.triggerNode.href.indexOf('mailto:') == 0)
   705       isEmail = true;
   706   }
   707   document.getElementById('context-copyLink').hidden = !isLink;
   708   document.getElementById('context-copyEmail').hidden = !isEmail;
   709 }
   711 function contextMenuCopyLinkOrEmail() {
   712   if (!gContextMenu.triggerNode)
   713     return;
   715   var href = gContextMenu.triggerNode.href;
   716   var clipboard = Cc['@mozilla.org/widget/clipboardhelper;1'].
   717                   getService(Ci.nsIClipboardHelper);
   718   clipboard.copyString(href.substring(href.indexOf(':') + 1), document);
   719 }

mercurial