Home | History | Annotate | Download | only in bindings
      1 /*
      2  * Copyright (C) 2012 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @interface
     33  */
     34 WebInspector.LinkifierFormatter = function()
     35 {
     36 }
     37 
     38 WebInspector.LinkifierFormatter.prototype = {
     39     /**
     40      * @param {!Element} anchor
     41      * @param {!WebInspector.UILocation} uiLocation
     42      */
     43     formatLiveAnchor: function(anchor, uiLocation) { }
     44 }
     45 
     46 /**
     47  * @constructor
     48  * @implements {WebInspector.TargetManager.Observer}
     49  * @param {!WebInspector.LinkifierFormatter=} formatter
     50  */
     51 WebInspector.Linkifier = function(formatter)
     52 {
     53     this._formatter = formatter || new WebInspector.Linkifier.DefaultFormatter(WebInspector.Linkifier.MaxLengthForDisplayedURLs);
     54     /** @type {!Map.<!WebInspector.Target, !Array.<{anchor: !Element, location: !WebInspector.LiveLocation}>>}*/
     55     this._liveLocationsByTarget = new Map();
     56     WebInspector.targetManager.observeTargets(this);
     57 }
     58 
     59 /**
     60  * @param {!WebInspector.Linkifier.LinkHandler} handler
     61  */
     62 WebInspector.Linkifier.setLinkHandler = function(handler)
     63 {
     64     WebInspector.Linkifier._linkHandler = handler;
     65 }
     66 
     67 /**
     68  * @param {string} url
     69  * @param {number=} lineNumber
     70  * @return {boolean}
     71  */
     72 WebInspector.Linkifier.handleLink = function(url, lineNumber)
     73 {
     74     if (!WebInspector.Linkifier._linkHandler)
     75         return false;
     76     return WebInspector.Linkifier._linkHandler.handleLink(url, lineNumber)
     77 }
     78 
     79 /**
     80  * @param {!Object} revealable
     81  * @param {string} text
     82  * @param {string=} fallbackHref
     83  * @param {number=} fallbackLineNumber
     84  * @param {string=} title
     85  * @param {string=} classes
     86  * @return {!Element}
     87  */
     88 WebInspector.Linkifier.linkifyUsingRevealer = function(revealable, text, fallbackHref, fallbackLineNumber, title, classes)
     89 {
     90     var a = document.createElement("a");
     91     a.className = (classes || "") + " webkit-html-resource-link";
     92     a.textContent = text.trimMiddle(WebInspector.Linkifier.MaxLengthForDisplayedURLs);
     93     a.title = title || text;
     94     if (fallbackHref) {
     95         a.href = fallbackHref;
     96         a.lineNumber = fallbackLineNumber;
     97     }
     98     /**
     99      * @param {!Event} event
    100      * @this {Object}
    101      */
    102     function clickHandler(event)
    103     {
    104         event.stopImmediatePropagation();
    105         event.preventDefault();
    106         if (fallbackHref && WebInspector.Linkifier.handleLink(fallbackHref, fallbackLineNumber))
    107             return;
    108 
    109         WebInspector.Revealer.reveal(this);
    110     }
    111     a.addEventListener("click", clickHandler.bind(revealable), false);
    112     return a;
    113 }
    114 
    115 WebInspector.Linkifier.prototype = {
    116     /**
    117      * @param {!WebInspector.Target} target
    118      */
    119     targetAdded: function(target)
    120     {
    121         this._liveLocationsByTarget.set(target, []);
    122     },
    123 
    124     /**
    125      * @param {!WebInspector.Target} target
    126      */
    127     targetRemoved: function(target)
    128     {
    129         var liveLocations = this._liveLocationsByTarget.remove(target);
    130         for (var i = 0; i < liveLocations.length; ++i) {
    131             delete liveLocations[i].anchor.__uiLocation;
    132             var anchor = liveLocations[i].anchor;
    133             if (anchor.__fallbackAnchor) {
    134                 anchor.href = anchor.__fallbackAnchor.href;
    135                 anchor.lineNumber = anchor.__fallbackAnchor.lineNumber;
    136                 anchor.title = anchor.__fallbackAnchor.title;
    137                 anchor.className = anchor.__fallbackAnchor.className;
    138                 anchor.textContent = anchor.__fallbackAnchor.textContent;
    139             }
    140             liveLocations[i].location.dispose();
    141         }
    142     },
    143 
    144     /**
    145      * @param {?WebInspector.Target} target
    146      * @param {?string} scriptId
    147      * @param {string} sourceURL
    148      * @param {number} lineNumber
    149      * @param {number=} columnNumber
    150      * @param {string=} classes
    151      * @return {!Element}
    152      */
    153     linkifyScriptLocation: function(target, scriptId, sourceURL, lineNumber, columnNumber, classes)
    154     {
    155         var rawLocation = target && !target.isDetached() ? target.debuggerModel.createRawLocationByScriptId(scriptId, sourceURL, lineNumber, columnNumber || 0) : null;
    156         var fallbackAnchor = WebInspector.linkifyResourceAsNode(sourceURL, lineNumber, classes);
    157         if (!rawLocation)
    158             return fallbackAnchor;
    159 
    160         var anchor = this._createAnchor(classes);
    161         var liveLocation = WebInspector.debuggerWorkspaceBinding.createLiveLocation(rawLocation, this._updateAnchor.bind(this, anchor));
    162         this._liveLocationsByTarget.get(rawLocation.target()).push({ anchor: anchor, location: liveLocation });
    163         anchor.__fallbackAnchor = fallbackAnchor;
    164         return anchor;
    165     },
    166 
    167     /**
    168      * @param {!WebInspector.DebuggerModel.Location} rawLocation
    169      * @param {string} fallbackUrl
    170      * @param {string=} classes
    171      * @return {!Element}
    172      */
    173     linkifyRawLocation: function(rawLocation, fallbackUrl, classes)
    174     {
    175         return this.linkifyScriptLocation(rawLocation.target(), rawLocation.scriptId, fallbackUrl, rawLocation.lineNumber, rawLocation.columnNumber, classes);
    176     },
    177 
    178     /**
    179      * @param {?WebInspector.Target} target
    180      * @param {!ConsoleAgent.CallFrame} callFrame
    181      * @param {string=} classes
    182      * @return {!Element}
    183      */
    184     linkifyConsoleCallFrame: function(target, callFrame, classes)
    185     {
    186         // FIXME(62725): console stack trace line/column numbers are one-based.
    187         var lineNumber = callFrame.lineNumber ? callFrame.lineNumber - 1 : 0;
    188         var columnNumber = callFrame.columnNumber ? callFrame.columnNumber - 1 : 0;
    189         var anchor = this.linkifyScriptLocation(target, callFrame.scriptId, callFrame.url, lineNumber, columnNumber, classes);
    190 
    191         var script = target && target.debuggerModel.scriptForId(callFrame.scriptId);
    192         var blackboxed = script ?
    193             WebInspector.BlackboxSupport.isBlackboxed(script.sourceURL, script.isContentScript()) :
    194             WebInspector.BlackboxSupport.isBlackboxedURL(callFrame.url);
    195         if (blackboxed)
    196             anchor.classList.add("webkit-html-blackbox-link");
    197 
    198         return anchor;
    199     },
    200 
    201     /**
    202      * @param {!WebInspector.CSSLocation} rawLocation
    203      * @param {string=} classes
    204      * @return {?Element}
    205      */
    206     linkifyCSSLocation: function(rawLocation, classes)
    207     {
    208         var anchor = this._createAnchor(classes);
    209         var liveLocation = WebInspector.cssWorkspaceBinding.createLiveLocation(rawLocation, this._updateAnchor.bind(this, anchor));
    210         if (!liveLocation)
    211             return null;
    212         this._liveLocationsByTarget.get(rawLocation.target()).push({ anchor: anchor, location: liveLocation });
    213         return anchor;
    214     },
    215 
    216     /**
    217      * @param {!WebInspector.CSSMedia} media
    218      * @return {?Element}
    219      */
    220     linkifyMedia: function(media)
    221     {
    222         var location = media.rawLocation();
    223         if (location)
    224             return this.linkifyCSSLocation(location);
    225 
    226         // The "linkedStylesheet" case.
    227         return WebInspector.linkifyResourceAsNode(media.sourceURL, undefined, "subtitle", media.sourceURL);
    228     },
    229 
    230     /**
    231      * @param {string=} classes
    232      * @return {!Element}
    233      */
    234     _createAnchor: function(classes)
    235     {
    236         var anchor = document.createElement("a");
    237         anchor.className = (classes || "") + " webkit-html-resource-link";
    238 
    239         /**
    240          * @param {!Event} event
    241          */
    242         function clickHandler(event)
    243         {
    244             if (!anchor.__uiLocation)
    245                 return;
    246             event.stopImmediatePropagation();
    247             event.preventDefault();
    248             if (WebInspector.Linkifier.handleLink(anchor.__uiLocation.uiSourceCode.url, anchor.__uiLocation.lineNumber))
    249                 return;
    250             WebInspector.Revealer.reveal(anchor.__uiLocation);
    251         }
    252         anchor.addEventListener("click", clickHandler, false);
    253         return anchor;
    254     },
    255 
    256     reset: function()
    257     {
    258         var keys = this._liveLocationsByTarget.keys();
    259         for (var i = 0; i < keys.length; ++i) {
    260             var target = keys[i];
    261             this.targetRemoved(target);
    262             this.targetAdded(target);
    263         }
    264     },
    265 
    266     /**
    267      * @param {!Element} anchor
    268      * @param {!WebInspector.UILocation} uiLocation
    269      */
    270     _updateAnchor: function(anchor, uiLocation)
    271     {
    272         anchor.__uiLocation = uiLocation;
    273         this._formatter.formatLiveAnchor(anchor, uiLocation);
    274     }
    275 }
    276 
    277 /**
    278  * @constructor
    279  * @implements {WebInspector.LinkifierFormatter}
    280  * @param {number=} maxLength
    281  */
    282 WebInspector.Linkifier.DefaultFormatter = function(maxLength)
    283 {
    284     this._maxLength = maxLength;
    285 }
    286 
    287 WebInspector.Linkifier.DefaultFormatter.prototype = {
    288     /**
    289      * @param {!Element} anchor
    290      * @param {!WebInspector.UILocation} uiLocation
    291      */
    292     formatLiveAnchor: function(anchor, uiLocation)
    293     {
    294         var text = uiLocation.linkText();
    295         if (this._maxLength)
    296             text = text.trimMiddle(this._maxLength);
    297         anchor.textContent = text;
    298 
    299         var titleText = uiLocation.uiSourceCode.originURL();
    300         if (typeof uiLocation.lineNumber === "number")
    301             titleText += ":" + (uiLocation.lineNumber + 1);
    302         anchor.title = titleText;
    303     }
    304 }
    305 
    306 /**
    307  * @constructor
    308  * @extends {WebInspector.Linkifier.DefaultFormatter}
    309  */
    310 WebInspector.Linkifier.DefaultCSSFormatter = function()
    311 {
    312     WebInspector.Linkifier.DefaultFormatter.call(this, WebInspector.Linkifier.DefaultCSSFormatter.MaxLengthForDisplayedURLs);
    313 }
    314 
    315 WebInspector.Linkifier.DefaultCSSFormatter.MaxLengthForDisplayedURLs = 30;
    316 
    317 WebInspector.Linkifier.DefaultCSSFormatter.prototype = {
    318     /**
    319      * @param {!Element} anchor
    320      * @param {!WebInspector.UILocation} uiLocation
    321      */
    322     formatLiveAnchor: function(anchor, uiLocation)
    323     {
    324         WebInspector.Linkifier.DefaultFormatter.prototype.formatLiveAnchor.call(this, anchor, uiLocation);
    325         anchor.classList.add("webkit-html-resource-link");
    326         anchor.setAttribute("data-uncopyable", anchor.textContent);
    327         anchor.textContent = "";
    328     },
    329     __proto__: WebInspector.Linkifier.DefaultFormatter.prototype
    330 }
    331 
    332 /**
    333  * The maximum number of characters to display in a URL.
    334  * @const
    335  * @type {number}
    336  */
    337 WebInspector.Linkifier.MaxLengthForDisplayedURLs = 150;
    338 
    339 /**
    340  * @interface
    341  */
    342 WebInspector.Linkifier.LinkHandler = function()
    343 {
    344 }
    345 
    346 WebInspector.Linkifier.LinkHandler.prototype = {
    347     /**
    348      * @param {string} url
    349      * @param {number=} lineNumber
    350      * @return {boolean}
    351      */
    352     handleLink: function(url, lineNumber) {}
    353 }
    354 
    355 /**
    356  * @param {!WebInspector.Target} target
    357  * @param {string} scriptId
    358  * @param {number} lineNumber
    359  * @param {number=} columnNumber
    360  * @return {string}
    361  */
    362 WebInspector.Linkifier.liveLocationText = function(target, scriptId, lineNumber, columnNumber)
    363 {
    364     var script = target.debuggerModel.scriptForId(scriptId);
    365     if (!script)
    366         return "";
    367     var location = /** @type {!WebInspector.DebuggerModel.Location} */ (target.debuggerModel.createRawLocation(script, lineNumber, columnNumber || 0));
    368     var uiLocation = /** @type {!WebInspector.UILocation} */ (WebInspector.debuggerWorkspaceBinding.rawLocationToUILocation(location));
    369     return uiLocation.linkText();
    370 }
    371