Bug 1633790 - pass channels to stream conversion getContentType and always allow PDF.js use for user-opened local PDF files, r=mattwoodrow,jaws
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Wed, 06 May 2020 20:34:51 +0000
changeset 528516 ae1a72a9dce69a7fd11cf79f270002659a6d9838
parent 528515 979a77b1cbf396e2442e8ff9e345fa2d459e94eb
child 528517 c1ee5647da8fb482d817bb0fe6532e3737d7e48c
push id37388
push usercsabou@mozilla.com
push dateThu, 07 May 2020 04:06:39 +0000
treeherdermozilla-central@98040184b6c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, jaws
bugs1633790
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1633790 - pass channels to stream conversion getContentType and always allow PDF.js use for user-opened local PDF files, r=mattwoodrow,jaws Differential Revision: https://phabricator.services.mozilla.com/D73511
browser/extensions/pdfjs/content/PdfStreamConverter.jsm
browser/extensions/pdfjs/test/browser.ini
browser/extensions/pdfjs/test/browser_pdfjs_force_opening_files.js
devtools/client/jsonview/converter-child.js
modules/libjar/zipwriter/nsDeflateConverter.cpp
netwerk/ipc/DocumentLoadListener.cpp
netwerk/streamconv/converters/mozTXTToHTMLConv.cpp
netwerk/streamconv/converters/nsFTPDirListingConv.cpp
netwerk/streamconv/converters/nsHTTPCompressConv.cpp
netwerk/streamconv/converters/nsIndexedToHTML.cpp
netwerk/streamconv/converters/nsMultiMixedConv.cpp
netwerk/streamconv/converters/nsUnknownDecoder.cpp
netwerk/streamconv/nsIStreamConverter.idl
netwerk/streamconv/nsIStreamConverterService.idl
netwerk/streamconv/nsStreamConverterService.cpp
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -1102,25 +1102,31 @@ PdfStreamConverter.prototype = {
     Cu.reportError("Found unusable PDF preferences. Fixing back to PDF.js");
 
     mime.preferredAction = Ci.nsIHandlerInfo.handleInternally;
     mime.alwaysAskBeforeHandling = false;
     Svc.handlers.store(mime);
     return true;
   },
 
-  getConvertedType(aFromType) {
-    if (!this._validateAndMaybeUpdatePDFPrefs()) {
-      throw new Components.Exception(
-        "Can't use PDF.js",
-        Cr.NS_ERROR_NOT_AVAILABLE
-      );
+  getConvertedType(aFromType, aChannel) {
+    const HTML = "text/html";
+    if (this._validateAndMaybeUpdatePDFPrefs()) {
+      return HTML;
+    }
+    // Hm, so normally, no pdfjs. However... if this is a file: channel loaded
+    // with system principal, load it anyway:
+    if (aChannel && aChannel.URI.schemeIs("file")) {
+      let triggeringPrincipal = aChannel?.loadInfo?.triggeringPrincipal;
+      if (triggeringPrincipal?.isSystemPrincipal) {
+        return HTML;
+      }
     }
 
-    return "text/html";
+    throw new Components.Exception("Can't use PDF.js", Cr.NS_ERROR_FAILURE);
   },
 
   // nsIStreamListener::onDataAvailable
   onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
     if (!this.dataListener) {
       return;
     }
 
--- a/browser/extensions/pdfjs/test/browser.ini
+++ b/browser/extensions/pdfjs/test/browser.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   file_pdfjs_test.pdf
   head.js
 
+[browser_pdfjs_force_opening_files.js]
 [browser_pdfjs_main.js]
 [browser_pdfjs_navigation.js]
 [browser_pdfjs_savedialog.js]
 skip-if = verify
 [browser_pdfjs_views.js]
 [browser_pdfjs_zoom.js]
 skip-if = (verify && debug && (os == 'win'))
copy from browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js
copy to browser/extensions/pdfjs/test/browser_pdfjs_force_opening_files.js
--- a/browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_force_opening_files.js
@@ -1,30 +1,51 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
-const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
+add_task(async function test_file_opening() {
+  // Get a ref to the pdf we want to open.
+  let dirFileObj = getChromeDir(getResolvedURI(gTestPath));
+  dirFileObj.append("file_pdfjs_test.pdf");
 
-function test() {
+  // Change the defaults.
   var oldAction = changeMimeHandler(Ci.nsIHandlerInfo.useSystemDefault, true);
-  var tab = BrowserTestUtils.addTab(gBrowser, TESTROOT + "file_pdfjs_test.pdf");
-  // Test: "Open with" dialog comes up when pdf.js is not selected as the default
-  // handler.
-  addWindowListener(
-    "chrome://mozapps/content/downloads/unknownContentType.xhtml",
-    finish
-  );
+
+  // Test: "Open with" dialog should not come up, despite pdf.js not being
+  // the default - because files from disk should always use pdfjs, unless
+  // it is forcibly disabled.
+  let openedWindow = false;
+  let windowOpenedPromise = new Promise((resolve, reject) => {
+    addWindowListener(
+      "chrome://mozapps/content/downloads/unknownContentType.xhtml",
+      () => {
+        openedWindow = true;
+        resolve();
+      }
+    );
+  });
 
-  waitForExplicitFinish();
+  // Open the tab with a system principal:
+  var tab = BrowserTestUtils.addTab(gBrowser, dirFileObj.path);
+
+  let pdfjsLoadedPromise = TestUtils.waitForCondition(() => {
+    let { contentPrincipal } = tab.linkedBrowser;
+    return (contentPrincipal?.URI?.spec || "").endsWith("viewer.html");
+  });
+  await Promise.race([pdfjsLoadedPromise, windowOpenedPromise]);
+  ok(!openedWindow, "Shouldn't open an unknownContentType window!");
+
   registerCleanupFunction(function() {
+    if (listenerCleanup) {
+      listenerCleanup();
+    }
     changeMimeHandler(oldAction[0], oldAction[1]);
     gBrowser.removeTab(tab);
   });
-}
+});
 
 function changeMimeHandler(preferredAction, alwaysAskBeforeHandling) {
   let handlerService = Cc[
     "@mozilla.org/uriloader/handler-service;1"
   ].getService(Ci.nsIHandlerService);
   let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
   let handlerInfo = mimeService.getFromTypeAndExtension(
     "application/pdf",
@@ -53,28 +74,32 @@ function changeMimeHandler(preferredActi
     handlerInfo.preferredAction,
     preferredAction,
     "mime handler change successful"
   );
 
   return oldAction;
 }
 
+let listenerCleanup;
 function addWindowListener(aURL, aCallback) {
-  Services.wm.addListener({
+  let listener = {
     onOpenWindow(aXULWindow) {
       info("window opened, waiting for focus");
-      Services.wm.removeListener(this);
+      listenerCleanup();
+      listenerCleanup = null;
 
       var domwindow = aXULWindow.docShell.domWindow;
       waitForFocus(function() {
         is(
           domwindow.document.location.href,
           aURL,
           "should have seen the right window open"
         );
         domwindow.close();
         aCallback();
       }, domwindow);
     },
     onCloseWindow(aXULWindow) {},
-  });
+  };
+  Services.wm.addListener(listener);
+  listenerCleanup = () => Services.wm.removeListener(listener);
 }
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -70,17 +70,17 @@ Converter.prototype = {
    */
   convert: function(fromStream, fromType, toType, ctx) {
     return fromStream;
   },
 
   asyncConvertData: function(fromType, toType, listener, ctx) {
     this.listener = listener;
   },
-  getConvertedType: function(fromType) {
+  getConvertedType: function(fromType, channel) {
     return "text/html";
   },
 
   onDataAvailable: function(request, inputStream, offset, count) {
     // Decode and insert data.
     const buffer = new ArrayBuffer(count);
     new BinaryInput(inputStream).readArrayBuffer(count, buffer);
     this.decodeAndInsertBuffer(buffer);
--- a/modules/libjar/zipwriter/nsDeflateConverter.cpp
+++ b/modules/libjar/zipwriter/nsDeflateConverter.cpp
@@ -88,16 +88,17 @@ NS_IMETHODIMP nsDeflateConverter::AsyncC
 
   mListener = aListener;
   mContext = aCtxt;
   return rv;
 }
 
 NS_IMETHODIMP
 nsDeflateConverter::GetConvertedType(const nsACString& aFromType,
+                                     nsIChannel* aChannel,
                                      nsACString& aToType) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP nsDeflateConverter::OnDataAvailable(nsIRequest* aRequest,
                                                   nsIInputStream* aInputStream,
                                                   uint64_t aOffset,
                                                   uint32_t aCount) {
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -112,17 +112,17 @@ class ParentProcessDocumentOpenInfo fina
     if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE)) {
       return nsDocumentOpenInfo::TryStreamConversion(aChannel);
     }
 
     nsresult rv;
     nsCOMPtr<nsIStreamConverterService> streamConvService =
         do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
     nsAutoCString str;
-    rv = streamConvService->ConvertedType(mContentType, str);
+    rv = streamConvService->ConvertedType(mContentType, aChannel, str);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // We only support passing data to the default content listener
     // (docshell), and we don't supported chaining converters.
     if (TryDefaultContentListener(aChannel, str)) {
       mContentType = str;
       return NS_OK;
     }
--- a/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp
+++ b/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp
@@ -1214,17 +1214,17 @@ NS_IMETHODIMP
 mozTXTToHTMLConv::AsyncConvertData(const char* aFromType, const char* aToType,
                                    nsIStreamListener* aListener,
                                    nsISupports* aCtxt) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 mozTXTToHTMLConv::GetConvertedType(const nsACString& aFromType,
-                                   nsACString& aToType) {
+                                   nsIChannel* aChannel, nsACString& aToType) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 mozTXTToHTMLConv::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
                                   uint64_t sourceOffset, uint32_t count) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
--- a/netwerk/streamconv/converters/nsFTPDirListingConv.cpp
+++ b/netwerk/streamconv/converters/nsFTPDirListingConv.cpp
@@ -67,16 +67,17 @@ nsFTPDirListingConv::AsyncConvertData(co
           ("nsFTPDirListingConv::AsyncConvertData() converting FROM raw, TO "
            "application/http-index-format\n"));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFTPDirListingConv::GetConvertedType(const nsACString& aFromType,
+                                      nsIChannel* aChannel,
                                       nsACString& aToType) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 // nsIStreamListener implementation
 NS_IMETHODIMP
 nsFTPDirListingConv::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
                                      uint64_t sourceOffset, uint32_t count) {
--- a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp
+++ b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp
@@ -112,16 +112,17 @@ nsHTTPCompressConv::AsyncConvertData(con
   // hook ourself up with the receiving listener.
   mListener = aListener;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTTPCompressConv::GetConvertedType(const nsACString& aFromType,
+                                     nsIChannel* aChannel,
                                      nsACString& aToType) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsHTTPCompressConv::OnStartRequest(nsIRequest* request) {
   LOG(("nsHttpCompresssConv %p onstart\n", this));
   nsCOMPtr<nsIStreamListener> listener;
--- a/netwerk/streamconv/converters/nsIndexedToHTML.cpp
+++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp
@@ -87,17 +87,17 @@ NS_IMETHODIMP
 nsIndexedToHTML::AsyncConvertData(const char* aFromType, const char* aToType,
                                   nsIStreamListener* aListener,
                                   nsISupports* aCtxt) {
   return Init(aListener);
 }
 
 NS_IMETHODIMP
 nsIndexedToHTML::GetConvertedType(const nsACString& aFromType,
-                                  nsACString& aToType) {
+                                  nsIChannel* aChannel, nsACString& aToType) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsIndexedToHTML::OnStartRequest(nsIRequest* request) {
   nsCString buffer;
   nsresult rv = DoOnStartRequest(request, nullptr, buffer);
   if (NS_FAILED(rv)) {
--- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -419,17 +419,17 @@ nsMultiMixedConv::AsyncConvertData(const
   // of these for each sub-part in the raw stream.
   mFinalListener = aListener;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMultiMixedConv::GetConvertedType(const nsACString& aFromType,
-                                   nsACString& aToType) {
+                                   nsIChannel* aChannel, nsACString& aToType) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 // nsIRequestObserver implementation
 NS_IMETHODIMP
 nsMultiMixedConv::OnStartRequest(nsIRequest* request) {
   // we're assuming the content-type is available at this stage
   NS_ASSERTION(mBoundary.IsEmpty(), "a second on start???");
--- a/netwerk/streamconv/converters/nsUnknownDecoder.cpp
+++ b/netwerk/streamconv/converters/nsUnknownDecoder.cpp
@@ -140,17 +140,17 @@ nsUnknownDecoder::AsyncConvertData(const
 
   MutexAutoLock lock(mMutex);
   mNextListener = aListener;
   return (aListener) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsUnknownDecoder::GetConvertedType(const nsACString& aFromType,
-                                   nsACString& aToType) {
+                                   nsIChannel* aChannel, nsACString& aToType) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 // ----
 //
 // nsIStreamListener methods...
 //
 // ----
--- a/netwerk/streamconv/nsIStreamConverter.idl
+++ b/netwerk/streamconv/nsIStreamConverter.idl
@@ -1,15 +1,16 @@
 /* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "nsIStreamListener.idl"
 
+interface nsIChannel;
 interface nsIInputStream;
 
 /**
  * nsIStreamConverter provides an interface to implement when you have code
  * that converts data from one type to another.
  *
  * Suppose you had code that converted plain text into HTML. You could implement
  * this interface to allow everyone else to use your conversion logic using a
@@ -93,17 +94,20 @@ interface nsIStreamConverter : nsIStream
                           in string aToType,
                           in nsIStreamListener aListener,
                           in nsISupports aCtxt);
 
     /**
      * Returns the content type that the stream listener passed to asyncConvertData will
      * see on the channel if the conversion is being done from aFromType to * /*.
      *
+     * @param aFromType     The type of the content prior to conversion.
+     * @param aChannel      The channel that we'd like to convert. May be null.
+     *
      * @throws if the converter does not support conversion to * /* or if it doesn't know
      *         the type in advance.
      */
-    ACString getConvertedType(in ACString aFromType);
+    ACString getConvertedType(in ACString aFromType, in nsIChannel aChannel);
 };
 
 %{C++
 #define NS_ISTREAMCONVERTER_KEY         "@mozilla.org/streamconv;1"
 %}
--- a/netwerk/streamconv/nsIStreamConverterService.idl
+++ b/netwerk/streamconv/nsIStreamConverterService.idl
@@ -1,15 +1,16 @@
 /* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "nsISupports.idl"
 
+interface nsIChannel;
 interface nsIInputStream;
 interface nsIStreamListener;
 
 %{C++
 #define NS_ISTREAMCONVERTER_KEY         "@mozilla.org/streamconv;1"
 %}
 
 /**
@@ -35,17 +36,17 @@ interface nsIStreamConverterService : ns
     boolean canConvert(in string aFromType, in string aToType);
 
     /**
      * Returns the content type that will be returned from a converter
      * created with aFromType and  * /*.
      * Can fail if no converters support this conversion, or if the
      * output type isn't known in advance.
      */
-    ACString convertedType(in ACString aFromType);
+    ACString convertedType(in ACString aFromType, in nsIChannel aChannel);
 
     /**
      * <b>SYNCHRONOUS VERSION</b>
      * Converts a stream of one type, to a stream of another type.
      *
      * Use this method when you have a stream you want to convert.
      *
      * @param aFromStream   The stream representing the original/raw data.
--- a/netwerk/streamconv/nsStreamConverterService.cpp
+++ b/netwerk/streamconv/nsStreamConverterService.cpp
@@ -352,29 +352,30 @@ nsStreamConverterService::CanConvert(con
   *_retval = NS_SUCCEEDED(rv);
 
   delete converterChain;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsStreamConverterService::ConvertedType(const nsACString& aFromType,
+                                        nsIChannel* aChannel,
                                         nsACString& aOutToType) {
   // first determine whether we can even handle this conversion
   // build a CONTRACTID
   nsAutoCString contractID;
   contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
   contractID.Append(aFromType);
   contractID.AppendLiteral("&to=*/*");
   const char* cContractID = contractID.get();
 
   nsresult rv;
   nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
   if (NS_SUCCEEDED(rv)) {
-    return converter->GetConvertedType(aFromType, aOutToType);
+    return converter->GetConvertedType(aFromType, aChannel, aOutToType);
   }
   return rv;
 }
 
 NS_IMETHODIMP
 nsStreamConverterService::Convert(nsIInputStream* aFromStream,
                                   const char* aFromType, const char* aToType,
                                   nsISupports* aContext,