素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: すかいぷ:javascripter_  か javascripter あっと tsukkun.net skypeのほうがいいです

AutoPageLoaderをxhtmlに対応させたり。

  1. jAutoPagerizeの$Xを参考に、document.evaluateの引数にネームスペースをきちんと設定した。xhtmlのページでも動くようになった。
  2. bind関数を作って、いちいちvar self=this;ってやらなくていいようにした。
  3. ソースコードをきれいにした。を通した後に手動で見づらいのを直した。
  4. あと細かい所で、必要ないremoveEventListenerを削除したり、ステータス管理をひとつのプロパティにまとめたり。
// ==UserScript==
// @name           AutoPageLoader
// @namespace      http://d.hatena.ne.jp/javascripter/
// @include        http*
// ==/UserScript==
(function() {
  if (window.name == 'autopagerize_iframe') return;

  var Class = function() function() {
    bind(this.initialize, this)()
  };

  var AutoPageLoader = new Class();
  AutoPageLoader.prototype = {
    Settings: {
      siteinfo_urls: ['http://wedata.net/databases/AutoPagerize/items.json'],
      AUTO_START: true,
      REMAIN_HEIGHT: 300,
      LOCAL_SITEINFO: [{
        name: 'GoogleImages',
        data: {
          pageElement: 'id("ImgContent")',
          url: '^http:\/\/images.google.co.jp\/images\?',
          nextLink: '//td[@class="b"]/a',
          exampleUrl: 'http:\/\/images.google.co.jp\/images?q=hatena'
        }
      }],
      MICROFORMATS: [{
        name: 'autopagerize',
        data: {
          url: '^https?://.*',
          nextLink: '(//a | //link)[@rel="next"]',
          insertBefore: '//*[contains(concat(" ",@class," "), " autopagerize_insert_before ")]',
          pageElement: '//*[contains(@class, "autopagerize_page_element")]'
        }
      },
      {
        name: 'hAtom',
        data: {
          url: '^https?://.*',
          nextLink: '(//a|//link)[contains(concat(" ",normalize-space(translate(@rel, "NEXT", "next"))," "), " next ")]',
          insertBefore: '//*[contains(concat(" ",@class," "), " hfeed ")]/following-sibling::node()',
          pageElement: '//*[contains(concat(" ",@class," "), " hfeed ")]'
        }
      },
      {
        name: 'xFolk',
        data: {
          url: '^https?://.*',
          pageElement: '//*[contains(concat(" ",@class," "), " xfolkentry ")]',
          insertBefore: '//*[contains(concat" ",@class," "), " xfolkentry ")]/following-sibling::node()',
          nextLink: '(//a|//link)[contains(concat(" ",normalize-space(translate(@rel, "NEXT", "next"))," "), " next ")]',
        }
      }],
      color: {
        on: '#00cc00',
        off: '#cccccc',
        loading: '#0000cc',
        finish: '#cccc00',
        error: '#cc0000'
      }
    },
    addFilter: function(callback) {
      this.filters.push(callback);
    },
    addDocumentFilter: function(callback) {
      this.documentFilters.push(callback);
    },
    getCache: function(callback) {
      var cache = GM_getValue('cache');
      cache ? callback(eval(cache)) : this.setCache(callback);
    },
    setCache: function(callback) {
      var siteinfo = [];
      this.Settings.siteinfo_urls.forEach(function(url, i, siteinfo_urls) {
        GM_xmlhttpRequest({
          method: 'GET',
          url: url,
          onload: function(res) {
            siteinfo.push(eval(res.responseText));
            if (siteinfo.length == siteinfo_urls.length) {
              GM_setValue('cache', uneval(siteinfo));
              callback(siteinfo);
            }
          }
        });
      });
    },
    clearCache: function() {
      GM_setValue('cache', '');
      this.getSiteinfo(function(data) {
        this.siteinfo = data
      });
    },
    getSiteinfo: function(callback) {
      this.getCache(bind(function(siteinfo) {
        siteinfo.unshift(this.Settings.LOCAL_SITEINFO, this.Settings.MICROFORMATS);
        var data = null;
        siteinfo.some(function(siteinfo) {
          for each(var {
            data: i
          } in siteinfo)
            if (RegExp(i.url).test(location.href) &&
              $x(i.nextLink, document, XPathResult.BOOLEAN_TYPE).booleanValue &&
              $x(i.pageElement, document, XPathResult.BOOLEAN_TYPE).booleanValue) {
              data = i;
              return true;
            }
          return false;
        });
        callback(data);
      },
      this));
    },
    onscroll: function() {
      if (this.state != 'off' && this.state != 'loading' &&
          this.appendPoint.getBoundingClientRect().bottom - window.innerHeight < this.Settings.REMAIN_HEIGHT)
        this.setNextPage();
    },
    ondblclick: function() {
      this.changeState(this.state == 'off' ? 'on': 'off');
    },
    setNextPage: function() {
      var nextLink = $x(this.siteinfo.nextLink, this.lastDocument, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
      if (!nextLink) {
        this.changeState('finish');
        return;
      }
      var nextUrl = nextLink.href;
      this.changeState('loading');
      var iframe = document.body.appendChild(document.createElement('iframe'));
      iframe.contentWindow.name = 'autopagerize_iframe';
      iframe.src = nextUrl;
      iframe.style.display = 'none';
      iframe.contentWindow.addEventListener('DOMContentLoaded', bind(function() {
        this.changeState('on');
        this.lastDocument = iframe.contentDocument;
        this.documentFilters.forEach(function(callback) {
          callback(this.lastDocument, nextUrl, this.siteinfo);
        });
        var pageElement = $x(this.siteinfo.pageElement, this.lastDocument, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
        if (pageElement.snapshotLength == 0)
          this.changeState('error');
        var pageNavi = document.createElement('div');
        pageNavi.innerHTML = 'page: <a class="autopagerize_link" href="' + nextUrl + '">' + (++this.pages) + '</a>';
        var insertPoint = null;
        this.siteinfo.insertBefore?
          insertPoint = $x(this.siteinfo.insertBefore, document, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue:
          insertPoint = this.appendPoint.nextSibling || this.appendPoint.parentNode.nextSibling;
        var addElement = function(elem)
          insertPoint.parentNode.insertBefore(elem, insertPoint);
        addElement(pageNavi);
        var pages = [];
        for (var i = 0; i < pageElement.snapshotLength; i++)
          pages.push(addElement(document.importNode(pageElement.snapshotItem(i), true)));
        this.filters.forEach(function(filter) {
          filter(pages)
        });
        this.appendPoint = pages[pages.length - 1];
        setTimeout(function() {
          iframe.parentNode.removeChild(iframe)
        },
        0);
      },
      this), false);
    },
    changeState: function(state) {
      this.state = state;
      this.icon.style.backgroundColor = this.Settings.color[state];
      switch (state) {
      case 'finish':
      case 'error':
        {
          window.removeEventListener('scroll', this.bindScroll, false);
          window.removeEventListener('scroll', this.bindDblclick, false);
          setTimeout(bind(function() {
            this.icon.parentNode.removeChild(this.icon)
          },
          this), 1000);
          break;
        }
      }
    },
    addIcon: function() {
      this.icon = document.body.appendChild(document.createElement('div'));
      this.icon.id = 'autopagerize_icon';
      with(this.icon.style) {
        top = '5px';
        right = '5px';
        width = '10px';
        height = '10px';
        position = 'fixed';
        zIndex = '9999';
      }
      this.changeState(this.Settings.AUTO_START ? 'on': 'off');
    },
    setMenu: function() {
      GM_registerMenuCommand('AutoPageLoader - clear cache', bind(this.clearCache, this));
    },
    initialize: function() {
      this.getSiteinfo(bind(function(data) {
        if (!data) return;
        this.siteinfo = data;
        this.filters = [];
        this.documentFilters = [];
        this.pages = 1;
        this.lastDocument = document;
        var elems = $x(data.pageElement, document, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
        this.appendPoint = elems.snapshotItem(elems.snapshotLength - 1);
        window.addEventListener('scroll', this.bindScroll = bind(this.onscroll, this), false);
        window.addEventListener('dblclick', this.bindDblclick = bind(this.ondblclick, this), false);
        this.addIcon();
        this.setMenu();
        window.AutoPagerize = {
          addFilter: bind(this.addFilter, this),
          addDocumentFilter: bind(this.addDocumentFilter, this)
        }
      },
      this));
    },
  }

  new AutoPageLoader();

  function bind(func, thisObj)
    function() func.apply(thisObj, arguments);

  function $x(exp, context, type)
    content.ownerDocument || context.createExpression(exp,
      function(prefix)
        document.createNSResolver((context.ownerDocument || context).documentElement)
        .lookupNamespaceURI(prefix) || document.documentElement.namespaceURI)
    .evaluate(context, type, null);
})();

結局addDocumentFilterが必要になってしまった。
Greasemonkeyスクリプトが実行された瞬間を取得するのはどうやったらいいんだろう。