2

I'm trying to test the feasibility of using the library jsPDF to render a PDF from the HTML rendered by an LWC. I've created a simple test project in a scratch org as follows:

  1. Created static resources for jsPDF and html2canvas, which is needed by jsPDF to read rendered html.

  2. Created a shadow-dom LWC to act as the parent, since Light Dom components cannot be top-level components.

// shadowParent.html
<template>
  <c-light-child ></c-light-child>
</template>
//shadowParent.js
import {LightningElement} from 'lwc';

export default class ShadowParent extends LightningElement {

}
  1. Created a light-dom LWC component to render some content and PDF it:
lightChild.html
<template lwc:render-mode="light">
  <div class="main-container">
    <h1>Light Dom Component</h1>
    <div for:each={sections} for:item="section" key={section}>
      <h2>{section}</h2>
      <p>This is the {section} section.</p>
    </div>
  </div>
  <button onclick={testPdf}>Test</button>
</template>
import {LightningElement} from 'lwc';
import { loadScript } from "lightning/platformResourceLoader";
import JS_PDF from '@salesforce/resourceUrl/jspdf';
import HTML2CANVAS from '@salesforce/resourceUrl/html2canvas';

export default class LightChild extends LightningElement {
    static renderMode = 'light';
    jsPdfInitialized = false;

    sections = ['Heading One', 'Heading Two', 'Heading Three'];
    renderedCallback() {
        if (this.jsPdfInitialized) {
            return;
        }
        this.jsPdfInitialized = true;
        Promise.all([
            loadScript(this, JS_PDF),
            loadScript(this, HTML2CANVAS)
        ]).then(() => {
            console.log('jsPdf loaded.');
        });
    }

    testPdf() {
        const content = this.querySelector('.main-container');
        const { jsPDF } = window.jspdf;
        const doc = new jsPDF();
        doc.html(content, {
            callback: function() {
                console.log('callback...');
                doc.save();
                console.log('complete!');
            }
        });
    }
}

Console logging has shown me that I appear to be getting a valid jsPDF object assigned to doc, but no logging occurs from the callback; instead I get the error Lightning Web Security: Cannot access write:

aura_prod.js:53 Uncaught (in promise) Error: Lightning Web Security: Cannot access write.
    at HTMLDocument.<anonymous> (aura_prod.js:53:2040)
    at Object.apply (aura_prod.js:59:36251)
    at aura_prod.js:59:21437
    at wt.eval [as St] (eval at bc (aura_prod.js:59:27602), <anonymous>:3:4554)
    at wt.eval (eval at bc (aura_prod.js:59:27602), <anonymous>:3:3993)
    at fn.toIFrame (eval at <anonymous> (eval at bc (aura_prod.js:59:27602)), <anonymous>:20:147482)
    at eval (eval at <anonymous> (eval at bc (aura_prod.js:59:27602)), <anonymous>:20:194607)
    at eval (eval at <anonymous> (eval at bc (aura_prod.js:59:27602)), <anonymous>:20:1976)
    at Object.eval [as next] (eval at <anonymous> (eval at bc (aura_prod.js:59:27602)), <anonymous>:20:2081)
    at eval (eval at <anonymous> (eval at bc (aura_prod.js:59:27602)), <anonymous>:20:1023)

At this point, I'm not sure how to troubleshoot further. LWS runs everything in JS "Sandboxes", so all the stack trace lines point to aura_prod.js instead of my code or the jsPDF code. The error indicates that a 'write' method is being called which is being disallowed, but I don't know where or why. I had hoped that by using Light DOM, jsPDF would be able to modify the DOM (I think it renders the HTML to a canvas element).

I've tried running in Debug mode; this causes hundreds of "Mutations on the membrane of an object originating outside of the sandbox will not be reflected on the object itself" messages, but no additional information about the actual error above.

I've tried turning off LWS and running under Locker Service; this causes loadScript to blow up with an undocumented error, plus I don't expect LS to allow the DOM manipulation jsPDF needs.

What are my options for tracking down and possibly addressing this error?

4
  • 1
    As far as I can tell, jsPDF creates an iframe, writes to it, and then passes that to html2canvas. The clue for this is the HTMLDocument error with the write function being blocked. This is a security feature to prevent breaking out of the LS/LWS sandbox. You need to use a non-iframe method of rendering. I don't have a good recommendation off the top of my head, though.
    – sfdcfox
    Commented Sep 12, 2022 at 1:21
  • @sfdcfox Thanks, that makes sense and tracks with what I know. I just wish I could see exactly where that's happening. Debugging under LWS is eluding me so far. I think I'll experiment with calling html2canvas directly. Commented Sep 12, 2022 at 2:32
  • @JasonClark Were you able to resolve this? Commented May 25, 2023 at 7:01
  • 1
    @SidFortunato if you mean LightDom debugging generally, no. If you mean getting jsPDF to work in a lightning component, also no, but I did get it work in a VF Page. My content was rendered emails which included images behind a servlet.FileDownload url so I had to customize html2canvas to use SalesforceProxy headers and pass it a Proxy function to fetch them and convert them to data: urls, and I ended up using pdflib to combine multiple pages into one PDF since I coudn't get jsPDF to handle mutlipage output correctly, but it is possible to use jsPDF from a VF page. Commented May 25, 2023 at 13:36

0

You must log in to answer this question.

Browse other questions tagged .