Revision b99473e7f0d1b818de2620a6d17c3176a0ce4440 authored by B2G Bumper Bot on 28 May 2014, 13:50:39 UTC, committed by B2G Bumper Bot on 28 May 2014, 13:50:39 UTC
========

https://hg.mozilla.org/integration/gaia-1_3/rev/bc67ee9ba9a1
Author: James Burke <jrburke@gmail.com>
Desc: Merge pull request #19492 from jrburke/bug1010867-email-slow-down-there-buddy

Bug 1010867 - [OPENC_1.3][email/UI] Message notification handling is pushing message_reader onto the stack twice r=asuth
1 parent e916f28
Raw File
UITour.jsm
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

this.EXPORTED_SYMBOLS = ["UITour"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
  "resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
  "resource://gre/modules/PermissionsUtils.jsm");


const UITOUR_PERMISSION   = "uitour";
const PREF_PERM_BRANCH    = "browser.uitour.";


this.UITour = {
  originTabs: new WeakMap(),
  pinnedTabs: new WeakMap(),
  urlbarCapture: new WeakMap(),

  highlightEffects: ["wobble", "zoom", "color"],
  targets: new Map([
    ["backforward", "#unified-back-forward-button"],
    ["appmenu", "#appmenu-button"],
    ["home", "#home-button"],
    ["urlbar", "#urlbar"],
    ["bookmarks", "#bookmarks-menu-button"],
    ["search", "#searchbar"],
    ["searchprovider", function UITour_target_searchprovider(aDocument) {
      let searchbar = aDocument.getElementById("searchbar");
      return aDocument.getAnonymousElementByAttribute(searchbar,
                                                     "anonid",
                                                     "searchbar-engine-button");
    }],
  ]),

  onPageEvent: function(aEvent) {
    let contentDocument = null;
    if (aEvent.target instanceof Ci.nsIDOMHTMLDocument)
      contentDocument = aEvent.target;
    else if (aEvent.target instanceof Ci.nsIDOMHTMLElement)
      contentDocument = aEvent.target.ownerDocument;
    else
      return false;

    // Ignore events if they're not from a trusted origin.
    if (!this.ensureTrustedOrigin(contentDocument))
      return false;

    if (typeof aEvent.detail != "object")
      return false;

    let action = aEvent.detail.action;
    if (typeof action != "string" || !action)
      return false;

    let data = aEvent.detail.data;
    if (typeof data != "object")
      return false;

    let window = this.getChromeWindow(contentDocument);

    switch (action) {
      case "showHighlight": {
        let target = this.getTarget(window, data.target);
        if (!target)
          return false;
        this.showHighlight(target);
        break;
      }

      case "hideHighlight": {
        this.hideHighlight(window);
        break;
      }

      case "showInfo": {
        let target = this.getTarget(window, data.target, true);
        if (!target)
          return false;
        this.showInfo(target, data.title, data.text);
        break;
      }

      case "hideInfo": {
        this.hideInfo(window);
        break;
      }

      case "previewTheme": {
        this.previewTheme(data.theme);
        break;
      }

      case "resetTheme": {
        this.resetTheme();
        break;
      }

      case "addPinnedTab": {
        this.ensurePinnedTab(window, true);
        break;
      }

      case "removePinnedTab": {
        this.removePinnedTab(window);
        break;
      }

      case "showMenu": {
        this.showMenu(window, data.name);
        break;
      }

      case "startUrlbarCapture": {
        if (typeof data.text != "string" || !data.text ||
            typeof data.url != "string" || !data.url) {
          return false;
        }

        let uri = null;
        try {
          uri = Services.io.newURI(data.url, null, null);
        } catch (e) {
          return false;
        }

        let secman = Services.scriptSecurityManager;
        let principal = contentDocument.nodePrincipal;
        let flags = secman.DISALLOW_INHERIT_PRINCIPAL;
        try {
          secman.checkLoadURIWithPrincipal(principal, uri, flags);
        } catch (e) {
          return false;
        }

        this.startUrlbarCapture(window, data.text, data.url);
        break;
      }

      case "endUrlbarCapture": {
        this.endUrlbarCapture(window);
        break;
      }
    }

    let tab = window.gBrowser._getTabForContentWindow(contentDocument.defaultView);
    if (!this.originTabs.has(window))
      this.originTabs.set(window, new Set());
    this.originTabs.get(window).add(tab);

    tab.addEventListener("TabClose", this);
    window.gBrowser.tabContainer.addEventListener("TabSelect", this);
    window.addEventListener("SSWindowClosing", this);

    return true;
  },

  handleEvent: function(aEvent) {
    switch (aEvent.type) {
      case "pagehide": {
        let window = this.getChromeWindow(aEvent.target);
        this.teardownTour(window);
        break;
      }

      case "TabClose": {
        let window = aEvent.target.ownerDocument.defaultView;
        this.teardownTour(window);
        break;
      }

      case "TabSelect": {
        let window = aEvent.target.ownerDocument.defaultView;
        let pinnedTab = this.pinnedTabs.get(window);
        if (pinnedTab && pinnedTab.tab == window.gBrowser.selectedTab)
          break;
        let originTabs = this.originTabs.get(window);
        if (originTabs && originTabs.has(window.gBrowser.selectedTab))
          break;

        this.teardownTour(window);
        break;
      }

      case "SSWindowClosing": {
        let window = aEvent.target;
        this.teardownTour(window, true);
        break;
      }

      case "input": {
        if (aEvent.target.id == "urlbar") {
          let window = aEvent.target.ownerDocument.defaultView;
          this.handleUrlbarInput(window);
        }
        break;
      }
    }
  },

  teardownTour: function(aWindow, aWindowClosing = false) {
    aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
    aWindow.removeEventListener("SSWindowClosing", this);

    let originTabs = this.originTabs.get(aWindow);
    if (originTabs) {
      for (let tab of originTabs)
        tab.removeEventListener("TabClose", this);
    }
    this.originTabs.delete(aWindow);

    if (!aWindowClosing) {
      this.hideHighlight(aWindow);
      this.hideInfo(aWindow);
    }

    this.endUrlbarCapture(aWindow);
    this.removePinnedTab(aWindow);
    this.resetTheme();
  },

  getChromeWindow: function(aContentDocument) {
    return aContentDocument.defaultView
                           .window
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIWebNavigation)
                           .QueryInterface(Ci.nsIDocShellTreeItem)
                           .rootTreeItem
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindow)
                           .wrappedJSObject;
  },

  importPermissions: function() {
    try {
      PermissionsUtils.importFromPrefs(PREF_PERM_BRANCH, UITOUR_PERMISSION);
    } catch (e) {
      Cu.reportError(e);
    }
  },

  ensureTrustedOrigin: function(aDocument) {
    if (aDocument.defaultView.top != aDocument.defaultView)
      return false;

    let uri = aDocument.documentURIObject;

    if (uri.schemeIs("chrome"))
      return true;

    let allowedSchemes = new Set(["https"]);
    if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
      allowedSchemes.add("http");

    if (!allowedSchemes.has(uri.scheme))
      return false;

    this.importPermissions();
    let permission = Services.perms.testPermission(uri, UITOUR_PERMISSION);
    return permission == Services.perms.ALLOW_ACTION;
  },

  getTarget: function(aWindow, aTargetName, aSticky = false) {
    if (typeof aTargetName != "string" || !aTargetName)
      return null;

    if (aTargetName == "pinnedtab")
      return this.ensurePinnedTab(aWindow, aSticky);

    let targetQuery = this.targets.get(aTargetName);
    if (!targetQuery)
      return null;

    if (typeof targetQuery == "function")
      return targetQuery(aWindow.document);

    return aWindow.document.querySelector(targetQuery);
  },

  previewTheme: function(aTheme) {
    let origin = Services.prefs.getCharPref("browser.uitour.themeOrigin");
    let data = LightweightThemeManager.parseTheme(aTheme, origin);
    if (data)
      LightweightThemeManager.previewTheme(data);
  },

  resetTheme: function() {
    LightweightThemeManager.resetPreview();
  },

  ensurePinnedTab: function(aWindow, aSticky = false) {
    let tabInfo = this.pinnedTabs.get(aWindow);

    if (tabInfo) {
      tabInfo.sticky = tabInfo.sticky || aSticky;
    } else {
      let url = Services.urlFormatter.formatURLPref("browser.uitour.pinnedTabUrl");

      let tab = aWindow.gBrowser.addTab(url);
      aWindow.gBrowser.pinTab(tab);
      tab.addEventListener("TabClose", () => {
        this.pinnedTabs.delete(aWindow);
      });

      tabInfo = {
        tab: tab,
        sticky: aSticky
      };
      this.pinnedTabs.set(aWindow, tabInfo);
    }

    return tabInfo.tab;
  },

  removePinnedTab: function(aWindow) {
    let tabInfo = this.pinnedTabs.get(aWindow);
    if (tabInfo)
      aWindow.gBrowser.removeTab(tabInfo.tab);
  },

  showHighlight: function(aTarget) {
    let highlighter = aTarget.ownerDocument.getElementById("UITourHighlight");

    let randomEffect = Math.floor(Math.random() * this.highlightEffects.length);
    if (randomEffect == this.highlightEffects.length)
      randomEffect--; // On the order of 1 in 2^62 chance of this happening.
    highlighter.setAttribute("active", this.highlightEffects[randomEffect]);

    let targetRect = aTarget.getBoundingClientRect();

    highlighter.style.height = targetRect.height + "px";
    highlighter.style.width = targetRect.width + "px";

    let highlighterRect = highlighter.getBoundingClientRect();

    let top = targetRect.top + (targetRect.height / 2) - (highlighterRect.height / 2);
    highlighter.style.top = top + "px";
    let left = targetRect.left + (targetRect.width / 2) - (highlighterRect.width / 2);
    highlighter.style.left = left + "px";
  },

  hideHighlight: function(aWindow) {
    let tabData = this.pinnedTabs.get(aWindow);
    if (tabData && !tabData.sticky)
      this.removePinnedTab(aWindow);

    let highlighter = aWindow.document.getElementById("UITourHighlight");
    highlighter.removeAttribute("active");
  },

  showInfo: function(aAnchor, aTitle, aDescription) {
    aAnchor.focus();

    let document = aAnchor.ownerDocument;
    let tooltip = document.getElementById("UITourTooltip");
    let tooltipTitle = document.getElementById("UITourTooltipTitle");
    let tooltipDesc = document.getElementById("UITourTooltipDescription");

    tooltip.hidePopup();

    tooltipTitle.textContent = aTitle;
    tooltipDesc.textContent = aDescription;

    let alignment = "bottomcenter topright";
    let anchorRect = aAnchor.getBoundingClientRect();

    tooltip.hidden = false;
    tooltip.openPopup(aAnchor, alignment);
  },

  hideInfo: function(aWindow) {
    let tooltip = aWindow.document.getElementById("UITourTooltip");
    tooltip.hidePopup();
  },

  showMenu: function(aWindow, aMenuName) {
    function openMenuButton(aId) {
      let menuBtn = aWindow.document.getElementById(aId);
      if (menuBtn && menuBtn.boxObject)
        menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(true);
    }

    if (aMenuName == "appmenu")
      openMenuButton("appmenu-button");
    else if (aMenuName == "bookmarks")
      openMenuButton("bookmarks-menu-button");
  },

  startUrlbarCapture: function(aWindow, aExpectedText, aUrl) {
    let urlbar = aWindow.document.getElementById("urlbar");
    this.urlbarCapture.set(aWindow, {
      expected: aExpectedText.toLocaleLowerCase(),
      url: aUrl
    });
    urlbar.addEventListener("input", this);
  },

  endUrlbarCapture: function(aWindow) {
    let urlbar = aWindow.document.getElementById("urlbar");
    urlbar.removeEventListener("input", this);
    this.urlbarCapture.delete(aWindow);
  },

  handleUrlbarInput: function(aWindow) {
    if (!this.urlbarCapture.has(aWindow))
      return;

    let urlbar = aWindow.document.getElementById("urlbar");

    let {expected, url} = this.urlbarCapture.get(aWindow);

    if (urlbar.value.toLocaleLowerCase().localeCompare(expected) != 0)
      return;

    urlbar.handleRevert();

    let tab = aWindow.gBrowser.addTab(url, {
      owner: aWindow.gBrowser.selectedTab,
      relatedToCurrent: true
    });
    aWindow.gBrowser.selectedTab = tab;
  },
};
back to top