AutoPagerize

loading next page and inserting into current page.

  1. // ==UserScript==
  2. // @name AutoPagerize
  3. // @namespace http://swdyh.yu.to/
  4. // @description loading next page and inserting into current page.
  5. // @include http://*
  6. // @include https://*
  7. // @exclude https://mail.google.com/*
  8. // @exclude http://b.hatena.ne.jp/*
  9. // @exclude http://www.facebook.com/plugins/like.php*
  10. // @exclude http://api.tweetmeme.com/button.js*
  11. // @version 0.0.66
  12. // @icon http://autopagerize.net/img/icons/icon_032.png
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_addStyle
  16. // @grant GM_log
  17. // @grant GM_xmlhttpRequest
  18. // @grant GM_registerMenuCommand
  19. // ==/UserScript==
  20. //
  21. // auther: swdyh http://d.hatena.ne.jp/swdyh/
  22. // version: 0.0.66 2012-08-31T18:23:34+09:00
  23. //
  24. // this script based on
  25. // GoogleAutoPager(http://la.ma.la/blog/diary_200506231749.htm) and
  26. // estseek autopager(http://la.ma.la/blog/diary_200601100209.htm).
  27. // thanks to ma.la.
  28. //
  29. // Released under the GPL license
  30. // http://www.gnu.org/copyleft/gpl.html
  31. //
  32.  
  33. if (isGreasemonkey()) {
  34. var ep = getPref('exclude_patterns')
  35. if (ep && isExclude(ep)) {
  36. // FIXME
  37. // return
  38. }
  39. }
  40. else {
  41. gmCompatible()
  42. }
  43.  
  44. var URL = 'http://autopagerize.net/'
  45. var VERSION = '0.0.66'
  46. var DEBUG = false
  47. var AUTO_START = true
  48. var CACHE_EXPIRE = 24 * 60 * 60 * 1000
  49. var BASE_REMAIN_HEIGHT = 400
  50. var FORCE_TARGET_WINDOW = getPref('force_target_window', true)
  51. var XHR_TIMEOUT = 30 * 1000
  52. var SITEINFO_IMPORT_URLS = [
  53. 'http://wedata.net/databases/AutoPagerize/items.json',
  54. ]
  55. var COLOR = {
  56. on: '#0f0',
  57. off: '#ccc',
  58. loading: '#0ff',
  59. terminated: '#00f',
  60. error: '#f0f'
  61. }
  62. var SITEINFO = [
  63. /* sample
  64. {
  65. url: 'http://(.*).google.+/(search).+',
  66. nextLink: 'id("navbar")//td[last()]/a',
  67. pageElement: '//div[@id="res"]/div',
  68. exampleUrl: 'http://www.google.com/search?q=nsIObserver',
  69. },
  70. */
  71. /* template
  72. {
  73. url: '',
  74. nextLink: '',
  75. pageElement: '',
  76. exampleUrl: '',
  77. },
  78. */
  79. ]
  80. var MICROFORMAT = {
  81. url: '.*',
  82. nextLink: '//a[@rel="next"] | //link[@rel="next"]',
  83. insertBefore: '//*[contains(@class, "autopagerize_insert_before")]',
  84. pageElement: '//*[contains(@class, "autopagerize_page_element")]',
  85. }
  86.  
  87. var AutoPager = function(info) {
  88. this.pageNum = 1
  89. this.info = info
  90. this.state = AUTO_START ? 'enable' : 'disable'
  91. var self = this
  92. var url = this.getNextURL(info.nextLink, document, location.href)
  93.  
  94. if ( !url ) {
  95. debug("getNextURL returns null.", info.nextLink)
  96. return
  97. }
  98. if (info.insertBefore) {
  99. this.insertPoint = getFirstElementByXPath(info.insertBefore)
  100. }
  101.  
  102. if (!this.insertPoint) {
  103. var lastPageElement = getElementsByXPath(info.pageElement).pop()
  104. if (lastPageElement) {
  105. this.insertPoint = lastPageElement.nextSibling ||
  106. lastPageElement.parentNode.appendChild(document.createTextNode(' '))
  107. }
  108. }
  109.  
  110. if (!this.insertPoint) {
  111. debug("insertPoint not found.", lastPageElement, info.pageElement)
  112. return
  113. }
  114.  
  115. this.requestURL = url
  116. this.loadedURLs = {}
  117. this.loadedURLs[location.href] = true
  118. var toggle = function() {self.stateToggle()}
  119. this.toggle = toggle
  120. GM_registerMenuCommand('AutoPagerize - on/off', toggle)
  121. this.scroll= function() { self.onScroll() }
  122. window.addEventListener("scroll", this.scroll, false)
  123.  
  124. if (isFirefoxExtension()) {
  125. var div = document.createElement("div")
  126. div.setAttribute('id', 'autopagerize_icon')
  127. div.style.display = 'none'
  128. document.body.appendChild(div)
  129. this.icon = div
  130. }
  131. else if (isChromeExtension() || isSafariExtension() || isJetpack()) {
  132. var frame = document.createElement('iframe')
  133. frame.style.display = 'none'
  134. frame.style.position = 'fixed'
  135. frame.style.bottom = '0px'
  136. frame.style.left = '0px'
  137. frame.style.height = '25px'
  138. frame.style.border = '0px'
  139. frame.style.opacity = '0.8'
  140. frame.style.zIndex = '1000'
  141. frame.width = '100%'
  142. frame.scrolling = 'no'
  143. this.messageFrame = frame
  144. var u = settings['extension_path'] ?
  145. settings['extension_path'] + 'loading.html' :
  146. 'http://autopagerize.net/files/loading.html'
  147. this.messageFrame.src = u
  148. document.body.appendChild(frame)
  149. if (isSafariExtension()) {
  150. safari.self.tab.dispatchMessage('launched', {url: location.href })
  151. }
  152. else if (isChromeExtension()) {
  153. chrome.extension.connect({name: "launched"}).postMessage()
  154. }
  155. if (isJetpack()) {
  156. postMessage({name: 'launched', data: location.href })
  157. }
  158. }
  159. else {
  160. this.initIcon()
  161. this.initHelp()
  162. GM_addStyle('@media print{#autopagerize_icon, #autopagerize_help {display: none !important;}}')
  163. GM_addStyle('hr.autopagerize_page_separator {clear: both;}')
  164. this.icon.addEventListener("mouseover", function() {
  165. self.viewHelp()
  166. }, true)
  167. }
  168.  
  169. var scrollHeight = getScrollHeight()
  170. var bottom = getElementPosition(this.insertPoint).top ||
  171. this.getPageElementsBottom() ||
  172. (Math.round(scrollHeight * 0.8))
  173. this.remainHeight = scrollHeight - bottom + BASE_REMAIN_HEIGHT
  174. this.onScroll()
  175.  
  176. var that = this
  177. document.addEventListener('AutoPagerizeToggleRequest', function() {
  178. that.toggle()
  179. }, false)
  180. document.addEventListener('AutoPagerizeUpdateIconRequest', function() {
  181. that.updateIcon()
  182. }, false)
  183. that.updateIcon()
  184. }
  185.  
  186. AutoPager.prototype.getPageElementsBottom = function() {
  187. try {
  188. var elem = getElementsByXPath(this.info.pageElement).pop()
  189. return getElementBottom(elem)
  190. }
  191. catch(e) {}
  192. }
  193.  
  194. AutoPager.prototype.initHelp = function() {
  195. var helpDiv = document.createElement('div')
  196. helpDiv.setAttribute('id', 'autopagerize_help')
  197. helpDiv.setAttribute('style', 'padding:5px;position:fixed;' +
  198. 'top:-200px;right:3px;font-size:10px;' +
  199. 'background:#fff;color:#000;border:1px solid #ccc;' +
  200. 'z-index:256;text-align:left;font-weight:normal;' +
  201. 'line-height:120%;font-family:verdana;')
  202.  
  203. var toggleDiv = document.createElement('div')
  204. toggleDiv.setAttribute('style', 'margin:0 0 0 50px;')
  205. var a = document.createElement('a')
  206. a.setAttribute('class', 'autopagerize_link')
  207. a.innerHTML = 'on/off'
  208. a.href = 'javascript:void(0)'
  209. var self = this
  210. var toggle = function() {
  211. self.stateToggle()
  212. helpDiv.style.top = '-200px'
  213. }
  214. a.addEventListener('click', toggle, false)
  215. toggleDiv.appendChild(a)
  216.  
  217. var s = '<div style="width:100px; float:left;">'
  218. for (var i in COLOR) {
  219. s += '<div style="float:left;width:1em;height:1em;' +
  220. 'margin:0 3px;background-color:' + COLOR[i] + ';' +
  221. '"></div><div style="margin:0 3px">' + i + '</div>'
  222. }
  223. s += '</div>'
  224. var colorDiv = document.createElement('div')
  225. colorDiv.innerHTML = s
  226. helpDiv.appendChild(colorDiv)
  227. helpDiv.appendChild(toggleDiv)
  228.  
  229. var versionDiv = document.createElement('div')
  230. versionDiv.setAttribute('style', 'clear:both;')
  231. versionDiv.innerHTML = '<a href="' + URL +
  232. '">AutoPagerize</a> ver ' + VERSION
  233. helpDiv.appendChild(versionDiv)
  234. document.body.appendChild(helpDiv)
  235.  
  236. var proc = function(e) {
  237. var c_style = document.defaultView.getComputedStyle(helpDiv, '')
  238. var s = ['top', 'left', 'height', 'width'].map(function(i) {
  239. return parseInt(c_style.getPropertyValue(i)) })
  240. if (e.clientX < s[1] || e.clientX > (s[1] + s[3] + 11) ||
  241. e.clientY < s[0] || e.clientY > (s[0] + s[2] + 11)) {
  242. helpDiv.style.top = '-200px'
  243. }
  244. }
  245. helpDiv.addEventListener('mouseout', proc, false)
  246. this.helpLayer = helpDiv
  247. GM_addStyle('#autopagerize_help a { color: #0f0; text-decoration: underline;}')
  248. }
  249.  
  250. AutoPager.prototype.viewHelp = function() {
  251. this.helpLayer.style.top = '3px'
  252. }
  253.  
  254. AutoPager.prototype.onScroll = function() {
  255. var scrollHeight = Math.max(document.documentElement.scrollHeight,
  256. document.body.scrollHeight)
  257. var remain = scrollHeight - window.innerHeight - window.scrollY
  258. if (this.state == 'enable' && remain < this.remainHeight) {
  259. this.request()
  260. }
  261. }
  262.  
  263. AutoPager.prototype.stateToggle = function() {
  264. if (this.state == 'enable') {
  265. this.disable()
  266. }
  267. else {
  268. this.enable()
  269. }
  270. }
  271.  
  272. AutoPager.prototype.enable = function() {
  273. this.state = 'enable'
  274. this.updateIcon()
  275. }
  276.  
  277. AutoPager.prototype.disable = function() {
  278. this.state = 'disable'
  279. this.updateIcon()
  280. }
  281.  
  282. AutoPager.prototype.updateIcon = function(state) {
  283. var st = state || this.state
  284. var rename = {'enable': 'on', 'disable': 'off' }
  285. if (rename[st]) {
  286. st = rename[st]
  287. }
  288. var color = COLOR[st]
  289. if (color) {
  290. if (isFirefoxExtension()) {
  291. chlorine.pageAction.update(color, location.href)
  292. }
  293. else if (isChromeExtension()) {
  294. chrome.extension.connect({name: "pageActionChannel"}).postMessage(color)
  295. }
  296. else if (isSafariExtension() || isJetpack()) {
  297. }
  298. else {
  299. this.icon.style.background = color
  300. }
  301. }
  302. }
  303.  
  304. AutoPager.prototype.request = function() {
  305. if (!this.requestURL || this.lastRequestURL == this.requestURL) {
  306. return
  307. }
  308. this.lastRequestURL = this.requestURL
  309. var self = this
  310. var mime = 'text/html; charset=' + document.characterSet
  311. var headers = {}
  312.  
  313. if (isSameDomain(this.requestURL)) {
  314. headers.Cookie = document.cookie
  315. }
  316. else {
  317. this.error()
  318. return
  319. }
  320. var opt = {
  321. method: 'get',
  322. url: this.requestURL,
  323. headers: headers,
  324. overrideMimeType: mime,
  325. onerror: function(res) {
  326. self.error()
  327. },
  328. onload: function(res) {
  329. if (res.finalUrl) {
  330. var url_s = res.finalUrl.split(/[\/\?]/)
  331. if (url_s[0] == location.protocol && location.host == url_s[2]) {
  332. self.requestLoad.apply(self, [res])
  333. return
  334. }
  335. }
  336. self.error()
  337. }
  338. }
  339. AutoPager.requestFilters.forEach(function(i) { i(opt) }, this)
  340. if (opt.stop) {
  341. this.requestURL = opt.url
  342. }
  343. else {
  344. this.showLoading(true)
  345. GM_xmlhttpRequest(opt)
  346. }
  347. }
  348.  
  349. AutoPager.prototype.showLoading = function(sw) {
  350. if (sw) {
  351. this.updateIcon('loading')
  352. if (this.messageFrame && settings['display_message_bar']) {
  353. this.messageFrame.style.display = 'block'
  354. }
  355. }
  356. else {
  357. this.updateIcon('enable')
  358. if (this.messageFrame) {
  359. this.messageFrame.style.display = 'none'
  360. }
  361. }
  362. }
  363.  
  364. AutoPager.prototype.requestLoad = function(res) {
  365. AutoPager.responseFilters.forEach(function(i) {
  366. i(res, this.requestURL)
  367. }, this)
  368. var htmlDoc = createHTMLDocumentByString(res.responseText)
  369. AutoPager.documentFilters.forEach(function(i) {
  370. i(htmlDoc, this.requestURL, this.info)
  371. }, this)
  372. try {
  373. var page = getElementsByXPath(this.info.pageElement, htmlDoc)
  374. var url = this.getNextURL(this.info.nextLink, htmlDoc, this.requestURL)
  375. }
  376. catch(e){
  377. log(e)
  378. this.error()
  379. return
  380. }
  381.  
  382. if (!page || page.length < 1 ) {
  383. debug('pageElement not found.' , this.info.pageElement)
  384. this.terminate()
  385. return
  386. }
  387.  
  388. if (this.loadedURLs[this.requestURL]) {
  389. debug('page is already loaded.', this.requestURL, this.info.nextLink)
  390. this.terminate()
  391. return
  392. }
  393.  
  394. this.loadedURLs[this.requestURL] = true
  395. page = this.addPage(htmlDoc, page)
  396. AutoPager.filters.forEach(function(i) {
  397. i(page)
  398. })
  399. this.requestURL = url
  400. this.showLoading(false)
  401. this.onScroll()
  402. if (!url) {
  403. debug('nextLink not found.', this.info.nextLink, htmlDoc)
  404. this.terminate()
  405. }
  406. var ev = document.createEvent('Event')
  407. ev.initEvent('GM_AutoPagerizeNextPageLoaded', true, false)
  408. document.dispatchEvent(ev)
  409. }
  410.  
  411. AutoPager.prototype.addPage = function(htmlDoc, page) {
  412. var HTML_NS = 'http://www.w3.org/1999/xhtml'
  413. var hr = document.createElementNS(HTML_NS, 'hr')
  414. var p = document.createElementNS(HTML_NS, 'p')
  415. hr.setAttribute('class', 'autopagerize_page_separator')
  416. p.setAttribute('class', 'autopagerize_page_info')
  417. var self = this
  418.  
  419. if (page[0] && /tr/i.test(page[0].tagName)) {
  420. var insertParent = this.insertPoint.parentNode
  421. var colNodes = getElementsByXPath('child::tr[1]/child::*[self::td or self::th]', insertParent)
  422.  
  423. var colums = 0
  424. for (var i = 0, l = colNodes.length; i < l; i++) {
  425. var col = colNodes[i].getAttribute('colspan')
  426. colums += parseInt(col, 10) || 1
  427. }
  428. var td = document.createElement('td')
  429. // td.appendChild(hr)
  430. td.appendChild(p)
  431. var tr = document.createElement('tr')
  432. td.setAttribute('colspan', colums)
  433. tr.appendChild(td)
  434. insertParent.insertBefore(tr, this.insertPoint)
  435. }
  436. else {
  437. this.insertPoint.parentNode.insertBefore(hr, this.insertPoint)
  438. this.insertPoint.parentNode.insertBefore(p, this.insertPoint)
  439. }
  440.  
  441. p.innerHTML = 'page: <a class="autopagerize_link" href="' +
  442. this.requestURL.replace(/&/g, '&amp;') + '">' + (++this.pageNum) + '</a>'
  443. return page.map(function(i) {
  444. var pe = document.importNode(i, true)
  445. self.insertPoint.parentNode.insertBefore(pe, self.insertPoint)
  446. var ev = document.createEvent('MutationEvent')
  447. ev.initMutationEvent('AutoPagerize_DOMNodeInserted', true, false,
  448. self.insertPoint.parentNode, null,
  449. self.requestURL, null, null)
  450. pe.dispatchEvent(ev)
  451. return pe
  452. })
  453. }
  454.  
  455. AutoPager.prototype.initIcon = function() {
  456. var div = document.createElement("div")
  457. div.setAttribute('id', 'autopagerize_icon')
  458. with (div.style) {
  459. fontSize = '12px'
  460. position = 'fixed'
  461. top = '3px'
  462. right = '3px'
  463. background = COLOR['on']
  464. color = '#fff'
  465. width = '10px'
  466. height = '10px'
  467. zIndex = '255'
  468. if (this.state != 'enable') {
  469. background = COLOR['off']
  470. }
  471. }
  472. document.body.appendChild(div)
  473. this.icon = div
  474. }
  475.  
  476. AutoPager.prototype.getNextURL = function(xpath, doc, url) {
  477. var nextLink = getFirstElementByXPath(xpath, doc)
  478. if (nextLink) {
  479. var nextValue = nextLink.getAttribute('href') ||
  480. nextLink.getAttribute('action') || nextLink.value
  481. if (nextValue.match(/^http(s)?:/)) {
  482. return nextValue
  483. }
  484. else {
  485. var base = getFirstElementByXPath('//base[@href]', doc)
  486. return resolvePath(nextValue, (base ? base.href : url))
  487. }
  488. }
  489. }
  490.  
  491. AutoPager.prototype.terminate = function() {
  492. window.removeEventListener('scroll', this.scroll, false)
  493. this.updateIcon('terminated')
  494. var self = this
  495. setTimeout(function() {
  496. if (self.icon) {
  497. self.icon.parentNode.removeChild(self.icon)
  498. }
  499. if (isSafariExtension()) {
  500. var mf = self.messageFrame
  501. mf.parentNode.removeChild(mf)
  502. }
  503. }, 1500)
  504. }
  505.  
  506. AutoPager.prototype.error = function() {
  507. this.updateIcon('error')
  508. window.removeEventListener('scroll', this.scroll, false)
  509. if (isSafariExtension() || isChromeExtension() || isJetpack()) {
  510. var mf = this.messageFrame
  511. var u = settings['extension_path'] ?
  512. settings['extension_path'] + 'error.html' :
  513. 'http://autopagerize.net/files/error.html'
  514. mf.src = u
  515. mf.style.display = 'block'
  516. setTimeout(function() {
  517. mf.parentNode.removeChild(mf)
  518. }, 3000)
  519. }
  520. }
  521.  
  522. AutoPager.documentFilters = []
  523. AutoPager.requestFilters = []
  524. AutoPager.responseFilters = []
  525. AutoPager.filters = []
  526.  
  527. var parseInfo = function(str) {
  528. var lines = str.split(/\r\n|\r|\n/)
  529. var re = /(^[^:]*?):(.*)$/
  530. var strip = function(str) {
  531. return str.replace(/^\s*/, '').replace(/\s*$/, '')
  532. }
  533. var info = {}
  534. for (var i = 0; i < lines.length; i++) {
  535. if (lines[i].match(re)) {
  536. info[RegExp.$1] = strip(RegExp.$2)
  537. }
  538. }
  539. var isValid = function(info) {
  540. var infoProp = ['url', 'nextLink', 'pageElement']
  541. for (var i = 0; i < infoProp.length; i++) {
  542. if (!info[infoProp[i]]) {
  543. return false
  544. }
  545. }
  546. return true
  547. }
  548. return isValid(info) ? info : null
  549. }
  550. var launchAutoPager = function(list) {
  551. if (list.length == 0) {
  552. return
  553. }
  554. for (var i = 0; i < list.length; i++) {
  555. try {
  556. if (ap) {
  557. return
  558. }
  559. else if (!location.href.match(list[i].url)) {
  560. }
  561. else if (!getFirstElementByXPath(list[i].nextLink)) {
  562. // FIXME microformats case detection.
  563. // limiting greater than 12 to filter microformats like SITEINFOs.
  564. if (list[i].url.length > 12 ) {
  565. debug("nextLink not found.", list[i].nextLink)
  566. }
  567. }
  568. else if (!getFirstElementByXPath(list[i].pageElement)) {
  569. if (list[i].url.length > 12 ) {
  570. debug("pageElement not found.", list[i].pageElement)
  571. }
  572. }
  573. else {
  574. ap = new AutoPager(list[i])
  575. return
  576. }
  577. }
  578. catch(e) {
  579. log(e)
  580. continue
  581. }
  582. }
  583. }
  584. var clearCache = function() {
  585. GM_setValue('cacheInfo', '')
  586. }
  587. var getCache = function() {
  588. try {
  589. return JSON.parse(GM_getValue('cacheInfo')) || {}
  590. }
  591. catch(e) {
  592. return {}
  593. }
  594. }
  595. var getCacheCallback = function(res, url) {
  596. if (res.status != 200) {
  597. return getCacheErrorCallback(url)
  598. }
  599.  
  600. var info
  601. try {
  602. info = JSON.parse(res.responseText).map(function(i) { return i.data })
  603. }
  604. catch(e) {
  605. info = []
  606. }
  607. if (info.length > 0) {
  608. info = info.filter(function(i) { return ('url' in i) })
  609. info.sort(function(a, b) { return (b.url.length - a.url.length) })
  610.  
  611. var r_keys = ['url', 'nextLink', 'insertBefore', 'pageElement']
  612. info = info.map(function(i) {
  613. var item = {}
  614. r_keys.forEach(function(key) {
  615. if (i[key]) {
  616. item[key] = i[key]
  617. }
  618. })
  619. return item
  620. })
  621.  
  622. cacheInfo[url] = {
  623. url: url,
  624. expire: new Date(new Date().getTime() + CACHE_EXPIRE),
  625. info: info
  626. }
  627. GM_setValue('cacheInfo', JSON.stringify(cacheInfo))
  628. launchAutoPager(info)
  629. }
  630. else {
  631. getCacheErrorCallback(url)
  632. }
  633. }
  634. var getCacheErrorCallback = function(url) {
  635. var expire = new Date(new Date().getTime() + CACHE_EXPIRE)
  636. if (cacheInfo[url]) {
  637. cacheInfo[url].expire = expire
  638. launchAutoPager(cacheInfo[url].info)
  639. }
  640. else {
  641. cacheInfo[url] = {
  642. url: url,
  643. expire: expire,
  644. info: []
  645. }
  646. }
  647. GM_setValue('cacheInfo', cacheInfo.toSource())
  648. }
  649.  
  650. var linkFilter = function(doc, url) {
  651. var base = getFirstElementByXPath('//base[@href]', doc)
  652. var baseUrl = base ? base.href : url
  653. var isSameBase = isSameBaseUrl(location.href, baseUrl)
  654. if (!FORCE_TARGET_WINDOW && isSameBase) {
  655. return
  656. }
  657.  
  658. var anchors = getElementsByXPath('descendant-or-self::a[@href]', doc)
  659. anchors.forEach(function(i) {
  660. var attrHref = i.getAttribute('href')
  661. if (FORCE_TARGET_WINDOW && !attrHref.match(/^#|^javascript:/) &&
  662. i.className.indexOf('autopagerize_link') < 0) {
  663. i.target = '_blank'
  664. }
  665. if (!isSameBase && !attrHref.match(/^#|^\w+:/)) {
  666. i.href = resolvePath(i.getAttribute('href'), baseUrl)
  667. }
  668. })
  669.  
  670. if (!isSameBase) {
  671. var images = getElementsByXPath('descendant-or-self::img', doc)
  672. images.forEach(function(i) {
  673. i.src = resolvePath(i.getAttribute('src'), baseUrl)
  674. })
  675. }
  676. }
  677. AutoPager.documentFilters.push(linkFilter)
  678.  
  679. fixResolvePath()
  680.  
  681. if (typeof(window.AutoPagerize) == 'undefined') {
  682. window.AutoPagerize = {}
  683. window.AutoPagerize.addFilter = function(f) {
  684. AutoPager.filters.push(f)
  685. }
  686. window.AutoPagerize.addDocumentFilter = function(f) {
  687. AutoPager.documentFilters.push(f)
  688. }
  689. window.AutoPagerize.addResponseFilter = function(f) {
  690. AutoPager.responseFilters.push(f)
  691. }
  692. window.AutoPagerize.addRequestFilter = function(f) {
  693. AutoPager.requestFilters.push(f)
  694. }
  695. window.AutoPagerize.launchAutoPager = launchAutoPager
  696.  
  697. var ev = document.createEvent('Event')
  698. ev.initEvent('GM_AutoPagerizeLoaded', true, false)
  699. document.dispatchEvent(ev)
  700. }
  701.  
  702. var settings = {}
  703. var ap = null
  704. if (isChromeExtension()) {
  705. var port = chrome.extension.connect({name: "settingsChannel"})
  706. port.postMessage()
  707. port.onMessage.addListener(function(res) {
  708. settings = res
  709. if (res['exclude_patterns'] && isExclude(res['exclude_patterns'])) {
  710. return
  711. }
  712. launchAutoPager(SITEINFO)
  713. var port_ = chrome.extension.connect({name: "siteinfoChannel"})
  714. port_.postMessage({ url: location.href })
  715. port_.onMessage.addListener(function(res) {
  716. launchAutoPager(res)
  717. chrome.extension.onConnect.addListener(function(port) {
  718. if (port.name == "toggleRequestChannel") {
  719. port.onMessage.addListener(function(msg) {
  720. if (ap) {
  721. ap.toggle()
  722. }
  723. })
  724. }
  725. })
  726. })
  727. })
  728. }
  729. else if (isSafariExtension()) {
  730. var re_exclude = /^(about:|safari-extension:)/
  731. if (!location.href.match(re_exclude)) {
  732. safari.self.addEventListener('message', function(event) {
  733. if (event.name === 'settings') {
  734. settings = event.message
  735. safari.self.tab.dispatchMessage('siteinfoChannel', {url: location.href })
  736. }
  737. else if (event.name === 'siteinfoChannel') {
  738. if (!settings['exclude_patterns'] || !isExclude(settings['exclude_patterns'])) {
  739. launchAutoPager(SITEINFO)
  740. launchAutoPager([MICROFORMAT])
  741. launchAutoPager(event.message)
  742. }
  743. }
  744. else if (event.name === 'toggleRequestChannel') {
  745. if (ap) {
  746. ap.toggle()
  747. }
  748. }
  749. else if (event.name === 'updateSettings') {
  750. settings = event.message
  751. }
  752. }, false)
  753. safari.self.tab.dispatchMessage('settings')
  754. }
  755. }
  756. else if (isJetpack()) {
  757. postMessage({ name: 'settings' })
  758. onMessage = function(message) {
  759. if (message.name == 'siteinfo') {
  760. // launchAutoPager(SITEINFO)
  761. launchAutoPager(message.data)
  762. }
  763. else if (message.name == 'settings') {
  764. settings = message.data
  765. if (settings['exclude_patterns'] && isExclude(settings['exclude_patterns'])) {
  766. // return
  767. }
  768. else {
  769. postMessage({ name: 'siteinfo', url: location.href })
  770. launchAutoPager([MICROFORMAT])
  771. }
  772. }
  773. }
  774. }
  775. else {
  776. launchAutoPager(SITEINFO)
  777. GM_registerMenuCommand('AutoPagerize - clear cache', clearCache)
  778. var cacheInfo = getCache()
  779. var xhrStates = {}
  780. SITEINFO_IMPORT_URLS.forEach(function(i) {
  781. if (!cacheInfo[i] || new Date(cacheInfo[i].expire) < new Date()) {
  782. var opt = {
  783. method: 'get',
  784. url: i,
  785. onload: function(res) {
  786. xhrStates[i] = 'loaded'
  787. getCacheCallback(res, i)
  788. },
  789. onerror: function(res){
  790. xhrStates[i] = 'error'
  791. getCacheErrorCallback(i)
  792. },
  793. }
  794. xhrStates[i] = 'start'
  795. GM_xmlhttpRequest(opt)
  796. setTimeout(function() {
  797. if (xhrStates[i] == 'start') {
  798. getCacheErrorCallback(i)
  799. }
  800. }, XHR_TIMEOUT)
  801. }
  802. else {
  803. launchAutoPager(cacheInfo[i].info)
  804. }
  805. })
  806. launchAutoPager([MICROFORMAT])
  807. }
  808.  
  809.  
  810. // new google search sucks!
  811. if (location.href.match('^http://[^.]+\.google\.(?:[^.]{2,3}\.)?[^./]{2,3}/.*(&fp=)')) {
  812. var to = location.href.replace(/&fp=.*/, '')
  813. // console.log([location.href, to])
  814. location.href = to
  815. }
  816.  
  817.  
  818.  
  819.  
  820. // utility functions.
  821. function createHTMLDocumentByString(str) {
  822. if (document.documentElement.nodeName != 'HTML') {
  823. return new DOMParser().parseFromString(str, 'application/xhtml+xml')
  824. }
  825. var html = strip_html_tag(str)
  826. var htmlDoc
  827. try {
  828. // We have to handle exceptions since Opera 9.6 throws
  829. // a NOT_SUPPORTED_ERR exception for |document.cloneNode(false)|
  830. // against the DOM 3 Core spec.
  831. htmlDoc = document.cloneNode(false)
  832. htmlDoc.appendChild(htmlDoc.importNode(document.documentElement, false))
  833. }
  834. catch(e) {
  835. htmlDoc = document.implementation.createDocument(null, 'html', null)
  836. }
  837. var fragment = createDocumentFragmentByString(html)
  838. try {
  839. fragment = htmlDoc.adoptNode(fragment)
  840. }
  841. catch(e) {
  842. fragment = htmlDoc.importNode(fragment, true)
  843. }
  844. htmlDoc.documentElement.appendChild(fragment)
  845. return htmlDoc
  846. }
  847.  
  848. function getElementsByXPath(xpath, node) {
  849. var nodesSnapshot = getXPathResult(xpath, node,
  850. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE)
  851. var data = []
  852. for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
  853. data.push(nodesSnapshot.snapshotItem(i))
  854. }
  855. return data
  856. }
  857.  
  858. function getFirstElementByXPath(xpath, node) {
  859. var result = getXPathResult(xpath, node,
  860. XPathResult.FIRST_ORDERED_NODE_TYPE)
  861. return result.singleNodeValue
  862. }
  863.  
  864. function getXPathResult(xpath, node, resultType) {
  865. var node = node || document
  866. var doc = node.ownerDocument || node
  867. var resolver = doc.createNSResolver(node.documentElement || node)
  868. // Use |node.lookupNamespaceURI('')| for Opera 9.5
  869. // A workaround for bugs of Node.lookupNamespaceURI(null)
  870. // https://bugzilla.mozilla.org/show_bug.cgi?id=693615
  871. // https://bugzilla.mozilla.org/show_bug.cgi?id=694754
  872. var defaultNS = null
  873. try {
  874. // This follows the spec: http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespaceURIAlgo
  875. if (node.nodeType == node.DOCUMENT_NODE) {
  876. defaultNS = node.documentElement.lookupNamespaceURI(null)
  877. }
  878. else {
  879. defaultNS = node.lookupNamespaceURI(null)
  880. }
  881. }
  882. catch(e) {}
  883.  
  884. if (defaultNS) {
  885. const defaultPrefix = '__default__'
  886. xpath = addDefaultPrefix(xpath, defaultPrefix)
  887. var defaultResolver = resolver
  888. resolver = function (prefix) {
  889. return (prefix == defaultPrefix)
  890. ? defaultNS : defaultResolver.lookupNamespaceURI(prefix)
  891. }
  892. }
  893. return doc.evaluate(xpath, node, resolver, resultType, null)
  894. }
  895.  
  896. function addDefaultPrefix(xpath, prefix) {
  897. const tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[\)\]])|(\/\/?|!=|[<>]=?|[\(\[|,=+-])|([@$])/g
  898. const TERM = 1, OPERATOR = 2, MODIFIER = 3
  899. var tokenType = OPERATOR
  900. prefix += ':'
  901. function replacer(token, identifier, suffix, term, operator, modifier) {
  902. if (suffix) {
  903. tokenType =
  904. (suffix == ':' || (suffix == '::' &&
  905. (identifier == 'attribute' || identifier == 'namespace')))
  906. ? MODIFIER : OPERATOR
  907. }
  908. else if (identifier) {
  909. if (tokenType == OPERATOR && identifier != '*') {
  910. token = prefix + token
  911. }
  912. tokenType = (tokenType == TERM) ? OPERATOR : TERM
  913. }
  914. else {
  915. tokenType = term ? TERM : operator ? OPERATOR : MODIFIER
  916. }
  917. return token
  918. }
  919. return xpath.replace(tokenPattern, replacer)
  920. }
  921.  
  922. function createDocumentFragmentByString(str) {
  923. var range = document.createRange()
  924. range.setStartAfter(document.body)
  925. return range.createContextualFragment(str)
  926. }
  927.  
  928. function log(message) {
  929. if (typeof console == 'object') {
  930. console.log(message)
  931. }
  932. else {
  933. GM_log(message)
  934. }
  935. }
  936.  
  937. function debug() {
  938. if ( typeof DEBUG != 'undefined' && DEBUG ) {
  939. if (console.log.apply) {
  940. console.log.apply(console, arguments)
  941. }
  942. else {
  943. Function.prototype.apply.apply(console.log, [console, arguments])
  944. }
  945. }
  946. }
  947.  
  948. function getElementPosition(elem) {
  949. var offsetTrail = elem
  950. var offsetLeft = 0
  951. var offsetTop = 0
  952. while (offsetTrail) {
  953. offsetLeft += offsetTrail.offsetLeft
  954. offsetTop += offsetTrail.offsetTop
  955. offsetTrail = offsetTrail.offsetParent
  956. }
  957. offsetTop = offsetTop || null
  958. offsetLeft = offsetLeft || null
  959. return {left: offsetLeft, top: offsetTop}
  960. }
  961.  
  962. function getElementBottom(elem) {
  963. var c_style = document.defaultView.getComputedStyle(elem, '')
  964. var height = 0
  965. var prop = ['height', 'borderTopWidth', 'borderBottomWidth',
  966. 'paddingTop', 'paddingBottom',
  967. 'marginTop', 'marginBottom']
  968. prop.forEach(function(i) {
  969. var h = parseInt(c_style[i])
  970. if (typeof h == 'number') {
  971. height += h
  972. }
  973. })
  974. var top = getElementPosition(elem).top
  975. return top ? (top + height) : null
  976. }
  977.  
  978. function getScrollHeight() {
  979. return Math.max(document.documentElement.scrollHeight,
  980. document.body.scrollHeight)
  981. }
  982.  
  983. function isSameDomain(url) {
  984. if (url.match(/^\w+:/)) {
  985. var url_s = url.split(/[\/\?]/)
  986. return url_s[0] == location.protocol && location.host == url_s[2]
  987. }
  988. else {
  989. return true
  990. }
  991. }
  992.  
  993. function isSameBaseUrl(urlA, urlB) {
  994. return (urlA.replace(/[^/]+$/, '') == urlB.replace(/[^/]+$/, ''))
  995. }
  996.  
  997. function resolvePath(path, base) {
  998. if (path.match(/^https?:\/\//)) {
  999. return path
  1000. }
  1001. if (path.match(/^\?/)) {
  1002. return base.replace(/\?.+$/, '') + path;
  1003. }
  1004. if (path.match(/^[^\/]/)) {
  1005. return base.replace(/[^/]+$/, '') + path
  1006. }
  1007. else {
  1008. return base.replace(/([^/]+:\/\/[^/]+)\/.*/, '\$1') + path
  1009. }
  1010. }
  1011.  
  1012. function fixResolvePath() {
  1013. if (resolvePath('', 'http://resolve.test/') == 'http://resolve.test/') {
  1014. return
  1015. }
  1016. // A workaround for WebKit and Mozilla 1.9.2a1pre,
  1017. // which don't support XML Base in HTML.
  1018. // https://bugs.webkit.org/show_bug.cgi?id=17423
  1019. // https://bugzilla.mozilla.org/show_bug.cgi?id=505783
  1020. var XML_NS = 'http://www.w3.org/XML/1998/namespace'
  1021. var baseElement = document.createElementNS(null, 'base')
  1022. var pathElement = document.createElementNS(null, 'path')
  1023. baseElement.appendChild(pathElement)
  1024. resolvePath = function resolvePath_workaround(path, base) {
  1025. baseElement.setAttributeNS(XML_NS, 'xml:base', base)
  1026. pathElement.setAttributeNS(XML_NS, 'xml:base', path)
  1027. return pathElement.baseURI
  1028. }
  1029. }
  1030.  
  1031. function strip_html_tag(str) {
  1032. var chunks = str.split(/(<html(?:[ \t\r\n][^>]*)?>)/)
  1033. if (chunks.length >= 3) {
  1034. chunks.splice(0, 2)
  1035. }
  1036. str = chunks.join('')
  1037. chunks = str.split(/(<\/html[ \t\r\n]*>)/)
  1038. if (chunks.length >= 3) {
  1039. chunks.splice(chunks.length - 2)
  1040. }
  1041. return chunks.join('')
  1042. }
  1043.  
  1044. function getPref(key, defaultValue) {
  1045. var value = GM_getValue(key)
  1046. return (typeof value == 'undefined') ? defaultValue : value
  1047. }
  1048.  
  1049. function wildcard2regep(str) {
  1050. return '^' + str.replace(/([-()\[\]{}+?.$\^|,:#<!\\])/g, '\\$1').replace(/\x08/g, '\\x08').replace(/\*/g, '.*')
  1051. }
  1052.  
  1053. function isExclude(patterns) {
  1054. var rr = /^\/(.+)\/$/
  1055. var eps = (patterns || '').split(/[\r\n ]+/)
  1056. for (var i = 0; i < eps.length; i++) {
  1057. var reg = null
  1058. if (rr.test(eps[i])) {
  1059. reg = eps[i].match(rr)[1]
  1060. }
  1061. else {
  1062. reg = wildcard2regep(eps[i])
  1063. }
  1064. if (location.href.match(reg)) {
  1065. return true
  1066. }
  1067. }
  1068. return false
  1069. }
  1070. // obsolete
  1071. function isFirefoxExtension() {
  1072. return (typeof chlorine == 'object')
  1073. }
  1074.  
  1075. function isChromeExtension() {
  1076. return (typeof chrome == 'object') &&
  1077. (typeof chrome.extension == 'object')
  1078. }
  1079.  
  1080. function isSafariExtension() {
  1081. return (typeof safari == 'object') &&
  1082. (typeof safari.extension == 'object')
  1083. }
  1084.  
  1085. function isGreasemonkey() {
  1086. return (typeof GM_log == 'function')
  1087. }
  1088.  
  1089. function isJetpack() {
  1090. // isFirefoxExtension is obsolete
  1091. return (!isGreasemonkey() && !isSafariExtension() &&
  1092. !isChromeExtension() && !isFirefoxExtension())
  1093. }
  1094.  
  1095. function gmCompatible() {
  1096. GM_registerMenuCommand = function() {}
  1097. GM_setValue = function() {}
  1098. GM_getValue = function() {}
  1099. GM_addStyle = function() {}
  1100. uneval = function() {}
  1101. fixResolvePath = function() {}
  1102. resolvePath = function (path, base) { return path }
  1103.  
  1104. if (isChromeExtension() || isSafariExtension()) {
  1105. createHTMLDocumentByString = function(str) {
  1106. if (document.documentElement.nodeName != 'HTML') {
  1107. return new DOMParser().parseFromString(str, 'application/xhtml+xml')
  1108. }
  1109. // FIXME
  1110. var html = str.replace(/<script(?:[ \t\r\n][^>]*)?>[\S\s]*?<\/script[ \t\r\n]*>|<\/?(?:i?frame|html|script|object)(?:[ \t\r\n][^<>]*)?>/gi, ' ')
  1111. var htmlDoc = document.implementation.createHTMLDocument ?
  1112. document.implementation.createHTMLDocument('apfc') :
  1113. document.implementation.createDocument(null, 'html', null)
  1114. var range = document.createRange()
  1115. range.selectNodeContents(document.documentElement)
  1116. htmlDoc.documentElement.appendChild(range.createContextualFragment(html))
  1117. return htmlDoc
  1118. }
  1119. }
  1120. return true
  1121. }