症状: 相対リンクを使用しているページでnextLinkのXPathが正確であるにも関わらず、 「page is already loaded. 」とコンソールに表示され、ページがつぎ足されない。
原因: 昔のバグfixがバグになる。
対策: fixResolvePath関数を削除する。
ああ、パソコンネタばっか。
ある日突然使えなくなった
実はAutopagerized0.66をTampermonkey上で使っている。
理由はSITEINFOを手書きしているから。これで決まりだ!!と思えるものはWeDataにも追加しているけど、結構決まらないまま悩んでいるうちにそのうちフォーマットが変わってしまう。
最近はlazyload大流行で相性も抜群に悪い。
そんな中先日のFirefox66に更新したとたんAutoPagerizedが効かなくなった。
急いでいるときにトラブルは起こるもの
ちょうどヤフオクのcssが2種類混在していた時で原因が何が何だか分からないまま、とりあえずcssで「次のページ」のボタンを固定化し様子を見ることに。するとやっぱりAutopagerized内でトラブルが。
page is already loaded. http://domain.org/nowViewingPage.html XPath
ちょっとConsole何言ってるのか分からない
示されているアドレスは正に今見ているページ。しかし続く表示されたXPathを$xで関数化してみても、ターゲットのアンカーを正確に示している。
$x('/html/body/div[1]/////a');
Array [ a ]
しばらくたったある日の子の下刻頃
AutoPagerizedハッキング決定。何故nextLinkが取得できないのか、console.logで関数の結果を出して調べまくる。(本業はCADなので原因究明から対策するのに一晩も掛かってしまった。)
if (this.loadedURLs[this.requestURL]) {
debug('page is already loaded.', this.requestURL, this.info.nextLink)
this.terminate()
return
}
このメッセージを直接出してくるのはこのブロック。読まれたURLと要求されたURLを比較し一致していれば、メッセージを出して終了する。(さらにretrun するとある。)
Level 2
requestURL はどこから来るかと言えば、
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) // <----HERE
// console.log("url",url);
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 // <----HERE
this.loadedURLs = {}
this.requestURL = urlであり、var url = this.getNextURLである。
次はgetNextURL関数を調べる。
Level 3
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))
}
}
}
getNextURL関数があるが、この関数の動作は新旧変わらなかった。また、nextValueはXPathを正確に解釈し返りの値は /nextpage/next.html の文字列であった。
この関数はnextValueがhttp:から始まる絶対パスの場合そのまま返す。その時は問題が起こらない。だから時々うまく動作した。やっほーとかね。
しかし、nextValeにて相対パスが返された場合絶対パスに変換するのだが、resolvePathにかかったとたん今見ているアドレスに変換されていた。
気になるが貴方は推定無罪
var base = getFirstElementByXPath('//base[@href]', doc)
というフレーズも気になり、テストしたところ悉くnullを返してくるのも問題だが、新旧共に同じ動作であったので今回は無視。プログラミングが本業でないから一晩で完璧なシステムなんぞ無理だ。
Level 4 更に疑惑は深まった
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 }
}
次はresolvePathだが、このシンプルな関数に問題があるようには見えなかった。
ところがここにconsole.logを仕掛けてみると、
resolvePath test fixResolvePath
path :
base : http://resolve.test/
何だこれは。resolve testなんてどこにもなかったぞ。新旧共にこの結果が1回だけ表示され、その後全く実行されていない。疑うのはこの関数をオーバーライドしている関数だ。
Levle 5 犯人(法を犯したコードは)貴方だ
更に検索するとすぐ下に
function fixResolvePath() {
console.log("resolvePath test 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
}
}
という関数があった。どうやらFirefox3.5時代にバグあり、その回避のためxml:baseにより新旧リンクを合成していたのだが、当然今は問題が無くなっているので用は無い。
但しその判定に(resolvePath(”, ‘http://resolve.test/’) == ‘http://resolve.test/’)となっていてその答えがhttp://resolve.testだったことからfalseとなり使わなくても良い互換のコードで今まで長々と実行されていた。
犯人の証拠集めだ
さてこのfixResolvePathをFirefox 65 でテストしてみる。
function testfixResolvePath( base, path){
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)
baseElement.setAttributeNS(XML_NS, 'xml:base', base)
pathElement.setAttributeNS(XML_NS, 'xml:base', path)
console.log( pathElement.baseURI)
}
var base = "http://resolve.test/nowpage/now.html"
testfixResolvePath( base, "https://new.com/nextpage/next.html")
testfixResolvePath( base, "nextpage/next.html")
testfixResolvePath( base, "/nextpage/next.html")
https://new.com/nextpage/next.html
http://resolve.test/nowpage/nextpage/next.html
http://resolve.test/nextpage/next.html
undefined
Firefox65時代は確かに上手くいっている。随分ややこしい制御だが。
ここでFirefox66にアップデートしてDebug consoleで試してみる。
今回の更新Firefox 66にて xml:baseが廃止され、 about:black 見事なまでの無回答。
判決 fixResolvePath が今回の事件の犯人であることを決定する。
原因はココにあった。6年前のバグの修正らしいが、何でもメンテナンスは大切。
対策はこのfixResolvePath関数を削除してしまう事。但し最初は心配でreturn;で返してしばらく運用してみることにする。
function fixResolvePath() {
return;
/** ........................ */
}