SafariHighlight.uc.js ver. 0.9

0.9.2にverUPしました。

d:id:DeaR:20080111:p1

userChrome.js(http://forums.mozillazine.org/viewtopic.php?t=556229)用のスクリプトです。

Safariのインライン検索のハイライトみたいに動作させるスクリプトです。

SearchWP(http://legege.com/en/mozilla/searchwp)と、Googlebar Lite(http://www.borngeek.com/firefox/googlebarlite/)のハイライトにも対応しています。

設定項目(ソースの最初の辺り)

alwaysHighlight
Safariのように検索開始時に常時強調表示状態にする。(true:有効, false:無効)
clickAndExit
Safariのように画面クリックでハイライトを終了する。(true:有効, false:無効)
overlayOpacity
オーバーレイの不透明度。(0:透明 <=> 1:不透明)
hitAnimation
ヒット箇所のアニメーションを行う。(true:有効, false:無効)

動作確認

マイナーなMODってゆーな!

変更点

  • オーバーレイの不透明度を変更できるようにした。
  • piroさんのXUL/Migemoによる実装を取り込み、ヒット箇所のアニメーションを実装。
  • 細かいバグの修正。

既知の不具合

  • リンク等がクリックできなくなる。
  • z-indexを無効化することによる、使用しているページのデザインがハイライト中は崩れてしまう、筈。
  • 横スクロールバーが存在するとオーバーレイの横幅がおかしい。

注意

約1週間対応が行えないので、0.8をバックアップしておく事をお勧めします。

// ==UserScript==
// @name          SafariHighlight
// @namespace     http://kuonn.mydns.jp
// @author        DeaR
// @include       chrome://browser/content/browser.xul
// @description   Safari like Highlight
// @version       0.9
// @compatibility Firefox 2.0 - 3.0
// ==/UserScript==

// this script based on
// Greased Lightbox (http://shiftingpixel.com/lightbox)
// userContent.js (http://pc11.2ch.net/test/read.cgi/software/1168635399/419)
// ucjs_findbar (http://space.geocities.yahoo.co.jp/gl/alice0775/view/20070301/1172678601)
// XUL/Migemo (http://piro.sakura.ne.jp/xul/_xulmigemo.html)


var safariHighlight = {
  alwaysHighlight : true,  // 常に検索開始時は強調表示する
  clickAndExit    : true,  // 画面クリックでハイライトを終了する
  overlayOpacity  : 0.3,   // オーバーレイの不透明度 (0:透明, 1:不透明)
  hitAnimeation   : true,  // ヒット箇所のアニメーションを行う。

  get browser() {
    return document.getElementById("content") ||     // Firefox
           document.getElementById("messagepane") || // Thunderbird
           document.getElementById("help-content");  // Help
  },

  get activeBrowser() {
    return ("SplitBrowser" in window ? SplitBrowser.activeBrowser : null ) ||
      this.browser;
  },

  initializeHighlightScreen : function(aFrame) {
    if(!aFrame)
      aFrame = this.activeBrowser.contentWindow;

    if(aFrame.frames && aFrame.frames.length) {
      var self = this;
      Array.prototype.slice.call(aFrame.frames).forEach(function(aSubFrame) {
        self.initializeHighlightScreen(aSubFrame);
      });
    }

    if(aFrame.document instanceof HTMLDocument)
      this.addHighlightScreen(aFrame.document);
  },

  addHighlightScreen : function(aDocument) {
    var doc = aDocument;
    if (doc.getElementById("__moz_findSafariHighlightStyle"))
      return;

    var pageSize = this.getPageSize(doc.defaultView);

    var heads = doc.getElementsByTagName("head");
    if(heads.length > 0) {
      var objHead = heads[0];
      var node = doc.createElement("style");
      node.id = "__moz_findSafariHighlightStyle";
      node.type = "text/css";
      node.innerHTML = this.highlightStyle +
                       "#__moz_findSafariHighlightScreen {" +
                        "  height: " + pageSize.height + "px;" +
                        "  opacity : " + this.overlayOpacity + ";" +
                        "  -moz-opacity : " + this.overlayOpacity + ";" +
                       "}";
      objHead.insertBefore(node, objHead.firstChild);
    }

    var bodies = doc.getElementsByTagName("body");
    if(bodies.length == 0)
      return;

    var objBody = bodies[0];

    var screen = doc.createElement("div");
    screen.setAttribute("id", "__moz_findSafariHighlightScreen");

    objBody.insertBefore(screen, objBody.firstChild);
  },

  highlightStyle : String(<![CDATA[ 
    :root[__moz_findSafariHighlightScreen="on"] * {
      z-index : auto !important;
    }
    :root[__moz_findSafariHighlightScreen="on"] #__firefox-findbar-search-id, /* Fx2 */
    :root[__moz_findSafariHighlightScreen="on"] .__mozilla-findbar-search, /* Fx3 */
    :root[__moz_findSafariHighlightScreen="on"] .searchwp-term-highlight1, /* SearchWP */
    :root[__moz_findSafariHighlightScreen="on"] .searchwp-term-highlight2,
    :root[__moz_findSafariHighlightScreen="on"] .searchwp-term-highlight3,
    :root[__moz_findSafariHighlightScreen="on"] .searchwp-term-highlight4,
    :root[__moz_findSafariHighlightScreen="on"] .GBL-Highlighted /* Googlebar Lite */ {
      position : relative !important;
      z-index : 3000000 !important;
    }
    #__moz_findSafariHighlightScreen {
      left : 0;
      top : 0;
      width : 100%;
      border : 0;
      margin : 0;
      padding : 0;
      background : #000000;
      position : absolute;
      display : none;
      z-index : 1000000 !important;
    }
    :root[__moz_findSafariHighlightScreen="on"] > body > #__moz_findSafariHighlightScreen {
      display : block !important;
    }
    :root[__moz_findSafariHighlightScreen="on"] embed {
      visibility : hidden !important;
    }
    :root[__moz_findSafariHighlightScreen="on"] iframe { 
      position : relative;
      z-index : 2000000 !important;
    }
    ]]>),

  getPageSize : function(aWindow) {
    var xScroll = aWindow.document.body.scrollWidth;
    var yScroll = aWindow.innerHeight + aWindow.scrollMaxY;
    var windowWidth  = aWindow.innerWidth;
    var windowHeight = aWindow.innerHeight;
    var pageHeight = (yScroll < windowHeight) ? windowHeight : yScroll ;
    var pageWidth  = (xScroll < windowWidth) ? windowWidth : xScroll ;
    return {
      width   : pageWidth,
      height  : pageHeight,
      wWidth  : windowWidth,
      wHeight : windowHeight
    };
  },

  destroyHighlightScreen : function(aFrame) {
    if(!aFrame)
      aFrame = this.activeBrowser.contentWindow;

    if(aFrame.frames && aFrame.frames.length) {
      var self = this;
      Array.prototype.slice.call(aFrame.frames).forEach(function(aSubFrame) {
        self.destroyHighlightScreen(aSubFrame);
      });
    }

    if(!(aFrame.document instanceof HTMLDocument))
      return;

    aFrame.document.documentElement.removeAttribute("__moz_findSafariHighlightScreen");
    if(this.clickAndExit)
      aFrame.removeEventListener("click", function(event) {
        safariHighlight.onClick(event);
      }, true);
  },

  toggleHighlightScreen : function(aHighlight, aFrame) {
    if(!aFrame)
      aFrame = this.activeBrowser.contentWindow;

    if(aFrame.frames && aFrame.frames.length) {
      var self = this;
      Array.prototype.slice.call(aFrame.frames).forEach(function(aSubFrame) {
        self.toggleHighlightScreen(aHighlight, aSubFrame);
      });
    }

    if(!(aFrame.document instanceof HTMLDocument))
      return;

    if(window.content)
      window.content.__moz_findSafariHighlightScreen = aHighlight;

    if(aHighlight) {
      aFrame.document.documentElement.setAttribute("__moz_findSafariHighlightScreen", "on");
      if(this.clickAndExit)
        aFrame.addEventListener("click", function(event) {
          safariHighlight.onClick(event);
        }, true);
    } else {
      aFrame.document.documentElement.removeAttribute("__moz_findSafariHighlightScreen");
      if(this.clickAndExit)
        aFrame.removeEventListener("click", function(event) {
          safariHighlight.onClick(event);
        }, true);
    }
  },

  findCommand : function() {
    this.initializeHighlightScreen();

    var highlightBtn;
    if("gFindBar" in window && "onFindAgainCommand" in gFindBar) { // Fx3
      var findbar = document.getElementById("FindToolbar");
      highlightBtn = document.getAnonymousElementByAttribute(findbar, "anonid", "highlight");
    } else if(typeof gFindBar == "object") { // Fx2
      highlightBtn = document.getElementById("highlight");
    }
    if(highlightBtn.getAttribute("checked") != "true") {
      highlightBtn.setAttribute("checked", "true");
      gFindBar.toggleHighlight(true);
      this.toggleHighlightScreen(true);
    }
  },

  onClick : function(aEvent) {
    var highlightBtn;
    if("gFindBar" in window && "onFindAgainCommand" in gFindBar) { // Fx3
      var findbar = document.getElementById("FindToolbar");
      highlightBtn = document.getAnonymousElementByAttribute(findbar, "anonid", "highlight");
    } else if(typeof gFindBar == "object") { // Fx2
      highlightBtn = document.getElementById("highlight");
    }
    if(highlightBtn.checked) {
      highlightBtn.removeAttribute("checked");
      highlightBtn.setAttribute("checkeState", "0");
      gFindBar.toggleHighlight(false);
    }

    if(typeof gSearchWP != "undefined") { // SearchWP
      highlightBtn = document.getElementById("searchwp-highlight-button");
      if(highlightBtn.checked) {
        highlightBtn.checked = false;
        gSearchWPOverlay.toggleHighlight(false);
      }
    }

    if(typeof GBL_Listener != "undefined") { // Googlebar Lite
      highlightBtn = document.getElementById("GBL-TB-Highlighter");
      if(highlightBtn.checked)
        GBL_ToggleHighlighting();
    }

    this.destroyHighlightScreen(null);
  },

  restoreHighlightScreen : function() {
    if("gFindBar" in window && "onFindAgainCommand" in gFindBar) { // Fx3
      var findbar = document.getElementById("FindToolbar");
      highlightBtn = document.getAnonymousElementByAttribute(findbar, "anonid", "highlight");
    } else if(typeof gFindBar == "object") { // Fx2
      highlightBtn = document.getElementById("highlight");
    }
    if(highlightBtn.getAttribute("checked") == "true")
      this.toggleHighlightScreen(true);
  },

  // Safari style highlight, animation
  //   based on XUL/Migemo (http://piro.sakura.ne.jp/xul/_xulmigemo.html)
  NSResolver : { 
    lookupNamespaceURI : function(aPrefix) {
      switch (aPrefix) {
      case "xul":
        return "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
      case "html":
      case "xhtml":
        return "http://www.w3.org/1999/xhtml";
      case "xlink":
        return "http://www.w3.org/1999/xlink";
      default:
        return "";
      }
    }
  },

  getFoundRange : function(aFrame) {
    var docShell = aFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                         .getInterface(Components.interfaces.nsIWebNavigation)
                         .QueryInterface(Components.interfaces.nsIDocShell);
    var selCon = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                         .getInterface(Components.interfaces.nsISelectionDisplay)
                         .QueryInterface(Components.interfaces.nsISelectionController);
    if(selCon.getDisplaySelection() == selCon.SELECTION_ATTENTION) {
      var sel   = aFrame.getSelection();
      var range = sel.getRangeAt(0);
      return range;
    }
    return null;
  },

  highlightFocusedFound : function(aFrame) {
    if(this.highlightFocusedFoundTimer)
      window.clearTimeout(this.highlightFocusedFoundTimer);
    this.highlightFocusedFoundTimer = window.setTimeout(
      "safariHighlight.highlightFocusedFoundCallback()", 0);
  },

  highlightFocusedFoundCallback : function(aFrame) {
    this.highlightFocusedFoundTimer = null;

    if(!aFrame)
      aFrame = this.activeBrowser.contentWindow;

    var range = this.getFoundRange(aFrame);
    if(range) {
      var node  = range.startContainer;
      try {
        var xpathResult = aFrame.document.evaluate(
          "ancestor-or-self::*[@id = '__firefox-findbar-search-id' or @class = '__mozilla-findbar-search']",
          node,
          this.NSResolver,
          XPathResult.FIRST_ORDERED_NODE_TYPE,
          null);
      } catch(e) {
        var xpathResult = document.evaluate(
          'ancestor-or-self::*[@id = "__firefox-findbar-search-id" or @class = "__mozilla-findbar-search"]',
          node,
          this.NSResolver,
          XPathResult.FIRST_ORDERED_NODE_TYPE,
          null);
      }
      if(xpathResult.singleNodeValue)
        this.animateFoundNode(xpathResult.singleNodeValue);
      return true;
    }

    if(aFrame.frames && aFrame.frames.length) {
      var frames = aFrame.frames;
      for(var i = 0, maxi = frames.length; i < maxi; i ++) {
        if(this.highlightFocusedFound(frames[i]))
          break;
      }
    }
    return false;
  },

  animateFoundNode : function(aNode) {
    if(this.animationTimer) {
      this.animationNode.style.top = 0;
      window.clearInterval(this.animationTimer);
      this.animationTimer = null;
      this.animationNode  = null;
    }
    this.animationNode = aNode;
    this.animationStart = (new Date()).getTime();
    this.animationTimer = window.setInterval(this.animateFoundNodeCallback, 1, this);
  },

  animateFoundNodeCallback : function(aThis) {
    var node = aThis.animationNode;
    var now = (new Date()).getTime();
    if(aThis.animationTime <= (now - aThis.animationStart) || !node.parentNode) {
      node.style.top = 0;
      window.clearInterval(aThis.animationTimer);
      aThis.animationTimer = null;
      aThis.animationNode  = null;
    } else {
      var step = ((now - aThis.animationStart) || 1) / aThis.animationTime;
      var y = parseInt(10 * Math.sin((180 - (180 * step)) * Math.PI / 180));
      node.style.top = "-0." + y + "0em";
    }
  },

  animationTimer : null,
  animationTime  : 250,
};

(function() {
  if(typeof XMigemoUI != "undefined") // XUL/Migemo
    return;

  if("gFindBar" in window && "onFindAgainCommand" in gFindBar) { // Fx3
    try {
      eval("gFindBar.open = " +
           gFindBar.open.toSource().replace(
             "return true;",
             "safariHighlight.initializeHighlightScreen();" +
             "safariHighlight.restoreHighlightScreen();" +
             "return true;"));

      eval("gFindBar.close = " +
           gFindBar.close.toSource().replace(
             "document.commandDispatcher.suppressFocusScroll = suppressedScroll;",
             "safariHighlight.destroyHighlightScreen();" +
             "document.commandDispatcher.suppressFocusScroll = suppressedScroll;"));

      eval("gFindBar.toggleHighlight = " +
           gFindBar.toggleHighlight.toSource().replace(
             "if (aHighlight) {",
             "safariHighlight.initializeHighlightScreen();" +
             "safariHighlight.toggleHighlightScreen(aHighlight);" +
             "if (aHighlight) {"));

      if(safariHighlight.alwaysHighlight) {
        eval("gFindBar._find = " +
             gFindBar._find.toSource().replace(
               "var val = aValue || this._findField.value",
               "var val = aValue || this._findField.value" +
               "safariHighlight.findCommand();"));

        eval("gFindBar.onFindAgainCommand = " +
             gFindBar.onFindAgainCommand.toSource().replace(
               "var findString = this._browser.fastFind.searchString || this._findField.value;",
               "safariHighlight.findCommand();" +
               "var findString = this._browser.fastFind.searchString || this._findField.value;"));
      }

      if(safariHighlight.hitAnimeation) {
        eval("gFindBar._updateStatusUI = " +
             gFindBar._updateStatusUI.toSource().replace(
               "switch (res) {",
               "if(arguments[0] != Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND)" +
               "  safariHighlight.highlightFocusedFound();" +
               "switch (res) {"));
      }
    } catch(e) {}
  } else if(typeof gFindBar == "object") { // Fx2
    try {
      eval("gFindBar.openFindBar = " +
           gFindBar.openFindBar.toSource().replace(
             "return true;",
             "safariHighlight.initializeHighlightScreen();" +
             "safariHighlight.restoreHighlightScreen();" +
             "return true;"));

      eval("gFindBar.closeFindBar = " +
           gFindBar.closeFindBar.toSource().replace(
             "setTimeout(findBar_DelayedCloseFindBar, 0);",
             "safariHighlight.destroyHighlightScreen();" +
             "setTimeout(findBar_DelayedCloseFindBar, 0);"));

      eval("gFindBar.toggleHighlight = " +
           gFindBar.toggleHighlight.toSource().replace(
             "if (aHighlight) {",
             "safariHighlight.initializeHighlightScreen();" +
             "safariHighlight.toggleHighlightScreen(aHighlight);" +
             "if (aHighlight) {"));

      if(safariHighlight.alwaysHighlight) {
        eval("gFindBar.findNext = " +
             gFindBar.findNext.toSource().replace(
               "var fastFind = getBrowser().fastFind;",
               "safariHighlight.findCommand();" +
               "var fastFind = getBrowser().fastFind;"));

        eval("gFindBar.findPrevious = " +
             gFindBar.findPrevious.toSource().replace(
               "var fastFind = getBrowser().fastFind;",
               "safariHighlight.findCommand();" +
               "var fastFind = getBrowser().fastFind;"));
      }

      if(safariHighlight.hitAnimeation) {
        eval("gFindBar.updateStatus = " +
             gFindBar.updateStatus.toSource().replace(
               "var findBar = document.getElementById(\"FindToolbar\");",
               "if(arguments[0] != Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND)" +
               "  safariHighlight.highlightFocusedFound();" +
               "var findBar = document.getElementById(\"FindToolbar\");"));
      }
    } catch(e) {}
  }

  if(typeof gSearchWP != "undefined") { // SearchWP
    try {
      eval("gSearchWPOverlay.toggleHighlight = " +
           gSearchWPOverlay.toggleHighlight.toSource().replace(
             "gSearchWPHighlighting.toggleHighlight(aHighlight);",
             "safariHighlight.initializeHighlightScreen();" +
             "safariHighlight.toggleHighlightScreen(aHighlight);" +
             "gSearchWPHighlighting.toggleHighlight(aHighlight);"));
    } catch(e) {}
  }

  if(typeof GBL_Listener != "undefined") { // Googlebar Lite
    try {
      eval("GBL_ToggleHighlighting = " +
           GBL_ToggleHighlighting.toSource().replace(
             "var hb = document.getElementById(\"GBL-TB-Highlighter\");",
             "var hb = document.getElementById(\"GBL-TB-Highlighter\");" +
             "safariHighlight.initializeHighlightScreen();" +
             "safariHighlight.toggleHighlightScreen(!hb.checked);"));
    } catch(e) {}
  }
})();