summaryrefslogtreecommitdiffstats
path: root/ceee/firefox/content
diff options
context:
space:
mode:
Diffstat (limited to 'ceee/firefox/content')
-rw-r--r--ceee/firefox/content/about.xul29
-rw-r--r--ceee/firefox/content/bookmarks_api.js605
-rw-r--r--ceee/firefox/content/ceee.pngbin0 -> 1062 bytes
-rw-r--r--ceee/firefox/content/cf.js370
-rw-r--r--ceee/firefox/content/cookie_api.js484
-rw-r--r--ceee/firefox/content/externs.js91
-rw-r--r--ceee/firefox/content/infobars_api.js218
-rw-r--r--ceee/firefox/content/js-coverage.js630
-rw-r--r--ceee/firefox/content/options.xul95
-rw-r--r--ceee/firefox/content/overlay.js964
-rw-r--r--ceee/firefox/content/overlay.xul84
-rw-r--r--ceee/firefox/content/sidebar_api.js691
-rw-r--r--ceee/firefox/content/tab_api.js480
-rw-r--r--ceee/firefox/content/us/base.js1291
-rw-r--r--ceee/firefox/content/us/ceee_bootstrap.js254
-rw-r--r--ceee/firefox/content/us/json.js318
-rw-r--r--ceee/firefox/content/userscript_api.js1077
-rw-r--r--ceee/firefox/content/window_api.js355
18 files changed, 8036 insertions, 0 deletions
diff --git a/ceee/firefox/content/about.xul b/ceee/firefox/content/about.xul
new file mode 100644
index 0000000..4d1792f6
--- /dev/null
+++ b/ceee/firefox/content/about.xul
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<!-- Copyright (c) 2010 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+
+ Defines the About dialog box.
+ -->
+
+<!DOCTYPE dialog SYSTEM "chrome://ceee/locale/about.dtd">
+
+<dialog title="&about; Google Chrome Extensions Execution Environment" orient="vertical" autostretch="always"
+ onload="sizeToContent()" buttons="accept"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <groupbox align="center" orient="horizontal">
+ <vbox>
+ <text value="Google Chrome Extensions Execution Environment"
+ style="font-weight: bold; font-size: x-large;"/>
+ <text value="&version; 0.2"/>
+ <separator class="thin"/>
+ <text value="&createdBy;" style="font-weight: bold;"/>
+ <text value="Google Inc."/>
+ <separator class="thin"/>
+ <text value="&homepage;" style="font-weight: bold;"/>
+ <text value="http://www.google.com" class="url"
+ onclick="window.open('http://www.google.com'); window.close();"/>
+ </vbox>
+ </groupbox>
+</dialog>
diff --git a/ceee/firefox/content/bookmarks_api.js b/ceee/firefox/content/bookmarks_api.js
new file mode 100644
index 0000000..a95643e
--- /dev/null
+++ b/ceee/firefox/content/bookmarks_api.js
@@ -0,0 +1,605 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains the implementation of the Chrome extension
+ * bookmarks APIs for the CEEE Firefox add-on. This file is loaded by
+ * the overlay.xul file, and requires that overlay.js has already been loaded.
+ *
+ * @supported Firefox 3.x
+ */
+
+/**
+ * Place-holder namespace-object for helper functions used in this file.
+ * @private
+ */
+var CEEE_bookmarks_internal_ = {
+ /** Reference to the instance object for the ceee. @private */
+ ceeeInstance_: null,
+
+ /**
+ * Log an informational message to the Firefox error console.
+ * @private
+ */
+ logInfo_: null,
+
+ /**
+ * Log an error message to the Firefox error console.
+ * @private
+ */
+ logError_: null
+};
+
+/**
+ * Iterates through all of the children of a given bookmark folder, invoking
+ * the callback for each child.
+ *
+ * @param {number} bookmarkId The id of the bookmark to find.
+ * @param {Function} callback Function to invoke for each child bookmark. If
+ * the function returns false, then the iteration breaks early.
+ * @private
+ */
+CEEE_bookmarks_internal_.forEachChild_ = function(bookmarkId, callback) {
+ try {
+ var index = 0;
+ // The Mozilla nsINavBookmarksService does not provide a means to directly
+ // access the number of bookmarks within a folder. Instead we iterate
+ // from 0 until an exception is thrown.
+ // TODO(twiz@chromium.org): Investigate if the bookmarks within a
+ // folder may be sparse, and so this loop will break too early.
+ do {
+ var childId = CEEE_bookmarks.getIdForItemAt(bookmarkId, index++);
+ // Using the !operator would coerce null values into false, which is
+ // not the break condition. We want to stop ONLY if false is returned.
+ if (false === callback(childId)) {
+ return;
+ }
+ } while(true);
+ } catch (e) {
+ // Catch the out-of-bounds exception thrown when accessing the element past
+ // the end of the set of children, otherwise pass the exception on.
+ if (e && e.result &&
+ e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) {
+ return;
+ } else {
+ throw e;
+ }
+ }
+};
+
+/** @typedef {{id: number,
+ * title: string,
+ * parentId: number,
+ * url: string,
+ * children: Array.<CEEE_bookmarks_internal_.BookmarkItem>}}
+ */
+CEEE_bookmarks_internal_.BookmarkItem;
+
+/**
+ * Packages an object containing the members specified by the Chrome
+ * bookmark extension BookmarkItem object.
+ *
+ * @param {number} bookmarkId The id of the bookmark to find.
+ * @param {string} title The title of the bookmark.
+ * @param {number} parentId The id of the parent of the bookmark.
+ * @param {Object} url A url object, as constructed by nsIIOService. If the
+ * bookmark is a folder, then this parameter should be null.
+ * @param {Array} children The array of children of the bookmark. Should be
+ * null if a url is provided.
+ * @return { {id: number, title: string, parentId: number, url: string,
+ * children: Array} } A BookmarkItem.
+ * @private
+ */
+CEEE_bookmarks_internal_.createChromeBookmark_ = function(bookmarkId,
+ title, parentId, url, children) {
+ return {
+ 'id': bookmarkId,
+ 'title': title,
+ 'parentId': parentId,
+ 'url': url ? url.spec : null,
+ 'children': children || []};
+};
+
+/**
+ * Constructs and returns a BookmarkItem structure as defined by the
+ * Chrome extension bookmark model corresponding to the Mozilla places
+ * bookmark with the given id.
+ *
+ * @param {number} bookmarkId The id of the bookmark to find.
+ * @return {CEEE_bookmarks_internal_.BookmarkItem}
+ * If a bookmark with the given id is found, returns an object with
+ * the following properties defined by the Chrome bookmark API.
+ * @private
+ */
+CEEE_bookmarks_internal_.getBookmarkItemFromId_ = function(bookmarkId) {
+ var title = CEEE_bookmarks.getItemTitle(bookmarkId);
+ var parentId = CEEE_bookmarks.getFolderIdForItem(bookmarkId);
+ var url;
+
+ try {
+ url = CEEE_bookmarks.getBookmarkURI(bookmarkId);
+ } catch (e) {
+ // If a bookmark does not exist for this id, getBookmarkURI will throw
+ // NS_ERROR_INVALID_ARG. We catch that here and return null instead.
+ if (!(e && e.result &&
+ e.result == Components.results.NS_ERROR_INVALID_ARG)) {
+ throw e;
+ }
+ }
+
+ var item = this.createChromeBookmark_(bookmarkId, title, parentId, url);
+
+ this.forEachChild_(bookmarkId, function(childId) {
+ item.children.push(childId);
+ });
+ return item;
+};
+
+/**
+ * Returns an array of BookmarkItem objects, as defined by the Chrome bookmark
+ * extension API for the children of the given folder.
+ *
+ * @param {number} folderId The id of the folder whose children are to
+ * be returned.
+ * @return {Array} An array of BookmarItem objects representing the children
+ * of the folder with id passed as folderId.
+ * @private
+ */
+CEEE_bookmarks_internal_.getChildren_ = function(folderId) {
+ var itemType = CEEE_bookmarks.getItemType(folderId);
+ var children = [];
+
+ // Bookmarks and separators do not have children, only folders have children.
+ if (CEEE_bookmarks.TYPE_BOOKMARK == itemType ||
+ CEEE_bookmarks.TYPE_SEPARATOR == itemType) {
+ return children;
+ }
+
+ var impl = this;
+ this.forEachChild_(folderId, function(childId) {
+ var treeNode = impl.createChromeBookmark_(
+ childId, CEEE_bookmarks.getItemTitle(childId), folderId);
+
+ var itemType = CEEE_bookmarks.getItemType(childId);
+ if (CEEE_bookmarks.TYPE_BOOKMARK == itemType) {
+ var url = CEEE_bookmarks.getBookmarkURI(childId);
+ if (url) {
+ treeNode.url = url.spec;
+ }
+ } else if (CEEE_bookmarks.TYPE_SEPARATOR != itemType) {
+ treeNode.children = impl.getChildren_(childId);
+ }
+ children.push(treeNode);
+ });
+
+ return children;
+};
+
+/**
+ * Helper function for searching for a set of search terms in a given text.
+ *
+ * @param {!Array.<string>} querySet An array of strings, for which we are
+ * searching.
+ * @param {string} text A string in which we are looking for all of the
+ * elements of querySet.
+ * @return {boolean} Whether all elements in querySet are present in text.
+ * @private
+ */
+CEEE_bookmarks_internal_.textInQuerySet_ = function(querySet, text) {
+ if (!text) {
+ return false;
+ }
+
+ for (var wordIndex = 0; wordIndex < querySet.length; ++wordIndex) {
+ if (-1 == text.indexOf(querySet[wordIndex])) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+/**
+ * Searches for the set of bookmarks containing all elements of the strings
+ * in querySet. The search starts at item bookmarkId, and recursively visits
+ * all children items.
+ * When a bookmark item is found, onMatch is called to register the match.
+ * If onMatch returns false, the search terminates.
+ *
+ * @param {!Array.<string>} querySet Array of strings to find in the title/url
+ * of the bookmarks contained in the tree rooted at bookmarkId.
+ * @param {number} bookmarkId The id of the folder/item to search.
+ * @param {function(number):boolean} onMatch Function to call when a match is
+ * found.
+ * @return {boolean} true if the search was exhaustive, and all bookmark
+ * children were examined.
+ * @private
+ */
+CEEE_bookmarks_internal_.getBookmarksContainingText_ =
+ function(querySet, bookmarkId, onMatch) {
+ var itemType = CEEE_bookmarks.getItemType(bookmarkId);
+ if (CEEE_bookmarks.TYPE_FOLDER == itemType ||
+ CEEE_bookmarks.TYPE_DYNAMIC_CONTAINER == itemType) {
+ var impl = this;
+ // Recurse on each of the children of bookmark with id bookmarkId.
+ this.forEachChild_(bookmarkId, function(childId) {
+ if (!impl.getBookmarksContainingText_(querySet, childId, onMatch)) {
+ return false;
+ }
+ });
+ } else {
+ var title = CEEE_bookmarks.getItemTitle(bookmarkId);
+ var url = CEEE_bookmarks.getBookmarkURI(bookmarkId);
+ if (url) {
+ url = url.spec;
+ }
+
+ if (this.textInQuerySet_(querySet, title) ||
+ this.textInQuerySet_(querySet, url)) {
+ return onMatch(bookmarkId);
+ }
+ }
+
+ return true;
+};
+
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.getChildren.
+ */
+CEEE_bookmarks_internal_.CMD_GET_BOOKMARK_CHILDREN =
+ 'bookmarks.getChildren';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.getTree.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.getBookmarkChildren_ = function(cmd, data) {
+ if (!data || !data.args) {
+ return;
+ }
+
+ var bookmarkId = data.args;
+ var children = [];
+ var impl = this;
+ this.forEachChild_(bookmarkId, function(childId) {
+ children.push(impl.getBookmarkItemFromId_(childId));
+ });
+ return children;
+};
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.getTree.
+ */
+CEEE_bookmarks_internal_.CMD_GET_BOOKMARK_TREE = 'bookmarks.getTree';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.getTree.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.getBookmarkTree_ = function(cmd, data) {
+ var treeRoot = this.createChromeBookmark_(
+ CEEE_bookmarks.placesRoot,
+ CEEE_bookmarks.getItemTitle(CEEE_bookmarks.placesRoot));
+ // TODO(twiz@chromium.org): This routine presently returns the root
+ // of places, which includes elements outside of the normal
+ // 'bookmarks scope', such as the recent browsing histroy, etc. It
+ // should be determined if bookmarksMenuFolder or
+ // unfiledBookmarksFolder is more appropriate.
+ treeRoot.children = this.getChildren_(CEEE_bookmarks.placesRoot);
+ return treeRoot;
+};
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.search.
+ */
+CEEE_bookmarks_internal_.CMD_SEARCH_BOOKMARKS = 'bookmarks.search';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.search.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.searchBookmarks_ = function(cmd, data) {
+ if (!data || !data.args) {
+ return;
+ }
+
+ var results = [];
+ var query = data.args;
+ // Because FireFox's JSON parsing engine does not support JSON encoding of
+ // primitive types (such as strings), we explicitly check for a 'null'
+ // string here to indicate that no arguments were given, and return
+ // an empty set.
+ // NOTE: The check against 3 is because encoded strings will be bracketed
+ // with ". ie "a".
+ if (!query || query == 'null' || query.length < 3) {
+ return results;
+ }
+
+ var impl = this;
+ // The query, as performed by the chrome implementation is case-insensitive.
+ query = query.toLowerCase();
+ query = query.substring(1, query.length - 1);
+ var querySet = query.split(' ');
+ this.getBookmarksContainingText_(
+ querySet,
+ CEEE_bookmarks.placesRoot,
+ function(bookmarkId) {
+
+ results.push(impl.getBookmarkItemFromId_(bookmarkId));
+
+ // Restrict the set of returned results to no greater than 50.
+ // This mimics the behaviour of the API as implemented in Chrome.
+ return results.length <= 50;
+ });
+
+ return results;
+};
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.remove.
+ */
+CEEE_bookmarks_internal_.CMD_REMOVE_BOOKMARK = 'bookmarks.remove';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.remove.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.removeBookmark_ = function(cmd, data) {
+ if (!data || !data.args) {
+ return;
+ }
+
+ var args = CEEE_json.decode(data.args);
+ if (!args || !args.id) {
+ return;
+ }
+
+ var recurse = cmd == this.CMD_REMOVE_BOOKMARK_TREE;
+ var itemType = CEEE_bookmarks.getItemType(args.id);
+ if (CEEE_bookmarks.TYPE_FOLDER == itemType ||
+ CEEE_bookmarks.TYPE_DYNAMIC_CONTAINER == itemType) {
+ // Disallow removal of a non-empty folder unless the recursive argument
+ // is set.
+ var children = this.getChildren_(args.id);
+ if (!(children && children.length > 0 && !recurse)) {
+ CEEE_bookmarks.removeFolder(args.id);
+ }
+ } else {
+ CEEE_bookmarks.removeItem(args.id);
+ }
+}
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.removeTree.
+ */
+CEEE_bookmarks_internal_.CMD_REMOVE_BOOKMARK_TREE = 'bookmarks.removeTree';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.removeTree.
+ * Note: This routine aliases to removeBookmark_, which specializes its
+ * behaviour based on the contents of the command string argument.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.removeBookmarkTree_ =
+ CEEE_bookmarks_internal_.removeBookmark_;
+
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.create.
+ */
+CEEE_bookmarks_internal_.CMD_CREATE_BOOKMARK = 'bookmarks.create';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.create.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.createBookmark_ = function(cmd, data) {
+ if (!data || !data.args) {
+ return;
+ }
+
+ var args = CEEE_json.decode(data.args);
+ if (!args) {
+ return;
+ }
+
+ // If no parent id is provided, default to the unfiledBookmarksFolder.
+ // Explicitly test for null, as 0 may be a valid bookmark id.
+ var parentId = args.parentId;
+ if (parentId == null) {
+ parentId = CEEE_bookmarks.unfiledBookmarksFolder;
+ }
+
+ // If no index is provided, then insert at the end of the child-list.
+ var index = args.index;
+ if (index == null) {
+ index = CEEE_bookmarks.DEFAULT_INDEX;
+ }
+
+ var title = args.title;
+ if (!title) {
+ return;
+ }
+
+ var url = args.url;
+ var newBookmarkId;
+ try {
+ // If no url is provided, then create a folder.
+ if (!url) {
+ newBookmarkId = CEEE_bookmarks.createFolder(parentId,
+ title,
+ index);
+ } else {
+ var uri = CEEE_ioService.newURI(url, null, null);
+ newBookmarkId = CEEE_bookmarks.insertBookmark(parentId,
+ uri,
+ index,
+ title);
+ }
+
+ return this.getBookmarkItemFromId_(newBookmarkId);
+ } catch (e) {
+ // If the provided url is not formatted correctly, then newURI will throw.
+ // Return null to indicate failure.
+ return;
+ }
+};
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.move.
+ */
+CEEE_bookmarks_internal_.CMD_MOVE_BOOKMARK = 'bookmarks.move';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.move.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.moveBookmark_ = function(cmd, data) {
+ if (!data || !data.args) {
+ return;
+ }
+
+ var args = CEEE_json.decode(data.args);
+ var id = args.id;
+ var parentId = args.parentId;
+
+ if (parentId == null) {
+ parentId = CEEE_bookmarks.getFolderIdForItem(id);
+ }
+
+ if (parentId == null) {
+ return;
+ }
+
+ var index = args.index;
+ if (!index) {
+ index = CEEE_bookmarks.DEFAULT_INDEX;
+ }
+
+ CEEE_bookmarks.moveItem(id, parentId, index);
+}
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.update.
+ */
+CEEE_bookmarks_internal_.CMD_SET_BOOKMARK_TITLE = 'bookmarks.update';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.update.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.setBookmarkTitle_ = function(cmd, data) {
+ if (!data || !data.args) {
+ return;
+ }
+
+ var args = CEEE_json.decode(data.args);
+ if (!args.id) {
+ return;
+ }
+
+ CEEE_bookmarks.setItemTitle(args.id, args.title);
+ return this.getBookmarkItemFromId_(args.id);
+}
+
+/**
+ * Command name for Chrome extension bookmarks API function,
+ * chrome.bookmarks.get.
+ */
+CEEE_bookmarks_internal_.CMD_GET_BOOKMARKS = 'bookmarks.get';
+
+/**
+ * Implementation of Chrome extension bookmarks API function,
+ * chrome.bookmarks.get.
+ * See
+ * http://dev.chromium.org/developers/design-documents/extensions/bookmarks-api
+ */
+CEEE_bookmarks_internal_.getBookmarks_ = function(cmd, data) {
+ if (!data || !data.args) {
+ return;
+ }
+
+ var args = CEEE_json.decode(data.args);
+ var bookmarks = [];
+ if (!args) {
+ bookmarks.push(this.getBookmarkItemFromId_(
+ CEEE_bookmarks.placesRoot));
+ } else {
+ for (var bookmarkId = 0; bookmarkId < args.length; ++bookmarkId) {
+ bookmarks.push(this.getBookmarkItemFromId_(
+ args[bookmarkId]));
+ }
+ }
+
+ return bookmarks;
+}
+
+/**
+ * Initialization routine for the CEEE Bookmark API module.
+ * @param {!Object} ceeeInstance Reference to the global ceee instance.
+ * @return {Object} Reference to the bookmark module.
+ * @public
+ */
+function CEEE_initialize_bookmarks(ceeeInstance) {
+ CEEE_bookmarks_internal_.ceeeInstance_ = ceeeInstance;
+ var bookmarks = CEEE_bookmarks_internal_;
+
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_GET_BOOKMARK_CHILDREN,
+ bookmarks,
+ bookmarks.getBookmarkChildren_);
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_GET_BOOKMARK_TREE,
+ bookmarks,
+ bookmarks.getBookmarkTree_);
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_SEARCH_BOOKMARKS,
+ bookmarks,
+ bookmarks.searchBookmarks_);
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_REMOVE_BOOKMARK,
+ bookmarks,
+ bookmarks.removeBookmark_);
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_REMOVE_BOOKMARK_TREE,
+ bookmarks,
+ bookmarks.removeBookmarkTree_);
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_CREATE_BOOKMARK,
+ bookmarks,
+ bookmarks.createBookmark_);
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_MOVE_BOOKMARK,
+ bookmarks,
+ bookmarks.moveBookmark_);
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_SET_BOOKMARK_TITLE,
+ bookmarks,
+ bookmarks.setBookmarkTitle_);
+ ceeeInstance.registerExtensionHandler(bookmarks.CMD_GET_BOOKMARKS,
+ bookmarks,
+ bookmarks.getBookmarks_);
+
+ bookmarks.logInfo_ = ceeeInstance.logInfo;
+ bookmarks.logError_ = ceeeInstance.logError;
+
+ return bookmarks;
+};
diff --git a/ceee/firefox/content/ceee.png b/ceee/firefox/content/ceee.png
new file mode 100644
index 0000000..f375ec4
--- /dev/null
+++ b/ceee/firefox/content/ceee.png
Binary files differ
diff --git a/ceee/firefox/content/cf.js b/ceee/firefox/content/cf.js
new file mode 100644
index 0000000..48c0328
--- /dev/null
+++ b/ceee/firefox/content/cf.js
@@ -0,0 +1,370 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains a helper object to wrap an instance of
+ * ChromeFrame for a given top-level window in Firefox. The main functions of
+ * this object are to initialize ChromeFrame and properly handle queueing of
+ * messages sent to.
+ *
+ * @supported Firefox 3.x
+ */
+
+var CEEE_CfHelper;
+
+(function() {
+
+/**
+ * Constructor for ChromeFrame helper object.
+ * @param {!Object} instance A reference the the main ceee instance for
+ * this top-level window.
+ * @constructor
+ */
+function CfHelper(instance) {
+ // The caller must specify the ceee instance object.
+ if (!instance)
+ throw 'Must specify instance';
+
+ /** A reference to the main ceee instance for this top-level window. */
+ this.ceee_ = instance;
+
+ /** Reference to the ChromeFrame <embed> element in the DOM. */
+ this.cf_ = null;
+
+ /** True when ChromeFrame is ready for use. */
+ this.isReady_ = false;
+
+ /** True if we found no enabled extensions and tried installing one. */
+ this.alreadyTriedInstalling_ = false;
+
+ /**
+ * Queue of pending messages to be sent to ChromeFrame once it becomes ready.
+ */
+ this.queue_ = [];
+
+ /**
+ * A reference to the parent element (the toolbar) of the cf_ node.
+ */
+ this.parent_ = null;
+}
+
+/**
+ * Value used for id attribute of the ChromeFrame <embed> element.
+ * @const
+ */
+CfHelper.prototype.CHROME_FRAME_ID = 'ceee-browser';
+
+/** Origin for use with postPrivateMessage. @const */
+CfHelper.prototype.ORIGIN_EXTENSION = '__priv_xtapi';
+
+/** Target for event notifications. @const */
+CfHelper.prototype.TARGET_EVENT_REQUEST = '__priv_evtreq';
+
+/** Target for responses to Chrome Extension API requests. @const */
+CfHelper.prototype.TARGET_API_RESPONSE = '__priv_xtres';
+
+/** Target for message port requests. @const */
+CfHelper.prototype.TARGET_PORT_REQUEST = '__priv_prtreq';
+
+/** Target for message port requests. @const */
+CfHelper.prototype.TARGET_PORT_RESPONSE = '__priv_prtres';
+
+/**
+ * Preference that holds the path of the CRX file that was last installed
+ * by the CEEE. @const
+ */
+CfHelper.prototype.LAST_INSTALL_PATH = 'last_install_path';
+
+/**
+ * Preference that holds the modified time of the CRX file that was last
+ * installed by the CEEE. @const
+ */
+CfHelper.prototype.LAST_INSTALL_TIME = 'last_install_time';
+
+/** @return {Object} The ChromeFrame <embed> element. */
+CfHelper.prototype.get = function() {
+ return this.cf_;
+};
+
+/** @return {boolean} True if ChromeFrame is ready for use. */
+CfHelper.prototype.isReady = function() {
+ return this.isReady_;
+};
+
+/** Post all pending messages we have queued up to ChromeFrame.
+ * @private
+ */
+CfHelper.prototype.postPendingMessages_ = function() {
+ this.ceee_.logInfo('CfHelper.postPendingMessages');
+
+ while (this.queue_.length > 0) {
+ var pair = this.queue_.shift();
+ this.postMessage(pair[0], pair[1]);
+ }
+};
+
+/**
+ * Posts a message to ChromeFrame.
+ *
+ * @param {!string} message The message to be sent.
+ * @param {!string} target The target of the message.
+ */
+CfHelper.prototype.postMessage = function(message, target) {
+ if (typeof message != 'string' || typeof target != 'string') {
+ this.ceee_.logError(
+ 'CfHelper.postMessage: message or target is not a string');
+ return;
+ }
+
+ if (!this.isReady_) {
+ this.ceee_.logInfo('CfHelper.postMessage defering msg=' + message);
+ // Queue the message and target for later.
+ this.queue_.push([message, target]);
+ return;
+ }
+
+ this.ceee_.logInfo('CfHelper.postMessage msg=' + message);
+
+ // We use private messages so that we can specify the origin.
+ //
+ // Messages must flow through the master CF instance.
+ CEEE_globals.masterCf.get().postPrivateMessage(message,
+ this.ORIGIN_EXTENSION,
+ target);
+};
+
+/**
+ * Create an <embed> element to hold the ChromeFrame and return it.
+ *
+ * @param {!HTMLElement} parent The DOM element that will hold the 'embed'
+ * element.
+ * @param {string} idValue Value of id attribute for created element.
+ * @param {!function()} onCfReady A callback for ChromeFrame becomes ready.
+ * @param {!function(Object, Object)} onCfMessage A callback to handle messages
+ * posted from the ChromeFrame guest page to the ChromeFrame host page.
+ * @return {HTMLElement} The <embed> element holding the ChromeFrame.
+ */
+CfHelper.prototype.create = function(parent, idValue, onCfReady, onCfMessage) {
+ // Create the ChromeFrame instance using the correct extension path and
+ // add it to the XUL DOM.
+ this.parent_ = parent;
+ var doc = parent.ownerDocument;
+ this.cf_ = doc.createElementNS('http://www.w3.org/1999/xhtml', 'embed');
+ if (!this.cf_) {
+ this.ceee_.logError('Unable to create ChromeFrame');
+ return null;
+ }
+
+ this.cf_.id = idValue;
+ this.cf_.setAttribute('type', 'application/chromeframe');
+ // TODO(rogerta@chromium.org): the height here is arbitrary. We
+ // will need to size appropriately when we have more info.
+ this.cf_.setAttribute('width', '1440');
+ this.cf_.setAttribute('height', '25');
+ this.cf_.setAttribute('flex', '1');
+ this.cf_.setAttribute('privileged_mode', '1');
+ this.cf_.setAttribute('chrome_extra_arguments',
+ '--enable-experimental-extension-apis');
+ // TODO(joi@chromium.org) Remove this once the "*" default is removed from CF.
+ this.cf_.setAttribute('chrome_functions_automated', '');
+
+ // Add ChromeFrame to the DOM. This *must* be done before setting the
+ // event listeners or things will just silently fail.
+ parent.insertBefore(this.cf_, parent.firstChild);
+
+ // Setup all event handlers.
+ this.cf_.onprivatemessage = onCfMessage;
+
+ // OnMessage handles messages via window.externalHost. We currently only
+ // need this to match the IE workaround for windows.getCurrent.
+ var originCF = this.cf_;
+ this.cf_.onmessage = function(event) {
+ if (!event || !event.origin || !event.data) {
+ return;
+ }
+ if (event.data == 'ceee_getCurrentWindowId') {
+ var windowId = CEEE_mozilla_windows.getWindowId(window);
+ originCF.postMessage('ceee_getCurrentWindowId ' + windowId,
+ event.origin);
+ }
+ };
+
+ var impl = this;
+ this.cf_.addEventListener('readystatechanged',
+ function() {impl.onReadyStateChange_(onCfReady);}, false);
+
+ return this.cf_;
+};
+
+// HTTP request states
+/**
+ * HTTP request state. open() has not been called yet.
+ * @const
+ */
+var READY_STATE_UNINITIALIZED = 0;
+
+/**
+ * HTTP request state. send() has not been called yet.
+ * @const
+ */
+var READY_STATE_LOADING = 1;
+
+/**
+ * HTTP request state. headers and status are available.
+ * @const
+ */
+var READY_STATE_LOADED = 2;
+
+/**
+ * HTTP request state. responseText holds partial data.
+ * @const
+ */
+var READY_STATE_INTERACTIVE = 3;
+
+/**
+ * HTTP request state. The operation is complete.
+ * @const
+ */
+var READY_STATE_COMPLETED = 4;
+
+/**
+ * Handle 'embed' element ready state events.
+ * @param {!function()} onCfReady A callback for ChromeFrame becomes ready.
+ * @private
+ */
+CfHelper.prototype.onReadyStateChange_ = function(onCfReady) {
+ this.ceee_.logInfo('CfHelper.readystatechange: state=' +
+ this.cf_.readystate);
+ if (this.cf_.readystate == READY_STATE_COMPLETED) {
+ // Do this before we even load the extension at the other end so
+ // that extension automation is set up before any background pages
+ // in the extension load.
+ CEEE_globals.masterCf.onChromeFrameReady(this.cf_,
+ this.ceee_.logInfo);
+
+ // TODO(ibazarny@google.com): Not every chrome frame needs to load
+ // extensions. However, I don't know how to make it
+ // work. Should be something like:
+ //if (!loadExtensions) {
+ // this.isReady_ = true;
+ // this.postPendingMessages_();
+ // onCfReady();
+ // return;
+ //}
+ var lastInstallPath =
+ CEEE_globals.getCharPreference(this.LAST_INSTALL_PATH);
+ var lastInstallTime =
+ CEEE_globals.getCharPreference(this.LAST_INSTALL_TIME);
+
+ // If the extension to load is a CRX file (and not a directory) and
+ // its a new path or its an old path with a later modification time,
+ // install it now.
+ var extensionFile = this.ceee_.getCrxToInstall();
+ var extensionPath = extensionFile && extensionFile.path;
+ var impl = this;
+ if (this.ceee_.isCrx(extensionPath) &&
+ (lastInstallPath != extensionPath ||
+ lastInstallTime < extensionFile.lastModifiedTime)) {
+ this.ceee_.logInfo('Installing extension ' + extensionPath);
+ // Install and save the path and file time.
+ this.cf_.installExtension(extensionPath, function(res) {
+ impl.cf_.getEnabledExtensions(function(extensionDirs) {
+ impl.onGetEnabledExtensionsComplete_(extensionDirs, onCfReady);
+ });
+ });
+ } else {
+ // We are not installing a CRX file. Before deciding what we should do,
+ // we need to find out what extension may already be installed.
+ this.cf_.getEnabledExtensions(function(extensionDirs) {
+ impl.onGetEnabledExtensionsComplete_(extensionDirs, onCfReady);
+ });
+ }
+ }
+};
+
+/**
+ * Process response from 'getEnabledExtensions'.
+ * @param {string} extensionDirs tab-separate list of extension dirs. Only first
+ * entry is handled.
+ * @param {function()} onCfReady A callback for ChromeFrame becomes ready.
+ * @private
+ */
+CfHelper.prototype.onGetEnabledExtensionsComplete_ = function (extensionDirs,
+ onCfReady) {
+ this.ceee_.logInfo('OnGetEnabledExtensions: ' + extensionDirs);
+
+ var extensions = extensionDirs.split('\t');
+ var extensionFile = this.ceee_.getCrxToInstall();
+ var impl = this;
+ if (extensions.length > 0 && extensions[0] != '') {
+ this.ceee_.logInfo('Got enabled extension: ' + extensions[0]);
+ if (extensionFile) {
+ CEEE_globals.setCharPreference(
+ this.LAST_INSTALL_PATH, extensionFile.path);
+ CEEE_globals.setCharPreference(
+ this.LAST_INSTALL_TIME,
+ extensionFile.lastModifiedTime.toString());
+ }
+ this.onLoadExtension_(extensions[0], onCfReady);
+ } else if (extensionFile && !this.ceee_.isCrx(extensionFile.path)) {
+ this.ceee_.logInfo('Loading exploded path:' + extensionFile.path);
+ this.cf_.loadExtension(extensionFile.path, function() {
+ impl.onLoadExtension_(extensionFile.path, onCfReady);
+ });
+ } else if (!this.alreadyTriedInstalling_ && extensionFile) {
+ this.ceee_.logInfo('Attempting to do first-time .crx install.');
+ this.alreadyTriedInstalling_ = true;
+ this.cf_.installExtension(extensionFile.path, function(res) {
+ impl.cf_.getEnabledExtensions(function(extensionDirs) {
+ impl.onGetEnabledExtensionsComplete_(extensionDirs, onCfReady);
+ });
+ });
+ } else {
+ // Remove the toolbar entirely. The direct parent is the toolbaritem
+ // (check overlay.xul for more information), so we to remove our parentNode.
+ var toolbar = this.parent_.parentNode.parentNode;
+ toolbar.parentNode.removeChild(toolbar);
+ this.ceee_.logInfo('No extension installed.');
+ }
+};
+
+/**
+ * Process response from 'loadExtension' call.
+ * @param {string} baseDir Extension home dir.
+ * @param {!function()} onCfReady A callback for ChromeFrame becomes ready.
+ * @private
+ */
+CfHelper.prototype.onLoadExtension_ = function (baseDir, onCfReady) {
+ var extensions = [baseDir];
+ this.ceee_.logInfo('OnLoadExtension: ' + baseDir);
+ this.ceee_.initExtensions(extensions);
+
+ var url = this.ceee_.getToolstripUrl();
+ if (url) {
+ // Navigate ChromeFrame to the URL we want for the toolstrip.
+ // NOTE: CF expects a full URL.
+ this.cf_.src = url;
+ this.ceee_.logInfo('OnLoadExtension: extension URL=' + url);
+ } else {
+ // Remove the toolbar entirely. The direct parent is the toolbaritem
+ // (check overlay.xul for more information), so we to remove our parentNode.
+ var toolbar = this.parent_.parentNode.parentNode;
+ toolbar.parentNode.removeChild(toolbar);
+ this.ceee_.logInfo('No extension URL');
+ }
+
+ this.isReady_ = true;
+ this.postPendingMessages_();
+ onCfReady();
+ var parent = this.parent_;
+ setTimeout(function(){
+ parent.selectedIndex = 0;
+ parent.removeChild(parent.lastChild);
+ }, 0);
+};
+
+// Make the constructor visible outside this anonymous block.
+CEEE_CfHelper = CfHelper;
+
+})();
diff --git a/ceee/firefox/content/cookie_api.js b/ceee/firefox/content/cookie_api.js
new file mode 100644
index 0000000..fda3296
--- /dev/null
+++ b/ceee/firefox/content/cookie_api.js
@@ -0,0 +1,484 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains the implementation of the Chrome extension
+ * cookies APIs for the CEEE Firefox add-on. This file is loaded by the
+ * overlay.xul file, and requires that overlay.js has already been loaded.
+ *
+ * @supported Firefox 3.x
+ */
+
+
+/**
+ * "cookie-changed" Mozilla event observer.
+ * @param {function()} handler Handle event function.
+ * @constructor
+ */
+function CEEE_CookieChangeObserver(handler) {
+ this.observe = handler;
+}
+
+/**
+ * XPCOM interface function.
+ * @param {nsIIDRef} iid Interface ID.
+ * @return this object when the queried interface is nsIObserver.
+ */
+CEEE_CookieChangeObserver.prototype.QueryInterface = function(iid) {
+ if (iid.equals(Components.interfaces.nsIObserver) ||
+ iid.equals(Components.interfaces.nsISupports)) {
+ return this;
+ }
+ throw Components.results.NS_NOINTERFACE;
+};
+
+/**
+ * Registers the observer in the Mozilla Observer service.
+ * @private
+ */
+CEEE_CookieChangeObserver.prototype.register_ = function() {
+ var observerService = Components.classes['@mozilla.org/observer-service;1']
+ .getService(Components.interfaces.nsIObserverService);
+ observerService.addObserver(this, 'cookie-changed', false);
+};
+
+/**
+ * Unregisters the observer in the Mozilla Observer service.
+ * @private
+ */
+CEEE_CookieChangeObserver.prototype.unregister_ = function() {
+ var observerService = Components.classes['@mozilla.org/observer-service;1']
+ .getService(Components.interfaces.nsIObserverService);
+ observerService.removeObserver(this, 'cookie-changed');
+};
+
+
+/**
+ * chrome.cookies interface implementation.
+ * @type Object
+ */
+var CEEE_cookie_internal_ = CEEE_cookie_internal_ || {
+ /**
+ * Reference to the instance object for the ceee.
+ * @private
+ */
+ ceeeInstance_: null,
+
+ // Cookie store IDs.
+ /** @const */ STORE_PRIVATE: 'private',
+ /** @const */ STORE_NORMAL: 'normal'
+};
+
+/**
+ * Initializes the cookies module during onLoad message from the browser.
+ * @public
+ */
+CEEE_cookie_internal_.onLoad = function() {
+ var cookies = this;
+ this.cookieChangeObserver_ = new CEEE_CookieChangeObserver(
+ function(subject, topic, data) {
+ if (topic != 'cookie-changed') {
+ return;
+ }
+ if (data == 'deleted' || data == 'added' || data == 'changed') {
+ cookies.onCookieChanged_(
+ subject.QueryInterface(Components.interfaces.nsICookie2), data);
+ }
+ });
+ this.cookieChangeObserver_.register_();
+}
+
+/**
+ * Unloads the cookies module during onUnload message from the browser.
+ * @public
+ */
+CEEE_cookie_internal_.onUnload = function() {
+ this.cookieChangeObserver_.unregister_();
+};
+
+/**
+ * Returns a cookie store ID according to the current private browsing state.
+ * @return Current cookie store ID.
+ * @private
+ */
+CEEE_cookie_internal_.getCurrentStoreId_ = function() {
+ return CEEE_globals.privateBrowsingService.isInPrivateBrowsing ?
+ this.STORE_PRIVATE :
+ this.STORE_NORMAL;
+};
+
+
+/**
+ * @typedef {{
+ * name : string,
+ * value : string,
+ * domain : string,
+ * hostOnly : boolean,
+ * path : string,
+ * secure : boolean,
+ * httpOnly : boolean,
+ * session : boolean,
+ * expirationDate: ?number,
+ * storeId : string
+ * }}
+ */
+CEEE_cookie_internal_.Cookie;
+
+/**
+ * Builds an object that represents a "cookie", as defined by the Google Chrome
+ * extension API.
+ * @param {nsICookie2} cookie Mozilla cookie info.
+ * @return {CEEE_cookie_internal_.Cookie} chrome.cookies.Cookie object.
+ * @private
+ */
+CEEE_cookie_internal_.buildCookieValue_ = function(cookie) {
+ var host = cookie.host;
+ if (host && host[0] == '.') host = host.substr(1);
+ var info = {
+ name : cookie.name,
+ value : cookie.value,
+ domain : host,
+ hostOnly : !cookie.isDomain,
+ path : cookie.path,
+ secure : cookie.isSecure,
+ httpOnly : cookie.isHttpOnly,
+ session : cookie.isSession,
+ storeId : this.getCurrentStoreId_()
+ };
+ if (!cookie.isSession) {
+ info.expirationDate = cookie.expires;
+ }
+
+ return info;
+};
+
+/**
+ * @typedef {{
+ * id: string,
+ * tabIds: Array.<string>
+ * }}
+ */
+CEEE_cookie_internal_.CookieStore;
+
+/**
+ * Builds an object that represents a "cookie store", as defined
+ * by the Google Chrome extension API.
+ * @return {CEEE_cookie_internal_.CookieStore}
+ * chrome.cookies.CookieStore object.
+ * @private
+ */
+CEEE_cookie_internal_.buildCookieStoreValue_ = function() {
+ var info = {};
+ info.id = this.getCurrentStoreId_();
+ info.tabIds = [];
+
+ return info;
+};
+
+/**
+ * Sends an event message to Chrome API when a cookie is set or removed.
+ * @param {nsICookie2} cookie Changed/removed cookie.
+ * @param {string} action One of the "deleted", "added" or "changed" strings.
+ * @private
+ */
+CEEE_cookie_internal_.onCookieChanged_ = function(cookie, action) {
+ // Send the event notification to ChromeFrame.
+ var info = [{
+ removed: action == 'deleted',
+ cookie: this.buildCookieValue_(cookie)
+ }];
+ var msg = ['cookies.onChanged', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+
+/**
+ * Implements the 'cookies.get' function in the Chrome extension API.
+ * @param cmd Command.
+ * @param data Arguments.
+ * @return {?CEEE_cookie_internal_.Cookie} Cookie found or null.
+ * @private
+ */
+CEEE_cookie_internal_.CMD_GET_COOKIE = 'cookies.get';
+CEEE_cookie_internal_.getCookie_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var details = args[0];
+ var cookie = this.getCookieInternal_(details);
+ if (cookie) {
+ return this.buildCookieValue_(cookie);
+ }
+};
+
+/**
+ * Implements the main (internal) part of the 'cookies.get' function.
+ * @param {{url:string, name:string, storeId:?string}} details
+ * Cookie filter to get.
+ * @return {?nsICookie2} Mozilla cookie object or null if cookie doesn't exist.
+ * @private
+ */
+CEEE_cookie_internal_.getCookieInternal_ = function(details) {
+ var newDetails = { url: details.url, name: details.name };
+ if (typeof(details.storeId) == 'string') {
+ newDetails.storeId = details.storeId;
+ }
+ var cookies = this.getAllCookiesInternal_(newDetails);
+ if (cookies.length == 0) {
+ return null;
+ } else {
+ var signum = function(n) {
+ return n > 0 ? 1 : n < 0 ? -1 : 0;
+ };
+ cookies.sort(function(a, b) {
+ return 4 * signum(b.host.length - a.host.length) +
+ 2 * signum(b.path.length - a.path.length) +
+ signum((a.creationTime || 0) - (b.creationTime || 0));
+ });
+ return cookies[0];
+ };
+};
+
+/**
+ * Implements the 'cookies.getAll' function in the Chrome extension API.
+ * @param cmd Command.
+ * @param data Arguments.
+ * @return {Array.<CEEE_cookie_internal_.Cookie>} Array of the cookies found
+ * against parameters.
+ * @private
+ */
+CEEE_cookie_internal_.CMD_GET_ALL_COOKIES = 'cookies.getAll';
+CEEE_cookie_internal_.getAllCookies_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var details = args[0];
+ var self = this;
+ return this.getAllCookiesInternal_(details).map(function(cookie, i, a) {
+ return self.buildCookieValue_(cookie);
+ });
+};
+
+/**
+ * Implements the main (internal) part of the 'cookies.getAll' function.
+ * @param {Object} details Search parameters.
+ * @return {Array.<nsICookie2>} Array of the cookies found against parameters.
+ * @private
+ */
+CEEE_cookie_internal_.getAllCookiesInternal_ = function(details) {
+ var storeId = this.getCurrentStoreId_();
+ if (typeof(details.storeId) == 'string' && details.storeId != storeId) {
+ // We do have access to current cookie store only.
+ return [];
+ }
+ var cme = CEEE_cookieManager.enumerator;
+ var cookies = [];
+ while (cme.hasMoreElements()) {
+ var cookie = cme.getNext().QueryInterface(Components.interfaces.nsICookie2);
+ cookies.push(cookie);
+ }
+
+
+ /**
+ * Filters cookies array with a filtering function.
+ * @param {string} prop Property to filter on.
+ * @param {string} type Property type.
+ * @param {function(Object, number, Array.<Object>)} func Filtering function.
+ */
+ var filterCookies = function(prop, type, func) {
+ if (typeof(details[prop]) == type) {
+ cookies = cookies.filter(func);
+ }
+ };
+
+ /**
+ * Checks whether a given domain is a subdomain of another one.
+ * @param {string} domain Domain name.
+ * @param {string} subdomain Subdomain name.
+ * @return {boolean} True iff subdomain is a sub-domain of the domain.
+ */
+ var isSubDomain = function(domain, subdomain) {
+ var domLen = domain.length;
+ var subdomLen = subdomain.length;
+ return domLen <= subdomLen &&
+ subdomain.substr(subdomLen - domLen) == domain &&
+ (domLen == subdomLen ||
+ domain[0] == '.' ||
+ subdomain[subdomLen - domLen - 1] == '.');
+ };
+
+ /**
+ * Filters cookies array by a domain name.
+ * @param {string} domain Domain name.
+ */
+ var filterCookiesByDomain = function(domain) {
+ cookies = cookies.filter(function(cookie, i, a) {
+ // Check whether the given string is equal or a subdomain
+ // of the cookie domain.
+ return isSubDomain(domain, cookie.host);
+ });
+ };
+
+ /**
+ * Filters cookies array by the URL host.
+ * @param {string} host Host name.
+ */
+ var filterCookiesByHost = function(host) {
+ cookies = cookies.filter(function(cookie, i, a) {
+ // Check whether the cookie domain is equal or a subdomain
+ // of the given URL host.
+ return isSubDomain(cookie.host, host);
+ });
+ };
+
+ // Filtering by argument details.
+ filterCookies('name', 'string', function(cookie, i, a) {
+ return cookie.name == details.name;
+ });
+ filterCookies('path', 'string', function(cookie, i, a) {
+ return cookie.path == details.path;
+ });
+ filterCookies('secure', 'boolean', function(cookie, i, a) {
+ return cookie.isSecure == details.secure;
+ });
+ filterCookies('session', 'boolean', function(cookie, i, a) {
+ return cookie.isSession == details.session;
+ });
+ if (details.domain) {
+ filterCookiesByDomain(details.domain);
+ }
+ if (typeof(details.url) == 'string') {
+ var uri = null;
+ try {
+ uri = CEEE_ioService.newURI(details.url, null, null);
+ } catch (e) {
+ // uri left null if something happens.
+ }
+ if (uri) {
+ filterCookiesByHost(uri.host);
+ var path = uri.path || '/';
+ cookies = cookies.filter(function(cookie, i, a) {
+ // Any valid subpath should pass the filter.
+ return path.indexOf(cookie.path) == 0 &&
+ (path.length == cookie.path.length ||
+ path.charAt(cookie.path.length) == '/');
+ });
+ } else {
+ // not valid URL; no cookie can match it.
+ cookies = [];
+ }
+ }
+
+ return cookies;
+};
+
+/**
+ * Implements the 'cookies.remove' function in the Chrome extension API.
+ * @param cmd Command.
+ * @param data Arguments.
+ * @private
+ */
+CEEE_cookie_internal_.CMD_REMOVE_COOKIE = 'cookies.remove';
+CEEE_cookie_internal_.removeCookie_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var details = args[0];
+ var newDetails = { url: details.url, name: details.name };
+ if (typeof(details.storeId) == 'string') {
+ newDetails.storeId = details.storeId;
+ }
+ var cookie = this.getCookieInternal_(newDetails);
+ if (cookie) {
+ CEEE_cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
+ }
+};
+
+/**
+ * Implements the 'cookies.set' function in the Chrome extension API.
+ * @param cmd Command.
+ * @param data Arguments.
+ * @private
+ */
+CEEE_cookie_internal_.CMD_SET_COOKIE = 'cookies.set';
+CEEE_cookie_internal_.setCookie_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var details = args[0];
+ var uri = null;
+ try {
+ uri = CEEE_ioService.newURI(details.url, null, null);
+ } catch (e) {
+ // Invalid URL.
+ return;
+ }
+ var DOOMSDAY = (new Date(2222, 0, 1)).getTime() / 1000;
+ var domain = details.domain ? '.' + details.domain : uri.host;
+ var path = details.path || uri.path || '/';
+ var name = details.name || '';
+ var value = details.value || '';
+ var secure = details.secure || false;
+ var isHttpOnly = !details.domain;
+ var isSession = typeof(details.expirationDate) != 'number';
+ // Expiration date should be set even for a session cookie!
+ // Otherwise the cookie never appears even up to the end of the session.
+ // Possibly Mozilla bug?
+ var expire = isSession ? DOOMSDAY : details.expirationDate;
+ CEEE_cookieManager.add(
+ domain, path, name, value, secure, isHttpOnly, isSession, expire);
+};
+
+/**
+ * Implements the 'cookies.getAllCookieStores' function in
+ * the Chrome extension API.
+ * The Firefox browser always contains exactly one cookie store.
+ * @param cmd Command.
+ * @param data Arguments.
+ * @return {Array.<CEEE_cookie_internal_.CookieStore>} Array of the
+ * CookieStore objects.
+ * @private
+ */
+CEEE_cookie_internal_.CMD_GET_ALL_COOKIE_STORES =
+ 'cookies.getAllCookieStores';
+CEEE_cookie_internal_.getAllCookieStores_ = function(cmd, data) {
+ var storeId = this.getCurrentStoreId_();
+ var wm = CEEE_mozilla_windows.service;
+ var be = wm.getEnumerator(CEEE_mozilla_windows.WINDOW_TYPE);
+ var stores = [this.buildCookieStoreValue_()];
+ var nTab = 0;
+ while (be.hasMoreElements()) {
+ var win = be.getNext();
+ var tabs = win.gBrowser.tabContainer.childNodes;
+ for (var iTab = 0; iTab < tabs.length; iTab++) {
+ var tabId = CEEE_mozilla_tabs.getTabId(tabs[iTab]);
+ stores[0].tabIds.push(tabId);
+ }
+ }
+ return stores;
+};
+
+/**
+ * Initialization routine for the CEEE Cookies API module.
+ * @param {!Object} ceeeInstance Reference to the global ceee instance.
+ * @return {Object} Reference to the cookies module.
+ * @public
+ */
+function CEEE_initialize_cookies(ceeeInstance) {
+ CEEE_cookie_internal_.ceeeInstance_ = ceeeInstance;
+ var cookies = CEEE_cookie_internal_;
+
+ // Register the extension handling functions with the ceee instance.
+ ceeeInstance.registerExtensionHandler(cookies.CMD_GET_COOKIE,
+ cookies,
+ cookies.getCookie_);
+ ceeeInstance.registerExtensionHandler(cookies.CMD_GET_ALL_COOKIES,
+ cookies,
+ cookies.getAllCookies_);
+ ceeeInstance.registerExtensionHandler(cookies.CMD_REMOVE_COOKIE,
+ cookies,
+ cookies.removeCookie_);
+ ceeeInstance.registerExtensionHandler(cookies.CMD_SET_COOKIE,
+ cookies,
+ cookies.setCookie_);
+ ceeeInstance.registerExtensionHandler(cookies.CMD_GET_ALL_COOKIE_STORES,
+ cookies,
+ cookies.getAllCookieStores_);
+
+ return cookies;
+}
diff --git a/ceee/firefox/content/externs.js b/ceee/firefox/content/externs.js
new file mode 100644
index 0000000..8170797
--- /dev/null
+++ b/ceee/firefox/content/externs.js
@@ -0,0 +1,91 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview 'externs' file for JSCompiler use with Firefox CEEE javascript
+ * code loaded from overlay.xul.
+ */
+
+// Defined in CEEE global module
+var CEEE_bookmarks
+var CEEE_globals
+var CEEE_ioService
+var CEEE_json
+var CEEE_mozilla_tabs
+var CEEE_mozilla_windows
+
+// Javascript predefined variables
+var arguments;
+var undefined;
+
+// DOM predefined variables
+var document;
+var window;
+
+// Javascript/DOM builtin function
+function parseInt(str, radix) {}
+function open(utl, target, features) {}
+
+
+// Javasript runtime classes
+/**
+ * @returns string
+ * @constructor
+ */
+function Date(opt_a, opt_b, opt_c, opt_d, opt_e, opt_f, opt_g) {}
+/**
+ * @param {*} opt_a
+ * @param {*} opt_b
+ * @param {*} opt_c
+ * @returns !Error
+ * @constructor
+ */
+function Error(opt_a, opt_b, opt_c) {}
+/**
+ * @param {*} opt_a
+ * @param {*} opt_b
+ * @returns !RegExp
+ * @constructor
+ */
+function RegExp(opt_a, opt_b) {}
+/**
+ * @param {*} opt_a
+ * @returns string
+ * @constructor
+ */
+function String(opt_a) {}
+
+
+// Predefined XPCOM runtime entities.
+/** @constructor */
+function XPCNativeWrapper(a) {}
+
+var Components;
+Components.classes;
+Components.interfaces;
+Components.utils;
+
+var Application;
+Application.console;
+Application.console.log = function(arg) {};
+
+function dump(text) {}
+
+/** @constructor */
+function nsICookie2(){}
+/** @constructor */
+function nsIFile(){}
+/** @constructor */
+function nsIIDRef(){}
+/** @constructor */
+function nsILocalFile(){}
+/** @constructor */
+function nsIDOMWindow(){}
+
+/**
+ * window.location pseudoclass, see
+ * https://developer.mozilla.org/en/window.location#Location_object
+ * @constructor
+ */
+function Location(){}
diff --git a/ceee/firefox/content/infobars_api.js b/ceee/firefox/content/infobars_api.js
new file mode 100644
index 0000000..44963a3
--- /dev/null
+++ b/ceee/firefox/content/infobars_api.js
@@ -0,0 +1,218 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains the implementation of the Chrome extension
+ * infobars API for the CEEE Firefox add-on. This file is loaded by the
+ * overlay.xul file, and requires that overlay.js has already been loaded.
+ *
+ * @supported Firefox 3.x
+ */
+
+/**
+ * Place-holder namespace-object for helper functions used in this file.
+ * @private
+ */
+var CEEE_infobars_internal_ = CEEE_infobars_internal_ || {
+ /** Reference to the instance object for the ceee. @private */
+ ceeeInstance_: null
+};
+
+/**
+ * @const
+ */
+CEEE_infobars_internal_.NOTIFICATION_ID = 'ceeeNotification';
+
+/**
+ * @const
+ */
+CEEE_infobars_internal_.ANONID = 'anonid';
+
+/**
+ * @const
+ */
+CEEE_infobars_internal_.PARENT_ANONID = 'details';
+
+/**
+ * @const
+ */
+CEEE_infobars_internal_.ID_PREFIX = 'ceee-infobar-';
+
+/**
+ * Get a File object for the given user script. Its assumed that the user
+ * script file name is relative to the Chrome Extension root.
+ *
+ * @param {!string} path Relative file name of the user script.
+ * @return {string} Absolute URL for the user script.
+ */
+function CEEE_infobars_getUserScriptUrl(path) {
+ var ceee = CEEE_infobars_internal_.ceeeInstance_;
+ var id = ceee.getToolstripExtensionId();
+ return 'chrome-extension://' + id + '/' + path;
+}
+
+/**
+ * Implementation of experimental.infobars.show method.
+ * @param {string} cmd Command name, 'experimenta.infobars.show' expected
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_infobars_internal_.show = function(cmd, data){
+ var ceee = CEEE_infobars_internal_.ceeeInstance_;
+ var args = CEEE_json.decode(data.args)[0];
+ var tabId = args.tabId;
+ var path = args.path;
+ var url = CEEE_infobars_getUserScriptUrl(path);
+ var tabbrowser = null;
+ var browser = null;
+ if ('number' != typeof tabId || tabId < 0) {
+ tabbrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ browser = tabbrowser.selectedBrowser;
+ } else {
+ var tabInfo = CEEE_mozilla_tabs.findTab(tabId);
+ tabbrowser = tabInfo.tabbrowser;
+ browser = tabbrowser.getBrowserAtIndex(tabInfo.index);
+ }
+ var impl = CEEE_infobars_internal_;
+ var notificationBox = tabbrowser.getNotificationBox(browser);
+ var notification = notificationBox.appendNotification(
+ '', // message
+ impl.NOTIFICATION_ID, // identifier
+ null, // icon
+ notificationBox.PRIORITY_INFO_HIGH,
+ null // buttons
+ );
+ var parent = document.getAnonymousElementByAttribute(notification,
+ impl.ANONID, impl.PARENT_ANONID);
+ // We don't need the default content there. Removing icon, message text etc.
+ while (parent.firstChild) {
+ parent.removeChild(parent.lastChild);
+ }
+
+ // Let's assign window id first and use it as base for chrome frame id.
+ var result = ceee.getWindowModule().buildWindowValue(
+ new NotificationAsWindow(notification, parent));
+ var id = impl.ID_PREFIX + result.id;
+ var cf = CEEE_infobars_createChromeFrame_(parent, id, url);
+ var infobars =
+ notification.ownerDocument.defaultView.CEEE_infobars_internal_;
+ infobars.lookupMap_ = infobars.lookupMap_ || {};
+ infobars.lookupMap_[id] = notificationBox.id;
+ return result;
+};
+CEEE_infobars_internal_.show.CMD = 'experimental.infobars.show';
+
+CEEE_infobars_internal_.lookup = function(id) {
+ var ceee = CEEE_infobars_internal_.ceeeInstance_;
+ var impl = CEEE_infobars_internal_;
+ var e = CEEE_mozilla_windows.service.getEnumerator(
+ CEEE_mozilla_windows.WINDOW_TYPE);
+ while (e.hasMoreElements()) {
+ var win = e.getNext();
+ var infobars = win.CEEE_infobars_internal_;
+ var notificationBoxId = infobars.lookupMap_ &&
+ infobars.lookupMap_[impl.ID_PREFIX + id];
+ if (!notificationBoxId) {
+ continue;
+ }
+ var doc = win.document;
+ var notificationBox = doc.getElementById(notificationBoxId);
+ var notifications = notificationBox.allNotifications;
+ for (var i = 0, curr; curr = notifications[i]; ++i) {
+ var parent = document.getAnonymousElementByAttribute(curr,
+ impl.ANONID, impl.PARENT_ANONID);
+ if (parent.firstChild.id == impl.ID_PREFIX + id) {
+ return new NotificationAsWindow(curr, parent);
+ }
+ }
+ // Notification not found. Remove from lookup cache
+ delete infobars.lookupMap_[impl.ID_PREFIX + id];
+ // Once we had it in lookup map, no point to continue search
+ break;
+ }
+ return null;
+};
+
+/**
+ * Create the ChromeFrame element for infobar content.
+ * @private
+ */
+function CEEE_infobars_createChromeFrame_(parent, id, url) {
+ var ceee = CEEE_infobars_internal_.ceeeInstance_;
+ var cf = parent.ownerDocument.createElementNS('http://www.w3.org/1999/xhtml',
+ 'embed');
+ cf.id = id;
+ // TODO(ibazarny@google.com): the height here is arbitrary. We will
+ // need to size appropriately when we have more info.
+ cf.setAttribute('height', '35');
+ cf.setAttribute('flex', '1');
+ cf.setAttribute('type', 'application/chromeframe');
+ cf.setAttribute('privileged_mode', '1');
+ cf.setAttribute('chrome_extra_arguments',
+ '--enable-experimental-extension-apis');
+ cf.setAttribute('src', url);
+ parent.appendChild(cf);
+ return cf;
+}
+
+/**
+ * Initialization routine for the CEEE infobars API module.
+ * @param {!Object} ceeeInstance Reference to the global ceee instance.
+ * @param {!Object} windowModule Window management module, used to register
+ * fake window lookup so we can turn notifications into windows.
+ * @return {Object} Reference to the infobars module.
+ * @public
+ */
+function CEEE_initialize_infobars(ceeeInstance, windowModule) {
+ CEEE_infobars_internal_.ceeeInstance_ = ceeeInstance;
+ var infobars = CEEE_infobars_internal_;
+ ceeeInstance.registerExtensionHandler(infobars.show.CMD,
+ infobars,
+ infobars.show);
+ windowModule.registerLookup(infobars.lookup);
+ return infobars;
+}
+
+/**
+ * Make notification look like Window, so that window api module can work with
+ * it.
+ * @constructor
+ */
+function NotificationAsWindow(notification, cfContainer){
+ this.isAdapter = true;
+ this.notification_ = notification;
+ this.cfContainer_ = cfContainer;
+}
+
+NotificationAsWindow.prototype.__defineGetter__('screenX', function(){
+ return this.cfContainer_.boxObject.screenX;
+});
+
+NotificationAsWindow.prototype.__defineGetter__('screenY', function(){
+ return this.cfContainer_.boxObject.screenY;
+});
+
+NotificationAsWindow.prototype.__defineGetter__('outerWidth', function(){
+ return this.cfContainer_.boxObject.width;
+});
+
+NotificationAsWindow.prototype.__defineGetter__('outerHeight', function(){
+ return this.cfContainer_.boxObject.height;
+});
+
+NotificationAsWindow.prototype.__defineGetter__(
+ CEEE_mozilla_windows.WINDOW_ID,
+ function(){
+ return this.notification_[CEEE_mozilla_windows.WINDOW_ID];
+ });
+
+NotificationAsWindow.prototype.__defineSetter__(
+ CEEE_mozilla_windows.WINDOW_ID,
+ function(value){
+ this.notification_[CEEE_mozilla_windows.WINDOW_ID] = value;
+ });
+
+NotificationAsWindow.prototype.close = function() {
+ this.notification_.close();
+};
diff --git a/ceee/firefox/content/js-coverage.js b/ceee/firefox/content/js-coverage.js
new file mode 100644
index 0000000..eca2227
--- /dev/null
+++ b/ceee/firefox/content/js-coverage.js
@@ -0,0 +1,630 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview JavaScript code-coverage utility.
+ * This module powers a FireFox extension that gathers code coverage results.
+ * It depends on the Venkman JavaScript Debugger extension.
+ * <p>
+ * To enable coverage tracking, set the JS_COVERAGE environment variable to 1.
+ * Then, optionally, set the JS_COVERAGE_FILE environment variable to the full
+ * path where the results LCOV data should be stored. The default path is
+ * "/tmp/jscov.dat". Finally, set the JS_COVERAGE_URL_PATTERN environment
+ * variable to a regex of the files you want to instrument. The default pattern
+ * is "^http://.*js".
+ * <p>
+ * The coverage seems to be a little flaky, in the sense that it does not
+ * always seem to catch all the javascript files being loaded into Firefox.
+ * From what I have observed, it seems that the files of extensions might be
+ * cached, and when loaded from this cache the onScriptCreated() callback is
+ * not always called. If I save any one file in the extension, then all files
+ * of that extension are caught correctly. Still investigating.
+ */
+
+
+/** Namespace for everything in this extension. */
+var LCOV = {};
+
+
+/**
+ * This helper method returns a given class based on a class name from the
+ * global Components repository.
+ * @param {String} className: The name of the class to return
+ * @return {Object}: The requested class (not an instance)
+ */
+LCOV.CC = function(className) {
+ return Components.classes[className];
+};
+
+
+/**
+ * This helper method returns a given interface based on an interface name
+ * from the global Components repository.
+ * @param {String} interfaceName The name of the interface to return
+ * @return {Object} The requested interface
+ */
+LCOV.CI = function(interfaceName) {
+ return Components.interfaces[interfaceName];
+};
+
+
+/** Debugger service component class */
+LCOV.debuggerService = LCOV.CC('@mozilla.org/js/jsd/debugger-service;1');
+
+
+/** Environment component class */
+LCOV.environment = LCOV.CC("@mozilla.org/process/environment;1");
+
+
+/** Local file component class */
+LCOV.localFile = LCOV.CC('@mozilla.org/file/local;1');
+
+
+/** File output stream component class */
+LCOV.fileOutputStream = LCOV.CC('@mozilla.org/network/file-output-stream;1');
+
+
+/** Debugger service component interface */
+LCOV.jsdIDebuggerService = LCOV.CI('jsdIDebuggerService');
+
+
+/** Script component interface */
+LCOV.jsdIScript = LCOV.CI('jsdIScript');
+
+
+/** Execution hook component interface */
+LCOV.jsdIExecutionHook = LCOV.CI("jsdIExecutionHook");
+
+
+/** Environment component interface */
+LCOV.nsIEnvironment = LCOV.CI('nsIEnvironment');
+
+
+/** Local file component interface */
+LCOV.nsILocalFile = LCOV.CI('nsILocalFile');
+
+
+/** File output stream component interface */
+LCOV.nsIFileOutputStream = LCOV.CI('nsIFileOutputStream');
+
+
+/** Log an informational message to the Firefox error console. */
+LCOV.logInfo = function(msg) {
+ Application.console.log('LCOV: ' + msg);
+};
+
+
+/** Log a error message to the Firefox error console. */
+LCOV.logError = function(msg) {
+ Components.utils.reportError('LCOV: *** ' + msg);
+};
+
+
+/**
+ * Useful function for dumping the properties of an object, if for some reason
+ * the JSON encoding does not work.
+ * @param {string} message String to prepend to dumped message.
+ * @param {Object} o The object to dump.
+ */
+LCOV.dumpit = function(message, o) {
+ var buf = 'dumpit:' + message + ': ';
+
+ if (o && o.wrappedJSObject)
+ o = o.wrappedJSObject;
+
+ if (o) {
+ for (pn in o) {
+ try {
+ if (o[pn] && typeof o[pn] != 'function')
+ buf += ' ' + pn + '=' + o[pn];
+ } catch (ex) {
+ buf += ' ' + pn + ':ex=' + ex;
+ }
+ }
+ } else {
+ buf += 'object is null or not defined';
+ }
+
+ LCOV.logInfo(buf);
+};
+
+
+/**
+ * The coverage class is responsible for creating and setting up a JavaScript
+ * debugger. It then tracks every line of javascript executed so that coverage
+ * data can be reported after a test run.
+ * @constructor
+ */
+LCOV.CoverageTracker = function() {
+
+ /**
+ * The Venkman JavaScript Debugger service
+ * @type jsdIDebuggerService
+ * @private
+ */
+ this.javascriptDebugger_ = LCOV.debuggerService.getService(
+ LCOV.jsdIDebuggerService);
+ if (!this.javascriptDebugger_) {
+ LCOV.logError('Coult not get debugger service.');
+ }
+
+ /**
+ * Whether coverage is enabled.
+ * @type boolean
+ * @private
+ */
+ this.isCoverageEnabled_ = this.getIsCoverageEnabled_();
+
+ if (this.isCoverageEnabled() && this.javascriptDebugger_) {
+ LCOV.logInfo('is enabled');
+
+ var pattern = this.getUrlPattern_();
+ LCOV.logInfo('Will instrument URLs macthing: ' + pattern);
+
+ /**
+ * Regular expression for matching page URLs to instrument.
+ * @type RegExp
+ * @private
+ */
+ this.reUrlPattern_ = new RegExp(pattern, 'i');
+
+ this.startCoverage_();
+ } else {
+ LCOV.logInfo('not enabled');
+ }
+};
+
+
+/**
+ * Environment variable name for whether coverage is requested
+ * @type String
+ * @private
+ */
+LCOV.CoverageTracker.JS_COVERAGE_ENV_ = 'JS_COVERAGE';
+
+
+/**
+ * Environment variable name for the output file path
+ * @type String
+ * @private
+ */
+LCOV.CoverageTracker.JS_COVERAGE_FILE_ENV_ = 'JS_COVERAGE_FILE';
+
+
+/**
+ * Environment variable name for regular expression used for matching page
+ * URLs to instrument
+ * @type String
+ * @private
+ */
+LCOV.CoverageTracker.JS_COVERAGE_URL_PATTERN_ENV_ = 'JS_COVERAGE_URL_PATTERN';
+
+
+/**
+ * Default output file path
+ * @type String
+ * @private
+ */
+LCOV.CoverageTracker.DEFAULT_FILE_ = '/tmp/jscov.dat';
+
+
+/**
+ * Interval between flushes in milliseconds
+ * @type Number
+ * @private
+ */
+LCOV.CoverageTracker.FLUSH_INTERVAL_MS_ = 2000;
+
+
+/**
+ * Maximum number of lines stored at a time before a flush is triggered
+ * @type Number
+ * @private
+ */
+LCOV.CoverageTracker.HITS_SIZE_THRESHOLD_ = 10000;
+
+
+/**
+ * Singleton instance of the coverage tracker
+ * @type LCOV.CoverageTracker
+ * @private
+ */
+LCOV.CoverageTracker.instance_ = null;
+
+
+/**
+ * All the recorded line hit data
+ * @type Object
+ * @private
+ */
+LCOV.CoverageTracker.prototype.hits_ = null;
+
+
+/**
+ * Number of lines stored in the {@code hits_} field
+ * @type Number
+ * @private
+ */
+LCOV.CoverageTracker.prototype.hitsSize_ = 0;
+
+
+/**
+ * Handle to an event for flushing the data regularly
+ * @type Number
+ * @private
+ */
+LCOV.CoverageTracker.prototype.flushEvent_ = null;
+
+
+/**
+ * Output file stream
+ * @type nsIFileOutputStream
+ * @private
+ */
+LCOV.CoverageTracker.prototype.stream_ = null;
+
+
+/**
+ * Gets a singleton instance of the coverage tracker. This method is
+ * idempotent.
+ * @return {LCOV.CoverageTracker} coverage tracker instance
+ */
+LCOV.CoverageTracker.getInstance = function() {
+ if (!LCOV.CoverageTracker.instance_) {
+ LCOV.CoverageTracker.instance_ = new LCOV.CoverageTracker();
+ }
+ return LCOV.CoverageTracker.instance_;
+};
+
+
+/**
+ * Returns a cached value for whether or not coverage is enabled for this test
+ * run.
+ * @return {boolean} Whether or not coverage is enabled
+ */
+LCOV.CoverageTracker.prototype.isCoverageEnabled = function() {
+ return this.isCoverageEnabled_;
+};
+
+
+/**
+ * Set of scripts that have been been created. This is really only used for
+ * logging and debuggin purposes.
+ * @private
+ */
+LCOV.scripts_ = {};
+
+
+/**
+ * Setup method that gets invoked from the extension's overlay. This must
+ * be called once from each top-level Firefox window.
+ */
+LCOV.CoverageTracker.prototype.setupCurrentWindow = function() {
+ if (this.isCoverageEnabled()) {
+ // The script hooks have to be registered in each top-level Firefox window.
+ var coverageTracker = this;
+ this.javascriptDebugger_.scriptHook = {
+ 'onScriptCreated':
+ function(script) {
+ // This function seems to be called once for each javascript
+ // function defined in the source file. The script will also
+ // be destroyed once when each function returns.
+ coverageTracker.instrumentScript_(script);
+ },
+ 'onScriptDestroyed':
+ function(script) {
+ if (typeof LVOC != 'undefined') {
+ var fileName = script.fileName.toString();
+ LCOV.scripts_[fileName] -= 1;
+ if (0 == LCOV.scripts_[fileName]) {
+ //LCOV.logInfo('onScriptDestroyed ' + fileName);
+ delete LCOV.scripts_[fileName];
+ }
+ }
+ coverageTracker.flushHits_();
+ }
+ };
+ }
+};
+
+
+/**
+ * Disposes of the object. Note that the singleton currently lives for as long
+ * as the browser is open, so there are no callers of this method.
+ */
+LCOV.CoverageTracker.prototype.dispose = function() {
+ this.endCoverage_();
+};
+
+
+/**
+ * Begin the coverage cycle. First, clear out any existing coverage data.
+ * Second, attach to the interrupt hook within the javascript debugger. Please
+ * note that this is a very expensive operation so as few scripts as possible
+ * should be evaluated.
+ * @private
+ */
+LCOV.CoverageTracker.prototype.startCoverage_ = function() {
+ LCOV.logInfo('starting');
+ this.openOutputStream_();
+ this.clearCoverageData_();
+
+ var coverageTracker = this;
+
+ this.javascriptDebugger_.on();
+
+ // Enum all scripts there were loaded prior to the debugger turned on.
+ this.javascriptDebugger_.enumerateScripts({
+ enumerateScript: function(script) {
+ //var fileName = script.fileName.toString();
+ //LCOV.logInfo('Script already loaded: ' + fileName);
+ coverageTracker.instrumentScript_(script);
+ }});
+
+ var flags = LCOV.jsdIDebuggerService.DISABLE_OBJECT_TRACE |
+ LCOV.jsdIDebuggerService.DEBUG_WHEN_SET |
+ LCOV.jsdIDebuggerService.MASK_TOP_FRAME_ONLY;
+ //flags |= this.javascriptDebugger_.flags;
+ this.javascriptDebugger_.flags |= flags;
+
+ this.javascriptDebugger_.interruptHook = {
+ 'onExecute':
+ function(frame, type, val) {
+ coverageTracker.recordExecution_(frame);
+ return LCOV.jsdIExecutionHook.RETURN_CONTINUE;
+ }
+ };
+ this.flushEvent_ = window.setInterval(
+ function() {
+ if (coverageTracker.hitsSize_ > 0)
+ LCOV.logInfo('timed out, flushing');
+
+ coverageTracker.flushHits_();
+ },
+ LCOV.CoverageTracker.FLUSH_INTERVAL_MS_);
+};
+
+
+/**
+ * End the coverage cycle by killing the interrupt hook attached to the
+ * javascript debugger. Additionally, turn the javascript debugger off.
+ * @private
+ */
+LCOV.CoverageTracker.prototype.endCoverage_ = function() {
+ LCOV.logInfo('ending');
+ window.clearInterval(this.flushEvent_);
+ this.flushEvent_ = null;
+ this.javascriptDebugger_.interruptHook = null;
+ this.javascriptDebugger_.scriptHook = null;
+ this.javascriptDebugger_.off();
+ this.clearCoverageData_();
+ this.closeOutputStream_();
+};
+
+
+/**
+ * Determines whether or not the script should be considered for coverage, and
+ * if so then begins instrumentation. Currently, the following rules are
+ * enforced:
+ * <ol>
+ * <li>The script cannot be a chrome script, (i.e. a browser or FF extension
+ * script)</li>
+ * <li>The script cannot be on the local filesystem, (i.e. a FF
+ * component)</li>
+ * <li>The script cannot be inline javascript</li>
+ * <li>The script must resemble a URL, (i.e. not "XStringBundle")</li>
+ * </ol>
+ * @param {jsdIScript} script The script to instrument
+ * @private
+ */
+LCOV.CoverageTracker.prototype.instrumentScript_ = function(script) {
+ var fileName = script.fileName.toString();
+ var doInstrumentation = this.reUrlPattern_.test(fileName) &&
+ !(fileName.lastIndexOf('js-coverage.js') == fileName.length - 14);
+
+ // Log whether the script will be instrumented or not.
+ if (!LCOV.scripts_[fileName]) {
+ LCOV.scripts_[fileName] = 1;
+ var prefix = doInstrumentation ? 'Instrumenting ' : 'Not Instrumenting ';
+ //LCOV.logInfo(prefix + fileName);
+ } else {
+ LCOV.scripts_[fileName] += 1;
+ }
+
+ if (doInstrumentation) {
+ // Turns on debugging for this script.
+ script.flags |= LCOV.jsdIScript.FLAG_DEBUG;
+
+ // Records all instrumentable lines in the script as not yet hit.
+ this.writeOutput_('SF:' + fileName + '\n');
+ var lineBegin = script.baseLineNumber;
+ var lineEnd = lineBegin + script.lineExtent + 1;
+ for (var lineIdx = lineBegin; lineIdx < lineEnd; lineIdx++) {
+ if (script.isLineExecutable(lineIdx,
+ LCOV.jsdIScript.PCMAP_SOURCETEXT)) {
+ this.writeOutput_('DA:' + lineIdx + ',0\n');
+ }
+ }
+ this.writeOutput_('end_of_record\n');
+ this.flushOutputStream_();
+ } else {
+ // Turns off debugging for this script.
+ script.flags &= ~LCOV.jsdIScript.FLAG_DEBUG;
+ }
+};
+
+
+/**
+ * Clears any stored coverage data.
+ * @private
+ */
+LCOV.CoverageTracker.prototype.clearCoverageData_ = function() {
+ //LCOV.logInfo('clearing data');
+ this.hits_ = {};
+ this.hitsSize_ = 0;
+};
+
+
+/**
+ * Checks to see whether or not coverage is enabled for this test run.
+ * @return {Boolean} Whether or not coverage is enabled
+ * @private
+ */
+LCOV.CoverageTracker.prototype.getIsCoverageEnabled_ = function() {
+ var userEnvironment = LCOV.environment.getService(LCOV.nsIEnvironment);
+ var isEnabled;
+ return userEnvironment.exists(LCOV.CoverageTracker.JS_COVERAGE_ENV_) &&
+ !!userEnvironment.get(LCOV.CoverageTracker.JS_COVERAGE_ENV_);
+};
+
+
+/**
+ * Gets the regular expression used for matching page URLs to instrument.
+ * Only pages that match this pattern will be instrumented.
+ */
+LCOV.CoverageTracker.prototype.getUrlPattern_ = function() {
+ var userEnvironment = LCOV.environment.getService(LCOV.nsIEnvironment);
+ var re = userEnvironment.exists(
+ LCOV.CoverageTracker.JS_COVERAGE_URL_PATTERN_ENV_) &&
+ userEnvironment.get(LCOV.CoverageTracker.JS_COVERAGE_URL_PATTERN_ENV_);
+ if (!re)
+ re = '^http://.*js$';
+
+ return re;
+};
+
+
+/**
+ * Gets the output file for coverage data.
+ * @return {String} Full path to the file
+ * @private
+ */
+LCOV.CoverageTracker.prototype.getCoverageFile_ = function() {
+ var userEnvironment = LCOV.environment.getService(LCOV.nsIEnvironment);
+ var file;
+ if (userEnvironment.exists(LCOV.CoverageTracker.JS_COVERAGE_FILE_ENV_)) {
+ file = userEnvironment.get(LCOV.CoverageTracker.JS_COVERAGE_FILE_ENV_);
+ if (file.length == 0) {
+ file = LCOV.CoverageTracker.DEFAULT_FILE_;
+ }
+ } else {
+ file = LCOV.CoverageTracker.DEFAULT_FILE_;
+ }
+
+ LCOV.logInfo('data will be written to ' + file);
+ return file;
+};
+
+
+/**
+ * Records a line-hit entry to the current collection data set.
+ * @param {jsdIStackFrame} frame A jsdIStackFrame object representing the
+ * bottom stack frame
+ * @private
+ */
+LCOV.CoverageTracker.prototype.recordExecution_ = function(frame) {
+ var lineNumber = frame.line;
+ var fileName = frame.script.fileName.toString();
+ var fileData;
+ if (fileName in this.hits_) {
+ fileData = this.hits_[fileName];
+ } else {
+ // Add this file to the data set.
+ fileData = this.hits_[fileName] = {};
+ }
+
+ if (lineNumber in fileData) {
+ // Increment the hit value at this line.
+ fileData[lineNumber]++;
+ } else {
+ // Make sure the working set doesn't grow too large.
+ if (this.hitsSize_ >= LCOV.CoverageTracker.HITS_SIZE_THRESHOLD_) {
+ LCOV.logInfo('threshold reached, flushing');
+ this.flushHits_();
+ }
+ // Since this line has not been tracked yet, set the value to 1 hit.
+ fileData[lineNumber] = 1;
+ this.hitsSize_++;
+ }
+};
+
+
+/**
+ * Appends to the output file in the "lcov" format which is outlined below.
+ * <pre>
+ * SF:<em>source file name 1</em>
+ * DA:<em>line_no</em>,<em>hit</em>
+ * DA:<em>line_no</em>,<em>hit</em>
+ * ...
+ * end_of_record
+ * SF:<em>source file name 2</em>
+ * DA:<em>line_no</em>,<em>hit</em>
+ * DA:<em>line_no</em>,<em>hit</em>
+ * ...
+ * end_of_record
+ * </pre>
+ * @private
+ */
+LCOV.CoverageTracker.prototype.flushHits_ = function() {
+ //LCOV.logInfo('flushing');
+ for (var file in this.hits_) {
+ var fileData = this.hits_[file];
+ this.writeOutput_('SF:' + file + '\n');
+
+ for (var lineHit in fileData) {
+ var lineHitCnt = fileData[lineHit];
+ this.writeOutput_('DA:' + lineHit + ',' + lineHitCnt + '\n');
+ }
+
+ this.writeOutput_('end_of_record\n');
+ }
+ this.clearCoverageData_();
+ this.flushOutputStream_();
+};
+
+
+/**
+ * Opens a file stream suitable for outputting coverage results.
+ * @private
+ */
+LCOV.CoverageTracker.prototype.openOutputStream_ = function() {
+ // We're outside of the sandbox so we can write directly to disk.
+ var outputFile = LCOV.localFile.createInstance(LCOV.nsILocalFile);
+ outputFile.initWithPath(this.getCoverageFile_());
+ this.stream_ = LCOV.fileOutputStream.createInstance(LCOV.nsIFileOutputStream);
+ this.stream_.init(outputFile, 0x04 | 0x08 | 0x10, 424, 0);
+};
+
+
+/**
+ * Writes a string to the output file stream.
+ * @param {String} str The string to write to the stream
+ * @private
+ */
+LCOV.CoverageTracker.prototype.writeOutput_ = function(str) {
+ this.stream_.write(str, str.length);
+};
+
+
+/**
+ * Closes the output stream.
+ * @private
+ */
+LCOV.CoverageTracker.prototype.closeOutputStream_ = function() {
+ this.flushOutputStream_();
+ this.stream_.close();
+};
+
+
+/**
+ * Closes the output stream.
+ * @private
+ */
+LCOV.CoverageTracker.prototype.flushOutputStream_ = function() {
+ this.stream_.flush();
+};
+
+// Setup the current top-level Firefox window for coverage.
+LCOV.CoverageTracker.getInstance().setupCurrentWindow();
+LCOV.logInfo('add-on loaded');
diff --git a/ceee/firefox/content/options.xul b/ceee/firefox/content/options.xul
new file mode 100644
index 0000000..655c6f8
--- /dev/null
+++ b/ceee/firefox/content/options.xul
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<!-- Copyright (c) 2010 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+
+ Defines the options dialog.
+ -->
+
+<!DOCTYPE prefwindow SYSTEM "chrome://ceee/locale/options.dtd">
+<prefwindow
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="ceee-options"
+ title="&options.title;"
+ ondialogaccept="onDialogAccept();">
+ <script>
+ <![CDATA[
+ function onDialogAccept() {
+ var prefs = Components.classes['@mozilla.org/preferences-service;1']
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch('capability.policy.default.checkloaduri.');
+ if (prefs) {
+ var debugging = document.getElementById('ceee-options-debug-bool');
+ var enableDebugging = debugging && debugging.checked;
+ if (enableDebugging) {
+ // Allowing all web pages to load file:/// URLs is a bad idea, and
+ // this state should not be left on all the time. This is a necessary
+ // evil for now because of the way content scripts are injected into
+ // the page (for debugging) via file:/// URL <script> elements, but
+ // we may need to look at this again.
+ prefs.setCharPref('enabled', 'allAccess');
+ } else {
+ prefs.setCharPref('enabled', 'noAccess');
+ }
+ }
+ return true;
+ };
+ ]]>
+ </script>
+ <prefpane id="pane1" label="&options.title;">
+ <!-- This section maps preferences to their type and name in the
+ about:config page. It also assigns an id so that the preference can
+ be associated with a UI element below. Note that nothing inside
+ the preferences element defines any UI at all.
+
+ If a preference should be a unicode string, use type="unichar" instead
+ -->
+ <preferences>
+ <preference
+ id="ceee-options-url"
+ name="extensions.ceee.url"
+ type="string"/>
+ <preference
+ id="ceee-options-path"
+ name="extensions.ceee.path"
+ type="string"/>
+ <preference
+ id="ceee-options-debug"
+ name="extensions.ceee.debug"
+ type="bool"/>
+ </preferences>
+
+ <!-- This section defines the actual UI for the options dialog. -->
+ <label
+ accesskey="&options.path.accesskey;"
+ control="ceee-options-path-text">
+ &options.path.label;
+ </label>
+ <textbox
+ id="ceee-options-path-text"
+ preference="ceee-options-path"
+ size="80"/>
+
+ <separator/>
+ <label
+ accesskey="&options.url.accesskey;"
+ control="ceee-options-url-text">
+ &options.url.label;
+ </label>
+ <textbox
+ id="ceee-options-url-text"
+ preference="ceee-options-url"
+ size="80"/>
+
+ <separator/>
+ <checkbox
+ id="ceee-options-debug-bool"
+ label="&options.debug.label;"
+ preference="ceee-options-debug"/>
+
+ <separator/>
+ <description><html:b>&options.restart.text;</html:b></description>
+ </prefpane>
+</prefwindow>
diff --git a/ceee/firefox/content/overlay.js b/ceee/firefox/content/overlay.js
new file mode 100644
index 0000000..482f99d
--- /dev/null
+++ b/ceee/firefox/content/overlay.js
@@ -0,0 +1,964 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains the main code for the CEEE Firefox
+ * add-on. This add-on mostly implements the Google Chrome extension APIs in
+ * Firefox.
+ *
+ * The CEEE extension is loaded into a ChromeFrame object (see the
+ * html:embed element in overlay.xul) and catches Chrome extension API requests
+ * using the ChromeFrame postMessage support.
+ *
+ * This add-on runs with privilege in Firefox, but the extension running inside
+ * the ChromeFrame is considered 'unsafe' code. Precautions are taken in the
+ * code to make sure that unsafe code is never executed with the privilege
+ * of the extension. In practice though, the extension running inside Chrome
+ * Frame is trusted code since there are mechanisms outside of this file that
+ * will ensure this.
+ *
+ * @supported Firefox 3.x
+ */
+
+// Commenting out anonymous function wrapper for this whole file to allow for
+// unit testing this code.
+// (function(){
+
+/**
+ * Imports the symbol CEEE_globals into this scope.
+ */
+Components.utils['import']('resource://modules/global.js', window);
+if (!CEEE_globals)
+ Components.utils.reportError('CEEE: *** globals module not imported');
+if (!CEEE_mozilla_windows)
+ Components.utils.reportError('CEEE: *** mozilla windows module not imported');
+if (!CEEE_mozilla_tabs)
+ Components.utils.reportError('CEEE: *** mozilla tabs module not imported');
+
+// Log from where we are running.
+var ffCeeeLoc = Components.classes['@mozilla.org/extensions/manager;1']
+ .getService(Components.interfaces.nsIExtensionManager)
+ .getInstallLocation(CEEE_globals.ADDON_ID)
+ .getItemLocation(CEEE_globals.ADDON_ID);
+Application.console.log('CEEE: running from ' + ffCeeeLoc.path);
+
+/**
+ * Constructor for the object that implements the CEEE API.
+ * @constructor
+ */
+function CEEE_Class() {
+ /** @const */ this.TAB_STATUS_LOADING = 'loading';
+ /** @const */ this.TAB_STATUS_COMPLETE = 'complete';
+
+ // Internal constants used by implementation.
+ /** @const */ this.API_EVENT_NAME_ = 'ceee-dom-api';
+ /** @const */ this.API_ELEMENT_NAME_ = 'ceee-api-element';
+ /** @const */ this.DATA_ATTRIBUTE_NAME_ = 'ceee-event-data';
+ /** @const */ this.RETURN_ATTRIBUTE_NAME_ = 'ceee-event-return';
+ /** @const */ this.ORIGIN_EXTENSION_ = '__priv_xtapi';
+
+ /**
+ * When true, this allows content scripts to be debugged using Firebug.
+ */
+ this.contentScriptDebugging = CEEE_globals.getBoolPreference('debug');
+
+ /**
+ * The dispatch table for the UI/user context API.
+ * @type {Object}
+ * @private
+ */
+ this.dispatch_ = {};
+
+ /**
+ * The dispatch table for the DOM context API.
+ * @type {Object}
+ * @private
+ */
+ this.dom_dispatch_ = {};
+
+ /**
+ * Reference to the API module implementing the CEEE windows API.
+ * @type {Object}
+ * @private
+ */
+ this.windowModule_ = null;
+
+ /**
+ * Reference to the API module implementing the CEEE tabs API.
+ * @type {Object}
+ * @private
+ */
+ this.tabsModule_ = null;
+
+ /**
+ * Reference to the API module implementing the CEEE cookies API.
+ * @type {Object}
+ * @private
+ */
+ this.cookiesModule_ = null;
+
+ /**
+ * Reference to the API module implementing the CEEE sidebar API.
+ * @type {Object}
+ * @private
+ */
+ this.sidebarModule_ = null;
+
+ /**
+ * Reference to the API module implementing the CEEE user scripts API.
+ * @type {Object}
+ * @private
+ */
+ this.userscriptsModule_ = null;
+
+ /**
+ * Collection of functions to run once the extensions have been fully
+ * initialized. Maps a content window to a map of description-to-function.
+ * @type {Object}
+ * @private
+ */
+ this.runWhenExtensionsInited_ = {};
+};
+
+/**
+ * Log an informational message to the Firefox error console.
+ * @public
+ */
+CEEE_Class.prototype.logInfo = function(msg) {
+ dump('[CEEE] ' + msg + '\n');
+ Application.console.log('CEEE: ' + msg);
+};
+
+/**
+ * Log an error message to the Firefox error console.
+ * @public
+ */
+CEEE_Class.prototype.logError = function(msg) {
+ var functionName = CEEE_Class.prototype.logError.caller.name + ':: ';
+ dump('[CEEE] *** ' + functionName + msg + '\n');
+ Components.utils.reportError('CEEE: *** ' + functionName + msg);
+};
+
+/**
+ * Called once the CEEE host document is fully loaded in a top-level
+ * firefox window. This will be called for each top-level window.
+ * @private
+ */
+CEEE_Class.prototype.onLoad_ = function() {
+ // Initialize all of the imported API modules.
+ this.windowModule_ = CEEE_initialize_windows(this);
+ this.tabsModule_ = CEEE_initialize_tabs(this);
+ this.cookiesModule_ = CEEE_initialize_cookies(this);
+
+ // TODO(twiz@chromium.org) : Re-enable bookmarks support once the
+ // Chrome Frame message passing system is more stable when a pop-up
+ // is present. bb2279154.
+ //CEEE_initialize_bookmarks(this);
+
+ this.userscriptsModule_ = new CEEE_UserScriptManager(this);
+ this.infobarsModule_ = CEEE_initialize_infobars(this,
+ (/**@type {!Object}*/this.windowModule_));
+
+ this.createChromeFrame_();
+
+ // NOTE: this function must be called after createChromeFrame_() in order to
+ // make sure that the chrome process started uses the correct command line
+ // arguments.
+ this.sidebarModule_ = CEEE_initialize_sidebar(this);
+
+ // TODO(joi@chromium.org) We used to have functionality here to show
+ // an error in case no extension was specified; this needs to be
+ // done by putting some HTML into CF now.
+
+ // Make a list of all registered functions - those are the ones we
+ // wish to overtake in case our CF becomes the new "master" CF, the others
+ // we will leave with Chrome.
+ var functions = [];
+ for (var key in this.dispatch_) {
+ functions.push(key);
+ }
+ var csFunctions = functions.join(',');
+ CEEE_globals.masterCf.init(csFunctions, this.logInfo);
+
+ var impl = this;
+
+ window.addEventListener('focus', function() {impl.onFocus_();},
+ false);
+ window.addEventListener('unload', function() {impl.onUnload_();},
+ false);
+
+ // TODO(rogerta@chromium.org): after setting the src property of
+ // ChromeFrame, it was asked in a code review whether the "load"
+ // event could race with the javascript below which sets the
+ // onCfReady_ function, or any functions that it calls directly or
+ // indirectly. For example, is it possible for ChromeFrame to fire
+ // the "load" event before userscripts_api.js runs, which sets the
+ // openPendingChannels property?
+ //
+ // From my experience with Firefox 3.0.x, this has not happened. The xul
+ // file loads the javascript files in the order specified, and
+ // userscripts_api.js always loads after this file. If this does end up
+ // being a problem, the solution would be create a new javascript file that
+ // is called *after* all the other javascript files that set all properties,
+ // that simply adds onLoad_ as a listener to the chrome window (i.e. the
+ // last executable line in this file).
+
+ // Listen for DOMContentLoaded in order to hook the windows when needed.
+ // No need to listen for DOMFrameContentLoaded, because this is generated
+ // for the <iframe> element in the parent document. We still get
+ // DOMContentLoaded for the actual document object even when a frame is
+ // embedded.
+ var ac = document.getElementById('appcontent');
+ ac.addEventListener('DOMContentLoaded', function (evt) {
+ impl.onDomContentLoaded_(evt.target);
+ }, false);
+
+ // Listen for tab events.
+ var mainBrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ mainBrowser.addEventListener('load', function(evt) {
+ impl.onContentLoaded_(evt.target);
+ }, true);
+
+ // For each tab already created, make to inject all required user scripts
+ // once the extension get loaded.
+ var tabCount = mainBrowser.tabContainer.itemCount;
+ for (var i = 0; i < tabCount; ++i) {
+ var doc = mainBrowser.getBrowserAtIndex(i).contentDocument;
+ impl.onDomContentLoaded_(doc);
+ impl.onContentLoaded_(doc);
+ }
+
+ // Notify the tabs module that the page has loaded.
+ this.tabsModule_.onLoad();
+
+ this.cookiesModule_.onLoad();
+ this.sidebarModule_.onLoad();
+
+ this.windowModule_.onWindowOpened_(window);
+ this.logInfo('onLoad_: done');
+};
+
+/**
+ * Adds a function to a collection of functions to execute once extensions
+ * have been initialized.
+ * @param w {Object} Content window to run in.
+ * @param desc {string} Description of function.
+ * @param f {function()} Function to defer.
+ */
+CEEE_Class.prototype.runWhenExtensionsInited = function(w, desc, f) {
+ this.logInfo('Deferring "' + desc + '" for window=' + w.location.href);
+ var map = this.runWhenExtensionsInited_[w];
+ if (!map) {
+ map = {};
+ this.runWhenExtensionsInited_[w] = map;
+ }
+ map[desc] = f;
+};
+
+/**
+ * Called once we know which extensions are loaded.
+ * @param extensions {Array.<string>} Base directories (paths, not
+ * File objects) of extensions loaded.
+ */
+CEEE_Class.prototype.initExtensions = function(extensions) {
+ // Initialize only once.
+ if (!this.toolstripUrl_ && extensions.length > 0) {
+ var baseDir = Components.classes['@mozilla.org/file/local;1']
+ .getService(Components.interfaces.nsILocalFile);
+ baseDir.initWithPath(extensions[0]);
+
+ // Will set attributes read from the manifest file.
+ // TODO(joi@chromium.org) Generalize to multiple extensions.
+ this.loadToolstripManifest(baseDir);
+ if (!this.toolstripUrl_) {
+ this.logInfo('Apparently, no extensions are loaded.');
+ } else {
+ this.toolstripDir_ = baseDir;
+
+ // Inject user scripts for pages loaded before we knew which
+ // extensions were installed. Make sure to delete all array elements
+ // and clear the array so that we don't leak references that may be
+ // held by the closures in the array.
+ for (var w in this.runWhenExtensionsInited_) {
+ var map = this.runWhenExtensionsInited_[w];
+ for (var desc in map) {
+ var windowName = (w && w.location && w.location.href) || '?';
+ this.logInfo('Running "' + desc + '" in window=' + windowName);
+ map[desc]();
+ delete map[desc];
+ }
+ delete this.runWhenExtensionsInited_[w];
+ }
+
+ this.runWhenExtensionsInited_ = {};
+ }
+ }
+}
+
+/**
+ * Create the ChromeFrame element that goes into the toolstrip.
+ * @private
+ */
+CEEE_Class.prototype.createChromeFrame_ = function() {
+ var impl = this;
+ var onCfReady = function() {impl.onCfReady_();};
+ var onCfMessage = function(evt, target) {impl.onCfMessage_(evt, target);};
+
+ this.cfHelper_ = new CEEE_CfHelper(this);
+ var parent = document.getElementById('ceee-frame-deck');
+ var cf = this.cfHelper_.create(parent, this.cfHelper_.CHROME_FRAME_ID,
+ onCfReady, onCfMessage);
+};
+
+/** Returns a helper object for working with ChromeFrame. */
+CEEE_Class.prototype.getCfHelper = function() {
+ return this.cfHelper_;
+};
+
+/** Returns a helper object for working with windows. */
+CEEE_Class.prototype.getWindowModule = function() {
+ return this.windowModule_;
+};
+
+/** Returns a helper object for working with tabs. */
+CEEE_Class.prototype.getTabModule = function() {
+ return this.tabsModule_;
+};
+
+/** Returns a helper object for working with cookies. */
+CEEE_Class.prototype.getCookieModule = function() {
+ return this.cookiesModule_;
+};
+
+/** Returns a helper object for working with sidebar. */
+CEEE_Class.prototype.getSidebarModule = function() {
+ return this.sidebarModule_;
+};
+
+/** Returns a helper object for working with user scripts. */
+CEEE_Class.prototype.getUserScriptsModule = function() {
+ return this.userscriptsModule_;
+};
+
+/**
+ * Called when the CEEE host window gets focus.
+ * @private
+ */
+CEEE_Class.prototype.onFocus_ = function() {
+ this.windowModule_.onWindowFocused_(window);
+};
+
+/**
+ * Called when the CEEE host document is unloaded. This will be called for
+ * each top-level window.
+ * @private
+ */
+CEEE_Class.prototype.onUnload_ = function() {
+ CEEE_globals.masterCf.onChromeFrameGoing(this.cfHelper_.get(),
+ this.logInfo);
+ this.windowModule_.onWindowRemoved_(window);
+ this.cookiesModule_.onUnload();
+ this.sidebarModule_.onUnload();
+ this.logInfo('onUnload_: done');
+};
+
+/**
+ * Called when the page inside ChromeFrame is loaded and ready. Before this
+ * call, we cannot assume that ChromeFrame is ready to accept any postMessage
+ * calls.
+ * @private
+ */
+CEEE_Class.prototype.onCfReady_ = function() {
+ this.logInfo('onCfReady_: done');
+};
+
+/**
+ * Called when the main html content of a document is loaded, but before any
+ * referenced resources, like images and so on, are loaded. This corresponds
+ * to the DOMContentLoaded event in Firefox.
+ *
+ * @param doc The document of the page that fired the event.
+ * @private
+ */
+CEEE_Class.prototype.onDomContentLoaded_ = function(doc) {
+ var impl = this;
+
+ // The target of the event is the document being loaded. The defaultView
+ // points to the window of this document. Remember that this window has
+ // been wrapped to make it safe.
+ var w = doc.defaultView;
+ if (w) {
+ w.addEventListener(this.API_EVENT_NAME_, function(evt2) {
+ impl.onDomApiCall_(w, evt2);
+ }, false);
+
+ // Attach a handler for onHashChange so that we can fire tab status changes
+ // when that particular event occurs.
+ w.addEventListener('hashchange', function() {
+ impl.onHashChange_(doc);
+ }, false);
+
+ // TODO(rogerta@chromium.org): I hook unload in order to perform
+ // cleanup for this content window, specifically to cleanup data
+ // for the ports. However, chrome extensions allow user scripts
+ // to post messages during unload, and this cleanup causes those
+ // messages to be lost. I need to find another event that
+ // guarantees to fire after all unload listeners to perform the
+ // cleanup.
+ w.addEventListener('unload', function() {
+ impl.userscriptsModule_.onContentUnloaded(w);
+ impl.logInfo('onContentUnloaded_: done');
+ }, false);
+
+ if (this.toolstripDir_) {
+ // Run all user scripts that match this page.
+ this.userscriptsModule_.runUserScripts(w);
+ } else {
+ this.runWhenExtensionsInited(w, 'runUserScripts', function() {
+ impl.userscriptsModule_.runUserScripts(w);
+ });
+ }
+
+ // TODO(rogerta@chromium.org): It would be cool to be able detect
+ // if the content is for an embedded frame, but I have not figured
+ // out how to do that. It seems that in some cases we get
+ // DOMContentLoaded even for embedded frames. Not a big deal
+ // since onTabStatusChanged_() handles it correctly.
+ this.tabsModule_.onTabStatusChanged(doc, impl.TAB_STATUS_LOADING);
+ } else {
+ this.logError('onDomContentLoaded_: no default view for document');
+ }
+
+ this.logInfo('onDomContentLoaded_: done loc=' + doc.location);
+};
+
+/**
+ * Called when the main html content of a document *and* all its referenced
+ * resources, like images and so on, are loaded. This corresponds
+ * to the "onload" event in Firefox, and always happens after the
+ * DOMContentLoaded event.
+ *
+ * @param doc The document of the page that fired the event.
+ * @private
+ */
+CEEE_Class.prototype.onContentLoaded_ = function(doc) {
+ // This event is also fired for images, but we don't want those. Filter out
+ // the document events as described in
+ // https://developer.mozilla.org/en/Code_snippets/On_page_load.
+ if (doc.nodeName == '#document') {
+ // We only want to generate tab notifications for top-level documents,
+ // not internal frames.
+ if (doc.defaultView.frameElement) {
+ this.logInfo('onContentLoaded_(frame): done loc=' + doc.location);
+ } else {
+ this.tabsModule_.onTabStatusChanged(doc, this.TAB_STATUS_COMPLETE);
+ this.logInfo('onContentLoaded_: done loc=' + doc.location);
+ }
+ }
+};
+
+/**
+ * Called when a hash change occurs in a given document.
+ *
+ * @param doc The document of the page that fired the event.
+ */
+CEEE_Class.prototype.onHashChange_ = function(doc) {
+ this.tabsModule_.onTabStatusChanged(doc, this.TAB_STATUS_LOADING);
+ this.tabsModule_.onTabStatusChanged(doc, this.TAB_STATUS_COMPLETE);
+};
+
+/**
+ * @param path {string} A filesystem path or the empty string.
+ * @return True if the specified path is a .crx (or empty).
+ */
+CEEE_Class.prototype.isCrx = function(path) {
+ return (path == '' || /\.crx$/.test(path));
+}
+
+/**
+ * Get the path of the .crx file to install.
+ *
+ * @return A File object that represents a .crx file to install with Chrome.
+ */
+CEEE_Class.prototype.getCrxToInstall = function() {
+ if (!this.crxToInstall_) {
+ // See if the user is overriding the location of the .crx file.
+ var crx;
+ var path = CEEE_globals.getCharPreference('path');
+ if (path) {
+ this.logInfo('getCrxToInstall: path preference exists, try it');
+ crx = Components.classes['@mozilla.org/file/local;1']
+ .getService(Components.interfaces.nsILocalFile);
+ try {
+ crx.initWithPath(path);
+
+ // Make sure the file we read is valid, so that otherwise we
+ // can use the first file found in the default directory. We
+ // allow paths from preferences to be paths to either a .crx
+ // file or an exploded directory, but only search for .crx
+ // files when scanning the directory below.
+ if (!crx.exists() || (this.isCrx(path) && crx.isDirectory()) ||
+ (!this.isCrx(path) && !crx.isDirectory())) {
+ this.logError('getCrxToInstall: "' + path +
+ '" does not exist or is of the wrong type');
+ crx = null;
+ }
+ } catch(e) {
+ this.logError('getCrxToInstall: ' +
+ 'extensions.ceee.path must be a valid absolute path');
+ crx = null;
+ }
+ }
+
+ // The user did not override the path to an extension to install.
+ // Get the default from a well-known directory.
+ if (!crx) {
+ var env = Components.classes['@mozilla.org/process/environment;1'];
+ var userEnv = env.getService(Components.interfaces['nsIEnvironment']);
+ var PFILES32 = 'ProgramFiles(x86)';
+ var PFILES = 'ProgramFiles';
+ var sysPfiles = (userEnv.exists(PFILES32) && userEnv.get(PFILES32)) ||
+ (userEnv.exists(PFILES) && userEnv.get(PFILES)) ||
+ 'C:\Program Files';
+ path = sysPfiles + '\\Google\\CEEE\\Extensions';
+ this.logInfo('getCrxToInstall: trying: ' + path);
+
+ var dir = Components.classes['@mozilla.org/file/local;1']
+ .getService(Components.interfaces.nsILocalFile);
+ dir.initWithPath(path);
+
+ // Find the first file with a .crx extension. Failing that, find the
+ // first directory.
+ // TODO(rogerta@chromium.org): eventually we want to support
+ // loading any number of extensions.
+ if (dir && dir.exists() && dir.isDirectory()) {
+ var e = dir.directoryEntries;
+ while (e.hasMoreElements()) {
+ var temp = e.getNext().QueryInterface(Components.interfaces.nsIFile);
+ if (temp && !temp.isDirectory() && /\.crx$/.test(temp.path)) {
+ crx = temp;
+ break;
+ }
+ }
+
+ // Fall back to using the first directory.
+ if (!crx) {
+ e = dir.directoryEntries;
+ while (e.hasMoreElements()) {
+ var temp = e.getNext().QueryInterface(
+ Components.interfaces.nsIFile);
+ if (temp && temp.isDirectory()) {
+ crx = temp;
+ break;
+ }
+ }
+ }
+
+ if (!crx)
+ crx = null;
+ } else {
+ this.logError('Not a directory: ' + path);
+ }
+ }
+
+ // Make sure the .crx or exploded directory is valid.
+ if (!crx) {
+ this.logInfo('getCrxToInstall: crx is undefined');
+ } else if (!crx.exists()) {
+ this.logError('getCrxToInstall: "' + crx.path +
+ '" does not exist');
+ crx = null;
+ } else {
+ this.logInfo('getCrxToInstall: extension found at: ' + crx.path);
+ }
+
+ this.crxToInstall_ = crx;
+ }
+
+ // NOTE: its important to return a clone in case the caller modifies the
+ // file object it receives.
+ return this.crxToInstall_ ? this.crxToInstall_.clone() : null;
+};
+
+/**
+ * Get a File object for the manifest of the Chrome extension registered.
+ *
+ * @param baseDir {File} A File object representing the base dir of
+ * the extension.
+ * @return A File object that represents the Chrome extension manifest.
+ * @private
+ */
+CEEE_Class.prototype.getToolstripManifestFile_ = function(baseDir) {
+ if (baseDir) {
+ baseDir = baseDir.clone();
+ baseDir.append('manifest.json');
+ if (!baseDir.exists() || !baseDir.isFile())
+ baseDir = null;
+ }
+
+ return baseDir;
+};
+
+/**
+ * Computes the extension id from the given key.
+ *
+ * @private
+ */
+CEEE_Class.prototype.computeExtensionId_ = function(key) {
+ if (key.length == 0)
+ return null;
+
+ // nsICryptoHash expects an array of base64 encoded bytes
+ var encodedKey = window.atob(key);
+ var encodedKeyArray = [];
+ for (var i = 0; i < encodedKey.length; ++i)
+ encodedKeyArray.push(encodedKey.charCodeAt(i));
+
+ var cryptoHash = Components.classes['@mozilla.org/security/hash;1']
+ .createInstance(Components.interfaces.nsICryptoHash);
+ cryptoHash.init(cryptoHash.SHA256);
+ cryptoHash.update(encodedKeyArray, encodedKeyArray.length);
+ var hashedKey = cryptoHash.finish(false);
+
+ // Then the Id is constructed by offsetting the char 'a' with the nibble
+ // values of the first 16 bytes of the Hex decoding of the hashed key.
+ var base = 'a'.charCodeAt(0);
+ var extensionId = '';
+ for (var i = 0; i < 16; ++i) {
+ var hexValue = ('0' + hashedKey.charCodeAt(i).toString(16)).slice(-2);
+ extensionId += String.fromCharCode(base + parseInt(hexValue[0], 16));
+ extensionId += String.fromCharCode(base + parseInt(hexValue[1], 16));
+ }
+ return extensionId;
+};
+
+/**
+ * Load the toolstrip manifest file info and set it as attributes.
+ *
+ * @public
+ */
+CEEE_Class.prototype.loadToolstripManifest = function(baseDir) {
+ if (!CEEE_json)
+ return;
+
+ var manifestFile = this.getToolstripManifestFile_(baseDir);
+ if (!manifestFile) {
+ this.logError('loadToolstripManifest: no manifest');
+ return;
+ }
+
+ this.logInfo(
+ 'loadToolstripManifest: loading manifest: ' + manifestFile.path);
+
+ try {
+ var stream = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ stream.init(manifestFile, -1, 0, 0);
+ var manifestData = CEEE_json.decodeFromStream(stream, -1);
+ } catch (ex) {
+ this.logError('loadToolstripManifest: error reading manifest: ' + ex);
+ return;
+ } finally {
+ if (stream)
+ stream.close();
+ }
+
+ if (!manifestData) {
+ this.logError('loadToolstripManifest: no manifest data');
+ return;
+ }
+
+ if (!manifestData.key) {
+ this.logError('loadToolstripManifest: key missing from manifest');
+ return;
+ }
+
+ if (manifestData.toolstrips && manifestData.toolstrips.length > 0) {
+ this.extensionId_ = this.computeExtensionId_(manifestData.key);
+ if (this.extensionId_) {
+ // TODO(mad@chromium.org): For now we only load the first one we find,
+ // we may want to stack them at one point...
+ this.toolstripUrl_ = 'chrome-extension://' + this.extensionId_ + '/' +
+ manifestData.toolstrips[0];
+ }
+ }
+
+ // Extract all content scripts from the manifest. This include javascript
+ // code to run as well as CSS scripts to load.
+ this.userscriptsModule_.loadUserScripts(manifestData);
+
+ // Add more code here to extract more stuff from the manifest,
+ // user scripts for example.
+};
+
+/**
+ * Get the Chrome extension Id of the toolstrip.
+ *
+ * @return A string representing the Chrome extension Id of the toolstrip.
+ * @public
+ */
+CEEE_Class.prototype.getToolstripExtensionId = function() {
+ return this.extensionId_;
+};
+
+/**
+ * Get the base directory of our one and only extension. Valid only
+ * after the extensions list has been loaded.
+ *
+ * @return A File object for the base directory of the extension.
+ */
+CEEE_Class.prototype.getToolstripDir = function() {
+ return this.toolstripDir_.clone();
+};
+
+/**
+ * Get the URL of the Toolstrip.
+ *
+ * @return A string that represents the URL of the HTML file to use for the
+ * toolstrip.
+ */
+CEEE_Class.prototype.getToolstripUrl = function() {
+ // If the URL is overridden in the prefences, then use that.
+ var url = CEEE_globals.getCharPreference('url');
+
+ // If no override found, use the toolstrip's default URL, if any.
+ if ((!url || 0 == url.length) && this.toolstripUrl_) {
+ url = this.toolstripUrl_;
+ }
+
+ return url;
+};
+
+/**
+ * Open a stream for reading from the given file.
+ *
+ * @param {!nsILocalFile} file The file to open for reading.
+ * @return An nsIConverterInputStream that can be read from.
+ * @public
+ */
+CEEE_Class.prototype.openFileStream = function(file) {
+ var fstream = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ fstream.init(file, -1, 0, 0);
+
+ // Make sure to specify the size of the file, or the read will be truncated
+ // the to default size, which is 8kb as of this writing.
+ var stream =
+ Components.classes['@mozilla.org/intl/converter-input-stream;1'].
+ createInstance(Components.interfaces.nsIConverterInputStream);
+ stream.init(fstream, 'UTF-8', file.fileSize, 0);
+
+ return stream;
+};
+
+/**
+ * Called in the following cases:
+ * - the extension running inside ChromeFrame makes a Google Chrome extension
+ * API call. In this case, the origin is '__priv_xtapi' and the target is
+ * '__priv_xtreq'
+ * - a post message request to a given port from the extenstion. In this case,
+ * the origin is '__priv_xtapi' and the target is '__priv_prtreq'.
+ * - a response from a post message request to the extenstion. In this case,
+ * the origin is '__priv_xtapi' and the target is '__priv_prtres'.
+ *
+ * Google Chrome extension API calls
+ * ---------------------------------
+ * This function is passed an object whose data property is a json-encoded
+ * string describing the API call that was made. The json encoding is defined
+ * as follows:
+ *
+ * {
+ * 'name': '<api-function-name>',
+ * 'args': '[<argument-0>, <argument-1>, ...]',
+ * 'rqid': <id>
+ * }
+ *
+ * Note that the args property is itself a json-encoded array of the APIs
+ * arguments. If the API has no arguments then args is 'null'.
+ *
+ * If the caller expects a response, then the rqid value must be
+ * present in that response. A response to an API call is done by
+ * json-encoding a response object and calling postMesasge() on the ChromeFrame
+ * instance. The response object is defined as follows:
+ *
+ * {
+ * 'res': '<return-value>',
+ * 'rqid': <id>
+ * }
+ *
+ * The <return-value> is itself a json-encoded string of whatever the API
+ * is supposed to return.
+ *
+ * Port Request and Response
+ * -------------------------
+ * For a description of the format of the evt argument refer to the docs for
+ * onPortEvent().
+ *
+ * @param {!Object} evt API event fired from the extension.
+ * @param {!string} target The target of the message.
+ * @private
+ */
+CEEE_Class.prototype.onCfMessage_ = function(evt, target) {
+ // Do some sanity checking on the event that is passed to us.
+ if (!evt || !CEEE_json)
+ return;
+
+ // if this is a response to a port request, give it special handling.
+ if (target == this.cfHelper_.TARGET_PORT_REQUEST ||
+ target == this.cfHelper_.TARGET_PORT_RESPONSE) {
+ this.userscriptsModule_.onPortEvent(evt);
+ return;
+ }
+
+ var data = CEEE_json.decode(evt.data);
+ if (!data) {
+ this.logError('onCfMessage_: no data');
+ return;
+ }
+
+ var cmd = data.name;
+ if (!cmd) {
+ this.logError('onCfMessage_: no name');
+ return;
+ }
+
+ if ('rqid' in data) {
+ this.logInfo('onCfMessage_: cmd=' + cmd + ' rqid=' + data.rqid);
+ }
+
+ // All is good so far, so try to make the appropriate call.
+ var value;
+ var err;
+ var handler = this.dispatch_[cmd];
+ if (typeof handler == 'function') {
+ try {
+ value = handler.call(this, cmd, data);
+ } catch(ex) {
+ this.logError('onCfMessage_: exception thrown by handler cmd=' + cmd +
+ ' err=' + ex);
+ err = ex.message;
+ value = null;
+ }
+ } else {
+ this.logError('onCfMessage_: dispatch entry is not a function');
+ }
+
+ // If the caller expects a response, then send it now.
+ if ('rqid' in data) {
+ var r = {};
+ r.rqid = data.rqid;
+ r.res = typeof value != 'undefined' ? CEEE_json.encode(value) : '';
+ if (err)
+ r.err = err;
+
+ this.cfHelper_.postMessage(CEEE_json.encode(r),
+ this.cfHelper_.TARGET_API_RESPONSE);
+ }
+};
+
+/**
+ * Called when code in the DOM context makes a CEEE API call. DOM context
+ * refers to code running in the context of a regular web page loaded into one
+ * of the Firefox main tabs.
+ *
+ * @param {!Object} w Window of the DOM context.
+ * @param {!Object} evt A DOM event fired from plugin.
+ * @private
+ */
+CEEE_Class.prototype.onDomApiCall_ = function(w, evt) {
+ // Do some sanity checking on the event that is passed to us.
+ if (!evt || !evt.target || !CEEE_json)
+ return;
+
+ var s = evt.target.getAttribute(this.DATA_ATTRIBUTE_NAME_);
+ var data = CEEE_json.decode(s);
+ if (!data) {
+ this.logError('onDomApiCall_: no data');
+ return;
+ }
+
+ var cmd = data.name;
+ if (!cmd) {
+ this.logError('onDomApiCall_: no name');
+ return;
+ }
+
+ var handler = this.dom_dispatch_[cmd];
+ var value;
+ if (typeof handler == 'function') {
+ try {
+ value = handler.call(this, w, cmd, data);
+ } catch(ex) {
+ this.logError('onDomApiCall_: exception thrown by handler cmd=' + cmd +
+ ' err=' + ex);
+ value = '';
+ }
+ } else {
+ this.logError('onDomApiCall_: no dom_dispatch function for cmd=' + cmd);
+ }
+
+ if (value) {
+ // The built-in Firefox json encoding does not work with primitive types,
+ // so check for that explicitly and handle correctly.
+ var s =
+ typeof value == 'object' ? CEEE_json.encode(value) : value;
+ evt.target.setAttribute(this.RETURN_ATTRIBUTE_NAME_, s);
+ }
+};
+
+/**
+ * Assign a handler associated with the string given in command.
+ * @param {string} command
+ * @param {Object} object
+ * @param {function(Object, Object):Object} handler
+ * @public
+ */
+CEEE_Class.prototype.registerExtensionHandler = function(command,
+ object,
+ handler) {
+ this.dispatch_[command] =
+ function() {return handler.apply(object, arguments);};
+}
+
+/**
+ * Assign a handler associated with the DOM event corresponding to the string
+ * given in command argument.
+ * @param {string} command
+ * @param {Object} object
+ * @param {function(Object, Object):Object} handler
+ * @public
+ */
+CEEE_Class.prototype.registerDomHandler = function(command, object, handler) {
+ this.dom_dispatch_[command] =
+ function() {return handler.apply(object, arguments);};
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Global CEEE initialization routine.
+ * @return {Object} A reference to the initialized CEEE instance.
+ * @public
+ */
+function CEEE_initialize() {
+ var instance = new CEEE_Class();
+
+ window.addEventListener('load', function() {instance.onLoad_();},
+ false);
+ instance.logInfo('Started');
+
+ if (instance.contentScriptDebugging)
+ instance.logInfo('Content script debugging is enabled');
+
+ return instance;
+}
+
+/**
+* Global reference to the CEEE instance.
+* @private
+*/
+var CEEE_instance_ = CEEE_initialize();
+
+// })();
diff --git a/ceee/firefox/content/overlay.xul b/ceee/firefox/content/overlay.xul
new file mode 100644
index 0000000..883d5ba
--- /dev/null
+++ b/ceee/firefox/content/overlay.xul
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://ceee/skin/overlay.css" type="text/css"?>
+<!-- Copyright (c) 2010 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+
+ Defines how the toolstrip is inserted into the firefox UI.
+ -->
+
+<!DOCTYPE overlay SYSTEM "chrome://ceee/locale/ceee.dtd">
+
+<overlay id="ceee-overlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <!-- TODO(rogerta@chromium.org): js-coverage.js is only needed for
+ coverage, should be removed before shipping. -->
+ <script id="ceee-coverage-js" src="js-coverage.js"/>
+ <script id="ceee-js" src="overlay.js"/>
+ <script id="cf-helper-js" src="cf.js"/>
+ <script id="ceee-bookmarks-api-js" src="bookmarks_api.js"/>
+ <script id="ceee-infobars-api-js" src="infobars_api.js"/>
+ <script id="ceee-window-api-js" src="window_api.js"/>
+ <script id="ceee-tab-api-js" src="tab_api.js"/>
+ <script id="ceee-userscript-api-js" src="userscript_api.js"/>
+ <script id="ceee-cookie-api-js" src="cookie_api.js"/>
+ <script id="ceee-sidebar-api-js" src="sidebar_api.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="ceee-strings"
+ src="chrome://ceee/locale/ceee.properties"/>
+ </stringbundleset>
+
+ <!-- all toolstrips should be overlayed inside the toolbox element for
+ best compatibility. -->
+ <toolbox id="navigator-toolbox">
+ <!-- the hidden=false attribute makes sure that the toolbat will be visible
+ right after the extension is installed. The persist attribute lists
+ the names of all the attributes whose value should be persisted when
+ firefox is stopped and restarted. In this case, we want the hidden
+ attribute of the toolbar to persist according to the user's choice.
+ -->
+ <toolbar id="ceee-toolstrip" toolbarname="&ceee.toolstrip.label;"
+ accesskey="&ceee.toolstrip.accesskey;" class="chromeclass-toolstrip"
+ context="toolstrip-context-menu" hidden="false" persist="hidden">
+ <!-- the only type of UI control that can be directly a child of a
+ toolbar is a toolbarbutton. Any other type of control, like the
+ browser control I am using here, must be wrapped inside a
+ toolbaritem to appear correctly. -->
+ <toolbaritem id="ceee-browser-item" title="&ceee.browser.label;"
+ flex="1" tooltiptext="&ceee.browser.tooltip;">
+ <deck id="ceee-frame-deck" flex="1" selectedIndex="1">
+ <!-- The ChromeFrame will be placed as a child of this element
+ just before the following div element in the overlay.js file. -->
+ <div xmlns="http://www.w3.org/1999/xhtml"
+ style="background-color:white"/>
+ </deck>
+ </toolbaritem>
+ </toolbar>
+ </toolbox>
+
+ <hbox id="browser">
+ <splitter id="ceee-sidebar-splitter"
+ resizeafter="flex"
+ resizebefore="flex"
+ collapsed="true"/>
+ <vbox id="ceee-sidebar-container"
+ flex="1"
+ collapsed="true"
+ minwidth="150" width="200">
+ <label id="ceee-sidebar-label"
+ value="My title"
+ style="font-size:14pt;"/>
+ <!-- The ChromeFrame will be placed as a child of this element
+ just before the following div element in the overlay.js file. -->
+ </vbox>
+ </hbox>
+
+ <window id="main-window">
+ <vbox id="ceee-sidebar-icon-box">
+ <!-- The ChromeFrame will be placed as a child of this element
+ just before the following div element in the overlay.js file. -->
+ </vbox>
+ </window>
+
+</overlay>
diff --git a/ceee/firefox/content/sidebar_api.js b/ceee/firefox/content/sidebar_api.js
new file mode 100644
index 0000000..fa19eba
--- /dev/null
+++ b/ceee/firefox/content/sidebar_api.js
@@ -0,0 +1,691 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains the implementation of the Chrome extension
+ * sidebars API for the CEEE Firefox add-on. This file is loaded by the
+ * overlay.xul file, and requires that overlay.js has already been loaded.
+ * The overlay.xul file contains the necessary DOM structure for the sidebar
+ * implementation.
+ *
+ * This is a preliminary version, not all the functions implemented properly.
+ * - sidebar minitab icon is fixed;
+ * - badgeText is not used;
+ * - others...
+ * TODO(akoub@google.com): implement all the features.
+ *
+ * @supported Firefox 3.x
+ */
+
+/**
+ * Place-holder namespace-object for helper functions used in this file.
+ * @private
+ */
+var CEEE_sidebar_internal_ = CEEE_sidebar_internal_ || {
+ /** Reference to the instance object for the ceee toolstrip. @private */
+ ceeeInstance_: null,
+
+ /** Commands. */
+ /** @const */ CMD_SHOW: 'experimental.sidebar.show',
+ /** @const */ CMD_HIDE: 'experimental.sidebar.hide',
+ /** @const */ CMD_EXPAND: 'experimental.sidebar.expand',
+ /** @const */ CMD_COLLAPSE: 'experimental.sidebar.collapse',
+ /** @const */ CMD_NAVIGATE: 'experimental.sidebar.navigate',
+ /** @const */ CMD_GET_STATE: 'experimental.sidebar.getState',
+ /** @const */ CMD_SET_ICON: 'experimental.sidebar.setIcon',
+ /** @const */ CMD_SET_TITLE: 'experimental.sidebar.setTitle',
+ /** @const */ CMD_SET_BADGE_TEXT: 'experimental.sidebar.setBadgeText',
+
+ /** Tab attribute that contains all the information about sidebar state.
+ * @type {string}
+ * @private
+ */
+ /** @const */ SIDEBAR_INFO: 'ceee_sidebar',
+ /** @const */ TAB_ID: 'ceee_tabid',
+
+ /** Overlay elements IDs that represent the sidebar.
+ * None of the elements are visible in the 'hidden' state.
+ * Only ICON_BOX_ID and enclosed ICON_ID elements are visible in the 'shown'
+ * and 'active' states. SPLITTER_ID and CONTAINER_ID (with the enclosed
+ * BROWSER_ID and LABEL_ID) are visible in the 'active' state only.
+ * @type {string}
+ * @private
+ */
+ /** @const */ ICON_BOX_ID: 'ceee-sidebar-icon-box',
+ /** @const */ SPLITTER_ID: 'ceee-sidebar-splitter',
+ /** @const */ CONTAINER_ID: 'ceee-sidebar-container',
+ /** @const */ BROWSER_ID: 'ceee-sidebar-browser',
+ /** @const */ ICON_ID: 'ceee-sidebar-icon',
+ /** @const */ LABEL_ID: 'ceee-sidebar-label',
+
+ /** Sidebar states.
+ * No sidebar elements are visible in the 'hidden' state.
+ * Icon box and the icon are visible in the 'shown' state.
+ * Icon box, icon, and the sidebar content are visible in 'active' state.
+ * @type {string}
+ * @private
+ */
+ /** @const */ STATE_HIDDEN: 'hidden',
+ /** @const */ STATE_SHOWN: 'shown',
+ /** @const */ STATE_ACTIVE: 'active',
+
+ /** scrollbars widths.
+ * @type {number}
+ * @private
+ */
+ scrollWidth_: 17
+};
+
+/**
+ * Provides a CEEE Toolstrip extension Id.
+ * @return {string} CEEE Toolstrip extension Id.
+ * @private
+ */
+CEEE_sidebar_internal_.getExtensionId_ = function() {
+ return this.ceeeInstance_.getToolstripExtensionId();
+};
+
+/**
+ * Get a full URL for the given relative path. Assumed that the user
+ * path is relative to the Chrome Extension root.
+ *
+ * @param {!string} path Relative path of the user resource.
+ * @return {string} Absolute URL for the user resource.
+ * @private
+ */
+CEEE_sidebar_internal_.getResourceUrl_ = function(path) {
+ return 'chrome-extension://' + this.getExtensionId_() + '/' + path;
+};
+
+/**
+ * Creates a sidebar object that is linked to a tab and reflects its state.
+ * @private
+ */
+CEEE_sidebar_internal_.createNewSidebar_ = function() {
+ var sidebar = {
+ state: this.STATE_HIDDEN,
+ title: '',
+ badgeText: '',
+ url: 'about:blank',
+ icon: ''
+ };
+ return sidebar;
+};
+
+/**
+ * Shows the sidebar in its current state. Normally this function is called
+ * on a TabSelect event, but it is also called whenever the sidebar status
+ * is changed.
+ * @param {nsIDOMXULElement} tab XUL tab element to show/hide the sidebar with.
+ * @private
+ */
+CEEE_sidebar_internal_.showSidebar_ = function(tab) {
+ var ceee = this.ceeeInstance_;
+
+ var sidebar = tab[this.SIDEBAR_INFO];
+ if (!sidebar) {
+ ceee.logError('NO SIDEBAR INFO linked to the tab');
+ return;
+ }
+
+ ceee.logInfo('sidebar.showSidebar_ state = ' + sidebar.state +
+ '; tabId = ' + tab[this.TAB_ID] +
+ '; url = ' + sidebar.url +
+ '; title = ' + sidebar.title);
+
+ // Show/hide the sidebar elements and possibly fill in the contents.
+ var sidebarIconBox = document.getElementById(this.ICON_BOX_ID);
+ var sidebarSplitter = document.getElementById(this.SPLITTER_ID);
+ var sidebarContainer = document.getElementById(this.CONTAINER_ID);
+ var sidebarBrowser = document.getElementById(this.BROWSER_ID);
+ var sidebarIcon = document.getElementById(this.ICON_ID);
+ var sidebarLabel = document.getElementById(this.LABEL_ID);
+ if (this.STATE_SHOWN == sidebar.state) {
+ sidebarIconBox.style.display = 'block';
+ sidebarSplitter.setAttribute('collapsed', 'true');
+ sidebarContainer.setAttribute('collapsed', 'true');
+ } else if (this.STATE_ACTIVE == sidebar.state) {
+ sidebarIconBox.style.display = 'block';
+ sidebarSplitter.setAttribute('collapsed', 'false');
+ sidebarContainer.setAttribute('collapsed', 'false');
+ if (sidebarBrowser.src != sidebar.url) {
+ sidebarBrowser.src = sidebar.url;
+ }
+ sidebarLabel.setAttribute('value', sidebar.title);
+ } else {
+ // STATE_HIDDEN:
+ sidebarIconBox.style.display = 'none';
+ sidebarSplitter.setAttribute('collapsed', 'true');
+ sidebarContainer.setAttribute('collapsed', 'true');
+ }
+ this.onWindowResize_(null);
+};
+
+/**
+ * Routine initializaing the sidebar module during onLoad message from the
+ * browser. Performed for every browser window.
+ * @public
+ */
+CEEE_sidebar_internal_.onLoad = function() {
+ var ceee = this.ceeeInstance_;
+ ceee.logInfo('sidebar.onLoad');
+
+ // Add listeners to Tabs events.
+ var mainBrowser = document.getElementById(
+ CEEE_globals.MAIN_BROWSER_ID);
+ var tabContainer = mainBrowser.tabContainer;
+ var sidebar = this;
+ tabContainer.addEventListener('TabOpen',
+ function(evt) { sidebar.onTabOpen_(evt); },
+ false);
+ tabContainer.addEventListener('TabMove',
+ function(evt) { sidebar.onTabMove_(evt); },
+ false);
+ tabContainer.addEventListener('TabSelect',
+ function(evt) { sidebar.onTabSelect_(evt); },
+ false);
+ tabContainer.addEventListener('TabClose',
+ function(evt) { sidebar.onTabClose_(evt); },
+ false);
+
+ // Create a sidebar object for each open tab.
+ for (var i = 0; i < tabContainer.itemCount; ++i) {
+ var tab = tabContainer.getItemAtIndex(i);
+ tab[this.SIDEBAR_INFO] = this.createNewSidebar_();
+ if (tab.selected) {
+ this.showSidebar_(tab);
+ }
+ }
+
+ this.scrollWidth_ = this.getScrollbarsWidth_();
+
+ window.addEventListener(
+ 'resize', CEEE_globals.hitch(this.onWindowResize_, this), false);
+};
+
+/**
+ * Calculates current width of a scroll bar. The function makes it with
+ * direct modelling of an element in the browser's document.
+ * Does anybody know a better way?
+ * @return {number} Scrollbars width.
+ * @private
+ */
+CEEE_sidebar_internal_.getScrollbarsWidth_ = function() {
+ var doc = gBrowser.getBrowserForTab(gBrowser.selectedTab).contentDocument;
+
+ if (doc && doc.body) {
+ var outer = doc.createElement('div');
+ outer.style.position = 'fixed';
+ outer.style.left = '-1000px';
+ outer.style.top = '-1000px';
+ outer.style.width = '100px';
+ outer.style.height = '100px';
+ outer.style.overflowY = 'hidden';
+
+ var inner = doc.createElement('div');
+ inner.width = '100%';
+ inner.height = '200px';
+ outer.appendChild(inner);
+ doc.body.appendChild(outer);
+
+ var widthNoBar = inner.offsetWidth;
+ outer.style.overflowY = 'scroll';
+ var widthWithBar = inner.offsetWidth;
+ doc.body.removeChild(outer);
+
+ return widthNoBar - widthWithBar;
+ } else {
+ return 17;
+ }
+};
+
+/**
+ * Positioning sidebar icon on window resize.
+ * TODO(akoub@google.com): meet RTL languages layout.
+ * @param {nsIDOMEvent} evt Resize event.
+ */
+CEEE_sidebar_internal_.onWindowResize_ = function(evt) {
+ var iconbox = document.getElementById(this.ICON_BOX_ID);
+ if (iconbox.style.display == 'none') {
+ return;
+ }
+ var right = 0;
+ var bottom = 0;
+
+ // Place the icon over status-bar if visible
+ var statusBar = document.getElementById('status-bar');
+ var statusBarStyles = window.getComputedStyle(statusBar, '');
+ var statusBarVisible = statusBarStyles.display != 'none';
+ if (statusBarVisible) {
+ bottom += parseInt(statusBarStyles.height, 10);
+ }
+
+ // Place icon on the left of the sidebar if visible.
+ var splitter = document.getElementById(this.SPLITTER_ID);
+ var splitterVisible = splitter.getAttribute('collapsed') == 'false';
+ if (splitterVisible) {
+ var splitterWidth =
+ parseInt(window.getComputedStyle(splitter, '').width, 10) +
+ parseInt(window.getComputedStyle(splitter, '').borderLeftWidth, 10) +
+ parseInt(window.getComputedStyle(splitter, '').borderRightWidth, 10);
+ var browser = document.getElementById(this.CONTAINER_ID);
+ var browserWidth =
+ parseInt(window.getComputedStyle(browser, '').width, 10);
+ right += splitterWidth + browserWidth;
+ }
+
+ // Shift more to the left/top when scrollbars are visible.
+ var innerDoc =
+ gBrowser.getBrowserForTab(gBrowser.selectedTab).contentDocument;
+ var innerWindow = innerDoc && innerDoc.defaultView;
+ if (innerDoc.body) {
+ var innerStyles = innerWindow.getComputedStyle(innerDoc.body, '');
+ if (innerStyles.overflow == 'scroll' ||
+ innerStyles.overflowY == 'scroll' ||
+ innerStyles.overflow != 'hidden' &&
+ innerStyles.overflowY != 'hidden' &&
+ innerDoc.body.clientHeight < innerDoc.body.scrollHeight) {
+ right += this.scrollWidth_;
+ }
+ if (innerStyles.overflow == 'scroll' ||
+ innerStyles.overflowX == 'scroll' ||
+ innerStyles.overflow != 'hidden' &&
+ innerStyles.overflowX != 'hidden' &&
+ innerDoc.body.clientWidth < innerDoc.body.scrollWidth) {
+ bottom += this.scrollWidth_;
+ }
+ }
+
+ iconbox.style.right = right + 'px';
+ iconbox.style.bottom = bottom + 'px';
+ iconbox.style.zIndex = '25';
+};
+
+/**
+ * TabOpen event handler. Creates a sidebar status object and link it
+ * to a newly created tab.
+ * @param {nsIDOMEvent} evt Event object.
+ * @private
+ */
+CEEE_sidebar_internal_.onTabOpen_ = function(evt) {
+ var ceee = this.ceeeInstance_;
+ ceee.logInfo('sidebar.TabOpen');
+ var tab = evt.target;
+ tab[this.SIDEBAR_INFO] = this.createNewSidebar_();
+};
+
+/**
+ * TabClose event handler. NOTHING TO DO.
+ * @param {nsIDOMEvent} evt Event object.
+ * @private
+ */
+CEEE_sidebar_internal_.onTabClose_ = function(evt) {
+ var ceee = this.ceeeInstance_;
+ ceee.logInfo('sidebar.TabClose');
+};
+
+/**
+ * TabSelect event handler. Updates the sidebar elements state according to
+ * the selected tab.
+ * @param {nsIDOMEvent} evt Event object.
+ * @private
+ */
+CEEE_sidebar_internal_.onTabSelect_ = function(evt) {
+ var ceee = this.ceeeInstance_;
+ ceee.logInfo('sidebar.TabSelect');
+ this.showSidebar_(evt.target);
+};
+
+/**
+ * TabMove event handler. NOTHING TO DO.
+ * @param {nsIDOMEvent} evt Event object.
+ * @private
+ */
+CEEE_sidebar_internal_.onTabMove_ = function(evt) {
+ var ceee = this.ceeeInstance_;
+ ceee.logInfo('sidebar.TabMove');
+};
+
+/**
+ * Deinitialization of the module. NOTHING TO DO.
+ * @public
+ */
+CEEE_sidebar_internal_.onUnload = function() {
+ var ceee = this.ceeeInstance_;
+ ceee.logInfo('sidebar.onUnload');
+};
+
+/**
+ * Finds a tab associated with the given tabId.
+ * Returns current tab if the tabId is undefined.
+ * @param {?number} tabId Tab identifier.
+ * @return {?nsIDOMXULElement} Associated tab XUL DOM element.
+ */
+CEEE_sidebar_internal_.findTab_ = function(tabId) {
+ if ('undefined' == typeof tabId) {
+ var tabbrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ return tabbrowser.selectedTab;
+ } else if ('number' == typeof tabId) {
+ var tabObject = CEEE_mozilla_tabs.findTab(tabId);
+ if (!tabObject) {
+ this.ceeeInstance_.logError('Tab with tabId=' + tabId + ' not found');
+ } else {
+ return tabObject.tab;
+ }
+ } else {
+ this.ceeeInstance_.logError('Not valid tabId value: ' + tabId);
+ }
+};
+
+/**
+ * Sets a new state to the sidebar.
+ * @param {nsIDOMXULElement} tab Tab associated with this sidebar.
+ * @param {string} newState New state to assign to the sidebar.
+ */
+CEEE_sidebar_internal_.setState_ = function(tab, newState) {
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+ if (sidebarInfo.state != newState) {
+ sidebarInfo.state = newState;
+ this.onStateChanged_(tab);
+ if (tab.selected) {
+ this.showSidebar_(tab);
+ }
+ }
+};
+
+/**
+ * Implementation of experimental.sidebar.show method.
+ * It performs HIDDEN -> SHOWN sidebar's state transition.
+ * Has no effect if the state is already SHOWN or ACTIVE.
+ * @param {string} cmd Command name, 'experimental.sidebar.show' expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.show_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+
+ var tab = this.findTab_(tabId);
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+
+ // We don't wanna change the state if it is already ACTIVE.
+ if (this.STATE_HIDDEN == sidebarInfo.state) {
+ this.setState_(tab, this.STATE_SHOWN);
+ }
+
+ ceee.logInfo('sidebar.show done');
+};
+
+/**
+ * Implementation of experimental.sidebar.hide method.
+ * It effectively hides the sidebar changing its state to HIDDEN.
+ * Has no effect if the state is already HIDDEN.
+ * @param {string} cmd Command name, 'experimental.sidebar.hide' expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.hide_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+
+ var tab = this.findTab_(tabId);
+ this.setState_(tab, this.STATE_HIDDEN);
+
+ ceee.logInfo('sidebar.hide done');
+};
+
+/**
+ * Implementation of experimental.sidebar.expand method.
+ * It performs SHOWN -> ACTIVE sidebar's state transition.
+ * It's not permitted to expand a hidden sidebar, the 'show' function should
+ * have been called before.
+ * @param {string} cmd Command name, 'experimental.sidebar.expand' expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.expand_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+
+ var tab = this.findTab_(tabId);
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+
+ // The function cannot be invoked when the sidebar in its 'hidden' state.
+ if (this.STATE_HIDDEN == sidebarInfo.state) {
+ ceee.logError('HIDDEN -> ACTIVE state transition not permitted.');
+ } else {
+ this.setState_(tab, this.STATE_ACTIVE);
+ }
+
+ ceee.logInfo('sidebar.expand done');
+};
+
+/**
+ * Implementation of experimental.sidebar.collapse method.
+ * It performs ACTIVE -> SHOWN sidebar's state transition.
+ * Has no effect if the state is already SHOWN or HIDDEN.
+ * @param {string} cmd Command name, 'experimental.sidebar.collapse' expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.collapse_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+
+ var tab = this.findTab_(tabId);
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+
+ // No effect if the sidebar in its 'hidden' state.
+ if (this.STATE_ACTIVE == sidebarInfo.state) {
+ this.setState_(tab, this.STATE_SHOWN);
+ }
+
+ ceee.logInfo('sidebar.collapse done');
+};
+
+/**
+ * Implementation of experimental.sidebar.navigateTo method.
+ * @param {string} cmd Command name, 'experimental.sidebar.navigate' expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.navigate_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+ var url = args.url;
+
+ var tab = this.findTab_(tabId);
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+ sidebarInfo.url = url;
+
+ if (this.STATE_ACTIVE == sidebarInfo.state && tab.selected) {
+ this.showSidebar_(tab);
+ }
+
+ ceee.logInfo('sidebar.navigate done');
+};
+
+/**
+ * Implementation of experimental.sidebar.setIcon method.
+ * @param {string} cmd Command name, 'experimental.sidebar.setIcon' expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.setIcon_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+ var imagePath = args.imagePath;
+ var imageData = args.imageData;
+
+ var tab = this.findTab_(tabId);
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+ if ('string' == typeof imagePath) {
+ sidebarInfo.icon = CEEE_sidebar_getUserScriptUrl(imagePath);
+ }
+
+ if (this.STATE_HIDDEN != sidebarInfo.state && tab.selected) {
+ this.showSidebar_(tab);
+ }
+
+ ceee.logInfo('sidebar.setIcon done');
+};
+
+/**
+ * Implementation of experimental.sidebar.setTitle method.
+ * @param {string} cmd Command name, 'experimental.sidebar.setTitle' expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.setTitle_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+ var title = args.title;
+
+ var tab = this.findTab_(tabId);
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+ sidebarInfo.title = title;
+
+ if (this.STATE_HIDDEN != sidebarInfo.state && tab.selected) {
+ this.showSidebar_(tab);
+ }
+
+ ceee.logInfo('sidebar.setTitle done');
+};
+
+/**
+ * Implementation of experimental.sidebar.setBadgeText method.
+ * @param {string} cmd Command name, 'experimental.sidebar.setBadgeText'
+ * expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.setBadgeText_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+ var badgeText = args.badgeText;
+
+ var tab = this.findTab_(tabId);
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+ sidebarInfo.badgeText = badgeText;
+
+ if (this.STATE_HIDDEN != sidebarInfo.state && tab.selected) {
+ this.showSidebar_(tab);
+ }
+
+ ceee.logInfo('sidebar.setAttributes done');
+};
+
+/**
+ * Implementation of experimental.sidebar.getState method.
+ * @param {string} cmd Command name, 'experimental.sidebar.getState' expected.
+ * @param {Object} data Additional call info, includes args field with
+ * json-encoded arguments.
+ */
+CEEE_sidebar_internal_.getState_ = function(cmd, data) {
+ var ceee = this.ceeeInstance_;
+
+ var args = CEEE_json.decode(data.args);
+ var tabId = args.tabId;
+
+ var tab = this.findTab_(tabId);
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+
+ ceee.logInfo('sidebar.getState(' + tabId + ') = ' + sidebarInfo.state);
+ return sidebarInfo.state;
+};
+
+/**
+ * Sends an event message to Chrome API when a sidebar state has changed.
+ * @param {nsIDOMXULElement} tab Where the associated sidebar state has changed.
+ * @private
+ */
+CEEE_sidebar_internal_.onStateChanged_ = function(tab) {
+ // Send the event notification to ChromeFrame.
+ var sidebarInfo = tab[this.SIDEBAR_INFO];
+ var info = [{
+ tabId: tab[this.TAB_ID],
+ state: sidebarInfo.state
+ }];
+ var msg = ['experimental.sidebar.onStateChanged', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+/**
+ * Initialization routine for the CEEE sidebar API module.
+ * @param {!Object} ceeeInstance Reference to the global CEEE toolstrip object.
+ * @return {Object} Reference to the sidebar module.
+ * @public
+ */
+function CEEE_initialize_sidebar(ceeeInstance) {
+ // NOTE: this function must be called after the CF instance used for the
+ // toolbar is created, in order to make sure that the chrome process started
+ // uses the correct command line arguments.
+
+ // Create ChromeFrame instances to host the sidebar content.
+ var cf = document.createElementNS('http://www.w3.org/1999/xhtml', 'embed');
+ cf.id = 'ceee-sidebar-browser';
+ cf.setAttribute('flex', '1');
+ cf.setAttribute('type', 'application/chromeframe');
+ document.getElementById('ceee-sidebar-container').appendChild(cf);
+
+ // Create ChromeFrame instances to host the sidebar icon.
+ cf = document.createElementNS('http://www.w3.org/1999/xhtml', 'embed');
+ cf.id = 'ceee-sidebar-icon';
+ cf.setAttribute('width', '16');
+ cf.setAttribute('height', '16');
+ cf.setAttribute('type', 'application/chromeframe');
+ cf.setAttribute('src', 'http://www.google.com/favicon.ico');
+ document.getElementById('ceee-sidebar-icon-box').appendChild(cf);
+
+ var sidebar = CEEE_sidebar_internal_;
+ sidebar.ceeeInstance_ = ceeeInstance;
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_SHOW,
+ sidebar,
+ sidebar.show_);
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_HIDE,
+ sidebar,
+ sidebar.hide_);
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_EXPAND,
+ sidebar,
+ sidebar.expand_);
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_COLLAPSE,
+ sidebar,
+ sidebar.collapse_);
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_NAVIGATE,
+ sidebar,
+ sidebar.navigate_);
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_SET_ICON,
+ sidebar,
+ sidebar.setIcon_);
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_SET_TITLE,
+ sidebar,
+ sidebar.setTitle_);
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_SET_BADGE_TEXT,
+ sidebar,
+ sidebar.setBadgeText_);
+ ceeeInstance.registerExtensionHandler(sidebar.CMD_GET_STATE,
+ sidebar,
+ sidebar.getState_);
+
+ ceeeInstance.logInfo('CEEE_initialize_sidebar done');
+ return sidebar;
+}
diff --git a/ceee/firefox/content/tab_api.js b/ceee/firefox/content/tab_api.js
new file mode 100644
index 0000000..35470be
--- /dev/null
+++ b/ceee/firefox/content/tab_api.js
@@ -0,0 +1,480 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains the implementation of the Chrome extension
+ * tab APIs for the CEEE Firefox add-on. This file is loaded by the
+ * overlay.xul file, and requires that overlay.js has already been loaded.
+ *
+ * @supported Firefox 3.x
+ */
+
+/**
+ * Place-holder namespace-object for helper functions used in this file.
+ * @private
+ */
+var CEEE_tabs_internal_ = CEEE_tabs_internal_ || {
+ /** Reference to the instance object for the CEEE. @private */
+ ceeeInstance_: null
+};
+
+/**
+ * Routine initializaing the tabs module during onLoad message from the
+ * browser.
+ * @public
+ */
+CEEE_tabs_internal_.onLoad = function() {
+ var mainBrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ var tabContainer = mainBrowser.tabContainer;
+ var tabs = this;
+ tabContainer.addEventListener('TabOpen',
+ function(evt) {tabs.onTabOpened_(evt);},
+ false);
+ tabContainer.addEventListener('TabMove',
+ function(evt) {tabs.onTabMoved_(evt);},
+ false);
+ tabContainer.addEventListener('TabSelect',
+ function(evt) {tabs.onTabSelected_(evt);},
+ false);
+ tabContainer.addEventListener('TabClose',
+ function(evt) {tabs.onTabClosed_(evt);},
+ false);
+};
+
+/**
+ * Build an object that represents a "tab", as defined by the Google Chrome
+ * extension API. For newly created tabs, the optional URL parameter needs to
+ * be specified since some part of tab creation are asynchronous. Note also
+ * that the title property of the returned "tab" may be '' for newly created
+ * tabs too.
+ *
+ * @param {!Object} mainBrowser Firefox's main tabbed browser.
+ * @param {!Object} tab The Firefox tab from which to build the description.
+ * @param {string} url_opt Optional string contain URL. IF not specified, the
+ * URL is taken from the tab's associated browser.
+ * @return A tab description as defined by Google Chrome extension API.
+ */
+CEEE_tabs_internal_.buildTabValue = function(mainBrowser, tab, url_opt) {
+ var browser = mainBrowser.getBrowserForTab(tab);
+ var container = mainBrowser.tabContainer;
+ var index = container.getIndexOfItem(tab);
+
+ var ret = {};
+ ret.id = CEEE_mozilla_tabs.getTabId(tab);
+ ret.index = index;
+ ret.windowId = CEEE_mozilla_windows.getWindowId(window);
+ ret.url = url_opt || browser.currentURI.asciiSpec;
+ ret.title = browser.contentTitle;
+ ret.selected = (tab == mainBrowser.selectedTab);
+ ret.incognito = CEEE_globals.privateBrowsingService.isInPrivateBrowsing;
+
+ return ret;
+};
+
+/**
+ * Called when a new tab is created in this top-level window.
+ *
+ * @param evt DOM event object.
+ * @private
+ */
+CEEE_tabs_internal_.onTabOpened_ = function(evt) {
+ var mainBrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ var container = mainBrowser.tabContainer;
+ var browser = evt.target && evt.target.linkedBrowser;
+ if (!mainBrowser || !container || !browser)
+ return;
+
+ // Build a tab info object that corresponds to the new tab.
+ var index = mainBrowser.getBrowserIndexForDocument(browser.contentDocument);
+ var tab = container.getItemAtIndex(index);
+ var info = [this.buildTabValue(mainBrowser, tab)];
+
+ // Send the event notification to ChromeFrame.
+ var msg = ['tabs.onCreated', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+/**
+ * Called when a tab is moved in this top-level window.
+ *
+ * @param evt DOM event object.
+ * @private
+ */
+CEEE_tabs_internal_.onTabMoved_ = function(evt) {
+ var mainBrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ var container = mainBrowser.tabContainer;
+ var browser = evt.target && evt.target.linkedBrowser;
+ if (!mainBrowser || !container || !browser)
+ return;
+
+ // Build info corresponding to moved tab.
+ //
+ // Through experimentation, I've discovered that the detail property of the
+ // evt object contains the old index of the tab. This was discovered with
+ // Firefox 3.0.11. According to the docs at
+ // https://developer.mozilla.org/en/DOM/event.detail, this property
+ // specifies extra details about the event, and its value is event-specific.
+ var oldIndex = evt.detail;
+ var index = mainBrowser.getBrowserIndexForDocument(browser.contentDocument);
+ if (oldIndex == index)
+ return;
+
+ var tab = container.getItemAtIndex(index);
+
+ var info = [CEEE_mozilla_tabs.getTabId(tab), {
+ windowId: CEEE_mozilla_windows.getWindowId(window),
+ fromIndex: oldIndex,
+ toIndex: index
+ }];
+
+ // Send the event notification to ChromeFrame.
+ var msg = ['tabs.onMoved', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+/**
+ * Called when a tab is selected.
+ *
+ * @param evt DOM event object.
+ * @private
+ */
+CEEE_tabs_internal_.onTabSelected_ = function(evt) {
+ var mainBrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ var container = mainBrowser.tabContainer;
+ var browser = evt.target && evt.target.linkedBrowser;
+ if (!mainBrowser || !container || !browser)
+ return;
+
+ // Build info corresponding to selected tab.
+ var index = mainBrowser.getBrowserIndexForDocument(browser.contentDocument);
+ var tab = container.getItemAtIndex(index);
+ var info = [CEEE_mozilla_tabs.getTabId(tab),
+ {windowId: CEEE_mozilla_windows.getWindowId(window)}];
+
+ // Send the event notification to ChromeFrame.
+ var msg = ['tabs.onSelectionChanged', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+/**
+ * Called when a tab is closed in this top-level window.
+ *
+ * @param evt DOM event object.
+ * @private
+ */
+CEEE_tabs_internal_.onTabClosed_ = function(evt) {
+ // This function is called *before* the tab is actually removed from the
+ // tab container. Therefore, we can call methods like
+ // getBrowserIndexForDocument() and still expect them to find the tab that
+ // has been closed.
+ var mainBrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ var container = mainBrowser.tabContainer;
+ var browser = evt.target && evt.target.linkedBrowser;
+ if (!mainBrowser || !container || !browser)
+ return;
+
+ // Send the event notification to ChromeFrame.
+ var index = mainBrowser.getBrowserIndexForDocument(browser.contentDocument);
+ var tab = container.getItemAtIndex(index);
+ var info = [CEEE_mozilla_tabs.getTabId(tab)];
+ var msg = ['tabs.onRemoved', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+/**
+ * Send a status change notification to the extension.
+ *
+ * @param doc The content document object for the tab.
+ * @param status New status of the tab.
+ * @public
+ */
+CEEE_tabs_internal_.onTabStatusChanged = function(doc, status) {
+ var mainBrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ var container = mainBrowser.tabContainer;
+ if (!mainBrowser || !container)
+ return;
+
+ // Build a tab info object that corresponds to the new status. If we can't
+ // find the document, then this means its an embedded frame. We don't want
+ // to generate a status change in this case anyway.
+ var index = mainBrowser.getBrowserIndexForDocument(doc);
+ if (-1 == index)
+ return;
+
+ var tab = container.getItemAtIndex(index);
+ var info = [CEEE_mozilla_tabs.getTabId(tab),
+ {status: status},
+ this.buildTabValue(mainBrowser, tab)
+ ];
+
+ // Send the event notification to ChromeFrame.
+ var msg = ['tabs.onUpdated', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+CEEE_tabs_internal_.CMD_GET_TAB = 'tabs.get';
+CEEE_tabs_internal_.getTab_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var r = CEEE_mozilla_tabs.findTab(id);
+ if (!r) {
+ throw(new Error(CEEE_tabs_internal_.CMD_GET_TAB +
+ ': invalid tab id=' + id));
+ }
+
+ return this.buildTabValue(r.tabBrowser, r.tab);
+};
+
+CEEE_tabs_internal_.CMD_GET_SELECTED_TAB = 'tabs.getSelected';
+CEEE_tabs_internal_.getSelectedTab_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var win = CEEE_mozilla_windows.findWindow(id);
+ if (!win) {
+ throw(new Error(CEEE_tabs_internal_.CMD_GET_SELECTED_TAB +
+ ': invalid window id=' + id));
+ }
+
+ var mainBrowser = win.document.getElementById(
+ CEEE_globals.MAIN_BROWSER_ID);
+ if (!mainBrowser) {
+ throw(new Error(CEEE_tabs_internal_.CMD_GET_SELECTED_TAB +
+ ': cannot find main browser win id=' + id));
+ }
+
+ var tab = mainBrowser.selectedTab;
+ return this.buildTabValue(mainBrowser, tab);
+};
+
+CEEE_tabs_internal_.CMD_GET_ALL_TABS_IN_WINDOW = 'tabs.getAllInWindow';
+CEEE_tabs_internal_.getAllTabsInWindow_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var win = CEEE_mozilla_windows.findWindow(id);
+ if (!win || !win.document) {
+ throw(new Error(CEEE_tabs_internal_.CMD_GET_ALL_TABS_IN_WINDOW +
+ ': invalid window id=' + id));
+ }
+
+ var mainBrowser = win.document.getElementById(
+ CEEE_globals.MAIN_BROWSER_ID);
+ if (!mainBrowser) {
+ throw(new Error(CEEE_tabs_internal_.CMD_GET_ALL_TABS_IN_WINDOW +
+ ': cannot find main browser win id=' + id));
+ }
+
+ var ret = [];
+ var tabs = mainBrowser.tabContainer;
+ var count = tabs.itemCount;
+ var selectedTab = mainBrowser.selectedTab;
+ for (var i = 0; i < count; ++i) {
+ var tab = tabs.getItemAtIndex(i);
+ var t = this.buildTabValue(mainBrowser, tab);
+ ret.push(t);
+ }
+
+ return ret;
+};
+
+CEEE_tabs_internal_.CMD_CREATE_TAB = 'tabs.create';
+CEEE_tabs_internal_.createTab_ = function(cmd, data) {
+ var args_list = CEEE_json.decode(data.args);
+ var args = args_list[0];
+ var win = CEEE_mozilla_windows.findWindow(args.windowId);
+ if (!win) {
+ throw(new Error(CEEE_tabs_internal_.CMD_CREATE_TAB +
+ ': invalid window id=' + args.windowId));
+ }
+
+ var mainBrowser = win.document.getElementById(
+ CEEE_globals.MAIN_BROWSER_ID);
+ if (!mainBrowser) {
+ throw(new Error(CEEE_tabs_internal_.CMD_CREATE_TAB +
+ ': cannot find main browser win id=' + args.windowId));
+ }
+
+ var tab = mainBrowser.addTab(args.url);
+
+ if (args.index)
+ mainBrowser.moveTabTo(tab, args.index);
+
+ if (!('selected' in args) || args.selected)
+ mainBrowser.selectedTab = tab;
+
+ return this.buildTabValue(mainBrowser, tab, args.url);
+};
+
+CEEE_tabs_internal_.CMD_UPDATE_TAB = 'tabs.update';
+CEEE_tabs_internal_.updateTab_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var t = CEEE_mozilla_tabs.findTab(id);
+ if (!t) {
+ throw(new Error(CEEE_tabs_internal_.CMD_UPDATE_TAB +
+ ': invalid tab id=' + id));
+ }
+
+ var info = args[1];
+ if (info && info.url)
+ t.tabBrowser.getBrowserForTab(t.tab).loadURI(info.url);
+
+ if (info && info.selected)
+ t.tabBrowser.selectedTab = t.tab;
+
+ return this.buildTabValue(t.tabBrowser, t.tab, info.url);
+};
+
+CEEE_tabs_internal_.CMD_MOVE_TAB = 'tabs.move';
+CEEE_tabs_internal_.moveTab_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var t = CEEE_mozilla_tabs.findTab(id);
+ if (!t) {
+ throw(new Error(CEEE_tabs_internal_.CMD_MOVE_TAB +
+ ': invalid tab id=' + id));
+ }
+
+ var newIndex = args[1].index;
+ var oldIndex = t.index;
+ if (oldIndex != newIndex)
+ t.tabBrowser.moveTabTo(t.tab, newIndex);
+
+ return this.buildTabValue(t.tabBrowser, t.tab);
+};
+
+CEEE_tabs_internal_.CMD_REMOVE_TAB = 'tabs.remove';
+CEEE_tabs_internal_.removeTab_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var r = CEEE_mozilla_tabs.findTab(id);
+ if (!r) {
+ throw(new Error(CEEE_tabs_internal_.CMD_REMOVE_TAB +
+ ': invalid tab id=' + id));
+ }
+
+ r.tabBrowser.removeTab(r.tab);
+};
+
+CEEE_tabs_internal_.CMD_EXECUTE_SCRIPT = 'tabs.executeScript';
+CEEE_tabs_internal_.executeScript_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var scriptDef = args[1];
+ var t;
+
+ if (id) {
+ t = CEEE_mozilla_tabs.findTab(id);
+ if (!t) {
+ throw(new Error(CEEE_tabs_internal_.CMD_EXECUTE_SCRIPT +
+ ': invalid tab id=' + id));
+ }
+ } else {
+ var win = CEEE_mozilla_windows.findWindow(null);
+ if (!win) {
+ throw(new Error(CEEE_tabs_internal_.CMD_EXECUTE_SCRIPT +
+ ': no window'));
+ }
+
+ var mainBrowser = win.document.getElementById(
+ CEEE_globals.MAIN_BROWSER_ID);
+ if (!mainBrowser) {
+ throw(new Error(CEEE_tabs_internal_.CMD_EXECUTE_SCRIPT +
+ ': cannot find main browser'));
+ }
+
+ t = {
+ 'tab': mainBrowser.selectedTab,
+ 'tabBrowser': mainBrowser
+ };
+ }
+
+ var w = t.tabBrowser.getBrowserForTab(t.tab).contentWindow;
+ return this.ceeeInstance_.getUserScriptsModule().executeScript(w, scriptDef);
+};
+
+CEEE_tabs_internal_.CMD_INSERT_CSS = 'tabs.insertCSS';
+CEEE_tabs_internal_.insertCss_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var details = args[1];
+ var t;
+
+ if (id) {
+ t = CEEE_mozilla_tabs.findTab(id);
+ if (!t) {
+ throw new Error(CEEE_tabs_internal_.CMD_INSERT_CSS +
+ ': invalid tab id=' + id);
+ }
+ } else {
+ var win = CEEE_mozilla_windows.findWindow(null);
+ if (!win) {
+ throw new Error(CEEE_tabs_internal_.CMD_INSERT_CSS + ': no window');
+ }
+
+ var mainBrowser = win.document.getElementById(
+ CEEE_globals.MAIN_BROWSER_ID);
+ if (!mainBrowser) {
+ throw new Error(CEEE_tabs_internal_.CMD_INSERT_CSS +
+ ': cannot find main browser');
+ }
+
+ t = {
+ tab: mainBrowser.selectedTab,
+ tabBrowser: mainBrowser
+ };
+ }
+
+ var w = t.tabBrowser.getBrowserForTab(t.tab).contentWindow;
+ return this.ceeeInstance_.getUserScriptsModule().insertCss(w, details);
+};
+
+/**
+ * Initialization routine for the CEEE tabs API module.
+ * @param {!Object} ceeeInstance Reference to the global ceee instance.
+ * @return {Object} Reference to the tabs module.
+ * @public
+ */
+function CEEE_initialize_tabs(ceeeInstance) {
+ CEEE_tabs_internal_.ceeeInstance_ = ceeeInstance;
+ var tabs = CEEE_tabs_internal_;
+ ceeeInstance.registerExtensionHandler(tabs.CMD_GET_TAB,
+ tabs,
+ tabs.getTab_);
+ ceeeInstance.registerExtensionHandler(tabs.CMD_GET_SELECTED_TAB,
+ tabs,
+ tabs.getSelectedTab_);
+ ceeeInstance.registerExtensionHandler(tabs.CMD_GET_ALL_TABS_IN_WINDOW,
+ tabs,
+ tabs.getAllTabsInWindow_);
+ ceeeInstance.registerExtensionHandler(tabs.CMD_CREATE_TAB,
+ tabs,
+ tabs.createTab_);
+ ceeeInstance.registerExtensionHandler(tabs.CMD_UPDATE_TAB,
+ tabs,
+ tabs.updateTab_);
+ ceeeInstance.registerExtensionHandler(tabs.CMD_MOVE_TAB,
+ tabs,
+ tabs.moveTab_);
+ ceeeInstance.registerExtensionHandler(tabs.CMD_REMOVE_TAB,
+ tabs,
+ tabs.removeTab_);
+ ceeeInstance.registerExtensionHandler(tabs.CMD_EXECUTE_SCRIPT,
+ tabs,
+ tabs.executeScript_);
+ ceeeInstance.registerExtensionHandler(tabs.CMD_INSERT_CSS,
+ tabs,
+ tabs.insertCss_);
+
+ return tabs;
+}
diff --git a/ceee/firefox/content/us/base.js b/ceee/firefox/content/us/base.js
new file mode 100644
index 0000000..58401de
--- /dev/null
+++ b/ceee/firefox/content/us/base.js
@@ -0,0 +1,1291 @@
+// Copyright 2006 Google Inc.
+// All Rights Reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the
+// distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * @fileoverview Bootstrap for the Google JS Library (Closure) Also includes
+ * stuff taken from //depot/google3/javascript/lang.js.
+ */
+
+/*
+ * ORIGINAL VERSION: http://doctype.googlecode.com/svn/trunk/goog/base.js
+ *
+ * LOCAL CHANGES: Tagged "CEEE changes" below.
+ *
+ * This file has been changed from its original so that the deps.js file is
+ * not automatically inserted into the page. In the context of CEEE, there
+ * is no deps.js file. Furthermore, because this file is being injected into
+ * the page using the Firefox sandbox mechanism, it does not have the security
+ * rights to write to the document using the write() method.
+ *
+ * The reason for injecting base.js into the file is to use the closure-based
+ * json.js file. If we no longer need json.js, then it would be possible to
+ * remove this file too. Firefox does not have native json library that can
+ * be used by unprivileged js code, but it will in version 3.5.
+ */
+
+/**
+ * @define {boolean} Overridden to true by the compiler when --closure_pass
+ * or --mark_as_compiled is specified.
+ */
+var COMPILED = false;
+
+
+/**
+ * Base namespace for the Closure library. Checks to see goog is
+ * already defined in the current scope before assigning to prevent
+ * clobbering if base.js is loaded more than once.
+ */
+var goog = goog || {}; // Check to see if already defined in current scope
+
+
+/**
+ * Reference to the global context. In most cases this will be 'window'.
+ */
+goog.global = this;
+
+
+/**
+ * @define {boolean} DEBUG is provided as a convenience so that debugging code
+ * that should not be included in a production js_binary can be easily stripped
+ * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
+ * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
+ * because they are generally used for debugging purposes and it is difficult
+ * for the JSCompiler to statically determine whether they are used.
+ */
+goog.DEBUG = true;
+
+
+/**
+ * @define {string} LOCALE defines the locale being used for compilation. It is
+ * used to select locale specific data to be compiled in js binary. BUILD rule
+ * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
+ * option.
+ *
+ * Take into account that the locale code format is important. You should use
+ * the canonical Unicode format with hyphen as a delimiter. Language must be
+ * lowercase, Language Script - Capitalized, Region - UPPERCASE.
+ * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
+ *
+ * See more info about locale codes here:
+ * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
+ *
+ * For language codes you should use values defined by ISO 693-1. See it here
+ * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
+ * this rule: the Hebrew language. For legacy reasons the old code (iw) should
+ * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
+ */
+// CEEE changes: Changed from 'en' to 'en_US'
+goog.LOCALE = 'en_US'; // default to en_US
+
+
+/**
+ * Indicates whether or not we can call 'eval' directly to eval code in the
+ * global scope. Set to a Boolean by the first call to goog.globalEval (which
+ * empirically tests whether eval works for globals). @see goog.globalEval
+ * @type {boolean?}
+ * @private
+ */
+goog.evalWorksForGlobals_ = null;
+
+
+/**
+ * Creates object stubs for a namespace. When present in a file, goog.provide
+ * also indicates that the file defines the indicated object. Calls to
+ * goog.provide are resolved by the compiler if --closure_pass is set.
+ * @param {string} name name of the object that this file defines.
+ */
+goog.provide = function(name) {
+ if (!COMPILED) {
+ // Ensure that the same namespace isn't provided twice. This is intended
+ // to teach new developers that 'goog.provide' is effectively a variable
+ // declaration. And when JSCompiler transforms goog.provide into a real
+ // variable declaration, the compiled JS should work the same as the raw
+ // JS--even when the raw JS uses goog.provide incorrectly.
+ if (goog.getObjectByName(name) && !goog.implicitNamespaces_[name]) {
+ throw Error('Namespace "' + name + '" already declared.');
+ }
+
+ var namespace = name;
+ while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
+ goog.implicitNamespaces_[namespace] = true;
+ }
+ }
+
+ goog.exportPath_(name);
+};
+
+
+if (!COMPILED) {
+ /**
+ * Namespaces implicitly defined by goog.provide. For example,
+ * goog.provide('goog.events.Event') implicitly declares
+ * that 'goog' and 'goog.events' must be namespaces.
+ *
+ * @type {Object}
+ * @private
+ */
+ goog.implicitNamespaces_ = {};
+}
+
+
+/**
+ * Builds an object structure for the provided namespace path,
+ * ensuring that names that already exist are not overwritten. For
+ * example:
+ * "a.b.c" -> a = {};a.b={};a.b.c={};
+ * Used by goog.provide and goog.exportSymbol.
+ * @param {string} name name of the object that this file defines.
+ * @param {Object} opt_object the object to expose at the end of the path.
+ * @param {Object} opt_objectToExportTo The object to add the path to; default
+ * is |goog.global|.
+ * @private
+ */
+goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
+ var parts = name.split('.');
+ var cur = opt_objectToExportTo || goog.global;
+ var part;
+
+ // Internet Explorer exhibits strange behavior when throwing errors from
+ // methods externed in this manner. See the testExportSymbolExceptions in
+ // base_test.html for an example.
+ if (!(parts[0] in cur) && cur.execScript) {
+ cur.execScript('var ' + parts[0]);
+ }
+
+ // Parentheses added to eliminate strict JS warning in Firefox.
+ while (parts.length && (part = parts.shift())) {
+ if (!parts.length && goog.isDef(opt_object)) {
+ // last part and we have an object; use it
+ cur[part] = opt_object;
+ } else if (cur[part]) {
+ cur = cur[part];
+ } else {
+ cur = cur[part] = {};
+ }
+ }
+};
+
+
+/**
+ * Returns an object based on its fully qualified external name. If you are
+ * using a compilation pass that renames property names beware that using this
+ * function will not find renamed properties.
+ *
+ * @param {string} name The fully qualified name.
+ * @param {Object} opt_obj The object within which to look; default is
+ * |goog.global|.
+ * @return {Object?} The object or, if not found, null.
+ */
+goog.getObjectByName = function(name, opt_obj) {
+ var parts = name.split('.');
+ var cur = opt_obj || goog.global;
+ for (var part; part = parts.shift(); ) {
+ if (cur[part]) {
+ cur = cur[part];
+ } else {
+ return null;
+ }
+ }
+ return cur;
+};
+
+
+/**
+ * Globalizes a whole namespace, such as goog or goog.lang.
+ *
+ * @param {Object} obj The namespace to globalize.
+ * @param {Object} opt_global The object to add the properties to.
+ * @deprecated Properties may be explicitly exported to the global scope, but
+ * this should no longer be done in bulk.
+ */
+goog.globalize = function(obj, opt_global) {
+ var global = opt_global || goog.global;
+ for (var x in obj) {
+ global[x] = obj[x];
+ }
+};
+
+
+/**
+ * Adds a dependency from a file to the files it requires.
+ * @param {string} relPath The path to the js file.
+ * @param {Array} provides An array of strings with the names of the objects
+ * this file provides.
+ * @param {Array} requires An array of strings with the names of the objects
+ * this file requires.
+ */
+goog.addDependency = function(relPath, provides, requires) {
+ if (!COMPILED) {
+ var provide, require;
+ var path = relPath.replace(/\\/g, '/');
+ var deps = goog.dependencies_;
+ for (var i = 0; provide = provides[i]; i++) {
+ deps.nameToPath[provide] = path;
+ if (!(path in deps.pathToNames)) {
+ deps.pathToNames[path] = {};
+ }
+ deps.pathToNames[path][provide] = true;
+ }
+ for (var j = 0; require = requires[j]; j++) {
+ if (!(path in deps.requires)) {
+ deps.requires[path] = {};
+ }
+ deps.requires[path][require] = true;
+ }
+ }
+};
+
+
+/**
+ * Implements a system for the dynamic resolution of dependencies
+ * that works in parallel with the BUILD system. Note that all calls
+ * to goog.require will be stripped by the JSCompiler when the
+ * --closure_pass option is used.
+ * @param {string} rule Rule to include, in the form goog.package.part.
+ */
+goog.require = function(rule) {
+
+ // if the object already exists we do not need do do anything
+ if (!COMPILED) {
+ if (goog.getObjectByName(rule)) {
+ return;
+ }
+ var path = goog.getPathFromDeps_(rule);
+ if (path) {
+ goog.included_[path] = true;
+ goog.writeScripts_();
+ } else {
+ // NOTE(nicksantos): We could always throw an error, but this would break
+ // legacy users that depended on this failing silently. Instead, the
+ // compiler should warn us when there are invalid goog.require calls.
+ // For now, we simply give clients a way to turn strict mode on.
+ if (goog.useStrictRequires) {
+ throw new Error('goog.require could not find: ' + rule);
+ }
+ }
+ }
+};
+
+
+/**
+ * Whether goog.require should throw an exception if it fails.
+ * @type {boolean}
+ */
+goog.useStrictRequires = false;
+
+
+/**
+ * Path for included scripts
+ * @type {string}
+ */
+goog.basePath = '';
+
+
+/**
+ * Null function used for default values of callbacks, etc.
+ * @type {!Function}
+ */
+goog.nullFunction = function() {};
+
+
+/**
+ * The identity function. Returns its first argument.
+ *
+ * @param {*} var_args The arguments of the function.
+ * @return {*} The first argument.
+ * @deprecated Use goog.functions.identity instead.
+ */
+goog.identityFunction = function(var_args) {
+ return arguments[0];
+};
+
+
+/**
+ * When defining a class Foo with an abstract method bar(), you can do:
+ *
+ * Foo.prototype.bar = goog.abstractMethod
+ *
+ * Now if a subclass of Foo fails to override bar(), an error
+ * will be thrown when bar() is invoked.
+ *
+ * Note: This does not take the name of the function to override as
+ * an argument because that would make it more difficult to obfuscate
+ * our JavaScript code.
+ *
+ * @throws {Error} when invoked to indicate the method should be
+ * overridden.
+ */
+goog.abstractMethod = function() {
+ throw Error('unimplemented abstract method');
+};
+
+
+/**
+ * Adds a {@code getInstance} static method that always return the same instance
+ * object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ * method to.
+ */
+goog.addSingletonGetter = function(ctor) {
+ ctor.getInstance = function() {
+ return ctor.instance_ || (ctor.instance_ = new ctor());
+ };
+};
+
+
+if (!COMPILED) {
+ /**
+ * Object used to keep track of urls that have already been added. This
+ * record allows the prevention of circular dependencies.
+ * @type {Object}
+ * @private
+ */
+ goog.included_ = {};
+
+
+ /**
+ * This object is used to keep track of dependencies and other data that is
+ * used for loading scripts
+ * @private
+ * @type {Object}
+ */
+ goog.dependencies_ = {
+ pathToNames: {}, // 1 to many
+ nameToPath: {}, // 1 to 1
+ requires: {}, // 1 to many
+ visited: {}, // used when resolving dependencies to prevent us from
+ // visiting the file twice
+ written: {} // used to keep track of script files we have written
+ };
+
+
+ /**
+ * Tries to detect the base path of the base.js script that bootstraps Closure
+ * @private
+ */
+ goog.findBasePath_ = function() {
+ var doc = goog.global.document;
+ if (typeof doc == 'undefined') {
+ return;
+ }
+ if (goog.global.CLOSURE_BASE_PATH) {
+ goog.basePath = goog.global.CLOSURE_BASE_PATH;
+ return;
+ } else {
+ // HACKHACK to hide compiler warnings :(
+ goog.global.CLOSURE_BASE_PATH = null;
+ }
+ var scripts = doc.getElementsByTagName('script');
+ for (var script, i = 0; script = scripts[i]; i++) {
+ var src = script.src;
+ var l = src.length;
+ if (src.substr(l - 7) == 'base.js') {
+ goog.basePath = src.substr(0, l - 7);
+ return;
+ }
+ }
+ };
+
+
+ /**
+ * Writes a script tag if, and only if, that script hasn't already been added
+ * to the document. (Must be called at execution time)
+ * @param {string} src Script source.
+ * @private
+ */
+ goog.writeScriptTag_ = function(src) {
+ var doc = goog.global.document;
+ if (typeof doc != 'undefined' &&
+ !goog.dependencies_.written[src]) {
+ goog.dependencies_.written[src] = true;
+ doc.write('<script type="text/javascript" src="' +
+ src + '"></' + 'script>');
+ }
+ };
+
+
+ /**
+ * Resolves dependencies based on the dependencies added using addDependency
+ * and calls writeScriptTag_ in the correct order.
+ * @private
+ */
+ goog.writeScripts_ = function() {
+ // the scripts we need to write this time
+ var scripts = [];
+ var seenScript = {};
+ var deps = goog.dependencies_;
+
+ function visitNode(path) {
+ if (path in deps.written) {
+ return;
+ }
+
+ // we have already visited this one. We can get here if we have cyclic
+ // dependencies
+ if (path in deps.visited) {
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ return;
+ }
+
+ deps.visited[path] = true;
+
+ if (path in deps.requires) {
+ for (var requireName in deps.requires[path]) {
+ if (requireName in deps.nameToPath) {
+ visitNode(deps.nameToPath[requireName]);
+ } else {
+ throw Error('Undefined nameToPath for ' + requireName);
+ }
+ }
+ }
+
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ }
+
+ for (var path in goog.included_) {
+ if (!deps.written[path]) {
+ visitNode(path);
+ }
+ }
+
+ for (var i = 0; i < scripts.length; i++) {
+ if (scripts[i]) {
+ goog.writeScriptTag_(goog.basePath + scripts[i]);
+ } else {
+ throw Error('Undefined script input');
+ }
+ }
+ };
+
+
+ /**
+ * Looks at the dependency rules and tries to determine the script file that
+ * fulfills a particular rule.
+ * @param {string} rule In the form goog.namespace.Class or project.script.
+ * @return {string?} Url corresponding to the rule, or null.
+ * @private
+ */
+ goog.getPathFromDeps_ = function(rule) {
+ if (rule in goog.dependencies_.nameToPath) {
+ return goog.dependencies_.nameToPath[rule];
+ } else {
+ return null;
+ }
+ };
+
+ goog.findBasePath_();
+ // start CEEE changes {{
+ // This file is injected into the page using the Firefox sandbox mechanism.
+ // There is no generated deps.js file to inject with it.
+ //goog.writeScriptTag_(goog.basePath + 'deps.js');
+ // }} end CEEE changes
+}
+
+
+
+//==============================================================================
+// Language Enhancements
+//==============================================================================
+
+
+/**
+ * This is a "fixed" version of the typeof operator. It differs from the typeof
+ * operator in such a way that null returns 'null' and arrays return 'array'.
+ * @param {*} value The value to get the type of.
+ * @return {string} The name of the type.
+ */
+goog.typeOf = function(value) {
+ var s = typeof value;
+ if (s == 'object') {
+ if (value) {
+ // We cannot use constructor == Array or instanceof Array because
+ // different frames have different Array objects. In IE6, if the iframe
+ // where the array was created is destroyed, the array loses its
+ // prototype. Then dereferencing val.splice here throws an exception, so
+ // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
+ // so that will work. In this case, this function will return false and
+ // most array functions will still work because the array is still
+ // array-like (supports length and []) even though it has lost its
+ // prototype.
+ // Mark Miller noticed that Object.prototype.toString
+ // allows access to the unforgeable [[Class]] property.
+ // 15.2.4.2 Object.prototype.toString ( )
+ // When the toString method is called, the following steps are taken:
+ // 1. Get the [[Class]] property of this object.
+ // 2. Compute a string value by concatenating the three strings
+ // "[object ", Result(1), and "]".
+ // 3. Return Result(2).
+ // and this behavior survives the destruction of the execution context.
+ if (value instanceof Array || // Works quickly in same execution context.
+ // If value is from a different execution context then
+ // !(value instanceof Object), which lets us early out in the common
+ // case when value is from the same context but not an array.
+ // The {if (value)} check above means we don't have to worry about
+ // undefined behavior of Object.prototype.toString on null/undefined.
+ //
+ // HACK: In order to use an Object prototype method on the arbitrary
+ // value, the compiler requires the value be cast to type Object,
+ // even though the ECMA spec explicitly allows it.
+ (!(value instanceof Object) &&
+ Object.prototype.toString.call(
+ /** @type {Object} */(value)) == '[object Array]')) {
+ return 'array';
+ }
+ // HACK: There is still an array case that fails.
+ // function ArrayImpostor() {}
+ // ArrayImpostor.prototype = [];
+ // var impostor = new ArrayImpostor;
+ // this can be fixed by getting rid of the fast path
+ // (value instanceof Array) and solely relying on
+ // (value && Object.prototype.toString.vall(value) === '[object Array]')
+ // but that would require many more function calls and is not warranted
+ // unless closure code is receiving objects from untrusted sources.
+
+ // IE in cross-window calls does not correctly marshal the function type
+ // (it appears just as an object) so we cannot use just typeof val ==
+ // 'function'. However, if the object has a call property, it is a
+ // function.
+ if (typeof value.call != 'undefined') {
+ return 'function';
+ }
+ } else {
+ return 'null';
+ }
+
+ // In Safari typeof nodeList returns 'function', and on Firefox
+ // typeof behaves similarly for HTML{Applet,Embed,Object}Elements
+ // and RegExps. We would like to return object for those and we can
+ // detect an invalid function by making sure that the function
+ // object has a call method.
+ } else if (s == 'function' && typeof value.call == 'undefined') {
+ return 'object';
+ }
+ return s;
+};
+
+
+/**
+ * Safe way to test whether a property is enumarable. It allows testing
+ * for enumerable on objects where 'propertyIsEnumerable' is overridden or
+ * does not exist (like DOM nodes in IE). Does not use browser native
+ * Object.propertyIsEnumerable.
+ * @param {Object} object The object to test if the property is enumerable.
+ * @param {string} propName The property name to check for.
+ * @return {boolean} True if the property is enumarable.
+ * @private
+ */
+goog.propertyIsEnumerableCustom_ = function(object, propName) {
+ // KJS in Safari 2 is not ECMAScript compatible and lacks crucial methods
+ // such as propertyIsEnumerable. We therefore use a workaround.
+ // Does anyone know a more efficient work around?
+ if (propName in object) {
+ for (var key in object) {
+ if (key == propName &&
+ Object.prototype.hasOwnProperty.call(object, propName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+
+if (Object.prototype.propertyIsEnumerable) {
+ /**
+ * Safe way to test whether a property is enumarable. It allows testing
+ * for enumerable on objects where 'propertyIsEnumerable' is overridden or
+ * does not exist (like DOM nodes in IE).
+ * @param {Object} object The object to test if the property is enumerable.
+ * @param {string} propName The property name to check for.
+ * @return {boolean} True if the property is enumarable.
+ * @private
+ */
+ goog.propertyIsEnumerable_ = function(object, propName) {
+ // In IE if object is from another window, cannot use propertyIsEnumerable
+ // from this window's Object. Will raise a 'JScript object expected' error.
+ if (object instanceof Object) {
+ return Object.prototype.propertyIsEnumerable.call(object, propName);
+ } else {
+ return goog.propertyIsEnumerableCustom_(object, propName);
+ }
+ };
+} else {
+ // CEEE changes: Added the conditional above and this case as a bugfix.
+ goog.propertyIsEnumerable_ = goog.propertyIsEnumerableCustom_;
+}
+
+/**
+ * Returns true if the specified value is not |undefined|.
+ * WARNING: Do not use this to test if an object has a property. Use the in
+ * operator instead.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is defined.
+ */
+goog.isDef = function(val) {
+ return typeof val != 'undefined';
+};
+
+
+/**
+ * Returns true if the specified value is |null|
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is null.
+ */
+goog.isNull = function(val) {
+ return val === null;
+};
+
+
+/**
+ * Returns true if the specified value is defined and not null
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is defined and not null.
+ */
+goog.isDefAndNotNull = function(val) {
+ return goog.isDef(val) && !goog.isNull(val);
+};
+
+
+/**
+ * Returns true if the specified value is an array
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArray = function(val) {
+ return goog.typeOf(val) == 'array';
+};
+
+
+/**
+ * Returns true if the object looks like an array. To qualify as array like
+ * the value needs to be either a NodeList or an object with a Number length
+ * property.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArrayLike = function(val) {
+ var type = goog.typeOf(val);
+ return type == 'array' || type == 'object' && typeof val.length == 'number';
+};
+
+
+/**
+ * Returns true if the object looks like a Date. To qualify as Date-like
+ * the value needs to be an object and have a getFullYear() function.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a like a Date.
+ */
+goog.isDateLike = function(val) {
+ return goog.isObject(val) && typeof val.getFullYear == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is a string
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a string.
+ */
+goog.isString = function(val) {
+ return typeof val == 'string';
+};
+
+
+/**
+ * Returns true if the specified value is a boolean
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is boolean.
+ */
+goog.isBoolean = function(val) {
+ return typeof val == 'boolean';
+};
+
+
+/**
+ * Returns true if the specified value is a number
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a number.
+ */
+goog.isNumber = function(val) {
+ return typeof val == 'number';
+};
+
+
+/**
+ * Returns true if the specified value is a function
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a function.
+ */
+goog.isFunction = function(val) {
+ return goog.typeOf(val) == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is an object. This includes arrays
+ * and functions.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an object.
+ */
+goog.isObject = function(val) {
+ var type = goog.typeOf(val);
+ return type == 'object' || type == 'array' || type == 'function';
+};
+
+
+/**
+ * Adds a hash code field to an object. The hash code is unique for the
+ * given object.
+ * @param {Object} obj The object to get the hash code for.
+ * @return {number} The hash code for the object.
+ */
+goog.getHashCode = function(obj) {
+ // In IE, DOM nodes do not extend Object so they do not have this method.
+ // we need to check hasOwnProperty because the proto might have this set.
+
+ if (obj.hasOwnProperty && obj.hasOwnProperty(goog.HASH_CODE_PROPERTY_)) {
+ var hashCode = obj[goog.HASH_CODE_PROPERTY_];
+ // CEEE changes: workaround for Chrome bug 1252508.
+ if (hashCode) {
+ return hashCode;
+ }
+ }
+ if (!obj[goog.HASH_CODE_PROPERTY_]) {
+ obj[goog.HASH_CODE_PROPERTY_] = ++goog.hashCodeCounter_;
+ }
+ return obj[goog.HASH_CODE_PROPERTY_];
+};
+
+
+/**
+ * Removes the hash code field from an object.
+ * @param {Object} obj The object to remove the field from.
+ */
+goog.removeHashCode = function(obj) {
+ // DOM nodes in IE are not instance of Object and throws exception
+ // for delete. Instead we try to use removeAttribute
+ if ('removeAttribute' in obj) {
+ obj.removeAttribute(goog.HASH_CODE_PROPERTY_);
+ }
+ /** @preserveTry */
+ try {
+ delete obj[goog.HASH_CODE_PROPERTY_];
+ } catch (ex) {
+ }
+};
+
+
+/**
+ * {String} Name for hash code property
+ * @private
+ */
+goog.HASH_CODE_PROPERTY_ = 'closure_hashCode_';
+
+
+/**
+ * @type {number} Counter for hash codes.
+ * @private
+ */
+goog.hashCodeCounter_ = 0;
+
+
+/**
+ * Clone an object/array (recursively)
+ * @param {Object} proto Object to clone.
+ * @return {Object} Clone of x;.
+ */
+goog.cloneObject = function(proto) {
+ var type = goog.typeOf(proto);
+ if (type == 'object' || type == 'array') {
+ if (proto.clone) {
+ return proto.clone.call(proto);
+ }
+ var clone = type == 'array' ? [] : {};
+ for (var key in proto) {
+ clone[key] = goog.cloneObject(proto[key]);
+ }
+ return clone;
+ }
+
+ return proto;
+};
+
+
+/**
+ * Forward declaration for the clone method. This is necessary until the
+ * compiler can better support duck-typing constructs as used in
+ * goog.cloneObject.
+ *
+ * @type {Function}
+ */
+Object.prototype.clone;
+
+
+/**
+ * Partially applies this function to a particular 'this object' and zero or
+ * more arguments. The result is a new function with some arguments of the first
+ * function pre-filled and the value of |this| 'pre-specified'.<br><br>
+ *
+ * Remaining arguments specified at call-time are appended to the pre-
+ * specified ones.<br><br>
+ *
+ * Also see: {@link #partial}.<br><br>
+ *
+ * Note that bind and partial are optimized such that repeated calls to it do
+ * not create more than one function object, so there is no additional cost for
+ * something like:<br>
+ *
+ * <pre>var g = bind(f, obj);
+ * var h = partial(g, 1, 2, 3);
+ * var k = partial(h, a, b, c);</pre>
+ *
+ * Usage:
+ * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
+ * barMethBound('arg3', 'arg4');</pre>
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {Object} selfObj Specifies the object which |this| should point to
+ * when the function is run. If the value is null or undefined, it will
+ * default to the global object.
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to the function.
+ *
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ */
+goog.bind = function(fn, selfObj, var_args) {
+ var boundArgs = fn.boundArgs_;
+
+ if (arguments.length > 2) {
+ var args = Array.prototype.slice.call(arguments, 2);
+ if (boundArgs) {
+ args.unshift.apply(args, boundArgs);
+ }
+ boundArgs = args;
+ }
+
+ selfObj = fn.boundSelf_ || selfObj;
+ fn = fn.boundFn_ || fn;
+
+ var newfn;
+ var context = selfObj || goog.global;
+
+ if (boundArgs) {
+ newfn = function() {
+ // Combine the static args and the new args into one big array
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift.apply(args, boundArgs);
+ return fn.apply(context, args);
+ };
+ } else {
+ newfn = function() {
+ return fn.apply(context, arguments);
+ };
+ }
+
+ newfn.boundArgs_ = boundArgs;
+ newfn.boundSelf_ = selfObj;
+ newfn.boundFn_ = fn;
+
+ return newfn;
+};
+
+
+/**
+ * Like bind(), except that a 'this object' is not required. Useful when the
+ * target function is already bound.
+ *
+ * Usage:
+ * var g = partial(f, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to fn.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ */
+goog.partial = function(fn, var_args) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ args.unshift(fn, null);
+ return goog.bind.apply(null, args);
+};
+
+
+/**
+ * Copies all the members of a source object to a target object.
+ * @param {Object} target Target.
+ * @param {Object} source Source.
+ * @deprecated Use goog.object.extend instead.
+ */
+goog.mixin = function(target, source) {
+ for (var x in source) {
+ target[x] = source[x];
+ }
+
+ // For IE the for-in-loop does not contain any properties that are not
+ // enumerable on the prototype object (for example, isPrototypeOf from
+ // Object.prototype) but also it will not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+};
+
+
+/**
+ * A simple wrapper for new Date().getTime().
+ *
+ * @return {number} An integer value representing the number of milliseconds
+ * between midnight, January 1, 1970 and the current time.
+ */
+goog.now = Date.now || (function() {
+ return new Date().getTime();
+});
+
+
+/**
+ * Evals javascript in the global scope. In IE this uses execScript, other
+ * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
+ * global scope (for example, in Safari), appends a script tag instead.
+ * Throws an exception if neither execScript or eval is defined.
+ * @param {string} script JavaScript string.
+ */
+goog.globalEval = function(script) {
+ if (goog.global.execScript) {
+ goog.global.execScript(script, 'JavaScript');
+ } else if (goog.global.eval) {
+ // Test to see if eval works
+ if (goog.evalWorksForGlobals_ == null) {
+ goog.global.eval('var _et_ = 1;');
+ if (typeof goog.global['_et_'] != 'undefined') {
+ delete goog.global['_et_'];
+ goog.evalWorksForGlobals_ = true;
+ } else {
+ goog.evalWorksForGlobals_ = false;
+ }
+ }
+
+ if (goog.evalWorksForGlobals_) {
+ goog.global.eval(script);
+ } else {
+ var doc = goog.global.document;
+ var scriptElt = doc.createElement('script');
+ scriptElt.type = 'text/javascript';
+ scriptElt.defer = false;
+ // Note(pupius): can't use .innerHTML since "t('<test>')" will fail and
+ // .text doesn't work in Safari 2. Therefore we append a text node.
+ scriptElt.appendChild(doc.createTextNode(script));
+ doc.body.appendChild(scriptElt);
+ doc.body.removeChild(scriptElt);
+ }
+ } else {
+ throw Error('goog.globalEval not available');
+ }
+};
+
+
+/**
+ * Forward declaration of a type name.
+ *
+ * A call of the form
+ * goog.declareType('goog.MyClass');
+ * tells JSCompiler "goog.MyClass is not a hard dependency of this file.
+ * But it may appear in the type annotations here. This is to assure
+ * you that the class does indeed exist, even if it's not declared in the
+ * final binary."
+ *
+ * In uncompiled code, does nothing.
+ * @param {string} typeName The name of the type.
+ */
+goog.declareType = function(typeName) {};
+
+
+/**
+ * A macro for defining composite types.
+ *
+ * By assigning goog.typedef to a name, this tells JSCompiler that this is not
+ * the name of a class, but rather it's the name of a composite type.
+ *
+ * For example,
+ * /** @type {Array|NodeList} / goog.ArrayLike = goog.typedef;
+ * will tell JSCompiler to replace all appearances of goog.ArrayLike in type
+ * definitions with the union of Array and NodeList.
+ *
+ * Does nothing in uncompiled code.
+ */
+goog.typedef = true;
+
+
+/**
+ * Handles strings that are intended to be used as CSS class names.
+ *
+ * Without JS Compiler the arguments are simple joined with a hyphen and passed
+ * through unaltered.
+ *
+ * With the JS Compiler the arguments are inlined, e.g:
+ * var x = goog.getCssName('foo');
+ * var y = goog.getCssName(this.baseClass, 'active');
+ * becomes:
+ * var x= 'foo';
+ * var y = this.baseClass + '-active';
+ *
+ * If a CSS renaming map is passed to the compiler it will replace symbols in
+ * the classname. If one argument is passed it will be processed, if two are
+ * passed only the modifier will be processed, as it is assumed the first
+ * argument was generated as a result of calling goog.getCssName.
+ *
+ * Names are split on 'hyphen' and processed in parts such that the following
+ * are equivalent:
+ * var base = goog.getCssName('baseclass');
+ * goog.getCssName(base, 'modifier');
+ * goog.getCSsName('baseclass-modifier');
+ *
+ * If any part does not appear in the renaming map a warning is logged and the
+ * original, unobfuscated class name is inlined.
+ *
+ * @param {string} className The class name.
+ * @param {string} opt_modifier A modifier to be appended to the class name.
+ * @return {string} The class name or the concatenation of the class name and
+ * the modifier.
+ */
+goog.getCssName = function(className, opt_modifier) {
+ return className + (opt_modifier ? '-' + opt_modifier : '');
+};
+
+
+/**
+ * Abstract implementation of goog.getMsg for use with localized messages.
+ * @param {string} str Translatable string, places holders in the form {$foo}.
+ * @param {Object} opt_values Map of place holder name to value.
+ * @return {string} message with placeholders filled.
+ */
+goog.getMsg = function(str, opt_values) {
+ var values = opt_values || {};
+ for (var key in values) {
+ str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), values[key]);
+ }
+ return str;
+};
+
+
+/**
+ * Exposes an unobfuscated global namespace path for the given object.
+ * Note that fields of the exported object *will* be obfuscated,
+ * unless they are exported in turn via this function or
+ * goog.exportProperty
+ *
+ * <p>Also handy for making public items that are defined in anonymous
+ * closures.
+ *
+ * ex. goog.exportSymbol('Foo', Foo);
+ *
+ * ex. goog.exportSymbol('public.path.Foo.staticFunction',
+ * Foo.staticFunction);
+ * public.path.Foo.staticFunction();
+ *
+ * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
+ * Foo.prototype.myMethod);
+ * new public.path.Foo().myMethod();
+ *
+ * @param {string} publicPath Unobfuscated name to export.
+ * @param {Object} object Object the name should point to.
+ * @param {Object} opt_objectToExportTo The object to add the path to; default
+ * is |goog.global|.
+ */
+goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
+ goog.exportPath_(publicPath, object, opt_objectToExportTo);
+};
+
+
+/**
+ * Exports a property unobfuscated into the object's namespace.
+ * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
+ * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
+ * @param {Object} object Object whose static property is being exported.
+ * @param {string} publicName Unobfuscated name to export.
+ * @param {Object} symbol Object the name should point to.
+ */
+goog.exportProperty = function(object, publicName, symbol) {
+ object[publicName] = symbol;
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * Usage:
+ * <pre>
+ * function ParentClass(a, b) { }
+ * ParentClass.prototype.foo = function(a) { }
+ *
+ * function ChildClass(a, b, c) {
+ * ParentClass.call(this, a, b);
+ * }
+ *
+ * goog.inherits(ChildClass, ParentClass);
+ *
+ * var child = new ChildClass('a', 'b', 'see');
+ * child.foo(); // works
+ * </pre>
+ *
+ * In addition, a superclass' implementation of a method can be invoked
+ * as follows:
+ *
+ * <pre>
+ * ChildClass.prototype.foo = function(a) {
+ * ChildClass.superClass_.foo.call(this, a);
+ * // other code
+ * };
+ * </pre>
+ *
+ * @param {Function} childCtor Child class.
+ * @param {Function} parentCtor Parent class.
+ */
+goog.inherits = function(childCtor, parentCtor) {
+ /** @constructor */
+ function tempCtor() {};
+ tempCtor.prototype = parentCtor.prototype;
+ childCtor.superClass_ = parentCtor.prototype;
+ childCtor.prototype = new tempCtor();
+ childCtor.prototype.constructor = childCtor;
+};
+
+
+//==============================================================================
+// Extending Function
+//==============================================================================
+
+
+/**
+ * @define {boolean} Whether to extend Function.prototype.
+ * Use --define='goog.MODIFY_FUNCTION_PROTOTYPES=false' to change.
+ */
+goog.MODIFY_FUNCTION_PROTOTYPES = true;
+
+if (goog.MODIFY_FUNCTION_PROTOTYPES) {
+
+
+ /**
+ * An alias to the {@link goog.bind()} global function.
+ *
+ * Usage:
+ * var g = f.bind(obj, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Object} selfObj Specifies the object to which |this| should point
+ * when the function is run. If the value is null or undefined, it will
+ * default to the global object.
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to fn.
+ * @return {!Function} A partially-applied form of the Function on which
+ * bind() was invoked as a method.
+ * @deprecated Use the static function goog.bind instead.
+ */
+ Function.prototype.bind = function(selfObj, var_args) {
+ if (arguments.length > 1) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ args.unshift(this, selfObj);
+ return goog.bind.apply(null, args);
+ } else {
+ return goog.bind(this, selfObj);
+ }
+ };
+
+
+ /**
+ * An alias to the {@link goog.partial()} static function.
+ *
+ * Usage:
+ * var g = f.partial(arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to fn.
+ * @return {!Function} A partially-applied form of the function partial() was
+ * invoked as a method of.
+ * @deprecated Use the static function goog.partial instead.
+ */
+ Function.prototype.partial = function(var_args) {
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift(this, null);
+ return goog.bind.apply(null, args);
+ };
+
+
+ /**
+ * Inherit the prototype methods from one constructor into another.
+ * @param {Function} parentCtor Parent class.
+ * @see goog.inherits
+ * @deprecated Use the static function goog.inherits instead.
+ */
+ Function.prototype.inherits = function(parentCtor) {
+ goog.inherits(this, parentCtor);
+ };
+
+
+ /**
+ * Mixes in an object's properties and methods into the callee's prototype.
+ * Basically mixin based inheritance, thus providing an alternative method for
+ * adding properties and methods to a class' prototype.
+ *
+ * <pre>
+ * function X() {}
+ * X.mixin({
+ * one: 1,
+ * two: 2,
+ * three: 3,
+ * doit: function() { return this.one + this.two + this.three; }
+ * });
+ *
+ * function Y() { }
+ * Y.mixin(X.prototype);
+ * Y.prototype.four = 15;
+ * Y.prototype.doit2 = function() { return this.doit() + this.four; }
+ * });
+ *
+ * // or
+ *
+ * function Y() { }
+ * Y.inherits(X);
+ * Y.mixin({
+ * one: 10,
+ * four: 15,
+ * doit2: function() { return this.doit() + this.four; }
+ * });
+ * </pre>
+ *
+ * @param {Object} source from which to copy properties.
+ * @see goog.mixin
+ * @deprecated Use the static function goog.object.extend instead.
+ */
+ Function.prototype.mixin = function(source) {
+ goog.mixin(this.prototype, source);
+ };
+} \ No newline at end of file
diff --git a/ceee/firefox/content/us/ceee_bootstrap.js b/ceee/firefox/content/us/ceee_bootstrap.js
new file mode 100644
index 0000000..24bc65b
--- /dev/null
+++ b/ceee/firefox/content/us/ceee_bootstrap.js
@@ -0,0 +1,254 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains a bootstrap for the goog.* and chrome.*
+ * APIs that are available to user scripts, when run inside of Firefox. This
+ * should be the first script injected into any page.
+ *
+ * @supported Firefox 3.x
+ */
+
+// Define the global namespaces.
+var chrome = chrome || {};
+var console = console || {};
+var goog = goog || {};
+var JSON = goog.json;
+JSON.stringify = JSON.serialize;
+var ceee = ceee || {
+ /** @const */ API_EVENT_NAME_ : 'ceee-dom-api',
+ /** @const */ API_ELEMENT_NAME_ : 'ceee-api-element',
+ /** @const */ DATA_ATTRIBUTE_NAME_ : 'ceee-event-data',
+ /** @const */ RETURN_ATTRIBUTE_NAME_ : 'ceee-event-return',
+
+ /**
+ * This object acts as the "chromeHidden" object for the chrome API
+ * implementation. Access to it is provided by the GetChromeHidden() to
+ * emulate Chrome's behaviour.
+ */
+ hidden: {}
+};
+
+/**
+ * Create and initialize a generic event for sending Google Chrome extension
+ * API calls from the user script to the CEEE add-on.
+ *
+ * @return {!object} The new API event.
+ * @private
+ */
+ceee.createApiEvent_ = function() {
+ var evt = document.createEvent('Events');
+ evt.initEvent(this.API_EVENT_NAME_, // name of ceee API event type
+ true, // event should bubble up through the event chain
+ false); // event is not cancelable
+ return evt;
+};
+
+/**
+ * Get the DOM element in the document used to hold the arguments and
+ * return value for Google Chrome user script extension API calls made to the
+ * ceee add-on. This element is added to the page DOM if not already
+ * present, and is not visible.
+ *
+ * The DATA_ATTRIBUTE_NAME_ attributes of the returned element will be set to
+ * the empty string upon return of this function.
+ *
+ * @param {!string} cmd The API command that will be fired by this event.
+ * @return {!object} The DOM element.
+ * @private
+ */
+ceee.getApiElement_ = function(cmd) {
+ // Get the element from the DOM used to pass arguments receive results from
+ // the ceee add-on. If the element does not exist create it now. There
+ // should not be any threading issues with this object.
+ var e = document.getElementById(this.API_ELEMENT_NAME_);
+ if (e == null) {
+ e = document.createElement(this.API_ELEMENT_NAME_);
+ document.documentElement.appendChild(e);
+ }
+
+ // Remove any existing attributes that may be left over from a previous
+ // API call.
+ e.setAttribute(this.DATA_ATTRIBUTE_NAME_, '');
+ e.setAttribute(this.RETURN_ATTRIBUTE_NAME_, '');
+
+ // Set the command of the new API call, plus an id so that we can find
+ // this element again. We don't need to create a bunch of these.
+ e.setAttribute('id', this.API_ELEMENT_NAME_);
+ return e;
+};
+
+/**
+ * Send a Google Chrome extension API call to the ceee add-on.
+ *
+ * @param {!string} cmd The API command that will be fired by this event.
+ * @param {object} args Arguments of the API call. Note that non-string
+ * entries in the array will be converted to string via toString().
+ * @private
+ */
+ceee.sendMessage_ = function(cmd, args) {
+ // Package up the DOM API call into the same format as the UI API. Then
+ // stuff it into the DOM API element.
+ var data = {};
+ data.name = cmd;
+ data.args = args;
+
+ var s = goog.json.serialize(data);
+ var evt = this.createApiEvent_();
+ var e = this.getApiElement_(cmd);
+
+ e.setAttribute(this.DATA_ATTRIBUTE_NAME_, s);
+ e.dispatchEvent(evt);
+
+ // The dispatch call above is synchronous. If there is a return value, it
+ // has been placed in the DOM API element. Extract it and return.
+ var r = e.getAttribute(this.RETURN_ATTRIBUTE_NAME_);
+ if (r && r.length > 0)
+ return goog.json.parse(r);
+};
+
+// Add any functions that are missing from console.
+
+if (!console.info) {
+ /**
+ * Write an informational log to the Firefox error console.
+ * @param {string} message The message to log to the console.
+ */
+ console.info = function(message) {
+ ceee.sendMessage_('LogToConsole', [message]);
+ };
+}
+
+if (!console.log) {
+ /**
+ * Write an informational log to the Firefox error console.
+ * @param {string} message The message to log to the console.
+ */
+ console.log = function(message) {
+ ceee.sendMessage_('LogToConsole', [message]);
+ };
+}
+
+if (!console.warn) {
+ /**
+ * Write a warning log to the Firefox error console.
+ * @param {string} message The message to log to the console.
+ */
+ console.warn = function(message) {
+ ceee.sendMessage_('LogToConsole', [message]);
+ };
+}
+
+if (!console.error) {
+ /**
+ * Write an error to the Firefox error console.
+ * @param {string} message The message to log to the console.
+ */
+ console.error = function(message) {
+ ceee.sendMessage_('ErrorToConsole', [message]);
+ };
+}
+
+if (!console.assert) {
+ /**
+ * Conditionally write an error to the Firefox error console.
+ * @param {string} message The message to log to the console.
+ */
+ console.assert = function(test, message) {
+ if (!test) {
+ ceee.sendMessage_('ErrorToConsole', ['Assertion failed: ' + message]);
+ }
+ };
+}
+
+if (!console.count) {
+ /**
+ * Send a count message to the Firefox error console.
+ * @param {string} name The name of the counter.
+ */
+ console.count = function(name) {
+ var count = console.count.map[name];
+ if (count) {
+ count++;
+ } else {
+ count = 1;
+ }
+ console.count.map[name] = count;
+
+ ceee.sendMessage_('LogToConsole', [name + ': ' + count]);
+ };
+
+ /**
+ * A map of names to counts.
+ * @type {Object.<string, number>}
+ */
+ console.count.map = {};
+}
+
+if (!console.time) {
+ /**
+ * Record a time message to be used with console.timeEnd().
+ * @param {string} name The name of the timer.
+ */
+ console.time = function(name) {
+ console.time.map[name] = new Date().getTime();
+ };
+
+ /**
+ * A map of names to times in ms.
+ * @type {Object.<string, number>}
+ */
+ console.time.map = {};
+}
+
+if (!console.timeEnd) {
+ /**
+ * Send a time end message to the Firefox error console.
+ * @param {string} name The name of the timer.
+ */
+ console.timeEnd = function(name) {
+ if (console.time.map && console.time.map[name]) {
+ var diff = (new Date().getTime()) - console.time.map[name];
+ ceee.sendMessage_('LogToConsole', [name + ': ' + diff + 'ms']);
+ }
+ };
+}
+
+ceee.OpenChannelToExtension = function(sourceId, targetId, name) {
+ return ceee.sendMessage_('OpenChannelToExtension', [
+ sourceId, targetId, name
+ ]);
+};
+
+// The following methods are the public bindings required by the chrome
+// javascript code implementation. These binds hide the implementation details
+// in FF, IE, and Chrome.
+
+ceee.CloseChannel = function(portId) {
+ return ceee.sendMessage_('CloseChannel', [portId]);
+};
+
+ceee.PortAddRef = function(portId) {
+ return ceee.sendMessage_('PortAddRef', [portId]);
+};
+
+ceee.PortRelease = function(portId) {
+ return ceee.sendMessage_('PortRelease', [portId]);
+};
+
+ceee.PostMessage = function(portId, msg) {
+ return ceee.sendMessage_('PostMessage', [portId, msg]);
+};
+
+ceee.GetChromeHidden = function() {
+ return ceee.hidden;
+};
+
+ceee.AttachEvent = function(eventName) {
+ ceee.sendMessage_('AttachEvent', [eventName]);
+};
+
+ceee.DetachEvent = function(eventName) {
+ ceee.sendMessage_('DetachEvent', [eventName]);
+};
diff --git a/ceee/firefox/content/us/json.js b/ceee/firefox/content/us/json.js
new file mode 100644
index 0000000..0ef04e4
--- /dev/null
+++ b/ceee/firefox/content/us/json.js
@@ -0,0 +1,318 @@
+// Copyright 2006 Google Inc.
+// All Rights Reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the
+// distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * @fileoverview JSON utility functions.
+ */
+
+/*
+ * ORIGINAL VERSION: http://doctype.googlecode.com/svn/trunk/goog/json/json.js
+ *
+ * LOCAL CHANGES: None.
+ */
+
+goog.provide('goog.json');
+goog.provide('goog.json.Serializer');
+
+
+
+/**
+ * Tests if a string is an invalid JSON string. This only ensures that we are
+ * not using any invalid characters
+ * @param {string} s The string to test.
+ * @return {boolean} True if the input is a valid JSON string.
+ * @private
+ */
+goog.json.isValid_ = function(s) {
+ // All empty whitespace is not valid.
+ if (/^\s*$/.test(s)) {
+ return false;
+ }
+
+ // This is taken from http://www.json.org/json2.js which is released to the
+ // public domain.
+ // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
+ // inside strings. We also treat \u2028 and \u2029 as whitespace which they
+ // are in the RFC but IE and Safari does not match \s to these so we need to
+ // include them in the reg exps in all places where whitespace is allowed.
+ // We allowed \x7f inside strings because some tools don't escape it,
+ // e.g. http://www.json.org/java/org/json/JSONObject.java
+
+ // Parsing happens in three stages. In the first stage, we run the text
+ // against regular expressions that look for non-JSON patterns. We are
+ // especially concerned with '()' and 'new' because they can cause invocation,
+ // and '=' because it can cause mutation. But just to be safe, we want to
+ // reject all unexpected forms.
+
+ // We split the first stage into 4 regexp operations in order to work around
+ // crippling inefficiencies in IE's and Safari's regexp engines. First we
+ // replace all backslash pairs with '@' (a non-JSON character). Second, we
+ // replace all simple value tokens with ']' characters. Third, we delete all
+ // open brackets that follow a colon or comma or that begin the text. Finally,
+ // we look to see that the remaining characters are only whitespace or ']' or
+ // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ // Don't make these static since they have the global flag.
+ var backslashesRe = /\\["\\\/bfnrtu]/g;
+ var simpleValuesRe =
+ /"[^"\\\n\r\u2028\u2029\x00-\x1f\x80-\x9f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
+ var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
+ var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
+
+ return remainderRe.test(s.replace(backslashesRe, '@').
+ replace(simpleValuesRe, ']').
+ replace(openBracketsRe, ''));
+};
+
+
+/**
+ * Parses a JSON string and returns the result. This throws an exception if
+ * the string is an invalid JSON string.
+ *
+ * Note that this is very slow on large strings. If you trust the source of
+ * the string then you should use unsafeParse instead.
+ *
+ * @param {*} s The JSON string to parse.
+ * @return {Object} The object generated from the JSON string.
+ */
+goog.json.parse = function(s) {
+ var o = String(s);
+ if (goog.json.isValid_(o)) {
+ /** @preserveTry */
+ try {
+ return eval('(' + o + ')');
+ } catch (ex) {
+ }
+ }
+ throw Error('Invalid JSON string: ' + o);
+};
+
+
+/**
+ * Parses a JSON string and returns the result. This uses eval so it is open
+ * to security issues and it should only be used if you trust the source.
+ *
+ * @param {string} s The JSON string to parse.
+ * @return {Object} The object generated from the JSON string.
+ */
+goog.json.unsafeParse = function(s) {
+ return eval('(' + s + ')');
+};
+
+/**
+ * Serializes an object or a value to a JSON string.
+ *
+ * @param {Object} object The object to serialize.
+ * @throws Error if there are loops in the object graph.
+ * @return {string} A JSON string representation of the input.
+ */
+goog.json.serialize = function(object) {
+ return new goog.json.Serializer().serialize(object);
+};
+
+
+
+/**
+ * Class that is used to serialize JSON objects to a string.
+ * @constructor
+ */
+goog.json.Serializer = function() {
+};
+
+
+/**
+ * Serializes an object or a value to a JSON string.
+ *
+ * @param {Object?} object The object to serialize.
+ * @throws Error if there are loops in the object graph.
+ * @return {string} A JSON string representation of the input.
+ */
+goog.json.Serializer.prototype.serialize = function(object) {
+ var sb = [];
+ this.serialize_(object, sb);
+ return sb.join('');
+};
+
+
+/**
+ * Serializes a generic value to a JSON string
+ * @private
+ * @param {string|number|boolean|undefined|Object|Array} object The object to
+ * serialize.
+ * @param {Array} sb Array used as a string builder.
+ * @throws Error if there are loops in the object graph.
+ */
+goog.json.Serializer.prototype.serialize_ = function(object, sb) {
+ switch (typeof object) {
+ case 'string':
+ this.serializeString_((/** @type {string} */ object), sb);
+ break;
+ case 'number':
+ this.serializeNumber_((/** @type {number} */ object), sb);
+ break;
+ case 'boolean':
+ sb.push(object);
+ break;
+ case 'undefined':
+ sb.push('null');
+ break;
+ case 'object':
+ if (object == null) {
+ sb.push('null');
+ break;
+ }
+ if (goog.isArray(object)) {
+ this.serializeArray_(object, sb);
+ break;
+ }
+ // should we allow new String, new Number and new Boolean to be treated
+ // as string, number and boolean? Most implementations do not and the
+ // need is not very big
+ this.serializeObject_(object, sb);
+ break;
+ case 'function':
+ // Skip functions.
+ break;
+ default:
+ throw Error('Unknown type: ' + typeof object);
+ }
+};
+
+
+/**
+ * Character mappings used internally for goog.string.quote
+ * @private
+ * @type {Object}
+ */
+goog.json.Serializer.charToJsonCharCache_ = {
+ '\"': '\\"',
+ '\\': '\\\\',
+ '/': '\\/',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+
+ '\x0B': '\\u000b' // '\v' is not supported in JScript
+};
+
+
+/**
+ * Regular expression used to match characters that need to be replaced.
+ * The S60 browser has a bug where unicode characters are not matched by
+ * regular expressions. The condition below detects such behaviour and
+ * adjusts the regular expression accordingly.
+ * @private
+ * @type {RegExp}
+ */
+goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
+ /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g;
+
+
+/**
+ * Serializes a string to a JSON string
+ * @private
+ * @param {string} s The string to serialize.
+ * @param {Array} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
+ // The official JSON implementation does not work with international
+ // characters.
+ sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
+ // caching the result improves performance by a factor 2-3
+ if (c in goog.json.Serializer.charToJsonCharCache_) {
+ return goog.json.Serializer.charToJsonCharCache_[c];
+ }
+
+ var cc = c.charCodeAt(0);
+ var rv = '\\u';
+ if (cc < 16) {
+ rv += '000';
+ } else if (cc < 256) {
+ rv += '00';
+ } else if (cc < 4096) { // \u1000
+ rv += '0';
+ }
+ return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16);
+ }), '"');
+};
+
+
+/**
+ * Serializes a number to a JSON string
+ * @private
+ * @param {number} n The number to serialize.
+ * @param {Array} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
+ sb.push(isFinite(n) && !isNaN(n) ? n : 'null');
+};
+
+
+/**
+ * Serializes an array to a JSON string
+ * @private
+ * @param {Array} arr The array to serialize.
+ * @param {Array} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeArray_ = function(arr, sb) {
+ var l = arr.length;
+ sb.push('[');
+ var sep = '';
+ for (var i = 0; i < l; i++) {
+ sb.push(sep)
+ this.serialize_(arr[i], sb);
+ sep = ',';
+ }
+ sb.push(']');
+};
+
+
+/**
+ * Serializes an object to a JSON string
+ * @private
+ * @param {Object} obj The object to serialize.
+ * @param {Array} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
+ sb.push('{');
+ var sep = '';
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ var value = obj[key];
+ // Skip functions.
+ if (typeof value != 'function') {
+ sb.push(sep);
+ this.serializeString_(key, sb);
+ sb.push(':');
+ this.serialize_(value, sb);
+ sep = ',';
+ }
+ }
+ }
+ sb.push('}');
+}; \ No newline at end of file
diff --git a/ceee/firefox/content/userscript_api.js b/ceee/firefox/content/userscript_api.js
new file mode 100644
index 0000000..aefc66cc
--- /dev/null
+++ b/ceee/firefox/content/userscript_api.js
@@ -0,0 +1,1077 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains the implementation of the Chrome extension
+ * user script APIs for the CEEE Firefox add-on. This file is loaded by the
+ * overlay.xul file, and requires that overlay.js has already been loaded.
+ *
+ * @supported Firefox 3.x
+ */
+
+/**
+ * Constructor for user script management API object.
+ * @param {!Object} instance A reference the the main CEEE instance for
+ * this top-level window.
+ * @constructor
+ */
+var CEEE_UserScriptManager;
+
+(function() {
+
+/** A reference to the main CEEE instance for this top-level window. */
+var ceee;
+
+/**
+ * A map of open ports for this web page for all extensions. The key to the
+ * map is a portId (number) that is local to this web page. This portId is
+ * assigned by the call to OpenChannelToExtension.
+ *
+ * The port is considered opened once ChromeFrame responds with the "channel
+ * opened" event. This event will contain another portId assigned on the
+ * chrome side of the port, referred to here as the remote port Id. Because
+ * Chrome and ChromeFrame do not know about the portIds in this code, the
+ * functions below map from portId to remotePortId and back as needed.
+ */
+var ports = CEEE_globals.ports;
+
+/**
+ * An array of "content_scripts", as defined by the Chrome Extension API.
+ */
+var scripts;
+
+/**
+ * Path relative to the add-on root where user-related scripts are found.
+ * @const
+ */
+var USER_SCRIPTS_DIR = 'content/us';
+
+/**
+ * Create an nsIURL object from the given string spec.
+ *
+ * @param {!string} spec The string that should be converted into an nsURL
+ * object.
+ */
+function makeUrl(spec) {
+ var uri = CEEE_ioService.newURI(spec, null, null);
+ return uri && uri.QueryInterface(Components.interfaces.nsIURL);
+}
+
+/**
+ * Create an nsIURL object from the given nsIFile object.
+ *
+ * @param {!nsIFile} file The file that should be converted into an nsURL
+ * object.
+ */
+function makeFileUrl(file) {
+ var uri = CEEE_ioService.newFileURI(file);
+ return uri && uri.QueryInterface(Components.interfaces.nsIURL);
+}
+
+/**
+ * Is the given pattern a valid host pattern, as defined by the Chrome
+ * URLPattern class? There are only 3 possible values:
+ *
+ * - '*', which matches all hosts
+ * - '<anychar except '*'>'+, which matches an exact domain
+ * - '*.<anychar except '*'>'+, which matches a subdomain
+ *
+ * Its assumed that the pattern does not contain an slashes (/).
+ *
+ * @param {!string} pattern String pattern to test.
+ * @return true if the pattern is valid.
+ */
+function isValidHostPattern(pattern) {
+ return pattern == '*' || pattern.indexOf('*') == -1 ||
+ (pattern.charAt(0) == '*' && pattern.charAt(1) == '.' &&
+ pattern.substr(2).indexOf('*') == -1);
+}
+
+/**
+ * Does the given URL match one of the URL patterns given?
+ *
+ * @param {!Location} url URL string to match.
+ * @param {Array} patterns Array of patterns to match.
+ */
+function matchesPattern(url, patterns) {
+ // Some URLs may not have a host, like about:blank. In this case, we can
+ // never match. In firefox, this is implemented with the nsSimpleURI class.
+ // The implementation of this class will always throw an error when accessing
+ // the host property, so we first check to see if the protocol is not
+ // "about:" before checking the host. nsSimpleURI is designed specifically
+ // for about:
+ var protocol = url.protocol;
+ if (protocol == 'about:')
+ return false;
+
+ var host = url.host;
+ var path = url.pathname + url.search;
+
+ for (var i = 0; i < patterns.length; ++i) {
+ var pattern = patterns[i];
+
+ // Make sure protocols match.
+ if (pattern.protocolPattern != protocol) {
+ continue;
+ }
+
+ // Make sure hosts match.
+ var hostPattern = pattern.hostPattern;
+ if (hostPattern.length > 0) {
+ if (hostPattern.charAt(0) != '*') {
+ if (host != hostPattern) {
+ continue;
+ }
+ } else {
+ // Make sure the host ends with the pattern (minus initial *).
+ var sub = hostPattern.substr(1);
+ if (sub != host.match(sub)) {
+ continue;
+ }
+ }
+ }
+
+ // Make sure paths match.
+ var pathPattern = pattern.pathPattern;
+ if (path && !pathPattern.test(path)) {
+ continue;
+ }
+
+ // Found a match!
+ ceee.logInfo('URL=' + url.toString() + ' matches with ' +
+ pattern.protocolPattern + ' ' + pattern.hostPattern + ' ' +
+ pattern.pathPattern);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Checks whether the given window is the top window.
+ *
+ * @param {!nsIDOMWindow} w The window to test.
+ * @return {boolean} true if the given window is the top window.
+ */
+function isTopWindow(w) {
+ return w.top == w.self;
+}
+
+/**
+ * Get a File object for the given user script. Its assumed that the user
+ * script file name is relative to USER_SCRIPTS_DIR in the add-on.
+ *
+ * @param {!string} name Relative file name of the user script.
+ * @return A File object that represents the user script.
+ */
+function getScriptFile(name) {
+ var m = Components.classes['@mozilla.org/extensions/manager;1'].
+ getService(Components.interfaces.nsIExtensionManager);
+ var file = m.getInstallLocation(CEEE_globals.ADDON_ID)
+ .getItemFile(CEEE_globals.ADDON_ID, USER_SCRIPTS_DIR);
+
+ file.append(name);
+ return file;
+}
+
+/**
+ * Get a File object for the given user script. Its assumed that the user
+ * script file name is relative to the Chrome Extension root.
+ *
+ * @param {!string} name Relative file name of the user script.
+ * @return A File object that represents the user script.
+ */
+function getUserScriptFile(name) {
+ var file = ceee.getToolstripDir();
+ if (file) {
+ var parts = name.split('/');
+ for (var i = 0; i < parts.length; ++i) {
+ file.append(parts[i]);
+ }
+
+ if (!file.exists() || !file.isFile())
+ file = null;
+ }
+
+ if (file)
+ ceee.logInfo('User script: ' + file.path);
+
+ return file;
+}
+
+/**
+ * Evaluate a user-defined script file in the context of a web page, with
+ * debugging in mind. Instead of evaluating the script in a sandbox, which
+ * Firebug cannot see, a <script> element is added to the page.
+ *
+ * Note that this depends on the Chrome Extension APIs being avaiable to page
+ * code, which normally it is not. See prepareSandbox() for how these APIs
+ * are conditionally injected to be accessed from the page.
+ *
+ * For Firefox to allow loading of file URLs into remote pages, the security
+ * manager needs to be told to allow this. For more details about this see:
+ * - http://kb.mozillazine.org/Security_Policies
+ * - http://www.mozilla.org/projects/security/components/ConfigPolicy.html
+ *
+ * If this is not setup correctly, Firefox's error console will show a message
+ * similar to this:
+ *
+ * Security Error: Content at <remote-url> may not load or link to <file-url>.
+ *
+ * The security manager is tweaked appropriately in the options dialog, see
+ * the code in options.xul.
+ *
+ * @param {nsIDOMWindow} w Content window into which to evaluate the script.
+ * @param {!nsIFile} file The script to evaluate.
+ */
+function evalUserScriptFileForDebugging(w, file) {
+ // Create the script from the given file object.
+ var d = w.document;
+ var script = d.createElement('script');
+ script.type = 'text/javascript';
+ script.src = makeFileUrl(file).spec;
+ d.body.appendChild(script);
+ ceee.logInfo('Debugging script: ' + file.path);
+}
+
+/**
+ * Add the CSS code string to the document of the specified window.
+ *
+ * @param {!nsIDOMWindow} w Content window into which to evaluate the script.
+ * @param {string} code The CSS code string to evaluate.
+ */
+function addCssCodeToPage(w, code) {
+ var d = w.document;
+ var coll = d.getElementsByTagName('head');
+ if (!coll || coll.length != 1) {
+ ceee.logError('Cannot find <head> in doc=' + w.location.toString());
+ return;
+ }
+
+ var head = coll[0];
+ var css = d.createElement('style');
+ css.type = 'text/css';
+ css.appendChild(d.createTextNode(code));
+ head.appendChild(css);
+}
+
+/**
+ * Add the CSS file to the document of the specified window.
+ *
+ * @param {!nsIDOMWindow} w Content window into which to evaluate the script.
+ * @param {nsIFile} file The script to evaluate.
+ */
+function addCssToPage(w, file) {
+ // Read the CSS from the file into memory.
+ var data = {};
+ try {
+ var stream = ceee.openFileStream(file);
+ stream.readString(-1, data);
+ addCssCodeToPage(w, data.value);
+ ceee.logInfo('Loaded CSS file: ' + file.path);
+ } catch (ex) {
+ // Note that for now, this will happen regularly if you have more
+ // than one extension installed and the extension not being
+ // treated as the "one and only" by CEEE happens to call
+ // insertCss.
+ ceee.logError('addCssToPage, exception: ' + ex.message);
+ } finally {
+ if (stream)
+ stream.close();
+ }
+}
+
+/**
+ * Post an open channel message to ChromeFrame for the given port.
+ *
+ * @param {number} portId The internal port Id of the port to open.
+ * @param {string} extId The extension Id associated with this port.
+ * @param {string} name The name to assign to the port.
+ * @param {!Object} tabInfo Information about the tab containing the
+ * content script that is opening the channel.
+ */
+function postOpenChannel(portId, extId, name, tabInfo) {
+ var msg = {
+ rqid: 0, // 0 == open channel
+ extid: extId,
+ chname: name,
+ connid: portId,
+ tab: tabInfo
+ };
+
+ var cfh = ceee.getCfHelper();
+ cfh.postMessage(CEEE_json.encode(msg), cfh.TARGET_PORT_REQUEST);
+}
+
+/**
+ * Post a close channel message to ChromeFrame for the given port.
+ *
+ * @param {!Object} info Information about the port.
+ */
+function postCloseChannel(info) {
+ // Don't send another close channel message if this port has alredy been
+ // disconnected.
+ if (info.disconnected)
+ return;
+
+ // If the remote port id is not valid, just return. This could happen if
+ // the port is closed before we get a response from ChromeFrame about the
+ // port being opened.
+ var remotePortId = info.remotePortId;
+ if (!remotePortId || remotePortId == -1)
+ return;
+
+ var msg = {
+ rqid: 3, // 3 == close channel
+ portid: remotePortId
+ };
+
+ var cfh = ceee.getCfHelper();
+ cfh.postMessage(CEEE_json.encode(msg), cfh.TARGET_PORT_REQUEST);
+ info.disconnected = true;
+}
+
+/**
+ * Post a port message to ChromeFrame for the given port.
+ *
+ * @param {number} remotePortId The remote port Id of the port.
+ * @param {string} msg The message to post.
+ */
+function postPortMessage(remotePortId, msg) {
+ var msg2 = {
+ rqid: 2, // 2 == post message
+ portid: remotePortId,
+ data: msg
+ };
+
+ var cfh = ceee.getCfHelper();
+ cfh.postMessage(CEEE_json.encode(msg2), cfh.TARGET_PORT_REQUEST);
+}
+
+/**
+ * Find a port given its remote port Id. The remote port Id is the Id assigned
+ * to the port by chrome upon connection, which is different from the port Id
+ * assigned by the FF CEEE and used as the key in the ports map.
+ *
+ * May want to review this code to see if we want to optimize the remote port
+ * Id lookup.
+ *
+ * @param {number} remotePortId An integer value representing the Id.
+ */
+function findPortByRemoteId(remotePortId) {
+ for (var portId in ports) {
+ var info = ports[portId];
+ if (remotePortId == info.remotePortId)
+ return info;
+ }
+}
+
+/**
+ * Cleanup all resources used by the port with the given Id.
+ *
+ * @param {number} portId An integer value representing the FF CEEE port Id.
+ */
+function cleanupPort(portId) {
+ var info = ports[portId];
+ if (info) {
+ postCloseChannel(info);
+ delete ports[portId];
+ ceee.logInfo('cleanupPort: cleaned up portId=' + portId);
+ } else {
+ ceee.logInfo('cleanupPort: Invalid portId=' + portId);
+ }
+}
+
+/**
+ * Evaluate a user-defined script in the context of a web page.
+ *
+ * @param {!Object} w The DOM window in which to run the script.
+ * @param {string} script A string that represents the javascript code to
+ * run against a web page.
+ * @param {string=} opt_filename An optional string that contains the name of
+ * the script being executed.
+ * @private
+ */
+function evalUserScript(w, script, opt_filename) {
+ evalInSandboxWithCatch(script, prepareSandbox(w), opt_filename);
+}
+
+/**
+ * Prepare a sandbox for running user scripts.
+ *
+ * @param {!Object} w The content window in which the user scripts will run.
+ * @return A Firefox sandbox properly initialized for running user scripts.
+ */
+function prepareSandbox(w) {
+ // If the window already has a FF CEEE sandbox, use it instead of creating
+ // a new one.
+ if (w.ceeeSandbox) {
+ ceee.logInfo('Reusing sandbox for ' + w.location.toString());
+ return w.ceeeSandbox;
+ }
+
+ // We need to be careful to run the user script with limited privilege,
+ // and in the scope of the given window. We do this by creating a sandbox
+ // around that window and evaluating the script there.
+ //
+ // There are security issues with using the object that is returned from
+ // evaluating the expression in the sandbox. For now, I ignore the return
+ // value, see https://developer.mozilla.org/En/Components.utils.evalInSandbox.
+ //
+ // We wrap the window given argument because that object comes from the
+ // unsafe environment of the web page being shown. We want to expose our
+ // script to a 'safe' environment by default. However, just in case some
+ // user scripts really want the original window, we'll pass that in too.
+ //
+ // To sum up, the code in this file is considered 'privileged' code, and must
+ // be careful what it executes. There are two sources of unsafe code: the
+ // user script, which comes from the CEEE add-on, and the document+window
+ // that come from the window argument. We need to be careful with objects we
+ // get from both of these places.
+ var unsafeWindow = w;
+ var safeWindow = new XPCNativeWrapper(unsafeWindow);
+
+ // Create a sandbox for running the script and initialize the environment.
+ // TODO(rogerta@chromium.org): for perfomance reasons, we may want
+ // to cache the user scripts somehow.
+ var s = Components.utils.Sandbox(safeWindow);
+ s.window = safeWindow;
+ s.document = safeWindow.document;
+ s.__proto__ = s.window;
+
+ // TODO(rogerta@chromium.org): Not sure if I need to set this member.
+ // s.XPathResult = Components.interfaces.nsIDOMXPathResult;
+
+ // TODO(rogerta@chromium.org): for performance reasons. maybe want
+ // to concat all these files into one and eval once. Or maybe load
+ // all files into memory and eval from there instead of from file
+ // each time.
+ evalScriptFileInSandbox(getScriptFile('base.js'), s);
+ evalScriptFileInSandbox(getScriptFile('json.js'), s);
+ evalScriptFileInSandbox(getScriptFile('ceee_bootstrap.js'), s);
+ evalScriptFileInSandbox(getScriptFile('event_bindings.js'), s);
+ evalScriptFileInSandbox(getScriptFile('renderer_extension_bindings.js'), s);
+
+ s.JSON = s.goog && s.goog.json;
+
+ // Firebug may install it own console, so we won't override it here.
+ s.window.console = s.window.console || s.console;
+
+ // For test purposes, I will add the APIs to the actual page itself, so that
+ // test code can call it. This is to make the APIs available to javascript
+ // already in the page, not to the inject scripts, which already have the
+ // APIs.
+ if (ceee.contentScriptDebugging) {
+ w.wrappedJSObject.chrome = w.wrappedJSObject.chrome || s.chrome;
+
+ // When running with Firebug enabled, the window object will already
+ // have console property. However, Firebug declares console with as getter
+ // only, so the standard javascript pattern "foo = foo || bar;" causes an
+ // exception to be thrown. Therefore using an if() check.
+ if (!w.wrappedJSObject.console)
+ w.wrappedJSObject.console = s.console;
+
+ w.wrappedJSObject.goog = w.wrappedJSObject.goog || s.goog;
+ w.wrappedJSObject.JSON = w.wrappedJSObject.JSON || s.JSON;
+ w.wrappedJSObject.ceee = w.wrappedJSObject.ceee || s.ceee;
+ }
+
+ // Make sure the chrome extension execution environment is properly setup
+ // for the user script.
+ // TODO(rogerta@chromium.org): for now, this assumes only one
+ // extension at a time.
+ var createExtensionScript = 'chrome.initExtension("';
+ createExtensionScript += ceee.getToolstripExtensionId();
+ createExtensionScript += '")';
+ evalInSandboxWithCatch(createExtensionScript, s, 'createExtensionScript');
+
+ w.ceeeSandbox = s;
+ ceee.logInfo('Created new sandbox for ' + w.location.toString());
+ return s;
+}
+
+/**
+ * Evaluate a script file in the context of a sandbox.
+ *
+ * @param {!Object} file A Firefox nsIFile object containing the script.
+ * @param {!Object} s A Firefox sandbox.
+ */
+function evalScriptFileInSandbox(file, s) {
+ if (!file) {
+ ceee.logError('Trying to eval a null script');
+ return;
+ }
+
+ // Read the script from the file into memory, so that we can execute it.
+ var data = {};
+ try {
+ var stream = ceee.openFileStream(file);
+ stream.readString(-1, data);
+
+ // Execute the script.
+ evalInSandboxWithCatch(data.value, s, file.leafName);
+ } finally {
+ if (stream)
+ stream.close();
+ }
+}
+
+/**
+ * Evaluate a script in the context of a sandbox.
+ *
+ * @param {string} script A string that represents the javascript code to
+ * run against a web page.
+ * @param {!Object} s A Firefox sandbox.
+ * @param {string=} opt_filename An optional string that contains the name of
+ * the script being executed.
+ * @private
+ */
+function evalInSandboxWithCatch(script, s, opt_filename) {
+ try {
+ // To get around a bug in firefox where it incorrectly determines the line
+ // number of exceptions thrown from inside a sandbox, create an error
+ // here to be used later.
+ var helper = new Error();
+ evalInSandboxWrapper(script, s);
+ } catch (ex) {
+ var line = ex && ex.lineNumber;
+ // If the line number from the exception is MAX_INT, then we need to look
+ // harder for the line number. See if the exception has a location
+ // property, and if so, if it has a line number.
+ if (4294967295 == line) {
+ if (ex.location && ex.location.lineNumber) {
+ line = ex.location.lineNumber;
+ } else {
+ line = 0;
+ }
+ }
+
+ // Subtract the line number from the dummy exception created before the
+ // call into the sandbox. As explained above, this is to get around a
+ // bug in the way that Firefox reports line numbers in sandboxes.
+ if (line > helper.lineNumber) {
+ line -= helper.lineNumber;
+ } else {
+ line = 'unknown-line';
+ }
+
+ // Build the message to log to the console. If a 'filename' is specified,
+ // add it to the message.
+ var message = 'Error in ';
+ if (opt_filename)
+ message += opt_filename + ':';
+
+ ceee.logError('Sandbox: ' + message + line + ':: ' + ex);
+ }
+}
+
+/**
+ * Constructor for user script manager object.
+ *
+ * @param {!Object} instance A reference the the main CEEE instance for
+ * this top-level window.
+ */
+function UserScriptManager(instance) {
+ // The caller must specify the CEEE instance object.
+ if (!instance)
+ throw 'Must specify instance';
+
+ ceee = instance;
+ ceee.logInfo('UserScriptManager created');
+
+ // Register all handlers.
+ ceee.registerDomHandler('AttachEvent', this, this.AttachEvent);
+ ceee.registerDomHandler('DetachEvent', this, this.DetachEvent);
+ ceee.registerDomHandler('OpenChannelToExtension', this,
+ this.OpenChannelToExtension);
+ ceee.registerDomHandler('PortAddRef', this, this.PortAddRef);
+ ceee.registerDomHandler('PortRelease', this, this.PortRelease);
+ ceee.registerDomHandler('CloseChannel', this, this.CloseChannel);
+ ceee.registerDomHandler('PostMessage', this, this.PostMessage);
+ ceee.registerDomHandler('LogToConsole', this, this.LogToConsole);
+ ceee.registerDomHandler('ErrorToConsole', this, this.ErrorToConsole);
+}
+
+/**
+ * Find all content scripts parts (javascript code and CSS) in the manifest
+ * file and load them.
+ *
+ * @param {!Object} manifest An object representing a valid Chrome Extension
+ * manifest.
+ */
+UserScriptManager.prototype.loadUserScripts = function(manifest) {
+ // If the user scripts have already been loaded, then nothing to do. This
+ // happens when the second top-level window is opened, and the extension
+ // has already been parsed.
+ if (scripts && scripts.length > 0)
+ return;
+
+ CEEE_globals.scripts = manifest.content_scripts || [];
+ scripts = CEEE_globals.scripts;
+ var count = (scripts && scripts.length) || 0;
+
+ for (var i = 0; i < count; ++i) {
+ var script = scripts[i];
+ var matches = script.matches;
+ if (!matches)
+ continue;
+
+ // Convert the URL strings in the matches property to two string: one for
+ // matching host names, and another for matching paths. These two parts
+ // are matched independently, as described in the Chrome URLPattern class.
+ var patterns = [];
+ for (var j = 0; j < matches.length; ++j) {
+ var spec = matches[j];
+ var url = makeUrl(spec);
+
+ // Make a pattern from the host glob.
+ var hostPattern = url.host;
+ if (!isValidHostPattern(hostPattern)) {
+ ceee.logError('Invalid user script pattern: ' + hostPattern);
+ continue;
+ }
+
+ // Make a regex from the path glob pattern.
+ var pathPattern = url.path;
+
+ // Replace all stars (*) with '.*'.
+ // Replace all '?' with '\?'.
+ pathPattern = pathPattern.replace(/\*/g, '.*');
+ pathPattern = pathPattern.replace(/\?/g, '\\?');
+
+ // The nsIURL object's scheme property is the protocol of the URL, but
+ // without the trailing colon. This will eventually be compared to a
+ // windows.location object, that has a protocol property *with* the
+ // trailing the colon. So I am adding the colon here to make things
+ // easier to compare later.
+ var pattern = {
+ protocolPattern: url.scheme + ':',
+ hostPattern: hostPattern,
+ pathPattern: new RegExp(pathPattern)
+ };
+ patterns.push(pattern);
+
+ ceee.logInfo('loadUserScripts: url=' + spec +
+ ' pattern=' + CEEE_json.encode(pattern));
+ }
+
+ script.patterns = patterns;
+
+ // Set the default value for all_frames
+ if (script.all_frames === undefined) {
+ script.all_frames = false;
+ }
+ }
+
+ ceee.logInfo('loadUserScripts: done count=' + count);
+};
+
+/**
+ * Run all user scripts installed on the system that match the URL of the
+ * document attached to the window specified in the w argument.
+ *
+ * @param {!Object} w The content window in which to run any matching user
+ * scripts.
+ */
+UserScriptManager.prototype.runUserScripts = function(w) {
+ // Note that we read the userscript files from disk every time that a page
+ // loads. This offers us the advantage of not having to reload scripts into
+ // memory when an extension autoupdates in Chrome. If we decide to cache
+ // these scripts in memory, then we will need a Chrome extension autoupdate
+ // automation notification.
+
+ ceee.logInfo('runUserScripts: starting');
+ var s = prepareSandbox(w);
+
+ // Run each user script whose pattern matches the URL of content window.
+ for (var i = 0; i < scripts.length; ++i) {
+ var script = scripts[i];
+ if ((script.all_frames || isTopWindow(w)) &&
+ matchesPattern(w.location, script.patterns)) {
+ // First inject CSS script, in case the javascript code assume the styles
+ // already exist.
+ var css = script.css || [];
+ var css_length = css.length;
+ for (var j = 0; j < css_length; ++j) {
+ var f = getUserScriptFile(css[j]);
+ addCssToPage(w, f);
+ }
+
+ // Now inject the javascript code.
+ var js = script.js || [];
+ var js_length = js.length;
+ for (var j = 0; j < js_length; ++j) {
+ var f = getUserScriptFile(js[j]);
+ if (ceee.contentScriptDebugging) {
+ evalUserScriptFileForDebugging(w, f);
+ } else {
+ evalScriptFileInSandbox(f, s);
+ }
+ }
+ }
+ }
+
+ ceee.logInfo('runUserScripts: done');
+};
+
+/**
+ * Execute a script file in the context of the given window.
+ *
+ * @param {!Object} w Content window context to execute in.
+ * @param {!Object} scriptDef A script definition object as defined by the
+ * Chrome Extensions API.
+ */
+UserScriptManager.prototype.executeScript = function(w, scriptDef) {
+ if (!ceee.getToolstripDir()) {
+ var self = this;
+ ceee.runWhenExtensionsInited(w, 'executeScript', function() {
+ self.executeScript(w, scriptDef);
+ });
+ ceee.logInfo('Deferred executeScript');
+ return;
+ }
+ ceee.logInfo('Executing executeScript');
+
+ var s = prepareSandbox(w);
+
+ if (scriptDef.code && scriptDef.file)
+ throw new Error('Code and file should not be specified at the same ' +
+ 'time in the second argument.');
+
+ if (scriptDef.code) {
+ evalInSandboxWithCatch(scriptDef.code, s, 'executeScripts.scriptDef.js');
+ } else if (scriptDef.file) {
+ try {
+ evalScriptFileInSandbox(getUserScriptFile(scriptDef.file), s);
+ } catch (ex) {
+ // Note that for now, this will happen regularly if you have more
+ // than one extension installed and the extension not being
+ // treated as the "one and only" by CEEE happens to call
+ // insertCss.
+ ceee.logError('executeScript, exception: ' + ex.message);
+ }
+ }
+};
+
+/**
+ * Insert a CSS file or script in the context of the given window.
+ *
+ * @param {!Window} w Content window context to execute in.
+ * @param {!Object} details A script definition object as defined by the
+ * Chrome Extensions API.
+ */
+UserScriptManager.prototype.insertCss = function(w, details) {
+ if (!ceee.getToolstripDir()) {
+ var self = this;
+ ceee.runWhenExtensionsInited(w, 'insertCss', function() {
+ self.insertCss(w, details);
+ });
+ ceee.logInfo('Deferred insertCss');
+ return;
+ }
+ ceee.logInfo('Executing insertCss');
+
+ if (details.code && details.file)
+ throw new Error('Code and file should not be specified at the same ' +
+ 'time in the second argument.');
+
+ if (details.code) {
+ addCssCodeToPage(w, details.code);
+ } else if (details.file) {
+ addCssToPage(w, getUserScriptFile(details.file));
+ } else {
+ throw new Error('No source code or file specified.');
+ }
+};
+
+/**
+ * Perform any cleanup required when a content window is unloaded.
+ *
+ * @param {!Object} w Content window being unloaded.
+ */
+UserScriptManager.prototype.onContentUnloaded = function(w) {
+ var script = 'ceee.hidden.dispatchOnUnload();';
+ evalUserScript(w, script, 'onContentUnloaded_cleanup');
+ this.cleanupPortsForWindow(w);
+ delete w.ceeeSandbox;
+};
+
+/**
+ * Cleanup port information for a closing window.
+ *
+ * @param {!Object} w Content window being closed.
+ */
+UserScriptManager.prototype.cleanupPortsForWindow = function(w) {
+ var toCleanup = [];
+
+ // First look though all the ports, looking for those that need to be cleaned
+ // up. Remember those for later.
+ for (portId in ports) {
+ var info = ports[portId];
+ if (w == info.contentWindow)
+ toCleanup.push(portId);
+ }
+
+ // Now loop though all ports that need to be cleaned up and do it.
+ for (var i = 0; i < toCleanup.length; ++i) {
+ var portId = toCleanup[i];
+ cleanupPort(portId);
+ }
+};
+
+/**
+ * Handle the response from a port request. Port requests are created via
+ * the functions below, like OpenChannelToExtension and PostMessage.
+ *
+ * Response of Post Message Request from Userscript Port to Extenstion
+ * -------------------------------------------------------------------
+ * The data property of the evt argument has the form:
+ *
+ * {
+ * 'rqid': 1, // 1 == channel opened
+ * 'connid': <portId>, // the key to ports map
+ * 'portid': <remoteId> // the port Id assigned on the extension side
+ * }
+ *
+ * Post Message Request from Extenstion to Userscript Port
+ * -------------------------------------------------------
+ * The data property of the evt argument has the form:
+ *
+ * {
+ * 'rqid': 1, // 2 == post message
+ * 'portid': <remoteId> // the port Id assigned on the extension side
+ * 'data': <message> // message being posted. A string
+ * }
+ *
+ * @param {!Object} evt An event object sent from ChromeFrame.
+ * @private
+ */
+UserScriptManager.prototype.onPortEvent = function(evt) {
+ var data = CEEE_json.decode(evt.data);
+ if (!data)
+ return;
+
+ if (data.rqid == 1) { // 1 == CHANNEL_OPENED
+ var info = ports[data.connid];
+ if (info) {
+ info.remotePortId = data.portid;
+
+ if (info.remotePortId != -1) {
+ ceee.logInfo('onPortEvent: CHANNEL_OPENED portId=' + info.portId +
+ ' <-> remotePortId=' + info.remotePortId);
+
+ // If this port has any pending messages, post them all now.
+ var q = info.queue;
+ if (q) {
+ ceee.logInfo('onPortEvent: posting deferred messages for portId='
+ + info.portId);
+ while (q.length > 0) {
+ var msg = q.shift();
+ postPortMessage(info.remotePortId, msg);
+ }
+ delete info.queue;
+ }
+ } else {
+ ceee.logError('onPortEvent: bad remote port for portId=' +
+ info.portId);
+ var q = info.queue;
+ if (q) {
+ while (q.length > 0) {
+ q.shift();
+ }
+ delete info.queue;
+ }
+ }
+ } else {
+ ceee.logError('onPortEvent: No port object found, rqid=1 connid=' +
+ data.connid);
+ }
+ } else if (data.rqid == 2) { // 2 == POST_MESSAGE
+ // Fire the given message to the appropriate port in the correct content
+ // window.
+ var info = findPortByRemoteId(data.portid);
+ if (info) {
+ var msgt = data.data.replace(/\\/gi, '\\\\');
+ var msg = msgt.replace(/"/gi, '\\"');
+ var script = 'ceee.hidden.Port.dispatchOnMessage("{msg}",{portId});';
+ script = script.replace(/{msg}/, msg);
+ script = script.replace(/{portId}/, info.portId);
+
+ evalUserScript(info.contentWindow, script, 'onPortEvent<post_message>');
+ } else {
+ ceee.logError('Could not find port for remote id=' + data.portid);
+ }
+ } else if (data.rqid == 3) { // 3 == CHANNEL_CLOSED
+ var info = findPortByRemoteId(data.portid);
+ if (info) {
+ var script = 'ceee.hidden.Port.dispatchOnDisconnect({portId});';
+ script = script.replace(/{portId}/, info.portId);
+ evalUserScript(info.contentWindow, script, 'onPortEvent<channel_closed>');
+ cleanupPort(info.portId);
+ }
+ } else {
+ ceee.logError('onPortEvent: unexptected rqid=' + data.rqid);
+ }
+};
+
+UserScriptManager.prototype.AttachEvent = function(w, cmd, data) {
+ //alert('AttachEvent '+ CEEE_json.encode(data.args));
+};
+
+UserScriptManager.prototype.DetachEvent = function(w, cmd, data) {
+ //alert('DetachEvent '+ CEEE_json.encode(data.args));
+};
+
+UserScriptManager.prototype.OpenChannelToExtension = function(w, cmd, data) {
+ var args = data.args;
+ if (args.length != 3) {
+ this.logError_('OpenChannelToExtension: invalid arguments');
+ return;
+ }
+
+ var portId = CEEE_globals.getNextPortId();
+
+ // Save some information about this port.
+ var info = {};
+ info.refCount = 0;
+ info.portId = portId;
+ info.sourceExtId = args[0];
+ info.targetExtId = args[1];
+ info.name = args[2];
+ info.contentWindow = w;
+ info.disconnected = false;
+ ports[portId] = info;
+ var tabInfo = null;
+ var mainBrowser = document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ var container = mainBrowser && mainBrowser.tabContainer;
+ if (container) {
+ // Build a tab info object that corresponds to the sender tab.
+ // Note that the content script may be running in a child window to the one
+ // that corresponds to the tab; we use the top-level window here to get the
+ // correct tab information.
+ var index = mainBrowser.getBrowserIndexForDocument(w.top.document);
+ var tab = container.getItemAtIndex(index);
+ tabInfo = ceee.getTabModule().buildTabValue(mainBrowser, tab);
+ }
+ postOpenChannel(portId, info.targetExtId, info.name, tabInfo);
+ return portId;
+};
+
+UserScriptManager.prototype.PortAddRef = function(w, cmd, data) {
+ var args = data.args;
+ if (args.length != 1) {
+ this.logError_('PortAddRef: invalid arguments');
+ return;
+ }
+
+ var portId = args[0];
+ var info = ports[portId];
+ if (info)
+ info.refCount++;
+};
+
+UserScriptManager.prototype.PortRelease = function(w, cmd, data) {
+ var args = data.args;
+ if (args.length != 1) {
+ this.logError_('PortRelease: invalid arguments');
+ return;
+ }
+
+ var portId = args[0];
+ var info = ports[portId];
+ if (info) {
+ if (--info.refCount == 0)
+ cleanupPort(portId);
+ }
+};
+
+UserScriptManager.prototype.CloseChannel = function(w, cmd, data) {
+ var args = data.args;
+ if (args.length != 1) {
+ this.logError_('CloseChannel: invalid arguments');
+ return;
+ }
+
+ var portId = args[0];
+ var info = ports[portId];
+ if (info) {
+ // We tell the other side that the port is closed, but we don't cleanup
+ // the info object just yet. It will get cleaned up either when the
+ // reference count goes to zero, or during the cleanup phase of a window
+ // unload.
+ postCloseChannel(info);
+ }
+};
+
+UserScriptManager.prototype.PostMessage = function(w, cmd, data) {
+ if (data.args.length != 2) {
+ this.logError_('PostMessage: invalid arguments');
+ return;
+ }
+
+ var portId = data.args[0];
+ var msg = data.args[1];
+ var info = ports[portId];
+ if (info) {
+ // Before posting or queuing the message, turn it into a string.
+ if (typeof msg == 'object') {
+ if (typeof msg != 'string')
+ msg = CEEE_json.encode(msg);
+ } else {
+ msg = msg.toString();
+ }
+
+ // If remotePortId is -1, this means there was a problem opening this port.
+ // Don't even bother trying to post a message in this case.
+ if (info.remotePortId != -1) {
+ if (typeof info.remotePortId != 'undefined') {
+ postPortMessage(info.remotePortId, msg);
+ } else {
+ // Queue up message for port. This message will eventually be sent
+ // when the port is fully opened. Messages are queued in the order in
+ // which they are posted.
+ info.queue = info.queue || [];
+ info.queue.push(msg);
+ ceee.logInfo('PostMessage: deferred for portId=' + portId);
+ }
+ }
+ } else {
+ ceee.logError('PostMessage: No port object found portId=' + portId +
+ ' doc=' + w.document.location);
+ }
+};
+
+UserScriptManager.prototype.LogToConsole = function(w, cmd, data) {
+ if (data.args.length == 0) {
+ ceee.logError('LogToConsole: needs one argument');
+ return;
+ }
+
+ ceee.logInfo('Userscript says: ' + data.args[0]);
+};
+
+UserScriptManager.prototype.ErrorToConsole = function(w, cmd, data) {
+ if (data.args.length == 0) {
+ ceee.logError('ErrorToConsole: needs one argument');
+ return;
+ }
+
+ ceee.logError('Userscript says: ' + data.args[0]);
+};
+
+// Make the constructor visible outside this anonymous block.
+CEEE_UserScriptManager = UserScriptManager;
+
+// This wrapper is purposefully the last function in this file, as far down
+// as possible, for code coverage reasons. It seems that code executed by
+// evalInSandbox() is counted as code executing in this file, and the line
+// number reported to coverage is the line number of the evalInSandbox() call
+// plus the line inside script. This messes up all the coverage data for all
+// lines in this file after the call to evalInSandbox(). To minimize the
+// problem, the call to Components.utils.evalInSandbox() is being placed as far
+// to the end of this file as possible.
+function evalInSandboxWrapper(script, s) {
+ Components.utils.evalInSandbox(script, s);
+}
+})();
diff --git a/ceee/firefox/content/window_api.js b/ceee/firefox/content/window_api.js
new file mode 100644
index 0000000..a3909c9
--- /dev/null
+++ b/ceee/firefox/content/window_api.js
@@ -0,0 +1,355 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file contains the implementation of the Chrome extension
+ * windows APIs for the CEEE Firefox add-on. This file is loaded by the
+ * overlay.xul file, and requires that overlay.js has already been loaded.
+ *
+ * @supported Firefox 3.x
+ */
+
+var CEEE_window_internal_ = CEEE_window_internal_ || {
+ /**
+ * Reference to the instance object for the CEEE.
+ * @private
+ */
+ ceeeInstance_: null,
+
+ /**
+ * Log an informational message to the Firefox error console.
+ * @private
+ */
+ logInfo_: null,
+
+ /**
+ * Log an error message to the Firefox error console.
+ * @private
+ */
+ logError_: null,
+
+ /**
+ * List of additional window sources. For example, infobars API is supposed
+ * to return window objects which don't correspond to top-level Firefox
+ * windows. Each element is a function which takes window id and returns a
+ * window-like object.
+ * @type Array.<function(number):Object>
+ */
+ fakeWindowLookup_: []
+};
+
+/**
+ * Build an object that represents a "window", as defined by the Google Chrome
+ * extension API.
+ *
+ * @param {!Object} win Firefox chrome window.
+ * @param {boolean} opt_populate If specified and true, populate the returned
+ * info object with information about the tabs in this window.
+ */
+CEEE_window_internal_.buildWindowValue = function(win, opt_populate) {
+ var winRecent = CEEE_mozilla_windows.service.getMostRecentWindow(
+ CEEE_mozilla_windows.WINDOW_TYPE);
+ var info = {};
+ info.id = CEEE_mozilla_windows.getWindowId(win);
+ info.focused = (win ==
+ winRecent.QueryInterface(Components.interfaces.nsIDOMWindow));
+ info.top = win.screenY;
+ info.left = win.screenX;
+ info.width = win.outerWidth;
+ info.height = win.outerHeight;
+ info.incognito = CEEE_globals.privateBrowsingService.isInPrivateBrowsing;
+ // Type can be any of 'normal', 'popup' or 'app'. Firefox doesn't support app
+ // windows, and we use chrome to create popups so Firefox doesn't know about
+ // them (i.e. all windows will be of type 'normal').
+ // TODO(mad@chromium.org): detect popups now that we support
+ // them. Not sure how though...
+ info.type = 'normal';
+
+ if (opt_populate) {
+ info.tabs = [];
+ var mainBrowser =
+ win.document.getElementById(CEEE_globals.MAIN_BROWSER_ID);
+ var container = mainBrowser.tabContainer;
+
+ for (var i = 0; i < container.itemCount; ++i) {
+ var tab = container.getItemAtIndex(i);
+ var tabInfo = this.ceeeInstance_.getTabModule().buildTabValue(
+ mainBrowser, tab);
+ info.tabs.push(tabInfo);
+ }
+ }
+
+ return info;
+};
+
+/**
+ * Called when a new top-level window is created.
+ *
+ * @param w The top-level window that was created.
+ * @private
+ */
+CEEE_window_internal_.onWindowOpened_ = function(w) {
+ // Send the event notification to ChromeFrame.
+ var info = [this.buildWindowValue(w)];
+ var msg = ['windows.onCreated', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+/**
+ * Called when a new top-level window is closed.
+ *
+ * @param w The top-level window that was closed.
+ * @private
+ */
+CEEE_window_internal_.onWindowRemoved_ = function(w) {
+ // Send the event notification to ChromeFrame.
+ var info = [CEEE_mozilla_windows.getWindowId(w)];
+ var msg = ['windows.onRemoved', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+/**
+ * Called when a top-level window gets focus.
+ *
+ * @param w The top-level window that got focus.
+ * @private
+ */
+CEEE_window_internal_.onWindowFocused_ = function(w) {
+ // Send the event notification to ChromeFrame.
+ var info = [CEEE_mozilla_windows.getWindowId(w)];
+ var msg = ['windows.onFocusChanged', CEEE_json.encode(info)];
+ this.ceeeInstance_.getCfHelper().postMessage(
+ CEEE_json.encode(msg),
+ this.ceeeInstance_.getCfHelper().TARGET_EVENT_REQUEST);
+};
+
+CEEE_window_internal_.CMD_GET_WINDOW = 'windows.get';
+CEEE_window_internal_.getWindow_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var win = CEEE_mozilla_windows.findWindow(id);
+ if (!win) {
+ throw(new Error(CEEE_window_internal_.CMD_GET_WINDOW +
+ ': invalid window id=' + id));
+ }
+
+ return this.buildWindowValue(win);
+};
+
+CEEE_window_internal_.CMD_GET_CURRENT_WINDOW = 'windows.getCurrent';
+CEEE_window_internal_.getCurrentWindow_ = function(cmd, data) {
+ return this.buildWindowValue(window);
+};
+
+CEEE_window_internal_.CMD_GET_LAST_FOCUSED_WINDOW =
+ 'windows.getLastFocused';
+CEEE_window_internal_.getLastFocusedWindow_ = function(cmd, data) {
+ var win = CEEE_mozilla_windows.service.getMostRecentWindow(
+ CEEE_mozilla_windows.WINDOW_TYPE);
+ return this.buildWindowValue(win);
+};
+
+CEEE_window_internal_.CMD_GET_ALL_WINDOWS = 'windows.getAll';
+CEEE_window_internal_.getAllWindows_ = function(cmd, data) {
+ var args = data.args && CEEE_json.decode(data.args);
+ var populate = args && args[0] && args[0].populate;
+ var e = CEEE_mozilla_windows.service.getEnumerator(
+ CEEE_mozilla_windows.WINDOW_TYPE);
+ var ret = [];
+ while (e.hasMoreElements()) {
+ var win = e.getNext();
+ var info = this.buildWindowValue(win, populate);
+ ret.push(info);
+ }
+
+ return ret;
+};
+
+CEEE_window_internal_.CMD_CREATE_WINDOW = 'windows.create';
+CEEE_window_internal_.createWindow_ = function(cmd, data) {
+ var args_list = data.args && CEEE_json.decode(data.args);
+ var args = args_list && args_list[0];
+ var url = '';
+ if (args && args.url)
+ url = args.url;
+
+ // TODO(rogerta@chromium.org): need to properly validate URL string.
+ // I noticed that if its a stripped down string, like 'google.ca',
+ // it tries to open a file from the local hard disk like
+ // jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/chrome/
+ // browser.jar!/content/browser/google.ca
+
+ // To avoid flickering, we set the position, size and popup attributes
+ // as part of the features string of the open function.
+
+ // Unfortunately, the behavior is a little weird here.
+ // FF documentation excerpt:
+ // https://developer.mozilla.org/En/DOM/Window.open
+ // "If you define the strWindowFeatures parameter, then the features
+ // that are not listed, requested in the string will be disabled or
+ // removed (except titlebar and close which are by default yes)."
+ // So we must specify the default values ourselves based on current
+ // window state.
+
+ // We tried to find a way to dynamically enumerate the window
+ // properties that have a visible sub-property but failed. This would
+ // have allowed us to support new visibility features as they get added.
+ // One of the problems with this approach is that the feature name is not
+ // always the same as the window property name, like location[bar] or
+ // status[bar]. Ho Well...
+
+ // Build the feature object with some default values.
+ var features = {
+ toolbar: window.toolbar.visible ? 'yes' : 'no',
+ location: window.locationbar.visible ? 'yes' : 'no',
+ menubar: window.menubar.visible ? 'yes' : 'no',
+ statusbar: window.menubar.visible ? 'yes' : 'no',
+ personalbar: window.personalbar.visible ? 'yes' : 'no',
+ scrollbars: window.scrollbars.visible ? 'yes' : 'no',
+ resizable: 'yes' // We can't get the current state of the window.
+ };
+
+ if (args) {
+ if (args.top)
+ features.top = args.top;
+ if (args.left)
+ features.left = args.left;
+ if (args.width)
+ features.outerWidth = args.width;
+ if (args.height)
+ features.outerHeight = args.height;
+ if (args.type == 'popup') {
+ features.toolbar = 'no';
+ features.location = 'no';
+ features.menubar = 'no';
+ features.status = 'no';
+ }
+ }
+
+ var options = [];
+ for (var i in features) {
+ options.push(i + '=' + features[i]);
+ }
+ options = options.join(',');
+
+ // Window name of '_blank' means 'always open a new window'. Note that the
+ // return value of open() is the content window used to display the specified
+ // URL, and not the top level navigator window we actually want here. Hence
+ // we need to map from the former to the latter. I have verified that this
+ // always opens a new top level window, even if the user has specified the
+ // option "New pages should be opened in a new tab".
+ var win = open(url, '_blank', options);
+ var winTopLevel =
+ CEEE_mozilla_windows.findWindowFromContentWindow(win).window;
+ if (winTopLevel) {
+ return this.buildWindowValue(winTopLevel);
+ }
+
+ throw(new Error(CEEE_window_internal_.CMD_CREATE_WINDOW +
+ ': error url=' + url));
+};
+
+CEEE_window_internal_.CMD_UPDATE_WINDOW = 'windows.update';
+CEEE_window_internal_.updateWindow_ = function(cmd, data) {
+ var args = data.args && CEEE_json.decode(data.args);
+ if (!args || args.length < 2 || !args[1]) {
+ throw(new Error(CEEE_window_internal_.CMD_UPDATE_WINDOW +
+ ': invalid arguments'));
+ }
+
+ var id = args[0];
+ var win = CEEE_mozilla_windows.findWindow(id);
+ if (!win) {
+ throw(new Error(CEEE_window_internal_.CMD_UPDATE_WINDOW +
+ ': invalid window id=' + id));
+ }
+
+ var info = args[1];
+
+ if (info.top)
+ win.screenY = info.top;
+
+ if (info.left)
+ win.screenX = info.left;
+
+ if (info.width)
+ win.outerWidth = info.width;
+
+ if (info.height)
+ win.outerHeight = info.height;
+
+ return this.buildWindowValue(win);
+};
+
+CEEE_window_internal_.CMD_REMOVE_WINDOW = 'windows.remove';
+CEEE_window_internal_.removeWindow_ = function(cmd, data) {
+ var args = CEEE_json.decode(data.args);
+ var id = args[0];
+ var win = CEEE_mozilla_windows.findWindow(id);
+ var lookupList = CEEE_window_internal_.fakeWindowLookup_;
+ for (var i = 0; !win && i < lookupList.length; ++i) {
+ win = lookupList[i](id);
+ }
+ if (!win) {
+ throw(new Error(CEEE_window_internal_.CMD_REMOVE_WINDOW +
+ ': invalid window id=' + id));
+ }
+
+ win.close();
+};
+
+/**
+ * Add lookup function for fake windows (e.g. infobar). Lookup function is
+ * passed window id and should return window-like object if there is a
+ * correspondence.
+ * @param {function(number):Object} lookup
+ */
+CEEE_window_internal_.registerLookup = function(lookup) {
+ CEEE_window_internal_.fakeWindowLookup_.push(lookup);
+};
+
+/**
+ * Initialization routine for the CEEE Windows API module.
+ * @param {!Object} ceeeInstance Reference to the global ceee instance.
+ * @return {Object} Reference to the window module.
+ * @public
+ */
+function CEEE_initialize_windows(ceeeInstance) {
+ CEEE_window_internal_.ceeeInstance_ = ceeeInstance;
+ var windows = CEEE_window_internal_;
+
+ // Register the extension handling functions with the CEEE instance.
+ ceeeInstance.registerExtensionHandler(windows.CMD_GET_WINDOW,
+ windows,
+ windows.getWindow_);
+ ceeeInstance.registerExtensionHandler(windows.CMD_GET_CURRENT_WINDOW,
+ windows,
+ windows.getCurrentWindow_);
+ ceeeInstance.registerExtensionHandler(windows.CMD_GET_LAST_FOCUSED_WINDOW,
+ windows,
+ windows.getLastFocusedWindow_);
+ ceeeInstance.registerExtensionHandler(windows.CMD_GET_ALL_WINDOWS,
+ windows,
+ windows.getAllWindows_);
+ ceeeInstance.registerExtensionHandler(windows.CMD_CREATE_WINDOW,
+ windows,
+ windows.createWindow_);
+ ceeeInstance.registerExtensionHandler(windows.CMD_UPDATE_WINDOW,
+ windows,
+ windows.updateWindow_);
+ ceeeInstance.registerExtensionHandler(windows.CMD_REMOVE_WINDOW,
+ windows,
+ windows.removeWindow_);
+
+ // Install the error/status reporting methods for this module from the
+ // owning CEEE instance.
+ windows.logInfo_ = ceeeInstance.logInfo;
+ windows.logError_ = ceeeInstance.logError;
+
+ return windows;
+}