tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

save-to-pdf.rst (5486B)


      1 GeckoView Save to PDF
      2 =====================
      3 
      4 Olivia Hall <ohall@mozilla.com>, Jonathan Almeida <jon@mozilla.com>
      5 
      6 Why
      7 ---
      8 
      9 - The Save to PDF feature was originally available in Fennec and users would
     10  like to see the return of this feature. There are a lot of user requests for
     11  Save to PDF in Fenix.
     12 - We would have more parity with Desktop, and be able to share the same
     13  underlying implementation with them.
     14 - Product is currently evaluating the addition of pdf.js as well; having Save
     15  to PDF would be an added bonus.
     16 
     17 Goals
     18 -----
     19 
     20 - Save the current page to a text-based PDF document.
     21 - Embedders should also be able to call into GeckoView to provide a PDF copy of
     22  the selected GeckoSession.
     23 - Enable the ability to iterate on PDF customizations.
     24 
     25 Non-Goals
     26 ---------
     27 
     28 - We do not want to implement a PDF “preview” of the document prior to the
     29  download. This has open questions: does Product want this, should this be
     30  implemented by the embedder, etc.
     31 - The generated PDF should not match the theme (e.g., light or dark mode) of
     32  the currently displayed page - the PDF will always appear as themeless or as
     33  a plain document.
     34 - No customizable settings. The current API design will not include
     35  customization settings that the embedder can control. This can be worked on
     36  in a follow-up feature request. Our current API design however, would enable
     37  for these particular iterations.
     38 
     39 What
     40 ----
     41 
     42 This work will add a method to ``GeckoSession`` called ``savePdf`` for
     43 embedders to use, which will communicate with a new ``GeckoViewPdf.sys.mjs`` to
     44 create the PDF file. When the document is available, the
     45 ``GeckoViewPdfController`` will notify the
     46 ``ContentDelegate.onExternalResponse`` with the downloadable document.
     47 
     48 - ``GeckoViewPdf.sys.mjs`` - JavaScript implementation that converts the content to
     49  a PDF and saves the file, also responds to messaging from
     50  ``GeckoViewPdfController``.
     51 - ``GeckoViewPdfController.java`` - The Controller coordinates between the Java
     52  and JS through response messaging and notifies the content delegate when the
     53  PDF is available for use.
     54 
     55 API
     56 ---
     57 
     58 GeckoSession.java
     59 ^^^^^^^^^^^^^^^^^
     60 
     61 .. code:: java
     62 
     63  public class GeckoSession {
     64    public GeckoSession(final @Nullable GeckoSessionSettings settings) {
     65      mPdfController = new PdfController(this);
     66    }
     67 
     68    @UiThread
     69    public void saveAsPdf(PdfSettings settings) {
     70      mPdfController.savePdf(null);
     71    }
     72  }
     73 
     74 
     75 GeckoViewPdf.sys.mjs
     76 ^^^^^^^^^^^^^^^^^^^^
     77 .. code:: java
     78 
     79  this.registerListener([
     80      "GeckoView:SavePdf",
     81    ]);
     82 
     83  async onEvent(aEvent, aData, aCallback) {
     84    debug`onEvent: event=${aEvent}, data=${aData}`;
     85 
     86    switch (aEvent) {
     87      case "GeckoView:SavePdf":
     88        this.saveToPDF();
     89        Break;
     90      }
     91    }
     92  }
     93 
     94  async saveToPDF() {
     95   // Reference: https://searchfox.org/mozilla-central/source/remote/cdp/domains/parent/Page.sys.mjs#519
     96  }
     97 
     98 
     99 GeckoViewPdfController.java
    100 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    101 .. code:: java
    102 
    103  class PdfController {
    104    private static final String LOGTAG = "PdfController";
    105    private final GeckoSession mSession;
    106 
    107    PdfController(final GeckoSession session) {
    108      mSession = session;
    109    }
    110 
    111    private PdfDelegate mDelegate;
    112    private BundleEventListener mEventListener;
    113 
    114    /* package */
    115    PdfController() {
    116      mEventListener = new EventListener();
    117      EventDispatcher.getInstance()
    118        .registerUiThreadListener(mEventListener,"GeckoView:PdfSaved");
    119    }
    120 
    121    @UiThread
    122    public void setDelegate(final @Nullable PdfDelegate delegate) {
    123      ThreadUtils.assertOnUiThread();
    124      mDelegate = delegate;
    125    }
    126 
    127    @UiThread
    128    @Nullable
    129    public PdfDelegate getDelegate() {
    130      ThreadUtils.assertOnUiThread();
    131      return mDelegate;
    132    }
    133 
    134    @UiThread
    135    public void savePdf() {
    136      ThreadUtils.assertOnUiThread();
    137      mEventDispatcher.dispatch("GeckoView:SavePdf", null);
    138    }
    139 
    140 
    141    private class EventListener implements BundleEventListener {
    142 
    143      @Override
    144      public void handleMessage(
    145        final String event,
    146        final GeckoBundle message,
    147        final EventCallback callback
    148      ) {
    149        if (mDelegate == null) {
    150          callback.sendError("Not allowed");
    151          return;
    152        }
    153 
    154        switch (event) {
    155          case "GeckoView:PdfSaved": {
    156            final ContentDelegate delegate = mSession.getContentDelegate();
    157 
    158            if (message.containsKey("pdfPath")) {
    159            InputStream inputStream; /* construct InputStream from local file path */
    160            WebResponse response = WebResponse.Builder()
    161              .body(inputStream)
    162              // Add other attributes as well.
    163              .build();
    164 
    165              if (delegate != null) {
    166                delegate.onExternalResponse(mSession, response);
    167              } else {
    168                throw Exception("Needs ContentDelegate for this to work.")
    169              }
    170            }
    171 
    172            break;
    173          }
    174        }
    175      }
    176    }
    177  }
    178 
    179 geckoview.js
    180 ^^^^^^^^^^^^
    181 .. code:: java
    182 
    183  {
    184    name: "GeckoViewPdf",
    185    onInit: {
    186       resource: "resource://gre/modules/GeckoViewPdf.sys.mjs",
    187    }
    188  }
    189 
    190 
    191 Testing
    192 -------
    193 
    194 - Tests for the sys.mjs and java code will be covered by mochitests and junit.
    195 - Make assertions to check that the text and images are in the finished PDF;
    196  the PDF is a non-zero file size.
    197 
    198 Risks
    199 -----
    200 
    201 The API and the code that this work would be using are pretty new, currently
    202 pref'd off in Nightly and could contain implementation bugs.