Bug 1399911 - preserve sourceURL comment directive on style sheets; r=bz,heycam

In addition to the sourceMappingURL comment, there is a second special
comment, "sourceURL", that can be used to set the "display name" of a
style sheet for developer tools.  This name is also used as the base
URL for the source-map URL resolution algorithm.  sourceURL is
described here:
https://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/

This patch changes Firefox to record this URL, if specified, and to
expose it (chrome-only) vai StyleSheet.webidl.

MozReview-Commit-ID: 7NwXsOf7nbY

--HG--
extra : rebase_source : bd5d25b4d44f5f220a4624db346edbc4236c9886
This commit is contained in:
Tom Tromey 2017-09-14 14:59:32 -06:00
Родитель fcc8fcf709
Коммит 916228aee7
11 изменённых файлов: 120 добавлений и 36 удалений

Просмотреть файл

@ -39,4 +39,10 @@ interface StyleSheet {
// then this is an empty string.
[ChromeOnly, Pure]
readonly attribute DOMString sourceMapURL;
// The source URL for this style sheet. If the style sheet has the
// special "# sourceURL=" comment, then this is the URL specified
// there. If no such comment is found, then this is the empty
// string.
[ChromeOnly, Pure]
readonly attribute DOMString sourceURL;
};

Просмотреть файл

@ -68,6 +68,8 @@ SERVO_BINDING_FUNC(Servo_StyleSheet_SizeOfIncludingThis, size_t,
RawServoStyleSheetContentsBorrowed sheet)
SERVO_BINDING_FUNC(Servo_StyleSheet_GetSourceMapURL, void,
RawServoStyleSheetContentsBorrowed sheet, nsAString* result)
SERVO_BINDING_FUNC(Servo_StyleSheet_GetSourceURL, void,
RawServoStyleSheetContentsBorrowed sheet, nsAString* result)
// We'd like to return `OriginFlags` here, but bindgen bitfield enums don't
// work as return values with the Linux 32-bit ABI at the moment because
// they wrap the value in a struct.

Просмотреть файл

@ -225,6 +225,10 @@ ServoStyleSheet::ParseSheet(css::Loader* aLoader,
Servo_StyleSheet_GetSourceMapURL(Inner()->mContents, &sourceMapURL);
SetSourceMapURLFromComment(sourceMapURL);
nsString sourceURL;
Servo_StyleSheet_GetSourceURL(Inner()->mContents, &sourceURL);
SetSourceURL(sourceURL);
Inner()->mURLData = extraData.forget();
return NS_OK;
}

Просмотреть файл

@ -261,6 +261,7 @@ StyleSheetInfo::StyleSheetInfo(StyleSheetInfo& aCopy,
// without children.
, mSourceMapURL(aCopy.mSourceMapURL)
, mSourceMapURLFromComment(aCopy.mSourceMapURLFromComment)
, mSourceURL(aCopy.mSourceURL)
#ifdef DEBUG
, mPrincipalSet(aCopy.mPrincipalSet)
#endif
@ -530,6 +531,18 @@ StyleSheet::SetSourceMapURLFromComment(const nsAString& aSourceMapURLFromComment
mInner->mSourceMapURLFromComment = aSourceMapURLFromComment;
}
void
StyleSheet::GetSourceURL(nsAString& aSourceURL)
{
aSourceURL = mInner->mSourceURL;
}
void
StyleSheet::SetSourceURL(const nsAString& aSourceURL)
{
mInner->mSourceURL = aSourceURL;
}
css::Rule*
StyleSheet::GetDOMOwnerRule() const
{

Просмотреть файл

@ -216,6 +216,8 @@ public:
void GetSourceMapURL(nsAString& aTitle);
void SetSourceMapURL(const nsAString& aSourceMapURL);
void SetSourceMapURLFromComment(const nsAString& aSourceMapURLFromComment);
void GetSourceURL(nsAString& aSourceURL);
void SetSourceURL(const nsAString& aSourceURL);
// WebIDL CSSStyleSheet API
// Can't be inline because we can't include ImportRule here. And can't be

Просмотреть файл

@ -70,6 +70,9 @@ struct StyleSheetInfo
// so that the value does not overwrite any value that might have
// come from a response header.
nsString mSourceMapURLFromComment;
// This stores any source URL that might have been seen in a comment
// in the style sheet.
nsString mSourceURL;
#ifdef DEBUG
bool mPrincipalSet;

Просмотреть файл

@ -1648,6 +1648,7 @@ CSSParserImpl::ParseSheet(const nsAString& aInput,
}
mSheet->SetSourceMapURLFromComment(scanner.GetSourceMapURL());
mSheet->SetSourceURL(scanner.GetSourceURL());
ReleaseScanner();
mParsingMode = css::eAuthorSheetFeatures;

Просмотреть файл

@ -542,29 +542,52 @@ nsCSSScanner::SkipWhitespace()
}
}
/**
* If the given text appears at the current offset in the buffer,
* advance over the text and return true. Otherwise, return false.
* mLength is the number of characters in mDirective.
*/
bool
nsCSSScanner::CheckCommentDirective(const nsAString& aDirective)
{
nsDependentSubstring text(&mBuffer[mOffset], &mBuffer[mCount]);
if (StringBeginsWith(text, aDirective)) {
Advance(aDirective.Length());
return true;
}
return false;
}
/**
* Skip over one CSS comment starting at the current read position.
*/
void
nsCSSScanner::SkipComment()
{
static const char sourceMappingURLDirective[] = "# sourceMappingURL=";
// Note that these do not start with "#" or "@" -- that is handled
// separately, below.
static NS_NAMED_LITERAL_STRING(kSourceMappingURLDirective, " sourceMappingURL=");
static NS_NAMED_LITERAL_STRING(kSourceURLDirective, " sourceURL=");
MOZ_ASSERT(Peek() == '/' && Peek(1) == '*', "should not have been called");
Advance(2);
// Look in each comment for a source map directive; using a simple
// state machine. The states are:
// * sourceMapIndex >= 0 means that we're still looking for the
// directive and expect the next character to be at that index of
// sourceMappingURLDirective.
// As a special case, when sourceMapIndex == 0, '@' is also recognized.
// * sourceMapIndex < 0 means that we don't need to look for the
// directive any more -- whether it was found or not.
// * copying == true means that the directive was found and we're
// copying characters into mSourceMapURL. This stops at the first
// whitespace, or at the end of the comment.
int sourceMapIndex = 0;
bool copying = false;
// If we saw one of the directives, this will be non-NULL and will
// point to the string into which the URL will be written.
nsString* directive = nullptr;
if (Peek() == '#' || Peek() == '@') {
// Check for the comment directives.
Advance();
if (CheckCommentDirective(kSourceMappingURLDirective)) {
mSourceMapURL.Truncate();
directive = &mSourceMapURL;
} else if (CheckCommentDirective(kSourceURLDirective)) {
mSourceURL.Truncate();
directive = &mSourceURL;
}
}
for (;;) {
int32_t ch = Peek();
if (ch < 0) {
@ -573,22 +596,6 @@ nsCSSScanner::SkipComment()
SetEOFCharacters(eEOFCharacters_Asterisk | eEOFCharacters_Slash);
return;
}
if (sourceMapIndex >= 0) {
if ((sourceMapIndex == 0 && ch == '@') || ch == sourceMappingURLDirective[sourceMapIndex]) {
++sourceMapIndex;
if (sourceMappingURLDirective[sourceMapIndex] == '\0') {
sourceMapIndex = -1;
mSourceMapURL.Truncate();
copying = true;
Advance();
// Make sure we don't copy out the '=' by falling through.
continue;
}
} else {
// Did not see the directive.
sourceMapIndex = -1;
}
}
if (ch == '*') {
Advance();
@ -605,20 +612,20 @@ nsCSSScanner::SkipComment()
Advance();
return;
}
if (copying) {
mSourceMapURL.Append('*');
if (directive != nullptr) {
directive->Append('*');
}
} else if (IsVertSpace(ch)) {
AdvanceLine();
// Done with the directive, so stop copying.
copying = false;
directive = nullptr;
} else if (IsWhitespace(ch)) {
Advance();
// Done with the directive, so stop copying.
copying = false;
directive = nullptr;
} else {
if (copying) {
mSourceMapURL.Append(ch);
if (directive != nullptr) {
directive->Append(ch);
}
Advance();
}

Просмотреть файл

@ -235,6 +235,9 @@ class nsCSSScanner {
const nsAString& GetSourceMapURL() const
{ return mSourceMapURL; }
const nsAString& GetSourceURL() const
{ return mSourceURL; }
// Get the text of the line containing the first character of
// the most recently processed token.
nsDependentSubstring GetCurrentLine() const;
@ -330,6 +333,7 @@ protected:
void AdvanceLine();
void SkipWhitespace();
bool CheckCommentDirective(const nsAString& aDirective);
void SkipComment();
bool GatherEscape(nsString& aOutput, bool aInString);
@ -366,6 +370,7 @@ protected:
bool mSeenVariableReference;
nsString mSourceMapURL;
nsString mSourceURL;
};
// Token for the grid-template-areas micro-syntax

Просмотреть файл

@ -14,3 +14,4 @@ support-files =
skip-if = stylo # Gecko-specific test
[browser_sourcemap.js]
[browser_sourcemap_comment.js]
[browser_sourceurl_comment.js]

Просмотреть файл

@ -0,0 +1,40 @@
add_task(async function() {
// Test text and expected results.
let test_cases = [
["/*# sourceURL=here*/", "here"],
["/*# sourceURL=here */", "here"],
["/*@ sourceURL=here*/", "here"],
["/*@ sourceURL=there*/ /*# sourceURL=here*/", "here"],
["/*# sourceURL=here there */", "here"],
["/*# sourceURL= here */", ""],
["/*# sourceURL=*/", ""],
["/*# sourceUR=here */", ""],
["/*! sourceURL=here */", ""],
["/*# sourceURL = here */", ""],
["/* # sourceURL=here */", ""],
];
let page = "<!DOCTYPE HTML>\n<html>\n<head>\n";
for (let i = 0; i < test_cases.length; ++i) {
page += `<style type="text/css"> #x${i} { color: red; }${test_cases[i][0]}</style>\n`;
}
page += "</head><body>some text</body></html>";
let uri = "data:text/html;base64," + btoa(page);
info(`URI is ${uri}`);
await BrowserTestUtils.withNewTab({
gBrowser,
url: uri
}, async function(browser) {
await ContentTask.spawn(browser, test_cases, function* (tests) {
for (let i = 0; i < content.document.styleSheets.length; ++i) {
let sheet = content.document.styleSheets[i];
info(`Checking sheet #${i}`);
is(sheet.sourceURL, tests[i][1], `correct source URL for sheet ${i}`);
}
});
});
});