Nathaniel M. Beaver
Nathaniel M. Beaver

Reputation: 755

generating HTML in WebExtension results in `this.mDialog is null` and `can't access dead object`

Using a WebExtension, I am trying to programmatically generate an HTML file like this:

<html><head><meta><title>example</title></head><body><p>Hello, world!</p></body></html>

and then download it using this method. (Background: I am generating a client-side redirect.)

Here's my manifest.json:

{
  "name": "Download HTML",
  "description": "Generates an HTML file and downloads it",
  "manifest_version": 2,
  "version": "0.1",
  "permissions": [
    "activeTab"
  ],
  "browser_action": {
    "default_title": "Download HTML"
  },
  "background": {
    "scripts": ["background.js"]
  }
}

and here's my background.js:

function downloadHTML(tab) {
  console.log('Begin downloadHTML()')
  function generateHTML(title) {
    var newHTML = document.createElement('html');
    var newHead = document.createElement('head');
    var newTitle = document.createElement('title');
    newTitle.text = title;
    var newMeta = document.createElement('meta');
    var newBody = document.createElement('body');
    var newPar = document.createElement('p');
    var newText = document.createTextNode('Hello, world!');
    newPar.appendChild(newText);
    newBody.appendChild(newPar);
    newHead.appendChild(newMeta);
    newHead.appendChild(newTitle);
    newHTML.append(newHead);
    newHTML.append(newBody);
    return newHTML;
  }
  // Now make an anchor to click to download the HTML.
  var tempAnchor = document.createElement('a');
  var myHTML = generateHTML(tab.title);
  var HTMLBlob = new Blob([myHTML.outerHTML], {type: 'text/html'});
  tempAnchor.href = URL.createObjectURL(HTMLBlob);
  var filename = 'index.html';
  tempAnchor.download = filename
  tempAnchor.style.display = 'none';
  document.body.appendChild(tempAnchor);
  tempAnchor.click();
  document.body.removeChild(tempAnchor);
  console.log('End downloadHTML()')
}
// Add downloadHTML() as a listener to clicks on the browser action.
browser.browserAction.onClicked.addListener(downloadHTML);

I've run the extension on this page:

http://info.cern.ch/hypertext/WWW/TheProject.html

When I do so, the browser console confirms that the function runs from start to finish.

Begin downloadHTML() background.js:2:3
End downloadHTML() background.js:31:3

However, there is no download prompt, and I receive this error message:

TypeError: this.mDialog is null [Learn More] nsHelperAppDlg.js:173:5 

The "Learn More" just links to "TypeError":

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Unexpected_type

When I debug the extension, I find these messages instead.

Webconsole context has changed

TypeError: can't access dead object [Learn More] accessible.js:140:5 

The "Learn More" links to information about dead objects.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Dead_object

I'm guessing that the HTML elements are not persisting between calls to document.creatElement(). I've tried moving the body of generateHTML() out into its parent function, but that did not help.

How can I make sure the HTML persists? I don't want to modify the actual tab HTML, I just want to access the tab's title (and eventually URL). I looked at the Web Storage API, but I want to store HTML, not key-value pairs.

I am running Firefox version 63.0.3.

Upvotes: 0

Views: 161

Answers (1)

erosman
erosman

Reputation: 7771

You need an actual DOM in order to click it.

Anyway ... it can be a lot simpler

browser.browserAction.onClicked.addListener(downloadHTML);

function downloadHTML() {

  // download to file, downloads string (not DOM)
  const data = '<html><head><meta><title>example</title></head><body><p>Hello, world!</p></body></html>';
  const blob = new Blob([data], {type : 'text/plain;charset=utf-8'});
  const filename = 'something.html';

  // both are fine: chrome.downloads.download or browser.downloads.download
  // requires "downloads" permission
  browser.downloads.download({
    url: URL.createObjectURL(blob),
    filename,
    saveAs: true,  // pops up a Save As dialog, omitting it will save the file to the Downloads folder set in Firefox Options
    conflictAction: 'uniquify' // renames file if filename already exists
  });  
}

If you want to show an actual page on browserAction then it would be easier to set a "default_popup" in manifest.json

Upvotes: 1

Related Questions