X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fbuffer.jsm;fp=common%2Fmodules%2Fbuffer.jsm;h=964c3c8166c4c6778202a58885c246498cb655d6;hb=10dd809e3c62dac9afcb5592982306ab22198f65;hp=357beea6556566c60eac0ce21dad1b9a82224b55;hpb=c8bcbfcd3f674388aa15b4096ce2c5ed6f91238b;p=dactyl.git diff --git a/common/modules/buffer.jsm b/common/modules/buffer.jsm index 357beea..964c3c8 100644 --- a/common/modules/buffer.jsm +++ b/common/modules/buffer.jsm @@ -1,22 +1,24 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2008-2013 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. -try {"use strict"; +"use strict"; -Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("buffer", { exports: ["Buffer", "buffer"], require: ["prefs", "services", "util"] -}, this); +}); -this.lazyRequire("finder", ["RangeFind"]); -this.lazyRequire("io", ["io"]); -this.lazyRequire("overlay", ["overlay"]); -this.lazyRequire("storage", ["File", "storage"]); -this.lazyRequire("template", ["template"]); +lazyRequire("bookmarkcache", ["bookmarkcache"]); +lazyRequire("contexts", ["Group"]); +lazyRequire("io", ["io"]); +lazyRequire("finder", ["RangeFind"]); +lazyRequire("overlay", ["overlay"]); +lazyRequire("sanitizer", ["sanitizer"]); +lazyRequire("storage", ["File", "storage"]); +lazyRequire("template", ["template"]); /** * A class to manage the primary web content buffer. The name comes @@ -66,6 +68,45 @@ var Buffer = Module("Buffer", { ); }, + /** + * Gets a content preference for the given buffer. + * + * @param {string} pref The preference to get. + * @param {function(string|number|boolean)} callback The callback to + * call with the preference value. @optional + * @returns {string|number|boolean} The value of the preference, if + * callback is not provided. + */ + getPref: function getPref(pref, callback) { + // God damn it. + if (config.haveGecko("19.0a1")) + services.contentPrefs.getPref(this.uri, pref, + sanitizer.getContext(this.win), callback); + else + services.contentPrefs.getPref(uri, pref, callback); + }, + + /** + * Sets a content preference for the given buffer. + * + * @param {string} pref The preference to set. + * @param {string} value The value to store. + */ + setPref: function setPref(pref, value) { + services.contentPrefs.setPref( + this.uri, pref, value, sanitizer.getContext(this.win)); + }, + + /** + * Clear a content preference for the given buffer. + * + * @param {string} pref The preference to clear. + */ + clearPref: function clearPref(pref) { + services.contentPrefs.removePref( + this.uri, pref, sanitizer.getContext(this.win)); + }, + climbUrlPath: function climbUrlPath(count) { let { dactyl } = this.modules; @@ -234,8 +275,8 @@ var Buffer = Module("Buffer", { })(win || this.win); if (focusedFirst) - return frames.filter(function (f) f === this.focusedFrame).concat( - frames.filter(function (f) f !== this.focusedFrame)); + return frames.filter(function (f) f === this.focusedFrame, this).concat( + frames.filter(function (f) f !== this.focusedFrame, this)); return frames; }, @@ -574,6 +615,31 @@ var Buffer = Module("Buffer", { */ get selectionController() util.selectionController(this.focusedFrame), + /** + * @property {string|null} The canonical short URL for the current + * document. + */ + get shortURL() { + let { uri, doc } = this; + + for each (let shortener in Buffer.uriShorteners) + try { + let shortened = shortener(uri, doc); + if (shortened) + return shortened.spec; + } + catch (e) { + util.reportError(e); + } + + let link = DOM("link[href][rev=canonical], \ + link[href][rel=shortlink]", doc); + if (link) + return link.attr("href"); + + return null; + }, + /** * Opens the appropriate context menu for *elem*. * @@ -585,8 +651,9 @@ var Buffer = Module("Buffer", { * Saves a page link to disk. * * @param {HTMLAnchorElement} elem The page link to save. + * @param {boolean} overwrite If true, overwrite any existing file. */ - saveLink: function saveLink(elem) { + saveLink: function saveLink(elem, overwrite) { let { completion, dactyl, io } = this.modules; let self = this; @@ -604,6 +671,8 @@ var Buffer = Module("Buffer", { if (file.exists() && file.isDirectory()) file.append(Buffer.getDefaultNames(elem)[0][0]); + util.assert(!file.exists() || overwrite, _("io.existsNoOverride", file.path)); + try { if (!file.exists()) file.create(File.NORMAL_FILE_TYPE, octal(644)); @@ -612,7 +681,7 @@ var Buffer = Module("Buffer", { util.assert(false, _("save.invalidDestination", e.name)); } - self.saveURI(uri, file); + self.saveURI({ uri: uri, file: file, context: elem }); }, completer: function (context) completion.savePage(context, elem) @@ -629,24 +698,33 @@ var Buffer = Module("Buffer", { * @param {nsIURI} uri The URI to save * @param {nsIFile} file The file into which to write the result. */ - saveURI: function saveURI(uri, file, callback, self) { + saveURI: function saveURI(params) { + if (params instanceof Ci.nsIURI) + // Deprecated? + params = { uri: arguments[0], file: arguments[1], + callback: arguments[2], self: arguments[3] }; + var persist = services.Persist(); persist.persistFlags = persist.PERSIST_FLAGS_FROM_CACHE | persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; let window = this.topWindow; + let privacy = sanitizer.getContext(params.context || this.win); + let file = File(params.file); if (!file.exists()) file.create(Ci.nsIFile.NORMAL_FILE_TYPE, octal(666)); let downloadListener = new window.DownloadListener(window, - services.Transfer(uri, File(file).URI, "", - null, null, null, persist)); + services.Transfer(params.uri, file.URI, "", null, null, null, + persist, privacy && privacy.usePrivateBrowsing)); + var { callback, self } = params; if (callback) persist.progressListener = update(Object.create(downloadListener), { onStateChange: util.wrapCallback(function onStateChange(progress, request, flags, status) { if (callback && (flags & Ci.nsIWebProgressListener.STATE_STOP) && status == 0) - util.trapErrors(callback, self, uri, file, progress, request, flags, status); + util.trapErrors(callback, self, params.uri, file.file, + progress, request, flags, status); return onStateChange.superapply(this, arguments); }) @@ -654,7 +732,8 @@ var Buffer = Module("Buffer", { else persist.progressListener = downloadListener; - persist.saveURI(uri, null, null, null, null, file); + persist.saveURI(params.uri, null, null, null, null, + file.file, privacy); }, /** @@ -748,8 +827,26 @@ var Buffer = Module("Buffer", { var sel = this.focusedFrame.getSelection(); } catch (e) {} + if (!elem && sel && sel.rangeCount) elem = sel.getRangeAt(0).startContainer; + + if (!elem) { + let area = -1; + for (let e in DOM(Buffer.SCROLLABLE_SEARCH_SELECTOR, + this.focusedFrame.document)) { + if (Buffer.isScrollable(e, dir, horizontal)) { + let r = DOM(e).rect; + let a = r.width * r.height; + if (a > area) { + area = a; + elem = e; + } + } + } + if (elem) + util.trapErrors("focus", elem); + } if (elem) elem = find(elem); @@ -865,14 +962,14 @@ var Buffer = Module("Buffer", { // add the frame indicator let doc = frames[next].document; - let indicator = DOM(
, doc) + let indicator = DOM(["div", { highlight: "FrameIndicator" }], doc) .appendTo(doc.body || doc.documentElement || doc); util.timeout(function () { indicator.remove(); }, 500); // Doesn't unattach - //doc.body.setAttributeNS(NS.uri, "activeframe", "true"); - //util.timeout(function () { doc.body.removeAttributeNS(NS.uri, "activeframe"); }, 500); + //doc.body.setAttributeNS(NS, "activeframe", "true"); + //util.timeout(function () { doc.body.removeAttributeNS(NS, "activeframe"); }, 500); }, // similar to pageInfo @@ -885,8 +982,7 @@ var Buffer = Module("Buffer", { showElementInfo: function showElementInfo(elem) { let { dactyl } = this.modules; - XML.ignoreWhitespace = XML.prettyPrinting = false; - dactyl.echo(<>Element:
{util.objectToString(elem, true)}); + dactyl.echo(["", /*L*/"Element:", ["br"], util.objectToString(elem, true)]); }, /** @@ -910,12 +1006,12 @@ var Buffer = Module("Buffer", { (sections || options["pageinfo"]) .map(function (opt) Buffer.pageInfo[opt].action.call(self)), function (res) res && iter(res).join(", ") || undefined, - ", "); + ", ").join(""); if (bookmarkcache.isBookmarked(this.URL)) info += ", " + _("buffer.bookmarked"); - let pageInfoText = <>{file.quote()} [{info}] {title}; + let pageInfoText = [file.quote(), " [", info, "] ", title].join(""); dactyl.echo(pageInfoText, commandline.FORCE_SINGLELINE); return; } @@ -923,7 +1019,7 @@ var Buffer = Module("Buffer", { let list = template.map(sections || options["pageinfo"], function (option) { let { action, title } = Buffer.pageInfo[option]; return template.table(title, action.call(self, true)); - },
); + }, ["br"]); commandline.commandOutput(list); }, @@ -1037,7 +1133,15 @@ var Buffer = Module("Buffer", { return true; }; - let uri = isString(doc) ? util.newURI(doc) : util.newURI(doc.location.href); + if (isString(doc)) { + var privacyContext = null; + var uri = util.newURI(doc); + } + else { + privacyContext = sanitizer.getContext(doc); + uri = util.newURI(doc.location.href); + } + let ext = uri.fileExtension || "txt"; if (doc.contentType) ext = services.mime.getPrimaryExtension(doc.contentType, ext); @@ -1058,7 +1162,8 @@ var Buffer = Module("Buffer", { var persist = services.Persist(); persist.persistFlags = persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; persist.progressListener = this; - persist.saveURI(uri, null, null, null, null, this.file); + persist.saveURI(uri, null, null, null, null, this.file, + privacyContext); } return null; }, @@ -1110,7 +1215,7 @@ var Buffer = Module("Buffer", { * closed range [Buffer.ZOOM_MIN, Buffer.ZOOM_MAX]. */ setZoom: function setZoom(value, fullZoom) { - let { dactyl, statusline } = this.modules; + let { dactyl, statusline, storage } = this.modules; let { ZoomManager } = this; if (fullZoom === undefined) @@ -1127,12 +1232,16 @@ var Buffer = Module("Buffer", { return dactyl.echoerr(_("zoom.illegal")); } - if (services.has("contentPrefs") && !storage.privateMode - && prefs.get("browser.zoom.siteSpecific")) { - services.contentPrefs[value != 1 ? "setPref" : "removePref"] - (this.uri, "browser.content.full-zoom", value); - services.contentPrefs[value != 1 ? "setPref" : "removePref"] - (this.uri, "dactyl.content.full-zoom", fullZoom); + if (prefs.get("browser.zoom.siteSpecific")) { + var privacy = sanitizer.getContext(this.win); + if (value == 1) { + this.clearPref("browser.content.full-zoom"); + this.clearPref("dactyl.content.full-zoom"); + } + else { + this.setPref("browser.content.full-zoom", value); + this.setPref("dactyl.content.full-zoom", fullZoom); + } } statusline.updateZoomLevel(); @@ -1145,12 +1254,13 @@ var Buffer = Module("Buffer", { let self = this; let uri = this.uri; - if (services.has("contentPrefs") && prefs.get("browser.zoom.siteSpecific")) - services.contentPrefs.getPref(uri, "dactyl.content.full-zoom", function (val) { + if (prefs.get("browser.zoom.siteSpecific")) { + this.getPref("dactyl.content.full-zoom", function (val) { if (val != null && uri.equals(self.uri) && val != prefs.get("browser.zoom.full")) [self.contentViewer.textZoom, self.contentViewer.fullZoom] = [self.contentViewer.fullZoom, self.contentViewer.textZoom]; }); + } }), /** @@ -1191,6 +1301,12 @@ var Buffer = Module("Buffer", { scrollTo: deprecated("Buffer.scrollTo", function scrollTo(x, y) this.win.scrollTo(x, y)), textZoom: deprecated("buffer.zoomValue/buffer.fullZoom", function textZoom() this.contentViewer.markupDocumentViewer.textZoom * 100) }, { + /** + * The pattern used to search for a scrollable element when we have + * no starting point. + */ + SCROLLABLE_SEARCH_SELECTOR: "html, body, div", + PageInfo: Struct("PageInfo", "name", "title", "action") .localize("title"), @@ -1209,6 +1325,27 @@ var Buffer = Module("Buffer", { this.pageInfo[option] = Buffer.PageInfo(option, title, func); }, + uriShorteners: [], + + /** + * Adds a new URI shortener for documents matching the given filter. + * + * @param {string|function(URI, Document):boolean} filter A site filter + * string or a function which accepts a URI and a document and + * returns true if it can shorten the document's URI. + * @param {function(URI, Document):URI} shortener Returns a shortened + * URL for the given URI and document. + */ + addURIShortener: function addURIShortener(filter, shortener) { + if (isString(filter)) + filter = Group.compileFilter(filter); + + this.uriShorteners.push(function uriShortener(uri, doc) { + if (filter(uri, doc)) + return shortener(uri, doc); + }); + }, + Scrollable: function Scrollable(elem) { if (elem instanceof Ci.nsIDOMElement) return elem; @@ -1224,6 +1361,9 @@ var Buffer = Module("Buffer", { get scrollWidth() this.win.scrollMaxX + this.win.innerWidth, get scrollHeight() this.win.scrollMaxY + this.win.innerHeight, + get scrollLeftMax() this.win.scrollMaxX, + get scrollRightMax() this.win.scrollMaxY, + get scrollLeft() this.win.scrollX, set scrollLeft(val) { this.win.scrollTo(val, this.win.scrollY) }, @@ -1321,23 +1461,30 @@ var Buffer = Module("Buffer", { }, canScroll: function canScroll(elem, dir, horizontal) { - let pos = "scrollTop", size = "clientHeight", max = "scrollHeight", layoutSize = "offsetHeight", + let pos = "scrollTop", size = "clientHeight", end = "scrollHeight", layoutSize = "offsetHeight", overflow = "overflowX", border1 = "borderTopWidth", border2 = "borderBottomWidth"; if (horizontal) - pos = "scrollLeft", size = "clientWidth", max = "scrollWidth", layoutSize = "offsetWidth", + pos = "scrollLeft", size = "clientWidth", end = "scrollWidth", layoutSize = "offsetWidth", overflow = "overflowX", border1 = "borderLeftWidth", border2 = "borderRightWidth"; + if (dir < 0) + return elem[pos] > 0; + + let max = pos + "Max"; + if (max in elem && pos > 0) + return elem[pos] < elem[max]; + let style = DOM(elem).style; let borderSize = Math.round(parseFloat(style[border1]) + parseFloat(style[border2])); let realSize = elem[size]; // Stupid Gecko eccentricities. May fail for quirks mode documents. - if (elem[size] + borderSize == elem[max] || elem[size] == 0) // Stupid, fallible heuristic. + if (elem[size] + borderSize >= elem[end] || elem[size] == 0) // Stupid, fallible heuristic. return false; if (style[overflow] == "hidden") realSize += borderSize; - return dir < 0 && elem[pos] > 0 || dir > 0 && elem[pos] + realSize < elem[max] || !dir && realSize < elem[max]; + return dir > 0 && elem[pos] + realSize < elem[end] || !dir && realSize < elem[end]; }, /** @@ -1444,7 +1591,7 @@ var Buffer = Module("Buffer", { /** * Scrolls the given element vertically. * - * @param {Element} elem The element to scroll. + * @param {Node} node The node to scroll. * @param {string} unit The increment by which to scroll. * Possible values are: "lines", "pages" * @param {number} number The possibly fractional number of @@ -1530,7 +1677,7 @@ var Buffer = Module("Buffer", { _exWidth: function _exWidth(elem) { try { - let div = DOM(, + let div = DOM(["elem", { style: "width: 1ex !important; position: absolute !important; padding: 0 !important; display: block;" }], elem.ownerDocument).appendTo(elem.body || elem); try { return parseFloat(div.style.width); @@ -1690,7 +1837,7 @@ var Buffer = Module("Buffer", { function (file) { let output = io.system(filename.substr(1), file); commandline.command = command; - commandline.commandOutput({output}); + commandline.commandOutput(["span", { highlight: "CmdOutput" }, output]); }); if (/^>>/.test(filename)) { @@ -1716,7 +1863,7 @@ var Buffer = Module("Buffer", { dactyl.assert(args.bang || !file.exists(), _("io.exists")); - chosenData = { file: file, uri: util.newURI(doc.location.href) }; + chosenData = { file: file.file, uri: util.newURI(doc.location.href) }; } // if browser.download.useDownloadDir = false then the "Save As" @@ -1734,7 +1881,7 @@ var Buffer = Module("Buffer", { window.internalSave(doc.location.href, doc, null, contentDisposition, doc.contentType, false, null, chosenData, doc.referrer ? window.makeURI(doc.referrer) : null, - true); + doc, true); }, { argCount: "?", @@ -1854,8 +2001,7 @@ var Buffer = Module("Buffer", { uri.query = uri.query.replace(/(?:^|&)utm_[^&]+/g, "") .replace(/^&/, ""); - let link = DOM("link[href][rev=canonical], link[href][rel=shortlink]", doc); - let url = link.length && options.get("yankshort").getKey(uri) ? link.attr("href") : uri.spec; + let url = options.get("yankshort").getKey(uri) && buffer.shortURL || uri.spec; dactyl.clipboardWrite(url, true); }); @@ -1907,7 +2053,7 @@ var Buffer = Module("Buffer", { function (args) { buffer.scrollVertical("lines", -Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], dactyl.has("mail") ? ["h", ""] : ["h", "", ""], + mappings.add([modes.NORMAL], dactyl.has("mail") ? ["h", ""] : ["h", "", ""], "Scroll document to the left", function (args) { buffer.scrollHorizontal("columns", -Math.max(args.count, 1)); }, { count: true }); @@ -2369,9 +2515,9 @@ Buffer.addPageInfoSection("e", "Search Engines", function (verbose) { if (verbose) for (let link in engines) yield [link.title || /*L*/ "Engine " + n++, - {link.href}]; + ["a", { href: link.href, highlight: "URL", + onclick: "if (event.button == 0) { window.external.AddSearchProvider(this.href); return false; }" }, + link.href]]; } if (!verbose && nEngines) @@ -2427,7 +2573,8 @@ Buffer.addPageInfoSection("f", "Feeds", function (verbose) { nFeed++; let type = feedTypes[feed.type] || "RSS"; if (verbose) - yield [feed.title, template.highlightURL(feed.href, true) +  ({type})]; + yield [feed.title, [template.highlightURL(feed.href, true), + ["span", { class: "extra-info" }, " (" + type + ")"]]]; } } @@ -2477,6 +2624,10 @@ Buffer.addPageInfoSection("g", "General Info", function (verbose) { yield ["Title", doc.title]; yield ["URL", template.highlightURL(doc.location.href, true)]; + let { shortURL } = this; + if (shortURL) + yield ["Short URL", template.highlightURL(shortURL, true)]; + let ref = "referrer" in doc && doc.referrer; if (ref) yield ["Referrer", template.highlightURL(ref, true)]; @@ -2499,7 +2650,8 @@ Buffer.addPageInfoSection("m", "Meta Tags", function (verbose) { // get meta tag data, sort and put into pageMeta[] let metaNodes = this.focusedFrame.document.getElementsByTagName("meta"); - return Array.map(metaNodes, function (node) [(node.name || node.httpEquiv), template.highlightURL(node.content)]) + return Array.map(metaNodes, function (node) [(node.name || node.httpEquiv), + template.highlightURL(node.content)]) .sort(function (a, b) util.compareIgnoreCase(a[0], b[0])); }); @@ -2541,7 +2693,7 @@ Buffer.addPageInfoSection("s", "Security", function (verbose) { } }); -} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } +// catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } endModule();