]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/buffer.jsm
Imported Upstream version 1.1+hg7904
[dactyl.git] / common / modules / buffer.jsm
index 15aea79b1257040fefb5bdc2b55f2e5e60b02b72..97398efa920803e5a35375396ddf893eb8fcc43f 100644 (file)
@@ -1,6 +1,6 @@
 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2013 Kris Maglione <maglione.k at Gmail>
+// Copyright (c) 2008-2014 Kris Maglione <maglione.k at Gmail>
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
@@ -16,6 +16,7 @@ lazyRequire("contexts", ["Group"]);
 lazyRequire("io", ["io"]);
 lazyRequire("finder", ["RangeFind"]);
 lazyRequire("overlay", ["overlay"]);
+lazyRequire("promises", ["Promise", "promises"]);
 lazyRequire("sanitizer", ["sanitizer"]);
 lazyRequire("storage", ["File", "storage"]);
 lazyRequire("template", ["template"]);
@@ -45,7 +46,7 @@ var Buffer = Module("Buffer", {
             this.win = win;
     },
 
-    get addPageInfoSection() Buffer.closure.addPageInfoSection,
+    get addPageInfoSection() Buffer.bound.addPageInfoSection,
 
     get pageInfo() Buffer.pageInfo,
 
@@ -61,13 +62,78 @@ var Buffer = Module("Buffer", {
      */
     get alternateStyleSheets() {
         let stylesheets = array.flatten(
-            this.allFrames().map(function (w) Array.slice(w.document.styleSheets)));
+            this.allFrames().map(w => Array.slice(w.document.styleSheets)));
 
         return stylesheets.filter(
-            function (stylesheet) /^(screen|all|)$/i.test(stylesheet.media.mediaText) && !/^\s*$/.test(stylesheet.title)
+            s => /^(screen|all|)$/i.test(s.media.mediaText) && !/^\s*$/.test(s.title)
         );
     },
 
+    /**
+     * The load context of the window bound to this buffer.
+     */
+    get loadContext() sanitizer.getContext(this.win),
+
+    /**
+     * Content preference methods.
+     */
+    prefs: Class.Memoize(function ()
+        let (self = this) ({
+            /**
+             * Returns a promise for the given preference name.
+             *
+             * @param {string} pref The name of the preference to return.
+             * @returns {Promise<*>}
+             */
+            get: promises.withCallbacks(function get([resolve, reject], pref) {
+                let val = services.contentPrefs.getCachedByDomainAndName(
+                    self.uri.spec, pref, self.loadContext);
+
+                let found = false;
+                if (val)
+                    resolve(val.value);
+                else
+                    services.contentPrefs.getByDomainAndName(
+                        self.uri.spec, pref, self.loadContext,
+                        { handleCompletion: () => {
+                              if (!found)
+                                  resolve(undefined);
+                          },
+                          handleResult: (pref) => {
+                              found = true;
+                              resolve(pref.value);
+                          },
+                          handleError: reject });
+            }),
+
+            /**
+             * Sets a content preference for the given buffer.
+             *
+             * @param {string} pref The preference to set.
+             * @param {string} value The value to store.
+             */
+            set: promises.withCallbacks(function set([resolve, reject], pref, value) {
+                services.contentPrefs.set(
+                    self.uri.spec, pref, value, self.loadContext,
+                    { handleCompletion: () => {},
+                      handleResult: resolve,
+                      handleError: reject });
+            }),
+
+            /**
+             * Clear a content preference for the given buffer.
+             *
+             * @param {string} pref The preference to clear.
+             */
+            clear: promises.withCallbacks(function clear([resolve, reject], pref) {
+                services.contentPrefs.removeByDomainAndName(
+                    self.uri.spec, pref, self.loadContext,
+                    { handleCompletion: () => {},
+                      handleResult: resolve,
+                      handleError: reject });
+            })
+        })),
+
     /**
      * Gets a content preference for the given buffer.
      *
@@ -77,14 +143,10 @@ var Buffer = Module("Buffer", {
      * @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(this.uri, pref, callback);
-    },
+    getPref: deprecated("prefs.get", function getPref(pref, callback) {
+        services.contentPrefs.getPref(this.uri, pref,
+                                      this.loadContext, callback);
+    }),
 
     /**
      * Sets a content preference for the given buffer.
@@ -92,20 +154,20 @@ var Buffer = Module("Buffer", {
      * @param {string} pref The preference to set.
      * @param {string} value The value to store.
      */
-    setPref: function setPref(pref, value) {
+    setPref: deprecated("prefs.set", function setPref(pref, value) {
         services.contentPrefs.setPref(
-            this.uri, pref, value, sanitizer.getContext(this.win));
-    },
+            this.uri, pref, value, this.loadContext);
+    }),
 
     /**
      * Clear a content preference for the given buffer.
      *
      * @param {string} pref The preference to clear.
      */
-    clearPref: function clearPref(pref) {
+    clearPref: deprecated("prefs.clear", function clearPref(pref) {
         services.contentPrefs.removePref(
-            this.uri, pref, sanitizer.getContext(this.win));
-    },
+            this.uri, pref, this.loadContext);
+    }),
 
     climbUrlPath: function climbUrlPath(count) {
         let { dactyl } = this.modules;
@@ -142,8 +204,8 @@ var Buffer = Module("Buffer", {
      */
     get loaded() Math.min.apply(null,
         this.allFrames()
-            .map(function (frame) ["loading", "interactive", "complete"]
-                                      .indexOf(frame.document.readyState))),
+            .map(frame => ["loading", "interactive", "complete"]
+                              .indexOf(frame.document.readyState))),
 
     /**
      * @property {Object} The local state store for the currently selected
@@ -275,8 +337,8 @@ var Buffer = Module("Buffer", {
         })(win || this.win);
 
         if (focusedFirst)
-            return frames.filter(function (f) f === this.focusedFrame, this).concat(
-                   frames.filter(function (f) f !== this.focusedFrame, this));
+            return frames.filter(f => f === this.focusedFrame).concat(
+                   frames.filter(f => f !== this.focusedFrame));
         return frames;
     },
 
@@ -435,7 +497,9 @@ var Buffer = Module("Buffer", {
                 yield elem;
 
             function a(regexp, elem) regexp.test(elem.textContent) === regexp.result ||
-                            Array.some(elem.childNodes, function (child) regexp.test(child.alt) === regexp.result);
+                            Array.some(elem.childNodes,
+                                       child => (regexp.test(child.alt) === regexp.result));
+
             function b(regexp, elem) regexp.test(elem.title) === regexp.result;
 
             let res = Array.filter(frame.document.querySelectorAll(selector), Hints.isVisible);
@@ -523,8 +587,6 @@ var Buffer = Module("Buffer", {
             };
 
             DOM(elem).mousedown(params).mouseup(params);
-            if (!config.haveGecko("2b"))
-                DOM(elem).click(params);
 
             let sel = util.selectionController(win);
             sel.getSelection(sel.SELECTION_FOCUS_REGION).collapseToStart();
@@ -541,7 +603,7 @@ var Buffer = Module("Buffer", {
         function getRanges(rect) {
             let nodes = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
                            .nodesFromRect(rect.x, rect.y, 0, rect.width, rect.height, 0, false, false);
-            return Array.filter(nodes, function (n) n instanceof Ci.nsIDOMText)
+            return Array.filter(nodes, n => n instanceof Ci.nsIDOMText)
                         .map(RangeFind.nodeContents);
         }
 
@@ -565,11 +627,11 @@ var Buffer = Module("Buffer", {
             rect = { x: w / 3, y: 0, width: w / 3, height: win.innerHeight };
         }
 
-        var reduce = function (a, b) DOM(a).rect.top < DOM(b).rect.top ? a : b;
+        var reduce = (a, b) => DOM(a).rect.top < DOM(b).rect.top ? a : b;
         var dir = "forward";
         var y = 0;
         if (reverse) {
-            reduce = function (a, b) DOM(b).rect.bottom > DOM(a).rect.bottom ? b : a;
+            reduce = (a, b) => DOM(b).rect.bottom > DOM(a).rect.bottom ? b : a;
             dir = "backward";
             y = win.innerHeight - 1;
         }
@@ -622,20 +684,30 @@ var Buffer = Module("Buffer", {
     get shortURL() {
         let { uri, doc } = this;
 
-        for each (let shortener in Buffer.uriShorteners)
+        function hashify(url) {
+            let newURI = util.newURI(url);
+
+            if (uri.hasRef && !newURI.hasRef)
+                newURI.ref = uri.ref;
+
+            return newURI.spec;
+        }
+
+        for (let shortener of Buffer.uriShorteners)
             try {
                 let shortened = shortener(uri, doc);
                 if (shortened)
-                    return shortened.spec;
+                    return hashify(shortened.spec);
             }
             catch (e) {
                 util.reportError(e);
             }
 
         let link = DOM("link[href][rev=canonical], \
-                        link[href][rel=shortlink]", doc);
+                        link[href][rel=shortlink]", doc)
+                       .attr("href");
         if (link)
-            return link.attr("href");
+            return hashify(link);
 
         return null;
     },
@@ -782,11 +854,10 @@ var Buffer = Module("Buffer", {
      * @param {number} count The multiple of 'scroll' lines to scroll.
      * @optional
      */
-    scrollByScrollSize: function scrollByScrollSize(direction, count) {
+    scrollByScrollSize: function scrollByScrollSize(direction, count=1) {
         let { options } = this.modules;
 
         direction = direction ? 1 : -1;
-        count = count || 1;
 
         if (options["scroll"] > 0)
             this.scrollVertical("lines", options["scroll"] * direction);
@@ -904,10 +975,13 @@ var Buffer = Module("Buffer", {
         let path = options["jumptags"][arg];
         util.assert(path, _("error.invalidArgument", arg));
 
-        let distance = reverse ? function (rect) -rect.top : function (rect) rect.top;
-        let elems = [[e, distance(e.getBoundingClientRect())] for (e in path.matcher(this.focusedFrame.document))]
-                        .filter(function (e) e[1] > FUDGE)
-                        .sort(function (a, b) a[1] - b[1]);
+        let distance = reverse ? rect => -rect.top
+                               : rect => rect.top;
+
+        let elems = [[e, distance(e.getBoundingClientRect())]
+                     for (e in path.matcher(this.focusedFrame.document))]
+                        .filter(e => e[1] > FUDGE)
+                        .sort((a, b) => a[1] - b[1]);
 
         if (offScreen && !reverse)
             elems = elems.filter(function (e) e[1] > this, this.topWindow.innerHeight);
@@ -941,8 +1015,8 @@ var Buffer = Module("Buffer", {
             return;
 
         // remove all hidden frames
-        frames = frames.filter(function (frame) !(frame.document.body instanceof Ci.nsIDOMHTMLFrameSetElement))
-                       .filter(function (frame) !frame.frameElement ||
+        frames = frames.filter(frame => !(frame.document.body instanceof Ci.nsIDOMHTMLFrameSetElement))
+                       .filter(frame => !frame.frameElement ||
             let (rect = frame.frameElement.getBoundingClientRect())
                 rect.width && rect.height);
 
@@ -1003,7 +1077,7 @@ var Buffer = Module("Buffer", {
             let info = template.map(
                 (sections || options["pageinfo"])
                     .map((opt) => Buffer.pageInfo[opt].action.call(this)),
-                function (res) res && iter(res).join(", ") || undefined,
+                res => (res && iter(res).join(", ") || undefined),
                 ", ").join("");
 
             if (bookmarkcache.isBookmarked(this.URL))
@@ -1091,7 +1165,7 @@ var Buffer = Module("Buffer", {
             else {
                 let url = loc || doc.location.href;
                 const PREFIX = "view-source:";
-                if (url.indexOf(PREFIX) == 0)
+                if (url.startsWith(PREFIX))
                     url = url.substr(PREFIX.length);
                 else
                     url = PREFIX + url;
@@ -1236,12 +1310,12 @@ var Buffer = Module("Buffer", {
         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");
+                this.prefs.clear("browser.content.full-zoom");
+                this.prefs.clear("dactyl.content.full-zoom");
             }
             else {
-                this.setPref("browser.content.full-zoom", value);
-                this.setPref("dactyl.content.full-zoom", fullZoom);
+                this.prefs.set("browser.content.full-zoom", value);
+                this.prefs.set("dactyl.content.full-zoom", fullZoom);
             }
         }
 
@@ -1251,15 +1325,15 @@ var Buffer = Module("Buffer", {
     /**
      * Updates the zoom level of this buffer from a content preference.
      */
-    updateZoom: util.wrapCallback(function updateZoom() {
+    updateZoom: promises.task(function updateZoom() {
         let uri = this.uri;
 
         if (prefs.get("browser.zoom.siteSpecific")) {
-            this.getPref("dactyl.content.full-zoom", (val) => {
-                if (val != null && uri.equals(this.uri) && val != prefs.get("browser.zoom.full"))
-                    [this.contentViewer.textZoom, this.contentViewer.fullZoom] =
-                        [this.contentViewer.fullZoom, this.contentViewer.textZoom];
-            });
+            let val = yield this.prefs.get("dactyl.content.full-zoom");
+
+            if (val != null && uri.equals(this.uri) && val != prefs.get("browser.zoom.full"))
+                [this.contentViewer.textZoom, this.contentViewer.fullZoom] =
+                    [this.contentViewer.fullZoom, this.contentViewer.textZoom];
         }
     }),
 
@@ -1443,9 +1517,9 @@ var Buffer = Module("Buffer", {
         names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")),
                     _("buffer.save.filename")]);
 
-        return names.filter(function ([leaf, title]) leaf)
-                    .map(function ([leaf, title]) [leaf.replace(config.OS.illegalCharacters, encodeURIComponent)
-                                                       .replace(re, ext), title]);
+        return names.filter(([leaf, title]) => leaf)
+                    .map(([leaf, title]) => [leaf.replace(config.OS.illegalCharacters, encodeURIComponent)
+                                                 .replace(re, ext), title]);
     },
 
     findScrollableWindow: deprecated("buffer.findScrollableWindow", function findScrollableWindow()
@@ -1526,7 +1600,8 @@ var Buffer = Module("Buffer", {
      * Like scrollTo, but scrolls more smoothly and does not update
      * marks.
      */
-    smoothScrollTo: function smoothScrollTo(node, x, y) {
+    smoothScrollTo: let (timers = WeakMap())
+                    function smoothScrollTo(node, x, y) {
         let { options } = overlay.activeModules;
 
         let time = options["scrolltime"];
@@ -1534,8 +1609,8 @@ var Buffer = Module("Buffer", {
 
         let elem = Buffer.Scrollable(node);
 
-        if (node.dactylScrollTimer)
-            node.dactylScrollTimer.cancel();
+        if (timers.has(node))
+            timers.get(node).cancel();
 
         if (x == null)
             x = elem.scrollLeft;
@@ -1556,7 +1631,7 @@ var Buffer = Module("Buffer", {
             else {
                 elem.scrollLeft = startX + (x - startX) / steps * n;
                 elem.scrollTop  = startY + (y - startY) / steps * n;
-                node.dactylScrollTimer = util.timeout(next, time / steps);
+                timers.set(node, util.timeout(next, time / steps));
             }
         }).call(this);
     },
@@ -1737,7 +1812,7 @@ var Buffer = Module("Buffer", {
                 let arg = args[0];
 
                 // FIXME: arg handling is a bit of a mess, check for filename
-                dactyl.assert(!arg || arg[0] == ">" && !config.OS.isWindows,
+                dactyl.assert(!arg || arg[0] == ">",
                               _("error.trailingCharacters"));
 
                 const PRINTER  = "PostScript/default";
@@ -1747,24 +1822,25 @@ var Buffer = Module("Buffer", {
                     BRANCHES.forEach(function (branch) { prefs.set(branch + pref, value); });
                 }
 
-                prefs.withContext(function () {
-                    if (arg) {
-                        prefs.set("print.print_printer", PRINTER);
+                let settings = services.printSettings.newPrintSettings;
+                settings.printSilent = args.bang;
+                if (arg) {
+                    settings.printToFile = true;
+                    settings.toFileName = io.File(arg.substr(1)).path;
+                    settings.outputFormat = settings.kOutputFormatPDF;
 
-                        set("print_to_file", true);
-                        set("print_to_filename", io.File(arg.substr(1)).path);
-
-                        dactyl.echomsg(_("print.toFile", arg.substr(1)));
-                    }
-                    else
-                        dactyl.echomsg(_("print.sending"));
+                    dactyl.echomsg(_("print.toFile", arg.substr(1)));
+                }
+                else {
+                    dactyl.echomsg(_("print.sending"));
 
-                    prefs.set("print.always_print_silent", args.bang);
                     if (false)
                         prefs.set("print.show_print_progress", !args.bang);
+                }
 
-                    config.browser.contentWindow.print();
-                });
+                config.browser.contentWindow
+                      .QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIWebBrowserPrint).print(settings, null);
 
                 dactyl.echomsg(_("print.sent"));
             },
@@ -1801,7 +1877,7 @@ var Buffer = Module("Buffer", {
             function (args) {
                 let arg = args[0] || "";
 
-                let titles = buffer.alternateStyleSheets.map(function (stylesheet) stylesheet.title);
+                let titles = buffer.alternateStyleSheets.map(sheet => sheet.title);
 
                 dactyl.assert(!arg || titles.indexOf(arg) >= 0,
                               _("error.invalidArgument", arg));
@@ -1993,7 +2069,7 @@ var Buffer = Module("Buffer", {
     events: function initEvents(dactyl, modules, window) {
         let { buffer, config, events } = modules;
 
-        events.listen(config.browser, "scroll", buffer.closure._updateBufferPosition, false);
+        events.listen(config.browser, "scroll", buffer.bound._updateBufferPosition, false);
     },
     mappings: function initMappings(dactyl, modules, window) {
         let { Editor, Events, buffer, editor, events, ex, mappings, modes, options, tabs } = modules;
@@ -2206,7 +2282,7 @@ var Buffer = Module("Buffer", {
 
                     let frames = buffer.allFrames(null, true);
 
-                    let elements = array.flatten(frames.map(function (win) [m for (m in DOM.XPath(xpath, win.document))]))
+                    let elements = array.flatten(frames.map(win => [m for (m in DOM.XPath(xpath, win.document))]))
                                         .filter(function (elem) {
                         if (isinstance(elem, [Ci.nsIDOMHTMLFrameElement,
                                               Ci.nsIDOMHTMLIFrameElement]))
@@ -2214,7 +2290,7 @@ var Buffer = Module("Buffer", {
 
                         elem = DOM(elem);
 
-                        if (elem[0].readOnly || !DOM(elem).isEditable)
+                        if (elem[0].readOnly || elem[0].disabled || !DOM(elem).isEditable)
                             return false;
 
                         let style = elem.style;
@@ -2388,7 +2464,7 @@ var Buffer = Module("Buffer", {
                     return vals;
                 },
                 validator: function (value) DOM.validateMatcher.call(this, value)
-                    && Object.keys(value).every(function (v) v.length == 1)
+                    && Object.keys(value).every(v => v.length == 1)
             });
 
         options.add(["linenumbers", "ln"],
@@ -2412,9 +2488,9 @@ var Buffer = Module("Buffer", {
                             if (/^func:/.test(filter.result))
                                 var res = dactyl.userEval("(" + Option.dequote(filter.result.substr(5)) + ")")(doc, line);
                             else
-                                res = iter.nth(filter.matcher(doc),
-                                               function (elem) (elem.nodeValue || elem.textContent).trim() == line && DOM(elem).display != "none",
-                                               0)
+                                res = iter.find(filter.matcher(doc),
+                                                elem => ((elem.nodeValue || elem.textContent).trim() == line &&
+                                                         DOM(elem).display != "none"))
                                    || iter.nth(filter.matcher(doc), util.identity, line - 1);
                             if (res)
                                 break;
@@ -2656,9 +2732,9 @@ 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)])
-                .sort(function (a, b) util.compareIgnoreCase(a[0], b[0]));
+    return Array.map(metaNodes, node => [(node.name || node.httpEquiv),
+                                         template.highlightURL(node.content)])
+                .sort((a, b) => util.compareIgnoreCase(a[0], b[0]));
 });
 
 Buffer.addPageInfoSection("s", "Security", function (verbose) {
@@ -2691,14 +2767,23 @@ Buffer.addPageInfoSection("s", "Security", function (verbose) {
 
         yield ["Verified by", data.caOrg];
 
-        if (identity._overrideService.hasMatchingOverride(identity._lastLocation.hostname,
-                                                      (identity._lastLocation.port || 443),
-                                                      data.cert, {}, {}))
+        let { host, port } = identity._lastUri;
+        if (port == -1)
+            port = 443;
+
+        if (identity._overrideService.hasMatchingOverride(host, port, data.cert, {}, {}))
             yield ["User exception", /*L*/"true"];
         break;
     }
 });
 
+// internal navigation doesn't currently update link[rel='shortlink']
+Buffer.addURIShortener("youtube.com", (uri, doc) => {
+    let video = array.toObject(uri.query.split("&")
+                                        .map(p => p.split("="))).v;
+    return video ? util.newURI("http://youtu.be/" + video) : null;
+});
+
 // catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
 
 endModule();