Commits

Anton Afanasyev committed 3741d1f

Nov 7, 2011 10:29

Comments (0)

Files changed (2)

Empty file added.

autopagerize.user.js

+// ==UserScript==
+// @name           AutoPagerize
+// @namespace      http://swdyh.yu.to/
+// @description    loading next page and inserting into current page.
+// @include        http://*
+// @include        https://*
+// @exclude        https://mail.google.com/*
+// @exclude        http://b.hatena.ne.jp/*
+// @exclude        http://www.facebook.com/plugins/like.php*
+// @exclude        http://api.tweetmeme.com/button.js*
+// ==/UserScript==
+//
+// auther:  swdyh http://d.hatena.ne.jp/swdyh/
+// version: 0.0.61 2011-11-07T19:18:11+09:00
+//
+// this script based on
+// GoogleAutoPager(http://la.ma.la/blog/diary_200506231749.htm) and
+// estseek autopager(http://la.ma.la/blog/diary_200601100209.htm).
+// thanks to ma.la.
+//
+// Released under the GPL license
+// http://www.gnu.org/copyleft/gpl.html
+//
+
+if (isGreasemonkey()) {
+    var ep = getPref('exclude_patterns')
+    if (ep && isExclude(ep)) {
+        // FIXME
+        // return
+    }
+}
+else {
+    gmCompatible()
+}
+
+var URL = 'http://autopagerize.net/'
+var VERSION = '0.0.61'
+var DEBUG = false
+var AUTO_START = true
+var CACHE_EXPIRE = 24 * 60 * 60 * 1000
+var BASE_REMAIN_HEIGHT = 400
+var FORCE_TARGET_WINDOW = getPref('force_target_window', true)
+var XHR_TIMEOUT = 30 * 1000
+var SITEINFO_IMPORT_URLS = [
+    'http://wedata.net/databases/AutoPagerize/items.json',
+]
+var COLOR = {
+    on: '#0f0',
+    off: '#ccc',
+    loading: '#0ff',
+    terminated: '#00f',
+    error: '#f0f'
+}
+var SITEINFO = [
+    /* sample
+    {
+        url:          'http://(.*).google.+/(search).+',
+        nextLink:     'id("navbar")//td[last()]/a',
+        pageElement:  '//div[@id="res"]/div',
+        exampleUrl:   'http://www.google.com/search?q=nsIObserver',
+    },
+    */
+    /* template
+    {
+        url:          '',
+        nextLink:     '',
+        pageElement:  '',
+        exampleUrl:   '',
+    },
+    */
+]
+var MICROFORMAT = {
+    url:          '.*',
+    nextLink:     '//a[@rel="next"] | //link[@rel="next"]',
+    insertBefore: '//*[contains(@class, "autopagerize_insert_before")]',
+    pageElement:  '//*[contains(@class, "autopagerize_page_element")]',
+}
+
+var AutoPager = function(info) {
+    this.pageNum = 1
+    this.info = info
+    this.state = AUTO_START ? 'enable' : 'disable'
+    var self = this
+    var url = this.getNextURL(info.nextLink, document, location.href)
+
+    if ( !url ) {
+        debug("getNextURL returns null.", info.nextLink)
+        return
+    }
+    if (info.insertBefore) {
+        this.insertPoint = getFirstElementByXPath(info.insertBefore)
+    }
+
+    if (!this.insertPoint) {
+        var lastPageElement = getElementsByXPath(info.pageElement).pop()
+        if (lastPageElement) {
+            this.insertPoint = lastPageElement.nextSibling ||
+                lastPageElement.parentNode.appendChild(document.createTextNode(' '))
+        }
+    }
+
+    if (!this.insertPoint) {
+        debug("insertPoint not found.", lastPageElement, info.pageElement)
+        return
+    }
+
+    this.requestURL = url
+    this.loadedURLs = {}
+    this.loadedURLs[location.href] = true
+    var toggle = function() {self.stateToggle()}
+    this.toggle = toggle
+    GM_registerMenuCommand('AutoPagerize - on/off', toggle)
+    this.scroll= function() { self.onScroll() }
+    window.addEventListener("scroll", this.scroll, false)
+
+    if (isFirefoxExtension()) {
+        var div = document.createElement("div")
+        div.setAttribute('id', 'autopagerize_icon')
+        div.style.display = 'none'
+        document.body.appendChild(div)
+        this.icon = div
+    }
+    else if (isChromeExtension() || isSafariExtension() || isJetpack()) {
+        var frame = document.createElement('iframe')
+        frame.style.display = 'none'
+        frame.style.position = 'fixed'
+        frame.style.bottom = '0px'
+        frame.style.left = '0px'
+        frame.style.height = '25px'
+        frame.style.border = '0px'
+        frame.style.opacity = '0.8'
+        frame.style.zIndex = '1000'
+        frame.width = '100%'
+        frame.scrolling = 'no'
+        this.messageFrame = frame
+        var u = settings['extension_path'] ?
+            settings['extension_path'] + 'loading.html' :
+            'http://autopagerize.net/files/loading.html'
+        this.messageFrame.src = u
+        document.body.appendChild(frame)
+        if (isSafariExtension()) {
+            safari.self.tab.dispatchMessage('launched', {url: location.href })
+        }
+        else if (isChromeExtension()) {
+            chrome.extension.connect({name: "launched"}).postMessage()
+        }
+        if (isJetpack()) {
+            postMessage({name: 'launched', data: location.href })
+        }
+   }
+    else {
+        this.initIcon()
+        this.initHelp()
+        GM_addStyle('@media print{#autopagerize_icon, #autopagerize_help {display: none !important;}}')
+        this.icon.addEventListener("mouseover", function() {
+            self.viewHelp()
+        }, true)
+    }
+
+    var scrollHeight = getScrollHeight()
+    var bottom = getElementPosition(this.insertPoint).top ||
+        this.getPageElementsBottom() ||
+        (Math.round(scrollHeight * 0.8))
+    this.remainHeight = scrollHeight - bottom + BASE_REMAIN_HEIGHT
+    this.onScroll()
+
+    var that = this
+    document.addEventListener('AutoPagerizeToggleRequest', function() {
+        that.toggle()
+    }, false)
+    document.addEventListener('AutoPagerizeUpdateIconRequest', function() {
+        that.updateIcon()
+    }, false)
+    that.updateIcon()
+}
+
+AutoPager.prototype.getPageElementsBottom = function() {
+    try {
+        var elem = getElementsByXPath(this.info.pageElement).pop()
+        return getElementBottom(elem)
+    }
+    catch(e) {}
+}
+
+AutoPager.prototype.initHelp = function() {
+    var helpDiv = document.createElement('div')
+    helpDiv.setAttribute('id', 'autopagerize_help')
+    helpDiv.setAttribute('style', 'padding:5px;position:fixed;' +
+                     'top:-200px;right:3px;font-size:10px;' +
+                     'background:#fff;color:#000;border:1px solid #ccc;' +
+                     'z-index:256;text-align:left;font-weight:normal;' +
+                     'line-height:120%;font-family:verdana;')
+
+    var toggleDiv = document.createElement('div')
+    toggleDiv.setAttribute('style', 'margin:0 0 0 50px;')
+    var a = document.createElement('a')
+    a.setAttribute('class', 'autopagerize_link')
+    a.innerHTML = 'on/off'
+    a.href = 'javascript:void(0)'
+    var self = this
+    var toggle = function() {
+        self.stateToggle()
+        helpDiv.style.top = '-200px'
+    }
+    a.addEventListener('click', toggle, false)
+    toggleDiv.appendChild(a)
+
+    var s = '<div style="width:100px; float:left;">'
+    for (var i in COLOR) {
+        s += '<div style="float:left;width:1em;height:1em;' +
+            'margin:0 3px;background-color:' + COLOR[i] + ';' +
+            '"></div><div style="margin:0 3px">' + i + '</div>'
+    }
+    s += '</div>'
+    var colorDiv = document.createElement('div')
+    colorDiv.innerHTML = s
+    helpDiv.appendChild(colorDiv)
+    helpDiv.appendChild(toggleDiv)
+
+    var versionDiv = document.createElement('div')
+    versionDiv.setAttribute('style', 'clear:both;')
+    versionDiv.innerHTML = '<a href="' + URL +
+        '">AutoPagerize</a> ver ' + VERSION
+    helpDiv.appendChild(versionDiv)
+    document.body.appendChild(helpDiv)
+
+    var proc = function(e) {
+        var c_style = document.defaultView.getComputedStyle(helpDiv, '')
+        var s = ['top', 'left', 'height', 'width'].map(function(i) {
+            return parseInt(c_style.getPropertyValue(i)) })
+        if (e.clientX < s[1] || e.clientX > (s[1] + s[3] + 11) ||
+            e.clientY < s[0] || e.clientY > (s[0] + s[2] + 11)) {
+                helpDiv.style.top = '-200px'
+        }
+    }
+    helpDiv.addEventListener('mouseout', proc, false)
+    this.helpLayer = helpDiv
+    GM_addStyle('#autopagerize_help a { color: #0f0; text-decoration: underline;}')
+}
+
+AutoPager.prototype.viewHelp = function() {
+    this.helpLayer.style.top = '3px'
+}
+
+AutoPager.prototype.onScroll = function() {
+    var scrollHeight = Math.max(document.documentElement.scrollHeight,
+                                document.body.scrollHeight)
+    var remain = scrollHeight - window.innerHeight - window.scrollY
+    if (this.state == 'enable' && remain < this.remainHeight) {
+          this.request()
+    }
+}
+
+AutoPager.prototype.stateToggle = function() {
+    if (this.state == 'enable') {
+        this.disable()
+    }
+    else {
+        this.enable()
+    }
+}
+
+AutoPager.prototype.enable = function() {
+    this.state = 'enable'
+    this.updateIcon()
+}
+
+AutoPager.prototype.disable = function() {
+    this.state = 'disable'
+    this.updateIcon()
+}
+
+AutoPager.prototype.updateIcon = function(state) {
+    var st = state || this.state
+    var rename = {'enable': 'on', 'disable': 'off' }
+    if (rename[st]) {
+        st = rename[st]
+    }
+    var color = COLOR[st]
+    if (color) {
+        if (isFirefoxExtension()) {
+            chlorine.pageAction.update(color, location.href)
+        }
+        else if (isChromeExtension()) {
+            chrome.extension.connect({name: "pageActionChannel"}).postMessage(color)
+        }
+        else if (isSafariExtension() || isJetpack()) {
+        }
+        else {
+            this.icon.style.background = color
+        }
+    }
+}
+
+AutoPager.prototype.request = function() {
+    if (!this.requestURL || this.lastRequestURL == this.requestURL) {
+        return
+    }
+    this.lastRequestURL = this.requestURL
+    var self = this
+    var mime = 'text/html; charset=' + document.characterSet
+    var headers = {}
+
+    if (isSameDomain(this.requestURL)) {
+        headers.Cookie = document.cookie
+    }
+    else {
+        this.error()
+        return
+    }
+    var opt = {
+        method: 'get',
+        url: this.requestURL,
+        headers: headers,
+        overrideMimeType: mime,
+        onerror: function(res) {
+            self.error()
+        },
+        onload: function(res) {
+            if (res.finalUrl && location.host == res.finalUrl.split('/')[2]) {
+                self.requestLoad.apply(self, [res])
+            }
+            else {
+                self.error()
+            }
+        }
+    }
+    AutoPager.requestFilters.forEach(function(i) { i(opt) }, this)
+    if (opt.stop) {
+        this.requestURL = opt.url
+    }
+    else {
+        this.showLoading(true)
+        GM_xmlhttpRequest(opt)
+    }
+}
+
+AutoPager.prototype.showLoading = function(sw) {
+    if (sw) {
+        this.updateIcon('loading')
+        if (this.messageFrame && settings['display_message_bar']) {
+            this.messageFrame.style.display = 'block'
+        }
+    }
+    else {
+        this.updateIcon('enable')
+        if (this.messageFrame) {
+            this.messageFrame.style.display = 'none'
+        }
+    }
+}
+
+AutoPager.prototype.requestLoad = function(res) {
+    AutoPager.responseFilters.forEach(function(i) {
+        i(res, this.requestURL)
+    }, this)
+    var htmlDoc = createHTMLDocumentByString(res.responseText)
+    AutoPager.documentFilters.forEach(function(i) {
+        i(htmlDoc, this.requestURL, this.info)
+    }, this)
+    try {
+        var page = getElementsByXPath(this.info.pageElement, htmlDoc)
+        var url = this.getNextURL(this.info.nextLink, htmlDoc, this.requestURL)
+    }
+    catch(e){
+        log(e)
+        this.error()
+        return
+    }
+
+    if (!page || page.length < 1 ) {
+        debug('pageElement not found.' , this.info.pageElement)
+        this.terminate()
+        return
+    }
+
+    if (this.loadedURLs[this.requestURL]) {
+        debug('page is already loaded.', this.requestURL, this.info.nextLink)
+        this.terminate()
+        return
+    }
+
+    this.loadedURLs[this.requestURL] = true
+    page = this.addPage(htmlDoc, page)
+    AutoPager.filters.forEach(function(i) {
+        i(page)
+    })
+    this.requestURL = url
+    this.showLoading(false)
+    this.onScroll()
+    if (!url) {
+        debug('nextLink not found.', this.info.nextLink, htmlDoc)
+        this.terminate()
+    }
+    var ev = document.createEvent('Event')
+    ev.initEvent('GM_AutoPagerizeNextPageLoaded', true, false)
+    document.dispatchEvent(ev)
+}
+
+AutoPager.prototype.addPage = function(htmlDoc, page) {
+    var HTML_NS  = 'http://www.w3.org/1999/xhtml'
+    var hr = document.createElementNS(HTML_NS, 'hr')
+    var p  = document.createElementNS(HTML_NS, 'p')
+    hr.setAttribute('class', 'autopagerize_page_separator')
+    p.setAttribute('class', 'autopagerize_page_info')
+    var self = this
+
+    if (page[0] && /tr/i.test(page[0].tagName)) {
+        var insertParent = this.insertPoint.parentNode
+        var colNodes = getElementsByXPath('child::tr[1]/child::*[self::td or self::th]', insertParent)
+
+        var colums = 0
+        for (var i = 0, l = colNodes.length; i < l; i++) {
+            var col = colNodes[i].getAttribute('colspan')
+            colums += parseInt(col, 10) || 1
+        }
+        var td = document.createElement('td')
+        // td.appendChild(hr)
+        td.appendChild(p)
+        var tr = document.createElement('tr')
+        td.setAttribute('colspan', colums)
+        tr.appendChild(td)
+        insertParent.insertBefore(tr, this.insertPoint)
+    }
+    else {
+        this.insertPoint.parentNode.insertBefore(hr, this.insertPoint)
+        this.insertPoint.parentNode.insertBefore(p, this.insertPoint)
+    }
+
+    p.innerHTML = 'page: <a class="autopagerize_link" href="' +
+        this.requestURL.replace(/&/g, '&amp;') + '">' + (++this.pageNum) + '</a>'
+    return page.map(function(i) {
+        var pe = document.importNode(i, true)
+        self.insertPoint.parentNode.insertBefore(pe, self.insertPoint)
+        var ev = document.createEvent('MutationEvent')
+        ev.initMutationEvent('AutoPagerize_DOMNodeInserted', true, false,
+                             self.insertPoint.parentNode, null,
+                             self.requestURL, null, null)
+        pe.dispatchEvent(ev)
+        return pe
+    })
+}
+
+AutoPager.prototype.initIcon = function() {
+    var div = document.createElement("div")
+    div.setAttribute('id', 'autopagerize_icon')
+    with (div.style) {
+        fontSize   = '12px'
+        position   = 'fixed'
+        top        = '3px'
+        right      = '3px'
+        background = COLOR['on']
+        color      = '#fff'
+        width = '10px'
+        height = '10px'
+        zIndex = '255'
+        if (this.state != 'enable') {
+            background = COLOR['off']
+        }
+    }
+    document.body.appendChild(div)
+    this.icon = div
+}
+
+AutoPager.prototype.getNextURL = function(xpath, doc, url) {
+    var nextLink = getFirstElementByXPath(xpath, doc)
+    if (nextLink) {
+        var nextValue = nextLink.getAttribute('href') ||
+            nextLink.getAttribute('action') || nextLink.value
+        if (nextValue.match(/^http(s)?:/)) {
+            return nextValue
+        }
+        else {
+            var base = getFirstElementByXPath('//base[@href]', doc)
+            return resolvePath(nextValue, (base ? base.href : url))
+        }
+    }
+}
+
+AutoPager.prototype.terminate = function() {
+    window.removeEventListener('scroll', this.scroll, false)
+    this.updateIcon('terminated')
+    var self = this
+    setTimeout(function() {
+        if (self.icon) {
+            self.icon.parentNode.removeChild(self.icon)
+        }
+        if (isSafariExtension()) {
+            var mf = self.messageFrame
+            mf.parentNode.removeChild(mf)
+        }
+    }, 1500)
+}
+
+AutoPager.prototype.error = function() {
+    this.updateIcon('error')
+    window.removeEventListener('scroll', this.scroll, false)
+    if (isSafariExtension() || isChromeExtension() || isJetpack()) {
+        var mf = this.messageFrame
+        var u = settings['extension_path'] ?
+            settings['extension_path'] + 'error.html' :
+            'http://autopagerize.net/files/error.html'
+        mf.src = u
+        mf.style.display = 'block'
+        setTimeout(function() {
+            mf.parentNode.removeChild(mf)
+        }, 3000)
+    }
+}
+
+AutoPager.documentFilters = []
+AutoPager.requestFilters = []
+AutoPager.responseFilters = []
+AutoPager.filters = []
+
+var parseInfo = function(str) {
+    var lines = str.split(/\r\n|\r|\n/)
+    var re = /(^[^:]*?):(.*)$/
+    var strip = function(str) {
+        return str.replace(/^\s*/, '').replace(/\s*$/, '')
+    }
+    var info = {}
+    for (var i = 0; i < lines.length; i++) {
+        if (lines[i].match(re)) {
+            info[RegExp.$1] = strip(RegExp.$2)
+        }
+    }
+    var isValid = function(info) {
+        var infoProp = ['url', 'nextLink', 'pageElement']
+        for (var i = 0; i < infoProp.length; i++) {
+            if (!info[infoProp[i]]) {
+                return false
+            }
+        }
+        return true
+    }
+    return isValid(info) ? info : null
+}
+var launchAutoPager = function(list) {
+    if (list.length == 0) {
+        return
+    }
+    for (var i = 0; i < list.length; i++) {
+        try {
+            if (ap) {
+                return
+            }
+            else if (!location.href.match(list[i].url)) {
+            }
+            else if (!getFirstElementByXPath(list[i].nextLink)) {
+                // FIXME microformats case detection.
+                // limiting greater than 12 to filter microformats like SITEINFOs.
+                if (list[i].url.length > 12 ) {
+                    debug("nextLink not found.", list[i].nextLink)
+                }
+            }
+            else if (!getFirstElementByXPath(list[i].pageElement)) {
+                if (list[i].url.length > 12 ) {
+                    debug("pageElement not found.", list[i].pageElement)
+                }
+            }
+            else {
+                ap = new AutoPager(list[i])
+                return
+            }
+        }
+        catch(e) {
+            log(e)
+            continue
+        }
+    }
+}
+var clearCache = function() {
+    GM_setValue('cacheInfo', '')
+}
+var getCache = function() {
+    try {
+        return JSON.parse(GM_getValue('cacheInfo')) || {}
+    }
+    catch(e) {
+        return {}
+    }
+}
+var getCacheCallback = function(res, url) {
+    if (res.status != 200) {
+        return getCacheErrorCallback(url)
+    }
+
+    var info
+    try {
+        info = JSON.parse(res.responseText).map(function(i) { return i.data })
+    }
+    catch(e) {
+        info = []
+    }
+    if (info.length > 0) {
+        info = info.filter(function(i) { return ('url' in i) })
+        info.sort(function(a, b) { return (b.url.length - a.url.length) })
+
+        var r_keys = ['url', 'nextLink', 'insertBefore', 'pageElement']
+        info = info.map(function(i) {
+            var item = {}
+            r_keys.forEach(function(key) {
+                if (i[key]) {
+                    item[key] = i[key]
+                }
+            })
+            return item
+        })
+
+        cacheInfo[url] = {
+            url: url,
+            expire: new Date(new Date().getTime() + CACHE_EXPIRE),
+            info: info
+        }
+        GM_setValue('cacheInfo', JSON.stringify(cacheInfo))
+        launchAutoPager(info)
+    }
+    else {
+        getCacheErrorCallback(url)
+    }
+}
+var getCacheErrorCallback = function(url) {
+    var expire = new Date(new Date().getTime() + CACHE_EXPIRE)
+    if (cacheInfo[url]) {
+        cacheInfo[url].expire = expire
+        launchAutoPager(cacheInfo[url].info)
+    }
+    else {
+        cacheInfo[url] = {
+            url: url,
+            expire: expire,
+            info: []
+        }
+    }
+    GM_setValue('cacheInfo', cacheInfo.toSource())
+}
+
+var linkFilter = function(doc, url) {
+    var base = getFirstElementByXPath('//base[@href]', doc)
+    var baseUrl = base ? base.href : url
+    var isSameBase = isSameBaseUrl(location.href, baseUrl)
+    if (!FORCE_TARGET_WINDOW && isSameBase) {
+        return
+    }
+
+    var anchors = getElementsByXPath('descendant-or-self::a[@href]', doc)
+    anchors.forEach(function(i) {
+        var attrHref = i.getAttribute('href')
+        if (FORCE_TARGET_WINDOW && !attrHref.match(/^#|^javascript:/) &&
+            i.className.indexOf('autopagerize_link') < 0) {
+            i.target = '_blank'
+        }
+        if (!isSameBase && !attrHref.match(/^#|^\w+:/)) {
+            i.href = resolvePath(i.getAttribute('href'), baseUrl)
+        }
+    })
+
+    if (!isSameBase) {
+        var images = getElementsByXPath('descendant-or-self::img', doc)
+        images.forEach(function(i) {
+            i.src = resolvePath(i.getAttribute('src'), baseUrl)
+        })
+    }
+}
+AutoPager.documentFilters.push(linkFilter)
+
+fixResolvePath()
+
+if (typeof(window.AutoPagerize) == 'undefined') {
+    window.AutoPagerize = {}
+    window.AutoPagerize.addFilter = function(f) {
+        AutoPager.filters.push(f)
+    }
+    window.AutoPagerize.addDocumentFilter = function(f) {
+        AutoPager.documentFilters.push(f)
+    }
+    window.AutoPagerize.addResponseFilter = function(f) {
+        AutoPager.responseFilters.push(f)
+    }
+    window.AutoPagerize.addRequestFilter = function(f) {
+        AutoPager.requestFilters.push(f)
+    }
+    window.AutoPagerize.launchAutoPager = launchAutoPager
+
+    var ev = document.createEvent('Event')
+    ev.initEvent('GM_AutoPagerizeLoaded', true, false)
+    document.dispatchEvent(ev)
+}
+
+var settings = {}
+var ap = null
+if (isChromeExtension()) {
+    var port = chrome.extension.connect({name: "settingsChannel"})
+    port.postMessage()
+    port.onMessage.addListener(function(res) {
+        settings = res
+        if (res['exclude_patterns'] && isExclude(res['exclude_patterns'])) {
+            return
+        }
+        launchAutoPager(SITEINFO)
+        var port_ = chrome.extension.connect({name: "siteinfoChannel"})
+        port_.postMessage({ url: location.href })
+        port_.onMessage.addListener(function(res) {
+            launchAutoPager(res)
+            chrome.extension.onConnect.addListener(function(port) {
+                if (port.name == "toggleRequestChannel") {
+                    port.onMessage.addListener(function(msg) {
+                        if (ap) {
+                            ap.toggle()
+                        }
+                    })
+                }
+            })
+        })
+    })
+}
+else if (isSafariExtension()) {
+    var re_exclude = /^(about:|safari-extension:)/
+    if (!location.href.match(re_exclude)) {
+        safari.self.addEventListener('message', function(event) {
+            if (event.name === 'settings') {
+                settings = event.message
+                safari.self.tab.dispatchMessage('siteinfoChannel', {url: location.href })
+            }
+            else if (event.name === 'siteinfoChannel') {
+                if (!settings['exclude_patterns'] || !isExclude(settings['exclude_patterns'])) {
+                    launchAutoPager(SITEINFO)
+                    launchAutoPager([MICROFORMAT])
+                    launchAutoPager(event.message)
+                }
+            }
+            else if (event.name === 'toggleRequestChannel') {
+                if (ap) {
+                    ap.toggle()
+                }
+            }
+            else if (event.name === 'updateSettings') {
+                settings = event.message
+            }
+        }, false)
+        safari.self.tab.dispatchMessage('settings')
+    }
+}
+else if (isJetpack()) {
+    postMessage({ name: 'settings' })
+    onMessage = function(message) {
+        if (message.name == 'siteinfo') {
+            // launchAutoPager(SITEINFO)
+            launchAutoPager(message.data)
+        }
+        else if (message.name == 'settings') {
+            settings = message.data
+            if (settings['exclude_patterns'] && isExclude(settings['exclude_patterns'])) {
+                // return
+            }
+            else  {
+                postMessage({ name: 'siteinfo', url: location.href })
+                launchAutoPager([MICROFORMAT])
+            }
+        }
+    }
+}
+else {
+    launchAutoPager(SITEINFO)
+    GM_registerMenuCommand('AutoPagerize - clear cache', clearCache)
+    var cacheInfo = getCache()
+    var xhrStates = {}
+    SITEINFO_IMPORT_URLS.forEach(function(i) {
+        if (!cacheInfo[i] || new Date(cacheInfo[i].expire) < new Date()) {
+            var opt = {
+                method: 'get',
+                url: i,
+                onload: function(res) {
+                    xhrStates[i] = 'loaded'
+                    getCacheCallback(res, i)
+                },
+                onerror: function(res){
+                    xhrStates[i] = 'error'
+                    getCacheErrorCallback(i)
+                },
+            }
+            xhrStates[i] = 'start'
+            GM_xmlhttpRequest(opt)
+            setTimeout(function() {
+                if (xhrStates[i] == 'start') {
+                    getCacheErrorCallback(i)
+                }
+            }, XHR_TIMEOUT)
+        }
+        else {
+            launchAutoPager(cacheInfo[i].info)
+        }
+    })
+    launchAutoPager([MICROFORMAT])
+}
+
+
+// new google search sucks!
+if (location.href.match('^http://[^.]+\.google\.(?:[^.]{2,3}\.)?[^./]{2,3}/.*(&fp=)')) {
+    var to = location.href.replace(/&fp=.*/, '')
+    // console.log([location.href, to])
+    location.href = to
+}
+
+
+
+
+// utility functions.
+function createHTMLDocumentByString(str) {
+    if (document.documentElement.nodeName != 'HTML') {
+        return new DOMParser().parseFromString(str, 'application/xhtml+xml')
+    }
+    var html = strip_html_tag(str)
+    var htmlDoc
+    try {
+        // We have to handle exceptions since Opera 9.6 throws
+        // a NOT_SUPPORTED_ERR exception for |document.cloneNode(false)|
+        // against the DOM 3 Core spec.
+        htmlDoc = document.cloneNode(false)
+        htmlDoc.appendChild(htmlDoc.importNode(document.documentElement, false))
+    }
+    catch(e) {
+        htmlDoc = document.implementation.createDocument(null, 'html', null)
+    }
+    var fragment = createDocumentFragmentByString(html)
+    try {
+        fragment = htmlDoc.adoptNode(fragment)
+    }
+    catch(e) {
+        fragment = htmlDoc.importNode(fragment, true)
+    }
+    htmlDoc.documentElement.appendChild(fragment)
+    return htmlDoc
+}
+
+function getElementsByXPath(xpath, node) {
+    var nodesSnapshot = getXPathResult(xpath, node,
+        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE)
+    var data = []
+    for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
+        data.push(nodesSnapshot.snapshotItem(i))
+    }
+    return data
+}
+
+function getFirstElementByXPath(xpath, node) {
+    var result = getXPathResult(xpath, node,
+        XPathResult.FIRST_ORDERED_NODE_TYPE)
+    return result.singleNodeValue
+}
+
+function getXPathResult(xpath, node, resultType) {
+    var node = node || document
+    var doc = node.ownerDocument || node
+    var resolver = doc.createNSResolver(node.documentElement || node)
+    // Use |node.lookupNamespaceURI('')| for Opera 9.5
+    // A workaround for bugs of Node.lookupNamespaceURI(null)
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=693615
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=694754
+    var defaultNS = null
+    try {
+        // This follows the spec: http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespaceURIAlgo
+        if (node.nodeType == node.DOCUMENT_NODE) {
+            defaultNS = node.documentElement.lookupNamespaceURI(null)
+        }
+        else {
+            defaultNS = node.lookupNamespaceURI(null)
+        }
+    }
+    catch(e) {}
+
+    if (defaultNS) {
+        const defaultPrefix = '__default__'
+        xpath = addDefaultPrefix(xpath, defaultPrefix)
+        var defaultResolver = resolver
+        resolver = function (prefix) {
+            return (prefix == defaultPrefix)
+                ? defaultNS : defaultResolver.lookupNamespaceURI(prefix)
+        }
+    }
+    return doc.evaluate(xpath, node, resolver, resultType, null)
+}
+
+function addDefaultPrefix(xpath, prefix) {
+    const tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[\)\]])|(\/\/?|!=|[<>]=?|[\(\[|,=+-])|([@$])/g
+    const TERM = 1, OPERATOR = 2, MODIFIER = 3
+    var tokenType = OPERATOR
+    prefix += ':'
+    function replacer(token, identifier, suffix, term, operator, modifier) {
+        if (suffix) {
+            tokenType =
+                (suffix == ':' || (suffix == '::' &&
+                 (identifier == 'attribute' || identifier == 'namespace')))
+                ? MODIFIER : OPERATOR
+        }
+        else if (identifier) {
+            if (tokenType == OPERATOR && identifier != '*') {
+                token = prefix + token
+            }
+            tokenType = (tokenType == TERM) ? OPERATOR : TERM
+        }
+        else {
+            tokenType = term ? TERM : operator ? OPERATOR : MODIFIER
+        }
+        return token
+    }
+    return xpath.replace(tokenPattern, replacer)
+}
+
+function createDocumentFragmentByString(str) {
+    var range = document.createRange()
+    range.setStartAfter(document.body)
+    return range.createContextualFragment(str)
+}
+
+function log(message) {
+    if (typeof console == 'object') {
+        console.log(message)
+    }
+    else {
+        GM_log(message)
+    }
+}
+
+function debug() {
+    if ( typeof DEBUG != 'undefined' && DEBUG ) {
+        console.log.apply(console, arguments)
+    }
+}
+
+function getElementPosition(elem) {
+    var offsetTrail = elem
+    var offsetLeft  = 0
+    var offsetTop   = 0
+    while (offsetTrail) {
+        offsetLeft += offsetTrail.offsetLeft
+        offsetTop  += offsetTrail.offsetTop
+        offsetTrail = offsetTrail.offsetParent
+    }
+    offsetTop = offsetTop || null
+    offsetLeft = offsetLeft || null
+    return {left: offsetLeft, top: offsetTop}
+}
+
+function getElementBottom(elem) {
+    var c_style = document.defaultView.getComputedStyle(elem, '')
+    var height  = 0
+    var prop    = ['height', 'borderTopWidth', 'borderBottomWidth',
+                   'paddingTop', 'paddingBottom',
+                   'marginTop', 'marginBottom']
+    prop.forEach(function(i) {
+        var h = parseInt(c_style[i])
+        if (typeof h == 'number') {
+            height += h
+        }
+    })
+    var top = getElementPosition(elem).top
+    return top ? (top + height) : null
+}
+
+function getScrollHeight() {
+    return Math.max(document.documentElement.scrollHeight,
+                                document.body.scrollHeight)
+}
+
+function isSameDomain(url) {
+    if (url.match(/^\w+:/)) {
+        var url_s = url.split(/[\/\?]/)
+        return url_s[0] == location.protocol && location.host == url_s[2]
+    }
+    else {
+        return true
+    }
+}
+
+function isSameBaseUrl(urlA, urlB) {
+    return (urlA.replace(/[^/]+$/, '') == urlB.replace(/[^/]+$/, ''))
+}
+
+function resolvePath(path, base) {
+    if (path.match(/^https?:\/\//)) {
+        return path
+    }
+    if (path.match(/^\?/)) {
+        return base.replace(/\?.+$/, '') + path;
+    }
+    if (path.match(/^[^\/]/)) {
+        return base.replace(/[^/]+$/, '') + path
+    }
+    else {
+        return base.replace(/([^/]+:\/\/[^/]+)\/.*/, '\$1') + path
+    }
+}
+
+function fixResolvePath() {
+    if (resolvePath('', 'http://resolve.test/') == 'http://resolve.test/') {
+        return
+    }
+    // A workaround for WebKit and Mozilla 1.9.2a1pre,
+    // which don't support XML Base in HTML.
+    // https://bugs.webkit.org/show_bug.cgi?id=17423
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=505783
+    var XML_NS = 'http://www.w3.org/XML/1998/namespace'
+    var baseElement = document.createElementNS(null, 'base')
+    var pathElement = document.createElementNS(null, 'path')
+    baseElement.appendChild(pathElement)
+    resolvePath = function resolvePath_workaround(path, base) {
+        baseElement.setAttributeNS(XML_NS, 'xml:base', base)
+        pathElement.setAttributeNS(XML_NS, 'xml:base', path)
+        return pathElement.baseURI
+    }
+}
+
+function strip_html_tag(str) {
+    var chunks = str.split(/(<html(?:[ \t\r\n][^>]*)?>)/)
+    if (chunks.length >= 3) {
+        chunks.splice(0, 2)
+    }
+    str = chunks.join('')
+    chunks = str.split(/(<\/html[ \t\r\n]*>)/)
+    if (chunks.length >= 3) {
+        chunks.splice(chunks.length - 2)
+    }
+    return chunks.join('')
+}
+
+function getPref(key, defaultValue) {
+    var value = GM_getValue(key)
+    return (typeof value == 'undefined') ? defaultValue : value
+}
+
+function wildcard2regep(str) {
+    return '^' + str.replace(/([-()\[\]{}+?.$\^|,:#<!\\])/g, '\\$1').replace(/\x08/g, '\\x08').replace(/\*/g, '.*')
+}
+
+function isExclude(patterns) {
+    var rr = /^\/(.+)\/$/
+    var eps = (patterns || '').split(/[\r\n ]+/)
+    for (var i = 0; i < eps.length; i++) {
+        var reg = null
+        if (rr.test(eps[i])) {
+            reg = eps[i].match(rr)[1]
+        }
+        else {
+            reg = wildcard2regep(eps[i])
+        }
+        if (location.href.match(reg)) {
+            return true
+        }
+    }
+    return false
+}
+// obsolete
+function isFirefoxExtension() {
+    return (typeof chlorine == 'object')
+}
+
+function isChromeExtension() {
+    return (typeof chrome == 'object') &&
+        (typeof chrome.extension == 'object')
+}
+
+function isSafariExtension() {
+    return (typeof safari == 'object') &&
+        (typeof safari.extension == 'object')
+}
+
+function isGreasemonkey() {
+    return (typeof GM_log == 'function')
+}
+
+function isJetpack() {
+    // isFirefoxExtension is obsolete
+    return (!isGreasemonkey() && !isSafariExtension() &&
+            !isChromeExtension() && !isFirefoxExtension())
+}
+
+function gmCompatible() {
+    GM_registerMenuCommand = function() {}
+    GM_setValue = function() {}
+    GM_getValue = function() {}
+    GM_addStyle = function() {}
+    uneval = function() {}
+    fixResolvePath = function() {}
+    resolvePath = function (path, base) { return path }
+
+    if (isChromeExtension() || isSafariExtension()) {
+        createHTMLDocumentByString = function(str) {
+            if (document.documentElement.nodeName != 'HTML') {
+                return new DOMParser().parseFromString(str, 'application/xhtml+xml')
+            }
+            // FIXME
+            var html = str.replace(/<script(?:[ \t\r\n][^>]*)?>[\S\s]*?<\/script[ \t\r\n]*>|<\/?(?:i?frame|html|script|object)(?:[ \t\r\n][^<>]*)?>/gi, ' ')
+            var htmlDoc = document.implementation.createHTMLDocument ?
+                document.implementation.createHTMLDocument('apfc') :
+                document.implementation.createDocument(null, 'html', null)
+            var range = document.createRange()
+            range.selectNodeContents(document.documentElement)
+            htmlDoc.documentElement.appendChild(range.createContextualFragment(html))
+            return htmlDoc
+        }
+    }
+    return true
+}