changeset 11:d0e32e673d94

Swapped samplecode.js w/ utils.js from Ubiquity.
author Atul Varma <varmaa@toolness.com>
date Tue, 06 Jan 2009 01:45:13 -0800
parents 5418d21d7751
children 947df63942ed
files samplecode.js thingy.js
diffstat 2 files changed, 530 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/samplecode.js	Mon Jan 05 21:31:56 2009 -0800
+++ b/samplecode.js	Tue Jan 06 01:45:13 2009 -0800
@@ -19,6 +19,8 @@
  *
  * Contributor(s):
  *   Atul Varma <atul@mozilla.com>
+ *   Blair McBride <unfocused@gmail.com>
+ *   Jono DiCarlo <jdicarlo@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -34,20 +36,41 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
-// = Sample Code =
+// = Utils =
 //
-// This is just a test of the Thingy documentation system.
+// This is a small library of all-purpose, general utility functions
+// for use by chrome code.  Everything clients need is contained within
+// the {{{Utils}}} namespace.
+
+var EXPORTED_SYMBOLS = ["Utils"];
 
-var BEANS = 1;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+var Utils = {};
+
+// Keep a reference to the global object, as certain utility functions
+// need it.
+Utils.__globalObject = this;
 
-// == Functions ==
-
-// ==== {{{blarg()}}} ====
+// ** {{{ Utils.reportWarning() }}} **
+//
+// This function can be used to report a warning to the JS Error Console,
+// which can be displayed in Firefox by choosing "Error Console" from
+// the "Tools" menu.
+//
+// {{{aMessage}}} is a plaintext string corresponding to the warning
+// to provide.
 //
-// This function takes no parameters and returns the string
-// {{{blarg}}}.  It's pretty cool that way.
+// {{{stackFrame}}} is an optional {{{nsIStackFrame}}} instance that
+// corresponds to the stack frame which is reporting the error; a link
+// to the line of source that it references will be shown in the JS
+// Error Console.  It defaults to the caller's stack frame.
 
-function blarg() {
+Utils.reportWarning = function reportWarning(aMessage, stackFrame) {
+  if (!stackFrame)
+    stackFrame = Components.stack.caller;
+
   var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
                        .getService(Components.interfaces.nsIConsoleService);
   var scriptError = Components.classes["@mozilla.org/scripterror;1"]
@@ -60,16 +83,505 @@
   var aCategory = "ubiquity javascript";
   scriptError.init(aMessage, aSourceName, aSourceLine, aLineNumber,
                    aColumnNumber, aFlags, aCategory);
+  consoleService.logMessage(scriptError);
+};
 
-  // All that was done for naught.
+// ** {{{ Utils.reportInfo() }}} **
+//
+// Reports a purely informational message to the JS Error Console.
+// Source code links aren't provided for informational messages, so
+// unlike {{{Utils.reportWarning()}}}, a stack frame can't be passed
+// in to this function.
+
+Utils.reportInfo = function reportInfo(aMessage) {
+  var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
+                       .getService(Components.interfaces.nsIConsoleService);
+  var aCategory = "ubiquity javascript: ";
+  consoleService.logStringMessage(aCategory + aMessage);
+};
+
+// ** {{{ Utils.encodeJson() }}} **
+//
+// This function serializes the given object using JavaScript Object
+// Notation (JSON).
+
+Utils.encodeJson = function encodeJson(object) {
+  var json = Cc["@mozilla.org/dom/json;1"]
+             .createInstance(Ci.nsIJSON);
+  return json.encode(object);
+};
+
+// ** {{{ Utils.decodeJson() }}} **
+//
+// This function unserializes the given string in JavaScript Object
+// Notation (JSON) format and returns the result.
+
+Utils.decodeJson = function decodeJson(string) {
+  var json = Cc["@mozilla.org/dom/json;1"]
+             .createInstance(Ci.nsIJSON);
+  return json.decode(string);
+};
+
+// ** {{{ Utils.setTimeout() }}} **
+//
+// This function works just like the {{{window.setTimeout()}}} method
+// in content space, but it can only accept a function (not a string)
+// as the callback argument.
+//
+// {{{callback}}} is the callback function to call when the given
+// delay period expires.  It will be called only once (not at a regular
+// interval).
+//
+// {{{delay}}} is the delay, in milliseconds, after which the callback
+// will be called once.
+//
+// This function returns a timer ID, which can later be given to
+// {{{Utils.clearTimeout()}}} if the client decides that it wants to
+// cancel the callback from being triggered.
+
+// TODO: Allow strings for the first argument like DOM setTimeout() does.
+
+Utils.setTimeout = function setTimeout(callback, delay) {
+  var classObj = Cc["@mozilla.org/timer;1"];
+  var timer = classObj.createInstance(Ci.nsITimer);
+  var timerID = Utils.__timerData.nextID;
+  // emulate window.setTimeout() by incrementing next ID by random amount
+  Utils.__timerData.nextID += Math.floor(Math.random() * 100) + 1;
+  Utils.__timerData.timers[timerID] = timer;
+
+  timer.initWithCallback(new Utils.__TimerCallback(callback),
+                         delay,
+                         classObj.TYPE_ONE_SHOT);
+  return timerID;
+};
+
+// ** {{{ Utils.clearTimeout() }}} **
+//
+// This function behaves like the {{{window.clearTimeout()}}} function
+// in content space, and cancels the callback with the given timer ID
+// from ever being called.
+
+Utils.clearTimeout = function clearTimeout(timerID) {
+  if(!(timerID in Utils.__timerData.timers))
+    return;
+
+  var timer = Utils.__timerData.timers[timerID];
+  timer.cancel();
+  delete Utils.__timerData.timers[timerID];
+};
+
+// Support infrastructure for the timeout-related functions.
+
+Utils.__TimerCallback = function __TimerCallback(callback) {
+  Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+  this._callback = callback;
+  this.QueryInterface = XPCOMUtils.generateQI([Ci.nsITimerCallback]);
+};
+
+Utils.__TimerCallback.prototype = {
+  notify : function notify(timer) {
+    for(timerID in Utils.__timerData.timers) {
+      if(Utils.__timerData.timers[timerID] == timer) {
+        delete Utils.__timerData.timers[timerID];
+        break;
+      }
+    }
+    this._callback();
+  }
+};
+
+Utils.__timerData = {
+  nextID: Math.floor(Math.random() * 100) + 1,
+  timers: {}
+};
+
+// ** {{{ Utils.url() }}} **
+//
+// Given a string representing an absolute URL or a {{{nsIURI}}}
+// object, returns an equivalent {{{nsIURI}}} object.  Alternatively,
+// an object with keyword arguments as keys can also be passed in; the
+// following arguments are supported:
+//
+// * {{{uri}}} is a string or {{{nsIURI}}} representing an absolute or
+//   relative URL.
+//
+// * {{{base}}} is a string or {{{nsIURI}}} representing an absolute
+//   URL, which is used as the base URL for the {{{uri}}} keyword
+//   argument.
+
+Utils.url = function url(spec) {
+  var base = null;
+  if (typeof(spec) == "object") {
+    if (spec instanceof Ci.nsIURI)
+      // nsIURL object was passed in, so just return it back
+      return spec;
+
+    // Assume jQuery-style dictionary with keyword args was passed in.
+    base = Utils.url(spec.base);
+    spec = spec.uri ? spec.uri : null;
+  }
+
+  var ios = Cc["@mozilla.org/network/io-service;1"]
+    .getService(Ci.nsIIOService);
+  return ios.newURI(spec, null, base);
+};
 
-  return "blarg";
+// ** {{{ Utils.openUrlInBrowser() }}} **
+//
+// This function opens the given URL in the user's browser, using
+// their current preferences for how new URLs should be opened (e.g.,
+// in a new window vs. a new tab, etc).
+//
+// {{{urlString}}} is a string corresponding to the URL to be
+// opened.
+//
+// {{{postData}}} is an optional argument that allows HTTP POST data
+// to be sent to the newly-opened page.  It may be a string, an Object
+// with keys and values corresponding to their POST analogues, or an
+// {{{nsIInputStream}}}.
+
+Utils.openUrlInBrowser = function openUrlInBrowser(urlString, postData) {
+  var postInputStream = null;
+  if(postData) {
+    if(postData instanceof Ci.nsIInputStream) {
+      postInputStream = postData;
+    } else {
+      if(typeof postData == "object") // json -> string
+        postData = Utils.paramsToString(postData);
+
+      var stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
+        .createInstance(Ci.nsIStringInputStream);
+      stringStream.data = postData;
+
+      postInputStream = Cc["@mozilla.org/network/mime-input-stream;1"]
+        .createInstance(Ci.nsIMIMEInputStream);
+      postInputStream.addHeader("Content-Type",
+                                "application/x-www-form-urlencoded");
+      postInputStream.addContentLength = true;
+      postInputStream.setData(stringStream);
+    }
+  }
+
+  var windowManager = Cc["@mozilla.org/appshell/window-mediator;1"]
+    .getService(Ci.nsIWindowMediator);
+  var browserWindow = windowManager.getMostRecentWindow("navigator:browser");
+  var browser = browserWindow.getBrowser();
+
+  var prefService = Cc["@mozilla.org/preferences-service;1"]
+    .getService(Ci.nsIPrefBranch);
+  var openPref = prefService.getIntPref("browser.link.open_newwindow");
+
+  //2 (default in SeaMonkey and Firefox 1.5): In a new window
+  //3 (default in Firefox 2 and above): In a new tab
+  //1 (or anything else): In the current tab or window
+
+  if(browser.mCurrentBrowser.currentURI.spec == "about:blank" &&
+     !browser.webProgress.isLoadingDocument )
+    browserWindow.loadURI(urlString, null, postInputStream, false);
+  else if(openPref == 3)
+    browser.loadOneTab(urlString, null, null, postInputStream, false, false);
+  else if(openPref == 2)
+    window.openDialog('chrome://browser/content', '_blank', 'all,dialog=no',
+                  urlString, null, null, postInputStream);
+  else
+    browserWindow.loadURI(urlString, null, postInputStream, false);
+};
+
+// ** {{{ Utils.focusUrlInBrowser() }}} **
+//
+// This function focuses a tab with the given URL if one exists in the
+// current window; otherwise, it delegates to
+// {{{Utils.openUrlInBrowser()}}}.
+
+Utils.focusUrlInBrowser = function focusUrlInBrowser(urlString) {
+  let Application = Components.classes["@mozilla.org/fuel/application;1"]
+                    .getService(Components.interfaces.fuelIApplication);
+
+  var tabs = Application.activeWindow.tabs;
+  for (var i = 0; i < tabs.length; i++)
+    if (tabs[i].uri.spec == urlString) {
+      tabs[i].focus();
+      return;
+    }
+  Utils.openUrlInBrowser(urlString);
+};
+
+// ** {{{ Utils.getCookie() }}} **
+//
+// This function returns the cookie for the given domain and with the
+// given name.  If no matching cookie exists, {{{null}}} is returned.
+
+Utils.getCookie = function getCookie(domain, name) {
+  var cookieManager = Cc["@mozilla.org/cookiemanager;1"].
+                      getService(Ci.nsICookieManager);
+
+  var iter = cookieManager.enumerator;
+  while (iter.hasMoreElements()) {
+    var cookie = iter.getNext();
+    if (cookie instanceof Ci.nsICookie)
+      if (cookie.host == domain && cookie.name == name )
+        return cookie.value;
+  }
+  // if no matching cookie:
+  return null;
+};
+
+Utils.paramsToString = function paramsToString(params) {
+  var stringPairs = [];
+  function valueTypeIsOk(val) {
+    if (typeof val == "function")
+      return false;
+    if (val === undefined)
+      return false;
+    if (val === null)
+      return false;
+    return true;
+  }
+  function addPair(key, value) {
+    if (valueTypeIsOk(value)) {
+      stringPairs.push(
+        encodeURIComponent(key) + "=" + encodeURIComponent(value.toString())
+      );
+    }
+  }
+  for (key in params) {
+    // note: explicitly ignoring values that are objects/functions/undefined!
+    if (Utils.isArray(params[key])) {
+      params[key].forEach(function(item) {
+        addPair(key + "[]", item);
+      });
+    } else {
+      addPair(key, params[key]);
+    };
+  }
+  return "?" + stringPairs.join("&");
+};
+
+// Synchronously retrieves the content of the given local URL
+// and returns it.
+Utils.getLocalUrl = function getLocalUrl(url) {
+  var req = new XMLHttpRequest();
+  req.open('GET', url, false);
+  req.overrideMimeType("text/plain");
+  req.send(null);
+  if (req.status == 0)
+    return req.responseText;
+  else
+    throw new Error("Failed to get " + url);
+};
+
+Utils.trim = function trim(str) {
+  return str.replace(/^\s+|\s+$/g,"");
+};
+
+Utils.isArray = function isArray(val) {
+  if (typeof val != "object")
+    return false;
+  if (val == null)
+    return false;
+  if (val.constructor.name != "Array")
+    return false;
+  return true;
 }
 
-// ==== {{{foo(x)}}} ====
-//
-// This function returns the parameter multiplied by 2.
+Utils.History = {
+  visitsToDomain : function visitsToDomain( domain ) {
+
+      var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+               getService(Ci.nsINavHistoryService);
+
+      var query = hs.getNewQuery();
+      var options = hs.getNewQueryOptions();
+
+      options.maxResults = 10;
+      query.domain = domain;
+
+      // execute query
+      var result = hs.executeQuery(query, options );
+      var root = result.root;
+      root.containerOpen = true;
+      var count = 0;
+      for( var i=0; i < root.childCount; ++i ) {
+        place = root.getChild( i );
+        count += place.accessCount;
+      }
+    return count;
+  }
+};
+
+// valid hash algorithms are: MD2, MD5, SHA1, SHA256, SHA384, SHA512
+Utils.computeCryptoHash = function computeCryptoHash(algo, str) {
+  var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+                            .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  var result = {};
+  var data = converter.convertToByteArray(str, result);
+  var crypto = Components.classes["@mozilla.org/security/hash;1"]
+                          .createInstance(Components.interfaces.nsICryptoHash);
+  crypto.initWithString(algo);
+  crypto.update(data, data.length);
+  var hash = crypto.finish(false);
+
+  function toHexString(charCode) {
+    return ("0" + charCode.toString(16)).slice(-2);
+  }
+  var hashString = [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
+  return hashString;
+};
+
+Utils.convertFromUnicode = function convertFromUnicode(toCharset, text) {
+  var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+                            .getService(Components.interfaces.nsIScriptableUnicodeConverter);
+  converter.charset = toCharset;
+  return converter.ConvertFromUnicode(text);
+};
+
+Utils.convertToUnicode = function convertToUnicode(fromCharset, text) {
+  var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+                            .getService(Components.interfaces.nsIScriptableUnicodeConverter);
+  converter.charset = fromCharset;
+  return converter.ConvertToUnicode(text);
+};
+
+Utils.tabs = {
+  /**
+   * Get open tabs.
+   *
+   * @param aName optional string tab name
+   *        If supplied, will return the named tab or null.
+   * @returns A hash of tab name -> tab reference, or if a
+   *          name parameter is passed, returns the matching
+   *          tab reference or null.
+   */
+  get: function Utils_tabs_get(aName) {
+    if (aName)
+      return this._cache[aName] || null;
+
+    return this._cache;
+  },
+
+  /**
+   * Search for tabs by tab name.
+   *
+   * @param aSearchText string
+   * @param aMaxResults integer
+   * @returns A hash of tab name -> tab reference
+   */
+  search: function Utils_tabs_search(aSearchText, aMaxResults) {
+    var matches = {};
+    var matchCount = 0;
+    for (var name in this._cache) {
+      //TODO: implement a better match algorithm
+      if (name.match(aSearchText, "i")) {
+        matches[name] = this._cache[name];
+        matchCount++;
+      }
+      if (aMaxResults && aMaxResults == matchCount)
+        break;
+    }
+    return matches;
+  },
 
-function foo(x) {
-  return x*2;
-}
+  /**
+   * Handles TabOpen, TabClose and load events
+   * clears tab cache
+   */
+  onTabEvent: function(aEvent, aTab) {
+    switch ( aEvent.type ) {
+      case "TabOpen":
+        // this is received before the page content has loaded.
+        // so need to find the new tab, and add a load
+        // listener to it, and only then add it to the cache.
+        // TODO: once bug 470163 is fixed, can move to a much
+        // cleaner way of doing this.
+        var self = this;
+        var windowCount = this.Application.windows.length;
+        for( var i=0; i < windowCount; i++ ) {
+          var window = this.Application.windows[i];
+          var tabCount = window.tabs.length;
+          for (var j = 0; j < tabCount; j++) {
+            let tab = window.tabs[j];
+            if (!this.__cache[tab.document.title]) {
+              // add a load listener to the tab
+              // and add the tab to the cache after it has loaded.
+              tab.events.addListener("load", function(aEvent) {
+                self.onTabEvent(aEvent, tab);
+              });
+            }
+          }
+        }
+        break;
+      case "TabClose":
+        // for TabClose events, invalidate the cache.
+        // TODO: once bug 470163 is fixed, can just delete the tab from
+        // from the cache, instead of invalidating the entire thing.
+        this.__cache = null;
+        break;
+      case "load":
+        // handle new tab page loads, and reloads of existing tabs
+        if (aTab && aTab.document.title) {
+
+          // if a tab with this title is not cached, add it
+          if (!this._cache[aTab.document.title])
+            this._cache[aTab.document.title] = aTab;
+
+          // evict previous cache entries for the tab
+          for (var title in this._cache) {
+            if (this._cache[title] == aTab && title != aTab.document.title) {
+              // if the cache contains an entry for this tab, and the title
+              // differs from the tab's current title, then evict the entry.
+              delete this._cache[title];
+              break;
+            }
+          }
+        }
+        break;
+    }
+  },
+
+  /**
+   * smart-getter for fuel
+   */
+  get Application() {
+    delete this.Application;
+    return this.Application = Components.classes["@mozilla.org/fuel/application;1"].
+                              getService(Components.interfaces.fuelIApplication);
+  },
+
+  /**
+   * getter for the tab cache
+   * manages reloading cache
+   */
+  __cache: null,
+  get _cache() {
+    if (this.__cache)
+      return this.__cache;
+
+    this.__cache = {};
+    var windowCount = this.Application.windows.length;
+    for( var j=0; j < windowCount; j++ ) {
+
+      var win = this.Application.windows[j];
+      win.events.addListener("TabOpen", function(aEvent) { self.onTabEvent(aEvent); });
+      win.events.addListener("TabClose", function(aEvent) { self.onTabEvent(aEvent); });
+
+      var tabCount = win.tabs.length;
+      for (var i = 0; i < tabCount; i++) {
+
+        let tab = win.tabs[i];
+
+        // add load listener to tab
+        var self = this;
+        tab.events.addListener("load", function(aEvent) {
+          self.onTabEvent(aEvent, tab);
+        });
+
+        // add tab to cache
+        this.__cache[tab.document.title] = tab;
+      }
+    }
+
+    return this.__cache;
+  }
+};
--- a/thingy.js	Mon Jan 05 21:31:56 2009 -0800
+++ b/thingy.js	Tue Jan 06 01:45:13 2009 -0800
@@ -35,7 +35,7 @@
           blockText += text + "\n";
           lastCommentLine += 1;
           isCode = false;
-        } else if (text[0] == "=") {
+        } else if (text[0] == "=" || text[0] == "*") {
           maybeAppendBlock();
           firstCommentLine = lineNum;
           lastCommentLine = lineNum;