202 lines
5.4 KiB
ReStructuredText
202 lines
5.4 KiB
ReStructuredText
GeckoView Save to PDF
|
|
=====================
|
|
|
|
Olivia Hall <ohall@mozilla.com>, Jonathan Almeida <jon@mozilla.com>
|
|
|
|
Why
|
|
---
|
|
|
|
- The Save to PDF feature was originally available in Fennec and users would
|
|
like to see the return of this feature. There are a lot of user requests for
|
|
Save to PDF in Fenix.
|
|
- We would have more parity with Desktop, and be able to share the same
|
|
underlying implementation with them.
|
|
- Product is currently evaluating the addition of pdf.js as well; having Save
|
|
to PDF would be an added bonus.
|
|
|
|
Goals
|
|
-----
|
|
|
|
- Save the current page to a text-based PDF document.
|
|
- Embedders should also be able to call into GeckoView to provide a PDF copy of
|
|
the selected GeckoSession.
|
|
- Enable the ability to iterate on PDF customizations.
|
|
|
|
Non-Goals
|
|
---------
|
|
|
|
- We do not want to implement a PDF “preview” of the document prior to the
|
|
download. This has open questions: does Product want this, should this be
|
|
implemented by the embedder, etc.
|
|
- The generated PDF should not match the theme (e.g., light or dark mode) of
|
|
the currently displayed page - the PDF will always appear as themeless or as
|
|
a plain document.
|
|
- No customizable settings. The current API design will not include
|
|
customization settings that the embedder can control. This can be worked on
|
|
in a follow-up feature request. Our current API design however, would enable
|
|
for these particular iterations.
|
|
|
|
What
|
|
----
|
|
|
|
This work will add a method to ``GeckoSession`` called ``savePdf`` for
|
|
embedders to use, which will communicate with a new ``GeckoViewPdf.sys.mjs`` to
|
|
create the PDF file. When the document is available, the
|
|
``GeckoViewPdfController`` will notify the
|
|
``ContentDelegate.onExternalResponse`` with the downloadable document.
|
|
|
|
- ``GeckoViewPdf.sys.mjs`` - JavaScript implementation that converts the content to
|
|
a PDF and saves the file, also responds to messaging from
|
|
``GeckoViewPdfController``.
|
|
- ``GeckoViewPdfController.java`` - The Controller coordinates between the Java
|
|
and JS through response messaging and notifies the content delegate when the
|
|
PDF is available for use.
|
|
|
|
API
|
|
---
|
|
|
|
GeckoSession.java
|
|
^^^^^^^^^^^^^^^^^
|
|
|
|
.. code:: java
|
|
|
|
public class GeckoSession {
|
|
public GeckoSession(final @Nullable GeckoSessionSettings settings) {
|
|
mPdfController = new PdfController(this);
|
|
}
|
|
|
|
@UiThread
|
|
public void saveAsPdf(PdfSettings settings) {
|
|
mPdfController.savePdf(null);
|
|
}
|
|
}
|
|
|
|
|
|
GeckoViewPdf.sys.mjs
|
|
^^^^^^^^^^^^^^^^^^^^
|
|
.. code:: java
|
|
|
|
this.registerListener([
|
|
"GeckoView:SavePdf",
|
|
]);
|
|
|
|
async onEvent(aEvent, aData, aCallback) {
|
|
debug`onEvent: event=${aEvent}, data=${aData}`;
|
|
|
|
switch (aEvent) {
|
|
case "GeckoView:SavePdf":
|
|
this.saveToPDF();
|
|
Break;
|
|
}
|
|
}
|
|
}
|
|
|
|
async saveToPDF() {
|
|
// Reference: https://searchfox.org/mozilla-central/source/remote/cdp/domains/parent/Page.sys.mjs#519
|
|
}
|
|
|
|
|
|
GeckoViewPdfController.java
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.. code:: java
|
|
|
|
class PdfController {
|
|
private static final String LOGTAG = "PdfController";
|
|
private final GeckoSession mSession;
|
|
|
|
PdfController(final GeckoSession session) {
|
|
mSession = session;
|
|
}
|
|
|
|
private PdfDelegate mDelegate;
|
|
private BundleEventListener mEventListener;
|
|
|
|
/* package */
|
|
PdfController() {
|
|
mEventListener = new EventListener();
|
|
EventDispatcher.getInstance()
|
|
.registerUiThreadListener(mEventListener,"GeckoView:PdfSaved");
|
|
}
|
|
|
|
@UiThread
|
|
public void setDelegate(final @Nullable PdfDelegate delegate) {
|
|
ThreadUtils.assertOnUiThread();
|
|
mDelegate = delegate;
|
|
}
|
|
|
|
@UiThread
|
|
@Nullable
|
|
public PdfDelegate getDelegate() {
|
|
ThreadUtils.assertOnUiThread();
|
|
return mDelegate;
|
|
}
|
|
|
|
@UiThread
|
|
public void savePdf() {
|
|
ThreadUtils.assertOnUiThread();
|
|
mEventDispatcher.dispatch("GeckoView:SavePdf", null);
|
|
}
|
|
|
|
|
|
private class EventListener implements BundleEventListener {
|
|
|
|
@Override
|
|
public void handleMessage(
|
|
final String event,
|
|
final GeckoBundle message,
|
|
final EventCallback callback
|
|
) {
|
|
if (mDelegate == null) {
|
|
callback.sendError("Not allowed");
|
|
return;
|
|
}
|
|
|
|
switch (event) {
|
|
case "GeckoView:PdfSaved": {
|
|
final ContentDelegate delegate = mSession.getContentDelegate();
|
|
|
|
if (message.containsKey("pdfPath")) {
|
|
InputStream inputStream; /* construct InputStream from local file path */
|
|
WebResponse response = WebResponse.Builder()
|
|
.body(inputStream)
|
|
// Add other attributes as well.
|
|
.build();
|
|
|
|
if (delegate != null) {
|
|
delegate.onExternalResponse(mSession, response);
|
|
} else {
|
|
throw Exception("Needs ContentDelegate for this to work.")
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
geckoview.js
|
|
^^^^^^^^^^^^
|
|
.. code:: java
|
|
|
|
{
|
|
name: "GeckoViewPdf",
|
|
onInit: {
|
|
resource: "resource://gre/modules/GeckoViewPdf.sys.mjs",
|
|
}
|
|
}
|
|
|
|
|
|
Testing
|
|
-------
|
|
|
|
- Tests for the sys.mjs and java code will be covered by mochitests and junit.
|
|
- Make assertions to check that the text and images are in the finished PDF;
|
|
the PDF is a non-zero file size.
|
|
|
|
Risks
|
|
-----
|
|
|
|
The API and the code that this work would be using are pretty new, currently
|
|
pref'd off in Nightly and could contain implementation bugs.
|