Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Spin off AwesomeBar HD from Home Dash.

Combine the location and search bar with category searches.
  • Loading branch information...
commit 89afdc206cfcc7c7a8b13f06c201b69fa1520790 1 parent 6be85dc
@Mardak Mardak authored
View
743 awesomeBarHD/bootstrap.js
@@ -0,0 +1,743 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is AwesomeBar HD.
+ *
+ * The Initial Developer of the Original Code is The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Edward Lee <edilee@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+const global = this;
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Keep a reference to the top level providers data
+let allProviders;
+
+// Get and set preferences under the prospector pref branch
+XPCOMUtils.defineLazyGetter(global, "prefs", function() {
+ Cu.import("resource://services-sync/ext/Preferences.js");
+ return new Preferences("extensions.prospector.awesomeBarHD.");
+});
+
+// Remove existing Firefox UI and add in custom AwesomeBar HD
+function addAwesomeBarHD(window) {
+ let {async, change, createNode} = makeWindowHelpers(window);
+ let {document, gBrowser, gIdentityHandler, gURLBar} = window;
+
+ // Get references to existing UI elements
+ let origIdentity = gIdentityHandler._identityBox;
+ let origInput = gURLBar.mInputField;
+
+ // Add an icon to indicate the active category
+ let iconBox = createNode("box");
+ iconBox.setAttribute("align", "center");
+ iconBox.setAttribute("hidden", true);
+ iconBox.setAttribute("id", "identity-box");
+ origIdentity.parentNode.insertBefore(iconBox, origIdentity.nextSibling);
+
+ unload(function() {
+ iconBox.parentNode.removeChild(iconBox);
+ });
+
+ let providerIcon = createNode("image");
+ providerIcon.setAttribute("id", "page-proxy-favicon");
+ iconBox.appendChild(providerIcon);
+
+ // Add stuff around the original urlbar input box
+ let urlbarStack = createNode("stack");
+ origInput.parentNode.insertBefore(urlbarStack, origInput.nextSibling);
+
+ urlbarStack.setAttribute("flex", 1);
+
+ unload(function() {
+ urlbarStack.parentNode.removeChild(urlbarStack);
+ });
+
+ // Create a browser to prefetch search results
+ let prefetcher = createNode("browser");
+ prefetcher.setAttribute("autocompletepopup", gBrowser.getAttribute("autocompletepopup"));
+ prefetcher.setAttribute("collapsed", true);
+ prefetcher.setAttribute("contextmenu", gBrowser.getAttribute("contentcontextmenu"));
+ prefetcher.setAttribute("tooltip", gBrowser.getAttribute("contenttooltip"));
+ prefetcher.setAttribute("type", "content");
+ gBrowser.appendChild(prefetcher);
+
+ // Save the prefetched page to a tab in the browser
+ prefetcher.persistTo = function(targetTab) {
+ let targetBrowser = targetTab.linkedBrowser;
+ targetBrowser.stop();
+
+ // Unhook our progress listener
+ let selectedIndex = targetTab._tPos;
+ const filter = gBrowser.mTabFilters[selectedIndex];
+ let tabListener = gBrowser.mTabListeners[selectedIndex];
+ targetBrowser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(tabListener);
+ let tabListenerBlank = tabListener.mBlank;
+
+ // Restore current registered open URI
+ let previewURI = prefetcher.currentURI;
+ let openPage = gBrowser._placesAutocomplete;
+ if (targetBrowser.registeredOpenURI) {
+ openPage.unregisterOpenPage(targetBrowser.registeredOpenURI);
+ delete targetBrowser.registeredOpenURI;
+ }
+ openPage.registerOpenPage(previewURI);
+ targetBrowser.registeredOpenURI = previewURI;
+
+ // Save the last history entry from the preview if it has loaded
+ let history = prefetcher.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
+ let lastEntry;
+ if (history.count > 0) {
+ lastEntry = history.getEntryAtIndex(history.index, false);
+ history.PurgeHistory(history.count);
+ }
+
+ // Copy over the history from the target browser if it's not empty
+ let origHistory = targetBrowser.sessionHistory;
+ for (let i = 0; i <= origHistory.index; i++) {
+ let origEntry = origHistory.getEntryAtIndex(i, false);
+ if (origEntry.URI.spec != "about:blank")
+ history.addEntry(origEntry, true);
+ }
+
+ // Add the last entry from the preview; in-progress preview will add itself
+ if (lastEntry != null)
+ history.addEntry(lastEntry, true);
+
+ // Swap the docshells then fix up various properties
+ targetBrowser.swapDocShells(prefetcher);
+ targetBrowser.webNavigation.sessionHistory = history;
+ targetBrowser.attachFormFill();
+ gBrowser.setTabTitle(targetTab);
+ gBrowser.updateCurrentBrowser(true);
+ gBrowser.useDefaultIcon(targetTab);
+
+ // Restore the progress listener
+ tabListener = gBrowser.mTabProgressListener(targetTab, targetBrowser, tabListenerBlank);
+ gBrowser.mTabListeners[selectedIndex] = tabListener;
+ filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
+ targetBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
+ };
+
+ unload(function() {
+ prefetcher.parentNode.removeChild(prefetcher);
+ });
+
+ // Prevent errors from browser.js/xul when it gets unexpected title changes
+ prefetcher.addEventListener("DOMTitleChanged", function(event) {
+ event.stopPropagation();
+ }, true);
+
+ // Add an area to show a list of categories
+ let categoryBox = createNode("hbox");
+ urlbarStack.appendChild(categoryBox);
+
+ categoryBox.setAttribute("flex", 1);
+ categoryBox.setAttribute("pack", "end");
+
+ categoryBox.style.cursor = "text";
+ categoryBox.style.overflow = "hidden";
+
+ // Activate a category with an optional provider index
+ categoryBox.activate = function(categoryLabel, index) {
+ // Cycle through providers when re-activating the same category
+ let {active} = categoryBox;
+ if (active == categoryLabel) {
+ let {defaultIndex, providers} = active.categoryData;
+ index = (defaultIndex + 1) % providers.length;
+ }
+ else {
+ // Keep track of the original text values
+ let {selectionEnd, selectionStart, value} = hdInput;
+
+ // Remove any active query terms when activating another
+ let query = value;
+ if (categoryBox.active != goCategory)
+ query = query.replace(/^[^:]+:\s*/, "");
+
+ // Update the text with the active keyword
+ let {keyword} = categoryLabel.categoryData;
+ let shortQuery = query.slice(0, selectionStart);
+ if (keyword == "")
+ hdInput.value = query;
+ // Use the partially typed short keyword
+ else if (selectionStart > 0 && shortQuery == keyword.slice(0, selectionStart))
+ hdInput.value = shortQuery + ": " + query.slice(selectionStart);
+ // Insert the full keyword
+ else
+ hdInput.value = keyword + query;
+
+ // Move the cursor to its original position
+ let newLen = hdInput.value.length;
+ let origLen = value.length;
+ hdInput.selectionStart = newLen + selectionStart - origLen;
+ hdInput.selectionEnd = newLen + selectionEnd - origLen;
+ }
+
+ // Switch to a particular provider if necessary
+ if (index != null)
+ categoryLabel.categoryData.defaultIndex = index;
+
+ // Update the autocomplete results now that we've activated
+ categoryBox.processInput();
+ gURLBar.mController.handleText();
+ hdInput.focus();
+ };
+
+ // Look through the input to decide what category could be activated
+ categoryBox.maybeHighlight = function() {
+ categoryBox.highlight = null;
+
+ // See if there's any potential categories to highlight
+ let {selectionStart, value} = hdInput;
+ let shortValue = value.slice(0, selectionStart);
+ let {length} = shortValue;
+ if (length > 0) {
+ Array.some(categoryBox.childNodes, function(label) {
+ let {categoryData} = label;
+ if (categoryData == null)
+ return;
+ let {keyword} = categoryData;
+ if (keyword == "")
+ return;
+ if (shortValue == keyword.slice(0, length)) {
+ categoryBox.highlight = label;
+ return true;
+ }
+ });
+ }
+ };
+
+ // Figure out if the current input text is activating a category
+ categoryBox.processInput = function() {
+ // Figure out what's the active category based on the input
+ let {value} = hdInput;
+ let inputQuery = value;
+ let inputParts = value.match(/^([^:]*):\s*(.*?)$/);
+ categoryBox.active = goCategory;
+ if (inputParts != null) {
+ let inputKeyword = inputParts[1];
+ Array.some(categoryBox.childNodes, function(label) {
+ let {categoryData} = label;
+ if (categoryData == null)
+ return;
+ let {keyword} = categoryData;
+ if (keyword == "")
+ return;
+ if (inputKeyword == keyword.slice(0, inputKeyword.length)) {
+ categoryBox.active = label;
+ inputQuery = inputParts[2];
+ return true;
+ }
+ });
+ }
+
+ // Update the UI now that we've figured out various states
+ categoryBox.maybeHighlight();
+ categoryBox.updateLook();
+
+ // Convert the input into a url for the location bar
+ let {active} = categoryBox;
+ let {defaultIndex, providers} = active.categoryData;
+ let url = providers[defaultIndex].url;
+ const termRegex = /{search(.)terms}/;
+ let spaceChar = url.match(termRegex)[1];
+ url = url.replace(termRegex, inputQuery.replace(/ /g, spaceChar));
+ gURLBar.value = url;
+
+ // Prefetch the search results if not going to a page
+ if (active != goCategory)
+ prefetcher.loadURI(url)
+
+ // Only show results for going to a history page
+ gURLBar.popup.collapsed = active != goCategory;
+
+ // Save the input value to restore later if necessary
+ gBrowser.selectedTab.HDinput = value;
+ };
+
+ // Clear out various state of the current input
+ categoryBox.reset = function() {
+ categoryBox.active = null;
+ categoryBox.highlight = null;
+ categoryBox.hover = null;
+ categoryBox.updateLook();
+ };
+
+ // Differently color certain categories depending on state
+ categoryBox.updateLook = function() {
+ // Restore some UI like the identity box
+ let {active, highlight, hover} = categoryBox;
+ if (active == null) {
+ gBrowser.selectedTab.HDinput = "";
+ hdInput.value = "";
+ origIdentity.hidden = false;
+ iconBox.hidden = true;
+ }
+ // Prepare the UI for showing an active category
+ else {
+ origIdentity.hidden = true;
+ iconBox.hidden = false;
+
+ let {defaultIndex, providers} = active.categoryData;
+ let {icon} = providers[defaultIndex];
+ if (icon == null)
+ providerIcon.removeAttribute("src");
+ else
+ providerIcon.setAttribute("src", icon);
+ }
+
+ // Go through each label and style it appropriately
+ let doActive = gURLBar.hasAttribute("focused") || hdInput.value != "";
+ Array.forEach(categoryBox.childNodes, function(label) {
+ let color = "#999";
+ if (label == active && doActive)
+ color = "#090";
+ else if (label == highlight || label == hover)
+ color = "#00f";
+ label.style.color = color;
+
+ label.style.textDecoration = label == hover ? "underline" : "";
+ });
+ };
+
+ // Pointing away removes the go category highlight
+ categoryBox.addEventListener("mouseout", function(event) {
+ if (event.target != categoryBox)
+ return;
+ if (gURLBar.hasAttribute("focused"))
+ return;
+ categoryBox.highlight = null;
+ categoryBox.updateLook();
+ }, false);
+
+ // Indicate the default behavior of a click is go
+ categoryBox.addEventListener("mouseover", function(event) {
+ if (event.target != categoryBox)
+ return;
+ if (gURLBar.hasAttribute("focused"))
+ return;
+ categoryBox.highlight = goCategory;
+ categoryBox.updateLook();
+ }, false);
+
+ // Select the text to edit for a website
+ categoryBox.addEventListener("click", function(event) {
+ if (event.target != categoryBox && event.target != goCategory)
+ return;
+ hdInput.focus();
+ hdInput.select();
+ }, false);
+
+ // Helper to add a category or comma
+ function addLabel(text) {
+ let label = createNode("label");
+ categoryBox.appendChild(label);
+
+ label.setAttribute("value", text);
+ label.style.margin = 0;
+
+ return label;
+ }
+
+ // Create a category label
+ function addCategory(categoryData) {
+ let {category, keyword, providers, text} = categoryData;
+
+ let label = addLabel(text);
+ label.categoryData = categoryData;
+
+ label.style.cursor = "pointer";
+
+ // For context-less, activate on plain click
+ label.addEventListener("click", function() {
+ categoryBox.activate(label);
+ }, false);
+
+ // Handle the mouse moving in or out of the related labels
+ function onMouse({type, relatedTarget}) {
+ // Ignore events between the two related labels
+ if (relatedTarget == label || relatedTarget == comma)
+ return;
+
+ let hovering = type == "mouseover";
+ categoryBox.hover = hovering ? label : null;
+ categoryBox.updateLook();
+
+ if (!hovering)
+ return;
+
+ if (context.state == "open")
+ return;
+ if (category == "go")
+ return;
+
+ context.updateChecked();
+ context.openPopup(label, "after_start");
+ }
+
+ label.addEventListener("mouseout", onMouse, false);
+ label.addEventListener("mouseover", onMouse, false);
+
+ // Add a comma after each category
+ let comma = addLabel(", ");
+ comma.addEventListener("mouseout", onMouse, false);
+ comma.addEventListener("mouseover", onMouse, false);
+
+ // Prepare a popup to show category providers
+ let context = createNode("menupopup");
+ document.getElementById("mainPopupSet").appendChild(context);
+
+ // Add a menuitem that knows how to switch to the provider
+ providers.forEach(function({icon, name}, index) {
+ let provider = createNode("menuitem");
+ provider.setAttribute("class", "menuitem-iconic");
+ provider.setAttribute("image", icon);
+ provider.setAttribute("label", name);
+ context.appendChild(provider);
+
+ provider.addEventListener("command", function() {
+ categoryBox.activate(label, index);
+ }, false);
+
+ return provider;
+ });
+
+ // Correctly mark which item is the default
+ context.updateChecked = function() {
+ let {defaultIndex} = categoryData;
+ Array.forEach(context.childNodes, function(item, index) {
+ if (index == defaultIndex)
+ item.setAttribute("checked", true);
+ else
+ item.removeAttribute("checked");
+ });
+ };
+
+ context.updateChecked();
+
+ unload(function() {
+ context.parentNode.removeChild(context);
+ });
+
+ // Track when the menu disappears to maybe activate
+ let unOver;
+ context.addEventListener("popuphiding", function() {
+ unOver();
+ categoryBox.processInput();
+ categoryBox.updateLook();
+
+ // Assume dismiss of the popup by clicking on the label is to activate
+ // Windows sends both popuphiding and click events, so ignore this one
+ if (!isWin && categoryBox.hover == label)
+ categoryBox.activate(label);
+ }, false);
+
+ // Keep the category highlighted and prepare to dismiss
+ context.addEventListener("popupshowing", function() {
+ categoryBox.highlight = label;
+ categoryBox.updateLook();
+
+ // Automatically hide the popup when pointing away
+ unOver = listen(window, window, "mouseover", function(event) {
+ // Allow pointing at the category label
+ switch (event.originalTarget) {
+ case label:
+ case comma:
+ return;
+ }
+
+ // Allow pointing at the menu
+ let {target} = event;
+ if (target == context)
+ return;
+
+ // And the menu items
+ if (target.parentNode == context)
+ return;
+
+ // Must have pointed away allowed items, so dismiss
+ context.hidePopup();
+ });
+ }, false);
+
+ return label;
+ }
+
+ // Add each category to the UI and remember some special categories
+ allProviders.forEach(addCategory);
+ let goCategory = categoryBox.firstChild;
+ let searchCategory = goCategory.nextSibling.nextSibling;
+ categoryBox.removeChild(categoryBox.lastChild);
+
+ // Copy most of the original input field
+ let hdInput = origInput.cloneNode(false);
+ urlbarStack.appendChild(hdInput);
+
+ // Hide the original input
+ change(origInput.style, "maxWidth", 0);
+ change(origInput.style, "overflow", "hidden");
+
+ hdInput.removeAttribute("onblur");
+ hdInput.removeAttribute("onfocus");
+ hdInput.removeAttribute("placeholder");
+
+ hdInput.style.pointerEvents = "none";
+
+ // Use white shadows to cover up the category text
+ let (shadow = []) {
+ for (let i = -10; i <= 30; i += 5)
+ for (let j = -6; j <= 3; j += 3)
+ shadow.push(i + "px " + j + "px 5px white");
+ hdInput.style.textShadow = shadow.join(", ");
+ }
+
+ hdInput.addEventListener("blur", function() {
+ categoryBox.updateLook();
+ }, false);
+
+ hdInput.addEventListener("focus", function() {
+ gURLBar.setAttribute("focused", true);
+ categoryBox.processInput();
+ }, false);
+
+ hdInput.addEventListener("input", function() {
+ categoryBox.processInput();
+ }, false);
+
+ // Allow escaping out of the input
+ hdInput.addEventListener("keydown", function(event) {
+ if (event.keyCode != event.DOM_VK_ESCAPE)
+ return;
+
+ // Return focus to the browser if already empty
+ if (hdInput.value == "")
+ gBrowser.selectedBrowser.focus();
+ // Empty out the input on first escape
+ else {
+ hdInput.value = "";
+ categoryBox.processInput();
+ }
+ }, false);
+
+ // Update what gets highlighted when moving the cursor
+ hdInput.addEventListener("keyup", function(event) {
+ switch (event.keyCode) {
+ case event.DOM_VK_LEFT:
+ case event.DOM_VK_RIGHT:
+ categoryBox.maybeHighlight();
+ categoryBox.updateLook();
+ break;
+ }
+ }, false);
+
+ // Detect tab switches to restore previous input
+ listen(window, gBrowser.tabContainer, "TabSelect", function() {
+ hdInput.value = gBrowser.selectedTab.HDinput || "";
+ categoryBox.processInput();
+ });
+
+ // Allow tab completion to activate
+ listen(window, gURLBar.parentNode, "keypress", function(event) {
+ if (event.keyCode != event.DOM_VK_TAB)
+ return;
+
+ let {active, highlight} = categoryBox;
+ if (active != goCategory)
+ categoryBox.activate(active);
+ else if (highlight != null)
+ categoryBox.activate(highlight);
+
+ event.preventDefault();
+ event.stopPropagation();
+ });
+
+ // Activate the go category when dismissing the autocomplete results
+ listen(window, gURLBar.popup, "popuphiding", function() {
+ if (categoryBox.hover == goCategory)
+ categoryBox.activate(goCategory);
+ });
+
+ // Redirect focus from the original input to the new one
+ listen(window, origInput, "focus", function(event) {
+ origInput.blur();
+ hdInput.focus();
+ }, false);
+
+ // Hook into the user selecting a result
+ change(gURLBar, "handleCommand", function(orig) {
+ return function(event) {
+ let isGo = categoryBox.active == goCategory;
+ categoryBox.reset();
+
+ // Just load the page into the current tab
+ if (isGo)
+ return orig.call(this, event);
+
+ // Reuse the current tab if it's empty
+ let targetTab = gBrowser.selectedTab;
+ if (!window.isTabEmpty(targetTab))
+ targetTab = gBrowser.addTab();
+
+ prefetcher.persistTo(targetTab);
+ gBrowser.selectedTab = targetTab;
+ };
+ });
+
+ // Catch various existing browser commands to redirect to the dashboard
+ let commandSet = document.getElementById("mainCommandSet");
+ let commandWatcher = function(event) {
+ // Figure out if it's a command we're stealing
+ switch (event.target.id) {
+ case "Browser:OpenLocation":
+ // For power users, allow getting the current tab's location when empty
+ if (hdInput.value == "") {
+ let url = gBrowser.selectedBrowser.currentURI.spec;
+ if (url != "about:blank")
+ hdInput.value = url;
+ }
+
+ hdInput.focus();
+ hdInput.select();
+ break;
+
+ case "Tools:Search":
+ categoryBox.activate(searchCategory);
+ break;
+
+ // Not something we care about, so nothing to do!
+ default:
+ return;
+ }
+
+ // Prevent the original command from triggering
+ event.stopPropagation();
+ };
+ commandSet.addEventListener("command", commandWatcher, true);
+ unload(function() {
+ commandSet.removeEventListener("command", commandWatcher, true);
+ }, window);
+
+ // Always make the star visible to prevent text shifting
+ let star = document.getElementById("star-button");
+ star.setAttribute("style", "visibility: visible;");
+ unload(function() {
+ star.removeAttribute("style");
+ });
+
+ // Remove the search bar when loading
+ change(document.getElementById("search-container"), "hidden", true);
+
+ // Make sure the identity box is visible
+ unload(function() {
+ origIdentity.hidden = false;
+ });
+
+ // Prepare the category box for first action!
+ categoryBox.reset();
+}
+
+/**
+ * Handle the add-on being activated on install/enable
+ */
+function startup({id}) AddonManager.getAddonByID(id, function(addon) {
+ // Load various javascript includes for helper functions
+ ["helper", "providers", "utils"].forEach(function(fileName) {
+ let fileURI = addon.getResourceURI("scripts/" + fileName + ".js");
+ Services.scriptloader.loadSubScript(fileURI.spec, global);
+ });
+
+ // Load in the provider data from preferences
+ try {
+ allProviders = JSON.parse(prefs.get("providers"));
+ }
+ catch(ex) {
+ // Restore provider data with hardcoded defaults
+ let categories = {};
+ allProviders = [];
+ PROVIDER_DATA.forEach(function([category, name, url, icon]) {
+ // Add a new category and initialize with the current item
+ let providers = categories[category];
+ if (providers == null) {
+ providers = categories[category] = [];
+ allProviders.push({
+ category: category,
+ defaultIndex: 0,
+ keyword: category == "go" ? "" : category + ": ",
+ providers: providers,
+ text: category == "go" ? "Go to a website" : category == "search" ? "search the web" : category,
+ });
+ }
+
+ // Save information about this provider for the category
+ providers.push({
+ icon: icon,
+ name: name,
+ url: url,
+ });
+ });
+ }
+
+ // Combine location and search!
+ watchWindows(addAwesomeBarHD);
+})
+
+
+/**
+ * Handle the add-on being deactivated on uninstall/disable
+ */
+function shutdown(data, reason) {
+ // Clean up with unloaders when we're deactivating
+ if (reason != APP_SHUTDOWN)
+ unload();
+
+ // Persist data across restarts and disables
+ prefs.set("providers", JSON.stringify(allProviders));
+}
+
+/**
+ * Handle the add-on being installed
+ */
+function install(data, reason) {}
+
+/**
+ * Handle the add-on being uninstalled
+ */
+function uninstall(data, reason) {
+ // Clear out any persisted data when the user gets rid of the add-on
+ if (reason == ADDON_UNINSTALL)
+ prefs.resetBranch("");
+}
View
24 awesomeBarHD/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<r:RDF xmlns="http://www.mozilla.org/2004/em-rdf#"
+ xmlns:r="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <r:Description about="urn:mozilla:install-manifest">
+ <creator>Mozilla Labs</creator>
+ <description>Find pages from your history, on a the web, for a particular category.</description>
+ <homepageURL>https://mozillalabs.com/prospector</homepageURL>
+ <iconURL>http://mozillalabs.com/wp-content/themes/labs_project/img/prospector-header.png</iconURL>
+ <id>awesomeBar.HD@prospector.labs.mozilla</id>
+ <name>Mozilla Labs: Prospector - AwesomeBar HD</name>
+ <version>1</version>
+
+ <bootstrap>true</bootstrap>
+ <type>2</type>
+
+ <targetApplication>
+ <r:Description>
+ <id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</id>
+ <minVersion>4.0</minVersion>
+ <maxVersion>6.0a1</maxVersion>
+ </r:Description>
+ </targetApplication>
+ </r:Description>
+</r:RDF>
View
154 awesomeBarHD/scripts/helper.js
@@ -0,0 +1,154 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Home Dash Helper Functions.
+ *
+ * The Initial Developer of the Original Code is The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Edward Lee <edilee@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const isMac = Services.appinfo.OS == "Darwin";
+const isWin = Services.appinfo.OS == "WINNT";
+
+// Take a window and create various helper properties and functions
+function makeWindowHelpers(window) {
+ const XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ let {document, clearTimeout, gBrowser, setTimeout} = window;
+
+ // Call a function after waiting a little bit
+ function async(callback, delay) {
+ let timer = setTimeout(function() {
+ stopTimer();
+ callback();
+ }, delay);
+
+ // Provide a way to stop an active timer
+ function stopTimer() {
+ if (timer == null)
+ return;
+ clearTimeout(timer);
+ timer = null;
+ unUnload();
+ }
+
+ // Make sure to stop the timer when unloading
+ let unUnload = unload(stopTimer, window);
+
+ // Give the caller a way to cancel the timer
+ return stopTimer;
+ }
+
+ // Replace a value with another value or a function of the original value
+ function change(obj, prop, val) {
+ let orig = obj[prop];
+ obj[prop] = typeof val == "function" ? val(orig) : val;
+ unload(function() obj[prop] = orig, window);
+ }
+
+ // Create a XUL node that can get some extra functionality
+ function createNode(nodeName, extend) {
+ let node = document.createElementNS(XUL, nodeName);
+
+ // Only extend certain top-level nodes that want to be
+ if (!extend)
+ return node;
+
+ // Make a delayable function that uses a sharable timer
+ let makeDelayable = function(timerName, func) {
+ timerName += "Timer";
+ return function(arg) {
+ // Stop the shared timer if it's still waiting
+ if (node[timerName] != null)
+ node[timerName]();
+
+ // Pick out the arguments that the function wants
+ let numArgs = func.length;
+ let args;
+ if (numArgs > 1)
+ args = Array.slice(arguments, 0, func.length);
+ function callFunc() {
+ node[timerName] = null;
+ if (numArgs == 0)
+ func.call(global);
+ else if (numArgs == 1)
+ func.call(global, arg);
+ else
+ func.apply(global, args);
+ }
+
+ // If we have some amount of time to wait, wait
+ let delay = arguments[func.length];
+ if (delay)
+ node[timerName] = async(callFunc, delay);
+ // Otherwise do it synchronously
+ else {
+ callFunc();
+ node[timerName] = null;
+ }
+ };
+ }
+
+ // Allow this node to be collapsed with a delay
+ let slowHide = makeDelayable("showHide", function() node.collapsed = true);
+ node.hide = function() {
+ shown = false;
+ slowHide.apply(global, arguments);
+ };
+
+ // Set the opacity after a delay
+ node.setOpacity = makeDelayable("opacity", function(val) {
+ node.style.opacity = val;
+ });
+
+ // Allow this node to be uncollapsed with a delay
+ let slowShow = makeDelayable("showHide", function() node.collapsed = false);
+ node.show = function() {
+ shown = true;
+ slowShow.apply(global, arguments);
+ };
+
+ // Indicate if the node should be shown even if it isn't visible yet
+ let shown = true;
+ Object.defineProperty(node, "shown", {
+ get: function() shown
+ });
+
+ return node;
+ }
+
+ return {
+ async: async,
+ change: change,
+ createNode: createNode,
+ };
+}
View
117 awesomeBarHD/scripts/providers.js
117 additions, 0 deletions not shown
View
310 awesomeBarHD/scripts/utils.js
@@ -0,0 +1,310 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Home Dash Utility.
+ *
+ * The Initial Developer of the Original Code is The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Edward Lee <edilee@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+/**
+ * Get a localized string with string replacement arguments filled in and
+ * correct plural form picked if necessary.
+ *
+ * @note: Initialize the strings to use with getString.init(addon).
+ *
+ * @usage getString(name): Get the localized string for the given name.
+ * @param [string] name: Corresponding string name in the properties file.
+ * @return [string]: Localized string for the string name.
+ *
+ * @usage getString(name, arg): Replace %S references in the localized string.
+ * @param [string] name: Corresponding string name in the properties file.
+ * @param [any] arg: Value to insert for instances of %S.
+ * @return [string]: Localized string with %S references replaced.
+ *
+ * @usage getString(name, args): Replace %1$S references in localized string.
+ * @param [string] name: Corresponding string name in the properties file.
+ * @param [array of any] args: Array of values to replace references like %1$S.
+ * @return [string]: Localized string with %N$S references replaced.
+ *
+ * @usage getString(name, args, plural): Pick the correct plural form.
+ * @param [string] name: Corresponding string name in the properties file.
+ * @param [array of any] args: Array of values to replace references like %1$S.
+ * @param [number] plural: Number to decide what plural form to use.
+ * @return [string]: Localized string of the correct plural form.
+ */
+function getString(name, args, plural) {
+ // Use the cached bundle to retrieve the string
+ let str;
+ try {
+ str = getString.bundle.GetStringFromName(name);
+ }
+ // Use the fallback in-case the string isn't localized
+ catch(ex) {
+ str = getString.fallback.GetStringFromName(name);
+ }
+
+ // Pick out the correct plural form if necessary
+ if (plural != null)
+ str = getString.plural(plural, str);
+
+ // Fill in the arguments if necessary
+ if (args != null) {
+ // Convert a string or something not array-like to an array
+ if (typeof args == "string" || args.length == null)
+ args = [args];
+
+ // Assume %S refers to the first argument
+ str = str.replace(/%s/gi, args[0]);
+
+ // Replace instances of %N$S where N is a 1-based number
+ Array.forEach(args, function(replacement, index) {
+ str = str.replace(RegExp("%" + (index + 1) + "\\$S", "gi"), replacement);
+ });
+ }
+
+ return str;
+}
+
+/**
+ * Initialize getString() for the provided add-on.
+ *
+ * @usage getString.init(addon): Load properties file for the add-on.
+ * @param [object] addon: Add-on object from AddonManager
+ *
+ * @usage getString.init(addon, getAlternate): Load properties with alternate.
+ * @param [object] addon: Add-on object from AddonManager
+ * @param [function] getAlternate: Convert a locale to an alternate locale
+ */
+getString.init = function(addon, getAlternate) {
+ // Set a default get alternate function if it doesn't exist
+ if (typeof getAlternate != "function")
+ getAlternate = function() "en-US";
+
+ // Get the bundled properties file for the app's locale
+ function getBundle(locale) {
+ let propertyPath = "locales/" + locale + ".properties";
+ let propertyFile = addon.getResourceURI(propertyPath);
+
+ // Get a bundle and test if it's able to do simple things
+ try {
+ // Avoid caching issues by always getting a new file
+ let uniqueFileSpec = propertyFile.spec + "#" + Math.random();
+ let bundle = Services.strings.createBundle(uniqueFileSpec);
+ bundle.getSimpleEnumeration();
+ return bundle;
+ }
+ catch(ex) {}
+
+ // The locale must not exist, so give nothing
+ return null;
+ }
+
+ // Use the current locale or the alternate as the primary bundle
+ let locale = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global");
+ getString.bundle = getBundle(locale) || getBundle(getAlternate(locale));
+
+ // Create a fallback in-case a string is missing
+ getString.fallback = getBundle("en-US");
+
+ // Get the appropriate plural form getter
+ Cu.import("resource://gre/modules/PluralForm.jsm");
+ let rule = getString("pluralRule");
+ [getString.plural] = PluralForm.makeGetter(rule);
+}
+
+/**
+ * Create a trigger that allows adding callbacks by default then triggering all
+ * of them.
+ */
+function makeTrigger() {
+ let callbacks = [];
+
+ // Provide the main function to add callbacks that can be removed
+ function addCallback(callback) {
+ callbacks.push(callback);
+ return function() {
+ let index = callbacks.indexOf(callback);
+ if (index != -1)
+ callbacks.splice(index, 1);
+ };
+ }
+
+ // Provide a way to clear out all the callbacks
+ addCallback.reset = function() {
+ callbacks.length = 0;
+ };
+
+ // Run each callback in order ignoring failures
+ addCallback.trigger = function(reason) {
+ callbacks.slice().forEach(function(callback) {
+ try {
+ callback(reason);
+ }
+ catch(ex) {}
+ });
+ };
+
+ return addCallback;
+}
+
+/**
+ * Apply a callback to each open and new browser windows.
+ *
+ * @usage watchWindows(callback): Apply a callback to each browser window.
+ * @param [function] callback: 1-parameter function that gets a browser window.
+ */
+function watchWindows(callback) {
+ // Wrap the callback in a function that ignores failures
+ function watcher(window) {
+ try {
+ callback(window);
+ }
+ catch(ex) {}
+ }
+
+ // Wait for the window to finish loading before running the callback
+ function runOnLoad(window) {
+ // Listen for one load event before checking the window type
+ window.addEventListener("load", function runOnce() {
+ window.removeEventListener("load", runOnce, false);
+
+ // Now that the window has loaded, only handle browser windows
+ let doc = window.document.documentElement;
+ if (doc.getAttribute("windowtype") == "navigator:browser")
+ watcher(window);
+ }, false);
+ }
+
+ // Add functionality to existing windows
+ let browserWindows = Services.wm.getEnumerator("navigator:browser");
+ while (browserWindows.hasMoreElements()) {
+ // Only run the watcher immediately if the browser is completely loaded
+ let browserWindow = browserWindows.getNext();
+ if (browserWindow.document.readyState == "complete")
+ watcher(browserWindow);
+ // Wait for the window to load before continuing
+ else
+ runOnLoad(browserWindow);
+ }
+
+ // Watch for new browser windows opening then wait for it to load
+ function windowWatcher(subject, topic) {
+ if (topic == "domwindowopened")
+ runOnLoad(subject);
+ }
+ Services.ww.registerNotification(windowWatcher);
+
+ // Make sure to stop watching for windows if we're unloading
+ unload(function() Services.ww.unregisterNotification(windowWatcher));
+}
+
+/**
+ * Save callbacks to run when unloading. Optionally scope the callback to a
+ * container, e.g., window. Provide a way to run all the callbacks.
+ *
+ * @usage unload(): Run all callbacks and release them.
+ *
+ * @usage unload(callback): Add a callback to run on unload.
+ * @param [function] callback: 0-parameter function to call on unload.
+ * @return [function]: A 0-parameter function that undoes adding the callback.
+ *
+ * @usage unload(callback, container) Add a scoped callback to run on unload.
+ * @param [function] callback: 0-parameter function to call on unload.
+ * @param [node] container: Remove the callback when this container unloads.
+ * @return [function]: A 0-parameter function that undoes adding the callback.
+ */
+function unload(callback, container) {
+ // Initialize the array of unloaders on the first usage
+ let unloaders = unload.unloaders;
+ if (unloaders == null)
+ unloaders = unload.unloaders = [];
+
+ // Calling with no arguments runs all the unloader callbacks
+ if (callback == null) {
+ unloaders.slice().forEach(function(unloader) unloader());
+ unloaders.length = 0;
+ return;
+ }
+
+ // The callback is bound to the lifetime of the container if we have one
+ if (container != null) {
+ // Remove the unloader when the container unloads
+ container.addEventListener("unload", removeUnloader, false);
+
+ // Wrap the callback to additionally remove the unload listener
+ let origCallback = callback;
+ callback = function() {
+ container.removeEventListener("unload", removeUnloader, false);
+ origCallback();
+ }
+ }
+
+ // Wrap the callback in a function that ignores failures
+ function unloader() {
+ try {
+ callback();
+ }
+ catch(ex) {}
+ }
+ unloaders.push(unloader);
+
+ // Provide a way to remove the unloader
+ function removeUnloader() {
+ let index = unloaders.indexOf(unloader);
+ if (index != -1)
+ unloaders.splice(index, 1);
+ }
+ return removeUnloader;
+}
+
+/**
+ * Helper that adds event listeners and remembers to remove on unload
+ */
+function listen(window, node, event, func, capture) {
+ // Default to use capture
+ if (capture == null)
+ capture = true;
+
+ node.addEventListener(event, func, capture);
+ function undoListen() {
+ node.removeEventListener(event, func, capture);
+ }
+
+ // Undo the listener on unload and provide a way to undo everything
+ let undoUnload = unload(undoListen, window);
+ return function() {
+ undoListen();
+ undoUnload();
+ };
+}
Please sign in to comment.
Something went wrong with that request. Please try again.