/*
* This file is part of Adblock Plus ,
* Copyright (C) 2006-2014 Eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see .
*/
/**
* @fileOverview This emulates a subset of the CustomizableUI API from Firefox 28.
*/
let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", null);
let {Utils} = require("utils");
// UI module has to be referenced lazily to avoid circular references
XPCOMUtils.defineLazyGetter(this, "UI", () => require("ui").UI);
let widgets = Map();
function getToolbox(/**Window*/ window, /**Widget*/ widget) /**Element*/
{
if (!("defaultArea" in widget) || !widget.defaultArea)
return null;
let toolbar = UI.findElement(window, widget.defaultArea);
if (!toolbar)
return null;
let toolbox = toolbar.toolbox;
if (toolbox && ("palette" in toolbox) && toolbox.palette)
return toolbox;
else
return null;
}
function getToolbar(/**Element*/ element) /**Element*/
{
for (let parent = element.parentNode; parent; parent = parent.parentNode)
if (parent.localName == "toolbar")
return parent;
return null;
}
function getPaletteItem(/**Element*/ toolbox, /**String*/ id) /**Element*/
{
for (let child of toolbox.palette.children)
if (child.id == id)
return child;
return null;
}
function restoreWidget(/**Element*/ toolbox, /**Widget*/ widget)
{
// Create node
let node = widget.onBuild(toolbox.ownerDocument);
// Insert into the palette first
toolbox.palette.insertBefore(node, toolbox.palette.firstChild);
// Now find out where we should put it
let position = toolbox.getAttribute(widget.positionAttribute);
if (!/^\S*,\S*,\S*$/.test(position))
position = null;
if (position == null)
{
// No explicitly saved position but maybe we can find it in a currentset
// attribute somewhere.
let toolbars = toolbox.externalToolbars.slice();
for (let child of toolbox.children)
if (child.localName == "toolbar")
toolbars.push(child);
for (let toolbar of toolbars)
{
let currentSet = toolbar.getAttribute("currentset");
if (currentSet)
{
let items = currentSet.split(",");
let index = items.indexOf(widget.id);
if (index >= 0)
{
let before = (index + 1 < items.length ? items[index + 1] : "");
position = "visible," + toolbar.id + "," + before;
toolbox.setAttribute(widget.positionAttribute, position);
toolbox.ownerDocument.persist(toolbox.id, widget.positionAttribute);
break;
}
}
}
}
showWidget(toolbox, widget, position);
}
function showWidget(/**Element*/ toolbox, /**Widget*/ widget, /**String*/ position)
{
let visible = "visible", parent = null, before = null;
if (position)
{
[visible, parent, before] = position.split(",", 3);
parent = toolbox.ownerDocument.getElementById(parent);
if (before == "")
before = null;
else
before = toolbox.ownerDocument.getElementById(before);
if (before && before.parentNode != parent)
before = null;
}
if (visible == "visible" && !parent)
{
let insertionPoint = {
parent: widget.defaultArea
};
if (typeof widget.defaultBefore != "undefined")
insertionPoint.before = widget.defaultBefore;
if (typeof widget.defaultAfter != "undefined")
insertionPoint.after = widget.defaultAfter;
[parent, before] = UI.resolveInsertionPoint(toolbox.ownerDocument.defaultView, insertionPoint);
}
if (parent && parent.localName != "toolbar")
parent = null;
if (visible != "visible")
{
// Move to palette if the item is currently visible
let node = toolbox.ownerDocument.getElementById(widget.id);
if (node)
toolbox.palette.appendChild(node);
}
else if (parent)
{
// Add the item to the toolbar
let items = parent.currentSet.split(",");
let index = (before ? items.indexOf(before.id) : -1);
if (index < 0)
before = null;
parent.insertItem(widget.id, before, null, false);
}
saveState(toolbox, widget);
}
function removeWidget(/**Window*/ window, /**Widget*/ widget)
{
let element = window.document.getElementById(widget.id);
if (element)
element.parentNode.removeChild(element);
let toolbox = getToolbox(window, widget);
if (toolbox)
{
let paletteItem = getPaletteItem(toolbox, widget.id);
if (paletteItem)
paletteItem.parentNode.removeChild(paletteItem);
}
}
function onToolbarCustomization(/**Event*/ event)
{
let toolbox = event.currentTarget;
for (let [id, widget] of widgets)
saveState(toolbox, widget);
}
function saveState(/**Element*/ toolbox, /**Widget*/ widget)
{
let node = toolbox.ownerDocument.getElementById(widget.id);
let position = toolbox.getAttribute(widget.positionAttribute) || "hidden,,";
if (node && node.parentNode.localName != "toolbarpalette")
{
if (typeof widget.onAdded == "function")
widget.onAdded(node)
let toolbar = getToolbar(node);
position = "visible," + toolbar.id + "," + (node.nextSibling ? node.nextSibling.id : "");
}
else
position = position.replace(/^visible,/, "hidden,")
toolbox.setAttribute(widget.positionAttribute, position);
toolbox.ownerDocument.persist(toolbox.id, widget.positionAttribute);
}
let CustomizableUI = exports.CustomizableUI =
{
createWidget: function(widget)
{
if (typeof widget.id == "undefined" ||
typeof widget.defaultArea == "undefined" ||
typeof widget.positionAttribute == "undefined")
{
throw new Error("Unexpected: required property missing from the widget data");
}
widgets.set(widget.id, widget);
// Show widget in any existing windows
for (let window of UI.applicationWindows)
{
let toolbox = getToolbox(window, widget);
if (toolbox)
{
toolbox.addEventListener("aftercustomization", onToolbarCustomization, false);
restoreWidget(toolbox, widget);
}
}
},
destroyWidget: function(id)
{
// Don't do anything here. This function is called on shutdown,
// removeFromWindow will take care of cleaning up already.
},
getPlacementOfWidget: function(id)
{
let window = UI.currentWindow;
if (!window)
return null;
let widget = window.document.getElementById(id);
if (!widget)
return null;
let toolbar = getToolbar(widget);
if (!toolbar)
return null;
return {area: toolbar.id};
},
addWidgetToArea: function(id)
{
// Note: the official API function also has area and position parameters.
// We ignore those here and simply restore the previous position instead.
let widget = widgets.get(id);
for (let window of UI.applicationWindows)
{
let toolbox = getToolbox(window, widget);
if (!toolbox)
continue;
let position = toolbox.getAttribute(widget.positionAttribute);
if (position)
position = position.replace(/^hidden,/, "visible,");
showWidget(toolbox, widget, position);
}
},
removeWidgetFromArea: function(id)
{
let widget = widgets.get(id);
for (let window of UI.applicationWindows)
{
let toolbox = getToolbox(window, widget);
if (!toolbox)
continue;
let position = toolbox.getAttribute(widget.positionAttribute);
if (position)
position = position.replace(/^visible,/, "hidden,");
else
position = "hidden,,";
showWidget(toolbox, widget, position);
}
}
};
let {WindowObserver} = require("windowObserver");
new WindowObserver({
applyToWindow: function(window)
{
let {isKnownWindow} = require("appSupport");
if (!isKnownWindow(window))
return;
for (let [id, widget] of widgets)
{
let toolbox = getToolbox(window, widget);
if (toolbox)
{
toolbox.addEventListener("aftercustomization", onToolbarCustomization, false);
// Restore widget asynchronously to allow the stylesheet to load
Utils.runAsync(restoreWidget.bind(null, toolbox, widget));
}
}
},
removeFromWindow: function(window)
{
let {isKnownWindow} = require("appSupport");
if (!isKnownWindow(window))
return;
for (let [id, widget] of widgets)
{
let toolbox = getToolbox(window, widget);
if (toolbox)
toolbox.removeEventListener("aftercustomization", onToolbarCustomization, false);
removeWidget(window, widget);
}
}
});