2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
34 WebInspector
.LinkifierFormatter = function()
38 WebInspector
.LinkifierFormatter
.prototype = {
40 * @param {!Element} anchor
41 * @param {!WebInspector.UILocation} uiLocation
43 formatLiveAnchor: function(anchor
, uiLocation
) { }
48 * @implements {WebInspector.TargetManager.Observer}
49 * @param {!WebInspector.LinkifierFormatter=} formatter
51 WebInspector
.Linkifier = function(formatter
)
53 this._formatter
= formatter
|| new WebInspector
.Linkifier
.DefaultFormatter(WebInspector
.Linkifier
.MaxLengthForDisplayedURLs
);
54 /** @type {!Map.<!WebInspector.Target, !Map.<!Element, !WebInspector.LiveLocation>>}*/
55 this._liveLocationsByTarget
= new Map();
56 WebInspector
.targetManager
.observeTargets(this);
60 * @param {?WebInspector.Linkifier.LinkHandler} handler
62 WebInspector
.Linkifier
.setLinkHandler = function(handler
)
64 WebInspector
.Linkifier
._linkHandler
= handler
;
69 * @param {number=} lineNumber
72 WebInspector
.Linkifier
.handleLink = function(url
, lineNumber
)
74 if (!WebInspector
.Linkifier
._linkHandler
)
76 return WebInspector
.Linkifier
._linkHandler
.handleLink(url
, lineNumber
);
80 * @param {!Object} revealable
81 * @param {string} text
82 * @param {string=} fallbackHref
83 * @param {number=} fallbackLineNumber
84 * @param {string=} title
85 * @param {string=} classes
88 WebInspector
.Linkifier
.linkifyUsingRevealer = function(revealable
, text
, fallbackHref
, fallbackLineNumber
, title
, classes
)
90 var a
= createElement("a");
91 a
.className
= (classes
|| "") + " webkit-html-resource-link";
92 a
.textContent
= text
.trimMiddle(WebInspector
.Linkifier
.MaxLengthForDisplayedURLs
);
93 a
.title
= title
|| text
;
95 a
.href
= fallbackHref
;
96 a
.lineNumber
= fallbackLineNumber
;
99 * @param {!Event} event
102 function clickHandler(event
)
104 event
.stopImmediatePropagation();
105 event
.preventDefault();
106 if (fallbackHref
&& WebInspector
.Linkifier
.handleLink(fallbackHref
, fallbackLineNumber
))
109 WebInspector
.Revealer
.reveal(this);
111 a
.addEventListener("click", clickHandler
.bind(revealable
), false);
115 WebInspector
.Linkifier
._uiLocationSymbol
= Symbol("uiLocation");
116 WebInspector
.Linkifier
._fallbackAnchorSymbol
= Symbol("fallbackAnchor");;
118 WebInspector
.Linkifier
.prototype = {
121 * @param {!WebInspector.Target} target
123 targetAdded: function(target
)
125 this._liveLocationsByTarget
.set(target
, new Map());
130 * @param {!WebInspector.Target} target
132 targetRemoved: function(target
)
134 var liveLocations
= this._liveLocationsByTarget
.remove(target
);
135 var anchors
= liveLocations
.keysArray();
136 for (var i
= 0; i
< anchors
.length
; ++i
) {
137 var anchor
= anchors
[i
];
138 var location
= liveLocations
.get(anchor
);
139 delete anchor
[WebInspector
.Linkifier
._uiLocationSymbol
];
140 var fallbackAnchor
= anchor
[WebInspector
.Linkifier
._fallbackAnchorSymbol
];
141 if (fallbackAnchor
) {
142 anchor
.href
= fallbackAnchor
.href
;
143 anchor
.lineNumber
= fallbackAnchor
.lineNumber
;
144 anchor
.title
= fallbackAnchor
.title
;
145 anchor
.className
= fallbackAnchor
.className
;
146 anchor
.textContent
= fallbackAnchor
.textContent
;
147 delete anchor
[WebInspector
.Linkifier
._fallbackAnchorSymbol
];
154 * @param {?WebInspector.Target} target
155 * @param {?string} scriptId
156 * @param {string} sourceURL
157 * @param {number} lineNumber
158 * @param {number=} columnNumber
159 * @param {string=} classes
162 linkifyScriptLocation: function(target
, scriptId
, sourceURL
, lineNumber
, columnNumber
, classes
)
164 var fallbackAnchor
= WebInspector
.linkifyResourceAsNode(sourceURL
, lineNumber
, classes
);
165 if (!target
|| target
.isDetached())
166 return fallbackAnchor
;
167 var debuggerModel
= WebInspector
.DebuggerModel
.fromTarget(target
);
169 return fallbackAnchor
;
171 var rawLocation
= scriptId
? debuggerModel
.createRawLocationByScriptId(scriptId
, lineNumber
, columnNumber
|| 0) :
172 debuggerModel
.createRawLocationByURL(sourceURL
, lineNumber
, columnNumber
|| 0);
174 return fallbackAnchor
;
176 var anchor
= this._createAnchor(classes
);
177 var liveLocation
= WebInspector
.debuggerWorkspaceBinding
.createLiveLocation(rawLocation
, this._updateAnchor
.bind(this, anchor
));
178 this._liveLocationsByTarget
.get(rawLocation
.target()).set(anchor
, liveLocation
);
179 anchor
[WebInspector
.Linkifier
._fallbackAnchorSymbol
] = fallbackAnchor
;
184 * @param {!WebInspector.DebuggerModel.Location} rawLocation
185 * @param {string} fallbackUrl
186 * @param {string=} classes
189 linkifyRawLocation: function(rawLocation
, fallbackUrl
, classes
)
191 return this.linkifyScriptLocation(rawLocation
.target(), rawLocation
.scriptId
, fallbackUrl
, rawLocation
.lineNumber
, rawLocation
.columnNumber
, classes
);
195 * @param {?WebInspector.Target} target
196 * @param {!ConsoleAgent.CallFrame} callFrame
197 * @param {string=} classes
200 linkifyConsoleCallFrame: function(target
, callFrame
, classes
)
202 // FIXME(62725): console stack trace line/column numbers are one-based.
203 var lineNumber
= callFrame
.lineNumber
? callFrame
.lineNumber
- 1 : 0;
204 var columnNumber
= callFrame
.columnNumber
? callFrame
.columnNumber
- 1 : 0;
205 var anchor
= this.linkifyScriptLocation(target
, callFrame
.scriptId
, callFrame
.url
, lineNumber
, columnNumber
, classes
);
206 var debuggerModel
= WebInspector
.DebuggerModel
.fromTarget(target
);
207 var script
= debuggerModel
&& debuggerModel
.scriptForId(callFrame
.scriptId
);
208 var blackboxed
= script
?
209 WebInspector
.BlackboxSupport
.isBlackboxed(script
.sourceURL
, script
.isContentScript()) :
210 WebInspector
.BlackboxSupport
.isBlackboxedURL(callFrame
.url
);
212 anchor
.classList
.add("webkit-html-blackbox-link");
218 * @param {!WebInspector.CSSLocation} rawLocation
219 * @param {string=} classes
222 linkifyCSSLocation: function(rawLocation
, classes
)
224 var anchor
= this._createAnchor(classes
);
225 var liveLocation
= WebInspector
.cssWorkspaceBinding
.createLiveLocation(rawLocation
, this._updateAnchor
.bind(this, anchor
));
226 this._liveLocationsByTarget
.get(rawLocation
.target()).set(anchor
, liveLocation
);
231 * @param {!WebInspector.CSSMedia} media
234 linkifyMedia: function(media
)
236 var location
= media
.rawLocation();
238 return this.linkifyCSSLocation(location
);
240 // The "linkedStylesheet" case.
241 return WebInspector
.linkifyResourceAsNode(media
.sourceURL
, undefined, "subtitle", media
.sourceURL
);
245 * @param {!WebInspector.Target} target
246 * @param {!Element} anchor
248 disposeAnchor: function(target
, anchor
)
250 delete anchor
[WebInspector
.Linkifier
._uiLocationSymbol
];
251 delete anchor
[WebInspector
.Linkifier
._fallbackAnchorSymbol
];
252 var liveLocations
= this._liveLocationsByTarget
.get(target
);
255 var location
= liveLocations
.remove(anchor
);
261 * @param {string=} classes
264 _createAnchor: function(classes
)
266 var anchor
= createElement("a");
267 anchor
.className
= (classes
|| "") + " webkit-html-resource-link";
270 * @param {!Event} event
272 function clickHandler(event
)
274 var uiLocation
= anchor
[WebInspector
.Linkifier
._uiLocationSymbol
];
279 var networkURL
= WebInspector
.networkMapping
.networkURL(uiLocation
.uiSourceCode
);
280 if (WebInspector
.Linkifier
.handleLink(networkURL
, uiLocation
.lineNumber
))
282 WebInspector
.Revealer
.reveal(uiLocation
);
284 anchor
.addEventListener("click", clickHandler
, false);
290 var targets
= this._liveLocationsByTarget
.keysArray();
291 for (var i
= 0; i
< targets
.length
; ++i
) {
292 var target
= targets
[i
];
293 this.targetRemoved(target
);
294 this.targetAdded(target
);
301 WebInspector
.targetManager
.unobserveTargets(this);
302 this._liveLocationsByTarget
.clear();
306 * @param {!Element} anchor
307 * @param {!WebInspector.UILocation} uiLocation
309 _updateAnchor: function(anchor
, uiLocation
)
311 anchor
[WebInspector
.Linkifier
._uiLocationSymbol
] = uiLocation
;
312 this._formatter
.formatLiveAnchor(anchor
, uiLocation
);
317 * @param {!Element} anchor
318 * @return {?WebInspector.UILocation} uiLocation
320 WebInspector
.Linkifier
.uiLocationByAnchor = function(anchor
)
322 return anchor
[WebInspector
.Linkifier
._uiLocationSymbol
];
327 * @implements {WebInspector.LinkifierFormatter}
328 * @param {number=} maxLength
330 WebInspector
.Linkifier
.DefaultFormatter = function(maxLength
)
332 this._maxLength
= maxLength
;
335 WebInspector
.Linkifier
.DefaultFormatter
.prototype = {
338 * @param {!Element} anchor
339 * @param {!WebInspector.UILocation} uiLocation
341 formatLiveAnchor: function(anchor
, uiLocation
)
343 var text
= uiLocation
.linkText();
345 text
= text
.trimMiddle(this._maxLength
);
346 anchor
.textContent
= text
;
348 var titleText
= uiLocation
.uiSourceCode
.originURL();
349 if (typeof uiLocation
.lineNumber
=== "number")
350 titleText
+= ":" + (uiLocation
.lineNumber
+ 1);
351 anchor
.title
= titleText
;
357 * @extends {WebInspector.Linkifier.DefaultFormatter}
359 WebInspector
.Linkifier
.DefaultCSSFormatter = function()
361 WebInspector
.Linkifier
.DefaultFormatter
.call(this, WebInspector
.Linkifier
.DefaultCSSFormatter
.MaxLengthForDisplayedURLs
);
364 WebInspector
.Linkifier
.DefaultCSSFormatter
.MaxLengthForDisplayedURLs
= 30;
366 WebInspector
.Linkifier
.DefaultCSSFormatter
.prototype = {
369 * @param {!Element} anchor
370 * @param {!WebInspector.UILocation} uiLocation
372 formatLiveAnchor: function(anchor
, uiLocation
)
374 WebInspector
.Linkifier
.DefaultFormatter
.prototype.formatLiveAnchor
.call(this, anchor
, uiLocation
);
375 anchor
.classList
.add("webkit-html-resource-link");
376 anchor
.setAttribute("data-uncopyable", anchor
.textContent
);
377 anchor
.textContent
= "";
379 __proto__
: WebInspector
.Linkifier
.DefaultFormatter
.prototype
383 * The maximum number of characters to display in a URL.
387 WebInspector
.Linkifier
.MaxLengthForDisplayedURLs
= 150;
392 WebInspector
.Linkifier
.LinkHandler = function()
396 WebInspector
.Linkifier
.LinkHandler
.prototype = {
398 * @param {string} url
399 * @param {number=} lineNumber
402 handleLink: function(url
, lineNumber
) {}
406 * @param {!WebInspector.Target} target
407 * @param {string} scriptId
408 * @param {number} lineNumber
409 * @param {number=} columnNumber
412 WebInspector
.Linkifier
.liveLocationText = function(target
, scriptId
, lineNumber
, columnNumber
)
414 var debuggerModel
= WebInspector
.DebuggerModel
.fromTarget(target
);
417 var script
= debuggerModel
.scriptForId(scriptId
);
420 var location
= /** @type {!WebInspector.DebuggerModel.Location} */ (debuggerModel
.createRawLocation(script
, lineNumber
, columnNumber
|| 0));
421 var uiLocation
= /** @type {!WebInspector.UILocation} */ (WebInspector
.debuggerWorkspaceBinding
.rawLocationToUILocation(location
));
422 return uiLocation
.linkText();
426 * @param {string} string
427 * @param {function(string,string,number=,number=):!Node} linkifier
428 * @return {!DocumentFragment}
430 WebInspector
.linkifyStringAsFragmentWithCustomLinkifier = function(string
, linkifier
)
432 var container
= createDocumentFragment();
433 var linkStringRegEx
= /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|data:|www\.)[\w$\-_+*'=\|\/\\(){}[\]^%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({^%@&#~]/;
436 var linkString
= linkStringRegEx
.exec(string
);
440 linkString
= linkString
[0];
441 var linkIndex
= string
.indexOf(linkString
);
442 var nonLink
= string
.substring(0, linkIndex
);
443 container
.appendChild(createTextNode(nonLink
));
445 var title
= linkString
;
446 var realURL
= (linkString
.startsWith("www.") ? "http://" + linkString
: linkString
);
447 var splitResult
= WebInspector
.ParsedURL
.splitLineAndColumn(realURL
);
450 linkNode
= linkifier(title
, splitResult
.url
, splitResult
.lineNumber
, splitResult
.columnNumber
);
452 linkNode
= linkifier(title
, realURL
);
454 container
.appendChild(linkNode
);
455 string
= string
.substring(linkIndex
+ linkString
.length
, string
.length
);
459 container
.appendChild(createTextNode(string
));
465 * @param {string} string
466 * @return {!DocumentFragment}
468 WebInspector
.linkifyStringAsFragment = function(string
)
471 * @param {string} title
472 * @param {string} url
473 * @param {number=} lineNumber
474 * @param {number=} columnNumber
477 function linkifier(title
, url
, lineNumber
, columnNumber
)
479 var isExternal
= !WebInspector
.resourceForURL(url
) && !WebInspector
.networkMapping
.uiSourceCodeForURLForAnyTarget(url
);
480 var urlNode
= WebInspector
.linkifyURLAsNode(url
, title
, undefined, isExternal
);
481 if (typeof lineNumber
!== "undefined") {
482 urlNode
.lineNumber
= lineNumber
;
483 if (typeof columnNumber
!== "undefined")
484 urlNode
.columnNumber
= columnNumber
;
490 return WebInspector
.linkifyStringAsFragmentWithCustomLinkifier(string
, linkifier
);
494 * @param {string} url
495 * @param {string=} linkText
496 * @param {string=} classes
497 * @param {boolean=} isExternal
498 * @param {string=} tooltipText
501 WebInspector
.linkifyURLAsNode = function(url
, linkText
, classes
, isExternal
, tooltipText
)
505 classes
= (classes
? classes
+ " " : "");
506 classes
+= isExternal
? "webkit-html-external-link" : "webkit-html-resource-link";
508 var a
= createElement("a");
509 var href
= sanitizeHref(url
);
512 a
.className
= classes
;
513 if (!tooltipText
&& linkText
!== url
)
515 else if (tooltipText
)
516 a
.title
= tooltipText
;
517 a
.textContent
= linkText
.trimMiddle(WebInspector
.Linkifier
.MaxLengthForDisplayedURLs
);
519 a
.setAttribute("target", "_blank");
525 * @param {string} article
526 * @param {string} title
529 WebInspector
.linkifyDocumentationURLAsNode = function(article
, title
)
531 return WebInspector
.linkifyURLAsNode("https://developers.google.com/web/tools/" + article
, title
, undefined, true);
535 * @param {string} url
536 * @param {number=} lineNumber
537 * @param {string=} classes
538 * @param {string=} tooltipText
539 * @param {string=} urlDisplayName
542 WebInspector
.linkifyResourceAsNode = function(url
, lineNumber
, classes
, tooltipText
, urlDisplayName
)
544 var linkText
= urlDisplayName
? urlDisplayName
: url
? WebInspector
.displayNameForURL(url
) : WebInspector
.UIString("(program)");
545 if (typeof lineNumber
=== "number")
546 linkText
+= ":" + (lineNumber
+ 1);
547 var anchor
= WebInspector
.linkifyURLAsNode(url
, linkText
, classes
, false, tooltipText
);
548 anchor
.lineNumber
= lineNumber
;
553 * @param {!WebInspector.NetworkRequest} request
556 WebInspector
.linkifyRequestAsNode = function(request
)
558 var anchor
= WebInspector
.linkifyURLAsNode(request
.url
);
559 anchor
.requestId
= request
.requestId
;