AutoPager

AutoPager

コード

function AutoPager(info) {
    this.pageNum = 1
    this.info = info
    this.state = settings.disable ? 'disable' : 'enable'
    var self = this
  • サイトインフォの nextLink にマッチする url を探す
    var url = this.getNextURL(info.nextLink, document, location.href)
  • getNextURL にサイトインフォの nextLink と document と1ページ目の location.href を渡して次のページの url を得る
  • nextLink にマッチする url が無ければ return で AutoPager を抜ける
    if (!url) {
        debug("getNextURL returns null.", info.nextLink)
        return
    }
  • サイトインフォの insertBefore が定義されている場合には、これにマッチする要素を探す
  • getFirstElementByXPath にサイトインフォの insertBefore を渡して insertPoint を得る
    if (info.insertBefore) {
        this.insertPoint = getFirstElementByXPath(info.insertBefore)
    }
  • insertPoint が無ければ、サイトインフォの pageElement から insertPoint を得る
    • サイトインフォの insertBefore が定義されているけどもマッチしなかった場合と、
    • サイトインフォの insertBefore が定義されていない場合
    if (!this.insertPoint) {
        var lastPageElement = getElementsByXPath(info.pageElement).pop()
        if (lastPageElement) {
            this.insertPoint = lastPageElement.nextSibling ||
                lastPageElement.parentNode.appendChild(document.createTextNode(' '))
        }
    }
  • insertPoint が無ければ return で AutoPager を抜ける
    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
    this.scroll= function() { self.onScroll() }
    window.addEventListener("scroll", this.scroll, false)

    this.initMessageBar()
    extension.addListener('toggleRequest', function(res) {
        if (ap) {
            ap.toggle()
        }
    })
    extension.addListener('enableRequest', function(res) {
        if (ap) {
            ap.enable()
        }
    })
    extension.addListener('disableRequest', function(res) {
        if (ap) {
            ap.disable()
        }
    })
    extension.postMessage('launched', {url: location.href })
    if (Extension.isSafari()) {
        document.addEventListener('contextmenu', function(event) {
            safari.self.tab.setContextMenuEventUserInfo(event, 'launched')
        }, false)
    }

    var scrollHeight = getScrollHeight()
    var bottom = getElementPosition(this.insertPoint).top ||
        this.getPageElementsBottom() ||
        (Math.round(scrollHeight * 0.8))
    this.remainHeight = scrollHeight - bottom + BASE_REMAIN_HEIGHT
    this.reqTime = new Date()
    this.onScroll()

    var that = this
    document.addEventListener('AutoPagerizeToggleRequest', function() {
        that.toggle()
    }, false)
    document.addEventListener('AutoPagerizeEnableRequest', function() {
        that.enable()
    }, false)
    document.addEventListener('AutoPagerizeDisableRequest', function() {
        that.disable()
    }, false)
    document.addEventListener('AutoPagerizeUpdateSettingsRequest', function() {
        extension.postMessage('settings', {}, function(res) {
            settings = res
        })
    }, false)
}

getPageElementsBottom

AutoPager.prototype.getPageElementsBottom = function() {
   try {
        var elems = getElementsByXPath(this.info.pageElement)
        var bs = elems.map(function(i) { return getElementBottom(i) })
        return Math.max.apply(Math, bs)
    }
    catch(e) {}
}

initMessageBar

AutoPager.prototype.initMessageBar = function() {
    var frame = document.createElement('iframe')
    frame.id = 'autopagerize_message_bar'
    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

    // no icon.
    var u = 'data:text/html;base64,PGh0bWw+CjxoZWFkPgo8c3R5bGU+CmJvZHkgewogIG1hcmdpbjogMDsKICBwYWRkaW5nOiA0cHggMCAwIDEwcHg7CiAgY29sb3I6ICNmZmY7CiAgYmFja2dyb3VuZC1jb2xvcjogIzAwMDsKICBmb250LXNpemU6IDEycHg7CiAgdGV4dC1hbGlnbjogY2VudGVyOwp9CmltZyB7CiAgdmVydGljYWwtYWxpZ246IHRvcDsKfQo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5PkxvYWRpbmcuLi48L2JvZHk+CjwvaHRtbD4K'
    if (settings['extension_path']) {
        u = settings['extension_path'] + 'loading.html'
    }
    else if (settings['loading_html']) {
        u = settings['loading_html']
    }
    this.messageFrame.src = u
    document.body.appendChild(frame)
}

onScroll

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()
    }
}

stateToggle

AutoPager.prototype.stateToggle = function() {
    if (this.state == 'enable') {
        this.disable()
    }
    else {
        this.enable()
    }
}

enable

AutoPager.prototype.enable = function() {
    this.state = 'enable'
}

disable

AutoPager.prototype.disable = function() {
    this.state = 'disable'
}

request

AutoPager.prototype.request = function() {
    if (!this.requestURL || this.lastRequestURL == this.requestURL) {
        return
    }
    var self = this
    var now = new Date()
    if (this.reqTime && now - this.reqTime < MIN_REQUEST_INTERVAL) {
        setTimeout(function() { self.onScroll() }, MIN_REQUEST_INTERVAL)
        return
    }
    else {
        this.reqTime = now
    }

    this.lastRequestURL = this.requestURL
    this.showLoading(true)
    if (Extension.isFirefox()) {
        extension.postMessage('get', { url:  this.requestURL, fromURL: location.href, charset: document.characterSet, cookie: document.cookie }, function(res) {
            if (res.responseText && res.finalURL) {
                self.load(createHTMLDocumentByString(res.responseText), res.finalURL)
            }
            else {
                self.error()
            }
        })
    }
    else {
        loadWithIframe(this.requestURL, function(doc, url) {
            self.load(doc, url)
        }, function(err) {
            self.error()
        })
    }
}

showLoading

AutoPager.prototype.showLoading = function(sw) {
    if (sw) {
        if (this.messageFrame && settings['display_message_bar']) {
            this.messageFrame.style.display = 'block'
        }
    }
    else {
        if (this.messageFrame) {
            this.messageFrame.style.display = 'none'
        }
    }
}

load

AutoPager.prototype.load = function(htmlDoc, url) {
    if (url && !isSameDomain(url)) {
        this.error()
        return
    }
    try {
        var page = getElementsByXPath(this.info.pageElement, htmlDoc)
        var url = this.getNextURL(this.info.nextLink, htmlDoc, this.requestURL)
    }
    catch(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)
}

addPage

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 (getRoot(this.insertPoint) != document) {
        var lastPageElement = getElementsByXPath(this.info.pageElement).pop()
        if (lastPageElement) {
            this.insertPoint = lastPageElement.nextSibling ||
                lastPageElement.parentNode.appendChild(document.createTextNode(' '))
        }
    }

    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: ' + (++this.pageNum) + ''
    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
    })
}

getNextURL

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))
        }
    }
}

terminate

AutoPager.prototype.terminate = function() {
    window.removeEventListener('scroll', this.scroll, false)
    var self = this
    setTimeout(function() {
        if (self.icon) {
            self.icon.parentNode.removeChild(self.icon)
        }
        if (self.messageFrame) {
            var mf = self.messageFrame
            mf.parentNode.removeChild(mf)
        }
    }, 1500)
}

error

AutoPager.prototype.error = function() {
    window.removeEventListener('scroll', this.scroll, false)
    if (this.messageFrame) {
        var mf = this.messageFrame
        var u = 'data:text/html;base64,PGh0bWw+CjxoZWFkPgo8c3R5bGU+CmJvZHkgewogIG1hcmdpbjogMDsKICBwYWRkaW5nOiA0cHggMCAwIDEwcHg7CiAgY29sb3I6ICNmZmY7CiAgYmFja2dyb3VuZC1jb2xvcjogI2EwMDsKICBmb250LXNpemU6IDEycHg7CiAgdGV4dC1hbGlnbjogY2VudGVyOwp9CmltZyB7CiAgdmVydGljYWwtYWxpZ246IHRvcDsKfQo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5PkVycm9yITwvYm9keT4KPC9odG1sPgo='
        if (settings['extension_path']) {
            u = settings['extension_path'] + 'error.html'
        }
        else if (settings['error_html']) {
            u = settings['error_html']
        }
        mf.src = u
        mf.style.display = 'block'
        setTimeout(function() {
            if (mf) {
                mf.parentNode.removeChild(mf)
            }
        }, 3000)
    }
}

filters

AutoPager.filters = []

launchAutoPager

AutoPager.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) {
            continue
        }
    }
}