summaryrefslogtreecommitdiffstats
path: root/comm/suite/browser/linkToolbarHandler.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/browser/linkToolbarHandler.js')
-rw-r--r--comm/suite/browser/linkToolbarHandler.js295
1 files changed, 295 insertions, 0 deletions
diff --git a/comm/suite/browser/linkToolbarHandler.js b/comm/suite/browser/linkToolbarHandler.js
new file mode 100644
index 0000000000..eb3e0cfb38
--- /dev/null
+++ b/comm/suite/browser/linkToolbarHandler.js
@@ -0,0 +1,295 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ChromeUtils.defineModuleGetter(this, "Feeds",
+ "resource:///modules/Feeds.jsm");
+
+/**
+ * LinkToolbarHandler is a Singleton that displays LINK elements
+ * and nodeLists of LINK elements in the Link Toolbar. It
+ * associates the LINK with a corresponding LinkToolbarItem based
+ * on it's REL attribute and the toolbar item's ID attribute.
+ * LinkToolbarHandler is also a Factory and will create
+ * LinkToolbarItems as necessary.
+ */
+function LinkToolbarHandler()
+{
+ this.items = new Array();
+ this.hasItems = false;
+}
+
+LinkToolbarHandler.prototype.handle =
+function(element)
+{
+ // XXX: if you're going to re-enable handling of anchor elements,
+ // you'll want to change this to AnchorElementDecorator
+ var linkElement = new LinkElementDecorator(element);
+
+ if (linkElement.isIgnored()) return;
+
+ for (var i = 0; i < linkElement.relValues.length; i++) {
+ // Skip "alternate" when we have e.g. "alternate XXX".
+ if (linkElement.relValues.length > 1 &&
+ linkElement.relValues[i] == "alternate")
+ continue;
+
+ var linkType = LinkToolbarHandler.getLinkType(linkElement.relValues[i], element);
+ if (linkType) {
+ if (!this.hasItems) {
+ this.hasItems = true;
+ linkToolbarUI.activate();
+ }
+ this.getItemForLinkType(linkType).displayLink(linkElement);
+ }
+ }
+}
+
+LinkToolbarHandler.getLinkType =
+function(relAttribute, element)
+{
+ var isFeed = false;
+ switch (relAttribute.toLowerCase()) {
+ case "top":
+ case "origin":
+ return "top";
+
+ case "up":
+ case "parent":
+ return "up";
+
+ case "start":
+ case "begin":
+ case "first":
+ return "first";
+
+ case "next":
+ case "child":
+ return "next";
+
+ case "prev":
+ case "previous":
+ return "prev";
+
+ case "end":
+ case "last":
+ return "last";
+
+ case "author":
+ case "made":
+ return "author";
+
+ case "contents":
+ case "toc":
+ return "toc";
+
+ case "feed":
+ isFeed = true;
+ // fall through
+ case "alternate":
+ if (Feeds.isValidFeed(element, element.nodePrincipal, isFeed)) {
+ return "feed";
+ }
+
+ if (!isFeed) {
+ return "alternate";
+ }
+ // fall through
+ case "prefetch":
+ return null;
+
+ default:
+ return relAttribute.toLowerCase();
+ }
+}
+
+LinkToolbarHandler.prototype.getItemForLinkType =
+function(linkType) {
+ if (!(linkType in this.items && this.items[linkType]))
+ this.items[linkType] = LinkToolbarHandler.createItemForLinkType(linkType);
+
+ return this.items[linkType];
+}
+
+LinkToolbarHandler.createItemForLinkType =
+function(linkType)
+{
+ if (!document.getElementById("link-" + linkType))
+ return new LinkToolbarTransientMenu(linkType);
+
+ // XXX: replace switch with polymorphism
+ var element = document.getElementById("link-" + linkType);
+ switch (element.getAttribute("type") || element.localName) {
+ case "toolbarbutton":
+ return new LinkToolbarButton(linkType);
+
+ case "menuitem":
+ return new LinkToolbarItem(linkType);
+
+ case "menu":
+ return new LinkToolbarMenu(linkType);
+
+ default:
+ return new LinkToolbarTransientMenu(linkType);
+ }
+}
+
+LinkToolbarHandler.prototype.clearAllItems =
+function()
+{
+ // Hide the 'miscellaneous' separator
+ document.getElementById("misc-separator").setAttribute("collapsed", "true");
+
+ // Disable the individual items
+ for (var linkType in this.items)
+ this.items[linkType].clear();
+
+ // Store the fact that the toolbar is empty
+ this.hasItems = false;
+}
+
+var linkToolbarHandler = new LinkToolbarHandler();
+
+function LinkElementDecorator(element) {
+ /*
+ * XXX: this is an incomplete decorator, because it doesn't implement
+ * the full Element interface. If you need to use a method
+ * or member in the Element interface, just add it here and
+ * have it delegate to this.element
+ *
+ * XXX: would rather add some methods to Element.prototype instead of
+ * using a decorator, but Element.prototype is no longer exposed
+ * since the XPCDOM landing, see bug 83433
+ */
+
+ if (!element) return; // skip the rest on foo.prototype = new ThisClass calls
+
+ this.element = element;
+
+ this.rel = LinkElementDecorator.convertRevMade(element.rel, element.rev);
+ if (this.rel)
+ this.relValues = this.rel.split(" ");
+ this.rev = element.rev;
+ this.title = element.title;
+ this.href = element.href;
+ this.hreflang = element.hreflang;
+ this.media = element.media;
+ this.longTitle = null;
+}
+
+LinkElementDecorator.prototype.isIgnored =
+function()
+{
+ if (!this.rel) return true;
+ for (var i = 0; i < this.relValues.length; i++)
+ if (/^stylesheet$|^icon$|^fontdef$|^p3pv|^schema./i.test(this.relValues[i]))
+ return true;
+ return false;
+}
+
+LinkElementDecorator.convertRevMade =
+function(rel, rev)
+{
+ if (!rel && rev && /\bmade\b/i.test(rev))
+ return rev;
+ else
+ return rel;
+}
+
+LinkElementDecorator.prototype.getTooltip =
+function()
+{
+ return this.getLongTitle() || this.href;
+}
+
+LinkElementDecorator.prototype.getLabel =
+function()
+{
+ return this.getLongTitle() || this.rel;
+}
+
+LinkElementDecorator.prototype.getLongTitle =
+function()
+{
+ if (this.longTitle == null)
+ this.longTitle = this.makeLongTitle();
+
+ return this.longTitle;
+}
+
+LinkElementDecorator.prototype.makeLongTitle =
+function()
+{
+ let prefix = "";
+
+ // XXX: lookup more meaningful and localized version of media,
+ // i.e. media="print" becomes "Printable" or some such
+ // XXX: use localized version of ":" separator
+ if (this.media && !/\ball\b|\bscreen\b/i.test(this.media))
+ prefix += this.media + ": ";
+ if (this.hreflang) {
+ try {
+ let languageBundle = document.getElementById("languageBundle");
+ let regionBundle = document.getElementById("regionBundle");
+
+ // In case hreflang contains region code.
+ let ISOcode = this.hreflang.split("-");
+
+ prefix += languageBundle.getString(ISOcode[0].toLowerCase());
+
+ // Test if region code exists.
+ if (ISOcode[1])
+ prefix += " (" + regionBundle.getString(ISOcode[1].toLowerCase()) + ")";
+ }
+ catch (e) {
+ // Return if language or region is not recognized.
+ prefix += gNavigatorBundle.getFormattedString("unknownLanguage",
+ [this.hreflang]);
+ }
+
+ prefix += ": ";
+ }
+
+ return this.title ? prefix + this.title : prefix;
+}
+
+function AnchorElementDecorator(element) {
+ this.constructor(element);
+}
+AnchorElementDecorator.prototype = new LinkElementDecorator;
+
+AnchorElementDecorator.prototype.getLongTitle =
+function()
+{
+ return this.title ? this.__proto__.getLongTitle.apply(this)
+ : getText(this.element);
+}
+
+AnchorElementDecorator.prototype.getText =
+function(element)
+{
+ return condenseWhitespace(getTextRecursive(element));
+}
+
+AnchorElementDecorator.prototype.getTextRecursive =
+function(node)
+{
+ var text = "";
+ node.normalize();
+ if (node.hasChildNodes()) {
+ for (var i = 0; i < node.childNodes.length; i++) {
+ if (node.childNodes.item(i).nodeType == Node.TEXT_NODE)
+ text += node.childNodes.item(i).nodeValue;
+ else if (node.childNodes.item(i).nodeType == Node.ELEMENT_NODE)
+ text += getTextRecursive(node.childNodes.item(i));
+ }
+ }
+
+ return text;
+}
+
+AnchorElementDecorator.prototype.condenseWhitespace =
+function(text)
+{
+ return text.replace(/\W*$/, "").replace(/^\W*/, "").replace(/\W+/g, " ");
+}