# HG changeset patch # User Atul Varma # Date 1231235113 28800 # Node ID d0e32e673d94892e31fd5ef752467bff81c5a48d # Parent 5418d21d7751d9c265ee8b8ff86ffbf80da282d2 Swapped samplecode.js w/ utils.js from Ubiquity. diff -r 5418d21d7751 -r d0e32e673d94 samplecode.js --- 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 + * Blair McBride + * Jono DiCarlo * * 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; + } +}; diff -r 5418d21d7751 -r d0e32e673d94 thingy.js --- 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;