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