Backout 66f4df0b1cb8 because the commit message got mangled; r=me
☠☠ backed out by 389e16afcbde ☠ ☠
authorNick Fitzgerald <fitzgen@gmail.com>
Fri, 20 Sep 2013 14:48:48 -0700
changeset 148617 951090191c431bceb89dcd7656bc4e9248d4fcaa
parent 148616 66f4df0b1cb845046b8d669e51b1ef5996107756
child 148618 389e16afcbde9c79da69878b139381da69217ea2
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersme
milestone27.0a1
backs out66f4df0b1cb845046b8d669e51b1ef5996107756
Backout 66f4df0b1cb8 because the commit message got mangled; r=me
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
js/src/jit-test/tests/debug/Source-displayURL-deprecated.js
js/src/jit-test/tests/debug/Source-displayURL.js
js/src/js.msg
js/src/jsscript.cpp
js/src/jsscript.h
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -35,16 +35,26 @@ CheckLength(ExclusiveContext *cx, size_t
         if (cx->isJSContext())
             JS_ReportErrorNumber(cx->asJSContext(), js_GetErrorMessage, NULL, JSMSG_SOURCE_TOO_LONG);
         return false;
     }
     return true;
 }
 
 static bool
+SetSourceURL(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss)
+{
+    if (tokenStream.hasSourceURL()) {
+        if (!ss->setSourceURL(cx, tokenStream.sourceURL()))
+            return false;
+    }
+    return true;
+}
+
+static bool
 SetSourceMap(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss)
 {
     if (tokenStream.hasSourceMapURL()) {
         if (!ss->setSourceMapURL(cx, tokenStream.sourceMapURL()))
             return false;
     }
     return true;
 }
@@ -352,16 +362,19 @@ frontend::CompileScript(ExclusiveContext
             return NULL;
 
         parser.handler.freeTree(pn);
     }
 
     if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref()))
         return NULL;
 
+    if (!SetSourceURL(cx, parser.tokenStream, ss))
+        return NULL;
+
     if (!SetSourceMap(cx, parser.tokenStream, ss))
         return NULL;
 
     /*
      * Source map URLs passed as a compile option (usually via a HTTP source map
      * header) override any source map urls passed as comment pragmas.
      */
     if (options.sourceMapURL) {
@@ -573,16 +586,19 @@ CompileFunctionBody(JSContext *cx, Mutab
 
         if (!EmitFunctionScript(cx, &funbce, fn->pn_body))
             return false;
     } else {
         fun.set(fn->pn_funbox->function());
         JS_ASSERT(IsAsmJSModuleNative(fun->native()));
     }
 
+    if (!SetSourceURL(cx, parser.tokenStream, ss))
+        return false;
+
     if (!SetSourceMap(cx, parser.tokenStream, ss))
         return false;
 
     if (!sct.complete())
         return false;
 
     return true;
 }
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -269,16 +269,17 @@ TokenStream::TokenStream(ExclusiveContex
     cursor(),
     lookahead(),
     lineno(options.lineno),
     flags(),
     linebase(base - options.column),
     prevLinebase(NULL),
     userbuf(cx, base - options.column, length + options.column), // See comment below
     filename(options.filename),
+    sourceURL_(NULL),
     sourceMapURL_(NULL),
     tokenbuf(cx),
     cx(cx),
     originPrincipals(options.originPrincipals()),
     strictModeGetter(smg),
     tokenSkip(cx, &tokens),
     linebaseSkip(cx, &linebase),
     prevLinebaseSkip(cx, &prevLinebase)
@@ -328,16 +329,17 @@ TokenStream::TokenStream(ExclusiveContex
 }
 
 #ifdef _MSC_VER
 #pragma warning(pop)
 #endif
 
 TokenStream::~TokenStream()
 {
+    js_free(sourceURL_);
     js_free(sourceMapURL_);
 
     JS_ASSERT_IF(originPrincipals, originPrincipals->refcount);
 }
 
 // Use the fastest available getc.
 #if defined(HAVE_GETC_UNLOCKED)
 # define fast_getc getc_unlocked
@@ -802,66 +804,101 @@ CharsMatch(const jschar *p, const char *
     while (*q) {
         if (*p++ != *q++)
             return false;
     }
     return true;
 }
 
 bool
-TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated)
+TokenStream::getDirectives(bool isMultiline, bool shouldWarnDeprecated)
 {
-    // Match comments of the form "//# sourceMappingURL=<url>" or
-    // "/\* //# sourceMappingURL=<url> *\/"
+    // Match directive comments used in debugging, such as "//# sourceURL" and
+    // "//# sourceMappingURL". Use of "//@" instead of "//#" is deprecated.
     //
     // To avoid a crashing bug in IE, several JavaScript transpilers wrap single
     // line comments containing a source mapping URL inside a multiline
     // comment. To avoid potentially expensive lookahead and backtracking, we
     // only check for this case if we encounter a '#' character.
+
+    if (!getSourceURL(isMultiline, shouldWarnDeprecated))
+        return false;
+    if (!getSourceMappingURL(isMultiline, shouldWarnDeprecated))
+        return false;
+
+    return true;
+}
+
+bool
+TokenStream::getDirective(bool isMultiline, bool shouldWarnDeprecated,
+                          const char *directive, int directiveLength,
+                          const char *errorMsgPragma, jschar **destination) {
+    JS_ASSERT(directiveLength <= 18);
     jschar peeked[18];
     int32_t c;
 
-    if (peekChars(18, peeked) && CharsMatch(peeked, " sourceMappingURL=")) {
-        if (shouldWarnDeprecated && !reportWarning(JSMSG_DEPRECATED_SOURCE_MAP)) {
+    if (peekChars(directiveLength, peeked) && CharsMatch(peeked, directive)) {
+        if (shouldWarnDeprecated &&
+            !reportWarning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma))
             return false;
-        }
 
-        skipChars(18);
+        skipChars(directiveLength);
         tokenbuf.clear();
 
         while ((c = peekChar()) && c != EOF && !IsSpaceOrBOM2(c)) {
             getChar();
-            // Source mapping URLs can occur in both single- and multiline
-            // comments. If we're currently inside a multiline comment, we also
-            // need to recognize multiline comment terminators.
+            // Debugging directives can occur in both single- and multi-line
+            // comments. If we're currently inside a multi-line comment, we also
+            // need to recognize multi-line comment terminators.
             if (isMultiline && c == '*' && peekChar() == '/') {
                 ungetChar('*');
                 break;
             }
             tokenbuf.append(c);
         }
 
         if (tokenbuf.empty())
-            // The source map's URL was missing, but not quite an exception that
-            // we should stop and drop everything for, though.
+            // The directive's URL was missing, but this is not quite an
+            // exception that we should stop and drop everything for.
             return true;
 
-        size_t sourceMapURLLength = tokenbuf.length();
+        size_t length = tokenbuf.length();
 
-        js_free(sourceMapURL_);
-        sourceMapURL_ = cx->pod_malloc<jschar>(sourceMapURLLength + 1);
-        if (!sourceMapURL_)
+        js_free(*destination);
+        *destination = cx->pod_malloc<jschar>(length + 1);
+        if (!*destination)
             return false;
 
-        PodCopy(sourceMapURL_, tokenbuf.begin(), sourceMapURLLength);
-        sourceMapURL_[sourceMapURLLength] = '\0';
+        PodCopy(*destination, tokenbuf.begin(), length);
+        (*destination)[length] = '\0';
     }
+
     return true;
 }
 
+bool
+TokenStream::getSourceURL(bool isMultiline, bool shouldWarnDeprecated)
+{
+    // Match comments of the form "//# sourceURL=<url>" or
+    // "/\* //# sourceURL=<url> *\/"
+
+    return getDirective(isMultiline, shouldWarnDeprecated, " sourceURL=", 11,
+                        "sourceURL", &sourceURL_);
+}
+
+bool
+TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated)
+{
+    // Match comments of the form "//# sourceMappingURL=<url>" or
+    // "/\* //# sourceMappingURL=<url> *\/"
+
+    return getDirective(isMultiline, shouldWarnDeprecated, " sourceMappingURL=", 18,
+                        "sourceMappingURL", &sourceMapURL_);
+}
+
 JS_ALWAYS_INLINE Token *
 TokenStream::newToken(ptrdiff_t adjust)
 {
     cursor = (cursor + 1) & ntokensMask;
     Token *tp = &tokens[cursor];
     tp->pos.begin = userbuf.addressOfNextRawChar() + adjust - userbuf.base();
 
     // NOTE: tp->pos.end is not set until the very end of getTokenInternal().
@@ -1501,17 +1538,18 @@ TokenStream::getTokenInternal(Modifier m
         tp->type = matchChar('=') ? TOK_MULASSIGN : TOK_MUL;
         goto out;
 
       case '/':
         // Look for a single-line comment.
         if (matchChar('/')) {
             c = peekChar();
             if (c == '@' || c == '#') {
-                if (!getSourceMappingURL(false, getChar() == '@'))
+                bool shouldWarn = getChar() == '@';
+                if (!getDirectives(false, shouldWarn))
                     goto error;
             }
 
         skipline:
             while ((c = getChar()) != EOF && c != '\n')
                 continue;
             ungetChar(c);
             cursor = (cursor - 1) & ntokensMask;
@@ -1519,17 +1557,18 @@ TokenStream::getTokenInternal(Modifier m
         }
 
         // Look for a multi-line comment.
         if (matchChar('*')) {
             unsigned linenoBefore = lineno;
             while ((c = getChar()) != EOF &&
                    !(c == '*' && matchChar('/'))) {
                 if (c == '@' || c == '#') {
-                    if (!getSourceMappingURL(true, c == '@'))
+                    bool shouldWarn = c == '@';
+                    if (!getDirectives(true, shouldWarn))
                         goto error;
                 }
             }
             if (c == EOF) {
                 reportError(JSMSG_UNTERMINATED_COMMENT);
                 goto error;
             }
             if (linenoBefore != lineno)
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -591,16 +591,24 @@ class MOZ_STACK_CLASS TokenStream
     void tell(Position *);
     void seek(const Position &pos);
     void seek(const Position &pos, const TokenStream &other);
 
     size_t positionToOffset(const Position &pos) const {
         return pos.buf - userbuf.base();
     }
 
+    bool hasSourceURL() const {
+        return sourceURL_ != NULL;
+    }
+
+    jschar *sourceURL() {
+        return sourceURL_;
+    }
+
     bool hasSourceMapURL() const {
         return sourceMapURL_ != NULL;
     }
 
     jschar *sourceMapURL() {
         return sourceMapURL_;
     }
 
@@ -798,16 +806,22 @@ class MOZ_STACK_CLASS TokenStream
     int32_t getCharIgnoreEOL();
     void ungetChar(int32_t c);
     void ungetCharIgnoreEOL(int32_t c);
     Token *newToken(ptrdiff_t adjust);
     bool peekUnicodeEscape(int32_t *c);
     bool matchUnicodeEscapeIdStart(int32_t *c);
     bool matchUnicodeEscapeIdent(int32_t *c);
     bool peekChars(int n, jschar *cp);
+
+    bool getDirectives(bool isMultiline, bool shouldWarnDeprecated);
+    bool getDirective(bool isMultiline, bool shouldWarnDeprecated,
+                      const char *directive, int directiveLength,
+                      const char *errorMsgPragma, jschar **destination);
+    bool getSourceURL(bool isMultiline, bool shouldWarnDeprecated);
     bool getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated);
 
     // |expect| cannot be an EOL char.
     bool matchChar(int32_t expect) {
         MOZ_ASSERT(!TokenBuf::isRawEOLChar(expect));
         return JS_LIKELY(userbuf.hasRawChars()) &&
                userbuf.matchRawChar(expect);
     }
@@ -838,16 +852,17 @@ class MOZ_STACK_CLASS TokenStream
     unsigned            cursor;             // index of last parsed token
     unsigned            lookahead;          // count of lookahead tokens
     unsigned            lineno;             // current line number
     Flags               flags;              // flags -- see above
     const jschar        *linebase;          // start of current line;  points into userbuf
     const jschar        *prevLinebase;      // start of previous line;  NULL if on the first line
     TokenBuf            userbuf;            // user input buffer
     const char          *filename;          // input filename or null
+    jschar              *sourceURL_;        // the user's requested source URL or null
     jschar              *sourceMapURL_;     // source map's filename or null
     CharBuffer          tokenbuf;           // current token string buffer
     bool                maybeEOL[256];      // probabilistic EOL lookup table
     bool                maybeStrSpecial[256];   // speeds up string scanning
     uint8_t             isExprEnding[TOK_LIMIT];// which tokens definitely terminate exprs?
     ExclusiveContext    *const cx;
     JSPrincipals        *const originPrincipals;
     StrictModeGetter    *strictModeGetter;  // used to test for strict mode
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-displayURL-deprecated.js
@@ -0,0 +1,26 @@
+/* -*- Mode: javascript; js-indent-level: 4; -*- */
+// Source.prototype.sourceURL can be a string or null.
+
+let g = newGlobal('new-compartment');
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+function getDisplayURL() {
+    let fw = gw.makeDebuggeeValue(g.f);
+    return fw.script.source.displayURL;
+}
+
+// Comment pragmas
+g.evaluate('function f() {}\n' +
+           '//@ sourceURL=file:///var/quux.js');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+g.evaluate('function f() {}\n' +
+           '/*//@ sourceURL=file:///var/quux.js*/');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+g.evaluate('function f() {}\n' +
+           '/*\n' +
+           '//@ sourceURL=file:///var/quux.js\n' +
+           '*/');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-displayURL.js
@@ -0,0 +1,71 @@
+/* -*- Mode: javascript; js-indent-level: 4; -*- */
+// Source.prototype.sourceURL can be a string or null.
+
+let g = newGlobal('new-compartment');
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+function getDisplayURL() {
+    let fw = gw.makeDebuggeeValue(g.f);
+    return fw.script.source.displayURL;
+}
+
+// Without a source url
+g.evaluate("function f(x) { return 2*x; }");
+assertEq(getDisplayURL(), null);
+
+// With a source url
+g.evaluate("function f(x) { return 2*x; }", {sourceURL: 'file:///var/foo.js'});
+assertEq(getDisplayURL(), 'file:///var/foo.js');
+
+// Nested functions
+let fired = false;
+dbg.onDebuggerStatement = function (frame) {
+    fired = true;
+    assertEq(frame.script.source.displayURL, 'file:///var/bar.js');
+};
+g.evaluate('(function () { (function () { debugger; })(); })();',
+           {sourceURL: 'file:///var/bar.js'});
+assertEq(fired, true);
+
+// Comment pragmas
+g.evaluate('function f() {}\n' +
+           '//# sourceURL=file:///var/quux.js');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+g.evaluate('function f() {}\n' +
+           '/*//# sourceURL=file:///var/quux.js*/');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+g.evaluate('function f() {}\n' +
+           '/*\n' +
+           '//# sourceURL=file:///var/quux.js\n' +
+           '*/');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+// Spaces are disallowed by the URL spec (they should have been
+// percent-encoded).
+g.evaluate('function f() {}\n' +
+           '//# sourceURL=http://example.com/has illegal spaces');
+assertEq(getDisplayURL(), 'http://example.com/has');
+
+// When the URL is missing, we don't set the sourceMapURL and we don't skip the
+// next line of input.
+g.evaluate('function f() {}\n' +
+           '//# sourceURL=\n' +
+           'function z() {}');
+assertEq(getDisplayURL(), null);
+assertEq('z' in g, true);
+
+// The last comment pragma we see should be the one which sets the displayURL.
+g.evaluate('function f() {}\n' +
+           '//# sourceURL=http://example.com/foo.js\n' +
+           '//# sourceURL=http://example.com/bar.js');
+assertEq(getDisplayURL(), 'http://example.com/bar.js');
+
+// With both a comment and the evaluate option.
+g.evaluate('function f() {}\n' +
+           '//# sourceURL=http://example.com/foo.js',
+           {sourceMapURL: 'http://example.com/bar.js'});
+assertEq(getDisplayURL(), 'http://example.com/foo.js');
+
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -348,17 +348,17 @@ MSG_DEF(JSMSG_DEBUG_VARIABLE_NOT_FOUND, 
 MSG_DEF(JSMSG_PARAMETER_AFTER_REST,   295, 0, JSEXN_SYNTAXERR, "parameter after rest parameter")
 MSG_DEF(JSMSG_NO_REST_NAME,           296, 0, JSEXN_SYNTAXERR, "no parameter name after ...")
 MSG_DEF(JSMSG_ARGUMENTS_AND_REST,     297, 0, JSEXN_SYNTAXERR, "'arguments' object may not be used in conjunction with a rest parameter")
 MSG_DEF(JSMSG_FUNCTION_ARGUMENTS_AND_REST, 298, 0, JSEXN_ERR, "the 'arguments' property of a function with a rest parameter may not be used")
 MSG_DEF(JSMSG_REST_WITH_DEFAULT,      299, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default")
 MSG_DEF(JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT, 300, 0, JSEXN_SYNTAXERR, "parameter(s) with default followed by parameter without default")
 MSG_DEF(JSMSG_YIELD_IN_DEFAULT,       301, 0, JSEXN_SYNTAXERR, "yield in default expression")
 MSG_DEF(JSMSG_INTRINSIC_NOT_DEFINED,  302, 1, JSEXN_REFERENCEERR, "no intrinsic function {0}")
-MSG_DEF(JSMSG_ALREADY_HAS_SOURCE_MAP_URL, 303, 1, JSEXN_ERR,      "{0} is being assigned a source map URL, but already has one")
+MSG_DEF(JSMSG_ALREADY_HAS_PRAGMA, 303, 2, JSEXN_ERR,      "{0} is being assigned a {1}, but already has one")
 MSG_DEF(JSMSG_PAR_ARRAY_BAD_ARG,      304, 1, JSEXN_RANGEERR, "invalid ParallelArray{0} argument")
 MSG_DEF(JSMSG_PAR_ARRAY_BAD_PARTITION, 305, 0, JSEXN_ERR, "argument must be divisible by outermost dimension")
 MSG_DEF(JSMSG_PAR_ARRAY_REDUCE_EMPTY, 306, 0, JSEXN_ERR, "cannot reduce ParallelArray object whose outermost dimension is empty")
 MSG_DEF(JSMSG_PAR_ARRAY_ALREADY_FLAT, 307, 0, JSEXN_ERR, "cannot flatten 1-dimensional ParallelArray object")
 MSG_DEF(JSMSG_PAR_ARRAY_SCATTER_CONFLICT, 308, 0, JSEXN_ERR, "no conflict resolution function provided")
 MSG_DEF(JSMSG_PAR_ARRAY_SCATTER_BOUNDS, 309, 0, JSEXN_ERR, "index in scatter vector out of bounds")
 MSG_DEF(JSMSG_CANT_REPORT_NC_AS_NE,   310, 0, JSEXN_TYPEERR, "proxy can't report a non-configurable own property as non-existent")
 MSG_DEF(JSMSG_CANT_REPORT_E_AS_NE,    311, 0, JSEXN_TYPEERR, "proxy can't report an existing own property as non-existent on a non-extensible object")
@@ -395,17 +395,17 @@ MSG_DEF(JSMSG_USE_ASM_DIRECTIVE_FAIL, 34
 MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL,      342, 1, JSEXN_TYPEERR, "asm.js type error: {0}")
 MSG_DEF(JSMSG_USE_ASM_LINK_FAIL,      343, 1, JSEXN_TYPEERR, "asm.js link error: {0}")
 MSG_DEF(JSMSG_USE_ASM_TYPE_OK,        344, 1, JSEXN_ERR,     "successfully compiled asm.js code ({0})")
 MSG_DEF(JSMSG_BAD_ARROW_ARGS,         345, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)")
 MSG_DEF(JSMSG_YIELD_IN_ARROW,         346, 0, JSEXN_SYNTAXERR, "arrow function may not contain yield")
 MSG_DEF(JSMSG_WRONG_VALUE,            347, 2, JSEXN_ERR, "expected {0} but found {1}")
 MSG_DEF(JSMSG_PAR_ARRAY_SCATTER_BAD_TARGET, 348, 1, JSEXN_ERR, "target for index {0} is not an integer")
 MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME,349, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups")
-MSG_DEF(JSMSG_DEPRECATED_SOURCE_MAP,  350, 0, JSEXN_SYNTAXERR, "Using //@ to indicate source map URL pragmas is deprecated. Use //# instead")
+MSG_DEF(JSMSG_DEPRECATED_PRAGMA,  350, 1, JSEXN_SYNTAXERR, "Using //@ to indicate {0} pragmas is deprecated. Use //# instead")
 MSG_DEF(JSMSG_BAD_DESTRUCT_ASSIGN,    351, 1, JSEXN_SYNTAXERR, "can't assign to {0} using destructuring assignment")
 MSG_DEF(JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS, 352, 0, JSEXN_ERR, "Invalid arguments")
 MSG_DEF(JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX, 353, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
 MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS, 354, 0, JSEXN_RANGEERR, "invalid field descriptor")
 MSG_DEF(JSMSG_TYPEDOBJECT_NOT_BINARYSTRUCT,   355, 1, JSEXN_TYPEERR, "{0} is not a BinaryStruct")
 MSG_DEF(JSMSG_TYPEDOBJECT_SUBARRAY_INTEGER_ARG, 356, 1, JSEXN_ERR, "argument {0} must be an integer")
 MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_EMPTY_DESCRIPTOR, 357, 0, JSEXN_ERR, "field descriptor cannot be empty")
 MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_FIELD, 358, 1, JSEXN_ERR, "field {0} is not a valid BinaryData Type descriptor")
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1203,16 +1203,17 @@ SourceCompressionTask::compress()
 }
 
 void
 ScriptSource::destroy()
 {
     JS_ASSERT(ready());
     adjustDataSize(0);
     js_free(filename_);
+    js_free(sourceURL_);
     js_free(sourceMapURL_);
     if (originPrincipals_)
         JS_DropPrincipals(TlsPerThreadData.get()->runtimeFromMainThread(), originPrincipals_);
     ready_ = false;
     js_free(this);
 }
 
 size_t
@@ -1293,16 +1294,41 @@ ScriptSource::performXDR(XDRState<mode> 
                 js_free(sourceMapURL_);
                 sourceMapURL_ = NULL;
             }
             return false;
         }
         sourceMapURL_[sourceMapURLLen] = '\0';
     }
 
+    uint8_t haveSourceURL = hasSourceURL();
+    if (!xdr->codeUint8(&haveSourceURL))
+        return false;
+
+    if (haveSourceURL) {
+        uint32_t sourceURLLen = (mode == XDR_DECODE) ? 0 : js_strlen(sourceURL_);
+        if (!xdr->codeUint32(&sourceURLLen))
+            return false;
+
+        if (mode == XDR_DECODE) {
+            size_t byteLen = (sourceURLLen + 1) * sizeof(jschar);
+            sourceURL_ = static_cast<jschar *>(xdr->cx()->malloc_(byteLen));
+            if (!sourceURL_)
+                return false;
+        }
+        if (!xdr->codeChars(sourceURL_, sourceURLLen)) {
+            if (mode == XDR_DECODE) {
+                js_free(sourceURL_);
+                sourceURL_ = NULL;
+            }
+            return false;
+        }
+        sourceURL_[sourceURLLen] = '\0';
+    }
+
     uint8_t haveFilename = !!filename_;
     if (!xdr->codeUint8(&haveFilename))
         return false;
 
     if (haveFilename) {
         const char *fn = filename();
         if (!xdr->codeCString(&fn))
             return false;
@@ -1326,30 +1352,62 @@ ScriptSource::setFilename(ExclusiveConte
     filename_ = cx->pod_malloc<char>(len);
     if (!filename_)
         return false;
     js_memcpy(filename_, filename, len);
     return true;
 }
 
 bool
+ScriptSource::setSourceURL(ExclusiveContext *cx, const jschar *sourceURL)
+{
+    JS_ASSERT(sourceURL);
+    if (hasSourceURL()) {
+        if (cx->isJSContext() &&
+            !JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING,
+                                          js_GetErrorMessage, NULL,
+                                          JSMSG_ALREADY_HAS_PRAGMA, filename_,
+                                          "//# sourceURL"))
+        {
+            return false;
+        }
+    }
+    size_t len = js_strlen(sourceURL) + 1;
+    if (len == 1)
+        return true;
+    sourceURL_ = js_strdup(cx, sourceURL);
+    if (!sourceURL_)
+        return false;
+    return true;
+}
+
+const jschar *
+ScriptSource::sourceURL()
+{
+    JS_ASSERT(hasSourceURL());
+    return sourceURL_;
+}
+
+bool
 ScriptSource::setSourceMapURL(ExclusiveContext *cx, const jschar *sourceMapURL)
 {
     JS_ASSERT(sourceMapURL);
     if (hasSourceMapURL()) {
         if (cx->isJSContext() &&
-            !JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING, js_GetErrorMessage,
-                                          NULL, JSMSG_ALREADY_HAS_SOURCE_MAP_URL, filename_))
+            !JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING,
+                                          js_GetErrorMessage, NULL,
+                                          JSMSG_ALREADY_HAS_PRAGMA, filename_,
+                                          "//# sourceMappingURL"))
         {
             return false;
         }
     }
 
     size_t len = js_strlen(sourceMapURL) + 1;
-    if (len == 1) 
+    if (len == 1)
         return true;
     sourceMapURL_ = js_strdup(cx, sourceMapURL);
     if (!sourceMapURL_)
         return false;
     return true;
 }
 
 const jschar *
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -299,32 +299,34 @@ class ScriptSource
         // this union is adjustDataSize(). Don't do it elsewhere.
         jschar *source;
         unsigned char *compressed;
     } data;
     uint32_t refs;
     uint32_t length_;
     uint32_t compressedLength_;
     char *filename_;
+    jschar *sourceURL_;
     jschar *sourceMapURL_;
     JSPrincipals *originPrincipals_;
 
     // True if we can call JSRuntime::sourceHook to load the source on
     // demand. If sourceRetrievable_ and hasSourceData() are false, it is not
     // possible to get source at all.
     bool sourceRetrievable_:1;
     bool argumentsNotIncluded_:1;
     bool ready_:1;
 
   public:
     ScriptSource(JSPrincipals *originPrincipals)
       : refs(0),
         length_(0),
         compressedLength_(0),
         filename_(NULL),
+        sourceURL_(NULL),
         sourceMapURL_(NULL),
         originPrincipals_(originPrincipals),
         sourceRetrievable_(false),
         argumentsNotIncluded_(false),
         ready_(true)
     {
         data.source = NULL;
         if (originPrincipals_)
@@ -362,16 +364,21 @@ class ScriptSource
     template <XDRMode mode>
     bool performXDR(XDRState<mode> *xdr);
 
     bool setFilename(ExclusiveContext *cx, const char *filename);
     const char *filename() const {
         return filename_;
     }
 
+    // Source URLs
+    bool setSourceURL(ExclusiveContext *cx, const jschar *sourceURL);
+    const jschar *sourceURL();
+    bool hasSourceURL() const { return sourceURL_ != NULL; }
+
     // Source maps
     bool setSourceMapURL(ExclusiveContext *cx, const jschar *sourceMapURL);
     const jschar *sourceMapURL();
     bool hasSourceMapURL() const { return sourceMapURL_ != NULL; }
 
     JSPrincipals *originPrincipals() const { return originPrincipals_; }
 
   private:
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -901,16 +901,17 @@ Evaluate(JSContext *cx, unsigned argc, j
     }
 
     bool newContext = false;
     bool compileAndGo = true;
     bool noScriptRval = false;
     const char *fileName = "@evaluate";
     RootedObject element(cx);
     JSAutoByteString fileNameBytes;
+    RootedString sourceURL(cx);
     RootedString sourceMapURL(cx);
     unsigned lineNumber = 1;
     RootedObject global(cx, NULL);
     bool catchTermination = false;
     bool saveFrameChain = false;
     RootedObject callerGlobal(cx, cx->global());
 
     global = JS_GetGlobalForObject(cx, &args.callee());
@@ -961,16 +962,24 @@ Evaluate(JSContext *cx, unsigned argc, j
                 return false;
         }
 
         if (!JS_GetProperty(cx, opts, "element", &v))
             return false;
         if (!JSVAL_IS_PRIMITIVE(v))
             element = JSVAL_TO_OBJECT(v);
 
+        if (!JS_GetProperty(cx, opts, "sourceURL", &v))
+            return false;
+        if (!JSVAL_IS_VOID(v)) {
+            sourceURL = JS_ValueToString(cx, v);
+            if (!sourceURL)
+                return false;
+        }
+
         if (!JS_GetProperty(cx, opts, "sourceMapURL", &v))
             return false;
         if (!JSVAL_IS_VOID(v)) {
             sourceMapURL = JS_ValueToString(cx, v);
             if (!sourceMapURL)
                 return false;
         }
 
@@ -1049,16 +1058,23 @@ Evaluate(JSContext *cx, unsigned argc, j
         CompileOptions options(cx);
         options.setFileAndLine(fileName, lineNumber);
         options.setElement(element);
         RootedScript script(cx, JS::Compile(cx, global, options, codeChars, codeLength));
         JS_SetOptions(cx, oldopts);
         if (!script)
             return false;
 
+        if (sourceURL && !script->scriptSource()->hasSourceURL()) {
+            const jschar *surl = JS_GetStringCharsZ(cx, sourceURL);
+            if (!surl)
+                return false;
+            if (!script->scriptSource()->setSourceURL(cx, surl))
+                return false;
+        }
         if (sourceMapURL && !script->scriptSource()->hasSourceMapURL()) {
             const jschar *smurl = JS_GetStringCharsZ(cx, sourceMapURL);
             if (!smurl)
                 return false;
             if (!script->scriptSource()->setSourceMapURL(cx, smurl))
                 return false;
         }
         if (!JS_ExecuteScript(cx, global, script, vp)) {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3748,19 +3748,40 @@ DebuggerSource_getUrl(JSContext *cx, uns
             return false;
         args.rval().setString(str);
     } else {
         args.rval().setNull();
     }
     return true;
 }
 
+static bool
+DebuggerSource_getDisplayURL(JSContext *cx, unsigned argc, Value *vp)
+{
+    THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject);
+
+    ScriptSource *ss = sourceObject->source();
+    JS_ASSERT(ss);
+
+    if (ss->hasSourceURL()) {
+        JSString *str = JS_NewUCStringCopyZ(cx, ss->sourceURL());
+        if (!str)
+            return false;
+        args.rval().setString(str);
+    } else {
+        args.rval().setNull();
+    }
+
+    return true;
+}
+
 static const JSPropertySpec DebuggerSource_properties[] = {
     JS_PSG("text", DebuggerSource_getText, 0),
     JS_PSG("url", DebuggerSource_getUrl, 0),
+    JS_PSG("displayURL", DebuggerSource_getDisplayURL, 0),
     JS_PS_END
 };
 
 static const JSFunctionSpec DebuggerSource_methods[] = {
     JS_FS_END
 };