summaryrefslogtreecommitdiffstats
path: root/comm/suite/components/sidebar
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/components/sidebar')
-rw-r--r--comm/suite/components/sidebar/SuiteSidebar.manifest4
-rw-r--r--comm/suite/components/sidebar/content/PageNotFound.xul13
-rw-r--r--comm/suite/components/sidebar/content/customize-panel.js43
-rw-r--r--comm/suite/components/sidebar/content/customize-panel.xul23
-rw-r--r--comm/suite/components/sidebar/content/customize.js692
-rw-r--r--comm/suite/components/sidebar/content/customize.xul137
-rw-r--r--comm/suite/components/sidebar/content/preview.js15
-rw-r--r--comm/suite/components/sidebar/content/preview.xul30
-rw-r--r--comm/suite/components/sidebar/content/sidebarBindings.xml34
-rw-r--r--comm/suite/components/sidebar/content/sidebarOverlay.css78
-rw-r--r--comm/suite/components/sidebar/content/sidebarOverlay.js1704
-rw-r--r--comm/suite/components/sidebar/content/sidebarOverlay.xul247
-rw-r--r--comm/suite/components/sidebar/jar.mn16
-rw-r--r--comm/suite/components/sidebar/moz.build18
-rw-r--r--comm/suite/components/sidebar/nsISidebar.idl26
-rw-r--r--comm/suite/components/sidebar/nsSidebar.js348
16 files changed, 3428 insertions, 0 deletions
diff --git a/comm/suite/components/sidebar/SuiteSidebar.manifest b/comm/suite/components/sidebar/SuiteSidebar.manifest
new file mode 100644
index 0000000000..3506176b04
--- /dev/null
+++ b/comm/suite/components/sidebar/SuiteSidebar.manifest
@@ -0,0 +1,4 @@
+component {22117140-9c6e-11d3-aaf1-00805f8a4905} nsSidebar.js
+contract @mozilla.org/sidebar;1 {22117140-9c6e-11d3-aaf1-00805f8a4905}
+category JavaScript-global-property sidebar @mozilla.org/sidebar;1
+category JavaScript-global-property external @mozilla.org/sidebar;1
diff --git a/comm/suite/components/sidebar/content/PageNotFound.xul b/comm/suite/components/sidebar/content/PageNotFound.xul
new file mode 100644
index 0000000000..960357f947
--- /dev/null
+++ b/comm/suite/components/sidebar/content/PageNotFound.xul
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://communicator/locale/sidebar/sidebarOverlay.dtd">
+
+<page xmlns ="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <description class="header">&sidebar.pagenotfound.label;</description>
+</page>
+
diff --git a/comm/suite/components/sidebar/content/customize-panel.js b/comm/suite/components/sidebar/content/customize-panel.js
new file mode 100644
index 0000000000..912619108a
--- /dev/null
+++ b/comm/suite/components/sidebar/content/customize-panel.js
@@ -0,0 +1,43 @@
+/* -*- Mode: Java -*-
+ * 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/. */
+
+// the rdf service
+var RDF = Cc["@mozilla.org/rdf/rdf-service;"]
+ .getService(Ci.nsIRDFService);
+
+var NC = "http://home.netscape.com/NC-rdf#";
+
+var sidebarObj = new Object;
+var customizeObj = new Object;
+
+function Init()
+{
+ customizeObj.id = window.arguments[0];
+ customizeObj.url = window.arguments[1];
+ sidebarObj.datasource_uri = window.arguments[2];
+ sidebarObj.resource = window.arguments[3];
+
+ sidebarObj.datasource = RDF.GetDataSource(sidebarObj.datasource_uri);
+
+ var customize_frame = document.getElementById('customize_frame');
+ customize_frame.setAttribute('src', customizeObj.url);
+}
+
+// Use an assertion to pass a "refresh" event to all the sidebars.
+// They use observers to watch for this assertion (in sidebarOverlay.js).
+function RefreshPanel() {
+ var sb_resource = RDF.GetResource(sidebarObj.resource);
+ var refresh_resource = RDF.GetResource(NC + "refresh_panel");
+ var panel_resource = RDF.GetLiteral(customizeObj.id);
+
+ sidebarObj.datasource.Assert(sb_resource,
+ refresh_resource,
+ panel_resource,
+ true);
+ sidebarObj.datasource.Unassert(sb_resource,
+ refresh_resource,
+ panel_resource);
+}
+
diff --git a/comm/suite/components/sidebar/content/customize-panel.xul b/comm/suite/components/sidebar/content/customize-panel.xul
new file mode 100644
index 0000000000..3fcd3908f1
--- /dev/null
+++ b/comm/suite/components/sidebar/content/customize-panel.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!--
+
+ 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/. -->
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="navigator:browser"
+ onload="Init();"
+ onunload="RefreshPanel();;">
+
+ <script src="chrome://communicator/content/sidebar/customize-panel.js" />
+
+ <browser id="customize_frame"
+ type="content"
+ primary="true"
+ src="about:blank"
+ flex="1"/>
+</window>
diff --git a/comm/suite/components/sidebar/content/customize.js b/comm/suite/components/sidebar/content/customize.js
new file mode 100644
index 0000000000..e8400e909c
--- /dev/null
+++ b/comm/suite/components/sidebar/content/customize.js
@@ -0,0 +1,692 @@
+/* -*- Mode: Java; tab-width: 4; insert-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+//////////////////////////////////////////////////////////////
+// Import modules
+//////////////////////////////////////////////////////////////
+
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+//////////////////////////////////////////////////////////////
+// Global variables
+//////////////////////////////////////////////////////////////
+
+// Set to true for noise
+const CUST_DEBUG = false;
+
+// the rdf service
+var RDF = Cc["@mozilla.org/rdf/rdf-service;1"]
+ .getService(Ci.nsIRDFService);
+var NC = "http://home.netscape.com/NC-rdf#";
+
+var sidebarObj = new Object;
+var allPanelsObj = new Object;
+var original_panels = new Array();
+
+//////////////////////////////////////////////////////////////
+// Sidebar Init/Destroy
+//////////////////////////////////////////////////////////////
+
+function sidebar_customize_init()
+{
+ allPanelsObj.datasources = window.arguments[0];
+ allPanelsObj.resource = window.arguments[1];
+ sidebarObj.datasource_uri = window.arguments[2];
+ sidebarObj.resource = window.arguments[3];
+
+ debug("Init: all panels datasources = " + allPanelsObj.datasources);
+ debug("Init: all panels resource = " + allPanelsObj.resource);
+ debug("Init: sidebarObj.datasource_uri = " + sidebarObj.datasource_uri);
+ debug("Init: sidebarObj.resource = " + sidebarObj.resource);
+
+ var all_panels = document.getElementById('other-panels');
+ var current_panels = document.getElementById('current-panels');
+
+ debug("Adding observer to all panels database.");
+ all_panels.database.AddObserver(panels_observer);
+
+ allPanelsObj.datasources = allPanelsObj.datasources.trim().split(/\s+/);
+ for (var ii = 0; ii < allPanelsObj.datasources.length; ii++) {
+ debug("Init: Adding "+allPanelsObj.datasources[ii]);
+
+ // This will load the datasource, if it isn't already.
+ var datasource = RDF.GetDataSource(allPanelsObj.datasources[ii]);
+ all_panels.database.AddDataSource(datasource);
+ }
+
+ // Add the datasource for current list of panels. It selects panels out
+ // of the other datasources.
+ debug("Init: Adding current panels, "+sidebarObj.datasource_uri);
+ sidebarObj.datasource = RDF.GetDataSource(sidebarObj.datasource_uri);
+
+ // Root the customize dialog at the correct place.
+ debug("Init: reset all panels ref, "+allPanelsObj.resource);
+ all_panels.setAttribute('ref', allPanelsObj.resource);
+
+ // Create a "container" wrapper around the current panels to
+ // manipulate the RDF:Seq more easily.
+ var panel_list = sidebarObj.datasource.GetTarget(RDF.GetResource(sidebarObj.resource), RDF.GetResource(NC + "panel-list"), true);
+ sidebarObj.container = Cc["@mozilla.org/rdf/container;1"].createInstance(Ci.nsIRDFContainer);
+ sidebarObj.container.Init(sidebarObj.datasource, panel_list);
+
+ // Add all the current panels to the tree
+ current_panels = sidebarObj.container.GetElements();
+ while (current_panels.hasMoreElements()) {
+ var panel = current_panels.getNext().QueryInterface(Ci.nsIRDFResource);
+ if (add_node_to_current_list(sidebarObj.datasource, panel) >= 0) {
+ original_panels.push(panel.Value);
+ original_panels[panel.Value] = true;
+ }
+ }
+
+ var links =
+ all_panels.database.GetSources(RDF.GetResource(NC + "haslink"),
+ RDF.GetLiteral("true"), true);
+
+ while (links.hasMoreElements()) {
+ var folder =
+ links.getNext().QueryInterface(Ci.nsIRDFResource);
+ var folder_name = folder.Value;
+ debug("+++ fixing up remote container " + folder_name + "\n");
+ fixup_remote_container(folder_name);
+ }
+
+ sizeToContent();
+}
+
+function sidebar_customize_destruct()
+{
+ var all_panels = document.getElementById('other-panels');
+ debug("Removing observer from all_panels database.");
+ all_panels.database.RemoveObserver(panels_observer);
+}
+
+
+//////////////////////////////////////////////////////////////////
+// Panels' RDF Datasource Observer
+//////////////////////////////////////////////////////////////////
+var panels_observer = {
+ onAssert : function(ds,src,prop,target) {
+ //debug ("observer: assert");
+ // "refresh" is asserted by select menu and by customize.js.
+ if (prop == RDF.GetResource(NC + "link")) {
+ setTimeout(fixup_remote_container, 100, src.Value);
+ }
+ },
+ onUnassert : function(ds,src,prop,target) {
+ //debug ("observer: unassert");
+ },
+ onChange : function(ds,src,prop,old_target,new_target) {
+ //debug ("observer: change");
+ },
+ onMove : function(ds,old_src,new_src,prop,target) {
+ //debug ("observer: move");
+ },
+ onBeginUpdateBatch : function(ds) {
+ //debug ("observer: onBeginUpdateBatch");
+ },
+ onEndUpdateBatch : function(ds) {
+ //debug ("observer: onEndUpdateBatch");
+ }
+};
+
+function fixup_remote_container(id)
+{
+ debug('fixup_remote_container('+id+')');
+
+ var container = document.getElementById(id);
+ if (container) {
+ container.setAttribute('container', 'true');
+ container.removeAttribute('open');
+ }
+}
+
+function fixup_children(id) {
+ // Add container="true" on nodes with "link" attribute
+ var treeitem = document.getElementById(id);
+
+ var children = treeitem.childNodes.item(1).childNodes;
+ for (var ii=0; ii < children.length; ii++) {
+ var child = children.item(ii);
+ if (child.getAttribute('link') != '' &&
+ child.getAttribute('container') != 'true') {
+ child.setAttribute('container', 'true');
+ child.removeAttribute('open');
+ }
+ }
+}
+
+function get_attr(registry,service,attr_name)
+{
+ var attr = registry.GetTarget(service,
+ RDF.GetResource(NC + attr_name),
+ true);
+ if (attr)
+ attr = attr.QueryInterface(Ci.nsIRDFLiteral);
+ if (attr)
+ attr = attr.Value;
+ return attr;
+}
+
+function SelectChangeForOtherPanels(event, target)
+{
+ enable_buttons_for_other_panels();
+}
+
+function ClickOnOtherPanels(event)
+{
+ var tree = document.getElementById("other-panels");
+
+ var rowIndex = -1;
+ if (event.type == "click" && event.button == 0) {
+ var b = tree.treeBoxObject;
+ var cell = b.getCellAt(event.clientX, event.clientY);
+
+ if (cell.childElt == "twisty" || event.detail == 2) {
+ rowIndex = cell.row;
+ }
+ }
+
+ if (rowIndex < 0) return;
+
+ var treeitem = tree.contentView.getItemAtIndex(rowIndex);
+ var res = RDF.GetResource(treeitem.id);
+
+ if (treeitem.getAttribute('container') == 'true') {
+ if (treeitem.getAttribute('open') == 'true') {
+ var link = treeitem.getAttribute('link');
+ var loaded_link = treeitem.getAttribute('loaded_link');
+ if (link != '' && !loaded_link) {
+ debug("Has remote datasource: "+link);
+ add_datasource_to_other_panels(link);
+ treeitem.setAttribute('loaded_link', 'true');
+ } else {
+ setTimeout(fixup_children, 100, treeitem.getAttribute('id'));
+ }
+ }
+ }
+
+ // Remove the selection in the "current" panels list
+ var current_panels = document.getElementById('current-panels');
+ current_panels.view.selection.clearSelection();
+ enable_buttons_for_current_panels();
+}
+
+function add_datasource_to_other_panels(link) {
+ // Convert the |link| attribute into a URL
+ var url = document.location;
+ debug("Current URL: " +url);
+ debug("Current link: " +link);
+
+ var uri = Cc['@mozilla.org/network/standard-url;1'].createInstance();
+ uri = uri.QueryInterface(Ci.nsIURI);
+ uri.spec = url;
+ uri = uri.resolve(link);
+
+ debug("New URL: " +uri);
+
+ // Add the datasource to the tree
+ var all_panels = document.getElementById('other-panels');
+ all_panels.database.AddDataSource(RDF.GetDataSource(uri));
+
+ // XXX This is a hack to force re-display
+ //all_panels.setAttribute('ref', allPanelsObj.resource);
+}
+
+// Handle a selection change in the current panels.
+function SelectChangeForCurrentPanels() {
+ // Remove the selection in the available panels list
+ var all_panels = document.getElementById('other-panels');
+ all_panels.view.selection.clearSelection();
+
+ enable_buttons_for_current_panels();
+}
+
+// Move the selected item up the the current panels list.
+function MoveUp() {
+ var tree = document.getElementById('current-panels');
+ if (tree.view.selection.count == 1) {
+ var index = tree.currentIndex;
+ var selected = tree.contentView.getItemAtIndex(index);
+ var before = selected.previousSibling;
+ if (before) {
+ selected.remove();
+ before.parentNode.insertBefore(selected, before);
+ tree.view.selection.select(index-1);
+ tree.treeBoxObject.ensureRowIsVisible(index-1);
+ }
+ }
+}
+
+// Move the selected item down the the current panels list.
+function MoveDown() {
+ var tree = document.getElementById('current-panels');
+ if (tree.view.selection.count == 1) {
+ var index = tree.currentIndex;
+ var selected = tree.contentView.getItemAtIndex(index);
+ if (selected.nextSibling) {
+ if (selected.nextSibling.nextSibling)
+ selected.parentNode.insertBefore(selected, selected.nextSibling.nextSibling);
+ else
+ selected.parentNode.appendChild(selected);
+ tree.view.selection.select(index+1);
+ tree.treeBoxObject.ensureRowIsVisible(index+1);
+ }
+ }
+}
+
+function PreviewPanel()
+{
+ var tree = document.getElementById('other-panels');
+ var database = tree.database;
+ var sel = tree.view.selection;
+ var rangeCount = sel.getRangeCount();
+ for (var range = 0; range < rangeCount; ++range) {
+ var min = {}, max = {};
+ sel.getRangeAt(range, min, max);
+ for (var index = min.value; index <= max.value; ++index) {
+ var item = tree.contentView.getItemAtIndex(index);
+ var res = RDF.GetResource(item.id);
+
+ var preview_name = get_attr(database, res, 'title');
+ var preview_URL = get_attr(database, res, 'content');
+ if (!preview_URL || !preview_name) continue;
+
+ window.openDialog("chrome://communicator/content/sidebar/preview.xul",
+ "_blank", "chrome,resizable,close,dialog=no",
+ preview_name, preview_URL);
+ }
+ }
+}
+
+// Add the selected panel(s).
+function AddPanel()
+{
+ var added = -1;
+
+ var tree = document.getElementById('other-panels');
+ var database = tree.database;
+ var sel = tree.view.selection;
+ var ranges = sel.getRangeCount();
+ for (var range = 0; range < ranges; ++range) {
+ var min = {}, max = {};
+ sel.getRangeAt(range, min, max);
+ for (var index = min.value; index <= max.value; ++index) {
+ var item = tree.contentView.getItemAtIndex(index);
+ if (item.getAttribute("container") != "true") {
+ var res = RDF.GetResource(item.id);
+ // Add the panel to the current list.
+ added = add_node_to_current_list(database, res);
+ }
+ }
+ }
+
+ if (added >= 0) {
+ // Remove the selection in the other list.
+ // Selection will move to "current" list.
+ tree.view.selection.clearSelection();
+
+ var current_panels = document.getElementById('current-panels');
+ current_panels.view.selection.select(added);
+ current_panels.treeBoxObject.ensureRowIsVisible(added);
+ }
+}
+
+// Copy a panel node into a database such as the current panel list.
+function add_node_to_current_list(registry, service)
+{
+ debug("Adding "+service.Value);
+
+ // Copy out the attributes we want
+ var option_title = get_attr(registry, service, 'title');
+ var option_customize = get_attr(registry, service, 'customize');
+ var option_content = get_attr(registry, service, 'content');
+ if (!option_title || !option_content)
+ return -1;
+
+ var tree = document.getElementById('current-panels');
+ var tree_root = tree.lastChild;
+
+ // Check to see if the panel already exists...
+ var i = 0;
+ for (var treeitem = tree_root.firstChild; treeitem; treeitem = treeitem.nextSibling) {
+ if (treeitem.id == service.Value)
+ // The panel is already in the current panel list.
+ // Avoid adding it twice.
+ return i;
+ ++i;
+ }
+
+ // Create a treerow for the new panel
+ var item = document.createElement('treeitem');
+ var row = document.createElement('treerow');
+ var cell = document.createElement('treecell');
+
+ // Copy over the attributes
+ item.setAttribute('id', service.Value);
+ cell.setAttribute('label', option_title);
+
+ // Add it to the current panels tree
+ item.appendChild(row);
+ row.appendChild(cell);
+ tree_root.appendChild(item);
+ return i;
+}
+
+// Remove the selected panel(s) from the current list tree.
+function RemovePanel()
+{
+ var tree = document.getElementById('current-panels');
+ var sel = tree.view.selection;
+
+ var nextNode = -1;
+ var rangeCount = sel.getRangeCount();
+ for (var range = rangeCount-1; range >= 0; --range) {
+ var min = {}, max = {};
+ sel.getRangeAt(range, min, max);
+ for (var index = max.value; index >= min.value; --index) {
+ var item = tree.contentView.getItemAtIndex(index);
+ nextNode = item.nextSibling ? index : -1;
+ item.remove();
+ }
+ }
+
+ if (nextNode >= 0)
+ sel.select(nextNode);
+}
+
+// Bring up a new window with the customize url
+// for an individual panel.
+function CustomizePanel()
+{
+ var tree = document.getElementById('current-panels');
+ var numSelected = tree.view.selection.count;
+
+ if (numSelected == 1) {
+ var index = tree.currentIndex;
+ var selectedNode = tree.contentView.getItemAtIndex(index);
+ var panel_id = selectedNode.getAttribute('id');
+ var customize_url = selectedNode.getAttribute('customize');
+
+ debug("url = " + customize_url);
+
+ if (!customize_url) return;
+
+ window.openDialog('chrome://communicator/content/sidebar/customize-panel.xul',
+ '_blank',
+ 'chrome,resizable,width=690,height=600,dialog=no,close',
+ panel_id,
+ customize_url,
+ sidebarObj.datasource_uri,
+ sidebarObj.resource);
+ }
+}
+
+function BrowseMorePanels()
+{
+ var url = '';
+ var browser_url = "chrome://navigator/content/navigator.xul";
+ var locale;
+ try {
+ url = Services.prefs.getCharPref("sidebar.customize.more_panels.url");
+ var temp = Services.prefs.getCharPref("browser.chromeURL");
+ if (temp)
+ browser_url = temp;
+ } catch(ex) {
+ debug("Unable to get prefs: "+ex);
+ }
+ window.openDialog(browser_url, "_blank", "chrome,all,dialog=no", url);
+}
+
+function customize_getBrowserURL()
+{
+ return url;
+}
+
+// Serialize the new list of panels.
+function Save()
+{
+ persist_dialog_dimensions();
+
+ var all_panels = document.getElementById('other-panels');
+ var current_panels = document.getElementById('current-panels');
+
+ // See if list membership has changed
+ var panels = [];
+ var tree_root = current_panels.lastChild.childNodes;
+ var list_unchanged = (tree_root.length == original_panels.length);
+ for (var i = 0; i < tree_root.length; i++) {
+ var panel = tree_root[i].id;
+ panels.push(panel);
+ panels[panel] = true;
+ if (list_unchanged && original_panels[i] != panel)
+ list_unchanged = false;
+ }
+ if (list_unchanged)
+ return;
+
+ // Remove all the current panels from the datasource.
+ current_panels = sidebarObj.container.GetElements();
+ while (current_panels.hasMoreElements()) {
+ panel = current_panels.getNext().QueryInterface(Ci.nsIRDFResource);
+
+ // "Check if the item is one of the broadcaster panels imported to RDF from
+ // mainBroadcasterSet. If so, then don't remove it from datasource.
+ var master_list = sidebarObj.datasource.GetTarget(RDF.GetResource(allPanelsObj.resource), RDF.GetResource(NC + "panel-list"), true);
+ var masterSeq = Cc["@mozilla.org/rdf/container;1"]
+ .createInstance(Ci.nsIRDFContainer);
+ masterSeq.Init(sidebarObj.datasource, master_list);
+ var inmaster = (masterSeq.IndexOf(panel) != -1);
+
+ if (panel.Value in panels || inmaster) {
+ // This panel will remain in the sidebar.
+ // Remove the resource, but keep all the other attributes.
+ // Removing it will allow it to be added in the correct order.
+ // Saving the attributes will preserve things such as the exclude state.
+ sidebarObj.container.RemoveElement(panel, false);
+ } else {
+ // Kiss it goodbye.
+ delete_resource_deeply(sidebarObj.container, panel);
+ }
+ }
+
+ // Add the new list of panels
+ for (var ii = 0; ii < panels.length; ++ii) {
+ var id = panels[ii];
+ var resource = RDF.GetResource(id);
+ if (id in original_panels) {
+ sidebarObj.container.AppendElement(resource);
+ } else {
+ copy_resource_deeply(all_panels.database, resource, sidebarObj.container);
+ }
+ }
+ refresh_all_sidebars();
+
+ // Write the modified panels out.
+ sidebarObj.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
+}
+
+// Search for an element in an array
+function has_element(array, element) {
+ for (var ii=0; ii < array.length; ii++) {
+ if (array[ii] == element) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Search for targets from resource in datasource
+function has_targets(datasource, resource) {
+ var arcs = datasource.ArcLabelsOut(resource);
+ return arcs.hasMoreElements();
+}
+
+// Use an assertion to pass a "refresh" event to all the sidebars.
+// They use observers to watch for this assertion (in sidebarOverlay.js).
+function refresh_all_sidebars() {
+ sidebarObj.datasource.Assert(RDF.GetResource(sidebarObj.resource),
+ RDF.GetResource(NC + "refresh"),
+ RDF.GetLiteral("true"),
+ true);
+ sidebarObj.datasource.Unassert(RDF.GetResource(sidebarObj.resource),
+ RDF.GetResource(NC + "refresh"),
+ RDF.GetLiteral("true"));
+}
+
+// Remove a resource and all the arcs out from it.
+function delete_resource_deeply(container, resource) {
+ var arcs = container.DataSource.ArcLabelsOut(resource);
+ while (arcs.hasMoreElements()) {
+ var arc = arcs.getNext();
+ var targets = container.DataSource.GetTargets(resource, arc, true);
+ while (targets.hasMoreElements()) {
+ var target = targets.getNext();
+ container.DataSource.Unassert(resource, arc, target, true);
+ }
+ }
+ container.RemoveElement(resource, false);
+}
+
+// Copy a resource and all its arcs out to a new container.
+function copy_resource_deeply(source_datasource, resource, dest_container) {
+ var arcs = source_datasource.ArcLabelsOut(resource);
+ while (arcs.hasMoreElements()) {
+ var arc = arcs.getNext();
+ var targets = source_datasource.GetTargets(resource, arc, true);
+ while (targets.hasMoreElements()) {
+ var target = targets.getNext();
+ dest_container.DataSource.Assert(resource, arc, target, true);
+ }
+ }
+ dest_container.AppendElement(resource);
+}
+
+function enable_buttons_for_other_panels()
+{
+ var add_button = document.getElementById('add_button');
+ var preview_button = document.getElementById('preview_button');
+ var all_panels = document.getElementById('other-panels');
+
+ var sel = all_panels.view.selection;
+ var num_selected = sel ? sel.count : 0;
+ if (sel) {
+ var ranges = sel.getRangeCount();
+ for (var range = 0; range < ranges; ++range) {
+ var min = {}, max = {};
+ sel.getRangeAt(range, min, max);
+ for (var index = min; index <= max; ++index) {
+ var node = all_panels.contentView.getItemAtIndex(index);
+ if (node.getAttribute('container') != 'true') {
+ ++num_selected;
+ }
+ }
+ }
+ }
+
+ if (num_selected > 0) {
+ add_button.removeAttribute('disabled');
+ preview_button.removeAttribute('disabled');
+ } else {
+ add_button.setAttribute('disabled','true');
+ preview_button.setAttribute('disabled','true');
+ }
+}
+
+function enable_buttons_for_current_panels() {
+ var up = document.getElementById('up');
+ var down = document.getElementById('down');
+ var tree = document.getElementById('current-panels');
+ var customize = document.getElementById('customize-button');
+ var remove = document.getElementById('remove-button');
+
+ var numSelected = tree.view.selection.count;
+ var canMoveUp = false, canMoveDown = false, customizeURL = '';
+
+ if (numSelected == 1 && tree.view.selection.isSelected(tree.currentIndex)) {
+ var selectedNode = tree.view.getItemAtIndex(tree.currentIndex);
+ customizeURL = selectedNode.getAttribute('customize');
+ canMoveUp = selectedNode != selectedNode.parentNode.firstChild;
+ canMoveDown = selectedNode != selectedNode.parentNode.lastChild;
+ }
+
+ up.disabled = !canMoveUp;
+ down.disabled = !canMoveDown;
+ customize.disabled = !customizeURL;
+ remove.disabled = !numSelected;
+}
+
+function persist_dialog_dimensions() {
+ // Stole this code from navigator.js to
+ // insure the windows dimensions are saved.
+
+ // Get the current window position/size.
+ var x = window.screenX;
+ var y = window.screenY;
+ var h = window.outerHeight;
+ var w = window.outerWidth;
+
+ // Store these into the window attributes (for persistence).
+ var win = document.getElementById( "main-window" );
+ win.setAttribute( "x", x );
+ win.setAttribute( "y", y );
+ win.setAttribute( "height", h );
+ win.setAttribute( "width", w );
+}
+
+///////////////////////////////////////////////////////////////
+// Handy Debug Tools
+//////////////////////////////////////////////////////////////
+var debug = null;
+var dump_attributes = null;
+var dump_tree = null;
+var _dump_tree_recur = null;
+
+if (!CUST_DEBUG) {
+ debug = function (s) {};
+ dump_attributes = function (node, depth) {};
+ dump_tree = function (node) {};
+ _dump_tree_recur = function (node, depth, index) {};
+} else {
+ debug = function (s) { dump("-*- sb customize: " + s + "\n"); };
+
+ dump_attributes = function (node, depth) {
+ var attributes = node.attributes;
+ var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | . ";
+
+ if (!attributes || attributes.length == 0) {
+ debug(indent.substr(indent.length - depth*2) + "no attributes");
+ }
+ for (var ii=0; ii < attributes.length; ii++) {
+ var attr = attributes.item(ii);
+ debug(indent.substr(indent.length - depth*2) + attr.name +
+ "=" + attr.value);
+ }
+ }
+ dump_tree = function (node) {
+ _dump_tree_recur(node, 0, 0);
+ }
+ _dump_tree_recur = function (node, depth, index) {
+ if (!node) {
+ debug("dump_tree: node is null");
+ }
+ var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | + ";
+ debug(indent.substr(indent.length - depth*2) + index +
+ " " + node.nodeName);
+ if (node.nodeType != Node.TEXT_NODE) {
+ dump_attributes(node, depth);
+ }
+ var kids = node.childNodes;
+ for (var ii=0; ii < kids.length; ii++) {
+ _dump_tree_recur(kids[ii], depth + 1, ii);
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////
+// Install the load/unload handlers
+//////////////////////////////////////////////////////////////
+addEventListener("load", sidebar_customize_init, false);
+addEventListener("unload", sidebar_customize_destruct, false);
diff --git a/comm/suite/components/sidebar/content/customize.xul b/comm/suite/components/sidebar/content/customize.xul
new file mode 100644
index 0000000000..0e76f65445
--- /dev/null
+++ b/comm/suite/components/sidebar/content/customize.xul
@@ -0,0 +1,137 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML; indent-tabs-mode: nil; -*- -->
+<!--
+
+ 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/. -->
+
+
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/sidebar/customize.css"
+ type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % customizeDTD SYSTEM "chrome://communicator/locale/sidebar/customize.dtd" >
+%customizeDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+]>
+
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="main-window"
+ title="&sidebar.customize.title;"
+ windowtype="sidebar:customize"
+ height="400"
+ persist="screenX screenY width height"
+ buttons="accept,cancel,extra2"
+ spacerflex="1"
+ buttonlabelextra2="&sidebar.more.label;"
+ buttonaccesskeyextra2="&sidebar.more.accesskey;"
+ ondialogextra2="BrowseMorePanels();"
+ ondialogaccept="return Save();">
+
+ <script src="chrome://communicator/content/sidebar/customize.js"/>
+
+ <hbox flex="1">
+ <vbox flex="1">
+ <label accesskey="&sidebar.customize.additional.accesskey;"
+ control="other-panels" value="&sidebar.customize.additional.label;"
+ crop="right"/>
+
+ <tree id="other-panels" flex="1"
+ datasources="rdf:null" hidecolumnpicker="true"
+ containment="http://home.netscape.com/NC-rdf#panel-list"
+ onselect="SelectChangeForOtherPanels(event, event.target.parentNode.parentNode);"
+ onclick="if (event.detail == 2) { AddPanel(); } ClickOnOtherPanels(event);">
+
+ <template>
+ <rule>
+ <conditions>
+ <content uri="?uri"/>
+ <triple subject="?uri" object="?panel-list"
+ predicate="http://home.netscape.com/NC-rdf#panel-list"/>
+ <member container="?panel-list" child="?panel"/>
+ </conditions>
+
+ <bindings>
+ <binding subject="?panel" object="?title"
+ predicate="http://home.netscape.com/NC-rdf#title"/>
+ <binding subject="?panel" object="?link"
+ predicate="http://home.netscape.com/NC-rdf#link"/>
+ </bindings>
+
+ <action>
+ <treechildren>
+ <treeitem uri="?panel" link="?link">
+ <treerow>
+ <treecell label="?title"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </action>
+ </rule>
+ </template>
+
+ <treecols>
+ <treecol id="AvailNameCol" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ </tree>
+
+ <!-- xxxslamm Need to add descriptive panel text here -->
+ <hbox class="button-group">
+ <button id="add_button" oncommand="AddPanel()"
+ label="&sidebar.customize.add.label;"
+ accesskey="&sidebar.customize.add.accesskey;" disabled="true"/>
+
+ <button id="preview_button" oncommand="PreviewPanel()"
+ label="&sidebar.customize.preview.label;"
+ accesskey="&sidebar.customize.preview.accesskey;"
+ disabled="true"/>
+ </hbox>
+ </vbox>
+
+ <separator orient="vertical"/>
+
+ <!-- The panels that the user currently has chosen -->
+ <vbox flex="1">
+ <label value="&sidebar.customize.current2.label;"
+ accesskey="&sidebar.customize.current2.accesskey;"
+ control="current-panels" crop="right"/>
+ <tree id="current-panels" flex="1" hidecolumnpicker="true"
+ onselect="SelectChangeForCurrentPanels();">
+ <treecols>
+ <treecol id="CurrentNameCol" flex="1" hideheader="true"/>
+ </treecols>
+
+ <treechildren/>
+ </tree>
+
+ <hbox class="button-group">
+ <button id="customize-button" oncommand="CustomizePanel();"
+ label="&sidebar.customize.customize.label;" disabled="true"
+ accesskey="&sidebar.customize.customize.accesskey;"/>
+ <button id="remove-button" oncommand="RemovePanel()"
+ label="&sidebar.customize.remove.label;" disabled="true"
+ accesskey="&sidebar.customize.remove.accesskey;"/>
+ </hbox>
+ </vbox>
+
+ <separator orient="vertical" class="thin"/>
+
+ <!-- The 'reorder' buttons -->
+ <vbox id="reorder">
+ <spacer flex="1"/>
+ <button oncommand="MoveUp();" id="up" class="up"
+ disabled="true" label="&sidebar.customize.up.label;"
+ accesskey="&sidebar.customize.up.accesskey;"/>
+ <button oncommand="MoveDown();" id="down" class="down"
+ disabled="true" label="&sidebar.customize.down.label;"
+ accesskey="&sidebar.customize.down.accesskey;"/>
+ <spacer flex="1"/>
+ </vbox>
+
+ </hbox>
+
+</dialog>
+
diff --git a/comm/suite/components/sidebar/content/preview.js b/comm/suite/components/sidebar/content/preview.js
new file mode 100644
index 0000000000..c5238beacd
--- /dev/null
+++ b/comm/suite/components/sidebar/content/preview.js
@@ -0,0 +1,15 @@
+/* -*- Mode: Java -*-
+ * 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/. */
+
+function Init()
+{
+ var panel_name = window.arguments[0];
+ var panel_URL = window.arguments[1];
+
+ var panel_title = document.getElementById('paneltitle');
+ var preview_frame = document.getElementById('previewframe');
+ panel_title.setAttribute('label', panel_name);
+ preview_frame.setAttribute('src', panel_URL);
+}
diff --git a/comm/suite/components/sidebar/content/preview.xul b/comm/suite/components/sidebar/content/preview.xul
new file mode 100644
index 0000000000..ef2434c278
--- /dev/null
+++ b/comm/suite/components/sidebar/content/preview.xul
@@ -0,0 +1,30 @@
+<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!--
+
+ 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/. -->
+
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/sidebar/sidebar.css"
+ type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/sidebar/preview.css"
+ type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://communicator/locale/sidebar/preview.dtd" >
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="Init();"
+ title="&sidebar.preview.title.label;">
+
+ <script src="chrome://communicator/content/sidebar/preview.js" />
+
+ <vbox id="panel-container" flex="1">
+
+ <hbox id="paneltitle" class="box-texttab texttab-sidebar" selected="true"/>
+ <!-- <iframe id="previewframe" type="content" src="about:blank" flex="1"/>-->
+ <iframe class="box-panel" id="previewframe" type="content" src="about:blank" flex="1"/>
+
+ </vbox>
+
+</window>
diff --git a/comm/suite/components/sidebar/content/sidebarBindings.xml b/comm/suite/components/sidebar/content/sidebarBindings.xml
new file mode 100644
index 0000000000..675d591957
--- /dev/null
+++ b/comm/suite/components/sidebar/content/sidebarBindings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+
+<bindings id="globalBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="texttab">
+ <content>
+ <xul:image class="box-texttab-left"/>
+ <xul:vbox class="box-texttab-text-container" xbl:inherits="value" flex="1">
+ <xul:spacer flex="1"/>
+ <xul:label class="box-texttab-text" xbl:inherits="value=label" crop="right"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ <xul:image class="box-texttab-right"/>
+ <xul:spacer class="box-texttab-right-space"/>
+ </content>
+ </binding>
+
+ <binding id="sidebar-header-box" extends="xul:box">
+ <content align="center">
+ <xul:label class="sidebar-header-text" xbl:inherits="value=label,crop" crop="right" flex="1"/>
+ <xul:box>
+ <children/>
+ </xul:box>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/comm/suite/components/sidebar/content/sidebarOverlay.css b/comm/suite/components/sidebar/content/sidebarOverlay.css
new file mode 100644
index 0000000000..0d4df5f16a
--- /dev/null
+++ b/comm/suite/components/sidebar/content/sidebarOverlay.css
@@ -0,0 +1,78 @@
+/* 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/. */
+
+/** sidebarOverlay.css [CONTENT]
+ * This file is for style rules essential for correct sidebar operation.
+ * These rules will not change on a skin-by-skin basis.
+ **/
+
+#sidebar-box {
+ width: 162px;
+ min-height: 10px;
+ min-width: 30px;
+ max-width: 400px;
+}
+
+#sidebar-panels {
+ min-width: 1px;
+ min-height: 10px;
+}
+
+.iframe-panel {
+ min-width: 1px;
+ min-height: 1px;
+}
+
+#sidebar-iframe-no-panels {
+ min-width: 1px;
+ min-height: 1px;
+ overflow: auto;
+}
+
+.browser-sidebar {
+ min-width: 1px;
+ min-height: 1px;
+}
+
+/*
+ * Sidebar and Panel title buttons
+ */
+sidebarheader[type="box"] {
+ -moz-binding: url(chrome://communicator/content/sidebar/sidebarBindings.xml#sidebar-header-box);
+}
+sidebarheader[type="splitter"] {
+ -moz-binding: url(chrome://communicator/content/sidebar/sidebarBindings.xml#sidebar-header-splitter);
+ /* a vertical splitter */
+ cursor: n-resize;
+}
+
+.sidebarheader-main {
+ min-width: 1px;
+ min-height: 1px;
+}
+
+/**
+ * texttab folder lookalike e.g. for sidebar panel headers
+ */
+ .box-texttab
+ {
+ min-height : 10px;
+ min-width : 10px;
+ }
+
+ .box-texttab-right-space
+ {
+ min-width : 1px;
+ }
+
+/**
+ * prevent the notification in the sidebar from being too wide
+ */
+.sidebar-notificationbox > notification {
+ -moz-binding: url(chrome://communicator/content/bindings/notification.xml#sidebar-notification);
+}
+
+.sidebar-notificationbox > notification[value="addon-progress"] {
+ -moz-binding: url(chrome://communicator/content/bindings/notification.xml#sidebar-addon-progress-notification);
+}
diff --git a/comm/suite/components/sidebar/content/sidebarOverlay.js b/comm/suite/components/sidebar/content/sidebarOverlay.js
new file mode 100644
index 0000000000..95b0af029d
--- /dev/null
+++ b/comm/suite/components/sidebar/content/sidebarOverlay.js
@@ -0,0 +1,1704 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+// xul/id summary:
+//
+// <box> sidebar-box
+// <splitter> sidebar-panels-splitter
+// <box> sidebar-panels-splitter-box*
+// <sidebarheader> sidebar-title-box
+// <menubutton> sidebar-panel-picker*
+// <menupopup> sidebar-panel-picker-popup
+// <box> sidebar-panels
+// <template> sidebar-template*
+// <box> sidebar-iframe-no-panels
+// <splitter> sidebar-splitter
+// <menupopup> menu_View_Popup*
+// <menuitem> sidebar-menu
+
+//////////////////////////////////////////////////////////////
+// Import modules
+//////////////////////////////////////////////////////////////
+
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+//////////////////////////////////////////////////////////////
+// Global variables
+//////////////////////////////////////////////////////////////
+
+
+var gCurFrame;
+var gTimeoutID = null;
+var gMustInit = true;
+var gAboutToUncollapse = false;
+var gCheckMissingPanels = true;
+
+function setBlank()
+{
+ gTimeoutID = null;
+ gCurFrame.setAttribute('src', 'chrome://communicator/content/sidebar/PageNotFound.xul');
+}
+
+
+// Uncomment for debug output
+const SB_DEBUG = false;
+
+// pref for limiting number of tabs in view
+// initialized in sidebar_overlay_init()
+var gNumTabsInViewPref;
+
+// The rdf service
+var RDF = Cc["@mozilla.org/rdf/rdf-service;1"]
+ .getService(Ci.nsIRDFService);
+
+const NC = "http://home.netscape.com/NC-rdf#";
+
+// The directory services property to find panels.rdf
+const PANELS_RDF_FILE = "UPnls";
+const SIDEBAR_VERSION = "0.1";
+
+// The default sidebar:
+var sidebarObj = new Object;
+sidebarObj.never_built = true;
+
+//////////////////////////////////////////////////////////////////////
+// sbPanelList Class
+//
+// Wrapper around DOM representation of the sidebar. This UI event
+// handlers and the sidebar datasource observers call into this. This
+// class is responsible for keeping the DOM state of the sidebar up to
+// date.
+// This class does not make any changes to the sidebar rdf datasource.
+//////////////////////////////////////////////////////////////////////
+
+function sbPanelList(container_id)
+{
+ debug("sbPanelList("+container_id+")");
+ this.node = document.getElementById(container_id);
+ this.childNodes = this.node.childNodes;
+ this.initialized = false; // set after first display of tabs
+}
+
+sbPanelList.prototype.get_panel_from_id =
+function (id)
+{
+ debug("get_panel_from_id(" + id + ")");
+ var index = 0;
+ var header = null;
+ if (id && id != '') {
+ for (var ii=2; ii < this.node.childNodes.length; ii += 2) {
+ header = this.node.childNodes.item(ii);
+ if (header.getAttribute('id') == id) {
+ debug("get_panel_from_id: Found at index, " + ii);
+ index = ii;
+ break;
+ }
+ }
+ }
+ if (index > 0) {
+ return new sbPanel(id, header, index);
+ } else {
+ return null;
+ }
+}
+
+sbPanelList.prototype.get_panel_from_header_node =
+function (node)
+{
+ return this.get_panel_from_id(node.getAttribute('id'));
+}
+
+sbPanelList.prototype.get_panel_from_header_index =
+function (index)
+{
+ return this.get_panel_from_header_node(this.node.childNodes.item(index));
+}
+
+sbPanelList.prototype.find_first =
+function (panels)
+{
+ debug("pick_default_panel: length=" + this.node.childNodes.length);
+ for (var ii = 2; ii < this.node.childNodes.length; ii += 2) {
+ var panel = this.get_panel_from_header_index(ii);
+ if (!panel.is_excluded() && panel.is_in_view()) {
+ return panel;
+ }
+ }
+ return null;
+}
+
+sbPanelList.prototype.find_last =
+function (panels)
+{
+ debug("pick_default_panel: length=" + this.node.childNodes.length);
+ for (var ii=(this.node.childNodes.length - 1); ii >= 2; ii -= 2) {
+ var panel = this.get_panel_from_header_index(ii);
+ if (!panel.is_excluded() && panel.is_in_view()) {
+ return panel;
+ }
+ }
+ return null;
+}
+
+sbPanelList.prototype.visible_panels_exist =
+function ()
+{
+ var i;
+ var panels = this.node.childNodes;
+ for (i = 2; i < panels.length; i += 2)
+ {
+ if (!panels.item(i).hidden)
+ return true;
+ }
+ return false;
+}
+
+sbPanelList.prototype.num_panels_included =
+function ()
+{
+ var count = 0;
+ var panels = this.node.childNodes;
+ for (var i = 2; i < panels.length; i += 2)
+ {
+ var curr = this.get_panel_from_header_index(i);
+ if (!curr.is_excluded())
+ count++;
+ }
+ return count;
+}
+
+sbPanelList.prototype.num_panels_in_view =
+function ()
+{
+ var count = 0;
+ var panels = this.node.childNodes;
+ for (var i = 2; i < panels.length; i += 2)
+ {
+ var curr = this.get_panel_from_header_index(i);
+ if (curr.is_in_view())
+ count++;
+ }
+ return count;
+}
+
+sbPanelList.prototype.select =
+function (panel, force_reload)
+{
+ if (!force_reload && panel.is_selected()) {
+ return;
+ }
+ // select(): Open this panel and possibly reload it.
+ if (this.node.getAttribute('last-selected-panel') != panel.id) {
+ // "last-selected-panel" is used as a global variable.
+ // this.update() will reference "last-selected-panel".
+ // This way the value can be persisted in xulstore.json.
+ this.node.setAttribute('last-selected-panel', panel.id);
+ }
+ this.update(force_reload);
+}
+
+sbPanelList.prototype.exclude =
+function (panel)
+{
+ if (this.node.getAttribute('last-selected-panel') == panel.id) {
+ this.select_default_panel();
+ } else {
+ this.update(false);
+ }
+}
+
+
+sbPanelList.prototype.select_default_panel =
+function ()
+{
+ var default_panel = null
+
+ // First, check the XUL for the "defaultpanel" attribute of "sidebar-box".
+ var sidebar_container = document.getElementById('sidebar-box');
+ var content_default_id = sidebar_container.getAttribute('defaultpanel');
+ if (content_default_id != '') {
+ var content = sidebarObj.panels.get_panel_from_id(content_default_id);
+ if (content && !content.is_excluded() && content.is_in_view()) {
+ default_panel = content;
+ }
+ }
+
+ // Second, try to use the panel persisted in 'last-selected-panel'.
+ if (!default_panel) {
+ var last_selected_id = this.node.getAttribute('last-selected-panel');
+ if (last_selected_id != '') {
+ var last = sidebarObj.panels.get_panel_from_id(last_selected_id);
+ if (last && !last.is_excluded() && last.is_in_view()) {
+ default_panel = last;
+ }
+ }
+ }
+
+ // Finally, just use the last one in the list.
+ if (!default_panel) {
+ default_panel = this.find_last();
+ }
+
+ if (default_panel) {
+ this.node.setAttribute('last-selected-panel', default_panel.id);
+ }
+ this.update(false);
+}
+
+sbPanelList.prototype.refresh =
+function ()
+{
+ var last_selected_id = this.node.getAttribute('last-selected-panel');
+ var last_selected = sidebarObj.panels.get_panel_from_id(last_selected_id);
+ if (last_selected && last_selected.is_selected()) {
+ // The desired panel is already selected
+ this.update(false);
+ } else {
+ this.select_default_panel();
+ }
+}
+
+// panel_loader(): called from a timer that is set in sbPanelList.update()
+// Removes the "Loading..." screen when the panel has finished loading.
+function panel_loader() {
+ debug("panel_loader()");
+
+ if (gTimeoutID != null) {
+ clearTimeout(gTimeoutID);
+ gTimeoutID = null;
+ }
+
+ this.removeEventListener("load", panel_loader, true);
+ this.removeAttribute('collapsed');
+ // uncollapse the notificationbox element
+ this.parentNode.removeAttribute('collapsed');
+ // register a progress listener for the notificationbox,
+ // now that this browser has a docShell
+ this.parentNode.addProgressListener();
+ this.setAttribute('loadstate', 'loaded');
+ // hide the load area
+ this.parentNode.parentNode.firstChild.setAttribute('hidden', 'true');
+
+ if (this.hasAttribute('focusOnLoad')) {
+ var elementToFocus = this.contentDocument.documentElement.getAttribute('elementtofocus');
+ if (elementToFocus) {
+ var element = this.contentDocument.getElementById(elementToFocus);
+ if (element)
+ element.focus();
+ else
+ dump(elementToFocus + ' element was not found to focus!\n');
+ } else {
+ this.contentWindow.focus();
+ }
+ this.removeAttribute('focusOnLoad');
+ }
+}
+sbPanelList.prototype.update =
+function (force_reload)
+{
+ // This function requires that the attribute 'last-selected-panel'
+ // holds the id of a non-excluded panel. If it doesn't, no panel will
+ // be selected. The attribute is used instead of a function
+ // parameter to allow the value to be persisted in xulstore.json.
+ var selected_id = this.node.getAttribute('last-selected-panel');
+
+ if (sidebar_is_collapsed()) {
+ sidebarObj.collapsed = true;
+ } else {
+ sidebarObj.collapsed = false;
+ }
+
+ var num_included = sidebarObj.panels.num_panels_included();
+ if (num_included > gNumTabsInViewPref)
+ document.getElementById("nav-buttons-box").hidden = false;
+ else
+ document.getElementById("nav-buttons-box").hidden = true;
+
+ var have_set_top = 0;
+ var have_set_after_selected = 0;
+ var is_after_selected = 0;
+ var last_header = 0;
+ var num_in_view = 0;
+ debug("this.initialized: " + this.initialized);
+ for (var ii=2; ii < this.node.childNodes.length; ii += 2) {
+ var header = this.node.childNodes.item(ii);
+ var content = this.node.childNodes.item(ii+1);
+ var id = header.getAttribute('id');
+ var panel = new sbPanel(id, header, ii);
+ var excluded = panel.is_excluded();
+ var in_view = false;
+ if (!this.initialized)
+ {
+ if (num_in_view < gNumTabsInViewPref)
+ in_view = true;
+ }
+ else
+ {
+ if (header.getAttribute("in-view") == "true")
+ in_view = true;
+ }
+ if (excluded || !in_view)
+ {
+ debug("item("+ii/2+") excluded: " + excluded +
+ " in view: " + in_view);
+ header.setAttribute('hidden','true');
+ content.setAttribute('hidden','true');
+ if (!in_view)
+ {
+ header.setAttribute("in-view", false);
+ header.removeAttribute("top-panel");
+ header.removeAttribute("last-panel");
+ }
+ } else {
+ // only set if in view
+ if (!this.initialized || (num_in_view < gNumTabsInViewPref))
+ last_header = header;
+ header.removeAttribute('last-panel');
+ // only set if in view
+ if (!have_set_top &&
+ (!this.initialized || (header.getAttribute("in-view") == "true")))
+ {
+ header.setAttribute('top-panel','true');
+ have_set_top = 1;
+ } else {
+ header.removeAttribute('top-panel');
+ }
+ if (!have_set_after_selected && is_after_selected) {
+ header.setAttribute('first-panel-after-selected','true');
+ have_set_after_selected = 1
+ } else {
+ header.removeAttribute('first-panel-after-selected');
+ }
+ header.removeAttribute('hidden');
+ header.setAttribute("in-view", true);
+ num_in_view++;
+
+ // (a) when we have hit the maximum number of tabs that can be in view and no tab
+ // has been selected yet
+ // -or-
+ // (b) when we have reached the last tab we are about to display
+ if ( ((num_in_view == num_included) ||
+ (num_in_view == gNumTabsInViewPref)) &&
+ !is_after_selected )
+ {
+ selected_id = id;
+ this.node.setAttribute('last-selected-panel', id);
+ }
+
+ // Pick sandboxed, or unsandboxed iframe
+ var iframe = panel.get_iframe();
+ var notificationbox = iframe.parentNode;
+ var load_state;
+
+ if (selected_id == id) {
+ is_after_selected = 1
+ debug("item("+ii/2+") selected");
+ header.setAttribute('selected', 'true');
+ content.removeAttribute('hidden');
+ content.removeAttribute('collapsed');
+
+ if (sidebarObj.collapsed && panel.is_sandboxed()) {
+ if (!panel.is_persistent()) {
+ debug(" set src=about:blank");
+ iframe.setAttribute('src', 'about:blank');
+ }
+ } else {
+ var saved_src = iframe.getAttribute('content');
+ var src = iframe.getAttribute('src');
+ // either we have been requested to force_reload or the
+ // panel src has changed so we must restore the original src
+ if (force_reload || (saved_src != src)) {
+ debug(" set src="+saved_src);
+ iframe.setAttribute('src', saved_src);
+
+ if (gTimeoutID != null)
+ clearTimeout(gTimeoutID);
+
+ gCurFrame = iframe;
+ gTimeoutID = setTimeout(setBlank, 20000);
+ }
+ }
+
+ load_state = content.getAttribute('loadstate');
+ if (load_state == 'stopped') {
+ load_state = 'never loaded';
+ toggleLoadarea(content);
+ }
+ if (load_state == 'never loaded') {
+ iframe.removeAttribute('hidden');
+ iframe.setAttribute('loadstate', 'loading');
+ iframe.addEventListener('load', panel_loader, true);
+ }
+ } else {
+ debug("item("+ii/2+")");
+ header.removeAttribute('selected');
+ content.setAttribute('collapsed','true');
+
+ if (!panel.is_persistent()) {
+ iframe.setAttribute('src', 'about:blank');
+ load_state = content.getAttribute('loadstate');
+ if (load_state == 'loading') {
+ iframe.removeEventListener("load", panel_loader, true);
+ content.setAttribute('hidden','true');
+ iframe.setAttribute('loadstate', 'never loaded');
+ }
+ }
+ }
+ }
+ }
+ if (last_header) {
+ last_header.setAttribute('last-panel','true');
+ }
+
+ var no_panels_iframe = document.getElementById('sidebar-iframe-no-panels');
+ if (have_set_top) {
+ no_panels_iframe.setAttribute('hidden','true');
+ // The hide and show of 'sidebar-panels' should not be needed,
+ // but some old profiles may have this persisted as hidden (50973).
+ this.node.removeAttribute('hidden');
+ } else {
+ no_panels_iframe.removeAttribute('hidden');
+ }
+
+ this.initialized = true;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// sbPanel Class
+//
+// Like sbPanelList, this class is a wrapper around DOM representation
+// of individual panels in the sidebar. This UI event handlers and the
+// sidebar datasource observers call into this. This class is
+// responsible for keeping the DOM state of the sidebar up to date.
+// This class does not make any changes to the sidebar rdf datasource.
+//////////////////////////////////////////////////////////////////////
+
+function sbPanel(id, header, index)
+{
+ // This constructor should only be called by sbPanelList class.
+ // To create a panel instance, use the helper functions in sbPanelList:
+ // sb_panel.get_panel_from_id(id)
+ // sb_panel.get_panel_from_header_node(dom_node)
+ // sb_panel.get_panel_from_header_index(index)
+ this.id = id;
+ this.header = header;
+ this.index = index;
+ this.parent = sidebarObj.panels;
+}
+
+sbPanel.prototype.get_header =
+function ()
+{
+ return this.header;
+}
+
+sbPanel.prototype.get_content =
+function ()
+{
+ return this.get_header().nextSibling;
+}
+
+sbPanel.prototype.is_sandboxed =
+function ()
+{
+ if (typeof this.sandboxed == "undefined") {
+ var notificationbox = this.get_content().childNodes.item(1);
+ var unsandboxed_iframe = notificationbox.firstChild;
+ this.sandboxed = !unsandboxed_iframe.getAttribute('content').match(/^chrome:/);
+ }
+ return this.sandboxed;
+}
+
+sbPanel.prototype.get_iframe =
+function ()
+{
+ if (typeof this.iframe == "undefined") {
+ var notificationbox = this.get_content().childNodes.item(1);
+ this.iframe = this.is_sandboxed() ? notificationbox.lastChild :
+ notificationbox.firstChild;
+ }
+ return this.iframe;
+}
+
+// This exclude function is used on panels and on the panel picker menu.
+// That is why it is hanging out in the global name space instead of
+// minding its own business in the class.
+function sb_panel_is_excluded(node)
+{
+ var exclude = node.getAttribute('exclude');
+ return ( exclude && exclude != '' &&
+ exclude.includes(sidebarObj.component));
+}
+sbPanel.prototype.is_excluded =
+function ()
+{
+ return sb_panel_is_excluded(this.get_header());
+}
+
+sbPanel.prototype.is_in_view =
+function()
+{
+ return (this.header.getAttribute("in-view") == "true");
+}
+
+sbPanel.prototype.is_selected =
+function (panel_id)
+{
+ return 'true' == this.get_header().getAttribute('selected');
+}
+
+sbPanel.prototype.is_persistent =
+function ()
+{
+ var rv = false;
+ var datasource = sidebarObj.datasource;
+ var persistNode = datasource.GetTarget(RDF.GetResource(this.id),
+ RDF.GetResource(NC + "persist"),
+ true);
+ if (persistNode)
+ {
+ persistNode =
+ persistNode.QueryInterface(Ci.nsIRDFLiteral);
+ rv = persistNode.Value == 'true';
+ }
+
+ return rv;
+}
+
+sbPanel.prototype.select =
+function (force_reload)
+{
+ this.parent.select(this, force_reload);
+}
+
+sbPanel.prototype.stop_load =
+function ()
+{
+ var iframe = this.get_iframe();
+ var content = this.get_content();
+ var load_state = iframe.getAttribute('loadstate');
+ if (load_state == "loading") {
+ debug("Stop the presses");
+ iframe.removeEventListener("load", panel_loader, true);
+ content.setAttribute("loadstate", "stopped");
+ iframe.setAttribute('src', 'about:blank');
+ toggleLoadarea(content);
+ }
+}
+
+function toggleLoadarea(content)
+{
+ // toggle between "loading" and "load stopped" in the UI
+ var widgetBox = content.firstChild.firstChild;
+ var widgetBoxKids = widgetBox.childNodes;
+ var stopButton = widgetBoxKids.item(3);
+ var reloadButton = widgetBoxKids.item(4);
+ var loadingImage = widgetBox.firstChild;
+ var loadingText = loadingImage.nextSibling;
+ var loadStoppedText = loadingText.nextSibling;
+
+ // sanity check
+ if (stopButton.getAttribute("type") != "stop")
+ {
+ debug("Error: Expected button of type=\"stop\" but didn't get one!");
+ return;
+ }
+
+ if (!stopButton.hidden)
+ {
+ // change button from "stop" to "reload"
+ stopButton.hidden = "true";
+ reloadButton.removeAttribute("hidden");
+
+ // hide the loading image and set text to "load stopped"
+ loadingImage.hidden = "true";
+ loadingText.hidden = "true";
+ loadStoppedText.removeAttribute("hidden");
+ }
+ else
+ {
+ // change button from "reload" to "stop"
+ stopButton.removeAttribute("hidden");
+ reloadButton.hidden = "true";
+
+ // show the loading image and set text to "loading"
+ loadingImage.removeAttribute("hidden");
+ loadingText.removeAttribute("hidden");
+ loadStoppedText.hidden = "true";
+ }
+}
+
+sbPanel.prototype.exclude =
+function ()
+{
+ // Exclusion is handled by the datasource,
+ // but we need to make sure this panel is no longer selected.
+ this.get_header().removeAttribute('selected');
+ this.parent.exclude(this);
+}
+
+sbPanel.prototype.reload =
+function ()
+{
+ if (!this.is_excluded()) {
+ this.select(true);
+ }
+}
+
+//////////////////////////////////////////////////////////////////
+// Panels' RDF Datasource Observer
+//
+// This observer will ensure that the Sidebar UI stays current
+// when the datasource changes.
+// - When "refresh" is asserted, the sidebar refreshed.
+// Currently this happens when a panel is included/excluded or
+// added/removed (the later comes from the customize dialog).
+// - When "refresh_panel" is asserted, the targeted panel is reloaded.
+// Currently this happens when the customize panel dialog is closed.
+//////////////////////////////////////////////////////////////////
+var panel_observer = {
+ onAssert : function(ds,src,prop,target) {
+ //debug ("observer: assert");
+ // "refresh" is asserted by select menu and by customize.js.
+ if (prop == RDF.GetResource(NC + "refresh")) {
+ sidebarObj.panels.initialized = false; // reset so panels are put in view
+ sidebarObj.panels.refresh();
+ } else if (prop == RDF.GetResource(NC + "refresh_panel")) {
+ var panel_id = target.QueryInterface(Ci.nsIRDFLiteral).Value;
+ var panel = sidebarObj.panels.get_panel_from_id(panel_id);
+ panel.reload();
+ }
+ },
+ onUnassert : function(ds,src,prop,target) {
+ //debug ("observer: unassert");
+ },
+ onChange : function(ds,src,prop,old_target,new_target) {
+ //debug ("observer: change");
+ },
+ onMove : function(ds,old_src,new_src,prop,target) {
+ //debug ("observer: move");
+ },
+ onBeginUpdateBatch : function(ds) {
+ //debug ("observer: onBeginUpdateBatch");
+ },
+ onEndUpdateBatch : function(ds) {
+ //debug ("observer: onEndUpdateBatch");
+ }
+};
+
+// Use an assertion to pass a "refresh" event to all the sidebars.
+// They use observers to watch for this assertion (see above).
+function refresh_all_sidebars() {
+ sidebarObj.datasource.Assert(RDF.GetResource(sidebarObj.resource),
+ RDF.GetResource(NC + "refresh"),
+ RDF.GetLiteral("true"),
+ true);
+ sidebarObj.datasource.Unassert(RDF.GetResource(sidebarObj.resource),
+ RDF.GetResource(NC + "refresh"),
+ RDF.GetLiteral("true"));
+}
+
+//////////////////////////////////////////////////////////////
+// Sidebar Init
+//////////////////////////////////////////////////////////////
+function sidebar_overlay_init() {
+ if (sidebar_is_collapsed() && !gAboutToUncollapse)
+ return;
+ gMustInit = false;
+ sidebarObj.panels = new sbPanelList('sidebar-panels');
+ sidebarObj.datasource_uri = get_sidebar_datasource_uri();
+ sidebarObj.datasource = RDF.GetDataSourceBlocking(sidebarObj.datasource_uri);
+ sidebarObj.resource = 'urn:sidebar:current-panel-list';
+
+ sidebarObj.master_datasources = sidebarObj.datasource_uri;
+ sidebarObj.master_resource = 'urn:sidebar:master-panel-list';
+ sidebarObj.component = gPrivate ? "navigator:browser" :
+ document.documentElement.getAttribute('windowtype');
+ debug("sidebarObj.component is " + sidebarObj.component);
+
+ // Sync RDF with broadcasters.
+ SidebarBroadcastersToRDF();
+
+ // Initialize the display
+ var sidebar_element = document.getElementById('sidebar-box');
+ var sidebar_menuitem = document.getElementById('sidebar-menu');
+ if (sidebar_is_hidden()) {
+ if (sidebar_menuitem) {
+ sidebar_menuitem.setAttribute('checked', 'false');
+ }
+ } else {
+ if (sidebar_menuitem) {
+ sidebar_menuitem.setAttribute('checked', 'true');
+ }
+
+ // for old profiles that don't persist the hidden attribute when splitter is not hidden.
+ var sidebar_splitter = document.getElementById('sidebar-splitter')
+ if (sidebar_splitter)
+ sidebar_splitter.setAttribute('hidden', 'false');
+
+ if (sidebarObj.never_built) {
+ sidebarObj.never_built = false;
+
+ debug("sidebar = " + sidebarObj);
+ debug("sidebarObj.resource = " + sidebarObj.resource);
+ debug("sidebarObj.datasource_uri = " + sidebarObj.datasource_uri);
+
+ // Obtain the pref for limiting the number of tabs in view, defaults to 8.
+ gNumTabsInViewPref = Services.prefs.getIntPref("sidebar.num_tabs_in_view", 8);
+
+ // Show the header for the panels area. Use a splitter if there
+ // is stuff over the panels area.
+ var sidebar_panels_splitter = document.getElementById('sidebar-panels-splitter');
+ if (sidebar_element.firstChild != sidebar_panels_splitter) {
+ debug("Showing the panels splitter");
+ sidebar_panels_splitter.removeAttribute('hidden');
+ }
+ }
+ if (sidebar_is_collapsed()) {
+ sidebarObj.collapsed = true;
+ } else {
+ sidebarObj.collapsed = false;
+ }
+
+ sidebar_open_default_panel(100, 0);
+ }
+}
+
+function sidebar_overlay_destruct() {
+ var panels = document.getElementById('sidebar-panels');
+ debug("Removing observer from database.");
+ panels.database.RemoveObserver(panel_observer);
+}
+
+var gBusyOpeningDefault = false;
+
+function sidebar_open_default_panel(wait, tries) {
+ // check for making function reentrant
+ if (gBusyOpeningDefault)
+ return;
+ gBusyOpeningDefault = true;
+
+ var ds = sidebarObj.datasource;
+ var currentListRes = RDF.GetResource("urn:sidebar:current-panel-list");
+ var panelListRes = RDF.GetResource("http://home.netscape.com/NC-rdf#panel-list");
+ var container = ds.GetTarget(currentListRes, panelListRes, true);
+ if (container) {
+ // Add the user's current panel choices to the template builder,
+ // which will aggregate it with the other datasources that describe
+ // the individual panel's title, customize URL, and content URL.
+ var panels = document.getElementById('sidebar-panels');
+ panels.database.AddDataSource(ds);
+
+ debug("Adding observer to database.");
+ panels.database.AddObserver(panel_observer);
+
+ // XXX This is a hack to force re-display
+ panels.builder.rebuild();
+ } else {
+ if (tries < 3) {
+ // No children yet, try again later
+ setTimeout(sidebar_open_default_panel, wait, wait*2, ++tries);
+ gBusyOpeningDefault = false;
+ return;
+ } else {
+ sidebar_fixup_datasource();
+ }
+ }
+
+ sidebarObj.panels.refresh();
+ gBusyOpeningDefault = false;
+ if (gCheckMissingPanels)
+ check_for_missing_panels();
+}
+
+function SidebarRebuild() {
+ sidebarObj.panels.initialized = false; // reset so panels are brought in view
+ var panels = document.getElementById("sidebar-panels");
+ panels.builder.rebuild();
+ sidebar_open_default_panel(100, 0);
+}
+
+function check_for_missing_panels() {
+ var tabs = sidebarObj.panels.node.childNodes;
+ var currHeader;
+ var currTab;
+ for (var i = 2; i < tabs.length; i += 2) {
+ currHeader = tabs[i];
+ currTab = new sbPanel(currHeader.getAttribute("id"), currHeader, i);
+ if (!currTab.is_excluded()) {
+ if (currHeader.hasAttribute("prereq") && currHeader.getAttribute("prereq") != "") {
+ var prereq_file = currHeader.getAttribute("prereq");
+ var channel =
+ Services.io.newChannelFromURI(Services.io.newURI(prereq_file),
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ try {
+ channel.open();
+ }
+ catch (ex) {
+ if (ex.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
+ throw ex;
+ }
+ sidebarObj.datasource.Assert(RDF.GetResource(currHeader.getAttribute("id")),
+ RDF.GetResource(NC + "exclude"),
+ RDF.GetLiteral(sidebarObj.component),
+ true);
+ currTab.exclude();
+ }
+ }
+ }
+ }
+ gCheckMissingPanels = false;
+}
+
+//////////////////////////////////////////////////////////////
+// Sidebar File and Datasource functions
+//////////////////////////////////////////////////////////////
+
+function sidebar_get_panels_file() {
+ try {
+ // Use the fileLocator to look in the profile directory to find
+ // 'panels.rdf', which is the database of the user's currently
+ // selected panels.
+ // If <profile>/panels.rdf doesn't exist, GetFileLocation() will copy
+ // bin/defaults/profile/panels.rdf to <profile>/panels.rdf
+ var sidebar_file = GetSpecialDirectory(PANELS_RDF_FILE);
+ if (!sidebar_file.exists()) {
+ // This should not happen, as GetFileLocation() should copy
+ // defaults/panels.rdf to the users profile directory
+ debug("Sidebar panels file does not exist");
+ throw("Panels file does not exist");
+ }
+ return sidebar_file;
+ } catch (ex) {
+ // This should not happen
+ debug("Error: Unable to grab panels file.\n");
+ throw(ex);
+ }
+ return null;
+}
+
+function sidebar_revert_to_default_panels() {
+ try {
+ var sidebar_file = sidebar_get_panels_file();
+
+ sidebar_file.remove(false);
+
+ // Since we just removed the panels file,
+ // this should copy the defaults over.
+ sidebar_file = sidebar_get_panels_file();
+
+ debug("sidebar defaults reloaded");
+ var datasource = sidebarObj.datasource;
+ datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Refresh(true);
+ } catch (ex) {
+ debug("Error: Unable to reload panel defaults file.\n");
+ }
+ return null;
+}
+
+function get_sidebar_datasource_uri() {
+ try {
+ var sidebar_file = sidebar_get_panels_file();
+
+ var fileHandler = Services.io.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
+
+ return fileHandler.getURLSpecFromFile(sidebar_file);
+ } catch (ex) {
+ // This should not happen
+ debug("Error: Unable to load panels file.\n");
+ }
+ return null;
+}
+
+function sidebar_fixup_datasource() {
+ var datasource = sidebarObj.datasource;
+ var resource = RDF.GetResource(sidebarObj.resource);
+
+ var panel_list = datasource.GetTarget(resource,
+ RDF.GetResource(NC+"panel-list"),
+ true);
+ if (!panel_list) {
+ debug("Sidebar datasource is an old format or busted\n");
+ sidebar_revert_to_default_panels();
+ } else {
+ // The datasource is ok, but it just has no panels.
+ // sidebar_refresh() will display some helper content.
+ // Do nothing here.
+ }
+}
+
+//////////////////////////////////////////////////////////////
+// Sidebar Interface for XUL
+//////////////////////////////////////////////////////////////
+
+// Change the sidebar content to the selected panel.
+// Called when a panel title is clicked.
+function SidebarSelectPanel(header, should_popopen, should_unhide) {
+ debug("SidebarSelectPanel("+header+","+should_popopen+","+should_unhide+")");
+ var panel = sidebarObj.panels.get_panel_from_header_node(header);
+
+ if (!panel) {
+ return false;
+ }
+
+ var popopen = false;
+ var unhide = false;
+
+ if (panel.is_excluded()) {
+ return false;
+ }
+ if (sidebar_is_hidden()) {
+ if (should_unhide) {
+ unhide = true;
+ } else {
+ return false;
+ }
+ }
+ if (sidebar_is_collapsed()) {
+ if (should_popopen) {
+ popopen = true;
+ } else {
+ return false;
+ }
+ }
+ if (unhide) SidebarShowHide();
+ if (popopen) SidebarExpandCollapse();
+
+ try {
+ panel.get_iframe().setAttribute('focusOnLoad', true);
+ } catch (ex) {
+ // ignore exception for cases where content isn't built yet
+ // e.g., auto opening search tab: we don't want to focus search field
+ }
+ if (!panel.is_selected()) panel.select(false);
+
+ return true;
+}
+
+function SidebarGetLastSelectedPanel()
+{
+ return (sidebarObj.panels &&
+ sidebarObj.panels.node.getAttribute('last-selected-panel'));
+}
+
+function SidebarGetRelativePanel(direction)
+{
+ // direction == 1 to view next panel, -1 to view prev panel
+
+ if (sidebar_is_hidden())
+ SidebarShowHide();
+ if (sidebar_is_collapsed())
+ SidebarExpandCollapse();
+
+ var currentPanel = sidebarObj.panels.get_panel_from_id(SidebarGetLastSelectedPanel());
+ if (!currentPanel) {
+ sidebarObj.panels.select_default_panel();
+ return;
+ }
+
+ var newPanel = currentPanel;
+
+ do {
+ var newPanelIndex = newPanel.index + (direction * 2);
+ if (newPanelIndex < 2 || newPanelIndex >= sidebarObj.panels.node.childNodes.length)
+ newPanel = (direction == 1)? sidebarObj.panels.find_first(): sidebarObj.panels.find_last();
+ else
+ newPanel = sidebarObj.panels.get_panel_from_header_index(newPanelIndex);
+
+ if (!newPanel)
+ break;
+
+ if (!newPanel.is_excluded()) {
+ SidebarSelectPanel(newPanel.header, true, true); // found a panel that's not excluded to select -- do it
+ break;
+ }
+ } while (newPanel != currentPanel); // keep looking for a panel, but don't loop infinitely
+}
+
+function SidebarStopPanelLoad(header) {
+ var panel = sidebarObj.panels.get_panel_from_header_node(header);
+ panel.stop_load();
+}
+
+function SidebarReloadPanel(header) {
+ var panel = sidebarObj.panels.get_panel_from_header_node(header);
+ panel.reload();
+}
+
+// No one is calling this right now.
+function SidebarReload() {
+ sidebarObj.panels.refresh();
+}
+
+// Set up a lame hack to avoid opening two customize
+// windows on a double click.
+var gDisableCustomize = false;
+function enable_customize() {
+ gDisableCustomize = false;
+}
+
+// Bring up the Sidebar customize dialog.
+function SidebarCustomize() {
+ // Use a single sidebar customize dialog
+ var customizeWindow = Services.wm.getMostRecentWindow('sidebar:customize');
+
+ if (customizeWindow) {
+ debug("Reuse existing customize dialog");
+ customizeWindow.focus();
+ } else {
+ debug("Open a new customize dialog");
+
+ if (false == gDisableCustomize) {
+ debug("First time creating customize dialog");
+ gDisableCustomize = true;
+
+ var panels = document.getElementById('sidebar-panels');
+
+ customizeWindow = window.openDialog(
+ 'chrome://communicator/content/sidebar/customize.xul',
+ '_blank','centerscreen,chrome,resizable,dialog=no,dependent',
+ sidebarObj.master_datasources,
+ sidebarObj.master_resource,
+ sidebarObj.datasource_uri,
+ sidebarObj.resource);
+ setTimeout(enable_customize, 2000);
+ }
+ }
+}
+
+function BrowseMorePanels()
+{
+ var url = '';
+ var browser_url = "chrome://navigator/content/navigator.xul";
+ var locale;
+ try {
+ url = Services.prefs.getCharPref("sidebar.customize.directory.url");
+ var temp = Services.prefs.getCharPref("browser.chromeURL");
+ if (temp)
+ browser_url = temp;
+ } catch(ex) {
+ debug("Unable to get prefs: "+ex);
+ }
+ window.openDialog(browser_url, "_blank", "chrome,all,dialog=no", url);
+}
+
+
+
+function sidebar_is_collapsed() {
+ var sidebar_splitter = document.getElementById('sidebar-splitter');
+ return (sidebar_splitter &&
+ sidebar_splitter.getAttribute('state') == 'collapsed');
+}
+
+function SidebarExpandCollapse() {
+ var sidebar_splitter = document.getElementById('sidebar-splitter');
+ var sidebar_box = document.getElementById('sidebar-box');
+ if (sidebar_splitter.getAttribute('state') == 'collapsed') {
+ if (gMustInit)
+ sidebar_overlay_init();
+ debug("Expanding the sidebar");
+ sidebar_splitter.removeAttribute('state');
+ sidebar_box.removeAttribute('collapsed');
+ SidebarSetButtonOpen(true);
+ } else {
+ debug("Collapsing the sidebar");
+ sidebar_splitter.setAttribute('state', 'collapsed');
+ sidebar_box.setAttribute('collapsed', 'true');
+ SidebarSetButtonOpen(false);
+ }
+}
+
+// sidebar_is_hidden() - Helper function for SidebarShowHide().
+function sidebar_is_hidden() {
+ var sidebar_title = document.getElementById('sidebar-title-box');
+ var sidebar_box = document.getElementById('sidebar-box');
+ return sidebar_box.getAttribute('hidden') == 'true'
+ || sidebar_title.getAttribute('hidden') == 'true';
+}
+
+// Show/Hide the entire sidebar.
+// Invoked by the "View / Sidebar" menu option.
+function SidebarShowHide() {
+ var sidebar_box = document.getElementById('sidebar-box');
+ var title_box = document.getElementById('sidebar-title-box');
+ var sidebar_panels_splitter = document.getElementById('sidebar-panels-splitter');
+ var sidebar_panels_splitter_box = document.getElementById('sidebar-panels-splitter-box');
+ var sidebar_splitter = document.getElementById('sidebar-splitter');
+ var sidebar_menu_item = document.getElementById('sidebar-menu');
+ var tabs_menu = document.getElementById('sidebar-panel-picker');
+
+ if (sidebar_is_hidden()) {
+ debug("Showing the sidebar");
+
+ // for older profiles:
+ sidebar_box.setAttribute('hidden', 'false');
+ sidebar_panels_splitter_box.setAttribute('hidden', 'false');
+
+ sidebar_box.removeAttribute('collapsed');
+ if (sidebar_splitter.getAttribute('state') == 'collapsed')
+ sidebar_splitter.removeAttribute('state');
+ title_box.removeAttribute('hidden');
+ sidebar_panels_splitter_box.removeAttribute('collapsed');
+ sidebar_splitter.setAttribute('hidden', 'false');
+ if (sidebar_box.firstChild != sidebar_panels_splitter) {
+ debug("Showing the panels splitter");
+ sidebar_panels_splitter.removeAttribute('hidden');
+ if (sidebar_panels_splitter.getAttribute('state') == 'collapsed')
+ sidebar_panels_splitter.removeAttribute('state');
+ }
+ sidebar_overlay_init();
+ sidebar_menu_item.setAttribute('checked', 'true');
+ tabs_menu.removeAttribute('hidden');
+ SidebarSetButtonOpen(true);
+ } else {
+ debug("Hiding the sidebar");
+ var hide_everything = sidebar_panels_splitter.getAttribute('hidden') == 'true';
+ if (hide_everything) {
+ debug("Hide everything");
+ sidebar_box.setAttribute('collapsed', 'true');
+ sidebar_splitter.setAttribute('hidden', 'true');
+ } else {
+ sidebar_panels_splitter.setAttribute('hidden', 'true');
+ }
+ title_box.setAttribute('hidden', 'true');
+ sidebar_panels_splitter_box.setAttribute('collapsed', 'true');
+ sidebar_menu_item.setAttribute('checked', 'false');
+ tabs_menu.setAttribute('hidden', 'true');
+ SidebarSetButtonOpen(false);
+ }
+ // Immediately save persistent values
+ document.persist('sidebar-title-box', 'hidden');
+ PersistWidth();
+ window.content.focus();
+}
+
+function SidebarGetState() {
+ if (sidebar_is_hidden())
+ return "hidden";
+ if (sidebar_is_collapsed())
+ return "collapsed";
+ return "visible";
+}
+
+function SidebarSetState(aState) {
+ document.getElementById("sidebar-box").hidden = aState != "visible";
+ document.getElementById("sidebar-splitter").hidden = aState == "hidden";
+}
+
+function SidebarBuildPickerPopup() {
+ var menu = document.getElementById('sidebar-panel-picker-popup');
+ menu.database.AddDataSource(sidebarObj.datasource);
+ menu.builder.rebuild();
+
+ for (var ii=3; ii < menu.childNodes.length; ii++) {
+ var panel_menuitem = menu.childNodes.item(ii);
+ if (sb_panel_is_excluded(panel_menuitem)) {
+ debug(ii+": "+panel_menuitem.getAttribute('label')+ ": excluded; uncheck.");
+ panel_menuitem.removeAttribute('checked');
+ } else {
+ debug(ii+": "+panel_menuitem.getAttribute('label')+ ": included; check.");
+ panel_menuitem.setAttribute('checked', 'true');
+ }
+ }
+}
+
+function SidebarTogglePanel(panel_menuitem) {
+ if (!panel_menuitem.classList.contains("menuitem-sidebar") &&
+ !panel_menuitem.classList.contains("texttab-sidebar"))
+ return;
+
+ // Create a "container" wrapper around the current panels to
+ // manipulate the RDF:Seq more easily.
+
+ var did_exclude = false;
+ var panel_id = panel_menuitem.getAttribute('id');
+ var panel = sidebarObj.panels.get_panel_from_id(panel_id);
+ var panel_exclude = panel_menuitem.getAttribute('exclude')
+ if (panel_exclude == '') {
+ // Nothing excluded for this panel yet, so add this component to the list.
+ debug("Excluding " + panel_id + " from " + sidebarObj.component);
+ sidebarObj.datasource.Assert(RDF.GetResource(panel_id),
+ RDF.GetResource(NC + "exclude"),
+ RDF.GetLiteral(sidebarObj.component),
+ true);
+ panel.exclude();
+ did_exclude = true;
+ } else {
+ // Panel has an exclude string, but it may or may not have the
+ // current component listed in the string.
+ debug("Current exclude string: " + panel_exclude);
+ var new_exclude = panel_exclude;
+ if (sb_panel_is_excluded(panel_menuitem)) {
+ debug("Plucking this component out of the exclude list");
+ var replace_pat = new RegExp(sidebarObj.component + "\s*");
+ new_exclude = new_exclude.replace(replace_pat, "").trimLeft();
+ // did_exclude remains false
+ } else {
+ debug("Adding this component to the exclude list");
+ new_exclude = new_exclude + " " + sidebarObj.component;
+ panel.exclude();
+ did_exclude = true;
+ }
+ if (new_exclude == '') {
+ debug("Removing exclude list");
+ sidebarObj.datasource.Unassert(RDF.GetResource(panel_id),
+ RDF.GetResource(NC + "exclude"),
+ RDF.GetLiteral(sidebarObj.component));
+ } else {
+ debug("New exclude string: " + new_exclude);
+ exclude_target =
+ sidebarObj.datasource.GetTarget(RDF.GetResource(panel_id),
+ RDF.GetResource(NC + "exclude"),
+ true);
+ sidebarObj.datasource.Change(RDF.GetResource(panel_id),
+ RDF.GetResource(NC + "exclude"),
+ exclude_target,
+ RDF.GetLiteral(new_exclude));
+ }
+ }
+
+ var tabs = sidebarObj.panels.node.childNodes;
+
+ if (did_exclude)
+ {
+ // if we excluded a tab in view then add another one
+ if (panel.is_in_view())
+ {
+ // we excluded one so let's try to bring a non-excluded one into view
+ var newFirst = null;
+ var added = false;
+ for (var i = 2; i < tabs.length ; i += 2)
+ {
+ var currTab = sidebarObj.panels.get_panel_from_header_index(i);
+ var hasPotential = !currTab.is_excluded() && !currTab.is_in_view();
+
+ // set potential new first tab in case we can't find one after the
+ // tab that was just excluded
+ if (!newFirst && hasPotential)
+ newFirst = currTab;
+
+ if (i > panel.index && hasPotential)
+ {
+ currTab.header.setAttribute("in-view", true);
+ added = true;
+ break;
+ }
+ }
+ if (!added && newFirst)
+ newFirst.header.setAttribute("in-view", true);
+
+ // lose it from current view
+ panel.header.setAttribute("in-view", false);
+ }
+ }
+ else
+ {
+ panel.header.setAttribute("in-view", true);
+
+ // if we have one too many tabs we better get rid of an old one
+ if (sidebarObj.panels.num_panels_in_view() > gNumTabsInViewPref)
+ {
+ // we included a new tab so let's take the last one out of view
+ for (i = 2; i < tabs.length; i += 2)
+ {
+ var currHeader = tabs[i];
+ if (currHeader.hasAttribute("last-panel"))
+ currHeader.setAttribute("in-view", false);
+ }
+ }
+
+ panel.select(false);
+ }
+
+ if (did_exclude && !sidebarObj.panels.visible_panels_exist())
+ // surrender focus to main content area
+ window.content.focus();
+ else
+ // force all the sidebars to update
+ refresh_all_sidebars();
+
+ // Write the modified panels out.
+ sidebarObj.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
+}
+
+function SidebarNavigate(aDirection)
+{
+ debug("SidebarNavigate " + aDirection);
+
+ var tabs = sidebarObj.panels.node.childNodes;
+ var i;
+ var currHeader;
+ var currTab;
+ // move forward a tab (down in the template)
+ if (aDirection > 0)
+ {
+ // ensure we have a tab below the last one
+ var foundLast = false;
+ var oldFirst = null;
+ for (i = 2; i < tabs.length; i += 2)
+ {
+ currHeader = tabs[i];
+ currTab = new sbPanel(currHeader.getAttribute("id"), currHeader, i);
+
+ if (!currTab.is_excluded())
+ {
+ if (foundLast)
+ {
+ debug("toggling old first and new last");
+ debug("new last: " + currHeader.getAttribute("id"));
+ debug("old first: " + oldFirst.getAttribute("id"));
+ currHeader.setAttribute("in-view", true);
+ oldFirst.setAttribute("in-view", false);
+
+ // if old first was selected select new first instead
+ if (oldFirst.getAttribute("id") ==
+ sidebarObj.panels.node.getAttribute("last-selected-panel"))
+ {
+ sidebarObj.panels.node.setAttribute('last-selected-panel',
+ currTab.id);
+ }
+
+ break;
+ }
+
+ if (!foundLast && currHeader.hasAttribute("last-panel"))
+ {
+ debug("found last");
+ foundLast = true;
+ }
+
+ // set the old first in case we find a new last below
+ // the old last and need to toggle the new first's ``in-view''
+ if (!oldFirst && currTab.is_in_view())
+ oldFirst = currHeader;
+ }
+ }
+ }
+
+ // move back a tab (up in the template)
+ else if (aDirection < 0)
+ {
+ var newFirst = null, newLast = null;
+ var foundFirst = false;
+ for (i = 2; i < tabs.length; i += 2)
+ {
+ currHeader = tabs[i];
+ currTab = new sbPanel(currHeader.getAttribute("id"), currHeader, i);
+
+ if (!currTab.is_excluded())
+ {
+ if (!foundFirst && currHeader.hasAttribute("top-panel"))
+ {
+ debug("found first");
+ foundFirst = true;
+ }
+ if (!foundFirst)
+ {
+ debug("setting newFirst");
+ newFirst = currHeader;
+ }
+
+ if (currHeader.hasAttribute("last-panel"))
+ {
+ debug("found last");
+
+ // ensure we have a tab above the first one
+ if (newFirst)
+ {
+ debug("toggling new first and old last");
+ debug("new first: " + newFirst.getAttribute("id"));
+ debug("old last: " + currHeader.getAttribute("id"));
+
+ newFirst.setAttribute("in-view", true);
+ currHeader.setAttribute("in-view", false); // hide old last
+
+ // if old last was selected, now select one above it
+ if (sidebarObj.panels.node.getAttribute("last-selected-panel") ==
+ currTab.id)
+ {
+ sidebarObj.panels.node.setAttribute("last-selected-panel",
+ newLast.getAttribute("id"));
+ }
+
+ break;
+ }
+ }
+ if (currTab.is_in_view())
+ newLast = currHeader;
+ }
+ }
+ }
+
+ if (aDirection)
+ sidebarObj.panels.update(false);
+}
+
+//////////////////////////////////////////////////////////////
+// Sidebar Hacks and Work-arounds
+//////////////////////////////////////////////////////////////
+
+// SidebarCleanUpExpandCollapse() - Respond to grippy click.
+function SidebarCleanUpExpandCollapse() {
+ // XXX Mini hack. Persist isn't working too well. Force the persist,
+ // but wait until the change has commited.
+ if (gMustInit) {
+ gAboutToUncollapse = true;
+ sidebar_overlay_init();
+ }
+
+ setTimeout(Persist, 100, "sidebar-box", "collapsed");
+ setTimeout(() => sidebarObj.panels.refresh(), 100);
+}
+
+function PersistHeight() {
+ // XXX Mini hack. Persist isn't working too well. Force the persist,
+ // but wait until the last drag has been committed.
+ // May want to do something smarter here like only force it if the
+ // height has really changed.
+ setTimeout(Persist, 100, "sidebar-panels-splitter-box", "height");
+}
+
+function PersistWidth() {
+ // XXX Mini hack. Persist isn't working too well. Force the persist,
+ // but wait until the width change has commited. Also see bug 16516.
+ setTimeout(Persist, 100, "sidebar-box", "width");
+
+ var is_collapsed = document.getElementById("sidebar-box")
+ .getAttribute("collapsed") == "true";
+ SidebarSetButtonOpen(!is_collapsed);
+}
+
+function Persist(aAttribute, aValue) {
+ document.persist(aAttribute, aValue);
+}
+
+function SidebarFinishClick() {
+ PersistWidth();
+
+ var is_collapsed = document.getElementById('sidebar-box').getAttribute('collapsed') == 'true';
+ debug("collapsed: " + is_collapsed);
+ if (is_collapsed != sidebarObj.collapsed) {
+ if (gMustInit)
+ sidebar_overlay_init();
+ }
+}
+
+function SidebarSetButtonOpen(aSidebarNowOpen)
+{
+ // change state so toolbar icon can be updated
+ var pt = document.getElementById("PersonalToolbar");
+ if (pt) {
+ pt.setAttribute("prefixopen", aSidebarNowOpen);
+
+ // set tooltip for toolbar icon
+ var header = document.getElementById("sidebar-title-box");
+ var tooltip = header.getAttribute(aSidebarNowOpen ?
+ "tooltipclose" : "tooltipopen");
+ pt.setAttribute("prefixtooltip", tooltip);
+ }
+}
+
+function SidebarInitContextMenu(aMenu, aPopupNode)
+{
+ var panel = sidebarObj.panels.get_panel_from_header_node(aPopupNode);
+ var switchItem = document.getElementById("switch-ctx-item");
+ var reloadItem = document.getElementById("reload-ctx-item");
+ var stopItem = document.getElementById("stop-ctx-item");
+
+ // the current panel can be reloaded, but other panels are not showing
+ // any content, so we only allow you to switch to other panels
+ if (panel.is_selected())
+ {
+ switchItem.setAttribute("collapsed", "true");
+ reloadItem.removeAttribute("disabled");
+ }
+ else
+ {
+ switchItem.removeAttribute("collapsed");
+ reloadItem.setAttribute("disabled", "true");
+ }
+
+ // only if a panel is currently loading enable the ``Stop'' item
+ if (panel.get_iframe().getAttribute("loadstate") == "loading")
+ stopItem.removeAttribute("disabled");
+ else
+ stopItem.setAttribute("disabled", "true");
+}
+
+///////////////////////////////////////////////////////////////
+// Handy Debug Tools
+//////////////////////////////////////////////////////////////
+var debug = null;
+var dump_attributes = null;
+var dump_tree = null;
+if (!SB_DEBUG) {
+ debug = function (s) {};
+ dump_attributes = function (node, depth) {};
+ dump_tree = function (node) {};
+ var _dump_tree_recur = function (node, depth, index) {};
+} else {
+ debug = function (s) { dump("-*- sbOverlay: " + s + "\n"); };
+
+ dump_attributes = function (node, depth) {
+ var attributes = node.attributes;
+ var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | . ";
+
+ if (!attributes || attributes.length == 0) {
+ debug(indent.substr(indent.length - depth*2) + "no attributes");
+ }
+ for (var ii=0; ii < attributes.length; ii++) {
+ var attr = attributes.item(ii);
+ debug(indent.substr(indent.length - depth*2) + attr.name +
+ "=" + attr.value);
+ }
+ }
+ dump_tree = function (node) {
+ _dump_tree_recur(node, 0, 0);
+ }
+ _dump_tree_recur = function (node, depth, index) {
+ if (!node) {
+ debug("dump_tree: node is null");
+ }
+ var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | + ";
+ debug(indent.substr(indent.length - depth*2) + index +
+ " " + node.nodeName);
+ if (node.nodeType != Node.TEXT_NODE) {
+ dump_attributes(node, depth);
+ }
+ var kids = node.childNodes;
+ for (var ii=0; ii < kids.length; ii++) {
+ _dump_tree_recur(kids[ii], depth + 1, ii);
+ }
+ }
+}
+
+function SidebarBroadcastersToRDF()
+{
+ // Only the broadcasters in browser are synced to panels.rdf
+ if (sidebarObj.component != "navigator:browser")
+ return;
+
+ // Translation rules to translate between new broadcaster id and old RDF id.
+ const TRANSLATE = {viewBookmarksSidebar: "bookmarks",
+ viewHistorySidebar: "history",
+ viewSearchSidebar: "search",
+ viewAddressbookSidebar: "addressbook"};
+ const URN_PREFIX = "urn:sidebar:panel:";
+
+ const RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
+ .getService(Ci.nsIRDFContainerUtils);
+
+ /*
+ * Initialize RDF stuff.
+ */
+ let ds = sidebarObj.datasource;
+ let panelListRes = RDF.GetResource(NC + "panel-list");
+
+ let currentListRes = RDF.GetResource(sidebarObj.resource);
+ let masterListRes = RDF.GetResource(sidebarObj.master_resource);
+ let currentTarget = ds.GetTarget(currentListRes, panelListRes, true);
+ let masterTarget = ds.GetTarget(masterListRes, panelListRes, true);
+ if (!masterTarget) {
+ // No "master-panel-list" found, so create it.
+ masterTarget = RDF.GetAnonymousResource();
+ ds.Assert(masterListRes, panelListRes, masterTarget, true);
+ }
+ let currentSeq = RDFCU.MakeSeq(ds, currentTarget);
+ let masterSeq = RDFCU.MakeSeq(ds, masterTarget);
+
+ /*
+ * Run over broadcasters in browser window and add/update RDF entries
+ * based on them.
+ */
+ let titleRes = RDF.GetResource(NC + "title");
+ let urlRes = RDF.GetResource(NC + "content");
+
+ let bset = document.getElementById("mainBroadcasterSet");
+ let broadcasters = bset.getElementsByTagName("broadcaster");
+ let bclist = {};
+ for (let bId = 0; bId < broadcasters.length; bId++) {
+ let curBC = broadcasters[bId];
+ let title = curBC.getAttribute("sidebartitle") || curBC.getAttribute("label");
+ let url = curBC.getAttribute("sidebarurl");
+ let bcid = (curBC.id in TRANSLATE) ? TRANSLATE[curBC.id] : curBC.id;
+
+ if (!url || !title || !bcid)
+ continue;
+
+ // This one is needed later to check for obsolete sidebars.
+ bclist[bcid] = 1;
+
+ let panelRes = RDF.GetResource(URN_PREFIX + bcid);
+
+ // Literals of values that should be in RDF.
+ let titleLit = RDF.GetLiteral(title);
+ let urlLit = RDF.GetLiteral(url);
+ // Literals of values that are in RDF.
+ let curtitleLit = ds.GetTarget(panelRes, titleRes, true);
+ let cururlLit = ds.GetTarget(panelRes, urlRes, true);
+
+ // If the item doesn't already exist, create it.
+ if (!curtitleLit && !cururlLit) {
+ ds.Assert(panelRes, titleRes, titleLit, true);
+ ds.Assert(panelRes, urlRes, urlLit, true);
+ masterSeq.AppendElement(panelRes);
+ if (currentSeq.IndexOf(panelRes) == -1)
+ currentSeq.AppendElement(panelRes);
+ }
+ // Item already exists, but perhaps we need to update...
+ else {
+ let curtitle = curtitleLit.QueryInterface(Ci.nsIRDFLiteral).Value;
+ let cururl = cururlLit.QueryInterface(Ci.nsIRDFLiteral).Value;
+
+ if (curtitle != title)
+ ds.Change(panelRes, titleRes, curtitleLit, titleLit);
+
+ if (cururl != url)
+ ds.Change(panelRes, urlRes, cururlLit, urlLit);
+ }
+ }
+
+ /*
+ * Do the same the other way around to delete obsolete sidebars.
+ */
+
+ let masterElements = masterSeq.GetElements();
+ while (masterElements.hasMoreElements()) {
+ let curElementRes = masterElements.getNext();
+ let curId = curElementRes.QueryInterface(Ci.nsIRDFResource).Value;
+
+ if (curId.substr(0, URN_PREFIX.length) != URN_PREFIX)
+ continue;
+
+ curId = curId.substr(URN_PREFIX.length);
+ if (!(curId in bclist)) {
+ let properties = ds.ArcLabelsOut(curElementRes);
+ while(properties.hasMoreElements()) {
+ let propertyRes = properties.getNext();
+ let valueLit = ds.GetTarget(curElementRes, propertyRes, true);
+ ds.Unassert(curElementRes, propertyRes, valueLit);
+ }
+ masterSeq.RemoveElement(curElementRes, true);
+ if (currentSeq.IndexOf(curElementRes) != -1)
+ currentSeq.RemoveElement(curElementRes, true);
+ }
+ }
+
+ // Write modified data.
+ sidebarObj.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
+}
+
+
+//////////////////////////////////////////////////////////////
+// Install the load/unload handlers
+//////////////////////////////////////////////////////////////
+addEventListener("load", sidebar_overlay_init, false);
+addEventListener("unload", sidebar_overlay_destruct, false);
diff --git a/comm/suite/components/sidebar/content/sidebarOverlay.xul b/comm/suite/components/sidebar/content/sidebarOverlay.xul
new file mode 100644
index 0000000000..0c1aa08566
--- /dev/null
+++ b/comm/suite/components/sidebar/content/sidebarOverlay.xul
@@ -0,0 +1,247 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML; indent-tabs-mode: nil -*- -->
+<!--
+
+ 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/. -->
+
+<!-- This overlay requires that the files it overlays has the menupopup
+ contentAreaContextMenu defined for context menus to work correctly in
+ certain custom tabs -->
+
+<?xml-stylesheet href="chrome://communicator/content/sidebar/sidebarOverlay.css" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/sidebar/sidebar.css" type="text/css"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % sidebarOverlayDTD SYSTEM "chrome://communicator/locale/sidebar/sidebarOverlay.dtd" >
+%sidebarOverlayDTD;
+]>
+
+<overlay id="sidebarOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <!-- Overlay of broadcasterset to get our panels in -->
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="viewBookmarksSidebar"
+ autoCheck="false"
+ type="checkbox"
+ group="sidebar"
+ sidebartitle="&sidebar.client-bookmarks.label;"
+ sidebarurl="chrome://communicator/content/bookmarks/bookmarksPanel.xul"
+ oncommand="toggleSidebar('viewBookmarksSidebar');"/>
+ <broadcaster id="viewHistorySidebar"
+ autoCheck="false"
+ type="checkbox"
+ group="sidebar"
+ sidebartitle="&sidebar.client-history.label;"
+ sidebarurl="chrome://communicator/content/history/history-panel.xul"
+ oncommand="toggleSidebar('viewHistorySidebar');"/>
+ <broadcaster id="viewSearchSidebar"
+ autoCheck="false"
+ type="checkbox"
+ group="sidebar"
+ sidebartitle="&sidebar.search.label;"
+ sidebarurl="chrome://communicator/content/search/search-panel.xul"
+ oncommand="toggleSidebar('viewSearchSidebar');"/>
+ <broadcaster id="viewAddressbookSidebar"
+ autoCheck="false"
+ type="checkbox"
+ group="sidebar"
+ sidebartitle="&sidebar.client-addressbook.label;"
+ sidebarurl="chrome://messenger/content/addressbook/addressbook-panel.xul"
+ oncommand="toggleSidebar('viewAddressbookSidebar');"/>
+ </broadcasterset>
+
+ <command id="toggleSidebar" oncommand="SidebarShowHide();"/>
+#ifndef XP_MACOSX
+ <key id="showHideSidebar"
+ keycode="VK_F9"
+ command="toggleSidebar"/>
+#else
+ <key id="showHideSidebar"
+ key="&showHideSidebarCmd.key;"
+ modifiers="accel,alt"
+ command="toggleSidebar"/>
+#endif
+ <menupopup id="sidebarPopup"
+ onpopupshowing="SidebarInitContextMenu(this, document.popupNode);">
+ <menuitem id="switch-ctx-item" label="&sidebar.switch.label;"
+ accesskey="&sidebar.switch.accesskey;" default="true"
+ oncommand="SidebarSelectPanel(document.popupNode,false,false);"/>
+ <menuitem id="reload-ctx-item" label="&sidebar.reload.label;"
+ accesskey="&sidebar.reload.accesskey;" disabled="true"
+ oncommand="SidebarReloadPanel(document.popupNode);"/>
+ <menuitem id="stop-ctx-item" label="&sidebar.loading.stop.label;"
+ accesskey="&sidebar.loading.stop.accesskey;" disabled="true"
+ oncommand="SidebarStopPanelLoad(document.popupNode);"/>
+ <menuseparator/>
+ <menuitem id="hide-ctx-item" label="&sidebar.hide.label;"
+ accesskey="&sidebar.hide.accesskey;"
+ oncommand="SidebarTogglePanel(document.popupNode);"/>
+ <menuseparator/>
+ <menuitem id="customize-ctx-item" label="&sidebar.customize.label;"
+ accesskey="&sidebar.customize.accesskey;"
+ oncommand="SidebarCustomize();"/>
+ </menupopup>
+
+ <!-- Overlay the sidebar panels -->
+ <vbox id="sidebar-box" hidden="true" persist="hidden width collapsed">
+ <splitter id="sidebar-panels-splitter" collapse="after" persist="state"
+ onmouseup="PersistHeight();" hidden="true">
+ <grippy/>
+ </splitter>
+ <vbox id="sidebar-panels-splitter-box" flex="1"
+ persist="collapsed">
+ <sidebarheader id="sidebar-title-box" class="sidebarheader-main"
+ label="&sidebar.panels.label;" persist="hidden" type="box"
+ collapse="after" onmouseup="PersistHeight();"
+ tooltipopen="&sidebar.open.tooltip;"
+ tooltipclose="&sidebar.close.tooltip;">
+ <toolbarbutton type="menu" id="sidebar-panel-picker" class="tabbable"
+ onpopupshowing="SidebarBuildPickerPopup();"
+ label="&sidebar.picker.label;" >
+ <menupopup id="sidebar-panel-picker-popup"
+ datasources="rdf:null"
+ ref="urn:sidebar:current-panel-list"
+ oncommand="SidebarTogglePanel(event.target);" >
+ <template>
+ <rule>
+ <conditions>
+ <content uri="?uri"/>
+ <triple subject="?uri"
+ predicate="http://home.netscape.com/NC-rdf#panel-list"
+ object="?panel-list"/>
+ <member container="?panel-list" child="?panel"/>
+ <triple subject="?panel"
+ predicate="http://home.netscape.com/NC-rdf#title"
+ object="?title" />
+ </conditions>
+ <bindings>
+ <binding subject="?panel"
+ predicate="http://home.netscape.com/NC-rdf#exclude"
+ object="?exclude"/>
+ <binding subject="?panel"
+ predicate="http://home.netscape.com/NC-rdf#prereq"
+ object="?prereq"/>
+ </bindings>
+ <action>
+ <menuitem uri="?panel" type="checkbox" class="menuitem-sidebar"
+ label="?title" exclude="?exclude" prereq="?prereq"/>
+ </action>
+ </rule>
+ </template>
+ <menuitem label="&sidebar.customize.label;" accesskey="&sidebar.customize.accesskey;"
+ oncommand="SidebarCustomize();" />
+ <menuitem label="&sidebar.sbDirectory.label;"
+ oncommand="BrowseMorePanels();" />
+ <menuseparator />
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="sidebar-close-button" oncommand="SidebarShowHide();"
+ tooltiptext="&sidebar.close.tooltip;"/>
+ </sidebarheader>
+
+ <vbox id="sidebar-panels"
+ datasources="rdf:null"
+ ref="urn:sidebar:current-panel-list"
+ last-selected-panel="urn:sidebar:panel:bookmarks"
+ persist="last-selected-panel height collapsed" flex="1"
+ onclick="return contentAreaClick(event);">
+ <template id="sidebar-template">
+ <rule>
+ <conditions>
+ <content uri="?uri"/>
+ <triple subject="?uri" object="?panel-list"
+ predicate="http://home.netscape.com/NC-rdf#panel-list" />
+ <member container="?panel-list" child="?panel"/>
+ <triple subject="?panel" object="?title"
+ predicate="http://home.netscape.com/NC-rdf#title" />
+ <triple subject="?panel" object="?content"
+ predicate="http://home.netscape.com/NC-rdf#content" />
+ </conditions>
+ <bindings>
+ <binding subject="?panel" object="?exclude"
+ predicate="http://home.netscape.com/NC-rdf#exclude" />
+ <binding subject="?panel" object="?prereq"
+ predicate="http://home.netscape.com/NC-rdf#prereq" />
+ </bindings>
+ <action>
+ <hbox uri="?panel" class="box-texttab texttab-sidebar"
+ oncommand="SidebarSelectPanel(this,false,false)"
+ hidden="true" label="?title" exclude="?exclude"
+ prereq="?prereq" context="sidebarPopup"/>
+ <vbox uri="?panel" flex="1" hidden="true"
+ loadstate="never loaded">
+ <vbox flex="1" class="iframe-panel loadarea">
+ <hbox flex="1" align="center">
+ <image class="image-panel-loading"/>
+ <label class="text-panel-loading"
+ value="&sidebar.loading.label;"/>
+ <label class="text-panel-loading" hidden="true"
+ loading="false"
+ value="&sidebar.loadstopped.label;"/>
+ <button type="stop" label="&sidebar.loading.stop.label;"
+ oncommand="SidebarStopPanelLoad(this.parentNode.parentNode.parentNode.previousSibling);"/>
+ <button label="&sidebar.reload.label;" hidden="true"
+ oncommand="SidebarReload();"/>
+ </hbox>
+ <spacer flex="100%"/>
+ </vbox>
+ <notificationbox flex="1" collapsed="true" class="sidebar-notificationbox browser-notificationbox">
+ <browser flex="1" class="browser-sidebar" src="about:blank"
+ hidden="true" collapsed="true" content="?content"
+ disablehistory="true"/>
+ <browser flex="1" class="browser-sidebar" src="about:blank"
+ hidden="true" collapsed="true" content="?content"
+ type="content" context="contentAreaContextMenu"
+ disablehistory="true" tooltip="aHTMLTooltip"/>
+ </notificationbox>
+ </vbox>
+ </action>
+ </rule>
+ </template>
+ <vbox id="sidebar-iframe-no-panels" class="iframe-panel" flex="1"
+ hidden="true">
+ <description>&sidebar.no-panels.state;</description>
+ <description>&sidebar.no-panels.add;</description>
+ <description>&sidebar.no-panels.hide;</description>
+ </vbox>
+ </vbox>
+ <vbox flex="0">
+ <hbox id="nav-buttons-box" hidden="true">
+ <toolbarbutton flex="1" pack="center"
+ class="sidebar-nav-button tab-fwd" onclick="SidebarNavigate(-1);"/>
+ <toolbarbutton flex="1" pack="center"
+ class="sidebar-nav-button tab-back" onclick="SidebarNavigate(1);"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+
+ <!-- Splitter on the right of sidebar -->
+ <splitter id="sidebar-splitter" collapse="before" persist="state hidden"
+ class="chromeclass-extrachrome sidebar-splitter" align="center"
+ hidden="true" onmouseup="SidebarFinishClick();">
+ <grippy class="sidebar-splitter-grippy"
+ onclick="SidebarCleanUpExpandCollapse();"/>
+ </splitter>
+
+ <!-- View->Sidebar toggle -->
+ <menupopup id="menu_View_Popup">
+ <menu id="menu_Toolbars">
+ <menupopup id="view_toolbars_popup">
+ <menuseparator/>
+ <menuitem id="sidebar-menu" type="checkbox"
+ label="&sidebarCmd.label;"
+ accesskey="&sidebarCmd.accesskey;"
+ command="toggleSidebar"
+ key="showHideSidebar"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+ <!-- Scripts go last, because they peek at state to tweak menus -->
+ <script src="chrome://communicator/content/sidebar/sidebarOverlay.js"/>
+
+</overlay>
+
diff --git a/comm/suite/components/sidebar/jar.mn b/comm/suite/components/sidebar/jar.mn
new file mode 100644
index 0000000000..d6ab848a98
--- /dev/null
+++ b/comm/suite/components/sidebar/jar.mn
@@ -0,0 +1,16 @@
+# 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/.
+
+comm.jar:
+ content/communicator/sidebar/customize-panel.js (content/customize-panel.js)
+ content/communicator/sidebar/customize-panel.xul (content/customize-panel.xul)
+ content/communicator/sidebar/customize.js (content/customize.js)
+ content/communicator/sidebar/customize.xul (content/customize.xul)
+ content/communicator/sidebar/PageNotFound.xul (content/PageNotFound.xul)
+ content/communicator/sidebar/preview.js (content/preview.js)
+ content/communicator/sidebar/preview.xul (content/preview.xul)
+ content/communicator/sidebar/sidebarBindings.xml (content/sidebarBindings.xml)
+ content/communicator/sidebar/sidebarOverlay.css (content/sidebarOverlay.css)
+ content/communicator/sidebar/sidebarOverlay.js (content/sidebarOverlay.js)
+* content/communicator/sidebar/sidebarOverlay.xul (content/sidebarOverlay.xul)
diff --git a/comm/suite/components/sidebar/moz.build b/comm/suite/components/sidebar/moz.build
new file mode 100644
index 0000000000..7a2e523961
--- /dev/null
+++ b/comm/suite/components/sidebar/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsISidebar.idl",
+]
+
+XPIDL_MODULE = "suite-sidebar"
+
+EXTRA_COMPONENTS += [
+ "nsSidebar.js",
+ "SuiteSidebar.manifest",
+]
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/comm/suite/components/sidebar/nsISidebar.idl b/comm/suite/components/sidebar/nsISidebar.idl
new file mode 100644
index 0000000000..515b939872
--- /dev/null
+++ b/comm/suite/components/sidebar/nsISidebar.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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/. */
+
+/*
+
+ The Sidebar API for 3rd parties
+
+*/
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(97bfa970-8222-4c3f-bbe8-42141e4c7982)]
+interface nsISidebar : nsISupports
+{
+ void addPanel(in AString aTitle, in AString aContentURL,
+ in AString aCustomizeURL);
+ void addPersistentPanel(in AString aTitle, in AString aContentURL,
+ in AString aCustomizeURL);
+ void addSearchEngine(in AString engineURL, in AString iconURL,
+ in AString suggestedTitle, in AString suggestedCategory);
+ void AddSearchProvider(in AString aDescriptionURL);
+ unsigned long IsSearchProviderInstalled(in AString aSearchURL);
+};
diff --git a/comm/suite/components/sidebar/nsSidebar.js b/comm/suite/components/sidebar/nsSidebar.js
new file mode 100644
index 0000000000..472ec25a5d
--- /dev/null
+++ b/comm/suite/components/sidebar/nsSidebar.js
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+/*
+ * No magic constructor behaviour, as is de rigeur for XPCOM.
+ * If you must perform some initialization, and it could possibly fail (even
+ * due to an out-of-memory condition), you should use an Init method, which
+ * can convey failure appropriately (thrown exception in JS,
+ * NS_FAILED(nsresult) return in C++).
+ *
+ * In JS, you can actually cheat, because a thrown exception will cause the
+ * CreateInstance call to fail in turn, but not all languages are so lucky.
+ * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code
+ * for portability reasons -- and even when you're building completely
+ * platform-specific code, you can't throw across an XPCOM method boundary.)
+ */
+
+const DEBUG = false; /* set to false to suppress debug messages */
+const PANELS_RDF_FILE = "UPnls"; /* directory services property to find panels.rdf */
+
+const SIDEBAR_CONTRACTID = "@mozilla.org/sidebar;1";
+const SIDEBAR_CID = Components.ID("{22117140-9c6e-11d3-aaf1-00805f8a4905}");
+const CONTAINER_CONTRACTID = "@mozilla.org/rdf/container;1";
+const NETSEARCH_CONTRACTID = "@mozilla.org/rdf/datasource;1?name=internetsearch"
+const nsISupports = Ci.nsISupports;
+const nsISidebar = Ci.nsISidebar;
+const nsIRDFContainer = Ci.nsIRDFContainer;
+const nsIProperties = Ci.nsIProperties;
+const nsIFileURL = Ci.nsIFileURL;
+const nsIRDFRemoteDataSource = Ci.nsIRDFRemoteDataSource;
+const nsIClassInfo = Ci.nsIClassInfo;
+
+// File extension for Sherlock search plugin description files
+const SHERLOCK_FILE_EXT_REGEXP = /\.src$/i;
+
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function nsSidebar()
+{
+ const RDF_CONTRACTID = "@mozilla.org/rdf/rdf-service;1";
+ const nsIRDFService = Ci.nsIRDFService;
+
+ this.rdf = Cc[RDF_CONTRACTID].getService(nsIRDFService);
+ this.datasource_uri = getSidebarDatasourceURI(PANELS_RDF_FILE);
+ gDebugLog('datasource_uri is ' + this.datasource_uri);
+ this.resource = 'urn:sidebar:current-panel-list';
+ this.datasource = this.rdf.GetDataSource(this.datasource_uri);
+}
+
+nsSidebar.prototype.nc = "http://home.netscape.com/NC-rdf#";
+
+nsSidebar.prototype.isPanel =
+function (aContentURL)
+{
+ var container =
+ Cc[CONTAINER_CONTRACTID].createInstance(nsIRDFContainer);
+
+ container.Init(this.datasource, this.rdf.GetResource(this.resource));
+
+ /* Create a resource for the new panel and add it to the list */
+ var panel_resource =
+ this.rdf.GetResource("urn:sidebar:3rdparty-panel:" + aContentURL);
+
+ return (container.IndexOf(panel_resource) != -1);
+}
+
+function sidebarURLSecurityCheck(url)
+{
+ if (!/(^http:|^ftp:|^https:)/i.test(url))
+ throw "Script attempted to add sidebar panel from illegal source";
+}
+
+/* decorate prototype to provide ``class'' methods and property accessors */
+nsSidebar.prototype.addPanel =
+function (aTitle, aContentURL, aCustomizeURL)
+{
+ gDebugLog("addPanel(" + aTitle + ", " + aContentURL + ", " +
+ aCustomizeURL + ")");
+
+ return this.addPanelInternal(aTitle, aContentURL, aCustomizeURL, false);
+}
+
+nsSidebar.prototype.addPersistentPanel =
+function(aTitle, aContentURL, aCustomizeURL)
+{
+ gDebugLog("addPersistentPanel(" + aTitle + ", " + aContentURL + ", " +
+ aCustomizeURL + ")\n");
+
+ return this.addPanelInternal(aTitle, aContentURL, aCustomizeURL, true);
+}
+
+nsSidebar.prototype.addPanelInternal =
+function (aTitle, aContentURL, aCustomizeURL, aPersist)
+{
+ sidebarURLSecurityCheck(aContentURL);
+
+ // Create a "container" wrapper around the current panels to
+ // manipulate the RDF:Seq more easily.
+ var panel_list = this.datasource.GetTarget(this.rdf.GetResource(this.resource), this.rdf.GetResource(nsSidebar.prototype.nc+"panel-list"), true);
+ if (panel_list) {
+ panel_list.QueryInterface(Ci.nsIRDFResource);
+ } else {
+ // Datasource is busted. Start over.
+ gDebugLog("Sidebar datasource is busted\n");
+ }
+
+ var container = Cc[CONTAINER_CONTRACTID].createInstance(nsIRDFContainer);
+ container.Init(this.datasource, panel_list);
+
+ /* Create a resource for the new panel and add it to the list */
+ var panel_resource =
+ this.rdf.GetResource("urn:sidebar:3rdparty-panel:" + aContentURL);
+ var panel_index = container.IndexOf(panel_resource);
+ var stringBundle, titleMessage, dialogMessage;
+ if (panel_index != -1)
+ {
+ try {
+ stringBundle = Services.strings.createBundle("chrome://communicator/locale/sidebar/sidebar.properties");
+ if (stringBundle) {
+ titleMessage = stringBundle.GetStringFromName("dupePanelAlertTitle");
+ dialogMessage = stringBundle.GetStringFromName("dupePanelAlertMessage2");
+ dialogMessage = dialogMessage.replace(/%url%/, aContentURL);
+ }
+ }
+ catch (e) {
+ titleMessage = "Sidebar";
+ dialogMessage = aContentURL + " already exists in Sidebar. No string bundle";
+ }
+
+ Services.prompt.alert(null, titleMessage, dialogMessage);
+
+ return;
+ }
+
+ try {
+ stringBundle = Services.strings.createBundle("chrome://communicator/locale/sidebar/sidebar.properties");
+ if (stringBundle) {
+ titleMessage = stringBundle.GetStringFromName("addPanelConfirmTitle");
+ dialogMessage = stringBundle.GetStringFromName("addPanelConfirmMessage2");
+ if (aPersist)
+ {
+ var warning = stringBundle.GetStringFromName("persistentPanelWarning2");
+ dialogMessage += "\n" + warning;
+ }
+ dialogMessage = dialogMessage.replace(/%title%/, aTitle);
+ dialogMessage = dialogMessage.replace(/%url%/, aContentURL);
+ dialogMessage = dialogMessage.replace(/#/g, "\n");
+ }
+ }
+ catch (e) {
+ titleMessage = "Add Tab to Sidebar";
+ dialogMessage = "No string bundle. Add the Tab '" + aTitle + "' to Sidebar?\n\n" + "Source: " + aContentURL;
+ }
+
+ var rv = Services.prompt.confirm(null, titleMessage, dialogMessage);
+
+ if (!rv)
+ return;
+
+ /* Now make some sidebar-ish assertions about it... */
+ this.datasource.Assert(panel_resource,
+ this.rdf.GetResource(this.nc + "title"),
+ this.rdf.GetLiteral(aTitle),
+ true);
+ this.datasource.Assert(panel_resource,
+ this.rdf.GetResource(this.nc + "content"),
+ this.rdf.GetLiteral(aContentURL),
+ true);
+ if (aCustomizeURL)
+ this.datasource.Assert(panel_resource,
+ this.rdf.GetResource(this.nc + "customize"),
+ this.rdf.GetLiteral(aCustomizeURL),
+ true);
+ var persistValue = aPersist ? "true" : "false";
+ this.datasource.Assert(panel_resource,
+ this.rdf.GetResource(this.nc + "persist"),
+ this.rdf.GetLiteral(persistValue),
+ true);
+
+ container.AppendElement(panel_resource);
+
+ // Use an assertion to pass a "refresh" event to all the sidebars.
+ // They use observers to watch for this assertion (in sidebarOverlay.js).
+ this.datasource.Assert(this.rdf.GetResource(this.resource),
+ this.rdf.GetResource(this.nc + "refresh"),
+ this.rdf.GetLiteral("true"),
+ true);
+ this.datasource.Unassert(this.rdf.GetResource(this.resource),
+ this.rdf.GetResource(this.nc + "refresh"),
+ this.rdf.GetLiteral("true"));
+
+ /* Write the modified panels out. */
+ this.datasource.QueryInterface(nsIRDFRemoteDataSource).Flush();
+}
+
+nsSidebar.prototype.validateSearchEngine =
+function (engineURL, iconURL)
+{
+ try
+ {
+ // Make sure the URLs are HTTP, HTTPS, or FTP.
+ var isWeb = /^(https?|ftp):\/\//i;
+
+ if (!isWeb.test(engineURL))
+ throw "Unsupported search engine URL";
+
+ if (iconURL && !isWeb.test(iconURL))
+ throw "Unsupported search icon URL.";
+ }
+ catch(ex)
+ {
+ gDebugLog(ex);
+ Cu.reportError("Invalid argument passed to window.sidebar.addSearchEngine: " + ex);
+
+ var searchBundle = Services.strings.createBundle("chrome://global/locale/search/search.properties");
+ var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ var brandName = brandBundle.GetStringFromName("brandShortName");
+ var title = searchBundle.GetStringFromName("error_invalid_engine_title");
+ var msg = searchBundle.formatStringFromName("error_invalid_engine_msg",
+ [brandName], 1);
+ Services.ww.getNewPrompter(null).alert(title, msg);
+ return false;
+ }
+
+ return true;
+}
+
+// The suggestedTitle and suggestedCategory parameters are ignored, but remain
+// for backward compatibility.
+nsSidebar.prototype.addSearchEngine =
+function (engineURL, iconURL, suggestedTitle, suggestedCategory)
+{
+ gDebugLog("addSearchEngine(" + engineURL + ", " + iconURL + ", " +
+ suggestedCategory + ", " + suggestedTitle + ")");
+
+ if (!this.validateSearchEngine(engineURL, iconURL))
+ return;
+
+ // OpenSearch files will likely be far more common than Sherlock files, and
+ // have less consistent suffixes, so we assume that ".src" is a Sherlock
+ // (text) file, and anything else is OpenSearch (XML).
+ var dataType;
+ if (SHERLOCK_FILE_EXT_REGEXP.test(engineURL))
+ dataType = Ci.nsISearchEngine.DATA_TEXT;
+ else
+ dataType = Ci.nsISearchEngine.DATA_XML;
+
+ Services.search.addEngine(engineURL, dataType, iconURL, true);
+}
+
+// This function exists largely to implement window.external.AddSearchProvider(),
+// to match other browsers' APIs. The capitalization, although nonstandard here,
+// is therefore important.
+nsSidebar.prototype.AddSearchProvider =
+function (aDescriptionURL)
+{
+ // Get the favicon URL for the current page, or our best guess at the current
+ // page since we don't have easy access to the active document. Most search
+ // engines will override this with an icon specified in the OpenSearch
+ // description anyway.
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+ var browser = win.getBrowser();
+ var iconURL = "";
+ // Use documentURIObject in the check for shouldLoadFavIcon so that we
+ // do the right thing with about:-style error pages. Bug 453442
+ if (browser.shouldLoadFavIcon(browser.selectedBrowser
+ .contentDocument
+ .documentURIObject))
+ iconURL = browser.getIcon();
+
+ if (!this.validateSearchEngine(aDescriptionURL, iconURL))
+ return;
+
+ const typeXML = Ci.nsISearchEngine.DATA_XML;
+ Services.search.addEngine(aDescriptionURL, typeXML, iconURL, true);
+}
+
+// This function exists to implement window.external.IsSearchProviderInstalled(),
+// for compatibility with other browsers. It will return an integer value
+// indicating whether the given engine is installed for the current user.
+// However, it is currently stubbed out due to security/privacy concerns
+// stemming from difficulties in determining what domain issued the request.
+// See bug 340604 and
+// http://msdn.microsoft.com/en-us/library/aa342526%28VS.85%29.aspx .
+// XXX Implement this!
+nsSidebar.prototype.IsSearchProviderInstalled =
+function (aSearchURL)
+{
+ return 0;
+}
+
+nsSidebar.prototype.classInfo = XPCOMUtils.generateCI({
+ classID: SIDEBAR_CID,
+ contractID: SIDEBAR_CONTRACTID,
+ classDescription: "Sidebar",
+ interfaces: [nsISidebar],
+ flags: nsIClassInfo.DOM_OBJECT});
+
+nsSidebar.prototype.QueryInterface =
+ XPCOMUtils.generateQI([nsISidebar]);
+
+nsSidebar.prototype.classID = SIDEBAR_CID;
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSidebar]);
+
+var gDebugLog;
+
+/* static functions */
+if (DEBUG)
+ gDebugLog = function (s) { dump("-*- sidebar component: " + s + "\n"); }
+else
+ gDebugLog = function (s) {}
+
+function getSidebarDatasourceURI(panels_file_id)
+{
+ try
+ {
+ /* use the fileLocator to look in the profile directory
+ * to find 'panels.rdf', which is the
+ * database of the user's currently selected panels.
+ * if <profile>/panels.rdf doesn't exist, get will copy
+ *bin/defaults/profile/panels.rdf to <profile>/panels.rdf */
+ var sidebar_file = Services.dirsvc.get(panels_file_id,
+ Ci.nsIFile);
+
+ if (!sidebar_file.exists())
+ {
+ /* this should not happen, as GetFileLocation() should copy
+ * defaults/panels.rdf to the users profile directory */
+ gDebugLog("sidebar file does not exist");
+ return null;
+ }
+
+ var file_handler = Services.io.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
+ var sidebar_uri = file_handler.getURLSpecFromFile(sidebar_file);
+ gDebugLog("sidebar uri is " + sidebar_uri);
+ return sidebar_uri;
+ }
+ catch (ex)
+ {
+ /* this should not happen */
+ gDebugLog("caught " + ex + " getting sidebar datasource uri");
+ return null;
+ }
+}