// ==UserScript== // @name WordHighlightToolbar.uc.js // @description word highlight toolbar. // @namespace http://d.hatena.ne.jp/Griever/ // @author Griever // @license MIT License // @compatibility Firefox 40 // @charset UTF-8 // @include main // @version 0.0.10 // @note 0.0.10 Firefox 40 で動かなくなった部分を修正 // @note 0.0.9 細部を修正 // @note 0.0.8 Firefox 25 でエラーが出ていたのを修正 // @note 0.0.7 ツールバーが自動で消えないことがあったのを修正 // @note 0.0.6 アイコンを作って検索時の強調を ON/OFF できるようにした // @note 0.0.6 背面のタブを複数開いた際の引き継ぎを修正 // @note 0.0.5 大幅に変更(変更し過ぎてどこを変更したのかすら忘れた) // @note 0.0.5 外部からイベントでハイライトできるようにした // @note 0.0.5 "戻る"動作にツールバーが連動するようにした // @note 0.0.5 色を選んで強調できるようにしてみた // @note 0.0.5 ツールバーが無駄にスペースをとる場合があるのを修正 // @note 0.0.5 // ==/UserScript== (function(CSS){ "use strict"; if (window.gWHT) { window.gWHT.destroy(); delete window.gWHT; } const UID = Math.random().toString(36).slice(-8); const PREFIX = 'wordhighlight-toolbar-'; const CLASS_ICON = PREFIX + 'icon'; const CLASS_ITEM = PREFIX + 'item'; const CLASS_SPAN = PREFIX + 'span'; const CLASS_INDEX = PREFIX + 'index'; const EVENT_RESPONSE = 'RESPONSE_' + UID; var GET_KEYWORD = true; var wmap = new WeakMap(); window.gWHT = { DEBUG: false, SITEINFO: [ /** url URL。正規表現。keyword, input が無い場合は $1 がキーワードになる。 keyword キーワード。スペース区切り。省略可。 input 検索ボックスの CSS Selector。 **/ { url: '.*\\btbm=isch\\b.*', keyword: ' ' }, { url: '^https?://\\w+\\.google\\.[a-z.]+/search', input: 'input[name="q"]' }, { url: '^http?://[\\w.]+\\.yahoo\\.co\\.jp/search', input: 'input[name="p"]' }, { url: '^https?://\\w+\\.bing\\.com/search', input: 'input[name="q"]' }, { url: '^http://[\\w.]+\\.nicovideo\\.jp/(?:search|tag)/.*', input: '#search_united, #bar_search' }, // {// MICROFORMAT // url: '^https?://.*[?&](?:q|word|keyword|search|query|search_query)=([^&]+)', // input: 'input[type="text"]:-moz-any([name="q"],[name="word"],[name="keyword"],[name="search"],[name="query"],[name="search_query"]), input[type="search"]' // }, ], FIND_FOUND : 0, FIND_NOTFOUND: 1, FIND_WRAPPED : 2, sound: Cc["@mozilla.org/sound;1"].createInstance(Ci.nsISound), getWins: getWins, checkDoc: checkDoc, getFocusedWindow: getFocusedWindow, getRangeAll: getRangeAll, wmap:wmap, tabhistory: {}, get prefs() { delete this.prefs; return this.prefs = Services.prefs.getBranch("WordHighlightToolbar."); }, get GET_KEYWORD() GET_KEYWORD, set GET_KEYWORD(bool) { bool = !!bool; var icon = $(PREFIX + "icon"); if (icon) { icon.setAttribute("state", bool ? "enable" : "disable"); icon.setAttribute("tooltiptext", bool ? "enable" : "disable"); } return GET_KEYWORD = bool; }, init: function() { this.xulstyle = addStyle(CSS); var icon = $("urlbar-icons").appendChild(document.createElement("image")); icon.setAttribute("id", PREFIX + "icon"); icon.setAttribute("class", PREFIX + "icon"); icon.setAttribute("onclick", "gWHT.GET_KEYWORD = !gWHT.GET_KEYWORD"); icon.setAttribute("context", ""); icon.setAttribute("style", "padding: 0px 2px;"); var bb = document.getElementById("appcontent"); var container = bb.appendChild(document.createElement("hbox")); container.setAttribute("id", PREFIX + "box"); container.setAttribute("style", "max-height: 23px;"); //container.setAttribute("ordinal", "0"); this.container = container; var sep = document.getElementById("context-viewpartialsource-selection"); var menu = sep.parentNode.insertBefore(document.createElement("menu"), sep); menu.setAttribute("label", "ハイライト"); menu.setAttribute("id", PREFIX + "highlight"); menu.setAttribute("class", CLASS_ICON + " menu-iconic"); menu.setAttribute("accesskey", "H"); menu.setAttribute("onclick", "\ if (event.target != this) return;\ closeMenus(this);\ if (event.button === 0) gWHT.highlightWord();\ else if (event.button === 1) gWHT.highlightWordAuto();\ "); var menupopup = menu.appendChild(document.createElement("menupopup")); var menuitem = menupopup.appendChild(document.createElement("menuitem")); menuitem.setAttribute("class", CLASS_ICON + " menuitem-iconic"); menuitem.setAttribute("label", "ハイライト"); menuitem.setAttribute("accesskey", "H"); menuitem.setAttribute("oncommand", "gWHT.highlightWord();"); var menuitem = menupopup.appendChild(document.createElement("menuitem")); menuitem.setAttribute("class", CLASS_ICON + " menuitem-iconic"); menuitem.setAttribute("label", "単語を探してハイライト"); menuitem.setAttribute("oncommand", "gWHT.highlightWordAuto();"); var cp = menupopup.appendChild(document.createElement("colorpicker")); cp.setAttribute("onclick", "\ closeMenus(this);\ var word = getBrowserSelection();\ setTimeout(function(){\ gWHT.addWord({ word: word, bgcolor: this.color, bold: true });\ }.bind(this), 10);\ "); try { this.GET_KEYWORD = this.prefs.getBoolPref("GET_KEYWORD"); } catch (e) { this.GET_KEYWORD = GET_KEYWORD; } gBrowser.mPanelContainer.addEventListener("DOMContentLoaded", this, false); // gBrowser.mPanelContainer.addEventListener("click", this, false); // gBrowser.mPanelContainer.addEventListener("dragend", this, false); gBrowser.mPanelContainer.addEventListener("pageshow", this, false); gBrowser.mPanelContainer.addEventListener(EVENT_RESPONSE, this, true); gBrowser.mPanelContainer.addEventListener("WordHighlightToolbarAddWord", this, false, true); gBrowser.mPanelContainer.addEventListener("WordHighlightToolbarRemoveWord", this, false, true); gBrowser.mTabContainer.addEventListener("TabOpen", this, false); gBrowser.mTabContainer.addEventListener("TabSelect", this, false); gBrowser.mTabContainer.addEventListener("TabClose", this, false); document.getElementById("contentAreaContextMenu").addEventListener("popupshowing", this, false); window.addEventListener("unload", this, false); }, uninit: function() { gBrowser.mPanelContainer.removeEventListener("DOMContentLoaded", this, false); // gBrowser.mPanelContainer.removeEventListener("click", this, false); // gBrowser.mPanelContainer.removeEventListener("dragend", this, false); gBrowser.mPanelContainer.removeEventListener("pageshow", this, false); gBrowser.mPanelContainer.removeEventListener(EVENT_RESPONSE, this, true); gBrowser.mPanelContainer.removeEventListener("WordHighlightToolbarAddWord", this, false); gBrowser.mPanelContainer.removeEventListener("WordHighlightToolbarRemoveWord", this, false); gBrowser.mTabContainer.removeEventListener("TabOpen", this, false); gBrowser.mTabContainer.removeEventListener("TabSelect", this, false); gBrowser.mTabContainer.removeEventListener("TabClose", this, false); document.getElementById("contentAreaContextMenu").removeEventListener("popupshowing", this, false); window.removeEventListener("unload", this, false); this.prefs.setBoolPref("GET_KEYWORD", this.GET_KEYWORD); }, destroy: function() { [PREFIX + "icon", PREFIX + "box", PREFIX + "highlight"].forEach(function(id){ var elem = $(id); if (elem) elem.parentNode.removeChild(elem); }, this); this.uninit(); if (this.xulstyle) this.xulstyle.parentNode.removeChild(this.xulstyle); }, handleEvent: function(event) { switch(event.type) { case "click": this.lastClickedTime = new Date().getTime(); break; case "dragend": var dt = event.dataTransfer; if (dt) { if (dt.types.contains("text/x-moz-place")) return; if (!dt.types.contains("text/x-moz-url")) return; } this.lastClickedTime = new Date().getTime(); break; case "DOMContentLoaded": if (!this.GET_KEYWORD) return; var doc = event.target; var win = doc.defaultView; // frame 内では動作しない if (win != win.parent) return; // HTMLDocument じゃない場合 if (!checkDoc(doc)) return; var keywords = this.GET_KEYWORD ? this.getKeyword(this.SITEINFO, doc) : []; setTimeout(function() { this.launch(doc, keywords); }.bind(this), 500); break; case "pageshow": var doc = event.target; var win = doc.defaultView; if (win != win.parent) return; this.updateToolbar( wmap.get(doc) ); break; case EVENT_RESPONSE: event.stopPropagation(); this.onResponse(event); break; case "WordHighlightToolbarAddWord": var { target, type, detail } = event; debug(type, detail); if (!detail) return; var doc = target.ownerDocument || target; var range; if (doc != target) { range = doc.createRange(); range.selectNode(target); } this.addWord(detail, false, range); break; case "WordHighlightToolbarRemoveWord": var { target, type, detail } = event; debug(type, detail); var doc = target.ownerDocument || target; if (!doc.wht) return; var words = Array.isArray(detail) ? detail : [detail]; words.forEach(function(word) doc.wht.removeWord(word)); break; case "TabSelect": var doc = event.target.linkedBrowser.contentDocument; var toolbar = wmap.get(doc); this.updateToolbar(toolbar); break; case "TabOpen": var tab = event.target; tab.whtOwner = gBrowser.mCurrentTab; tab.whtTime = new Date().getTime(); break; case "TabClose": delete this.tabhistory[event.target.linkedPanel]; break; case "popupshowing": if (event.target != event.currentTarget) return; var {isTextSelected, onTextInput, target} = gContextMenu; gContextMenu.showItem(PREFIX + "highlight", isTextSelected && !onTextInput); break; case "unload": this.uninit(); break; } }, onResponse: function(event) { var { target, detail: { name, args } } = event; debug(name, args, target); var doc = target.ownerDocument || target; var win = doc.defaultView; var topWin = win.top; if ('initialized' === name) { doc.addEventListener("dragend", this, false); doc.addEventListener("click", this, false); var tab = gBrowser._getTabForContentWindow(topWin); var linkedPanel = tab.linkedPanel; var { index, count } = tab.linkedBrowser.docShell.sessionHistory; this.tabhistory[linkedPanel][index] = doc.wht.items; return; } var toolbar = wmap.get(topWin.document); if (!toolbar) { toolbar = this.addToolbar(); wmap.set(doc, toolbar); wmap.set(topWin.document, toolbar); } if ('highlight' === name || 'highlightAll' === name) { var itemArr = args.length > 0 ? args : Object.keys(doc.wht.items).map(function(key) doc.wht.items[key]); itemArr.forEach(function(item) { var button = toolbar.querySelector('.' + CLASS_ITEM + '[index="'+ item.index +'"]'); button = this.addButton(toolbar, item, button); }, this); this.updateToolbar(toolbar); return; } if ('lowlight' === name) { var item = args[0]; var button = toolbar.querySelector('.' + CLASS_ITEM + '[index="'+ item.index +'"]'); if (button) { button.parentNode.removeChild(button); } return; } if ('lowlightAll' === name) { var range = document.createRange(); range.selectNodeContents(toolbar.getElementsByTagName('arrowscrollbox')[0]); range.deleteContents(); //delete this.tabhistory[linkedPanel][index]; return; } }, _launch: function(doc, tab) { if (!tab) { tab = gBrowser._getTabForContentWindow(doc.defaultView.top); } var linkedPanel = tab.linkedPanel; var { count, index } = tab.linkedBrowser.docShell.sessionHistory; var tabhis = this.tabhistory[linkedPanel] || (this.tabhistory[linkedPanel] = []); if (!doc.wht) { doc.wht = new this.ContentClass(doc); tabhis[index] = doc.wht.items; if (tabhis.length > count) { tabhis.splice(count); } } }, launch: function(doc, keywords) { var win = doc.defaultView; var tab = gBrowser._getTabForContentWindow(win.top); var linkedPanel = tab.linkedPanel; // loadType 1=Bookmark, 2=Reload, 4=History, 4>other var { loadType, sessionHistory: { count, index } } = tab.linkedBrowser.docShell; var tabhis = this.tabhistory[linkedPanel] || (this.tabhistory[linkedPanel] = []); var newtabflag = (tab.whtTime || Infinity) - this.lastClickedTime < 250; // newtab from user. tab.whtTime = Infinity; keywords || (keywords = []); let hikitugi = []; let hiki_for_items = function(items, boldOnly) { Object.keys(items).map(function(key) { var item = items[key]; if (boldOnly && !item.bold) return; hikitugi.push({ word: item.word, //index: item.index, bgcolor: item.bgcolor, fgcolor: item.fgcolor, bold: item.bold, }); }) } if (newtabflag) { // newtab from user. if (tab.whtOwner) { let ownhis = this.tabhistory[tab.whtOwner.linkedPanel]; if (ownhis) { let items = ownhis[tab.whtOwner.linkedBrowser.docShell.sessionHistory.index]; if (items) { hiki_for_items(items, keywords.length > 0); } } } tab.whtOwner = null; } else if (loadType === 2) { // reload var items = tabhis[index]; if (items) { hiki_for_items(items, keywords.length > 0); } } else if (loadType > 1) { // currenttab for user var items = tabhis[index-1] || tabhis[index]; if (items) { hiki_for_items(items, keywords.length > 0); } } // debug([doc.URL + '\n' // ,'loadType:' + loadType,'newtabflag:' + newtabflag // ,'keywords:[' + keywords + ']' // ,'hikitugi:[' + hikitugi.map(function(o) o.word) + ']' // ].join(', ')); if (keywords.length || hikitugi.length) { this._launch(doc, tab); doc.wht.addWord(keywords.concat(hikitugi)); } }, launchFrame: function(doc) { }, updateToolbar: function(toolbar) { if (this.updateTimer) clearTimeout(this.updateTimer); this.updateTimer = setTimeout(function() { var toolbar = toolbar || wmap.get(content.document); if (toolbar && toolbar.parentNode) { return; } var range = document.createRange(); range.selectNodeContents(this.container); if (toolbar) { range.collapse(true); range.insertNode(toolbar); range.selectNodeContents(this.container); range.setStartAfter(toolbar); } range.deleteContents(); }.bind(this), 150); }, updateToolbar_: function(toolbar) { if (toolbar && toolbar.parentNode) { return; } var range = document.createRange(); range.selectNodeContents(this.container); range.deleteContents(); if (toolbar) range.insertNode(toolbar); }, addToolbar: function() { var toolbar = document.createElement("hbox"); toolbar.setAttribute("class", PREFIX + "toolbar"); toolbar.setAttribute("flex", "1"); var box = toolbar.appendChild(document.createElement("arrowscrollbox")); box.setAttribute("class", PREFIX + "arrowscrollbox"); box.setAttribute("flex", "1"); box.setAttribute("orient", "horizontal"); box.setAttribute("ordinal", "5"); var closebutton = toolbar.appendChild(document.createElement("toolbarbutton")); closebutton.setAttribute("class", PREFIX + "closebutton tabs-closebutton close-icon"); closebutton.setAttribute("oncommand", "gWHT.destroyToolbar();"); closebutton.setAttribute("ordinal", "1"); var reloadbutton = toolbar.appendChild(document.createElement("toolbarbutton")); reloadbutton.setAttribute("class", PREFIX + "reloadbutton"); reloadbutton.setAttribute("tooltiptext", "ワードをハイライトし直す"); reloadbutton.setAttribute("ordinal", "10"); reloadbutton.setAttribute("oncommand", "gWHT.recoveryToolbar();"); var addbutton = toolbar.appendChild(document.createElement("toolbarbutton")); addbutton.setAttribute("class", PREFIX + "addbutton"); addbutton.setAttribute("tooltiptext", "ワードを追加"); addbutton.setAttribute("ordinal", "10"); addbutton.setAttribute("oncommand", "gWHT.addWord();"); return toolbar; }, destroyToolbar: function() { var win = getFocusedWindow(); var doc = win.document; if (doc.wht) { doc.wht.lowlightAll(); } this.updateToolbar(); }, recoveryToolbar: function() { var win = getFocusedWindow(); var doc = win.document; if (doc.wht) { doc.wht.highlightAll(); // マッチしなかったワードを削除 Object.keys(doc.wht.items).forEach(function(key){ var item = doc.wht.items[key]; if (item.length === 0) doc.wht.removeWord(item.word); }, this); } }, addButton: function(toolbar, aItem, aButton) { var button = aButton; if (!button) { button = document.createElement('toolbarbutton'); button.style.setProperty('-moz-appearance', 'none', 'important'); button.setAttribute('oncommand', 'gWHT.find(this.getAttribute("word"), event.shiftKey);'); button.setAttribute('onDOMMouseScroll', 'event.stopPropagation(); gWHT.find(this.getAttribute("word"), event.detail < 0);'); button.setAttribute('onclick', 'if (event.button != 1) return; this.hidden = true; gWHT.removeWord(this.getAttribute("word"));'); button.setAttribute('class', CLASS_ITEM); button.setAttribute('tooltiptext', [ 'クリック or ホイールダウンで次を検索', 'Shift+クリック or ホイールアップで前を検索', 'ホイールクリックで削除'].join('\n')); toolbar.getElementsByTagName('arrowscrollbox')[0].appendChild(button); } button.style.setProperty('color', aItem.fgcolor, 'important'); button.style.setProperty('background-color', aItem.bgcolor, 'important'); button.setAttribute('word', aItem.word); button.setAttribute('index', aItem.index); button.setAttribute('bgcolor', aItem.bgcolor); button.setAttribute('fgcolor', aItem.fgcolor); button.setAttribute('length', aItem.length); button.setAttribute('label', aItem.word + '(' + aItem.length + ')'); button.setAttribute('hidden', 'false'); if (aItem.bold) { button.setAttribute('bold', aItem.bold); button.style.setProperty('font-weight', 'bold', 'important'); } else { button.style.removeProperty('font-weight'); } return button; }, highlightWord: function() { var keywords = getRangeAll().map(function(r) r.toString()); if (keywords.length) this.addWord(keywords, true); }, highlightWordAuto: function() { var keywords = getRangeAll().join(' ').match(this.tangoReg) || []; if (keywords.length) this.addWord(keywords, true); }, addWord: function(aWord, aBold, aRange) { if (!aWord) { aWord = prompt('ワードを追加', getBrowserSelection()); aBold = true; } if (!aWord) return; var keywords = Array.isArray(aWord) ? aWord : [aWord]; var doc, win; if (aRange) { doc = aRange.startContainer.ownerDocument; win = doc.defaultView; } else { win = getFocusedWindow(); doc = win.document; } if (!doc.wht) { win.getSelection().removeAllRanges(); this._launch(doc); } keywords = keywords.map(function(str){ return typeof str === "string" ? str.trim() : str; }); doc.wht.addWord(keywords, aBold, aRange); }, removeWord: function(aWord) { if (!aWord) return; var doc = getFocusedWindow().document; if (!doc.wht) return; doc.wht.removeWord(aWord); }, getLength: function(aWord, aWin) { var w = aWord.toLowerCase(); var len = 0; getWins(aWin).forEach(function(win) { var doc = win.document; if (!doc.wht) return; var item = doc.wht.items[w]; if (item) len += item.length; }, this); return len; }, get tangoReg() { if (this._tangoReg) return this._tangoReg; var arr = [ "[\\u4E00-\\u9FA0]{2,}" // 漢字 ,"[\\u4E00-\\u9FA0][\\u3040-\\u309F]+" // 漢字1文字+ひらがな ,"[\\u30A0-\\u30FA\\u30FC]{2,}" // カタカナ ,"[\\uFF41-\\uFF5A\\uFF21-\\uFF3A\\uFF10-\\uFF19]{2,}" // 全角英数数字(小文字、大文字、数字) ,"[\\w%$\\@#+]{5,}" ,"\\d[\\d.,]+" ,"\\w[\\w.]+" ]; return this._tangoReg = new RegExp(arr.join('|'), 'g'); }, get kukuriReg() { if (this._kukuriReg) return this._kukuriReg; var obj = { '"': '"', "'": "'", '\uFF3B': '\uFF3D',//[] '\u3010': '\u3011',//【】 '\u300E': '\u300F',//『』 '\uFF08': '\uFF09',//() '\u201D': '\u201D',// ”” '\u2019': '\u2019',// ’’ }; var arr = Object.keys(obj).map(function(key) '\\' + key + '[^\\n\\'+ obj[key] +']{2,}\\' + obj[key]); return this._kukuriReg = new RegExp(arr.join('|'), 'g'); }, getKeyword: function (list, aDoc) { if (!list) list = this.SITEINFO; var locationHref = aDoc.location.href; for (let [index, info] in Iterator(list)) { try { var exp = info.url_regexp || (info.url_regexp = new RegExp(info.url)); if ( !exp.test(locationHref) ) continue; if (info.keyword) return Array.isArray(info.keyword) ? info.keyword : info.keyword.split(/\s+/); if (info.input) { var input = aDoc.querySelector(info.input); if (input && input.value && /\S/.test(input.value)) return this.clean(input.value); } else if (RegExp.$1) { try { return this.clean(decodeURIComponent(RegExp.$1)); } catch (e) {} return this.clean(RegExp.$1); } } catch(e) { log('error at ' + e); } } return []; }, clean: function clean(str) { var res = []; var kukuri = str.match(this.kukuriReg); if (kukuri) { [].push.apply(res, kukuri.map(function(w) w.slice(1,-1))); str = str.replace(this.kukuriReg, ' '); } str = str.replace(/\b(?:(?:all)?(?:inurl|inanchor)|link|cache|related|info|site|filetype|daterange|movie|weather|blogurl):\S*/g, ""); str = str.replace(/\b(?:AND|OR)\b|\s\-\S+/g, " ") str = str.replace(/(?:all)?(?:intitle|intext):/g, " "); //str = (' ' + str + ' ').replace(/\s\-\S+|(?:(?:all)?(?:inurl|intitle|intext|inanchor)|link|cache|related|info|site|filetype|daterange|movie|weather|blogurl)\:\S*|\s(?:AND|OR)\s/g, ' '); // \x20-\x29 !"#$%&'()*+,-./ \x3A-\x40 :;<=>?@ \x5B-\x60 [\]^_` x7B-\x7E {|}~ var tango = str.match(/[^\x20-\x29\x3B-\x3F\x5B-\x5E\x60\x7B-\x7E\s]{2,}/g); if (tango) { [].push.apply(res, tango/*.sort(function(a,b) b.length - a.length)*/); } return res.filter(function(e,i,a) e && a.indexOf(e) === i); }, find: function(aWord, isBack) { var res; var fastFind = gBrowser.mCurrentBrowser.fastFind; if (fastFind.searchString != aWord) { res = fastFind.find(aWord, false); if (isBack) { res = fastFind.findAgain(isBack, false); } } else { res = fastFind.findAgain(isBack, false); } if (res === this.FIND_NOTFOUND) return this.sound.beep(); if (res === this.FIND_WRAPPED) this.sound.beep(); var win = fastFind.currentWindow; if (!win) return; var sel = win.getSelection(); var node = sel.getRangeAt(0).startContainer; var span = node.parentNode; if (!span.classList.contains(CLASS_SPAN)) return; isBack ? sel.collapseToStart() : sel.collapseToEnd(); // sel.collapse(node, 1); span.style.setProperty('outline', '4px solid #36F', 'important'); win.setTimeout(function () { span.style.removeProperty('outline'); }, 400); }, }; window.gWHT.ContentClass = function(){ this.init.apply(this, arguments) }; window.gWHT.ContentClass.prototype = { finder: Cc["@mozilla.org/embedcomp/rangefind;1"].createInstance().QueryInterface(Ci.nsIFind), styles: [ ['hsl( 60, 100%, 80%)','#000'] // bgcolor, textcolor ,['hsl(120, 100%, 80%)','#000'] ,['hsl(180, 100%, 80%)','#000'] ,['hsl(240, 100%, 80%)','#000'] ,['hsl(300, 100%, 80%)','#000'] ,['hsl(360, 100%, 80%)','#000'] ,['hsl( 30, 100%, 80%)','#000'] ,['hsl( 90, 100%, 80%)','#000'] ,['hsl(150, 100%, 80%)','#000'] ,['hsl(210, 100%, 80%)','#000'] ,['hsl(270, 100%, 80%)','#000'] ,['hsl(330, 100%, 80%)','#000'] ], css: [ 'font: inherit !important;' ,'margin: 0px !important;' ,'padding: 0px !important;' ,'border: none !important;' ,'text-shadow: none !important;' ,'float: none !important;' ,'display: inline !important;' ].join(' '), throughSelector: ['textarea', 'input', '.' + CLASS_SPAN].map(function(w) w+', '+w+' *').join(','), init: function(doc, keywords) { this.doc = doc; this.win = doc.defaultView; this.body = doc.body, this.items = {}; this.finder.findBackwards = false; /* 後ろから前に向かって検索するか */ this.finder.caseSensitive = false; /* 大文字小文字を区別するか */ this.isEmpty = true; // TreeWalker で無駄に探さない為のフラグ if (keywords) { this.initItems(keywords); } this.doc.addEventListener("keypress", this, false); this.doc.addEventListener("GM_AutoPagerizeNextPageLoaded", this, false); this.win.setTimeout(function() { this.fireEvent('initialized', this.doc); }.bind(this), 100); }, handleEvent: function(event) { switch (event.type) { case "keypress": if (event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLSelectElement || event.target instanceof HTMLInputElement && (event.target.mozIsTextField(false))) return; var {charCode, ctrlKey, shiftKey, altKey} = event; if ((charCode === 78 || charCode === 110) && !ctrlKey && !altKey) { this.find(shiftKey); event.preventDefault(); event.stopPropagation(); } break; case "GM_AutoPagerizeNextPageLoaded": // AutoPagerizeの最後の区切り以降のRangeを取得 var sep = this.doc.querySelectorAll('.autopagerize_page_separator, .autopagerize_page_info'); sep = sep[sep.length-1]; if (!sep) return; var range = this.doc.createRange(); if (sep.parentNode.localName == 'td') { range.setStartAfter(sep.parentNode.parentNode); range.setEndAfter(sep.parentNode.parentNode.parentNode); } else { range.setStartAfter(sep); range.setEndAfter(sep.parentNode.lastChild); } this.highlightAll(range); break; } }, initItem: function(aWord, aBold, aBG, aFG, aIndex) { if (!aWord) return null; if (typeof aWord === 'object') { aBold = aWord.bold; aBG = aWord.bgcolor; aFG = aWord.fgcolor; aIndex = aWord.index; aWord = aWord.word; if (!aWord) return null; } var w = aWord.toLowerCase(); if (this.items[w]) return null; var index = typeof aIndex == 'number' && aIndex != NaN ? aIndex : this.newIndexOf(); var [bg, fg] = this.styles[index % this.styles.length]; if (aBG) { bg = aBG; fg = aFG || rgb2bw(aBG); } var obj = this.items[w] = { word: aWord, index: index, bgcolor: bg, fgcolor: fg, bold: !!aBold, length: 0, }; Object.defineProperty(obj, 'toString', { enumerable: false, value: function() { return '[' + this.index + ':' + this.length + ':' + this.word + ']'; } }); return obj; }, initItems: function(array, aBold) { return array.map(function(aWord, index) { return this.initItem(aWord, aBold); }, this); }, _highlight: function(aItem, aRange) { this.finder.findBackwards = false; /* 後ろから前に向かって検索するか */ var doc = this.doc; var range = aRange; if (!range) { range = doc.createRange(); range.selectNodeContents(this.body); } var sRange = range.cloneRange(); sRange.collapse(true); var eRange = range.cloneRange(); eRange.collapse(false); // タイマーを使わなくて良いおまじない // http://piro.sakura.ne.jp/latest/blosxom/mozilla/xul/2010-07-06_dynamic.htm doc.documentElement.clientHeight; var rangeArr = []; var len = 0; for (var retRange = null; retRange = this.finder.Find(aItem.word, range, sRange, eRange); sRange = retRange.cloneRange(), sRange.collapse(false)) { rangeArr[++len] = retRange; } if (len > 0) { var temp = doc.createElementNS("http://www.w3.org/1999/xhtml", "font"); temp.setAttribute("style", this.css + 'background-color: ' + aItem.bgcolor + ' !important;' + 'color: ' + aItem.fgcolor + ' !important;'); temp.setAttribute("class", CLASS_SPAN + ' ' + CLASS_INDEX + aItem.index); len = 0; rangeArr.forEach(function(range){ var node = range.startContainer; if (node.nodeType != 1) node = node.parentNode; if (node.mozMatchesSelector(this.throughSelector)) { if (node.classList.contains( CLASS_INDEX + aItem.index )) ++len; return; } node = range.endContainer; if (node.nodeType != 1) node = node.parentNode; if (node.mozMatchesSelector(this.throughSelector)) { if (node.classList.contains( CLASS_INDEX + aItem.index )) ++len; return; } var span = temp.cloneNode(false); try { range.surroundContents(span); ++len; return; } catch (e) {} try {// 範囲内の要素を細切れにしてでも強調する。行儀が悪い span.appendChild(range.extractContents()); range.insertNode(span); ++len; return; } catch (e) {} }, this); } if (aRange) aItem.length += len; else aItem.length = len; if (aItem.length) this.isEmpty = false; }, _lowlight: function(aItem) { var doc = this.doc; $A(doc.getElementsByClassName(CLASS_INDEX + aItem.index)).forEach(function(elem){ var range = doc.createRange(); range.selectNodeContents(elem); var df = range.extractContents(); range.setStartBefore(elem); range.insertNode(df); range.selectNode(elem); range.deleteContents(); }, this); aItem.length = 0; if (Object.keys(this.items).length === 0) this.isEmpty = true; }, highlightAll: function(aRange) { Object.keys(this.items).forEach(function(key){ this._highlight(this.items[key], aRange); }, this); this.fireEvent('highlightAll', this.doc); }, lowlightAll: function() { var doc = this.doc; $A(doc.getElementsByClassName(CLASS_SPAN)).forEach(function(elem){ var range = doc.createRange(); range.selectNodeContents(elem); var df = range.extractContents(); range.setStartBefore(elem); range.insertNode(df); range.selectNode(elem); range.deleteContents(); }, this); Object.keys(this.items).forEach(function(key){ delete this.items[key]; }, this); this.isEmpty = true; this.fireEvent('lowlightAll', this.doc); }, addWord: function(aWord, aBold, aRange) { var itemArr = Array.isArray(aWord) ? this.initItems(aWord, aBold) : [this.initItem(aWord, aBold)]; itemArr = itemArr.filter(function(item) { if (item) { this._highlight(item, aRange); return true; } }, this); if (itemArr.length) { var args = ['highlight', this.doc].concat(itemArr); this.fireEvent.apply(this, args); } }, removeWord: function(aWord) { var w = aWord.toLowerCase(); var obj = this.items[w]; if (obj) { this._lowlight(obj); this.fireEvent('lowlight', this.doc, obj); } delete this.items[w]; }, newIndexOf: function() { // index プロパティの欠番を探す var arr = []; Object.keys(this.items).forEach(function(key) arr[this.items[key].index] = true, this); for (var i = 0, len = arr.length; i < len; i++) { if (!arr[i]) return i; }; return arr.length; }, find: function(isPrev) { if (this.isEmpty) { debug('強調されていないようなので検索しません'); return; } var tw = this.tw; if (!tw) { let fn = function(node) { if (node.classList.contains(CLASS_SPAN)) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_SKIP; } tw = this.tw = this.doc.createTreeWalker(this.doc.body, NodeFilter.SHOW_ELEMENT, fn, false); } // ツリーの現在地を最後にクリックした位置に合わせる var sel = this.win.getSelection(); if (sel.focusNode) { var n = isPrev ? sel.anchorNode : sel.focusNode; var o = isPrev ? sel.anchorOffset : sel.focusOffset; tw.currentNode = o ? (n.childNodes[o] || n) : n; } sel.removeAllRanges(); var node; if (isPrev) { node = tw.previousNode(); if (!node) { node = Array.pop(this.doc.getElementsByClassName(CLASS_SPAN)); tw.currentNode = node || this.doc.body.lastChild; if (node) gWHT.sound.beep(); } } else { node = tw.nextNode(); if (!node) { node = this.doc.getElementsByClassName(CLASS_SPAN)[0]; tw.currentNode = node || this.doc.body; if (node) gWHT.sound.beep(); } } if (!node) { gWHT.sound.beep(); this.isEmpty = true; return; } this.isEmpty = false; sel.selectAllChildren(node); try { sel.QueryInterface(Ci.nsISelectionPrivate) .scrollIntoView(Ci.nsISelectionController.SELECTION_ANCHOR_REGION, true, 50, 50); } catch (e) {} var anchor = this.doc.evaluate('descendant-or-self::a[@href]|ancestor-or-self::a[@href]', node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (anchor && !/^mailto/.test(anchor.href)) { anchor.focus(); sel.selectAllChildren(node); } isPrev ? sel.collapseToStart() : sel.collapseToEnd(); node.style.setProperty('outline', '3px solid #36F', 'important'); this.win.setTimeout(function() { node.style.removeProperty('outline'); }, 400); }, fireEvent: function (aName, aTarget) { var evt = new CustomEvent(EVENT_RESPONSE, { bubbles: true, cancelable: true, detail: { name: aName, args: $A(arguments).slice(2), } }); aTarget.dispatchEvent(evt); }, }; function rgb2bw(code) { if (!code) return "#000"; var m = code.match(/^#?([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})$/i); if (!m || !m[1]) return "#000"; m = m.slice(1, 4).map(function(c){ return parseInt(!c[1] ? c[0] + c[0] : c, 16); }); return (m[0] + m[1] + m[2]) > 128*3 ? "#000" : "#fff"; } function getWins(win) { var wins = win.frames.length ? [win].concat(Array.slice(win.frames)) : [win]; return wins.filter(function(win) checkDoc(win.document)); } function checkDoc(doc) { if (!(doc instanceof HTMLDocument)) return false; if (!mimeTypeIsTextBased(doc.contentType)) return false; if (!doc.body || !doc.body.hasChildNodes()) return false; if (doc.body instanceof HTMLFrameSetElement) return false; return true; } function mimeTypeIsTextBased(contentType) { return /^text\/|\+xml$/.test(contentType) || contentType == "application/x-javascript" || contentType == "application/javascript" || contentType == "application/xml" || contentType == "mozilla.application/cached-xul"; } function getFocusedWindow() { var win = document.commandDispatcher.focusedWindow; return (!win || win == window) ? content : win; } function getRangeAll(win) { var sel = (win || getFocusedWindow()).getSelection(); var res = []; if (sel.isCollapsed) return res; for(var i = 0, l = sel.rangeCount; i < l; i++) { res.push(sel.getRangeAt(i)); } return res; } function $(id) { return document.getElementById(id); } function $$(exp, doc) { return Array.prototype.slice.call((doc || document).querySelectorAll(exp)); } function $A(args) { return Array.prototype.slice.call(args); } function log() { Services.console.logStringMessage($A(arguments).join(', ')); } function debug() { if (gWHT.DEBUG) log("wht debug: " + $A(arguments).join(', ')); } function addStyle(css) { var pi = document.createProcessingInstruction( 'xml-stylesheet', 'type="text/css" href="data:text/css;utf-8,' + encodeURIComponent(css) + '"' ); return document.insertBefore(pi, document.documentElement); } window.gWHT.init(); })('\ .wordhighlight-toolbar-icon {\ list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAANUlEQVQ4jWNgGBTg6dOi/6RgrAb8/19PFB7EBlAUBoMDFD0t+k8qxjCgngQ4SA2gKAwGDAAAM3SE/usVkKQAAAAASUVORK5CYII=");\ }\ .wordhighlight-toolbar-icon[state="disable"] {\ filter: grayscale(100%);\ }\ .wordhighlight-toolbar-arrowscrollbox > .autorepeatbutton-up,\ .wordhighlight-toolbar-arrowscrollbox > .autorepeatbutton-down {\ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");\ }\ .wordhighlight-toolbar-arrowscrollbox > .autorepeatbutton-down {\ transform: scaleX(-1);\ }\ .wordhighlight-toolbar-item {\ text-shadow: none;\ }\ .wordhighlight-toolbar-item > .toolbarbutton-icon { visibility: collapse; }\ \ .wordhighlight-toolbar-reloadbutton,\ .wordhighlight-toolbar-addbutton {\ list-style-image: url("chrome://browser/skin/Toolbar.png");\ }\ .wordhighlight-toolbar-reloadbutton { -moz-image-region: rect(0pt, 270px, 18px, 252px); }\ .wordhighlight-toolbar-addbutton { -moz-image-region: rect(0px, 576px, 18px, 558px); }\ \ #wordhighlight-toolbar-box:empty,\ .wordhighlight-toolbar-arrowscrollbox:empty,\ .wordhighlight-toolbar-arrowscrollbox:empty ~ * { visibility: collapse; }\ \ \ /*.wordhighlight-toolbar-arrowscrollbox > .autorepeatbutton-up:-moz-lwtheme-brighttext,\ .wordhighlight-toolbar-arrowscrollbox > .autorepeatbutton-down:-moz-lwtheme-brighttext {\ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png");\ }\ .wordhighlight-toolbar-reloadbutton:-moz-lwtheme-brighttext,\ .wordhighlight-toolbar-addbutton:-moz-lwtheme-brighttext {\ list-style-image: url("chrome://browser/skin/Toolbar-inverted.png");\ }*/\ \ ');