"use strict";
var INFO =
["plugin", { name: "flashblock",
             version: "1.3",
             href: "http://dactyl.sf.net/pentadactyl/plugins#flashblock-plugin",
             summary: "Flash Blocker",
             xmlns: "dactyl" },
    ["author", { email: "maglione.k@gmail.com" },
        "Kris Maglione"],
    ["license", { href: "http://opensource.org/licenses/mit-license.php" },
        "MIT"],
    ["project", { name: "Pentadactyl", "min-version": "1.0" }],
    ["p", {},
        "This plugin provides the same features as the ever popular FlashBlock ",
        "Firefox add-on. Place holders are substituted for flash animations and ",
        "embedded videos. When clicked, the original embedded content is ",
        "restored. Additionally, this plugin provides options to control which ",
        "sites can play animations without restrictions and triggers to toggle ",
        "the playing of animations on the current page."],

    ["item", {},
        ["tags", {}, "'fb' 'flashblock'"],
        ["spec", {}, "'flashblock' 'fb'"],
        ["type", {}, "boolean"],
        ["default", {}, "true"],
        ["description", {},
            ["p", {},
                "Controls the blocking of flash animations. When true, place ",
                "holders are substituted for flash animations on untrusted sites."]]],

    ["item", {},
        ["tags", {}, "'fbw' 'fbwhitelist'"],
        ["spec", {}, "'fbwhitelist' 'fbw'"],
        ["type", {}, "sitelist"],
        ["default", {}, ""],
        ["description", {},
            ["p", {},
                "Controls which sites may play flash animations without user ",
                "intervention. See ", ["ex", {}, ":mk" + config.name.toLowerCase() + "rc"], "."]]],

    ["item", {},
        ["tags", {}, ":flashplay :flp"],
        ["strut"],
        ["spec", {}, ":flashplay"],
        ["description", {},
            ["p", {},
                "Plays any blocked flash animations on the current page."]]],

    ["item", {},
        ["tags", {}, ":flashstop :fls"],
        ["strut"],
        ["spec", {}, ":flashstop"],
        ["description", {},
            ["p", {},
                "Stops any currently playing flash animations on the current ",
                "page."]]],

    ["item", {},
        ["tags", {}, ":flashtoggle :flt"],
        ["strut"],
        ["spec", {}, ":flashtoggle"],
        ["description", {},
            ["p", {},
                "Toggles the playing of all animations on the current page. If ",
                "any flash animations are currently blocked, all may begin ",
                "playing. Otherwise, all animations are stopped."],

            ["example", {},
                ["ex", {}, ":map"], " -silent ",
                ["k", { name: "A-p", link: "false" }],
                " ", ["ex", {}, ":flashtoggle"],
                ["k", { name: "CR" }]]]]];

group.options.add(["flashblock", "fb"],
    "Enable blocking of flash animations",
    "boolean", true,
    { setter: reload });
group.options.add(["fbwhitelist", "fbw"],
    "Sites which may run flash animations without prompting",
    "sitelist", "",
    {
        completer: context => completion.visibleHosts(context),
        privateData: true,
        setter: reload,
        validator: () => true,
    });

["Play", "Stop"].forEach(action => {
    group.commands.add(["flash" + action, "fl" + action[0]].map(String.toLowerCase),
        action + " all flash animations on the current page",
        function () { postMessage(content, "flashblock" + action) },
        { argCount: "0" }, true);
});

group.commands.add(["flashtoggle", "flt"],
    "Toggle playing of flash animations on the current page",
    function () {
        if (buffer.allFrames().some(w => DOM("pseudoembed", w.document).length))
            commands.get("flashplay").action();
        else
            commands.get("flashstop").action();
    },
    { argCount: "0" }, true);

group.mappings.add([modes.NORMAL], ["<Leader>fbwhitelist"],
    "Add the current site to the flash whitelist",
    function () { whitelist.op("+", whitelist.parse(content.location.hostname)) });
group.mappings.add([modes.NORMAL], ["<Leader>fbWhitelist"],
    "Toggle the current site in the flash whitelist",
    function () {
        let host = content.location.hostname;
        if (!removeHost(host))
            whitelist.op("+", whitelist.parse(host));
    });

var enabled = options.get("flashblock");
var whitelist = options.get("fbwhitelist");
function postMessage(content, message) {
    buffer.allFrames(content).forEach(f => { f.postMessage(message, "*"); });
}
function reload(values) {
    //for (let [,t] in tabs.browsers)
    //    t.contentWindow.postMessage("flashblockReload", "*");
    postMessage(gBrowser.mCurrentBrowser.contentWindow, "flashblockReload");
    return values;
}

function removeHost(host) {
    let len = whitelist.value.length;
    let uri = util.createURI(host);
    whitelist.value = whitelist.value.filter(f => !f(uri));
    return whitelist.value.length != len;
}

function onUnload() {
    group.events.unlisten(null);
}
group.events.listen(window, "flashblockCheckLoad",
    function checkLoadFlash(event) {
        if(!enabled.value || whitelist.getKey(event.target.documentURIObject))
            event.preventDefault();
        event.stopPropagation();
    }, true, true);

var data = {
    bindings: "dactyl://data/text/xml," + encodeURIComponent('<?xml version="1.0"?>' +
      String.raw`
        <bindings
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xbl="http://www.mozilla.org/xbl"
           xmlns:html="http://www.w3.org/1999/xhtml">

          <binding id="flash">
            <implementation>
              <constructor>
                <![CDATA[
                    var myDocument = XPCNativeWrapper(document);
                    var myWindow = XPCNativeWrapper(window);

                    function copyAttribs(to, from) {
                        Array.map(from.attributes, function(attrib) {
                            to.setAttribute(attrib.name, attrib.value);
                        });
                    }
                    function capitalize(str) { return str[0].toUpperCase() + str.substr(1) };

                    function Placeholder(embed) {
                        var self = this;
                        this.embed = embed;

                        if (!document.flashblockStyle) {
                            var head = document.getElementsByTagName("head")[0];
                            var node = document.createElement("style");
                            node.setAttribute("type", "text/css");
                            head.insertBefore(node, head.firstChild);
                            document.flashblockStyle = node.sheet;
                        }

                        document.flashblockIdx = (document.flashblockIdx || 0) + 1;
                        this.idx = document.flashblockIdx;
                        embed.setAttribute("flashblock", this.idx);

                        document.flashblockStyle.insertRule("pseudoembed[flashblock='" + this.idx + "'] {}", 0);
                        this.style = document.flashblockStyle.cssRules[0].style;

                        this.div = myDocument.createElement('pseudoembed');
                        this.div.addEventListener("click", function() { self.showEmbed(true) }, true);
                        this.div.flashblockEmbed = embed;
                    }
                    Placeholder.prototype = {
                        showEmbed: function(clicked) {
                            this.embed.clicked = clicked;
                            if (this.embed.parentNode)
                                return;
                            copyAttribs(this.embed, this.div);
                            this.div.parentNode.replaceChild(this.embed, this.div);
                        },
                        hideEmbed: function() {
                            let parent = this.embed.parentNode;
                            if (!parent)
                                return;

                            this.div.setAttribute("embedtype", this.embed.localName);
                            copyAttribs(this.div, this.embed);

                            ['width', 'height'].forEach(function(dimen) {
                                this.style[dimen] = "";
                                if (this.embed[dimen])
                                    if (/%$/.test(this.embed[dimen]))
                                        this.style[dimen] = this.embed[dimen];
                                    else
                                        this.style[dimen] = parseInt(this.embed[dimen]) + "px";
                            }, this);

                            let style = myWindow.getComputedStyle(parent, "");
                            if (style.getPropertyValue("text-align") == "center") {
                                this.style.marginRight = "auto";
                                this.style.marginLeft = "auto";
                            }

                            parent.replaceChild(this.div, this.embed);
                        }
                    }

                    var parent = this.parentNode
                    var self = this;
                    if (!this.getAttribute("flashblock"))
                        this.setAttribute("flashblock", true);
                    if (this.placeholder || parent.placeholder)
                        return;
                    this.placeholder = new Placeholder(self);

                    function checkReplace(e) {
                        if (!e || e.data == "flashblockReload") {
                            if (self.clicked)
                                return;
                            let event = myDocument.createEvent("UIEvents");
                            event.initEvent("flashblockCheckLoad", true, true);
                            myDocument.dispatchEvent(event);
                            if (event.getPreventDefault())
                                self.placeholder.showEmbed();
                            else
                                self.placeholder.hideEmbed();
                          }
                          else if (e.data == "flashblockPlay")
                              self.placeholder.showEmbed(true);
                          else if (e.data == "flashblockStop")
                              self.placeholder.hideEmbed();
                    }
                    checkReplace();
                    myWindow.addEventListener("message", checkReplace, false);

                    // if(this.src == this.ownerDocument.location)
                    //     myWindow.location = 'dactyl://data/application/xhtml+xml,' + encodeURIComponent('<?xml version="1.0" encoding="UTF-8"?>' +
                    //                         '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">' +
                    //         <html xmlns="http://www.w3.org/1999/xhtml">
                    //             <head><title></title></head>
                    //             <body>{new XML(parent.innerHTML)}</body>
                    //         </html>);
                ]]>
              </constructor>
            </implementation>
          </binding>
        </bindings>
      `),
    flash: `data:image/png;base64,
[encoded data, 5355 characters]
    `,
    play: `data:image/png;base64,
[encoded data, 5355 characters]
        AAMACHALg12qSjsAAAAASUVORK5CYII
    `,
};

var CSS =
    /*
     * Flash Click to View by Ted Mielczarek (luser_mozilla@perilith.com)
     * Original code by Jesse Ruderman (jruderman@hmc.edu)
     * taken from http://www.squarefree.com/userstyles/xbl.html
     *
     * Change XBL binding for <object> tags, click to view flash
     */
    String.raw`

    pseudoembed {
            display: inline-block;
            min-width: 32px !important;
            min-height: 32px !important;
            border: 1px solid #dfdfdf;
            cursor: pointer;
            overflow: hidden;
            -moz-box-sizing: border-box;
            background: url("${data.play}") no-repeat center;
    }
    pseudoembed:hover {
            background-image: url("${data.flash}");
    }

    video,
    object[classid*=":D27CDB6E-AE6D-11cf-96B8-444553540000"],
    object[codebase*="swflash.cab"],
    object[data*=".swf"],
    embed[type="application/x-shockwave-flash"],
    embed[src*=".swf"],
    object[type="application/x-shockwave-flash"],
    object[src*=".swf"] {
            -moz-binding: url("{bindings}") !important;
    }

    /// TODO: Could do better.
    /// NoScript is incredibly annoying. The binding can't execute JS on
    /// untrusted sites.
    video:not([flashblock]),
    object[classid*=":D27CDB6E-AE6D-11cf-96B8-444553540000"]:not([flashblock]),
    object[codebase*="swflash.cab"]:not([flashblock]),
    object[data*=".swf"]:not([flashblock]),
    embed[type="application/x-shockwave-flash"]:not([flashblock]),
    embed[src*=".swf"]:not([flashblock]),
    object[type="application/x-shockwave-flash"]:not([flashblock]),
    object[src*=".swf"]:not([flashblock]) {
        display: none !important;
    }

    /// Java identifiers.
    /// TODO: Make this work.
    /// applet,
    /// object[classid*=":8AD9C840-044E-11D1-B3E9-00805F499D93"],
    /// object[classid^="clsid:CAFEEFAC-"],
    /// object[classid^="java:"],
    /// object[type="application/x-java-applet"],
    /// embed[classid*=":8AD9C840-044E-11D1-B3E9-00805F499D93"],
    /// embed[classid^="clsid:CAFEEFAC-"],
    /// embed[classid^="java:"],
    /// embed[type="application/x-java-applet"]
    /// {
    ///      -moz-binding: url("{bindings}") !important;
    /// }
`.replace(/\/\/\/.*/gm, "");

styles.system.add("flashblock", "*", CSS);
data = null;
CSS = null;

/* vim:se sts=4 sw=4 et: */
