How can i get pdf version of webpage if NSExtensionJavaScriptPreprocessingFile is set?

214 Views Asked by At

I am making a Share extension for all possible types. Now I am stuck with sharing from Safari. I'm using "NSExtensionJavaScriptPreprocessingFile" to get title, favicon and html of common pages. But this option changes type of attachment from "com.adobe.pdf" to "com.apple.property-list" without pdf content.

2

There are 2 best solutions below

0
On

After taking some more insight I think I can safely say that this is a bug in Safari.

Safari sends only NSData that has 135 bytes. It can be converted to the property list that appears as NSDictionary, but it has only 4 keys that don't appear to hold any useful information ("version", "archiver", "objects" - it is nil, "top").

Developers can report it to Apple using the Feedback Assistant app.

1
On

Dropbox sets it to a JS file with the following code:

var WebSaverExtension = function() {};

function db_checkDefined(object) {
    return (typeof object !== "undefined") && (object !== null);
}

var db_baseURL = "";
var db_documentHTML = "";
var db_title = "";
var db_height = -1;
var db_width = -1;
var db_URL = "";

// This is used for unit tests. This must point to the document object that this script alters.
var unitTest_DBWebSaverSecurityTests_updatedDocument = "";

if (db_checkDefined(window) && db_checkDefined(window.location)) {
    db_URL = window.location.href;
}

if (db_checkDefined(document)) {
    // get width and height of element
    // documentClone doesn't have body defined so we get the body straight from the document
    if (db_checkDefined(document.body)) {
        db_height = document.body.scrollHeight > 0 ? document.body.scrollHeight : window.innerHeight;
        db_width = document.body.scrollWidth;
    }

    // get title
    if (db_checkDefined(document.title)) {
        db_title = document.title;
    }

    // get base url
    var baseArray = document.getElementsByTagName('base');
    if  (baseArray.length != 0) {
        db_baseURL = baseArray[0].href;
    } else {
        var href = db_URL;
        var index = href.lastIndexOf("/");
        if (index == -1) {
            db_baseURL = href;
        } else {
            db_baseURL = href.substring(0, index + 1);
        }
    }

    // documentElement should always exist for a html file
    if (db_checkDefined(document.documentElement)) {
        var documentClone = document.cloneNode(true);

        // for unit tests to test this document object
        unitTest_DBWebSaverSecurityTests_updatedDocument = documentClone;

        // set the image sizes so they don't change size when we reload them
        // getElementsByTagName returns nodes in the order in which they would be encountered in a
        // preorder traversal of the Document tree.
        var images = document.documentElement.getElementsByTagName('img');

        // don't bother. This will be a timeout
        if (images.length <= 10000) {
            var imagesCloned = documentClone.documentElement.getElementsByTagName('img');
            for (var i = images.length-1; i >= 0; i--) {
                imagesCloned[i].style.height = images[i].scrollHeight + "px";
                imagesCloned[i].style.width = images[i].scrollWidth + "px";
            }
        }

        var tags = [ "header", "div", "span", "footer", "iframe" ];
        for (var iTag = 0; iTag < tags.length; iTag++) {
            var tag = tags[iTag];
            var elements = document.documentElement.getElementsByTagName(tag);

            // don't bother. This will be a timeout
            if (elements.length > 10000) {
                continue;
            }

            var elementsCloned = documentClone.documentElement.getElementsByTagName(tag);

            for (var i = elements.length-1; i >= 0; i--) {
                var element = elements[i];
                var elementClone = elementsCloned[i];

                if (db_checkDefined(element) && db_checkDefined(elementClone) && db_checkDefined(element.style)) {
                    var height = window.getComputedStyle(element).getPropertyValue('height');

                    if (db_checkDefined(height)) {
                        var minHeight = window.getComputedStyle(element).getPropertyValue('min-height');
                        if (db_checkDefined(minHeight)) {
                            elementClone.style.minHeight = height;
                        }

                        elementClone.style.height = height;

                        var maxHeight = window.getComputedStyle(element).getPropertyValue('max-height');
                        if (db_checkDefined(maxHeight)) {
                            elementClone.style.maxHeight = height;
                        }
                    }

                    var width = window.getComputedStyle(element).getPropertyValue('width');
                    if (db_checkDefined(width)) {
                        var minWidth = window.getComputedStyle(element).getPropertyValue('min-width');
                        if (db_checkDefined(minWidth)) {
                            elementClone.style.minWidth = width;
                        }
                    }
                }
            }
        }

        // remove script tags
        var scripts = documentClone.documentElement.getElementsByTagName('script');
        for (var i = scripts.length-1; i >= 0; i--) {
            scripts[i].parentNode.removeChild(scripts[i]);
        }

        // disable scripts in iframes (can cause Javascript selected ads to not be shown)
        // we can't access the html in the iframe so we have to live with it being reloaded but we can prevent
        // Javascript from being executed again
        var iframes = documentClone.documentElement.getElementsByTagName('iframe');
        for (var i = iframes.length-1; i >= 0; i--) {
            var iframe = iframes[i];
            iframe.sandbox = true;
        }

        // Add csp policy to prevent script loading

        // safari should auto create head if the html is missing it
        // this is here for safety
        var head = documentClone.head;
        if (!db_checkDefined(head)) {
            // make a head
            head = documentClone.createElement('head');

            // if firstchild is null, insertBefore adds to end of children list
            documentClone.documentElement.insertBefore(head, documentClone.documentElement.firstChild);
        }

        // http://www.w3.org/TR/CSP2/#delivery-html-meta-element
        // Note: Having multiple policies is ok
        var metaElement = documentClone.createElement('meta');
        metaElement.httpEquiv = 'Content-Security-Policy';
        metaElement.content = "script-src 'none'";

        // if firstchild is null, insertBefore adds to end of children list
        head.insertBefore(metaElement, head.firstChild);

        // get the changed html
        if (db_checkDefined(documentClone.documentElement.outerHTML)) {
            db_documentHTML = documentClone.documentElement.outerHTML;
        }
    }
}

WebSaverExtension.prototype = {
    run: function(arguments) {
        arguments.completionFunction({"baseURL": db_baseURL,
                                     "URL": db_URL,
                                     "html": db_documentHTML,
                                     "title": db_title,
                                     "height": db_height,
                                     "width": db_width});
    },
    finalize: function(arguments) {

    }
}

var ExtensionPreprocessingJS = new WebSaverExtension;

It nicely creates a PDF file out of the web page in Safari, but just creates a .url file when used in Chrome.