Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 26 Nov 2014 21:08:54 -0500
changeset 217700 c63e741bca2e3d27d57609fcc59ab474eacb6066
parent 217652 159a0ec99a6d614d8839184e81eeac93f9a5045e (current diff)
parent 217699 30de9d876213a98d0db99ae95784eb29322383ee (diff)
child 217745 cef590a6f94681064fa954890bae6014db819158
push id27887
push userryanvm@gmail.com
push dateThu, 27 Nov 2014 02:08:38 +0000
treeherdermozilla-central@c63e741bca2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c. a=merge
browser/app/profile/firefox.js
browser/devtools/fontinspector/test/browser_font.woff
mobile/android/base/resources/drawable-hdpi/shield.png
mobile/android/base/resources/drawable-hdpi/shield_doorhanger.png
mobile/android/base/resources/drawable-hdpi/warning.png
mobile/android/base/resources/drawable-hdpi/warning_doorhanger.png
mobile/android/base/resources/drawable-mdpi/shield.png
mobile/android/base/resources/drawable-mdpi/shield_doorhanger.png
mobile/android/base/resources/drawable-mdpi/warning.png
mobile/android/base/resources/drawable-mdpi/warning_doorhanger.png
mobile/android/base/resources/drawable-xhdpi/shield.png
mobile/android/base/resources/drawable-xhdpi/shield_doorhanger.png
mobile/android/base/resources/drawable-xhdpi/warning.png
mobile/android/base/resources/drawable-xhdpi/warning_doorhanger.png
modules/libpref/init/all.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1642,18 +1642,18 @@ pref("shumway.disabled", true);
 pref("image.mem.max_decoded_image_kb", 256000);
 
 pref("loop.enabled", true);
 pref("loop.server", "https://loop.services.mozilla.com/v0");
 pref("loop.seenToS", "unseen");
 pref("loop.gettingStarted.seen", false);
 pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start");
 pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
-pref("loop.legal.ToS_url", "https://hello.firefox.com/legal/terms/");
-pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/");
+pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
+pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
 pref("loop.do_not_disturb", false);
 pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/ringtone.ogg");
 pref("loop.retry_delay.start", 60000);
 pref("loop.retry_delay.limit", 300000);
 pref("loop.feedback.baseUrl", "https://input.mozilla.org/api/v1/feedback");
 pref("loop.feedback.product", "Loop");
 pref("loop.debug.loglevel", "Error");
 pref("loop.debug.dispatcher", false);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1094,16 +1094,17 @@
             if (listener && listener.mStateFlags) {
               this._callProgressListeners(null, "onUpdateCurrentBrowser",
                                           [listener.mStateFlags, listener.mStatus,
                                            listener.mMessage, listener.mTotalProgress],
                                           true, false);
             }
 
             if (!this._previewMode) {
+              this.mCurrentTab.lastAccessed = Infinity;
               this.mCurrentTab.removeAttribute("unread");
               oldTab.lastAccessed = Date.now();
 
               let oldFindBar = oldTab._findBar;
               if (oldFindBar &&
                   oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
                   !oldFindBar.hidden)
                 this._lastFindValue = oldFindBar._findField.value;
@@ -1633,16 +1634,17 @@
 
             var position = this.tabs.length - 1;
             var uniqueId = this._generateUniquePanelID();
             notificationbox.id = uniqueId;
             t.linkedPanel = uniqueId;
             t.linkedBrowser = b;
             this._tabForBrowser.set(b, t);
             t._tPos = position;
+            t.lastAccessed = Date.now();
             this.tabContainer._setPositionalAttributes();
 
             // Prevent the superfluous initial load of a blank document
             // if we're going to load something other than about:blank.
             if (!uriIsAboutBlank) {
               b.setAttribute("nodefaultsrc", "true");
             }
 
@@ -3128,16 +3130,17 @@
           els.addSystemEventListener(document, "keypress", this, false);
           window.addEventListener("sizemodechange", this, false);
 
           var uniqueId = this._generateUniquePanelID();
           this.mPanelContainer.childNodes[0].id = uniqueId;
           this.mCurrentTab.linkedPanel = uniqueId;
           this.mCurrentTab._tPos = 0;
           this.mCurrentTab._fullyOpen = true;
+          this.mCurrentTab.lastAccessed = Infinity;
           this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
           this._tabForBrowser.set(this.mCurrentBrowser, this.mCurrentTab);
 
           // set up the shared autoscroll popup
           this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
           this._autoScrollPopup.id = "autoscroller";
           this.appendChild(this._autoScrollPopup);
           this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
@@ -4939,23 +4942,22 @@
       <property name="hidden" readonly="true">
         <getter>
           return this.getAttribute("hidden") == "true";
         </getter>
       </property>
 
       <property name="lastAccessed">
         <getter>
-          return this.selected ? Date.now() : this._lastAccessed;
+          return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
         </getter>
         <setter>
           this._lastAccessed = val;
         </setter>
       </property>
-      <field name="_lastAccessed">0</field>
 
       <field name="mOverCloseButton">false</field>
       <field name="mCorrespondingMenuitem">null</field>
       <field name="closing">false</field>
 
       <method name="_mouseenter">
         <body><![CDATA[
           if (this.hidden || this.closing)
--- a/browser/base/content/test/general/browser_lastAccessedTab.js
+++ b/browser/base/content/test/general/browser_lastAccessedTab.js
@@ -1,24 +1,35 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+let originalTab;
+let newTab;
+
 function test() {
-  let originalTab = gBrowser.selectedTab;
-  isnot(originalTab.lastAccessed, 0, "selectedTab has been selected");
-  ok(originalTab.lastAccessed <= Date.now(), "selectedTab has a valid timestamp");
+  waitForExplicitFinish();
+
+  originalTab = gBrowser.selectedTab;
+  setTimeout(step2, 100);
+}
 
-  let newTab = gBrowser.addTab("about:blank", {skipAnimation: true});
-  is(newTab.lastAccessed, 0, "newTab hasn't been selected so far");
+function step2() {
+  is(originalTab.lastAccessed, Date.now(), "selected tab has the current timestamp");
+  newTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+  setTimeout(step3, 100);
+}
 
+function step3() {
+  ok(newTab.lastAccessed < Date.now(), "new tab hasn't been selected so far");
   gBrowser.selectedTab = newTab;
-
-  isnot(newTab.lastAccessed, 0, "newTab has been selected");
-  ok(newTab.lastAccessed <= Date.now(), "newTab has a valid timestamp");
+  is(newTab.lastAccessed, Date.now(), "new tab has the current timestamp after being selected");
+  setTimeout(step4, 100);
+}
 
-  isnot(originalTab.lastAccessed, 0, "originalTab has been selected");
-  ok(originalTab.lastAccessed <= Date.now(), "originalTab has a valid timestamp");
-
-  ok(originalTab.lastAccessed <= newTab.lastAccessed,
-     "originalTab's timestamp must be lower than newTab's");
+function step4() {
+  ok(originalTab.lastAccessed < Date.now(),
+     "original tab has old timestamp after being deselected");
+  is(newTab.lastAccessed, Date.now(),
+     "new tab has the current timestamp since it's still selected");
 
   gBrowser.removeTab(newTab);
+  finish();
 }
--- a/browser/branding/aurora/pref/firefox-branding.js
+++ b/browser/branding/aurora/pref/firefox-branding.js
@@ -1,17 +1,16 @@
 
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-// We don't have pages ready for this, so leave these prefs empty for now (bug 648330)
 pref("startup.homepage_override_url","");
-pref("startup.homepage_welcome_url","");
+pref("startup.homepage_welcome_url","https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 28800); // 8 hours
 // The time interval between the downloading of mar file chunks in the
 // background (in seconds)
 // 0 means "download everything at once"
 pref("app.update.download.backgroundInterval", 0);
 // Give the user x seconds to react before showing the big UI. default=168 hours
 pref("app.update.promptWaitTime", 604800);
--- a/browser/components/loop/LoopRooms.jsm
+++ b/browser/components/loop/LoopRooms.jsm
@@ -171,28 +171,41 @@ let LoopRoomsInternal = {
       let roomsList = JSON.parse(response.body);
       if (!Array.isArray(roomsList)) {
         throw new Error("Missing array of rooms in response.");
       }
 
       for (let room of roomsList) {
         // See if we already have this room in our cache.
         let orig = this.rooms.get(room.roomToken);
-        if (orig) {
-          checkForParticipantsUpdate(orig, room);
+
+        if (room.deleted) {
+          // If this client deleted the room, then we'll already have
+          // deleted the room in the function below.
+          if (orig) {
+            this.rooms.delete(room.roomToken);
+          }
+
+          eventEmitter.emit("delete", room);
+          eventEmitter.emit("delete:" + room.roomToken, room);
+        } else {
+          if (orig) {
+            checkForParticipantsUpdate(orig, room);
+          }
+          // Remove the `currSize` for posterity.
+          if ("currSize" in room) {
+            delete room.currSize;
+          }
+
+          this.rooms.set(room.roomToken, room);
+
+          let eventName = orig ? "update" : "add";
+          eventEmitter.emit(eventName, room);
+          eventEmitter.emit(eventName + ":" + room.roomToken, room);
         }
-        // Remove the `currSize` for posterity.
-        if ("currSize" in room) {
-          delete room.currSize;
-        }
-        this.rooms.set(room.roomToken, room);
-
-        let eventName = orig ? "update" : "add";
-        eventEmitter.emit(eventName, room);
-        eventEmitter.emit(eventName + ":" + room.roomToken, room);
       }
 
       // If there's no rooms in the list, remove the guest created room flag, so that
       // we don't keep registering for guest when we don't need to.
       if (this.sessionType == LOOP_SESSION_TYPE.GUEST && !this.rooms.size) {
         this.setGuestCreatedRoom(false);
       }
 
@@ -225,23 +238,32 @@ let LoopRoomsInternal = {
       return;
     }
 
     MozLoopService.hawkRequest(this.sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET")
       .then(response => {
         let data = JSON.parse(response.body);
 
         room.roomToken = roomToken;
-        checkForParticipantsUpdate(room, data);
-        extend(room, data);
-        this.rooms.set(roomToken, room);
+
+        if (data.deleted) {
+          this.rooms.delete(room.roomToken);
 
-        let eventName = !needsUpdate ? "update" : "add";
-        eventEmitter.emit(eventName, room);
-        eventEmitter.emit(eventName + ":" + roomToken, room);
+          extend(room, data);
+          eventEmitter.emit("delete", room);
+          eventEmitter.emit("delete:" + room.roomToken, room);
+        } else {
+          checkForParticipantsUpdate(room, data);
+          extend(room, data);
+          this.rooms.set(roomToken, room);
+
+          let eventName = !needsUpdate ? "update" : "add";
+          eventEmitter.emit(eventName, room);
+          eventEmitter.emit(eventName + ":" + roomToken, room);
+        }
         callback(null, room);
       }, err => callback(err)).catch(err => callback(err));
   },
 
   /**
    * Create a room.
    *
    * @param {Object}   room     Properties to be sent to the LoopServer
@@ -318,18 +340,18 @@ let LoopRoomsInternal = {
   delete: function(roomToken, callback) {
     // XXX bug 1092954: Before deleting a room, the client should check room
     //     membership and forceDisconnect() all current participants.
     let room = this.rooms.get(roomToken);
     let url = "/rooms/" + encodeURIComponent(roomToken);
     MozLoopService.hawkRequest(this.sessionType, url, "DELETE")
       .then(response => {
         this.rooms.delete(roomToken);
-        eventEmitter.emit("delete", room);
         callback(null, room);
+        // We'll emit an update when the push notification is received.
       }, error => callback(error)).catch(error => callback(error));
   },
 
   /**
    * Internal function to handle POSTs to a room.
    *
    * @param {String} roomToken  The room token.
    * @param {Object} postData   The data to post to the room.
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -119,16 +119,20 @@ const injectObjectAPI = function(api, ta
       // If the last parameter is a function, assume its a callback
       // and wrap it differently.
       if (callbackIsFunction) {
         api[func](...params, function(...results) {
           // When the function was garbage collected due to async events, like
           // closing a window, we want to circumvent a JS error.
           if (callbackIsFunction && typeof lastParam != "function") {
             MozLoopService.log.debug(func + ": callback function was lost.");
+            // Assume the presence of a first result argument to be an error.
+            if (results[0]) {
+              MozLoopService.log.error(func + " error:", results[0]);
+            }
             return;
           }
           lastParam(...[cloneValueInto(r, targetWindow) for (r of results)]);
         });
       } else {
         try {
           return cloneValueInto(api[func](...params, lastParam), targetWindow);
         } catch (ex) {
@@ -512,17 +516,17 @@ function injectLoopAPI(targetWindow) {
         // XXX Should really return a DOM promise here.
         let callbackIsFunction = (typeof callback == "function");
         MozLoopService.hawkRequest(sessionType, path, method, payloadObj).then((response) => {
           callback(null, response.body);
         }, hawkError => {
           // When the function was garbage collected due to async events, like
           // closing a window, we want to circumvent a JS error.
           if (callbackIsFunction && typeof callback != "function") {
-            MozLoopService.log.debug("hawkRequest: callback function was lost.");
+            MozLoopService.log.error("hawkRequest: callback function was lost.", hawkError);
             return;
           }
           // The hawkError.error property, while usually a string representing
           // an HTTP response status message, may also incorrectly be a native
           // error object that will cause the cloning function to fail.
           callback(Cu.cloneInto({
             error: (hawkError.error && typeof hawkError.error == "string")
                    ? hawkError.error : "Unexpected exception",
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -630,17 +630,17 @@ loop.contacts = (function(_, mozL10n) {
           React.DOM.label(null, mozL10n.get("edit_contact_name_label")), 
           React.DOM.input({ref: "name", required: true, pattern: "\\s*\\S.*", type: "text", 
                  className: cx({pristine: this.state.pristine}), 
                  valueLink: this.linkState("name")}), 
           React.DOM.label(null, mozL10n.get("edit_contact_email_label")), 
           React.DOM.input({ref: "email", type: "email", required: phoneOrEmailRequired, 
                  className: cx({pristine: this.state.pristine}), 
                  valueLink: this.linkState("email")}), 
-          React.DOM.label(null, mozL10n.get("new_contact_phone_placeholder")), 
+          React.DOM.label(null, mozL10n.get("new_contact_fxos_phone_placeholder")), 
           React.DOM.input({ref: "tel", type: "tel", required: phoneOrEmailRequired, 
                  className: cx({pristine: this.state.pristine}), 
                  valueLink: this.linkState("tel")}), 
           ButtonGroup(null, 
             Button({additionalClass: "button-cancel", 
                     caption: mozL10n.get("cancel_button"), 
                     onClick: this.handleCancelButtonClick}), 
             Button({additionalClass: "button-accept", 
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -630,17 +630,17 @@ loop.contacts = (function(_, mozL10n) {
           <label>{mozL10n.get("edit_contact_name_label")}</label>
           <input ref="name" required pattern="\s*\S.*" type="text"
                  className={cx({pristine: this.state.pristine})}
                  valueLink={this.linkState("name")} />
           <label>{mozL10n.get("edit_contact_email_label")}</label>
           <input ref="email" type="email" required={phoneOrEmailRequired}
                  className={cx({pristine: this.state.pristine})}
                  valueLink={this.linkState("email")} />
-          <label>{mozL10n.get("new_contact_phone_placeholder")}</label>
+          <label>{mozL10n.get("new_contact_fxos_phone_placeholder")}</label>
           <input ref="tel" type="tel" required={phoneOrEmailRequired}
                  className={cx({pristine: this.state.pristine})}
                  valueLink={this.linkState("tel")} />
           <ButtonGroup>
             <Button additionalClass="button-cancel"
                     caption={mozL10n.get("cancel_button")}
                     onClick={this.handleCancelButtonClick} />
             <Button additionalClass="button-accept"
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -209,17 +209,17 @@ loop.panel = (function(_, mozL10n) {
             )
           ),
           "privacy_notice": React.renderComponentToStaticMarkup(
             React.DOM.a({href: privacy_notice_url, target: "_blank"}, 
               mozL10n.get("legal_text_privacy")
             )
           ),
         });
-        return React.DOM.div(null, 
+        return React.DOM.div({id: "powered-by-wrapper"}, 
           React.DOM.p({id: "powered-by"}, 
             mozL10n.get("powered_by_beforeLogo"), 
             React.DOM.img({id: "powered-by-logo", className: locale}), 
             mozL10n.get("powered_by_afterLogo")
           ), 
           React.DOM.p({className: "terms-service", 
              dangerouslySetInnerHTML: {__html: tosHTML}})
          );
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -209,17 +209,17 @@ loop.panel = (function(_, mozL10n) {
             </a>
           ),
           "privacy_notice": React.renderComponentToStaticMarkup(
             <a href={privacy_notice_url} target="_blank">
               {mozL10n.get("legal_text_privacy")}
             </a>
           ),
         });
-        return <div>
+        return <div id="powered-by-wrapper">
           <p id="powered-by">
             {mozL10n.get("powered_by_beforeLogo")}
             <img id="powered-by-logo" className={locale} />
             {mozL10n.get("powered_by_afterLogo")}
           </p>
           <p className="terms-service"
              dangerouslySetInnerHTML={{__html: tosHTML}}></p>
          </div>;
--- a/browser/components/loop/content/shared/css/contacts.css
+++ b/browser/components/loop/content/shared/css/contacts.css
@@ -23,30 +23,29 @@
   overflow-y: auto;
   /* Space for six contacts, not affected by filtering.  This is enough space
      to show the dropdown menu when there is only one contact. */
   height: 306px;
 }
 
 .contact,
 .contact-separator {
-  padding: 5px 10px;
+  padding: .5rem 1rem;
   font-size: 13px;
 }
 
 .contact {
   position: relative;
   display: flex;
   flex-direction: row;
   align-items: center;
   color: #666;
 }
 
 .contact-separator {
-  height: 24px;
   background-color: #eee;
   color: #888;
 }
 
 .contact:not(:first-child) {
   border-top: 1px solid #ddd;
 }
 
@@ -118,17 +117,17 @@
   background-image: url("../img/icons-16x16.svg#block-red");
   background-position: center;
   background-size: 10px 10px;
   background-repeat: no-repeat;
 }
 
 .contact > .details > .username > i.icon-google {
   position: absolute;
-  right: 10px;
+  right: 1rem;
   top: 35%;
   width: 14px;
   height: 14px;
   border-radius: 50%;
   background-image: url("../img/icons-16x16.svg#google");
   background-position: center;
   background-size: 16px 16px;
   background-repeat: no-repeat;
@@ -223,10 +222,10 @@
   background-image: url("../img/icons-16x16.svg#unblock");
 }
 
 .contact > .dropdown-menu > .dropdown-menu-item > .icon-remove {
   background-image: url("../img/icons-16x16.svg#delete");
 }
 
 .contact-form > .button-group {
-  margin-top: 14px;
+  margin-top: 1rem;
 }
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -842,8 +842,13 @@ html, .fx-embedded, #main,
 .standalone .room-conversation .conversation-toolbar {
   background: #000;
   border: none;
 }
 
 .standalone .room-conversation .conversation-toolbar .btn-hangup-entry {
   display: block;
 }
+
+.standalone .room-conversation-wrapper .ended-conversation {
+  position: relative;
+  height: auto;
+}
--- a/browser/components/loop/content/shared/css/panel.css
+++ b/browser/components/loop/content/shared/css/panel.css
@@ -29,17 +29,17 @@ body {
   background-image: url("../img/beta-ribbon.svg#beta-ribbon");
   background-size: 36px 36px;
   background-repeat: no-repeat;
 }
 
 .tab-view {
   display: flex;
   flex-direction: row;
-  padding: 10px;
+  padding: 10px 0;
   border-bottom: 1px solid #ccc;
   color: #000;
   border-top-right-radius: 2px;
   border-top-left-radius: 2px;
   list-style: none;
 }
 
 .tab-view > li {
@@ -92,44 +92,48 @@ body {
 
 .tab.selected {
   display: block;
 }
 
 /* Content area and input fields */
 
 .content-area {
-  padding: 14px;
+  padding: .5rem 1rem;
 }
 
 .content-area header {
   font-weight: 700;
 }
 
 #fte-getstarted {
-  padding-top: 1em;
-  padding-bottom: 1em;
-  margin-bottom: 1em;
+  padding-top: .5rem;
+  padding-bottom: .5rem;
 }
 
 #fte-title {
   text-align: center;
-  margin-bottom: .5em;
+  margin-bottom: .5rem;
 }
 
 #fte-button {
   width: 50%;
   display: block;
   margin-left: auto;
   margin-right: auto;
   font-size: 1rem;
   padding: .5rem 1rem;
   height: auto; /* Needed to override .button's height:26px; */
 }
 
+#fte-getstarted + #powered-by-wrapper {
+  position: absolute;
+  bottom: 0;
+}
+
 /* Need to remove when these rules when the Beta tag is removed */
 #share-link-header {
   -moz-padding-start: 20px;
 }
 #fte-getstarted + .generate-url > #share-link-header,
 .tab-view + .tab .content-area > .generate-url > #share-link-header {
   /* The header shouldn't be indented if the tabs are present. */
   -moz-padding-start: 0;
@@ -167,76 +171,80 @@ body {
 .rooms {
   min-height: 100px;
 }
 
 .rooms > h1 {
   font-weight: bold;
   color: #999;
   padding: .5rem 1rem;
-  border-bottom: 1px solid #ddd;
 }
 
 .rooms > p {
-  border-top: 1px solid #ddd;
-  padding: 1rem 0;
+  padding: .5rem 0;
   margin: 0;
 }
 
 .rooms > p > .btn {
   display: block;
   font-size: 1rem;
   margin: 0 auto;
   padding: .5rem 1rem;
   border-radius: 3px;
 }
 
 .room-list {
   max-height: 335px; /* XXX better computation needed */
   overflow: auto;
+  border-top: 1px solid #ccc;
+  border-bottom: 1px solid #ccc;
+}
+
+.room-list:empty {
+  border-bottom-width: 0;
 }
 
 .room-list > .room-entry {
-  padding: 1rem 1rem 0 .5rem;
+  padding: .5rem 1rem;
 }
 
 .room-list > .room-entry > h2 {
   font-size: .85rem;
   color: #777;
 }
 
 .room-list > .room-entry.room-active > h2 {
   font-weight: bold;
   color: #000;
 }
 
 .room-list > .room-entry > h2 > .room-notification {
-  display: inline-block;
-  background: transparent;
+  display: none;
+  background: #00a0ec;
   width: 8px;
   height: 8px;
   border-radius: 50%;
   margin-right: .3rem;
 }
 
 .room-list > .room-entry.room-active > h2 > .room-notification {
-  background-color: #00a0ec;
+  display: inline-block;
 }
 
 .room-list > .room-entry:hover {
   background: #f1f1f1;
 }
 
 .room-list > .room-entry:not(:last-child) {
-  border-bottom: 1px solid #ddd;
+  border-bottom: 1px solid #ccc;
 }
 
 .room-list > .room-entry > p {
   margin: 0;
-  padding: .2em 0 1rem .8rem;
+  padding: .2rem 0;
 }
 
 .room-list > .room-entry > p > a {
   color: #777;
   opacity: .5;
   transition: opacity .1s ease-in-out 0s;
   text-decoration: none;
 }
@@ -470,25 +478,24 @@ body[dir=rtl] .generate-url-spinner {
   background-color: #008acb;
   border-color: #008acb;
   color: #fff;
 }
 
 #powered-by,
 .terms-service {
   color: #888;
-  text-align: center;
   font-size: .9em;
 }
 
 #powered-by {
   border-top: 1px solid #ccc;
-  padding-top: 1em;
-  margin-left: -14px;
-  margin-right: -14px;
+  padding-top: .5rem;
+  text-align: center;
+  margin-top: 0;
 }
 
 #powered-by-logo {
   display: inline-block;
   margin-left: 10px;
   margin-right: 10px;
   vertical-align: middle;
   background-image: url("../img/telefonica.png");
@@ -533,16 +540,22 @@ body[dir=rtl] .generate-url-spinner {
     background-image: url("../img/vivo@2x.png");
   }
 
   #powered-by-logo[class^="es-"] {
     background-image: url("../img/movistar@2x.png");
   }
 }
 
+.terms-service {
+  padding-left: 1rem;
+  padding-right: 1rem;
+  padding-bottom: .5rem;
+}
+
 .terms-service > a {
   color: #00caee;
 }
 
 /* DnD menu */
 
 .dnd-status {
   border: 1px solid transparent;
@@ -552,17 +565,17 @@ body[dir=rtl] .generate-url-spinner {
      as if there was no additional spacing. */
   -moz-margin-start: calc(-1px + -4px);
   font-size: .9em;
   cursor: pointer;
   border-radius: 3px;
 }
 
 .dnd-status:hover {
-  border: 1px solid #DDD;
+  border-color: #ddd;
   background-color: #f1f1f1;
 }
 
 /* Status badges -- Available/Unavailable */
 
 .status {
   display: inline-block;
   width: 8px;
@@ -671,22 +684,21 @@ body[dir=rtl] .generate-url-spinner {
   flex-wrap: nowrap;
   justify-content: space-between;
   align-content: stretch;
   align-items: center;
   font-size: 1em;
   border-top: 1px solid #D1D1D1;
   background-color: #eaeaea;
   color: #7f7f7f;
-  padding: 14px;
+  padding: .5rem 1rem;
 }
 
 .footer .signin-details {
   align-items: center;
   display: flex;
-  -moz-margin-start: 5px;
 }
 
 .footer .user-identity {
   color: #000;
   font-weight: bold;
   margin: 0;
 }
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -158,16 +158,22 @@ loop.shared.actions = (function() {
       publisherConfig: Object,
       // The local stream element
       getLocalElementFunc: Function,
       // The remote stream element
       getRemoteElementFunc: Function
     }),
 
     /**
+     * Used for notifying that local media has been obtained.
+     */
+    GotMediaPermission: Action.define("gotMediaPermission", {
+    }),
+
+    /**
      * Used for notifying that the media is now up for the call.
      */
     MediaConnected: Action.define("mediaConnected", {
     }),
 
     /**
      * Used to mute or unmute a stream
      */
@@ -320,16 +326,23 @@ loop.shared.actions = (function() {
     JoinedRoom: Action.define("joinedRoom", {
       apiKey: String,
       sessionToken: String,
       sessionId: String,
       expires: Number
     }),
 
     /**
+     * Used to indicate that the feedback cycle is completed and the countdown
+     * finished.
+     */
+    FeedbackComplete: Action.define("feedbackComplete", {
+    }),
+
+    /**
      * Used to indicate the user wishes to leave the room.
      */
     LeaveRoom: Action.define("leaveRoom", {
     }),
 
     /**
      * Requires detailed information on sad feedback.
      */
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -23,16 +23,18 @@ loop.store.ActiveRoomStore = (function()
 
   var ROOM_STATES = loop.store.ROOM_STATES = {
     // The initial state of the room
     INIT: "room-init",
     // The store is gathering the room data
     GATHER: "room-gather",
     // The store has got the room data
     READY: "room-ready",
+    // Obtaining media from the user
+    MEDIA_WAIT: "room-media-wait",
     // The room is known to be joined on the loop-server
     JOINED: "room-joined",
     // The room is connected to the sdk server.
     SESSION_CONNECTED: "room-session-connected",
     // There are participants in the room.
     HAS_PARTICIPANTS: "room-has-participants",
     // There was an issue with the room
     FAILED: "room-failed",
@@ -107,40 +109,43 @@ loop.store.ActiveRoomStore = (function()
         }
       }
 
       console.error("Error in state `" + this._storeState.roomState + "`:",
         actionData.error);
 
       this.setStoreState({
         error: actionData.error,
-        failureReason: getReason(actionData.error.errno),
-        roomState: actionData.error.errno === SERVER_CODES.ROOM_FULL ?
-          ROOM_STATES.FULL : ROOM_STATES.FAILED
+        failureReason: getReason(actionData.error.errno)
       });
+
+      this._leaveRoom(actionData.error.errno === SERVER_CODES.ROOM_FULL ?
+          ROOM_STATES.FULL : ROOM_STATES.FAILED);
     },
 
     /**
      * Registers the actions with the dispatcher that this store is interested
      * in after the initial setup has been performed.
      */
     _registerPostSetupActions: function() {
       this.dispatcher.register(this, [
         "roomFailure",
         "setupRoomInfo",
         "updateRoomInfo",
+        "gotMediaPermission",
         "joinRoom",
         "joinedRoom",
         "connectedToSdkServers",
         "connectionFailure",
         "setMute",
         "remotePeerDisconnected",
         "remotePeerConnected",
         "windowUnload",
-        "leaveRoom"
+        "leaveRoom",
+        "feedbackComplete"
       ]);
     },
 
     /**
      * Execute setupWindowData event action from the dispatcher. This gets
      * the room data from the mozLoop api, and dispatches an UpdateRoomInfo event.
      * It also dispatches JoinRoom as this action is only applicable to the desktop
      * client, and needs to auto-join.
@@ -255,16 +260,24 @@ loop.store.ActiveRoomStore = (function()
      * Handles the action to join to a room.
      */
     joinRoom: function() {
       // Reset the failure reason if necessary.
       if (this.getStoreState().failureReason) {
         this.setStoreState({failureReason: undefined});
       }
 
+      this.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
+    },
+
+    /**
+     * Handles the action that signifies when media permission has been
+     * granted and starts joining the room.
+     */
+    gotMediaPermission: function() {
       this._mozLoop.rooms.join(this._storeState.roomToken,
         function(error, responseData) {
           if (error) {
             this.dispatchAction(new sharedActions.RoomFailure({error: error}));
             return;
           }
 
           this.dispatchAction(new sharedActions.JoinedRoom({
@@ -288,16 +301,31 @@ loop.store.ActiveRoomStore = (function()
         apiKey: actionData.apiKey,
         sessionToken: actionData.sessionToken,
         sessionId: actionData.sessionId,
         roomState: ROOM_STATES.JOINED
       });
 
       this._setRefreshTimeout(actionData.expires);
       this._sdkDriver.connectSession(actionData);
+
+      // If we haven't got a room name yet, go and get one. We typically
+      // need to do this in the case of the standalone window.
+      // XXX When bug 1103331 lands this can be moved to earlier.
+      if (!this._storeState.roomName) {
+        this._mozLoop.rooms.get(this._storeState.roomToken,
+          function(err, result) {
+            if (err) {
+              console.error("Failed to get room data:", err);
+              return;
+            }
+
+            this.dispatcher.dispatch(new sharedActions.UpdateRoomInfo(result));
+        }.bind(this));
+      }
     },
 
     /**
      * Handles recording when the sdk has connected to the servers.
      */
     connectedToSdkServers: function() {
       this.setStoreState({
         roomState: ROOM_STATES.SESSION_CONNECTED
@@ -330,33 +358,29 @@ loop.store.ActiveRoomStore = (function()
       muteState[actionData.type + "Muted"] = !actionData.enabled;
       this.setStoreState(muteState);
     },
 
     /**
      * Handles recording when a remote peer has connected to the servers.
      */
     remotePeerConnected: function() {
-      this.setStoreState({
-        roomState: ROOM_STATES.HAS_PARTICIPANTS
-      });
+      this.setStoreState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
 
       // We've connected with a third-party, therefore stop displaying the ToS etc.
       this._mozLoop.setLoopPref("seenToS", "seen");
     },
 
     /**
-     * Handles a remote peer disconnecting from the session.
+     * Handles a remote peer disconnecting from the session. As we currently only
+     * support 2 participants, we declare the room as SESSION_CONNECTED as soon as
+     * one participantleaves.
      */
     remotePeerDisconnected: function() {
-      // As we only support two users at the moment, we just set this
-      // back to joined.
-      this.setStoreState({
-        roomState: ROOM_STATES.SESSION_CONNECTED
-      });
+      this.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
     },
 
     /**
      * Handles the window being unloaded. Ensures the room is left.
      */
     windowUnload: function() {
       this._leaveRoom();
 
@@ -420,16 +444,23 @@ loop.store.ActiveRoomStore = (function()
 
       if (this._storeState.roomState === ROOM_STATES.JOINED ||
           this._storeState.roomState === ROOM_STATES.SESSION_CONNECTED ||
           this._storeState.roomState === ROOM_STATES.HAS_PARTICIPANTS) {
         this._mozLoop.rooms.leave(this._storeState.roomToken,
           this._storeState.sessionToken);
       }
 
-      this.setStoreState({
-        roomState: nextState ? nextState : ROOM_STATES.ENDED
-      });
+      this.setStoreState({roomState: nextState || ROOM_STATES.ENDED});
+    },
+
+    /**
+     * When feedback is complete, we reset the room to the initial state.
+     */
+    feedbackComplete: function() {
+      // Note, that we want some values, such as the windowId, so we don't
+      // do a full reset here.
+      this.setStoreState(this.getInitialStoreState());
     }
   });
 
   return ActiveRoomStore;
 })();
--- a/browser/components/loop/content/shared/js/feedbackStore.js
+++ b/browser/components/loop/content/shared/js/feedbackStore.js
@@ -32,17 +32,18 @@ loop.store.FeedbackStore = (function() {
    * @param {Object} options Options object:
    * - {mozLoop}        mozLoop                 The MozLoop API object.
    * - {feedbackClient} loop.FeedbackAPIClient  The feedback API client.
    */
   var FeedbackStore = loop.store.createStore({
     actions: [
       "requireFeedbackDetails",
       "sendFeedback",
-      "sendFeedbackError"
+      "sendFeedbackError",
+      "feedbackComplete"
     ],
 
     initialize: function(options) {
       if (!options.feedbackClient) {
         throw new Error("Missing option feedbackClient");
       }
       this._feedbackClient = options.feedbackClient;
     },
@@ -86,13 +87,21 @@ loop.store.FeedbackStore = (function() {
      *
      * @param {sharedActions.SendFeedback} actionData The action data.
      */
     sendFeedbackError: function(actionData) {
       this.setStoreState({
         feedbackState: FEEDBACK_STATES.FAILED,
         error: actionData.error
       });
+    },
+
+    /**
+     * Resets the store to its initial state as feedback has been completed,
+     * i.e. ready for the next round of feedback.
+     */
+    feedbackComplete: function() {
+      this.resetStoreState();
     }
   });
 
   return FeedbackStore;
 })();
--- a/browser/components/loop/content/shared/js/feedbackViews.js
+++ b/browser/components/loop/content/shared/js/feedbackViews.js
@@ -10,17 +10,18 @@ var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = loop.shared.views || {};
 loop.shared.views.FeedbackView = (function(l10n) {
   "use strict";
 
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
 
-  var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
+  var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS =
+      loop.shared.views.WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
   var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
 
   /**
    * Feedback outer layout.
    *
    * Props:
    * -
    */
--- a/browser/components/loop/content/shared/js/feedbackViews.jsx
+++ b/browser/components/loop/content/shared/js/feedbackViews.jsx
@@ -10,17 +10,18 @@ var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = loop.shared.views || {};
 loop.shared.views.FeedbackView = (function(l10n) {
   "use strict";
 
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
 
-  var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
+  var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS =
+      loop.shared.views.WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
   var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
 
   /**
    * Feedback outer layout.
    *
    * Props:
    * -
    */
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -202,16 +202,19 @@ loop.OTSdkDriver = (function() {
     /**
      * Handles the publishing being complete.
      *
      * @param {OT.Event} event
      */
     _onPublishComplete: function(event) {
       event.preventDefault();
       this._publisherReady = true;
+
+      this.dispatcher.dispatch(new sharedActions.GotMediaPermission());
+
       this._maybePublishLocalStream();
     },
 
     /**
      * Handles publishing of media being denied.
      *
      * @param {OT.Event} event
      */
--- a/browser/components/loop/content/shared/js/store.js
+++ b/browser/components/loop/content/shared/js/store.js
@@ -49,16 +49,27 @@ loop.store.createStore = (function() {
      * @param {Object} newState The new store state object.
      */
     setStoreState: function(newState) {
       for (var key in newState) {
         this._storeState[key] = newState[key];
         this.trigger("change:" + key);
       }
       this.trigger("change");
+    },
+
+    /**
+     * Resets the store state to the initially defined state.
+     */
+    resetStoreState: function() {
+      if (typeof this.getInitialStoreState === "function") {
+        this._storeState = this.getInitialStoreState();
+      } else {
+        this._storeState = {};
+      }
     }
   };
 
   /**
    * Creates a new Store constructor.
    *
    * @param  {Object}   storeProto The store prototype.
    * @return {Function}            A store constructor.
--- a/browser/components/loop/standalone/Makefile
+++ b/browser/components/loop/standalone/Makefile
@@ -11,18 +11,18 @@
 # functionality in Gruntfile.js rather than here.
 # Bug 1066176 tracks moving all functionality currently here
 # to the Gruntfile and getting rid of this Makefile entirely.
 
 LOOP_SERVER_URL := $(shell echo $${LOOP_SERVER_URL-http://localhost:5000/v0})
 LOOP_FEEDBACK_API_URL := $(shell echo $${LOOP_FEEDBACK_API_URL-"https://input.allizom.org/api/v1/feedback"})
 LOOP_FEEDBACK_PRODUCT_NAME := $(shell echo $${LOOP_FEEDBACK_PRODUCT_NAME-Loop})
 LOOP_BRAND_WEBSITE_URL := $(shell echo $${LOOP_BRAND_WEBSITE_URL-"https://www.mozilla.org/firefox/"})
-LOOP_PRIVACY_WEBSITE_URL := $(shell echo $${LOOP_PRIVACY_WEBSITE_URL-"https://www.mozilla.org/privacy"})
-LOOP_LEGAL_WEBSITE_URL := $(shell echo $${LOOP_LEGAL_WEBSITE_URL-"/legal/terms"})
+LOOP_PRIVACY_WEBSITE_URL := $(shell echo $${LOOP_PRIVACY_WEBSITE_URL-"https://www.mozilla.org/privacy/firefox-hello/"})
+LOOP_LEGAL_WEBSITE_URL := $(shell echo $${LOOP_LEGAL_WEBSITE_URL-"https://www.mozilla.org/about/legal/terms/firefox-hello/"})
 LOOP_PRODUCT_HOMEPAGE_URL := $(shell echo $${LOOP_PRODUCT_HOMEPAGE_URL-"https://www.firefox.com/hello/"})
 
 NODE_LOCAL_BIN=./node_modules/.bin
 
 install: npm_install tos
 
 npm_install:
 	@npm install
--- a/browser/components/loop/standalone/content/js/standaloneMozLoop.js
+++ b/browser/components/loop/standalone/content/js/standaloneMozLoop.js
@@ -64,16 +64,54 @@ loop.StandaloneMozLoop = (function(mozL1
       throw new Error("missing required baseServerUrl");
     }
 
     this._baseServerUrl = options.baseServerUrl;
   };
 
   StandaloneMozLoopRooms.prototype = {
     /**
+     * Request information about a specific room from the server.
+     *
+     * @param {String}   roomToken Room identifier
+     * @param {Function} callback  Function that will be invoked once the operation
+     *                             finished. The first argument passed will be an
+     *                             `Error` object or `null`. The second argument will
+     *                             be the list of rooms, if it was fetched successfully.
+     */
+    get: function(roomToken, callback) {
+      var req = $.ajax({
+        url:         this._baseServerUrl + "/rooms/" + roomToken,
+        method:      "GET",
+        contentType: "application/json",
+        beforeSend: function(xhr) {
+          if (this.sessionToken) {
+            xhr.setRequestHeader("Authorization", "Basic " + btoa(this.sessionToken));
+          }
+        }.bind(this)
+      });
+
+      req.done(function(responseData) {
+        try {
+          // We currently only require things we need rather than everything possible.
+          callback(null, validate(responseData, {
+            roomName: String,
+            roomOwner: String,
+            roomUrl: String
+          }));
+        } catch (err) {
+          console.error("Error requesting call info", err.message);
+          callback(err);
+        }
+      }.bind(this));
+
+      req.fail(failureHandler.bind(this, callback));
+    },
+
+    /**
      * Internal function to actually perform a post to a room.
      *
      * @param {String} roomToken The rom token.
      * @param {String} sessionToken The sessionToken for the room if known
      * @param {Object} roomData The data to send with the request
      * @param {Array} expectedProps The expected properties we should receive from the
      *                              server
      * @param {Function} callback The callback for when the request completes. The
@@ -110,26 +148,36 @@ loop.StandaloneMozLoop = (function(mozL1
      * Joins a room
      *
      * @param {String} roomToken  The room token.
      * @param {Function} callback Function that will be invoked once the operation
      *                            finished. The first argument passed will be an
      *                            `Error` object or `null`.
      */
     join: function(roomToken, callback) {
+      function callbackWrapper(err, result) {
+        // XXX Save the sessionToken for purposes of get.
+        // When bug 1103331 this can probably be removed.
+        if (result) {
+          this.sessionToken = result.sessionToken;
+        }
+
+        callback(err, result);
+      }
+
       this._postToRoom(roomToken, null, {
         action: "join",
         displayName: mozL10n.get("rooms_display_name_guest"),
         clientMaxSize: ROOM_MAX_CLIENTS
       }, {
         apiKey: String,
         sessionId: String,
         sessionToken: String,
         expires: Number
-      }, callback);
+      }, callbackWrapper.bind(this));
     },
 
     /**
      * Refreshes a room
      *
      * @param {String} roomToken    The room token.
      * @param {String} sessionToken The session token for the session that has been
      *                              joined
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -14,17 +14,28 @@ loop.standaloneRoomViews = (function(moz
   var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
   var sharedViews = loop.shared.views;
 
   var StandaloneRoomInfoArea = React.createClass({displayName: 'StandaloneRoomInfoArea',
     propTypes: {
-      helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
+      helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired,
+      activeRoomStore:
+        React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
+      feedbackStore:
+        React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
+    },
+
+    onFeedbackSent: function() {
+      // We pass a tick to prevent React warnings regarding nested updates.
+      setTimeout(function() {
+        this.props.activeRoomStore.dispatchAction(new sharedActions.FeedbackComplete());
+      }.bind(this));
     },
 
     _renderCallToActionLink: function() {
       if (this.props.helper.isFirefox(navigator.userAgent)) {
         return (
           React.DOM.a({href: loop.config.learnMoreUrl, className: "btn btn-info"}, 
             mozL10n.get("rooms_room_full_call_to_action_label", {
               clientShortname: mozL10n.get("clientShortname2")
@@ -50,63 +61,85 @@ loop.standaloneRoomViews = (function(moz
           return mozL10n.get("rooms_media_denied_message");
         case FAILURE_REASONS.EXPIRED_OR_INVALID:
           return mozL10n.get("rooms_unavailable_notification_message");
         default:
           return mozL10n.get("status_error");
       }
     },
 
-    _renderContent: function() {
+    render: function() {
       switch(this.props.roomState) {
         case ROOM_STATES.INIT:
-        case ROOM_STATES.READY:
-        case ROOM_STATES.ENDED: {
+        case ROOM_STATES.READY: {
           // XXX: In ENDED state, we should rather display the feedback form.
           return (
-            React.DOM.button({className: "btn btn-join btn-info", 
-                    onClick: this.props.joinRoom}, 
-              mozL10n.get("rooms_room_join_label")
+            React.DOM.div({className: "room-inner-info-area"}, 
+              React.DOM.button({className: "btn btn-join btn-info", 
+                      onClick: this.props.joinRoom}, 
+                mozL10n.get("rooms_room_join_label")
+              )
+            )
+          );
+        }
+        case ROOM_STATES.MEDIA_WAIT: {
+          var msg = mozL10n.get("call_progress_getting_media_description",
+                                {clientShortname: mozL10n.get("clientShortname2")});
+          // XXX Bug 1047040 will add images to help prompt the user.
+          return (
+            React.DOM.div({className: "room-inner-info-area"}, 
+              React.DOM.p({className: "prompt-media-message"}, 
+                msg
+              )
             )
           );
         }
         case ROOM_STATES.JOINED:
         case ROOM_STATES.SESSION_CONNECTED: {
           return (
-            React.DOM.p({className: "empty-room-message"}, 
-              mozL10n.get("rooms_only_occupant_label")
+            React.DOM.div({className: "room-inner-info-area"}, 
+              React.DOM.p({className: "empty-room-message"}, 
+                mozL10n.get("rooms_only_occupant_label")
+              )
             )
           );
         }
-        case ROOM_STATES.FULL:
+        case ROOM_STATES.FULL: {
           return (
-            React.DOM.div(null, 
+            React.DOM.div({className: "room-inner-info-area"}, 
               React.DOM.p({className: "full-room-message"}, 
                 mozL10n.get("rooms_room_full_label")
               ), 
               React.DOM.p(null, this._renderCallToActionLink())
             )
           );
-        case ROOM_STATES.FAILED:
+        }
+        case ROOM_STATES.ENDED: {
           return (
-            React.DOM.p({className: "failed-room-message"}, 
-              this._getFailureString()
+            React.DOM.div({className: "ended-conversation"}, 
+              sharedViews.FeedbackView({
+                feedbackStore: this.props.feedbackStore, 
+                onAfterFeedbackReceived: this.onFeedbackSent}
+              )
             )
           );
-        default:
+        }
+        case ROOM_STATES.FAILED: {
+          return (
+            React.DOM.div({className: "room-inner-info-area"}, 
+              React.DOM.p({className: "failed-room-message"}, 
+                this._getFailureString()
+              )
+            )
+          );
+        }
+        default: {
           return null;
+        }
       }
-    },
-
-    render: function() {
-      return (
-        React.DOM.div({className: "room-inner-info-area"}, 
-          this._renderContent()
-        )
-      );
     }
   });
 
   var StandaloneRoomHeader = React.createClass({displayName: 'StandaloneRoomHeader',
     render: function() {
       return (
         React.DOM.header(null, 
           React.DOM.h1(null, mozL10n.get("clientShortname2")), 
@@ -149,16 +182,18 @@ loop.standaloneRoomViews = (function(moz
     mixins: [
       Backbone.Events,
       sharedMixins.RoomsAudioMixin
     ],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
+      feedbackStore:
+        React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
@@ -206,41 +241,73 @@ loop.standaloneRoomViews = (function(moz
           bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
+    /**
+     * Used to update the video container whenever the orientation or size of the
+     * display area changes.
+     */
+    updateVideoContainer: function() {
+      var localStreamParent = this._getElement('.local .OT_publisher');
+      var remoteStreamParent = this._getElement('.remote .OT_subscriber');
+      if (localStreamParent) {
+        localStreamParent.style.width = "100%";
+      }
+      if (remoteStreamParent) {
+        remoteStreamParent.style.height = "100%";
+      }
+    },
+
     componentDidMount: function() {
+      /**
+       * OT inserts inline styles into the markup. Using a listener for
+       * resize events helps us trigger a full width/height on the element
+       * so that they update to the correct dimensions.
+       * XXX: this should be factored as a mixin, bug 1104930
+       */
+      window.addEventListener('orientationchange', this.updateVideoContainer);
+      window.addEventListener('resize', this.updateVideoContainer);
+
       // Adding a class to the document body element from here to ease styling it.
       document.body.classList.add("is-standalone-room");
     },
 
     componentWillUnmount: function() {
       this.stopListening(this.props.activeRoomStore);
     },
 
     /**
      * Watches for when we transition to JOINED room state, so we can request
      * user media access.
      *
      * @param  {Object} nextProps (Unused)
      * @param  {Object} nextState Next state object.
      */
     componentWillUpdate: function(nextProps, nextState) {
-      if (this.state.roomState !== ROOM_STATES.JOINED &&
-          nextState.roomState === ROOM_STATES.JOINED) {
+      if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
+          nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this._getPublisherConfig(),
           getLocalElementFunc: this._getElement.bind(this, ".local"),
           getRemoteElementFunc: this._getElement.bind(this, ".remote")
         }));
       }
+
+      if (this.state.roomState !== ROOM_STATES.JOINED &&
+          nextState.roomState === ROOM_STATES.JOINED) {
+        // This forces the video size to update - creating the publisher
+        // first, and then connecting to the session doesn't seem to set the
+        // initial size correctly.
+        this.updateVideoContainer();
+      }
     },
 
     joinRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
     },
 
     leaveRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
@@ -279,17 +346,19 @@ loop.standaloneRoomViews = (function(moz
       });
 
       return (
         React.DOM.div({className: "room-conversation-wrapper"}, 
           StandaloneRoomHeader(null), 
           StandaloneRoomInfoArea({roomState: this.state.roomState, 
                                   failureReason: this.state.failureReason, 
                                   joinRoom: this.joinRoom, 
-                                  helper: this.props.helper}), 
+                                  helper: this.props.helper, 
+                                  activeRoomStore: this.props.activeRoomStore, 
+                                  feedbackStore: this.props.feedbackStore}), 
           React.DOM.div({className: "video-layout-wrapper"}, 
             React.DOM.div({className: "conversation room-conversation"}, 
               React.DOM.h2({className: "room-name"}, this.state.roomName), 
               React.DOM.div({className: "media nested"}, 
                 React.DOM.div({className: "video_wrapper remote_wrapper"}, 
                   React.DOM.div({className: "video_inner remote"})
                 ), 
                 React.DOM.div({className: localStreamClasses})
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -14,17 +14,28 @@ loop.standaloneRoomViews = (function(moz
   var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
   var sharedViews = loop.shared.views;
 
   var StandaloneRoomInfoArea = React.createClass({
     propTypes: {
-      helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
+      helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired,
+      activeRoomStore:
+        React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
+      feedbackStore:
+        React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
+    },
+
+    onFeedbackSent: function() {
+      // We pass a tick to prevent React warnings regarding nested updates.
+      setTimeout(function() {
+        this.props.activeRoomStore.dispatchAction(new sharedActions.FeedbackComplete());
+      }.bind(this));
     },
 
     _renderCallToActionLink: function() {
       if (this.props.helper.isFirefox(navigator.userAgent)) {
         return (
           <a href={loop.config.learnMoreUrl} className="btn btn-info">
             {mozL10n.get("rooms_room_full_call_to_action_label", {
               clientShortname: mozL10n.get("clientShortname2")
@@ -50,63 +61,85 @@ loop.standaloneRoomViews = (function(moz
           return mozL10n.get("rooms_media_denied_message");
         case FAILURE_REASONS.EXPIRED_OR_INVALID:
           return mozL10n.get("rooms_unavailable_notification_message");
         default:
           return mozL10n.get("status_error");
       }
     },
 
-    _renderContent: function() {
+    render: function() {
       switch(this.props.roomState) {
         case ROOM_STATES.INIT:
-        case ROOM_STATES.READY:
-        case ROOM_STATES.ENDED: {
+        case ROOM_STATES.READY: {
           // XXX: In ENDED state, we should rather display the feedback form.
           return (
-            <button className="btn btn-join btn-info"
-                    onClick={this.props.joinRoom}>
-              {mozL10n.get("rooms_room_join_label")}
-            </button>
+            <div className="room-inner-info-area">
+              <button className="btn btn-join btn-info"
+                      onClick={this.props.joinRoom}>
+                {mozL10n.get("rooms_room_join_label")}
+              </button>
+            </div>
+          );
+        }
+        case ROOM_STATES.MEDIA_WAIT: {
+          var msg = mozL10n.get("call_progress_getting_media_description",
+                                {clientShortname: mozL10n.get("clientShortname2")});
+          // XXX Bug 1047040 will add images to help prompt the user.
+          return (
+            <div className="room-inner-info-area">
+              <p className="prompt-media-message">
+                {msg}
+              </p>
+            </div>
           );
         }
         case ROOM_STATES.JOINED:
         case ROOM_STATES.SESSION_CONNECTED: {
           return (
-            <p className="empty-room-message">
-              {mozL10n.get("rooms_only_occupant_label")}
-            </p>
+            <div className="room-inner-info-area">
+              <p className="empty-room-message">
+                {mozL10n.get("rooms_only_occupant_label")}
+              </p>
+            </div>
           );
         }
-        case ROOM_STATES.FULL:
+        case ROOM_STATES.FULL: {
           return (
-            <div>
+            <div className="room-inner-info-area">
               <p className="full-room-message">
                 {mozL10n.get("rooms_room_full_label")}
               </p>
               <p>{this._renderCallToActionLink()}</p>
             </div>
           );
-        case ROOM_STATES.FAILED:
+        }
+        case ROOM_STATES.ENDED: {
           return (
-            <p className="failed-room-message">
-              {this._getFailureString()}
-            </p>
+            <div className="ended-conversation">
+              <sharedViews.FeedbackView
+                feedbackStore={this.props.feedbackStore}
+                onAfterFeedbackReceived={this.onFeedbackSent}
+              />
+            </div>
           );
-        default:
+        }
+        case ROOM_STATES.FAILED: {
+          return (
+            <div className="room-inner-info-area">
+              <p className="failed-room-message">
+                {this._getFailureString()}
+              </p>
+            </div>
+          );
+        }
+        default: {
           return null;
+        }
       }
-    },
-
-    render: function() {
-      return (
-        <div className="room-inner-info-area">
-          {this._renderContent()}
-        </div>
-      );
     }
   });
 
   var StandaloneRoomHeader = React.createClass({
     render: function() {
       return (
         <header>
           <h1>{mozL10n.get("clientShortname2")}</h1>
@@ -149,16 +182,18 @@ loop.standaloneRoomViews = (function(moz
     mixins: [
       Backbone.Events,
       sharedMixins.RoomsAudioMixin
     ],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
+      feedbackStore:
+        React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
@@ -206,41 +241,73 @@ loop.standaloneRoomViews = (function(moz
           bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
+    /**
+     * Used to update the video container whenever the orientation or size of the
+     * display area changes.
+     */
+    updateVideoContainer: function() {
+      var localStreamParent = this._getElement('.local .OT_publisher');
+      var remoteStreamParent = this._getElement('.remote .OT_subscriber');
+      if (localStreamParent) {
+        localStreamParent.style.width = "100%";
+      }
+      if (remoteStreamParent) {
+        remoteStreamParent.style.height = "100%";
+      }
+    },
+
     componentDidMount: function() {
+      /**
+       * OT inserts inline styles into the markup. Using a listener for
+       * resize events helps us trigger a full width/height on the element
+       * so that they update to the correct dimensions.
+       * XXX: this should be factored as a mixin, bug 1104930
+       */
+      window.addEventListener('orientationchange', this.updateVideoContainer);
+      window.addEventListener('resize', this.updateVideoContainer);
+
       // Adding a class to the document body element from here to ease styling it.
       document.body.classList.add("is-standalone-room");
     },
 
     componentWillUnmount: function() {
       this.stopListening(this.props.activeRoomStore);
     },
 
     /**
      * Watches for when we transition to JOINED room state, so we can request
      * user media access.
      *
      * @param  {Object} nextProps (Unused)
      * @param  {Object} nextState Next state object.
      */
     componentWillUpdate: function(nextProps, nextState) {
-      if (this.state.roomState !== ROOM_STATES.JOINED &&
-          nextState.roomState === ROOM_STATES.JOINED) {
+      if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
+          nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this._getPublisherConfig(),
           getLocalElementFunc: this._getElement.bind(this, ".local"),
           getRemoteElementFunc: this._getElement.bind(this, ".remote")
         }));
       }
+
+      if (this.state.roomState !== ROOM_STATES.JOINED &&
+          nextState.roomState === ROOM_STATES.JOINED) {
+        // This forces the video size to update - creating the publisher
+        // first, and then connecting to the session doesn't seem to set the
+        // initial size correctly.
+        this.updateVideoContainer();
+      }
     },
 
     joinRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
     },
 
     leaveRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
@@ -279,17 +346,19 @@ loop.standaloneRoomViews = (function(moz
       });
 
       return (
         <div className="room-conversation-wrapper">
           <StandaloneRoomHeader />
           <StandaloneRoomInfoArea roomState={this.state.roomState}
                                   failureReason={this.state.failureReason}
                                   joinRoom={this.joinRoom}
-                                  helper={this.props.helper} />
+                                  helper={this.props.helper}
+                                  activeRoomStore={this.props.activeRoomStore}
+                                  feedbackStore={this.props.feedbackStore} />
           <div className="video-layout-wrapper">
             <div className="conversation room-conversation">
               <h2 className="room-name">{this.state.roomName}</h2>
               <div className="media nested">
                 <div className="video_wrapper remote_wrapper">
                   <div className="video_inner remote"></div>
                 </div>
                 <div className={localStreamClasses}></div>
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -685,19 +685,20 @@ loop.webapp = (function($, _, OT, mozL10
       this.props.conversation.off(null, null, this);
     },
 
     shouldComponentUpdate: function(nextProps, nextState) {
       // Only rerender if current state has actually changed
       return nextState.callStatus !== this.state.callStatus;
     },
 
-    callStatusSwitcher: function(status) {
+    resetCallStatus: function() {
+      this.props.feedbackStore.dispatchAction(new sharedActions.FeedbackComplete());
       return function() {
-        this.setState({callStatus: status});
+        this.setState({callStatus: "start"});
       }.bind(this);
     },
 
     /**
      * Renders the conversation views.
      */
     render: function() {
       switch (this.state.callStatus) {
@@ -739,17 +740,17 @@ loop.webapp = (function($, _, OT, mozL10
           );
         }
         case "end": {
           return (
             EndedConversationView({
               sdk: this.props.sdk, 
               conversation: this.props.conversation, 
               feedbackStore: this.props.feedbackStore, 
-              onAfterFeedbackReceived: this.callStatusSwitcher("start")}
+              onAfterFeedbackReceived: this.resetCallStatus()}
             )
           );
         }
         case "expired": {
           return (
             CallUrlExpiredView({helper: this.props.helper})
           );
         }
@@ -1005,16 +1006,17 @@ loop.webapp = (function($, _, OT, mozL10
                feedbackStore: this.props.feedbackStore}
             )
           );
         }
         case "room": {
           return (
             loop.standaloneRoomViews.StandaloneRoomView({
               activeRoomStore: this.props.activeRoomStore, 
+              feedbackStore: this.props.feedbackStore, 
               dispatcher: this.props.dispatcher, 
               helper: this.props.helper}
             )
           );
         }
         case "home": {
           return HomeView(null);
         }
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -685,19 +685,20 @@ loop.webapp = (function($, _, OT, mozL10
       this.props.conversation.off(null, null, this);
     },
 
     shouldComponentUpdate: function(nextProps, nextState) {
       // Only rerender if current state has actually changed
       return nextState.callStatus !== this.state.callStatus;
     },
 
-    callStatusSwitcher: function(status) {
+    resetCallStatus: function() {
+      this.props.feedbackStore.dispatchAction(new sharedActions.FeedbackComplete());
       return function() {
-        this.setState({callStatus: status});
+        this.setState({callStatus: "start"});
       }.bind(this);
     },
 
     /**
      * Renders the conversation views.
      */
     render: function() {
       switch (this.state.callStatus) {
@@ -739,17 +740,17 @@ loop.webapp = (function($, _, OT, mozL10
           );
         }
         case "end": {
           return (
             <EndedConversationView
               sdk={this.props.sdk}
               conversation={this.props.conversation}
               feedbackStore={this.props.feedbackStore}
-              onAfterFeedbackReceived={this.callStatusSwitcher("start")}
+              onAfterFeedbackReceived={this.resetCallStatus()}
             />
           );
         }
         case "expired": {
           return (
             <CallUrlExpiredView helper={this.props.helper} />
           );
         }
@@ -1005,16 +1006,17 @@ loop.webapp = (function($, _, OT, mozL10
                feedbackStore={this.props.feedbackStore}
             />
           );
         }
         case "room": {
           return (
             <loop.standaloneRoomViews.StandaloneRoomView
               activeRoomStore={this.props.activeRoomStore}
+              feedbackStore={this.props.feedbackStore}
               dispatcher={this.props.dispatcher}
               helper={this.props.helper}
             />
           );
         }
         case "home": {
           return <HomeView />;
         }
--- a/browser/components/loop/standalone/server.js
+++ b/browser/components/loop/standalone/server.js
@@ -20,19 +20,19 @@ function getConfigFile(req, res) {
     "loop.config = loop.config || {};",
     "loop.config.serverUrl = 'http://localhost:" + loopServerPort + "/v0';",
     "loop.config.feedbackApiUrl = '" + feedbackApiUrl + "';",
     "loop.config.feedbackProductName = '" + feedbackProductName + "';",
     // XXX Update with the real marketplace url once the FxOS Loop app is
     //     uploaded to the marketplace bug 1053424
     "loop.config.marketplaceUrl = 'http://fake-market.herokuapp.com/iframe-install.html'",
     "loop.config.brandWebsiteUrl = 'https://www.mozilla.org/firefox/';",
-    "loop.config.privacyWebsiteUrl = 'https://www.mozilla.org/privacy';",
+    "loop.config.privacyWebsiteUrl = 'https://www.mozilla.org/privacy/firefox-hello/';",
     "loop.config.learnMoreUrl = 'https://www.mozilla.org/hello/';",
-    "loop.config.legalWebsiteUrl = '/legal/terms';",
+    "loop.config.legalWebsiteUrl = 'https://www.mozilla.org/about/legal/terms/firefox-hello/';",
     "loop.config.fxosApp = loop.config.fxosApp || {};",
     "loop.config.fxosApp.name = 'Loop';",
     "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';",
     "loop.config.roomsSupportUrl = 'https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc';",
     "loop.config.guestSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';"
   ].join("\n"));
 }
 
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -72,26 +72,28 @@ describe("loop.store.ActiveRoomStore", f
     var fakeError;
 
     beforeEach(function() {
       sandbox.stub(console, "error");
 
       fakeError = new Error("fake");
 
       store.setStoreState({
-        roomState: ROOM_STATES.READY
+        roomState: ROOM_STATES.JOINED,
+        roomToken: "fakeToken",
+        sessionToken: "1627384950"
       });
     });
 
     it("should log the error", function() {
       store.roomFailure({error: fakeError});
 
       sinon.assert.calledOnce(console.error);
       sinon.assert.calledWith(console.error,
-        sinon.match(ROOM_STATES.READY), fakeError);
+        sinon.match(ROOM_STATES.JOINED), fakeError);
     });
 
     it("should set the state to `FULL` on server error room full", function() {
       fakeError.errno = SERVER_CODES.ROOM_FULL;
 
       store.roomFailure({error: fakeError});
 
       expect(store._storeState.roomState).eql(ROOM_STATES.FULL);
@@ -118,16 +120,45 @@ describe("loop.store.ActiveRoomStore", f
       "expired", function() {
         fakeError.errno = SERVER_CODES.EXPIRED;
 
         store.roomFailure({error: fakeError});
 
         expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
         expect(store._storeState.failureReason).eql(FAILURE_REASONS.EXPIRED_OR_INVALID);
       });
+
+    it("should reset the multiplexGum", function() {
+      store.roomFailure({error: fakeError});
+
+      sinon.assert.calledOnce(fakeMultiplexGum.reset);
+    });
+
+    it("should disconnect from the servers via the sdk", function() {
+      store.roomFailure({error: fakeError});
+
+      sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
+    });
+
+    it("should clear any existing timeout", function() {
+      sandbox.stub(window, "clearTimeout");
+      store._timeout = {};
+
+      store.roomFailure({error: fakeError});
+
+      sinon.assert.calledOnce(clearTimeout);
+    });
+
+    it("should call mozLoop.rooms.leave", function() {
+      store.roomFailure({error: fakeError});
+
+      sinon.assert.calledOnce(fakeMozLoop.rooms.leave);
+      sinon.assert.calledWithExactly(fakeMozLoop.rooms.leave,
+        "fakeToken", "1627384950");
+    });
   });
 
   describe("#setupWindowData", function() {
     var fakeToken, fakeRoomData;
 
     beforeEach(function() {
       fakeToken = "337-ff-54";
       fakeRoomData = {
@@ -228,16 +259,32 @@ describe("loop.store.ActiveRoomStore", f
         windowType: "room",
         token: "fakeToken"
       }));
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
     });
   });
 
+  describe("#feedbackComplete", function() {
+    it("should reset the room store state", function() {
+      var initialState = store.getInitialStoreState();
+      store.setStoreState({
+        roomState: ROOM_STATES.ENDED,
+        audioMuted: true,
+        videoMuted: true,
+        failureReason: "foo"
+      });
+
+      store.feedbackComplete(new sharedActions.FeedbackComplete());
+
+      expect(store.getStoreState()).eql(initialState);
+    });
+  });
+
   describe("#setupRoomInfo", function() {
     var fakeRoomInfo;
 
     beforeEach(function() {
       fakeRoomInfo = {
         roomName: "Its a room",
         roomOwner: "Me",
         roomToken: "fakeToken",
@@ -279,58 +326,68 @@ describe("loop.store.ActiveRoomStore", f
       var state = store.getStoreState();
       expect(state.roomName).eql(fakeRoomInfo.roomName);
       expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
       expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
     });
   });
 
   describe("#joinRoom", function() {
-    beforeEach(function() {
-      store.setStoreState({roomToken: "tokenFake"});
-    });
-
     it("should reset failureReason", function() {
       store.setStoreState({failureReason: "Test"});
 
       store.joinRoom();
 
       expect(store.getStoreState().failureReason).eql(undefined);
     });
 
+    it("should set the state to MEDIA_WAIT", function() {
+      store.setStoreState({roomState: ROOM_STATES.READY});
+
+      store.joinRoom();
+
+      expect(store.getStoreState().roomState).eql(ROOM_STATES.MEDIA_WAIT);
+    });
+  });
+
+  describe("#gotMediaPermission", function() {
+    beforeEach(function() {
+      store.setStoreState({roomToken: "tokenFake"});
+    });
+
     it("should call rooms.join on mozLoop", function() {
-      store.joinRoom();
+      store.gotMediaPermission();
 
       sinon.assert.calledOnce(fakeMozLoop.rooms.join);
       sinon.assert.calledWith(fakeMozLoop.rooms.join, "tokenFake");
     });
 
     it("should dispatch `JoinedRoom` on success", function() {
       var responseData = {
         apiKey: "keyFake",
         sessionToken: "14327659860",
         sessionId: "1357924680",
         expires: 8
       };
 
       fakeMozLoop.rooms.join.callsArgWith(1, null, responseData);
 
-      store.joinRoom();
+      store.gotMediaPermission();
 
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWith(dispatcher.dispatch,
         new sharedActions.JoinedRoom(responseData));
     });
 
     it("should dispatch `RoomFailure` on error", function() {
       var fakeError = new Error("fake");
 
       fakeMozLoop.rooms.join.callsArgWith(1, fakeError);
 
-      store.joinRoom();
+      store.gotMediaPermission();
 
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWith(dispatcher.dispatch,
         new sharedActions.RoomFailure({error: fakeError}));
     });
   });
 
   describe("#joinedRoom", function() {
@@ -369,16 +426,44 @@ describe("loop.store.ActiveRoomStore", f
 
       store.joinedRoom(actionData);
 
       sinon.assert.calledOnce(fakeSdkDriver.connectSession);
       sinon.assert.calledWithExactly(fakeSdkDriver.connectSession,
         actionData);
     });
 
+    it("should call mozLoop.rooms.get to get the room data if the roomName" +
+      "is not known", function() {
+        store.setStoreState({roomName: undefined});
+
+        store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
+
+        sinon.assert.calledOnce(fakeMozLoop.rooms.get);
+      });
+
+    it("should dispatch UpdateRoomInfo if mozLoop.rooms.get is successful",
+      function() {
+        var roomDetails = {
+          roomName: "fakeName",
+          roomUrl: "http://invalid",
+          roomOwner: "gavin"
+        };
+
+        fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
+
+        store.setStoreState({roomName: undefined});
+
+        store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.UpdateRoomInfo(roomDetails));
+      });
+
     it("should call mozLoop.rooms.refreshMembership before the expiresTime",
       function() {
         store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
 
         sandbox.clock.tick(fakeJoinedData.expires * 1000);
 
         sinon.assert.calledOnce(fakeMozLoop.rooms.refreshMembership);
         sinon.assert.calledWith(fakeMozLoop.rooms.refreshMembership,
@@ -541,17 +626,17 @@ describe("loop.store.ActiveRoomStore", f
       store.setStoreState({
         roomState: ROOM_STATES.JOINED,
         roomToken: "fakeToken",
         sessionToken: "1627384950"
       });
     });
 
     it("should reset the multiplexGum", function() {
-      store.leaveRoom();
+      store.windowUnload();
 
       sinon.assert.calledOnce(fakeMultiplexGum.reset);
     });
 
     it("should disconnect from the servers via the sdk", function() {
       store.windowUnload();
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
--- a/browser/components/loop/test/shared/feedbackStore_test.js
+++ b/browser/components/loop/test/shared/feedbackStore_test.js
@@ -100,9 +100,21 @@ describe("loop.store.FeedbackStore", fun
       store.once("change:feedbackState", function() {
         expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.FAILED);
         done();
       });
 
       store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
     });
   });
+
+  describe("feedbackComplete", function() {
+    it("should reset the store state", function() {
+      store.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
+
+      store.feedbackComplete();
+
+      expect(store.getStoreState()).eql({
+        feedbackState: FEEDBACK_STATES.INIT
+      });
+    });
+  });
 });
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -290,16 +290,24 @@ describe("loop.OTSdkDriver", function ()
     describe("accessAllowed", function() {
       it("should publish the stream if the connection is ready", function() {
         driver._sessionConnected = true;
 
         publisher.trigger("accessAllowed", fakeEvent);
 
         sinon.assert.calledOnce(session.publish);
       });
+
+      it("should dispatch a GotMediaPermission action", function() {
+        publisher.trigger("accessAllowed", fakeEvent);
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.GotMediaPermission());
+      });
     });
 
     describe("accessDenied", function() {
       it("should prevent the default event behavior", function() {
         publisher.trigger("accessDenied", fakeEvent);
 
         sinon.assert.calledOnce(fakeEvent.preventDefault);
       });
--- a/browser/components/loop/test/standalone/standaloneMozLoop_test.js
+++ b/browser/components/loop/test/standalone/standaloneMozLoop_test.js
@@ -71,16 +71,52 @@ describe("loop.StandaloneMozLoop", funct
 
     it("should return the value of the preference", function() {
       localStorage.setItem("fakePref", "fakeValue");
 
       expect(mozLoop.getLoopPref("fakePref")).eql("fakeValue");
     });
   });
 
+  describe("#rooms.get", function() {
+    it("should GET to the server", function() {
+      mozLoop.rooms.get("fakeToken", callback);
+
+      expect(requests).to.have.length.of(1);
+      expect(requests[0].url).eql(fakeBaseServerUrl + "/rooms/fakeToken");
+      expect(requests[0].method).eql("GET");
+    });
+
+    it("should call the callback with success parameters", function() {
+      mozLoop.rooms.get("fakeToken", callback);
+
+      var roomDetails = {
+        roomName: "fakeName",
+        roomUrl: "http://invalid",
+        roomOwner: "gavin"
+      };
+
+      requests[0].respond(200, {"Content-Type": "application/json"},
+        JSON.stringify(roomDetails));
+
+      sinon.assert.calledOnce(callback);
+      sinon.assert.calledWithExactly(callback, null, roomDetails);
+    });
+
+    it("should call the callback with failure parameters", function() {
+      mozLoop.rooms.get("fakeToken", callback);
+
+      requests[0].respond(401, {"Content-Type": "application/json"},
+                          JSON.stringify(fakeServerErrorDescription));
+      sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
+        return /HTTP 401 Unauthorized/.test(err.message);
+      }));
+    });
+  });
+
   describe("#rooms.join", function() {
     it("should POST to the server", function() {
       mozLoop.rooms.join("fakeToken", callback);
 
       expect(requests).to.have.length.of(1);
       expect(requests[0].url).eql(fakeBaseServerUrl + "/rooms/fakeToken");
       expect(requests[0].method).eql("POST");
 
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -5,40 +5,50 @@
 /* global loop, sinon */
 
 var expect = chai.expect;
 
 describe("loop.standaloneRoomViews", function() {
   "use strict";
 
   var ROOM_STATES = loop.store.ROOM_STATES;
+  var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
   var sharedActions = loop.shared.actions;
 
-  var sandbox, dispatcher, activeRoomStore, dispatch;
+  var sandbox, dispatcher, activeRoomStore, feedbackStore, dispatch;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     dispatcher = new loop.Dispatcher();
     dispatch = sandbox.stub(dispatcher, "dispatch");
     activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
       mozLoop: {},
       sdkDriver: {}
     });
+    feedbackStore = new loop.store.FeedbackStore(dispatcher, {
+      feedbackClient: {}
+    });
+
+    sandbox.useFakeTimers();
+
+    // Prevents audio request errors in the test console.
+    sandbox.useFakeXMLHttpRequest();
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
-  describe("standaloneRoomView", function() {
+  describe("StandaloneRoomView", function() {
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         loop.standaloneRoomViews.StandaloneRoomView({
           dispatcher: dispatcher,
           activeRoomStore: activeRoomStore,
+          feedbackStore: feedbackStore,
           helper: new loop.shared.utils.Helper()
         }));
     }
 
     function expectActionDispatched(view) {
       sinon.assert.calledOnce(dispatch);
       sinon.assert.calledWithExactly(dispatch,
         sinon.match.instanceOf(sharedActions.SetupStreamElements));
@@ -48,37 +58,59 @@ describe("loop.standaloneRoomViews", fun
       }));
       sinon.assert.calledWithExactly(dispatch, sinon.match(function(value) {
         return value.getRemoteElementFunc() ===
                view.getDOMNode().querySelector(".remote");
       }));
     }
 
     describe("#componentWillUpdate", function() {
-      it("should dispatch a `SetupStreamElements` action on room joined",
-        function() {
+      it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state " +
+        "is entered", function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
           var view = mountTestComponent();
 
-          sinon.assert.notCalled(dispatch);
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
+
+          expectActionDispatched(view);
+        });
 
-          activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
+      it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is " +
+        "re-entered", function() {
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
+          var view = mountTestComponent();
+
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
 
           expectActionDispatched(view);
         });
 
-      it("should dispatch a `SetupStreamElements` action on room rejoined",
-        function() {
-          activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
+      it("should updateVideoContainer when the JOINED state is entered", function() {
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
+
           var view = mountTestComponent();
 
+          sandbox.stub(view, "updateVideoContainer");
+
           activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
 
-          expectActionDispatched(view);
-        });
+          sinon.assert.calledOnce(view.updateVideoContainer);
+      });
+
+      it("should updateVideoContainer when the JOINED state is re-entered", function() {
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
+
+          var view = mountTestComponent();
+
+          sandbox.stub(view, "updateVideoContainer");
+
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
+
+          sinon.assert.calledOnce(view.updateVideoContainer);
+      });
     });
 
     describe("#publishStream", function() {
       var view;
 
       beforeEach(function() {
         view = mountTestComponent();
         view.setState({
@@ -138,16 +170,26 @@ describe("loop.standaloneRoomViews", fun
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
 
             expect(view.getDOMNode().querySelector(".empty-room-message"))
               .eql(null);
           });
       });
 
+      describe("Prompt media message", function() {
+        it("should display a prompt for user media on MEDIA_WAIT",
+          function() {
+            activeRoomStore.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
+
+            expect(view.getDOMNode().querySelector(".prompt-media-message"))
+              .not.eql(null);
+          });
+      });
+
       describe("Full room message", function() {
         it("should display a full room message on FULL",
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
 
             expect(view.getDOMNode().querySelector(".full-room-message"))
               .not.eql(null);
           });
@@ -242,11 +284,33 @@ describe("loop.standaloneRoomViews", fun
           activeRoomStore.setStoreState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
 
           TestUtils.Simulate.click(getLeaveButton(view));
 
           sinon.assert.calledOnce(dispatch);
           sinon.assert.calledWithExactly(dispatch, new sharedActions.LeaveRoom());
         });
       });
+
+      describe("Feedback", function() {
+        beforeEach(function() {
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
+        });
+
+        it("should display a feedback form when the user leaves the room",
+          function() {
+            expect(view.getDOMNode().querySelector(".faces")).not.eql(null);
+          });
+
+        it("should dispatch a `FeedbackComplete` action after feedback is sent",
+          function() {
+            feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
+
+            sandbox.clock.tick(
+              loop.shared.views.WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS * 1000 + 1000);
+
+            sinon.assert.calledOnce(dispatch);
+            sinon.assert.calledWithExactly(dispatch, new sharedActions.FeedbackComplete());
+          });
+      });
     });
   });
 });
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -12,29 +12,25 @@ describe("loop.webapp", function() {
 
   var sharedActions = loop.shared.actions;
   var sharedModels = loop.shared.models,
       sharedViews = loop.shared.views,
       sharedUtils = loop.shared.utils,
       standaloneMedia = loop.standaloneMedia,
       sandbox,
       notifications,
-      feedbackApiClient,
       stubGetPermsAndCacheMedia,
       fakeAudioXHR,
       dispatcher,
       feedbackStore;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     dispatcher = new loop.Dispatcher();
     notifications = new sharedModels.NotificationCollection();
-    feedbackApiClient = new loop.FeedbackAPIClient("http://invalid", {
-      product: "Loop"
-    });
     feedbackStore = new loop.store.FeedbackStore(dispatcher, {
       feedbackClient: {}
     });
 
     stubGetPermsAndCacheMedia = sandbox.stub(
       loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia");
 
     fakeAudioXHR = {
@@ -645,25 +641,25 @@ describe("loop.webapp", function() {
 
   describe("WebappRootView", function() {
     var helper, sdk, conversationModel, client, props, standaloneAppStore;
     var activeRoomStore;
 
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         loop.webapp.WebappRootView({
-        client: client,
-        helper: helper,
-        notifications: notifications,
-        sdk: sdk,
-        conversation: conversationModel,
-        feedbackApiClient: feedbackApiClient,
-        standaloneAppStore: standaloneAppStore,
-        activeRoomStore: activeRoomStore
-      }));
+          client: client,
+          helper: helper,
+          notifications: notifications,
+          sdk: sdk,
+          conversation: conversationModel,
+          standaloneAppStore: standaloneAppStore,
+          activeRoomStore: activeRoomStore,
+          feedbackStore: feedbackStore
+        }));
     }
 
     beforeEach(function() {
       helper = new sharedUtils.Helper();
       sdk = {
         checkSystemRequirements: function() { return true; }
       };
       conversationModel = new sharedModels.ConversationModel({}, {
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -78,16 +78,19 @@ const kRoomUpdates = {
     }, {
       displayName: "Alexis",
       account: "alexis@example.com",
       roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb"
     },  {
       displayName: "Ruharb",
       roomConnectionId: "5de6281c-6568-455f-af08-c0b0a973100e"
     }]
+  },
+  "5": {
+    deleted: true
   }
 };
 
 const kCreateRoomProps = {
   roomName: "UX Discussion",
   expiresIn: 5,
   roomOwner: "Alexis",
   maxSize: 2
@@ -113,16 +116,17 @@ const normalizeRoom = function(room) {
 
 const compareRooms = function(room1, room2) {
   Assert.deepEqual(normalizeRoom(room1), normalizeRoom(room2));
 };
 
 // LoopRooms emits various events. Test if they work as expected here.
 let gExpectedAdds = [];
 let gExpectedUpdates = [];
+let gExpectedDeletes = [];
 let gExpectedJoins = {};
 let gExpectedLeaves = {};
 
 const onRoomAdded = function(e, room) {
   let expectedIds = gExpectedAdds.map(room => room.roomToken);
   let idx = expectedIds.indexOf(room.roomToken);
   Assert.ok(idx > -1, "Added room should be expected");
   let expected = gExpectedAdds[idx];
@@ -131,16 +135,22 @@ const onRoomAdded = function(e, room) {
 };
 
 const onRoomUpdated = function(e, room) {
   let idx = gExpectedUpdates.indexOf(room.roomToken);
   Assert.ok(idx > -1, "Updated room should be expected");
   gExpectedUpdates.splice(idx, 1);
 };
 
+const onRoomDeleted = function(e, room) {
+  let idx = gExpectedDeletes.indexOf(room.roomToken);
+  Assert.ok(idx > -1, "Deleted room should be expected");
+  gExpectedDeletes.splice(idx, 1);
+}
+
 const onRoomJoined = function(e, roomToken, participant) {
   let participants = gExpectedJoins[roomToken];
   Assert.ok(participants, "Participant should be expected to join");
   let idx = participants.indexOf(participant.roomConnectionId);
   Assert.ok(idx > -1, "Participant should be expected to join");
   participants.splice(idx, 1);
   if (!participants.length) {
     delete gExpectedJoins[roomToken];
@@ -186,16 +196,17 @@ add_task(function* setup_server() {
       Assert.deepEqual(data, kCreateRoomProps);
 
       res.write(JSON.stringify(kCreateRoomData));
     } else {
       if (req.queryString) {
         let qs = parseQueryString(req.queryString);
         let room = kRooms.get("_nxD4V4FflQ");
         room.participants = kRoomUpdates[qs.version].participants;
+        room.deleted = kRoomUpdates[qs.version].deleted;
         res.write(JSON.stringify([room]));
       } else {
         res.write(JSON.stringify([...kRooms.values()]));
       }
     }
 
     res.processAsync();
     res.finish();
@@ -381,37 +392,46 @@ add_task(function* test_leaveRoom() {
 
 // Test if renaming a room works as expected.
 add_task(function* test_renameRoom() {
   let roomToken = "_nxD4V4FflQ";
   let renameData = yield LoopRooms.promise("rename", roomToken, "fakeName");
   Assert.equal(renameData.roomName, "fakeName");
 });
 
+add_task(function* test_roomDeleteNotifications() {
+  gExpectedDeletes.push("_nxD4V4FflQ");
+  roomsPushNotification("5");
+  yield waitForCondition(() => gExpectedDeletes.length === 0);
+});
+
 // Test if the event emitter implementation doesn't leak and is working as expected.
 add_task(function* () {
   Assert.strictEqual(gExpectedAdds.length, 0, "No room additions should be expected anymore");
   Assert.strictEqual(gExpectedUpdates.length, 0, "No room updates should be expected anymore");
+  Assert.strictEqual(gExpectedDeletes.length, 0, "No room deletes should be expected anymore");
  });
 
 function run_test() {
   setupFakeLoopServer();
 
   LoopRooms.on("add", onRoomAdded);
   LoopRooms.on("update", onRoomUpdated);
+  LoopRooms.on("delete", onRoomDeleted);
   LoopRooms.on("joined", onRoomJoined);
   LoopRooms.on("left", onRoomLeft);
 
   do_register_cleanup(function () {
     // Revert original Chat.open implementation
     Chat.open = openChatOrig;
 
     Services.prefs.clearUserPref("loop.fxa_oauth.profile");
     Services.prefs.clearUserPref("loop.fxa_oauth.tokendata");
 
     LoopRooms.off("add", onRoomAdded);
     LoopRooms.off("update", onRoomUpdated);
+    LoopRooms.off("delete", onRoomDeleted);
     LoopRooms.off("joined", onRoomJoined);
     LoopRooms.off("left", onRoomLeft);
   });
 
   run_next_test();
 }
--- a/browser/components/safebrowsing/content/test/browser_bug400731.js
+++ b/browser/components/safebrowsing/content/test/browser_bug400731.js
@@ -15,32 +15,40 @@ function test() {
         gBrowser.removeTabsProgressListener(this);
         window.addEventListener("DOMContentLoaded", testMalware, true);
       }
     }
   });
   content.location = "http://www.itisatrap.org/firefox/its-an-attack.html";
 }
 
-function testMalware() {
+function testMalware(event) {
+  if (event.target != gBrowser.selectedBrowser.contentDocument) {
+    return;
+  }
+
   window.removeEventListener("DOMContentLoaded", testMalware, true);
 
   // Confirm that "Ignore this warning" is visible - bug 422410
   var el = content.document.getElementById("ignoreWarningButton");
   ok(el, "Ignore warning button should be present for malware");
   
   var style = content.getComputedStyle(el, null);
   is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for malware");
   
   // Now launch the phishing test
   window.addEventListener("DOMContentLoaded", testPhishing, true);
   content.location = "http://www.itisatrap.org/firefox/its-a-trap.html";
 }
 
-function testPhishing() {
+function testPhishing(event) {
+  if (event.target != gBrowser.selectedBrowser.contentDocument) {
+    return;
+  }
+
   window.removeEventListener("DOMContentLoaded", testPhishing, true);
   
   var el = content.document.getElementById("ignoreWarningButton");
   ok(el, "Ignore warning button should be present for phishing");
   
   var style = content.getComputedStyle(el, null);
   is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for phishing");
   
--- a/browser/devtools/canvasdebugger/canvasdebugger.js
+++ b/browser/devtools/canvasdebugger/canvasdebugger.js
@@ -1248,18 +1248,19 @@ function getThumbnailForCall(thumbnails,
 }
 
 /**
  * Opens/selects the debugger in this toolbox and jumps to the specified
  * file name and line number.
  */
 function viewSourceInDebugger(url, line) {
   let showSource = ({ DebuggerView }) => {
-    if (DebuggerView.Sources.containsValue(url)) {
-      DebuggerView.setEditorLocation(url, line, { noDebug: true }).then(() => {
+    let item = DebuggerView.Sources.getItemForAttachment(a => a.source.url === url);
+    if (item) {
+      DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true }).then(() => {
         window.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
       }, () => {
         window.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
       });
     }
   }
 
   // If the Debugger was already open, switch to it and try to show the
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js
@@ -59,16 +59,16 @@ function ifTestingSupported() {
 
   let jumpedToSource = once(window, EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
   EventUtils.sendMouseEvent({ type: "mousedown" }, $(".call-item-stack-fn-location", callItem.target));
   yield jumpedToSource;
 
   let toolbox = yield gDevTools.getToolbox(target);
   let { panelWin: { DebuggerView: view } } = toolbox.getPanel("jsdebugger");
 
-  is(view.Sources.selectedValue, SIMPLE_CANVAS_DEEP_STACK_URL,
+  is(view.Sources.selectedValue, getSourceActor(view.Sources, SIMPLE_CANVAS_DEEP_STACK_URL),
     "The expected source was shown in the debugger.");
   is(view.editor.getCursor().line, 25,
     "The expected source line is highlighted in the debugger.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js
@@ -34,16 +34,16 @@ function ifTestingSupported() {
 
   let jumpedToSource = once(window, EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
   EventUtils.sendMouseEvent({ type: "mousedown" }, $(".call-item-location", callItem.target));
   yield jumpedToSource;
 
   let toolbox = yield gDevTools.getToolbox(target);
   let { panelWin: { DebuggerView: view } } = toolbox.getPanel("jsdebugger");
 
-  is(view.Sources.selectedValue, SIMPLE_CANVAS_DEEP_STACK_URL,
+  is(view.Sources.selectedValue, getSourceActor(view.Sources, SIMPLE_CANVAS_DEEP_STACK_URL),
     "The expected source was shown in the debugger.");
   is(view.editor.getCursor().line, 23,
     "The expected source line is highlighted in the debugger.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/canvasdebugger/test/head.js
+++ b/browser/devtools/canvasdebugger/test/head.js
@@ -265,8 +265,13 @@ function evalInDebuggee (script) {
     }
 
     mm.removeMessageListener("devtools:test:eval:response", handler);
     deferred.resolve(data.value);
   }
 
   return deferred.promise;
 }
+
+function getSourceActor(aSources, aURL) {
+  let item = aSources.getItemForAttachment(a => a.source.url === aURL);
+  return item ? item.value : null;
+}
--- a/browser/devtools/debugger/debugger-commands.js
+++ b/browser/devtools/debugger/debugger-commands.js
@@ -31,28 +31,42 @@ exports.items = [];
 function getAllBreakpoints(dbg) {
   let breakpoints = [];
   let sources = dbg._view.Sources;
   let { trimUrlLength: trim } = dbg.panelWin.SourceUtils;
 
   for (let source of sources) {
     for (let { attachment: breakpoint } of source) {
       breakpoints.push({
-        url: source.value,
+        url: source.attachment.source.url,
         label: source.attachment.label + ":" + breakpoint.line,
         lineNumber: breakpoint.line,
         lineText: breakpoint.text,
         truncatedLineText: trim(breakpoint.text, MAX_LINE_TEXT_LENGTH, "end")
       });
     }
   }
 
   return breakpoints;
 }
 
+function getAllSources(dbg) {
+  if (!dbg) {
+    return [];
+  }
+
+  let items = dbg._view.Sources.items;
+  return items
+    .filter(item => !!item.attachment.source.url)
+    .map(item => ({
+      name: item.attachment.source.url,
+      value: item.attachment.source.actor
+    }));
+}
+
 /**
  * 'break' command
  */
 exports.items.push({
   name: "break",
   description: gcli.lookup("breakDesc"),
   manual: gcli.lookup("breakManual")
 });
@@ -137,22 +151,18 @@ exports.items.push({
 exports.items.push({
   name: "break add line",
   description: gcli.lookup("breakaddlineDesc"),
   params: [
     {
       name: "file",
       type: {
         name: "selection",
-        data: function(context) {
-          let dbg = getPanel(context, "jsdebugger");
-          if (dbg) {
-            return dbg._view.Sources.values;
-          }
-          return [];
+        lookup: function(context) {
+          return getAllSources(getPanel(context, "jsdebugger"));
         }
       },
       description: gcli.lookup("breakaddlineFileDesc")
     },
     {
       name: "line",
       type: { name: "number", min: 1, step: 10 },
       description: gcli.lookup("breakaddlineLineDesc")
@@ -161,17 +171,20 @@ exports.items.push({
   returnType: "string",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
 
     let deferred = context.defer();
-    let position = { url: args.file, line: args.line };
+    let item = dbg._view.Sources.getItemForAttachment(a => {
+      return a.source && a.source.actor === args.file;
+    })
+    let position = { actor: item.value, line: args.line };
 
     dbg.addBreakpoint(position).then(() => {
       deferred.resolve(gcli.lookup("breakaddAdded"));
     }, aError => {
       deferred.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
     });
 
     return deferred.promise;
@@ -206,18 +219,23 @@ exports.items.push({
   ],
   returnType: "string",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
 
+    let source = dbg._view.Sources.getItemForAttachment(a => {
+      return a.source && a.source.url === args.breakpoint.url
+    });
+
     let deferred = context.defer();
-    let position = { url: args.breakpoint.url, line: args.breakpoint.lineNumber };
+    let position = { actor: source.attachment.source.actor,
+                     line: args.breakpoint.lineNumber };
 
     dbg.removeBreakpoint(position).then(() => {
       deferred.resolve(gcli.lookup("breakdelRemoved"));
     }, () => {
       deferred.resolve(gcli.lookup("breakNotFound"));
     });
 
     return deferred.promise;
@@ -385,24 +403,24 @@ exports.items.push({
   params: [],
   returnType: "dom",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerClosed");
     }
 
-    let sources = dbg._view.Sources.values;
+    let sources = getAllSources(dbg);
     let doc = context.environment.chromeDocument;
     let div = createXHTMLElement(doc, "div");
     let ol = createXHTMLElement(doc, "ol");
 
     sources.forEach(source => {
       let li = createXHTMLElement(doc, "li");
-      li.textContent = source;
+      li.textContent = source.name;
       ol.appendChild(li);
     });
     div.appendChild(ol);
 
     return div;
   }
 });
 
@@ -428,22 +446,18 @@ exports.items.push({
   exports.items.push({
     name: "dbg " + cmd.name,
     description: lookup("Desc"),
     params: [
       {
         name: "source",
         type: {
           name: "selection",
-          data: function(context) {
-            let dbg = getPanel(context, "jsdebugger");
-            if (dbg) {
-              return dbg._view.Sources.values;
-            }
-            return [];
+          lookup: function(context) {
+            return getAllSources(getPanel(context, "jsdebugger"));
           }
         },
         description: lookup("SourceDesc"),
         defaultValue: null
       },
       {
         name: "glob",
         type: "string",
@@ -467,17 +481,17 @@ exports.items.push({
       const { promise, resolve, reject } = context.defer();
       const { activeThread } = dbg._controller;
       const globRegExp = args.glob ? globToRegExp(args.glob) : null;
 
       // Filter the sources down to those that we will need to black box.
 
       function shouldBlackBox(source) {
         var value = globRegExp && globRegExp.test(source.url)
-          || args.source && source.url == args.source;
+          || args.source && source.actor == args.source;
         return args.invert ? !value : value;
       }
 
       const toBlackBox = [s.attachment.source
                           for (s of dbg._view.Sources.items)
                           if (shouldBlackBox(s.attachment.source))];
 
       // If we aren't black boxing any sources, bail out now.
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -659,20 +659,22 @@ StackFrames.prototype = {
 
   /**
    * Fill the StackFrames view with the frames we have in the cache, compressing
    * frames which have black boxed sources into single frames.
    */
   _refillFrames: function() {
     // Make sure all the previous stackframes are removed before re-adding them.
     DebuggerView.StackFrames.empty();
+
     for (let frame of this.activeThread.cachedFrames) {
-      let { depth, where: { url, line }, source } = frame;
+      let { depth, source, where: { line } } = frame;
+
       let isBlackBoxed = source ? this.activeThread.source(source).isBlackBoxed : false;
-      let location = NetworkHelper.convertToUnicode(unescape(url));
+      let location = NetworkHelper.convertToUnicode(unescape(source.url || source.introductionUrl));
       let title = StackFrameUtils.getFrameTitle(frame);
       DebuggerView.StackFrames.addFrame(title, location, line, depth, isBlackBoxed);
     }
 
     DebuggerView.StackFrames.selectedDepth = Math.max(this.currentFrameDepth, 0);
     DebuggerView.StackFrames.dirty = this.activeThread.moreFrames;
 
     window.emit(EVENTS.AFTER_FRAMES_REFILLED);
@@ -757,32 +759,32 @@ StackFrames.prototype = {
   selectFrame: function(aDepth) {
     // Make sure the frame at the specified depth exists first.
     let frame = this.activeThread.cachedFrames[this.currentFrameDepth = aDepth];
     if (!frame) {
       return;
     }
 
     // Check if the frame does not represent the evaluation of debuggee code.
-    let { environment, where } = frame;
+    let { environment, where, source } = frame;
     if (!environment) {
       return;
     }
 
     // Don't change the editor's location if the execution was paused by a
     // public client evaluation. This is useful for adding overlays on
     // top of the editor, like a variable inspection popup.
     let isClientEval = this._currentFrameDescription == FRAME_TYPE.PUBLIC_CLIENT_EVAL;
     let isPopupShown = DebuggerView.VariableBubble.contentsShown();
     if (!isClientEval && !isPopupShown) {
       // Move the editor's caret to the proper url and line.
-      DebuggerView.setEditorLocation(where.url, where.line);
+      DebuggerView.setEditorLocation(source.actor, where.line);
     } else {
       // Highlight the line where the execution is paused in the editor.
-      DebuggerView.setEditorLocation(where.url, where.line, { noCaret: true });
+      DebuggerView.setEditorLocation(source.actor, where.line, { noCaret: true });
     }
 
     // Highlight the breakpoint at the line and column if it exists.
     DebuggerView.Sources.highlightBreakpointAtCursor();
 
     // Don't display the watch expressions textbox inputs in the pane.
     DebuggerView.WatchExpressions.toggleContents(false);
 
@@ -928,16 +930,17 @@ StackFrames.prototype = {
   _handleConditionalBreakpoint: Task.async(function*() {
     if (gClient.mainRoot.traits.conditionalBreakpoints) {
       return;
     }
     let breakLocation = this._currentBreakpointLocation;
     if (!breakLocation) {
       return;
     }
+
     let breakpointPromise = DebuggerController.Breakpoints._getAdded(breakLocation);
     if (!breakpointPromise) {
       return;
     }
     let breakpointClient = yield breakpointPromise;
     let conditionalExpression = breakpointClient.conditionalExpression;
     if (!conditionalExpression) {
       return;
@@ -1240,18 +1243,18 @@ SourceScripts.prototype = {
 
     // Signal that sources have been added.
     window.emit(EVENTS.SOURCES_ADDED);
   },
 
   /**
    * Handler for the debugger client's 'blackboxchange' notification.
    */
-  _onBlackBoxChange: function (aEvent, { url, isBlackBoxed }) {
-    const item = DebuggerView.Sources.getItemByValue(url);
+  _onBlackBoxChange: function (aEvent, { actor, isBlackBoxed }) {
+    const item = DebuggerView.Sources.getItemByValue(actor);
     if (item) {
       item.prebuiltNode.classList.toggle("black-boxed", isBlackBoxed);
     }
     DebuggerView.Sources.updateToolbarButtonsState();
     DebuggerView.maybeShowBlackBoxMessage();
   },
 
   /**
@@ -1307,23 +1310,23 @@ SourceScripts.prototype = {
     // Only use the existing promise if it is pretty printed.
     let textPromise = this._cache.get(aSource.url);
     if (textPromise && textPromise.pretty === wantPretty) {
       return textPromise;
     }
 
     const deferred = promise.defer();
     deferred.promise.pretty = wantPretty;
-    this._cache.set(aSource.url, deferred.promise);
+    this._cache.set(aSource.actor, deferred.promise);
 
     const afterToggle = ({ error, message, source: text, contentType }) => {
       if (error) {
         // Revert the rejected promise from the cache, so that the original
         // source's text may be shown when the source is selected.
-        this._cache.set(aSource.url, textPromise);
+        this._cache.set(aSource.actor, textPromise);
         deferred.reject([aSource, message || error]);
         return;
       }
       deferred.resolve([aSource, text, contentType]);
     };
 
     if (wantPretty) {
       sourceClient.prettyPrint(Prefs.editorTabSize, afterToggle);
@@ -1355,23 +1358,23 @@ SourceScripts.prototype = {
    * @param number aDelay [optional]
    *        The amount of time it takes to consider a source slow to fetch.
    *        If unspecified, it defaults to a predefined value.
    * @return object
    *         A promise that is resolved after the source text has been fetched.
    */
   getText: function(aSource, aOnTimeout, aDelay = FETCH_SOURCE_RESPONSE_DELAY) {
     // Fetch the source text only once.
-    let textPromise = this._cache.get(aSource.url);
+    let textPromise = this._cache.get(aSource.actor);
     if (textPromise) {
       return textPromise;
     }
 
     let deferred = promise.defer();
-    this._cache.set(aSource.url, deferred.promise);
+    this._cache.set(aSource.actor, deferred.promise);
 
     // If the source text takes a long time to fetch, invoke a callback.
     if (aOnTimeout) {
       var fetchTimeout = window.setTimeout(() => aOnTimeout(aSource), aDelay);
     }
 
     // Get the source text from the active thread.
     this.activeThread.source(aSource).source(({ error, source: text, contentType }) => {
@@ -1392,52 +1395,52 @@ SourceScripts.prototype = {
    * Starts fetching all the sources, silently.
    *
    * @param array aUrls
    *        The urls for the sources to fetch. If fetching a source's text
    *        takes too long, it will be discarded.
    * @return object
    *         A promise that is resolved after source texts have been fetched.
    */
-  getTextForSources: function(aUrls) {
+  getTextForSources: function(aActors) {
     let deferred = promise.defer();
-    let pending = new Set(aUrls);
+    let pending = new Set(aActors);
     let fetched = [];
 
     // Can't use promise.all, because if one fetch operation is rejected, then
     // everything is considered rejected, thus no other subsequent source will
     // be getting fetched. We don't want that. Something like Q's allSettled
     // would work like a charm here.
 
     // Try to fetch as many sources as possible.
-    for (let url of aUrls) {
-      let sourceItem = DebuggerView.Sources.getItemByValue(url);
+    for (let actor of aActors) {
+      let sourceItem = DebuggerView.Sources.getItemByValue(actor);
       let sourceForm = sourceItem.attachment.source;
       this.getText(sourceForm, onTimeout).then(onFetch, onError);
     }
 
     /* Called if fetching a source takes too long. */
     function onTimeout(aSource) {
       onError([aSource]);
     }
 
     /* Called if fetching a source finishes successfully. */
     function onFetch([aSource, aText, aContentType]) {
       // If fetching the source has previously timed out, discard it this time.
-      if (!pending.has(aSource.url)) {
+      if (!pending.has(aSource.actor)) {
         return;
       }
-      pending.delete(aSource.url);
-      fetched.push([aSource.url, aText, aContentType]);
+      pending.delete(aSource.actor);
+      fetched.push([aSource.actor, aText, aContentType]);
       maybeFinish();
     }
 
     /* Called if fetching a source failed because of an error. */
     function onError([aSource, aError]) {
-      pending.delete(aSource.url);
+      pending.delete(aSource.actor);
       maybeFinish();
     }
 
     /* Called every time something interesting happens while fetching sources. */
     function maybeFinish() {
       if (pending.size == 0) {
         // Sort the fetched sources alphabetically by their url.
         deferred.resolve(fetched.sort(([aFirst], [aSecond]) => aFirst > aSecond));
@@ -1773,17 +1776,17 @@ EventListeners.prototype = {
     let deferred = promise.defer();
 
     gThreadClient.pauseGrip(aFunction).getDefinitionSite(aResponse => {
       if (aResponse.error) {
         // Don't make this error fatal, because it would break the entire events pane.
         const msg = "Error getting function definition site: " + aResponse.message;
         DevToolsUtils.reportException("_getDefinitionSite", msg);
       }
-      deferred.resolve(aResponse.url);
+      deferred.resolve(aResponse.source.url);
     });
 
     return deferred.promise;
   }
 };
 
 /**
  * Handles all the breakpoints in the current debugger.
@@ -1833,85 +1836,88 @@ Breakpoints.prototype = {
 
   /**
    * Event handler for new breakpoints that come from the editor.
    *
    * @param number aLine
    *        Line number where breakpoint was set.
    */
   _onEditorBreakpointAdd: Task.async(function*(_, aLine) {
-    let url = DebuggerView.Sources.selectedValue;
-    let location = { url: url, line: aLine + 1 };
-    let breakpointClient = yield this.addBreakpoint(location, { noEditorUpdate: true });
+    let actor = DebuggerView.Sources.selectedValue;
+    let location = { actor: actor, line: aLine + 1 };
 
     // Initialize the breakpoint, but don't update the editor, since this
-    // callback is invoked because a breakpoint was added in the editor itself.
-    this.addBreakpoint(location, { noEditorUpdate: true }).then(aBreakpointClient => {
-      // If the breakpoint client has a "requestedLocation" attached, then
-      // the original requested placement for the breakpoint wasn't accepted.
-      // In this case, we need to update the editor with the new location.
-      if (breakpointClient.requestedLocation) {
-        DebuggerView.editor.moveBreakpoint(
-          breakpointClient.requestedLocation.line - 1,
-          breakpointClient.location.line - 1
-        );
-      }
-      // Notify that we've shown a breakpoint in the source editor.
-      window.emit(EVENTS.BREAKPOINT_SHOWN_IN_EDITOR);
-    });
+    // callback is invoked because a breakpoint was added in the
+    // editor itself.
+    let breakpointClient = yield this.addBreakpoint(location, { noEditorUpdate: true });
+
+    // If the breakpoint client has a "requestedLocation" attached, then
+    // the original requested placement for the breakpoint wasn't accepted.
+    // In this case, we need to update the editor with the new location.
+    if (breakpointClient.requestedLocation) {
+      DebuggerView.editor.moveBreakpoint(
+        breakpointClient.requestedLocation.line - 1,
+        breakpointClient.location.line - 1
+      );
+    }
+
+    // Notify that we've shown a breakpoint in the source editor.
+    window.emit(EVENTS.BREAKPOINT_SHOWN_IN_EDITOR);
   }),
 
   /**
    * Event handler for breakpoints that are removed from the editor.
    *
    * @param number aLine
    *        Line number where breakpoint was removed.
    */
   _onEditorBreakpointRemove: Task.async(function*(_, aLine) {
-    let url = DebuggerView.Sources.selectedValue;
-    let location = { url: url, line: aLine + 1 };
+    let actor = DebuggerView.Sources.selectedValue;
+    let location = { actor: actor, line: aLine + 1 };
     yield this.removeBreakpoint(location, { noEditorUpdate: true });
 
     // Notify that we've hidden a breakpoint in the source editor.
     window.emit(EVENTS.BREAKPOINT_HIDDEN_IN_EDITOR);
   }),
 
   /**
    * Update the breakpoints in the editor view. This function takes the list of
    * breakpoints in the debugger and adds them back into the editor view.
    * This is invoked when the selected script is changed, or when new sources
    * are received via the _onNewSource and _onSourcesAdded event listeners.
    */
   updateEditorBreakpoints: Task.async(function*() {
     for (let breakpointPromise of this._addedOrDisabled) {
       let breakpointClient = yield breakpointPromise;
-      let currentSourceUrl = DebuggerView.Sources.selectedValue;
-      let breakpointUrl = breakpointClient.location.url;
+      let location = breakpointClient.location;
+      let currentSourceActor = DebuggerView.Sources.selectedValue;
+      let sourceActor = DebuggerView.Sources.getActorForLocation(location);
 
-      // Update the view only if the breakpoint is in the currently shown source.
-      if (currentSourceUrl == breakpointUrl) {
+      // Update the view only if the breakpoint is in the currently
+      // shown source.
+      if (currentSourceActor === sourceActor) {
         yield this._showBreakpoint(breakpointClient, { noPaneUpdate: true });
       }
     }
   }),
 
   /**
    * Update the breakpoints in the pane view. This function takes the list of
    * breakpoints in the debugger and adds them back into the breakpoints pane.
    * This is invoked when new sources are received via the _onNewSource and
    * _onSourcesAdded event listeners.
    */
   updatePaneBreakpoints: Task.async(function*() {
     for (let breakpointPromise of this._addedOrDisabled) {
       let breakpointClient = yield breakpointPromise;
       let container = DebuggerView.Sources;
-      let breakpointUrl = breakpointClient.location.url;
+      let sourceActor = breakpointClient.location.actor;
 
       // Update the view only if the breakpoint exists in a known source.
-      if (container.containsValue(breakpointUrl)) {
+      if (container.containsValue(sourceActor)) {
         yield this._showBreakpoint(breakpointClient, { noEditorUpdate: true });
       }
     }
   }),
 
   /**
    * Add a breakpoint.
    *
@@ -1951,18 +1957,21 @@ Breakpoints.prototype = {
     }
 
     let deferred = promise.defer();
 
     // Remember the breakpoint initialization promise in the store.
     let identifier = this.getIdentifier(aLocation);
     this._added.set(identifier, deferred.promise);
 
-    // Try adding the breakpoint.
-    gThreadClient.setBreakpoint(aLocation, Task.async(function*(aResponse, aBreakpointClient) {
+    let source = gThreadClient.source(
+      DebuggerView.Sources.getItemByValue(aLocation.actor).attachment.source
+    );
+
+    source.setBreakpoint(aLocation, Task.async(function*(aResponse, aBreakpointClient) {
       // If the breakpoint response has an "actualLocation" attached, then
       // the original requested placement for the breakpoint wasn't accepted.
       if (aResponse.actualLocation) {
         // Remember the initialization promise for the new location instead.
         let oldIdentifier = identifier;
         let newIdentifier = identifier = this.getIdentifier(aResponse.actualLocation);
         this._added.delete(oldIdentifier);
         this._added.set(newIdentifier, deferred.promise);
@@ -1983,28 +1992,31 @@ Breakpoints.prototype = {
             condition
           );
         }
       }
 
       if (aResponse.actualLocation) {
         // Store the originally requested location in case it's ever needed
         // and update the breakpoint client with the actual location.
+        let actualLoc = aResponse.actualLocation;
         aBreakpointClient.requestedLocation = aLocation;
-        aBreakpointClient.location = aResponse.actualLocation;
+        aBreakpointClient.location = actualLoc;
+        aBreakpointClient.location.actor = actualLoc.source ? actualLoc.source.actor : null;
       }
 
       // Preserve information about the breakpoint's line text, to display it
       // in the sources pane without requiring fetching the source (for example,
       // after the target navigated). Note that this will get out of sync
       // if the source text contents change.
       let line = aBreakpointClient.location.line - 1;
       aBreakpointClient.text = DebuggerView.editor.getText(line).trim();
 
-      // Show the breakpoint in the editor and breakpoints pane, and resolve.
+      // Show the breakpoint in the editor and breakpoints pane, and
+      // resolve.
       yield this._showBreakpoint(aBreakpointClient, aOptions);
 
       // Notify that we've added a breakpoint.
       window.emit(EVENTS.BREAKPOINT_ADDED, aBreakpointClient);
       deferred.resolve(aBreakpointClient);
     }.bind(this)));
 
     return deferred.promise;
@@ -2137,59 +2149,61 @@ Breakpoints.prototype = {
     // so we need to update the store
     this._added.set(this.getIdentifier(aLocation), promise);
     return promise;
   }),
 
   /**
    * Update the editor and breakpoints pane to show a specified breakpoint.
    *
-   * @param object aBreakpointData
-   *        Information about the breakpoint to be shown.
-   *        This object must have the following properties:
-   *          - location: the breakpoint's source location and line number
+   * @param object aBreakpointClient
+   *        A BreakpointClient instance.
+   *        This object has additional properties dynamically added by
+   *        our code:
    *          - disabled: the breakpoint's disabled state, boolean
    *          - text: the breakpoint's line text to be displayed
    * @param object aOptions [optional]
    *        @see DebuggerController.Breakpoints.addBreakpoint
    */
-  _showBreakpoint: function(aBreakpointData, aOptions = {}) {
+  _showBreakpoint: function(aBreakpointClient, aOptions = {}) {
     let tasks = [];
-    let currentSourceUrl = DebuggerView.Sources.selectedValue;
-    let location = aBreakpointData.location;
+    let currentSourceActor = DebuggerView.Sources.selectedValue;
+    let location = aBreakpointClient.location;
+    let actor = DebuggerView.Sources.getActorForLocation(location);
 
     // Update the editor if required.
-    if (!aOptions.noEditorUpdate && !aBreakpointData.disabled) {
-      if (location.url == currentSourceUrl) {
+    if (!aOptions.noEditorUpdate && !aBreakpointClient.disabled) {
+      if (currentSourceActor === actor) {
         tasks.push(DebuggerView.editor.addBreakpoint(location.line - 1));
       }
     }
 
     // Update the breakpoints pane if required.
     if (!aOptions.noPaneUpdate) {
-      DebuggerView.Sources.addBreakpoint(aBreakpointData, aOptions);
+      DebuggerView.Sources.addBreakpoint(aBreakpointClient, aOptions);
     }
 
     return promise.all(tasks);
   },
 
   /**
    * Update the editor and breakpoints pane to hide a specified breakpoint.
    *
    * @param object aLocation
    *        @see DebuggerController.Breakpoints.addBreakpoint
    * @param object aOptions [optional]
    *        @see DebuggerController.Breakpoints.addBreakpoint
    */
   _hideBreakpoint: function(aLocation, aOptions = {}) {
-    let currentSourceUrl = DebuggerView.Sources.selectedValue;
+    let currentSourceActor = DebuggerView.Sources.selectedValue;
+    let actor = DebuggerView.Sources.getActorForLocation(aLocation);
 
     // Update the editor if required.
     if (!aOptions.noEditorUpdate) {
-      if (aLocation.url == currentSourceUrl) {
+      if (currentSourceActor === actor) {
         DebuggerView.editor.removeBreakpoint(aLocation.line - 1);
       }
     }
 
     // Update the breakpoints pane if required.
     if (!aOptions.noPaneUpdate) {
       DebuggerView.Sources.removeBreakpoint(aLocation);
     }
@@ -2228,17 +2242,18 @@ Breakpoints.prototype = {
    * identified in the store by a string representation of their location.
    *
    * @param object aLocation
    *        The location to serialize to a string.
    * @return string
    *         The identifier string.
    */
   getIdentifier: function(aLocation) {
-    return aLocation.url + ":" + aLocation.line;
+    return (aLocation.source ? aLocation.source.actor : aLocation.actor) +
+      ":" + aLocation.line;
   }
 };
 
 /**
  * Gets all Promises for the BreakpointActor client objects that are
  * either enabled (added to the server) or disabled (removed from the server,
  * but for which some details are preserved).
  */
@@ -2257,23 +2272,25 @@ function HitCounts() {
    * Storage of hit counts for every location
    * hitCount = _locations[url][line][column]
    */
   this._hitCounts = Object.create(null);
 }
 
 HitCounts.prototype = {
   set: function({url, line, column}, aHitCount) {
-    if (!this._hitCounts[url]) {
-      this._hitCounts[url] = Object.create(null);
+    if (url) {
+      if (!this._hitCounts[url]) {
+        this._hitCounts[url] = Object.create(null);
+      }
+      if (!this._hitCounts[url][line]) {
+        this._hitCounts[url][line] = Object.create(null);
+      }
+      this._hitCounts[url][line][column] = aHitCount;
     }
-    if (!this._hitCounts[url][line]) {
-      this._hitCounts[url][line] = Object.create(null);
-    }
-    this._hitCounts[url][line][column] = aHitCount;
   },
 
   /**
    * Update all the hit counts in the editor view. This is invoked when the
    * selected script is changed, or when new sources are received via the
    * _onNewSource and _onSourcesAdded event listeners.
    */
   updateEditorHitCounts: function() {
@@ -2296,17 +2313,18 @@ HitCounts.prototype = {
   _updateEditorHitCount: function({url, line, column}) {
     // Editor must be initialized.
     if (!DebuggerView.editor) {
       return;
     }
 
     // No need to do anything if the counter's source is not being shown in the
     // editor.
-    if (DebuggerView.Sources.selectedValue != url) {
+    if (url &&
+        DebuggerView.Sources.selectedItem.attachment.source.url != url) {
       return;
     }
 
     // There might be more counters on the same line. We need to combine them
     // into one.
     let content = Object.keys(this._hitCounts[url][line])
                     .sort() // Sort by key (column).
                     .map(a => this._hitCounts[url][line][a]) // Extract values.
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -128,94 +128,109 @@ SourcesView.prototype = Heritage.extend(
    *
    * @param object aSource
    *        The source object coming from the active thread.
    * @param object aOptions [optional]
    *        Additional options for adding the source. Supported options:
    *        - staged: true to stage the item to be appended later
    */
   addSource: function(aSource, aOptions = {}) {
-    let fullUrl = aSource.url;
-    let url = fullUrl.split(" -> ").pop();
-    let label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url);
-    let group = aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url);
-    let unicodeUrl = NetworkHelper.convertToUnicode(unescape(fullUrl));
+    if (!(aSource.url || aSource.introductionUrl)) {
+      // These would be most likely eval scripts introduced in inline
+      // JavaScript in HTML, and we don't show those yet (bug 1097873)
+      return;
+    }
+
+    let { label, group, unicodeUrl } = this._parseUrl(aSource);
 
     let contents = document.createElement("label");
     contents.className = "plain dbg-source-item";
     contents.setAttribute("value", label);
     contents.setAttribute("crop", "start");
     contents.setAttribute("flex", "1");
     contents.setAttribute("tooltiptext", unicodeUrl);
 
     // If the source is blackboxed, apply the appropriate style.
     if (gThreadClient.source(aSource).isBlackBoxed) {
       contents.classList.add("black-boxed");
     }
 
     // Append a source item to this container.
-    this.push([contents, fullUrl], {
+    this.push([contents, aSource.actor], {
       staged: aOptions.staged, /* stage the item to be appended later? */
       attachment: {
         label: label,
         group: group,
         checkboxState: !aSource.isBlackBoxed,
         checkboxTooltip: this._blackBoxCheckboxTooltip,
         source: aSource
       }
     });
   },
 
+  _parseUrl: function(aSource) {
+    let fullUrl = aSource.url || aSource.introductionUrl;
+    let url = fullUrl.split(" -> ").pop();
+    let label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url);
+
+    if (aSource.introductionUrl) {
+      label += ' > eval';
+    }
+
+    return {
+      label: label,
+      group: aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url),
+      unicodeUrl: NetworkHelper.convertToUnicode(unescape(fullUrl))
+    };
+  },
+
   /**
    * Adds a breakpoint to this sources container.
    *
-   * @param object aBreakpointData
-   *        Information about the breakpoint to be shown.
-   *        This object must have the following properties:
-   *          - location: the breakpoint's source location and line number
-   *          - disabled: the breakpoint's disabled state, boolean
-   *          - text: the breakpoint's line text to be displayed
+   * @param object aBreakpointClient
+   *               See Breakpoints.prototype._showBreakpoint
    * @param object aOptions [optional]
    *        @see DebuggerController.Breakpoints.addBreakpoint
    */
-  addBreakpoint: function(aBreakpointData, aOptions = {}) {
-    let { location, disabled } = aBreakpointData;
+  addBreakpoint: function(aBreakpointClient, aOptions = {}) {
+    let { location, disabled } = aBreakpointClient;
 
     // Make sure we're not duplicating anything. If a breakpoint at the
     // specified source url and line already exists, just toggle it.
     if (this.getBreakpoint(location)) {
       this[disabled ? "disableBreakpoint" : "enableBreakpoint"](location);
       return;
     }
 
     // Get the source item to which the breakpoint should be attached.
-    let sourceItem = this.getItemByValue(location.url);
+    let sourceItem = this.getItemByValue(this.getActorForLocation(location));
 
     // Create the element node and menu popup for the breakpoint item.
-    let breakpointArgs = Heritage.extend(aBreakpointData, aOptions);
+    let breakpointArgs = Heritage.extend(aBreakpointClient, aOptions);
     let breakpointView = this._createBreakpointView.call(this, breakpointArgs);
     let contextMenu = this._createContextMenu.call(this, breakpointArgs);
 
     // Append a breakpoint child item to the corresponding source item.
     sourceItem.append(breakpointView.container, {
       attachment: Heritage.extend(breakpointArgs, {
-        url: location.url,
+        actor: location.actor,
         line: location.line,
         view: breakpointView,
         popup: contextMenu
       }),
       attributes: [
         ["contextmenu", contextMenu.menupopupId]
       ],
       // Make sure that when the breakpoint item is removed, the corresponding
       // menupopup and commandset are also destroyed.
       finalize: this._onBreakpointRemoved
     });
 
-    // Highlight the newly appended breakpoint child item if necessary.
+    // Highlight the newly appended breakpoint child item if
+    // necessary.
     if (aOptions.openPopup || !aOptions.noEditorUpdate) {
       this.highlightBreakpoint(location, aOptions);
     }
 
     window.emit(EVENTS.BREAKPOINT_SHOWN_IN_PANE);
   },
 
   /**
@@ -223,17 +238,17 @@ SourcesView.prototype = Heritage.extend(
    * It does not also remove the breakpoint from the controller. Be careful.
    *
    * @param object aLocation
    *        @see DebuggerController.Breakpoints.addBreakpoint
    */
   removeBreakpoint: function(aLocation) {
     // When a parent source item is removed, all the child breakpoint items are
     // also automagically removed.
-    let sourceItem = this.getItemByValue(aLocation.url);
+    let sourceItem = this.getItemByValue(aLocation.actor);
     if (!sourceItem) {
       return;
     }
     let breakpointItem = this.getBreakpoint(aLocation);
     if (!breakpointItem) {
       return;
     }
 
@@ -248,17 +263,17 @@ SourcesView.prototype = Heritage.extend(
    *
    * @param object aLocation
    *        @see DebuggerController.Breakpoints.addBreakpoint
    * @return object
    *         The corresponding breakpoint item if found, null otherwise.
    */
   getBreakpoint: function(aLocation) {
     return this.getItemForPredicate(aItem =>
-      aItem.attachment.url == aLocation.url &&
+      aItem.attachment.actor == aLocation.actor &&
       aItem.attachment.line == aLocation.line);
   },
 
   /**
    * Returns all breakpoints for all sources.
    *
    * @return array
    *         The breakpoints for all sources if any, an empty array otherwise.
@@ -275,18 +290,18 @@ SourcesView.prototype = Heritage.extend(
    * @param array aStore [optional]
    *        A list in which to store the corresponding breakpoints.
    * @return array
    *         The corresponding breakpoints if found, an empty array otherwise.
    */
   getOtherBreakpoints: function(aLocation = {}, aStore = []) {
     for (let source of this) {
       for (let breakpointItem of source) {
-        let { url, line } = breakpointItem.attachment;
-        if (url != aLocation.url || line != aLocation.line) {
+        let { actor, line } = breakpointItem.attachment;
+        if (actor != aLocation.actor || line != aLocation.line) {
           aStore.push(breakpointItem);
         }
       }
     }
     return aStore;
   },
 
   /**
@@ -399,36 +414,37 @@ SourcesView.prototype = Heritage.extend(
       return;
     }
 
     // Breakpoint will now be selected.
     this._selectBreakpoint(breakpointItem);
 
     // Update the editor location if necessary.
     if (!aOptions.noEditorUpdate) {
-      DebuggerView.setEditorLocation(aLocation.url, aLocation.line, { noDebug: true });
+      DebuggerView.setEditorLocation(aLocation.actor, aLocation.line, { noDebug: true });
     }
 
     // If the breakpoint requires a new conditional expression, display
     // the panel to input the corresponding expression.
     if (aOptions.openPopup) {
       this._openConditionalPopup();
     } else {
       this._hideConditionalPopup();
     }
   },
 
   /**
    * Highlight the breakpoint on the current currently focused line/column
    * if it exists.
    */
   highlightBreakpointAtCursor: function() {
-    let url = DebuggerView.Sources.selectedValue;
+    let actor = DebuggerView.Sources.selectedValue;
     let line = DebuggerView.editor.getCursor().line + 1;
-    let location = { url: url, line: line };
+
+    let location = { actor: actor, line: line };
     this.highlightBreakpoint(location, { noEditorUpdate: true });
   },
 
   /**
    * Unhighlights the current breakpoint in this sources container.
    */
   unhighlightBreakpoint: function() {
     this._hideConditionalPopup();
@@ -461,20 +477,20 @@ SourcesView.prototype = Heritage.extend(
   /**
    * Toggle the pretty printing of the selected source.
    */
   togglePrettyPrint: Task.async(function*() {
     if (this._prettyPrintButton.hasAttribute("disabled")) {
       return;
     }
 
-    const resetEditor = ([{ url }]) => {
+    const resetEditor = ([{ actor }]) => {
       // Only set the text when the source is still selected.
-      if (url == this.selectedValue) {
-        DebuggerView.setEditorLocation(url, 0, { force: true });
+      if (actor == this.selectedValue) {
+        DebuggerView.setEditorLocation(actor, 0, { force: true });
       }
     };
 
     const printError = ([{ url }, error]) => {
       DevToolsUtils.reportException("togglePrettyPrint", error);
     };
 
     DebuggerView.showProgressBar();
@@ -560,26 +576,52 @@ SourcesView.prototype = Heritage.extend(
 
     if (this._prettyPrintButton.style.display === 'none') {
       let sep = document.querySelector('#sources-toolbar .devtools-separator');
       sep.style.display = 'none';
     }
   },
 
   /**
+   * Look up a source actor id for a location. This is necessary for
+   * backwards compatibility; otherwise we could just use the `actor`
+   * property. Older servers don't use the same actor ids for sources
+   * across reloads, so we resolve a url to the current actor if a url
+   * exists.
+   *
+   * @param object aLocation
+   *        An object with the following properties:
+   *        - actor: the source actor id
+   *        - url: a url (might be null)
+   */
+  getActorForLocation: function(aLocation) {
+    if (aLocation.url) {
+      for (var item of this) {
+        let source = item.attachment.source;
+
+        if (aLocation.url === source.url) {
+          return source.actor;
+        }
+      }
+    }
+    return aLocation.actor;
+  },
+
+  /**
    * Marks a breakpoint as selected in this sources container.
    *
    * @param object aItem
    *        The breakpoint item to select.
    */
   _selectBreakpoint: function(aItem) {
     if (this._selectedBreakpointItem == aItem) {
       return;
     }
     this._unselectBreakpoint();
+
     this._selectedBreakpointItem = aItem;
     this._selectedBreakpointItem.target.classList.add("selected");
 
     // Ensure the currently selected breakpoint is visible.
     this.widget.ensureElementIsVisible(aItem.target);
   },
 
   /**
@@ -815,19 +857,19 @@ SourcesView.prototype = Heritage.extend(
 
   /**
    * The selection listener for the source editor.
    */
   _onEditorCursorActivity: function(e) {
     let editor = DebuggerView.editor;
     let start  = editor.getCursor("start").line + 1;
     let end    = editor.getCursor().line + 1;
-    let url    = this.selectedValue;
-
-    let location = { url: url, line: start };
+    let actor    = this.selectedValue;
+
+    let location = { actor: actor, line: start };
 
     if (this.getBreakpoint(location) && start == end) {
       this.highlightBreakpoint(location, { noEditorUpdate: true });
     } else {
       this.unhighlightBreakpoint();
     }
   },
 
@@ -849,17 +891,18 @@ SourcesView.prototype = Heritage.extend(
       let isMinified = yield SourceUtils.isMinified(sourceClient);
       if (isMinified) {
         this.togglePrettyPrint();
       }
     }
 
     // Set window title. No need to split the url by " -> " here, because it was
     // already sanitized when the source was added.
-    document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", sourceItem.value);
+    document.title = L10N.getFormatStr("DebuggerWindowScriptTitle",
+                                       sourceItem.attachment.source.url);
 
     DebuggerView.maybeShowBlackBoxMessage();
     this.updateToolbarButtonsState();
   }),
 
   /**
    * The click listener for the "stop black boxing" button.
    */
@@ -969,42 +1012,42 @@ SourcesView.prototype = Heritage.extend(
       this._hideConditionalPopup();
     }
   },
 
   /**
    * Called when the add breakpoint key sequence was pressed.
    */
   _onCmdAddBreakpoint: function(e) {
-    let url = DebuggerView.Sources.selectedValue;
+    let actor = DebuggerView.Sources.selectedValue;
     let line = (e && e.sourceEvent.target.tagName == 'menuitem' ?
                 DebuggerView.clickedLine + 1 :
                 DebuggerView.editor.getCursor().line + 1);
-    let location = { url: url, line: line };
+    let location = { actor, line };
     let breakpointItem = this.getBreakpoint(location);
 
     // If a breakpoint already existed, remove it now.
     if (breakpointItem) {
       DebuggerController.Breakpoints.removeBreakpoint(location);
     }
     // No breakpoint existed at the required location, add one now.
     else {
       DebuggerController.Breakpoints.addBreakpoint(location);
     }
   },
 
   /**
    * Called when the add conditional breakpoint key sequence was pressed.
    */
   _onCmdAddConditionalBreakpoint: function(e) {
-    let url =  DebuggerView.Sources.selectedValue;
+    let actor = DebuggerView.Sources.selectedValue;
     let line = (e && e.sourceEvent.target.tagName == 'menuitem' ?
                 DebuggerView.clickedLine + 1 :
                 DebuggerView.editor.getCursor().line + 1);
-    let location = { url: url, line: line };
+    let location = { actor, line };
     let breakpointItem = this.getBreakpoint(location);
 
     // If a breakpoint already existed or wasn't a conditional, morph it now.
     if (breakpointItem) {
       this.highlightBreakpoint(location, { openPopup: true });
     }
     // No breakpoint existed at the required location, add one now.
     else {
@@ -1308,17 +1351,21 @@ TracerView.prototype = Heritage.extend(W
    */
   _onSelect: function _onSelect({ detail: traceItem }) {
     if (!traceItem) {
       return;
     }
 
     const data = traceItem.attachment.trace;
     const { location: { url, line } } = data;
-    DebuggerView.setEditorLocation(url, line, { noDebug: true });
+    DebuggerView.setEditorLocation(
+      DebuggerView.Sources.getActorForLocation({ url }),
+      line,
+      { noDebug: true }
+    );
 
     DebuggerView.Variables.empty();
     const scope = DebuggerView.Variables.addScope();
 
     if (data.type == "call") {
       const params = DevToolsUtils.zip(data.parameterNames, data.arguments);
       for (let [name, val] of params) {
         if (val === undefined) {
@@ -1533,17 +1580,17 @@ let SourceUtils = {
   /**
    * Returns true if the specified url and/or content type are specific to
    * javascript files.
    *
    * @return boolean
    *         True if the source is likely javascript.
    */
   isJavaScript: function(aUrl, aContentType = "") {
-    return /\.jsm?$/.test(this.trimUrlQuery(aUrl)) ||
+    return (aUrl && /\.jsm?$/.test(this.trimUrlQuery(aUrl))) ||
            aContentType.contains("javascript");
   },
 
   /**
    * Determines if the source text is minified by using
    * the percentage indented of a subset of lines
    *
    * @return object
@@ -2739,18 +2786,18 @@ GlobalSearchView.prototype = Heritage.ex
   scheduleSearch: function(aToken, aWait) {
     // The amount of time to wait for the requests to settle.
     let maxDelay = GLOBAL_SEARCH_ACTION_MAX_DELAY;
     let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
 
     // Allow requests to settle down first.
     setNamedTimeout("global-search", delay, () => {
       // Start fetching as many sources as possible, then perform the search.
-      let urls = DebuggerView.Sources.values;
-      let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(urls);
+      let actors = DebuggerView.Sources.values;
+      let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(actors);
       sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
     });
   },
 
   /**
    * Finds string matches in all the sources stored in the controller's cache,
    * and groups them by url and line number.
    *
@@ -2769,23 +2816,29 @@ GlobalSearchView.prototype = Heritage.ex
     // Search is not case sensitive, prepare the actual searched token.
     let lowerCaseToken = aToken.toLowerCase();
     let tokenLength = aToken.length;
 
     // Create a Map containing search details for each source.
     let globalResults = new GlobalResults();
 
     // Search for the specified token in each source's text.
-    for (let [url, text] of aSources) {
+    for (let [actor, text] of aSources) {
+      let item = DebuggerView.Sources.getItemByValue(actor);
+      let url = item.attachment.source.url;
+      if (!url) {
+        continue;
+      }
+
       // Verify that the search token is found anywhere in the source.
       if (!text.toLowerCase().contains(lowerCaseToken)) {
         continue;
       }
       // ...and if so, create a Map containing search details for each line.
-      let sourceResults = new SourceResults(url, globalResults);
+      let sourceResults = new SourceResults(actor, globalResults);
 
       // Search for the specified token in each line's text.
       text.split("\n").forEach((aString, aLine) => {
         // Search is not case sensitive, prepare the actual searched line.
         let lowerCaseLine = aString.toLowerCase();
 
         // Verify that the search token is found anywhere in this line.
         if (!lowerCaseLine.contains(lowerCaseToken)) {
@@ -2912,20 +2965,20 @@ GlobalSearchView.prototype = Heritage.ex
     let sourceResultsItem = SourceResults.getItemForElement(target);
     let lineResultsItem = LineResults.getItemForElement(target);
 
     sourceResultsItem.instance.expand();
     this._currentlyFocusedMatch = LineResults.indexOfElement(target);
     this._scrollMatchIntoViewIfNeeded(target);
     this._bounceMatch(target);
 
-    let url = sourceResultsItem.instance.url;
+    let actor = sourceResultsItem.instance.actor;
     let line = lineResultsItem.instance.line;
 
-    DebuggerView.setEditorLocation(url, line + 1, { noDebug: true });
+    DebuggerView.setEditorLocation(actor, line + 1, { noDebug: true });
 
     let range = lineResultsItem.lineData.range;
     let cursor = DebuggerView.editor.getOffset({ line: line, ch: 0 });
     let [ anchor, head ] = DebuggerView.editor.getPosition(
       cursor + range.start,
       cursor + range.start + range.length
     );
 
@@ -2990,23 +3043,25 @@ GlobalResults.prototype = {
    */
   get matchCount() this._store.length
 };
 
 /**
  * An object containing all the matched lines for a specific source.
  * Iterable via "for (let [lineNumber, lineResults] of sourceResults) { }".
  *
- * @param string aUrl
- *        The target source url.
+ * @param string aActor
+ *        The target source actor id.
  * @param GlobalResults aGlobalResults
  *        An object containing all source results, grouped by source location.
  */
-function SourceResults(aUrl, aGlobalResults) {
-  this.url = aUrl;
+function SourceResults(aActor, aGlobalResults) {
+  let item = DebuggerView.Sources.getItemByValue(aActor);
+  this.actor = aActor;
+  this.label = item.attachment.source.url;
   this._globalResults = aGlobalResults;
   this._store = [];
 }
 
 SourceResults.prototype = {
   /**
    * Adds line results to this store.
    *
@@ -3078,17 +3133,17 @@ SourceResults.prototype = {
   createView: function(aElementNode, aCallbacks) {
     this._target = aElementNode;
 
     let arrow = this._arrow = document.createElement("box");
     arrow.className = "arrow";
 
     let locationNode = document.createElement("label");
     locationNode.className = "plain dbg-results-header-location";
-    locationNode.setAttribute("value", this.url);
+    locationNode.setAttribute("value", this.label);
 
     let matchCountNode = document.createElement("label");
     matchCountNode.className = "plain dbg-results-header-match-count";
     matchCountNode.setAttribute("value", "(" + this.matchCount + ")");
 
     let resultsHeader = this._resultsHeader = document.createElement("hbox");
     resultsHeader.className = "dbg-results-header";
     resultsHeader.setAttribute("align", "center")
@@ -3111,24 +3166,24 @@ SourceResults.prototype = {
       this.expand();
     }
 
     let resultsBox = document.createElement("vbox");
     resultsBox.setAttribute("flex", "1");
     resultsBox.appendChild(resultsHeader);
     resultsBox.appendChild(resultsContainer);
 
-    aElementNode.id = "source-results-" + this.url;
+    aElementNode.id = "source-results-" + this.actor;
     aElementNode.className = "dbg-source-results";
     aElementNode.appendChild(resultsBox);
 
     SourceResults._itemsByElement.set(aElementNode, { instance: this });
   },
 
-  url: "",
+  actor: "",
   _globalResults: null,
   _store: null,
   _target: null,
   _arrow: null,
   _resultsHeader: null,
   _resultsContainer: null
 };
 
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -931,17 +931,17 @@ FilterView.prototype = {
 
     // Perform the required search based on the specified operator.
     switch (this.searchOperator) {
       case SEARCH_GLOBAL_FLAG:
         // Schedule a global search for when the user stops typing.
         DebuggerView.GlobalSearch.scheduleSearch(this.searchArguments[0]);
         break;
       case SEARCH_FUNCTION_FLAG:
-        // Schedule a function search for when the user stops typing.
+      // Schedule a function search for when the user stops typing.
         DebuggerView.FilteredFunctions.scheduleSearch(this.searchArguments[0]);
         break;
       case SEARCH_VARIABLE_FLAG:
         // Schedule a variable search for when the user stops typing.
         DebuggerView.Variables.scheduleSearch(this.searchArguments[0]);
         break;
       case SEARCH_TOKEN_FLAG:
         // Schedule a file+token search for when the user stops typing.
@@ -1112,17 +1112,17 @@ FilterView.prototype = {
     }
     if (DebuggerView.editor.somethingSelected()) {
       this._searchbox.value = aOperator + DebuggerView.editor.getSelection();
       return;
     }
     if (SEARCH_AUTOFILL.indexOf(aOperator) != -1) {
       let cursor = DebuggerView.editor.getCursor();
       let content = DebuggerView.editor.getText();
-      let location = DebuggerView.Sources.selectedValue;
+      let location = DebuggerView.Sources.selectedItem.attachment.source.url;
       let source = DebuggerController.Parser.get(content, location);
       let identifier = source.getIdentifierAt({ line: cursor.line+1, column: cursor.ch });
 
       if (identifier && identifier.name) {
         this._searchbox.value = aOperator + identifier.name;
         this._searchbox.select();
         this._searchbox.selectionStart += aOperator.length;
         return;
@@ -1301,29 +1301,33 @@ FilteredSourcesView.prototype = Heritage
     // If there are no matches found, keep the popup hidden and avoid
     // creating the view.
     if (!aSearchResults.length) {
       window.emit(EVENTS.FILE_SEARCH_MATCH_NOT_FOUND);
       return;
     }
 
     for (let item of aSearchResults) {
-      // Create the element node for the location item.
-      let itemView = this._createItemView(
-        SourceUtils.trimUrlLength(item.attachment.label),
-        SourceUtils.trimUrlLength(item.value, 0, "start")
-      );
+      let url = item.attachment.source.url;
+
+      if (url) {
+        // Create the element node for the location item.
+        let itemView = this._createItemView(
+          SourceUtils.trimUrlLength(item.attachment.label),
+          SourceUtils.trimUrlLength(url, 0, "start")
+        );
 
-      // Append a location item to this container for each match.
-      this.push([itemView], {
-        index: -1, /* specifies on which position should the item be appended */
-        attachment: {
-          url: item.value
-        }
-      });
+        // Append a location item to this container for each match.
+        this.push([itemView], {
+          index: -1, /* specifies on which position should the item be appended */
+          attachment: {
+            url: url
+          }
+        });
+      }
     }
 
     // There's at least one item displayed in this container. Don't select it
     // automatically if not forced (by tests) or in tandem with an operator.
     if (this._autoSelectFirstItem || DebuggerView.Filtering.searchOperator) {
       this.selectedIndex = 0;
     }
     this.hidden = false;
@@ -1346,17 +1350,18 @@ FilteredSourcesView.prototype = Heritage
   /**
    * The select listener for this container.
    *
    * @param object aItem
    *        The item associated with the element to select.
    */
   _onSelect: function({ detail: locationItem }) {
     if (locationItem) {
-      DebuggerView.setEditorLocation(locationItem.attachment.url, undefined, {
+      let actor = DebuggerView.Sources.getActorForLocation({ url: locationItem.attachment.url });
+      DebuggerView.setEditorLocation(actor, undefined, {
         noCaret: true,
         noDebug: true
       });
     }
   }
 });
 
 /**
@@ -1403,18 +1408,18 @@ FilteredFunctionsView.prototype = Herita
   scheduleSearch: function(aToken, aWait) {
     // The amount of time to wait for the requests to settle.
     let maxDelay = FUNCTION_SEARCH_ACTION_MAX_DELAY;
     let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
 
     // Allow requests to settle down first.
     setNamedTimeout("function-search", delay, () => {
       // Start fetching as many sources as possible, then perform the search.
-      let urls = DebuggerView.Sources.values;
-      let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(urls);
+      let actors = DebuggerView.Sources.values;
+      let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(actors);
       sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
     });
   },
 
   /**
    * Finds function matches in all the sources stored in the cache, and groups
    * them by location and line number.
    *
@@ -1424,29 +1429,35 @@ FilteredFunctionsView.prototype = Herita
    *        An array of [url, text] tuples for each source.
    */
   _doSearch: function(aToken, aSources, aStore = []) {
     // Continue parsing even if the searched token is an empty string, to
     // cache the syntax tree nodes generated by the reflection API.
 
     // Make sure the currently displayed source is parsed first. Once the
     // maximum allowed number of results are found, parsing will be halted.
-    let currentUrl = DebuggerView.Sources.selectedValue;
-    let currentSource = aSources.filter(([sourceUrl]) => sourceUrl == currentUrl)[0];
+    let currentActor = DebuggerView.Sources.selectedValue;
+    let currentSource = aSources.filter(([actor]) => actor == currentActor)[0];
     aSources.splice(aSources.indexOf(currentSource), 1);
     aSources.unshift(currentSource);
 
     // If not searching for a specific function, only parse the displayed source,
     // which is now the first item in the sources array.
     if (!aToken) {
       aSources.splice(1);
     }
 
-    for (let [location, contents] of aSources) {
-      let parsedSource = DebuggerController.Parser.get(contents, location);
+    for (let [actor, contents] of aSources) {
+      let item = DebuggerView.Sources.getItemByValue(actor);
+      let url = item.attachment.source.url;
+      if (!url) {
+        continue;
+      }
+
+      let parsedSource = DebuggerController.Parser.get(contents, url);
       let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken);
 
       for (let scriptResult of sourceResults) {
         for (let parseResult of scriptResult) {
           aStore.push({
             sourceUrl: scriptResult.sourceUrl,
             scriptOffset: scriptResult.scriptOffset,
             functionName: parseResult.functionName,
@@ -1548,20 +1559,21 @@ FilteredFunctionsView.prototype = Herita
   },
 
   /**
    * The select listener for this container.
    */
   _onSelect: function({ detail: functionItem }) {
     if (functionItem) {
       let sourceUrl = functionItem.attachment.sourceUrl;
+      let actor = DebuggerView.Sources.getActorForLocation({ url: sourceUrl });
       let scriptOffset = functionItem.attachment.scriptOffset;
       let actualLocation = functionItem.attachment.actualLocation;
 
-      DebuggerView.setEditorLocation(sourceUrl, actualLocation.start.line, {
+      DebuggerView.setEditorLocation(actor, actualLocation.start.line, {
         charOffset: scriptOffset,
         columnOffset: actualLocation.start.column,
         align: "center",
         noDebug: true
       });
     }
   },
 
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -241,17 +241,17 @@ let DebuggerView = {
       this.editor.extend(DebuggerEditor);
       this._loadingText = L10N.getStr("loadingText");
       this._onEditorLoad(aCallback);
     });
 
     this.editor.on("gutterClick", (ev, line, button) => {
       // A right-click shouldn't do anything but keep track of where
       // it was clicked.
-      if(button == 2) {
+      if (button == 2) {
         this.clickedLine = line;
       }
       else {
         if (this.editor.hasBreakpoint(line)) {
           this.editor.removeBreakpoint(line);
         } else {
           this.editor.addBreakpoint(line);
         }
@@ -385,41 +385,42 @@ let DebuggerView = {
    * @param object aFlags
    *        Additional options for setting the source. Supported options:
    *          - force: boolean forcing all text to be reshown in the editor
    * @return object
    *         A promise that is resolved after the source text has been set.
    */
   _setEditorSource: function(aSource, aFlags={}) {
     // Avoid setting the same source text in the editor again.
-    if (this._editorSource.url == aSource.url && !aFlags.force) {
+    if (this._editorSource.actor == aSource.actor && !aFlags.force) {
       return this._editorSource.promise;
     }
     let transportType = gClient.localTransport ? "_LOCAL" : "_REMOTE";
     let histogramId = "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE" + transportType + "_MS";
     let histogram = Services.telemetry.getHistogramById(histogramId);
     let startTime = Date.now();
 
     let deferred = promise.defer();
 
     this._setEditorText(L10N.getStr("loadingText"));
-    this._editorSource = { url: aSource.url, promise: deferred.promise };
+    this._editorSource = { actor: aSource.actor, promise: deferred.promise };
 
     DebuggerController.SourceScripts.getText(aSource).then(([, aText, aContentType]) => {
       // Avoid setting an unexpected source. This may happen when switching
       // very fast between sources that haven't been fetched yet.
-      if (this._editorSource.url != aSource.url) {
+      if (this._editorSource.actor != aSource.actor) {
         return;
       }
 
       this._setEditorText(aText);
       this._setEditorMode(aSource.url, aContentType, aText);
 
-      // Synchronize any other components with the currently displayed source.
-      DebuggerView.Sources.selectedValue = aSource.url;
+      // Synchronize any other components with the currently displayed
+      // source.
+      DebuggerView.Sources.selectedValue = aSource.actor;
       DebuggerController.Breakpoints.updateEditorBreakpoints();
       DebuggerController.HitCounts.updateEditorHitCounts();
 
       histogram.add(Date.now() - startTime);
 
       // Resolve and notify that a source file was shown.
       window.emit(EVENTS.SOURCE_SHOWN, aSource);
       deferred.resolve([aSource, aText, aContentType]);
@@ -437,56 +438,62 @@ let DebuggerView = {
 
     return deferred.promise;
   },
 
   /**
    * Update the source editor's current caret and debug location based on
    * a requested url and line.
    *
-   * @param string aUrl
-   *        The target source url.
+   * @param string aActor
+   *        The target actor id.
    * @param number aLine [optional]
    *        The target line in the source.
    * @param object aFlags [optional]
    *        Additional options for showing the source. Supported options:
    *          - charOffset: character offset for the caret or debug location
    *          - lineOffset: line offset for the caret or debug location
    *          - columnOffset: column offset for the caret or debug location
    *          - noCaret: don't set the caret location at the specified line
    *          - noDebug: don't set the debug location at the specified line
    *          - align: string specifying whether to align the specified line
    *                   at the "top", "center" or "bottom" of the editor
    *          - force: boolean forcing all text to be reshown in the editor
    * @return object
    *         A promise that is resolved after the source text has been set.
    */
-  setEditorLocation: function(aUrl, aLine = 0, aFlags = {}) {
+  setEditorLocation: function(aActor, aLine = 0, aFlags = {}) {
     // Avoid trying to set a source for a url that isn't known yet.
-    if (!this.Sources.containsValue(aUrl)) {
+    if (!this.Sources.containsValue(aActor)) {
       return promise.reject(new Error("Unknown source for the specified URL."));
     }
 
     // If the line is not specified, default to the current frame's position,
     // if available and the frame's url corresponds to the requested url.
     if (!aLine) {
       let cachedFrames = DebuggerController.activeThread.cachedFrames;
       let currentDepth = DebuggerController.StackFrames.currentFrameDepth;
       let frame = cachedFrames[currentDepth];
-      if (frame && frame.where.url == aUrl) {
+      if (frame && frame.source.actor == aActor) {
         aLine = frame.where.line;
       }
     }
 
-    let sourceItem = this.Sources.getItemByValue(aUrl);
+    let sourceItem = this.Sources.getItemByValue(aActor);
     let sourceForm = sourceItem.attachment.source;
 
+    this._editorLoc = { actor: sourceForm.actor };
+
     // Make sure the requested source client is shown in the editor, then
     // update the source editor's caret position and debug location.
     return this._setEditorSource(sourceForm, aFlags).then(([,, aContentType]) => {
+      if (this._editorLoc.actor !== sourceForm.actor) {
+        return;
+      }
+
       // Record the contentType learned from fetching
       sourceForm.contentType = aContentType;
       // Line numbers in the source editor should start from 1. If invalid
       // or not specified, then don't do anything.
       if (aLine < 1) {
         window.emit(EVENTS.EDITOR_LOCATION_SET);
         return;
       }
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -20,16 +20,17 @@ support-files =
   code_function-search-02.js
   code_function-search-03.js
   code_location-changes.js
   code_math.js
   code_math.map
   code_math.min.js
   code_math_bogus_map.js
   code_same-line-functions.js
+  code_script-eval.js
   code_script-switching-01.js
   code_script-switching-02.js
   code_test-editor-mode
   code_tracing-01.js
   code_ugly.js
   code_ugly-2.js
   code_ugly-3.js
   code_ugly-4.js
@@ -79,16 +80,17 @@ support-files =
   doc_promise.html
   doc_random-javascript.html
   doc_recursion-stack.html
   doc_same-line-functions.html
   doc_scope-variable.html
   doc_scope-variable-2.html
   doc_scope-variable-3.html
   doc_scope-variable-4.html
+  doc_script-eval.html
   doc_script-switching-01.html
   doc_script-switching-02.html
   doc_split-console-paused-reload.html
   doc_step-out.html
   doc_terminate-on-tab-close.html
   doc_tracing-01.html
   doc_watch-expressions.html
   doc_watch-expression-button.html
@@ -164,16 +166,18 @@ skip-if = e10s && debug
 [browser_dbg_breakpoints-contextmenu-add.js]
 skip-if = e10s && debug
 [browser_dbg_breakpoints-contextmenu.js]
 skip-if = e10s && debug
 [browser_dbg_breakpoints-disabled-reload.js]
 skip-if = e10s # Bug 1093535
 [browser_dbg_breakpoints-editor.js]
 skip-if = e10s && debug
+[browser_dbg_breakpoints-eval.js]
+skip-if = e10s && debug
 [browser_dbg_breakpoints-highlight.js]
 skip-if = e10s && debug
 [browser_dbg_breakpoints-new-script.js]
 skip-if = e10s && debug
 [browser_dbg_breakpoints-other-tabs.js]
 skip-if = e10s && debug
 [browser_dbg_breakpoints-pane.js]
 skip-if = e10s && debug
@@ -396,16 +400,18 @@ skip-if = e10s && debug
 [browser_dbg_source-maps-02.js]
 skip-if = e10s && debug
 [browser_dbg_source-maps-03.js]
 skip-if = e10s && debug
 [browser_dbg_source-maps-04.js]
 skip-if = e10s # Bug 1093535
 [browser_dbg_sources-cache.js]
 skip-if = e10s && debug
+[browser_dbg_sources-eval.js]
+skip-if = e10s && debug
 [browser_dbg_sources-labels.js]
 skip-if = e10s && debug
 [browser_dbg_sources-sorting.js]
 skip-if = e10s && debug
 [browser_dbg_split-console-paused-reload.js]
 skip-if = e10s && debug
 [browser_dbg_stack-01.js]
 skip-if = e10s && debug
--- a/browser/devtools/debugger/test/browser_dbg_addon-sources.js
+++ b/browser/devtools/debugger/test/browser_dbg_addon-sources.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Ensure that the sources listed when debugging an addon are either from the 
 // addon itself, or the SDK, with proper groups and labels.
 
 const ADDON_URL = EXAMPLE_URL + "addon3.xpi";
+let gClient;
 
 function test() {
   Task.spawn(function () {
     let addon = yield addAddon(ADDON_URL);
     let addonDebugger = yield initAddonDebugger(ADDON_URL);
 
     is(addonDebugger.title, "Debugger - browser_dbg_addon3", "Saw the right toolbox title.");
 
--- a/browser/devtools/debugger/test/browser_dbg_addonactor.js
+++ b/browser/devtools/debugger/test/browser_dbg_addonactor.js
@@ -20,18 +20,18 @@ function test() {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     installAddon()
       .then(attachAddonActorForUrl.bind(null, gClient, ADDON3_URL))
       .then(attachAddonThread)
       .then(testDebugger)
       .then(testSources)
+      .then(closeConnection)
       .then(uninstallAddon)
-      .then(closeConnection)
       .then(finish)
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function installAddon () {
@@ -67,20 +67,19 @@ function testDebugger() {
 }
 
 function testSources() {
   let deferred = promise.defer();
 
   gThreadClient.getSources(aResponse => {
     // source URLs contain launch-specific temporary directory path,
     // hence the ".contains" call.
-    const matches = aResponse.sources.filter(s =>
-      s.url.contains(ADDON_MODULE_URL));
-    is(matches.length, 1,
-      "the main script of the addon is present in the source list");
+    const matches = aResponse.sources.filter(s => s.url.contains(ADDON_MODULE_URL));
+    ok(matches.length > 0,
+       "the main script of the addon is present in the source list");
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
 function uninstallAddon() {
   return removeAddon(gAddon);
--- a/browser/devtools/debugger/test/browser_dbg_auto-pretty-print-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_auto-pretty-print-01.js
@@ -31,37 +31,38 @@ function test(){
     waitForSourceShown(gPanel, gFirstSourceLabel)
       .then(testSourceIsUgly)
       .then(() => waitForSourceShown(gPanel, gFirstSourceLabel))
       .then(testSourceIsPretty)
       .then(disableAutoPrettyPrint)
       .then(testAutoPrettyPrintOff)
       .then(() => {
         let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
-          gSources.selectedIndex = 1;
-          return finished;
+        gSources.selectedIndex = 1;
+        return finished;
       })
       .then(testSecondSourceLabel)
       .then(testSourceIsUgly)
-      // Re-enable auto pretty printing for browser_dbg_auto-pretty-print-02.js
+       // Re-enable auto pretty printing for browser_dbg_auto-pretty-print-02.js
       .then(enableAutoPrettyPrint)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
       })
   });
 }
 
 function testSourceIsUgly() {
   ok(!gEditor.getText().contains("\n  "),
     "The source shouldn't be pretty printed yet.");
 }
 
 function testSecondSourceLabel(){
-  ok(gSources.containsValue(EXAMPLE_URL + gSecondSourceLabel),
+  let source = gSources.selectedItem.attachment.source;
+  ok(source.url === EXAMPLE_URL + gSecondSourceLabel,
     "Second source url is correct.");
 }
 
 function testProgressBarShown() {
   const deck = gDebugger.document.getElementById("editor-deck");
   is(deck.selectedIndex, 2, "The progress bar should be shown");
 }
 
--- a/browser/devtools/debugger/test/browser_dbg_auto-pretty-print-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_auto-pretty-print-02.js
@@ -63,22 +63,24 @@ function test(){
 }
 
 function testSourceIsUgly() {
   ok(!gEditor.getText().contains("\n  "),
     "The source shouldn't be pretty printed yet.");
 }
 
 function testFirstSourceLabel(){
-  ok(gSources.containsValue(EXAMPLE_URL + gFirstSourceLabel),
+  let source = gSources.selectedItem.attachment.source;
+  ok(source.url === EXAMPLE_URL + gFirstSourceLabel,
     "First source url is correct.");
 }
 
 function testSecondSourceLabel(){
-  ok(gSources.containsValue(EXAMPLE_URL + gSecondSourceLabel),
+  let source = gSources.selectedItem.attachment.source;
+  ok(source.url === EXAMPLE_URL + gSecondSourceLabel,
     "Second source url is correct.");
 }
 
 function testAutoPrettyPrintOn(){
   is(gPrefs.autoPrettyPrint, true,
     "The auto-pretty-print pref should be on.");
   is(gOptions._autoPrettyPrint.getAttribute("checked"), "true",
     "The Auto pretty print menu item should be checked.");
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
@@ -5,24 +5,25 @@
  * Test that black boxed frames are compressed into a single frame on the stack
  * view when we are already paused.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"
 
 let gTab, gPanel, gDebugger;
-let gFrames;
+let gFrames, gSources;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
       .then(testBlackBoxStack)
       .then(testBlackBoxSource)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
@@ -34,24 +35,25 @@ function test() {
 function testBlackBoxStack() {
   is(gFrames.itemCount, 6,
     "Should get 6 frames.");
   is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
     "And none of them are black boxed.");
 }
 
 function testBlackBoxSource() {
-  return toggleBlackBoxing(gPanel, BLACKBOXME_URL).then(aSource => {
+  return toggleBlackBoxing(gPanel, getSourceActor(gSources, BLACKBOXME_URL)).then(aSource => {
     ok(aSource.isBlackBoxed, "The source should be black boxed now.");
 
     is(gFrames.itemCount, 3,
       "Should only get 3 frames.");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
       "And one of them should be the combined black boxed frames.");
   });
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
+  gSources = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
@@ -5,40 +5,42 @@
  * Test that we get a stack frame for each black boxed source, not a single one
  * for all of them.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"
 
 let gTab, gPanel, gDebugger;
-let gFrames;
+let gFrames, gSources;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceShown(gPanel, BLACKBOXME_URL)
       .then(blackBoxSources)
       .then(testBlackBoxStack)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function blackBoxSources() {
   let finished = waitForThreadEvents(gPanel, "blackboxchange", 3);
-  toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_one.js");
-  toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_two.js");
-  toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_three.js");
+
+  toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_one.js"));
+  toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_two.js"));
+  toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_three.js"));
   return finished;
 }
 
 function testBlackBoxStack() {
   let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
     is(gFrames.itemCount, 4,
       "Should get 4 frames (one -> two -> three -> doDebuggerStatement).");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
@@ -49,9 +51,10 @@ function testBlackBoxStack() {
   return finished;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
+  gSources = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-06.js
@@ -25,25 +25,25 @@ function test() {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "runTest");
   });
 }
 
 function testBlackBox() {
-  const selectedUrl = gSources.selectedValue;
+  const selectedActor = gSources.selectedValue;
 
   let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => {
-    const newSelectedUrl = gSources.selectedValue;
-    isnot(selectedUrl, newSelectedUrl,
+    const newSelectedActor = gSources.selectedValue;
+    isnot(selectedActor, newSelectedActor,
       "Should not have the same url selected.");
 
     return toggleBlackBoxing(gPanel).then(() => {
-      is(gSources.selectedValue, newSelectedUrl,
+      is(gSources.selectedValue, newSelectedActor,
         "The selected source did not change.");
     });
   });
 
   gSources.selectedIndex = 0;
   return finished;
 }
 
--- a/browser/devtools/debugger/test/browser_dbg_breadcrumbs-access.js
+++ b/browser/devtools/debugger/test/browser_dbg_breadcrumbs-access.js
@@ -13,82 +13,74 @@ function test() {
 
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
-    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
       .then(checkNavigationWhileNotFocused)
       .then(focusCurrentStackFrame)
       .then(checkNavigationWhileFocused)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 
   function checkNavigationWhileNotFocused() {
-    checkState({ frame: 3, source: 1, line: 1 });
+    checkState({ frame: 1, source: 1, line: 6 });
 
     EventUtils.sendKey("DOWN", gDebugger);
-    checkState({ frame: 3, source: 1, line: 2 });
+    checkState({ frame: 1, source: 1, line: 7 });
 
     EventUtils.sendKey("UP", gDebugger);
-    checkState({ frame: 3, source: 1, line: 1 });
+    checkState({ frame: 1, source: 1, line: 6 });
   }
 
   function focusCurrentStackFrame() {
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gFrames.selectedItem.target,
       gDebugger);
   }
 
   function checkNavigationWhileFocused() {
     return Task.spawn(function() {
       yield promise.all([
         waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
-        EventUtils.sendKey("UP", gDebugger)
-      ]);
-      checkState({ frame: 2, source: 1, line: 1 });
-
-      yield promise.all([
-        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
-        waitForSourceAndCaret(gPanel, "-01.js", 1),
-        EventUtils.sendKey("UP", gDebugger)
-      ]);
-      checkState({ frame: 1, source: 0, line: 1 });
-
-      yield promise.all([
-        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+        waitForSourceAndCaret(gPanel, "-01.js", 5),
+        waitForEditorLocationSet(gPanel),
         EventUtils.sendKey("UP", gDebugger)
       ]);
       checkState({ frame: 0, source: 0, line: 5 });
 
       yield promise.all([
         waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
-        waitForSourceAndCaret(gPanel, "-02.js", 1),
+        waitForSourceAndCaret(gPanel, "-02.js", 6),
+        waitForEditorLocationSet(gPanel),
         EventUtils.sendKey("END", gDebugger)
       ]);
-      checkState({ frame: 3, source: 1, line: 1 });
+      checkState({ frame: 1, source: 1, line: 6 });
 
       yield promise.all([
         waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
-        waitForSourceAndCaret(gPanel, "-01.js", 1),
+        waitForSourceAndCaret(gPanel, "-01.js", 5),
+        waitForEditorLocationSet(gPanel),
         EventUtils.sendKey("HOME", gDebugger)
       ]);
+
       checkState({ frame: 0, source: 0, line: 5 });
     });
   }
 
-  function checkState({ frame, source, line }) {
+  function checkState({ frame, source, line, column }) {
     is(gFrames.selectedIndex, frame,
       "The currently selected stackframe is incorrect.");
     is(gSources.selectedIndex, source,
       "The currently selected source is incorrect.");
-    ok(isCaretPos(gPanel, line),
+    ok(isCaretPos(gPanel, line, column),
       "The source editor caret position was incorrect.");
   }
 }
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-actual-location.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-actual-location.js
@@ -30,58 +30,58 @@ function test() {
     is(gBreakpointsAdded.size, 0,
       "No breakpoints currently added.");
     is(gBreakpointsRemoving.size, 0,
       "No breakpoints currently being removed.");
     is(gEditor.getBreakpoints().length, 0,
       "No breakpoints currently shown in the editor.");
 
     gEditor.on("breakpointAdded", onEditorBreakpointAdd);
-    gPanel.addBreakpoint({ url: gSources.selectedValue, line: 4 }).then(onBreakpointAdd);
+    gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 4 }).then(onBreakpointAdd);
   }
 
   let onBpDebuggerAdd = false;
   let onBpEditorAdd = false;
 
   function onBreakpointAdd(aBreakpointClient) {
     ok(aBreakpointClient,
       "Breakpoint added, client received.");
-    is(aBreakpointClient.location.url, gSources.selectedValue,
+    is(aBreakpointClient.location.actor, gSources.selectedValue,
       "Breakpoint client url is the same.");
     is(aBreakpointClient.location.line, 6,
       "Breakpoint client line is new.");
 
-    is(aBreakpointClient.requestedLocation.url, gSources.selectedValue,
+    is(aBreakpointClient.requestedLocation.actor, gSources.selectedValue,
       "Requested location url is correct");
     is(aBreakpointClient.requestedLocation.line, 4,
       "Requested location line is correct");
 
     onBpDebuggerAdd = true;
     maybeFinish();
   }
 
   function onEditorBreakpointAdd() {
     gEditor.off("breakpointAdded", onEditorBreakpointAdd);
 
     is(gEditor.getBreakpoints().length, 1,
       "There is only one breakpoint in the editor");
 
-    ok(!gBreakpoints._getAdded({ url: gSources.selectedValue, line: 4 }),
+    ok(!gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 4 }),
       "There isn't any breakpoint added on an invalid line.");
-    ok(!gBreakpoints._getRemoving({ url: gSources.selectedValue, line: 4 }),
+    ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 4 }),
       "There isn't any breakpoint removed from an invalid line.");
 
-    ok(gBreakpoints._getAdded({ url: gSources.selectedValue, line: 6 }),
+    ok(gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }),
       "There is a breakpoint added on the actual line.");
-    ok(!gBreakpoints._getRemoving({ url: gSources.selectedValue, line: 6 }),
+    ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 6 }),
       "There isn't any breakpoint removed from the actual line.");
 
-    gBreakpoints._getAdded({ url: gSources.selectedValue, line: 6 }).then(aBreakpointClient => {
-      is(aBreakpointClient.location.url, gSources.selectedValue,
-        "Breakpoint client location url is correct.");
+    gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }).then(aBreakpointClient => {
+      is(aBreakpointClient.location.actor, gSources.selectedValue,
+        "Breakpoint client location actor is correct.");
       is(aBreakpointClient.location.line, 6,
         "Breakpoint client location line is correct.");
 
       onBpEditorAdd = true;
       maybeFinish();
     });
   }
 
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-actual-location2.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-actual-location2.js
@@ -32,34 +32,34 @@ function test() {
       "No breakpoints currently added.");
     is(gBreakpointsRemoving.size, 0,
       "No breakpoints currently being removed.");
     is(gEditor.getBreakpoints().length, 0,
       "No breakpoints currently shown in the editor.");
 
     Task.spawn(function*() {
       let bpClient = yield gPanel.addBreakpoint({
-        url: gSources.selectedValue,
+        actor: gSources.selectedValue,
         line: 19
       });
       yield gPanel.addBreakpoint({
-        url: gSources.selectedValue,
+        actor: gSources.selectedValue,
         line: 20
       });
 
       let movedBpClient = yield gPanel.addBreakpoint({
-        url: gSources.selectedValue,
+        actor: gSources.selectedValue,
         line: 17
       });
       testMovedLocation(movedBpClient);
 
       yield resumeAndTestBreakpoint(19);
 
       yield gPanel.removeBreakpoint({
-        url: gSources.selectedValue,
+        actor: gSources.selectedValue,
         line: 19
       });
 
       yield resumeAndTestBreakpoint(20);
       yield doResume(gPanel);
 
       callInTab(gTab, "ermahgerd");
       yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
@@ -84,19 +84,19 @@ function test() {
        "There should be a selected breakpoint on line " + line);
     is(selectedBreakpoint.attachment.line, line,
        "The breakpoint on line " + line + " was not hit");
   }
 
   function testMovedLocation(breakpointClient) {
     ok(breakpointClient,
       "Breakpoint added, client received.");
-    is(breakpointClient.location.url, gSources.selectedValue,
+    is(breakpointClient.location.actor, gSources.selectedValue,
       "Breakpoint client url is the same.");
     is(breakpointClient.location.line, 19,
       "Breakpoint client line is new.");
 
-    is(breakpointClient.requestedLocation.url, gSources.selectedValue,
+    is(breakpointClient.requestedLocation.actor, gSources.selectedValue,
       "Requested location url is correct");
     is(breakpointClient.requestedLocation.line, 17,
       "Requested location line is correct");
   }
 }
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -8,23 +8,24 @@
 
 const TAB_URL = EXAMPLE_URL + "doc_breakpoints-break-on-last-line-of-script-on-reload.html";
 const CODE_URL = EXAMPLE_URL + "code_breakpoints-break-on-last-line-of-script-on-reload.js";
 
 function test() {
   // Debug test slaves are a bit slow at this test.
   requestLongerTimeout(2);
 
-  let gPanel, gDebugger, gThreadClient, gEvents;
+  let gPanel, gDebugger, gThreadClient, gEvents, gSources;
 
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gThreadClient = gDebugger.gThreadClient;
     gEvents = gDebugger.EVENTS;
+    gSources = gDebugger.DebuggerView.Sources;
 
     Task.spawn(function* () {
       try {
 
         // Refresh and hit the debugger statement before the location we want to
         // set our breakpoints. We have to pause before the breakpoint locations
         // so that GC doesn't get a chance to kick in and collect the IIFE's
         // script, which would causes us to receive a 'noScript' error from the
@@ -87,18 +88,21 @@ function test() {
           e
         );
         ok(false);
       }
     });
   });
 
   function setBreakpoint(location) {
+    let item = gSources.getItemByValue(getSourceActor(gSources, location.url));
+    let source = gThreadClient.source(item.attachment.source);
+
     let deferred = promise.defer();
-    gThreadClient.setBreakpoint(location, ({ error, message }, bpClient) => {
+    source.setBreakpoint(location, ({ error, message }, bpClient) => {
       if (error) {
         deferred.reject(error + ": " + message);
       }
       deferred.resolve(bpClient);
     });
     return deferred.promise;
   }
 }
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-button-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-button-01.js
@@ -26,19 +26,19 @@ function test() {
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function addBreakpoints() {
     return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[0], line: 5 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 6 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 7 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
       .then(() => ensureThreadClientState(gPanel, "resumed"));
   }
 
   function testDisableBreakpoints() {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 3);
     gSources.toggleBreakpoints();
     return finished.then(() => checkBreakpointsDisabled(true));
   }
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-button-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-button-02.js
@@ -28,26 +28,26 @@ function test() {
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function addBreakpoints() {
     return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[0], line: 5 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 6 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 7 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
       .then(() => ensureThreadClientState(gPanel, "resumed"));
   }
 
   function disableSomeBreakpoints() {
     return promise.all([
-      gSources.disableBreakpoint({ url: gSources.values[0], line: 5 }),
-      gSources.disableBreakpoint({ url: gSources.values[1], line: 6 })
+      gSources.disableBreakpoint({ actor: gSources.values[0], line: 5 }),
+      gSources.disableBreakpoint({ actor: gSources.values[1], line: 6 })
     ]);
   }
 
   function testToggleBreakpoints() {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 1);
     gSources.toggleBreakpoints();
     return finished.then(() => checkBreakpointsDisabled(true));
   }
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-contextmenu-add.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-contextmenu-add.js
@@ -61,17 +61,17 @@ function test() {
       let bpShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_EDITOR);
       EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
       return bpShown;
     }).then(() => {
       is(gBreakpointsAdded.size, 1,
          "1 breakpoint correctly added");
       is(gEditor.getBreakpoints().length, 1,
          "1 breakpoint currently shown in the editor.");
-      ok(gBreakpoints._getAdded({ url: gSources.values[1], line: 7 }),
+      ok(gBreakpoints._getAdded({ actor: gSources.values[1], line: 7 }),
          "Breakpoint on line 7 exists");
     });
   }
 
   function testAddConditionalBreakpoint() {
     gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
     gEditor.emit("gutterClick", 7, 2);
 
@@ -83,13 +83,13 @@ function test() {
       let bpShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
       EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
       return bpShown;
     }).then(() => {
       is(gBreakpointsAdded.size, 2,
          "2 breakpoints correctly added");
       is(gEditor.getBreakpoints().length, 2,
          "2 breakpoints currently shown in the editor.");
-      ok(gBreakpoints._getAdded({ url: gSources.values[1], line: 8 }),
+      ok(gBreakpoints._getAdded({ actor: gSources.values[1], line: 8 }),
          "Breakpoint on line 8 exists");
     });
   }
 }
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-contextmenu.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-contextmenu.js
@@ -27,21 +27,21 @@ function test() {
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function addBreakpoints() {
     return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[0], line: 5 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 6 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 7 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 8 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 9 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 8 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 9 }))
       .then(() => ensureThreadClientState(gPanel, "resumed"));
   }
 
   function performTestWhileNotPaused() {
     info("Performing test while not paused...");
 
     return addBreakpoints()
       .then(initialChecks)
@@ -74,25 +74,27 @@ function test() {
       .then(() => checkBreakpointToggleOthers(3))
       .then(() => checkBreakpointToggleSelf(4))
       .then(() => checkBreakpointToggleOthers(4))
       .then(testDeleteAll);
   }
 
   function pauseAndCheck() {
     let finished = waitForSourceAndCaretAndScopes(gPanel, "-01.js", 5).then(() => {
-      is(gSources.selectedValue, EXAMPLE_URL + "code_script-switching-01.js",
+      let source = gSources.selectedItem.attachment.source;
+      is(source.url, EXAMPLE_URL + "code_script-switching-01.js",
         "The currently selected source is incorrect (3).");
       is(gSources.selectedIndex, 0,
         "The currently selected source is incorrect (4).");
       ok(isCaretPos(gPanel, 5),
         "The editor location is correct after pausing.");
     });
 
-    is(gSources.selectedValue, EXAMPLE_URL + "code_script-switching-02.js",
+    let source = gSources.selectedItem.attachment.source;
+    is(source.url, EXAMPLE_URL + "code_script-switching-02.js",
       "The currently selected source is incorrect (1).");
     is(gSources.selectedIndex, 1,
       "The currently selected source is incorrect (2).");
     ok(isCaretPos(gPanel, 9),
       "The editor location is correct before pausing.");
 
     sendMouseClickToTab(gTab, content.document.querySelector("button"));
 
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-disabled-reload.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-disabled-reload.js
@@ -10,20 +10,22 @@ const TAB_URL = EXAMPLE_URL + "doc_scrip
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let gTab = aTab;
     let gDebugger = aPanel.panelWin;
     let gEvents = gDebugger.EVENTS;
     let gEditor = gDebugger.DebuggerView.editor;
     let gSources = gDebugger.DebuggerView.Sources;
     let gBreakpoints = gDebugger.DebuggerController.Breakpoints;
-    let gBreakpointLocation = { url: EXAMPLE_URL + "code_script-switching-01.js", line: 5 };
-
+    let gBreakpointLocation;
     Task.spawn(function() {
       yield waitForSourceShown(aPanel, "-01.js");
+      gBreakpointLocation = { actor: getSourceActor(gSources, EXAMPLE_URL + "code_script-switching-01.js"),
+                              line: 5 };
+
       yield aPanel.addBreakpoint(gBreakpointLocation);
 
       yield ensureThreadClientState(aPanel, "resumed");
       yield testWhenBreakpointEnabledAndFirstSourceShown();
 
       yield reloadActiveTab(aPanel, gEvents.SOURCE_SHOWN);
       yield testWhenBreakpointEnabledAndSecondSourceShown();
 
@@ -97,20 +99,20 @@ function test() {
     function testWhenBreakpointDisabledAndSecondSourceShown() {
       return Task.spawn(function() {
         yield ensureSourceIs(aPanel, "-02.js", true);
         yield verifyView({ disabled: true, visible: false });
 
         callInTab(gTab, "firstCall");
         yield waitForDebuggerEvents(aPanel, gEvents.FETCHED_SCOPES);
         yield ensureSourceIs(aPanel, "-02.js");
-        yield ensureCaretAt(aPanel, 1);
+        yield ensureCaretAt(aPanel, 6);
         yield verifyView({ disabled: true, visible: false });
 
         executeSoon(() => gDebugger.gThreadClient.resume());
         yield waitForDebuggerEvents(aPanel, gEvents.AFTER_FRAMES_CLEARED);
         yield ensureSourceIs(aPanel, "-02.js");
-        yield ensureCaretAt(aPanel, 1);
+        yield ensureCaretAt(aPanel, 6);
         yield verifyView({ disabled: true, visible: false });
       });
     }
   });
 }
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-editor.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-editor.js
@@ -26,17 +26,17 @@ function test() {
     callInTab(gTab, "firstCall");
   });
 
   function performTest() {
     is(gDebugger.gThreadClient.state, "paused",
       "Should only be getting stack frames while paused.");
     is(gSources.itemCount, 2,
       "Found the expected number of sources.");
-    is(gEditor.getText().indexOf("debugger"), 172,
+    is(gEditor.getText().indexOf("debugger"), 166,
       "The correct source was loaded initially.");
     is(gSources.selectedValue, gSources.values[1],
       "The correct source is selected.");
 
     is(gBreakpointsAdded.size, 0,
       "No breakpoints currently added.");
     is(gBreakpointsRemoving.size, 0,
       "No breakpoints currently being removed.");
@@ -47,17 +47,17 @@ function test() {
       "_getAdded('foo', 3) returns falsey.");
     ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
       "_getRemoving('bar', 3) returns falsey.");
 
     is(gSources.values[1], gSources.selectedValue,
       "The second source should be currently selected.");
 
     info("Add the first breakpoint.");
-    let location = { url: gSources.selectedValue, line: 6 };
+    let location = { actor: gSources.selectedValue, line: 6 };
     gEditor.once("breakpointAdded", onEditorBreakpointAddFirst);
     gPanel.addBreakpoint(location).then(onBreakpointAddFirst);
   }
 
   let breakpointsAdded = 0;
   let breakpointsRemoved = 0;
   let editorBreakpointChanges = 0;
 
@@ -73,17 +73,17 @@ function test() {
       "editor.getBreakpoints().length is correct.");
   }
 
   function onBreakpointAddFirst(aBreakpointClient) {
     breakpointsAdded++;
 
     ok(aBreakpointClient,
       "breakpoint1 added, client received.");
-    is(aBreakpointClient.location.url, gSources.selectedValue,
+    is(aBreakpointClient.location.actor, gSources.selectedValue,
       "breakpoint1 client url is correct.");
     is(aBreakpointClient.location.line, 6,
       "breakpoint1 client line is correct.");
 
     ok(gBreakpoints._getAdded(aBreakpointClient.location),
       "breakpoint1 client found in the list of added breakpoints.");
     ok(!gBreakpoints._getRemoving(aBreakpointClient.location),
       "breakpoint1 client found in the list of removing breakpoints.");
@@ -118,42 +118,42 @@ function test() {
       "editor.getBreakpoints().length is correct.");
   }
 
   function onBreakpointRemoveFirst(aLocation) {
     breakpointsRemoved++;
 
     ok(aLocation,
       "breakpoint1 removed");
-    is(aLocation.url, gSources.selectedValue,
+    is(aLocation.actor, gSources.selectedValue,
       "breakpoint1 removal url is correct.");
     is(aLocation.line, 6,
       "breakpoint1 removal line is correct.");
 
     testBreakpointAddBackground();
   }
 
   function testBreakpointAddBackground() {
     is(gBreakpointsAdded.size, 0,
       "No breakpoints currently added.");
     is(gBreakpointsRemoving.size, 0,
       "No breakpoints currently being removed.");
     is(gEditor.getBreakpoints().length, 0,
       "No breakpoints currently shown in the editor.");
 
-    ok(!gBreakpoints._getAdded({ url: gSources.selectedValue, line: 6 }),
+    ok(!gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }),
       "_getAdded('gSources.selectedValue', 6) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ url: gSources.selectedValue, line: 6 }),
+    ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 6 }),
       "_getRemoving('gSources.selectedValue', 6) returns falsey.");
 
     is(gSources.values[1], gSources.selectedValue,
       "The second source should be currently selected.");
 
     info("Add a breakpoint to the first source, which is not selected.");
-    let location = { url: gSources.values[0], line: 5 };
+    let location = { actor: gSources.values[0], line: 5 };
     let options = { noEditorUpdate: true };
     gEditor.on("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
     gPanel.addBreakpoint(location, options).then(onBreakpointAddBackground);
   }
 
   function onEditorBreakpointAddBackgroundTrap() {
     // Trap listener: no breakpoint must be added to the editor when a
     // breakpoint is added to a source that is not currently selected.
@@ -161,17 +161,17 @@ function test() {
     ok(false, "breakpoint2 must not be added to the editor.");
   }
 
   function onBreakpointAddBackground(aBreakpointClient, aResponseError) {
     breakpointsAdded++;
 
     ok(aBreakpointClient,
       "breakpoint2 added, client received");
-    is(aBreakpointClient.location.url, gSources.values[0],
+    is(aBreakpointClient.location.actor, gSources.values[0],
       "breakpoint2 client url is correct.");
     is(aBreakpointClient.location.line, 5,
       "breakpoint2 client line is correct.");
 
     ok(gBreakpoints._getAdded(aBreakpointClient.location),
       "breakpoint2 client found in the list of added breakpoints.");
     ok(!gBreakpoints._getRemoving(aBreakpointClient.location),
       "breakpoint2 client found in the list of removing breakpoints.");
@@ -271,29 +271,29 @@ function test() {
   function finalCheck() {
     is(gBreakpointsAdded.size, 0,
       "No breakpoints currently added.");
     is(gBreakpointsRemoving.size, 0,
       "No breakpoints currently being removed.");
     is(gEditor.getBreakpoints().length, 0,
       "No breakpoints currently shown in the editor.");
 
-    ok(!gBreakpoints._getAdded({ url: gSources.values[0], line: 5 }),
+    ok(!gBreakpoints._getAdded({ actor: gSources.values[0], line: 5 }),
       "_getAdded('gSources.values[0]', 5) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ url: gSources.values[0], line: 5 }),
+    ok(!gBreakpoints._getRemoving({ actor: gSources.values[0], line: 5 }),
       "_getRemoving('gSources.values[0]', 5) returns falsey.");
 
-    ok(!gBreakpoints._getAdded({ url: gSources.values[1], line: 6 }),
+    ok(!gBreakpoints._getAdded({ actor: gSources.values[1], line: 6 }),
       "_getAdded('gSources.values[1]', 6) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ url: gSources.values[1], line: 6 }),
+    ok(!gBreakpoints._getRemoving({ actor: gSources.values[1], line: 6 }),
       "_getRemoving('gSources.values[1]', 6) returns falsey.");
 
-    ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
+    ok(!gBreakpoints._getAdded({ actor: "foo", line: 3 }),
       "_getAdded('foo', 3) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
+    ok(!gBreakpoints._getRemoving({ actor: "bar", line: 3 }),
       "_getRemoving('bar', 3) returns falsey.");
 
     is(breakpointsAdded, 2,
       "Correct number of breakpoints have been added.");
     is(breakpointsRemoved, 1,
       "Correct number of breakpoints have been removed.");
     is(editorBreakpointChanges, 4,
       "Correct number of editor breakpoint changes.");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-eval.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test setting breakpoints on an eval script
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
+
+function test() {
+  let gTab, gPanel, gDebugger;
+  let gSources, gBreakpoints;
+
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+    gTab = aTab;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gSources = gDebugger.DebuggerView.Sources;
+    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+    waitForSourceShown(gPanel, "-eval.js")
+      .then(run)
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+  });
+
+  function run() {
+    return Task.spawn(function*() {
+      let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
+      callInTab(gTab, "evalSource");
+      yield newSource;
+
+      yield gPanel.addBreakpoint({ actor: gSources.values[1], line: 2 });
+      yield ensureThreadClientState(gPanel, "resumed");
+
+      const paused = waitForThreadEvents(gPanel, "paused");
+      callInTab(gTab, "bar");
+      let frame = (yield paused).frame;
+      is(frame.where.source.actor, gSources.values[1], "Should have broken on the eval'ed source");
+      is(frame.where.line, 2, "Should break on line 2");
+
+      yield resumeDebuggerThenCloseAndFinish(gPanel);
+    });
+  }
+}
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-highlight.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-highlight.js
@@ -30,27 +30,27 @@ function test() {
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function addBreakpoints() {
     return promise.resolve(null)
       .then(() => initialChecks(0, 1))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[0], line: 5 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
       .then(() => initialChecks(0, 5))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 6 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
       .then(() => waitForSourceShown(gPanel, "-02.js"))
       .then(() => waitForCaretUpdated(gPanel, 6))
       .then(() => initialChecks(1, 6))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 7 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
       .then(() => initialChecks(1, 7))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 8 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 8 }))
       .then(() => initialChecks(1, 8))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 9 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 9 }))
       .then(() => initialChecks(1, 9));
   }
 
   function initialChecks(aSourceIndex, aCaretLine) {
     checkEditorContents(aSourceIndex);
 
     is(gSources.selectedLabel, gSources.items[aSourceIndex].label,
       "The currently selected source label is incorrect (0).");
@@ -75,29 +75,29 @@ function test() {
 
     EventUtils.sendMouseEvent({ type: "click" },
       gDebugger.document.querySelectorAll(".dbg-breakpoint")[aBreakpointIndex],
       gDebugger);
 
     return finished;
   }
 
-  function checkHighlight(aUrl, aLine) {
-    is(gSources._selectedBreakpointItem, gSources.getBreakpoint({ url: aUrl, line: aLine }),
+  function checkHighlight(aActor, aLine) {
+    is(gSources._selectedBreakpointItem, gSources.getBreakpoint({ actor: aActor, line: aLine }),
       "The currently selected breakpoint item is incorrect.");
-    is(gSources._selectedBreakpointItem.attachment.url, aUrl,
+    is(gSources._selectedBreakpointItem.attachment.actor, aActor,
       "The selected breakpoint item's source location attachment is incorrect.");
     is(gSources._selectedBreakpointItem.attachment.line, aLine,
       "The selected breakpoint item's source line number is incorrect.");
     ok(gSources._selectedBreakpointItem.target.classList.contains("selected"),
       "The selected breakpoint item's target should have a selected class.");
   }
 
   function checkEditorContents(aSourceIndex) {
     if (aSourceIndex == 0) {
       is(gEditor.getText().indexOf("firstCall"), 118,
         "The first source is correctly displayed.");
     } else {
-      is(gEditor.getText().indexOf("debugger"), 172,
+      is(gEditor.getText().indexOf("debugger"), 166,
         "The second source is correctly displayed.");
     }
   }
 }
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-new-script.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-new-script.js
@@ -3,36 +3,37 @@
 
 /**
  * Bug 771452: Make sure that setting a breakpoint in an inline source doesn't
  * add it twice.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_inline-script.html";
 
-let gTab, gPanel, gDebugger;
+let gTab, gPanel, gDebugger, gSources;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
+    gSources = gDebugger.DebuggerView.Sources;
 
     addBreakpoint();
   });
 }
 
 function addBreakpoint() {
   waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
     is(gDebugger.gThreadClient.state, "paused",
       "The debugger statement was reached.");
     ok(isCaretPos(gPanel, 16),
       "The source editor caret position is incorrect (1).");
 
-    gPanel.addBreakpoint({ url: TAB_URL, line: 20 }).then(() => {
+    gPanel.addBreakpoint({ actor: getSourceActor(gSources, TAB_URL), line: 20 }).then(() => {
       testResume();
     });
   });
 
   callInTab(gTab, "runDebuggerStatement");
 }
 
 function testResume() {
@@ -78,9 +79,10 @@ function testBreakpointHit() {
     gDebugger.document.getElementById("resume"),
     gDebugger);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
+  gSources = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-other-tabs.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-other-tabs.js
@@ -12,17 +12,17 @@ let test = Task.async(function* () {
   const [tab1,, panel1] = yield initDebugger(TAB_URL);
   const [tab2,, panel2] = yield initDebugger(TAB_URL);
 
   yield ensureSourceIs(panel1, "code_breakpoints-other-tabs.js", true);
 
   const sources = panel1.panelWin.DebuggerView.Sources;
 
   yield panel1.addBreakpoint({
-    url: sources.selectedValue,
+    actor: sources.selectedValue,
     line: 2
   });
 
   const paused = waitForThreadEvents(panel2, "paused");
   callInTab(tab2, "testCase");
   const packet = yield paused;
 
   is(packet.why.type, "debuggerStatement",
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-pane.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-pane.js
@@ -30,17 +30,17 @@ function test() {
   let breakpointsDisabled = 0;
   let breakpointsRemoved = 0;
 
   function performTest() {
     is(gDebugger.gThreadClient.state, "paused",
       "Should only be getting stack frames while paused.");
     is(gSources.itemCount, 2,
       "Found the expected number of sources.");
-    is(gEditor.getText().indexOf("debugger"), 172,
+    is(gEditor.getText().indexOf("debugger"), 166,
       "The correct source was loaded initially.");
     is(gSources.selectedValue, gSources.values[1],
       "The correct source is selected.");
 
     is(gBreakpointsAdded.size, 0,
       "No breakpoints currently added.");
     is(gBreakpointsRemoving.size, 0,
       "No breakpoints currently being removed.");
@@ -130,31 +130,31 @@ function test() {
           });
         });
       });
     });
 
     function addBreakpoints(aIncrementFlag) {
       let deferred = promise.defer();
 
-      gPanel.addBreakpoint({ url: gSources.selectedValue, line: 6 }).then(aClient => {
+      gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 6 }).then(aClient => {
         onBreakpointAdd(aClient, {
           increment: aIncrementFlag,
           line: 6,
-          text: "eval(\"debugger;\");"
+          text: "debugger;"
         });
 
-        gPanel.addBreakpoint({ url: gSources.selectedValue, line: 7 }).then(aClient => {
+        gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 7 }).then(aClient => {
           onBreakpointAdd(aClient, {
             increment: aIncrementFlag,
             line: 7,
             text: "function foo() {}"
           });
 
-          gPanel.addBreakpoint({ url: gSources.selectedValue, line: 9 }).then(aClient => {
+          gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 9 }).then(aClient => {
             onBreakpointAdd(aClient, {
               increment: aIncrementFlag,
               line: 9,
               text: "foo();"
             });
 
             deferred.resolve();
           });
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-reload.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-reload.js
@@ -11,25 +11,25 @@ const TAB_URL = EXAMPLE_URL + "doc_break
 let test = Task.async(function* () {
   requestLongerTimeout(4);
 
   const [tab,, panel] = yield initDebugger(TAB_URL);
 
   yield ensureSourceIs(panel, "doc_breakpoints-reload.html", true);
 
   const sources = panel.panelWin.DebuggerView.Sources;
-
   yield panel.addBreakpoint({
-    url: sources.selectedValue,
+    actor: sources.selectedValue,
     line: 10 // "break on me" string
   });
 
   const paused = waitForThreadEvents(panel, "paused");
   reloadActiveTab(panel);
   const packet = yield paused;
 
   is(packet.why.type, "breakpoint",
      "Should have hit the breakpoint after the reload");
   is(packet.frame.where.line, 10,
      "Should have stopped at line 10, where we set the breakpoint");
 
+  yield waitForDebuggerEvents(panel, panel.panelWin.EVENTS.SOURCE_SHOWN)
   yield resumeDebuggerThenCloseAndFinish(panel);
 });
--- a/browser/devtools/debugger/test/browser_dbg_clean-exit.js
+++ b/browser/devtools/debugger/test/browser_dbg_clean-exit.js
@@ -15,21 +15,22 @@ function test() {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
 
     testCleanExit();
   });
 }
 
 function testCleanExit() {
-  waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
+  promise.all([
+    waitForSourceAndCaretAndScopes(gPanel, ".html", 16),
+    waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED)
+  ]).then(() => {
     is(gDebugger.gThreadClient.paused, true,
       "Should be paused after the debugger statement.");
-
-    return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED);
   }).then(() => closeDebuggerAndFinish(gPanel, { whilePaused: true }));
 
   callInTab(gTab, "runDebuggerStatement");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
--- a/browser/devtools/debugger/test/browser_dbg_cmd-break.js
+++ b/browser/devtools/debugger/test/browser_dbg_cmd-break.js
@@ -1,200 +1,221 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the break commands works as they should.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_cmd-break.html";
+let TAB_URL_ACTOR;
 
 function test() {
-  let gPanel, gDebugger, gThreadClient;
+  let gPanel, gDebugger, gThreadClient, gSources;
   let gLineNumber;
 
+  let expectedActorObj = {
+    value: null,
+    message: ''
+  };
+
   helpers.addTabWithToolbar(TAB_URL, aOptions => {
-    return helpers.audit(aOptions, [
-      {
+    return Task.spawn(function() {
+      yield helpers.audit(aOptions, [{
         setup: 'break',
         check: {
           input:  'break',
           hints:       ' add line',
           markup: 'IIIII',
           status: 'ERROR',
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         setup: 'break add',
         check: {
           input:  'break add',
           hints:           ' line',
           markup: 'IIIIIVIII',
           status: 'ERROR'
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         setup: 'break add line',
         check: {
           input:  'break add line',
           hints:                ' <file> <line>',
           markup: 'VVVVVVVVVVVVVV',
           status: 'ERROR'
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         name: 'open toolbox',
         setup: function() {
           return initDebugger(gBrowser.selectedTab).then(([aTab, aDebuggee, aPanel]) => {
             // Spin the event loop before causing the debuggee to pause, to allow
             // this function to return first.
             executeSoon(() => aDebuggee.firstCall());
 
             return waitForSourceAndCaretAndScopes(aPanel, ".html", 1).then(() => {
               gPanel = aPanel;
               gDebugger = gPanel.panelWin;
               gThreadClient = gPanel.panelWin.gThreadClient;
               gLineNumber = '' + aOptions.window.wrappedJSObject.gLineNumber;
+              gSources = gDebugger.DebuggerView.Sources;
+
+              expectedActorObj.value = getSourceActor(gSources, TAB_URL);
             });
           });
         },
         post: function() {
           ok(gThreadClient, "Debugger client exists.");
-          is(gLineNumber, 1, "gLineNumber is correct.");
+          is(gLineNumber, 14, "gLineNumber is correct.");
         },
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         name: 'break add line .../doc_cmd-break.html 14',
         setup: function() {
           // We have to setup in a function to allow gLineNumber to be initialized.
           let line = 'break add line ' + TAB_URL + ' ' + gLineNumber;
           return helpers.setInput(aOptions, line);
         },
         check: {
           hints: '',
           status: 'VALID',
           message: '',
           args: {
-            file: { value: TAB_URL, message: '' },
-            line: { value: 1 }
+            file: expectedActorObj,
+            line: { value: 14 }
           }
         },
         exec: {
           output: 'Added breakpoint'
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         setup: 'break add line ' + TAB_URL + ' 17',
         check: {
           hints: '',
           status: 'VALID',
           message: '',
           args: {
-            file: { value: TAB_URL, message: '' },
+            file: expectedActorObj,
             line: { value: 17 }
           }
         },
         exec: {
           output: 'Added breakpoint'
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         setup: 'break list',
         check: {
           input:  'break list',
           hints:            '',
           markup: 'VVVVVVVVVV',
           status: 'VALID'
         },
         exec: {
           output: [
             /Source/, /Remove/,
-            /doc_cmd-break\.html:1/,
-            /doc_cmd-break\.html:1/
+            /doc_cmd-break\.html:14/,
+            /doc_cmd-break\.html:17/
           ]
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         name: 'cleanup',
         setup: function() {
           let deferred = promise.defer();
           gThreadClient.resume(deferred.resolve);
           return deferred.promise;
         }
-      },
-      {
-        setup: 'break del 1',
+      }]);
+
+      yield helpers.audit(aOptions, [{
+        setup: 'break del 14',
         check: {
-          input:  'break del 1',
-          hints:              ' -> doc_cmd-break.html:1',
-          markup: 'VVVVVVVVVVI',
+          input:  'break del 14',
+          hints:              ' -> doc_cmd-break.html:14',
+          markup: 'VVVVVVVVVVII',
           status: 'ERROR',
           args: {
             breakpoint: {
               status: 'INCOMPLETE',
               message: 'Value required for \'breakpoint\'.'
             }
           }
         }
-      },
-      {
-        setup: 'break del doc_cmd-break.html:1',
+      }]);
+
+      yield helpers.audit(aOptions, [{
+        setup: 'break del doc_cmd-break.html:14',
         check: {
-          input:  'break del doc_cmd-break.html:1',
+          input:  'break del doc_cmd-break.html:14',
           hints:                                 '',
-          markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+          markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
           status: 'VALID',
           args: {
-            breakpoint: { arg: ' doc_cmd-break.html:1' },
+            breakpoint: { arg: ' doc_cmd-break.html:14' },
           }
         },
         exec: {
           output: 'Breakpoint removed'
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         setup: 'break list',
         check: {
           input:  'break list',
           hints:            '',
           markup: 'VVVVVVVVVV',
           status: 'VALID'
         },
         exec: {
           output: [
             /Source/, /Remove/,
             /doc_cmd-break\.html:17/
           ]
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         setup: 'break del doc_cmd-break.html:17',
         check: {
           input:  'break del doc_cmd-break.html:17',
           hints:                                 '',
           markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
           status: 'VALID',
           args: {
             breakpoint: { arg: ' doc_cmd-break.html:17' },
           }
         },
         exec: {
           output: 'Breakpoint removed'
         }
-      },
-      {
+      }]);
+
+      yield helpers.audit(aOptions, [{
         setup: 'break list',
         check: {
           input:  'break list',
           hints:            '',
           markup: 'VVVVVVVVVV',
           status: 'VALID'
         },
         exec: {
           output: 'No breakpoints set'
         },
         post: function() {
           return teardown(gPanel, { noTabRemoval: true });
         }
-      },
-    ]);
+      }]);
+    });
   }).then(finish);
 }
--- a/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints-01.js
@@ -66,41 +66,41 @@ function test() {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "ermahgerd");
   });
 
   function addBreakpoints() {
     return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 18 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 18 }))
       .then(aClient => aClient.conditionalExpression = "undefined")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 19 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 19 }))
       .then(aClient => aClient.conditionalExpression = "null")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 20 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 20 }))
       .then(aClient => aClient.conditionalExpression = "42")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 21 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 21 }))
       .then(aClient => aClient.conditionalExpression = "true")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 22 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 22 }))
       .then(aClient => aClient.conditionalExpression = "'nasu'")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 23 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 23 }))
       .then(aClient => aClient.conditionalExpression = "/regexp/")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 24 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 24 }))
       .then(aClient => aClient.conditionalExpression = "({})")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 25 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 25 }))
       .then(aClient => aClient.conditionalExpression = "(function() {})")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 26 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 26 }))
       .then(aClient => aClient.conditionalExpression = "(function() { return false; })()")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 27 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 27 }))
       .then(aClient => aClient.conditionalExpression = "a")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 28 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 28 }))
       .then(aClient => aClient.conditionalExpression = "a !== undefined")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 29 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 29 }))
       .then(aClient => aClient.conditionalExpression = "b")
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue, line: 30 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 30 }))
       .then(aClient => aClient.conditionalExpression = "a !== null");
   }
 
   function initialChecks() {
     is(gDebugger.gThreadClient.state, "paused",
       "Should only be getting stack frames while paused.");
     is(gSources.itemCount, 1,
       "Found the expected number of sources.");
@@ -158,57 +158,59 @@ function test() {
 
     return finished;
   }
 
   function testBreakpoint(aLine, aHighlightBreakpoint) {
     // Highlight the breakpoint only if required.
     if (aHighlightBreakpoint) {
       let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
-      gSources.highlightBreakpoint({ url: gSources.selectedValue, line: aLine });
+      gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: aLine });
       return finished;
     }
 
-    let selectedUrl = gSources.selectedValue;
+    let selectedActor = gSources.selectedValue;
     let selectedBreakpoint = gSources._selectedBreakpointItem;
 
-    ok(selectedUrl,
+    ok(selectedActor,
       "There should be a selected item in the sources pane.");
     ok(selectedBreakpoint,
       "There should be a selected breakpoint in the sources pane.");
 
-    is(selectedBreakpoint.attachment.url, selectedUrl,
+    let source = gSources.selectedItem.attachment.source;
+
+    is(selectedBreakpoint.attachment.actor, source.actor,
       "The breakpoint on line " + aLine + " wasn't added on the correct source.");
     is(selectedBreakpoint.attachment.line, aLine,
       "The breakpoint on line " + aLine + " wasn't found.");
     is(!!selectedBreakpoint.attachment.disabled, false,
       "The breakpoint on line " + aLine + " should be enabled.");
     is(!!selectedBreakpoint.attachment.openPopup, false,
       "The breakpoint on line " + aLine + " should not have opened a popup.");
     is(gSources._conditionalPopupVisible, false,
       "The breakpoint conditional expression popup should not have been shown.");
 
     return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
-      is(aBreakpointClient.location.url, selectedUrl,
+      is(aBreakpointClient.location.url, source.url,
         "The breakpoint's client url is correct");
       is(aBreakpointClient.location.line, aLine,
         "The breakpoint's client line is correct");
       isnot(aBreakpointClient.conditionalExpression, undefined,
         "The breakpoint on line " + aLine + " should have a conditional expression.");
 
       ok(isCaretPos(gPanel, aLine),
         "The editor caret position is not properly set.");
     });
   }
 
   function testAfterReload() {
-    let selectedUrl = gSources.selectedValue;
+    let selectedActor = gSources.selectedValue;
     let selectedBreakpoint = gSources._selectedBreakpointItem;
 
-    ok(selectedUrl,
+    ok(selectedActor,
       "There should be a selected item in the sources pane after reload.");
     ok(!selectedBreakpoint,
       "There should be no selected breakpoint in the sources pane after reload.");
 
     return promise.resolve(null)
       .then(() => testBreakpoint(18, true))
       .then(() => testBreakpoint(19, true))
       .then(() => testBreakpoint(20, true))
--- a/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints-02.js
@@ -81,25 +81,25 @@ function test() {
 
     is(gBreakpointsAdded.size, 0,
       "No breakpoints currently added.");
     is(gBreakpointsRemoving.size, 0,
       "No breakpoints currently being removed.");
     is(gEditor.getBreakpoints().length, 0,
       "No breakpoints currently shown in the editor.");
 
-    ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
+    ok(!gBreakpoints._getAdded({ actor: "foo", line: 3 }),
       "_getAdded('foo', 3) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
+    ok(!gBreakpoints._getRemoving({ actor: "bar", line: 3 }),
       "_getRemoving('bar', 3) returns falsey.");
   }
 
   function addBreakpoint1() {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
-    gPanel.addBreakpoint({ url: gSources.selectedValue, line: 18 });
+    gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 18 });
     return finished;
   }
 
   function addBreakpoint2() {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
     setCaretPosition(19);
     gSources._onCmdAddBreakpoint();
     return finished;
@@ -136,55 +136,57 @@ function test() {
   function delBreakpoint4() {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
     setCaretPosition(21);
     gSources._onCmdAddBreakpoint();
     return finished;
   }
 
   function testBreakpoint(aLine, aOpenPopupFlag, aPopupVisible, aConditionalExpression) {
-    let selectedUrl = gSources.selectedValue;
+    let selectedActor = gSources.selectedValue;
     let selectedBreakpoint = gSources._selectedBreakpointItem;
 
-    ok(selectedUrl,
+    ok(selectedActor,
       "There should be a selected item in the sources pane.");
     ok(selectedBreakpoint,
-      "There should be a selected brekapoint in the sources pane.");
+       "There should be a selected brekapoint in the sources pane.");
 
-    is(selectedBreakpoint.attachment.url, selectedUrl,
+    let source = gSources.selectedItem.attachment.source;
+
+    is(selectedBreakpoint.attachment.actor, source.actor,
       "The breakpoint on line " + aLine + " wasn't added on the correct source.");
     is(selectedBreakpoint.attachment.line, aLine,
       "The breakpoint on line " + aLine + " wasn't found.");
     is(!!selectedBreakpoint.attachment.disabled, false,
       "The breakpoint on line " + aLine + " should be enabled.");
     is(!!selectedBreakpoint.attachment.openPopup, aOpenPopupFlag,
       "The breakpoint on line " + aLine + " should have a correct popup state (1).");
     is(gSources._conditionalPopupVisible, aPopupVisible,
       "The breakpoint on line " + aLine + " should have a correct popup state (2).");
 
     return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
-      is(aBreakpointClient.location.url, selectedUrl,
-        "The breakpoint's client url is correct");
+      is(aBreakpointClient.location.actor, selectedActor,
+        "The breakpoint's client actor is correct");
       is(aBreakpointClient.location.line, aLine,
         "The breakpoint's client line is correct");
       is(aBreakpointClient.conditionalExpression, aConditionalExpression,
         "The breakpoint on line " + aLine + " should have a correct conditional expression.");
       is("conditionalExpression" in aBreakpointClient, !!aConditionalExpression,
         "The breakpoint on line " + aLine + " should have a correct conditional state.");
 
       ok(isCaretPos(gPanel, aLine),
         "The editor caret position is not properly set.");
     });
   }
 
   function testNoBreakpoint(aLine) {
-    let selectedUrl = gSources.selectedValue;
+    let selectedActor = gSources.selectedValue;
     let selectedBreakpoint = gSources._selectedBreakpointItem;
 
-    ok(selectedUrl,
+    ok(selectedActor,
       "There should be a selected item in the sources pane for line " + aLine + ".");
     ok(!selectedBreakpoint,
       "There should be no selected brekapoint in the sources pane for line " + aLine + ".");
 
     ok(isCaretPos(gPanel, aLine),
       "The editor caret position is not properly set.");
   }
 
--- a/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints-03.js
@@ -18,17 +18,17 @@ function test() {
     gSources = gDebugger.DebuggerView.Sources;
     gBreakpoints = gDebugger.DebuggerController.Breakpoints;
 
     // This test forces conditional breakpoints to be evaluated on the
     // client-side
     var client = gPanel.target.client;
     client.mainRoot.traits.conditionalBreakpoints = false;
 
-    gLocation = { url: gSources.selectedValue, line: 18 };
+    gLocation = { actor: gSources.selectedValue, line: 18 };
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
       .then(addBreakpoint)
       .then(setConditional)
       .then(() => {
         let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
         toggleBreakpoint();
         return finished;
--- a/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints-04.js
@@ -19,17 +19,17 @@ function test() {
     gSources = gDebugger.DebuggerView.Sources;
     gBreakpoints = gDebugger.DebuggerController.Breakpoints;
 
     // This test forces conditional breakpoints to be evaluated on the
     // client-side
     var client = gPanel.target.client;
     client.mainRoot.traits.conditionalBreakpoints = false;
 
-    gLocation = { url: gSources.selectedValue, line: 18 };
+    gLocation = { actor: gSources.selectedValue, line: 18 };
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
       .then(addBreakpoint)
       .then(setDummyConditional)
       .then(() => {
         let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
         toggleBreakpoint();
         return finished;
--- a/browser/devtools/debugger/test/browser_dbg_controller-evaluate-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_controller-evaluate-01.js
@@ -16,17 +16,17 @@ function test() {
     let sources = win.DebuggerController.SourceScripts;
     let sourcesView = win.DebuggerView.Sources;
     let editorView = win.DebuggerView.editor;
     let events = win.EVENTS;
 
     function checkView(frameDepth, selectedSource, caretLine, editorText) {
       is(win.gThreadClient.state, "paused",
         "Should only be getting stack frames while paused.");
-      is(framesView.itemCount, 4,
+      is(framesView.itemCount, 2,
         "Should have four frames.");
       is(framesView.selectedDepth, frameDepth,
         "The correct frame is selected in the widget.");
       is(sourcesView.selectedIndex, selectedSource,
         "The correct source is selected in the widget.");
       ok(isCaretPos(panel, caretLine),
         "Editor caret location is correct.");
       is(editorView.getText().search(editorText[0]), editorText[1],
@@ -41,42 +41,42 @@ function test() {
     try {
       yield frames.evaluate("foo");
     } catch (error) {
       is(error.message, "No stack frame available.",
         "Evaluating shouldn't work while the debuggee isn't paused.");
     }
 
     callInTab(tab, "firstCall");
-    yield waitForSourceAndCaretAndScopes(panel, "-02.js", 1);
-    checkView(0, 1, 1, [/secondCall/, 118]);
+    yield waitForSourceAndCaretAndScopes(panel, "-02.js", 6);
+    checkView(0, 1, 6, [/secondCall/, 118]);
 
     // Eval in the topmost frame, while paused.
     let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
     let result = yield frames.evaluate("foo");
     ok(!result.throw, "The evaluation hasn't thrown.");
     is(result.return.type, "object", "The evaluation return type is correct.");
     is(result.return.class, "Function", "The evaluation return class is correct.");
 
     yield updatedView;
-    checkView(0, 1, 1, [/secondCall/, 118]);
+    checkView(0, 1, 6, [/secondCall/, 118]);
     ok(true, "Evaluating in the topmost frame works properly.");
 
     // Eval in a different frame, while paused.
     updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
     try {
-      yield frames.evaluate("foo", { depth: 3 }); // oldest frame
+      yield frames.evaluate("foo", { depth: 1 }); // oldest frame
     } catch (result) {
       is(result.return.type, "object", "The evaluation thrown type is correct.");
       is(result.return.class, "Error", "The evaluation thrown class is correct.");
       ok(!result.return, "The evaluation hasn't returned.");
     }
 
     yield updatedView;
-    checkView(0, 1, 1, [/secondCall/, 118]);
+    checkView(0, 1, 6, [/secondCall/, 118]);
     ok(true, "Evaluating in a custom frame works properly.");
 
     // Eval in a non-existent frame, while paused.
     waitForDebuggerEvents(panel, events.FETCHED_SCOPES).then(() => {
       ok(false, "Shouldn't have updated the view when trying to evaluate " +
         "an expression in a non-existent stack frame.");
     });
     try {
--- a/browser/devtools/debugger/test/browser_dbg_controller-evaluate-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_controller-evaluate-02.js
@@ -16,17 +16,17 @@ function test() {
     let sources = win.DebuggerController.SourceScripts;
     let sourcesView = win.DebuggerView.Sources;
     let editorView = win.DebuggerView.editor;
     let events = win.EVENTS;
 
     function checkView(selectedFrame, selectedSource, caretLine, editorText) {
       is(win.gThreadClient.state, "paused",
         "Should only be getting stack frames while paused.");
-      is(framesView.itemCount, 4,
+      is(framesView.itemCount, 2,
         "Should have four frames.");
       is(framesView.selectedDepth, selectedFrame,
         "The correct frame is selected in the widget.");
       is(sourcesView.selectedIndex, selectedSource,
         "The correct source is selected in the widget.");
       ok(isCaretPos(panel, caretLine),
         "Editor caret location is correct.");
       is(editorView.getText().search(editorText[0]), editorText[1],
@@ -34,33 +34,33 @@ function test() {
     }
 
     // Cache the sources text to avoid having to wait for their retrieval.
     yield promise.all(sourcesView.attachments.map(e => sources.getText(e.source)));
     is(sources._cache.size, 2, "There should be two cached sources in the cache.");
 
     // Allow this generator function to yield first.
     callInTab(tab, "firstCall");
-    yield waitForSourceAndCaretAndScopes(panel, "-02.js", 1);
-    checkView(0, 1, 1, [/secondCall/, 118]);
+    yield waitForSourceAndCaretAndScopes(panel, "-02.js", 6);
+    checkView(0, 1, 6, [/secondCall/, 118]);
 
     // Change the selected frame and eval inside it.
     let updatedFrame = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
-    framesView.selectedDepth = 3; // oldest frame
+    framesView.selectedDepth = 1; // oldest frame
     yield updatedFrame;
-    checkView(3, 0, 5, [/firstCall/, 118]);
+    checkView(1, 0, 5, [/firstCall/, 118]);
 
     let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
     try {
       yield frames.evaluate("foo");
     } catch (result) {
       is(result.return.type, "object", "The evaluation thrown type is correct.");
       is(result.return.class, "Error", "The evaluation thrown class is correct.");
       ok(!result.return, "The evaluation hasn't returned.");
     }
 
     yield updatedView;
-    checkView(3, 0, 5, [/firstCall/, 118]);
+    checkView(1, 0, 5, [/firstCall/, 118]);
     ok(true, "Evaluating while in a user-selected frame works properly.");
 
     yield resumeDebuggerThenCloseAndFinish(panel);
   });
 }
--- a/browser/devtools/debugger/test/browser_dbg_editor-contextmenu.js
+++ b/browser/devtools/debugger/test/browser_dbg_editor-contextmenu.js
@@ -23,17 +23,17 @@ function test() {
     callInTab(gTab, "firstCall");
   });
 
   function performTest() {
     is(gDebugger.gThreadClient.state, "paused",
       "Should only be getting stack frames while paused.");
     is(gSources.itemCount, 2,
       "Found the expected number of sources.");
-    is(gEditor.getText().indexOf("debugger"), 172,
+    is(gEditor.getText().indexOf("debugger"), 166,
       "The correct source was loaded initially.");
     is(gSources.selectedValue, gSources.values[1],
       "The correct source is selected.");
 
     is(gEditor.getText().indexOf("\u263a"), 162,
       "Unicode characters are converted correctly.");
 
     ok(gContextMenu,
--- a/browser/devtools/debugger/test/browser_dbg_editor-mode.js
+++ b/browser/devtools/debugger/test/browser_dbg_editor-mode.js
@@ -35,17 +35,17 @@ function test() {
 function testInitialSource() {
   is(gSources.itemCount, 3,
     "Found the expected number of sources.");
 
   is(gEditor.getMode().name, "text",
     "Found the expected editor mode.");
   is(gEditor.getText().search(/firstCall/), -1,
     "The first source is not displayed.");
-  is(gEditor.getText().search(/debugger/), 141,
+  is(gEditor.getText().search(/debugger/), 135,
     "The second source is displayed.");
   is(gEditor.getText().search(/banana/), -1,
     "The third source is not displayed.");
 
   let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
   gSources.selectedItem = e => e.attachment.label == "code_script-switching-01.js";
   return finished;
 }
--- a/browser/devtools/debugger/test/browser_dbg_event-listeners-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_event-listeners-01.js
@@ -72,17 +72,17 @@ function testEventListeners(aThreadClien
       const lDeferred = promise.defer();
       aThreadClient.pauseGrip(listener.function).getDefinitionSite(aResponse => {
         if (aResponse.error) {
           const msg = "Error getting function definition site: " + aResponse.message;
           ok(false, msg);
           lDeferred.reject(msg);
           return;
         }
-        listener.function.url = aResponse.url;
+        listener.function.url = aResponse.source.url;
         lDeferred.resolve(listener);
       });
       return lDeferred.promise;
     })).then(listeners => {
       let types = [];
 
       for (let l of listeners) {
         info("Listener for the "+l.type+" event.");
@@ -92,17 +92,22 @@ function testEventListeners(aThreadClien
         ok(node.selector == "window" ||
           content.document.querySelectorAll(node.selector).length == 1,
           "The node property is a unique CSS selector.");
 
         let func = l.function;
         ok(func, "There is a function property.");
         is(func.type, "object", "The function form is of type 'object'.");
         is(func.class, "Function", "The function form is of class 'Function'.");
-        is(func.url, TAB_URL, "The function url is correct.");
+
+        // The onchange handler is an inline string that doesn't have
+        // a URL because it's basically eval'ed
+        if (l.type !== 'change') {
+          is(func.url, TAB_URL, "The function url is correct.");
+        }
 
         is(l.allowsUntrusted, true,
           "'allowsUntrusted' property has the right value.");
         is(l.inSystemEventGroup, false,
           "'inSystemEventGroup' property has the right value.");
 
         types.push(l.type);
 
--- a/browser/devtools/debugger/test/browser_dbg_event-listeners-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_event-listeners-02.js
@@ -73,17 +73,17 @@ function testEventListeners(aThreadClien
       const lDeferred = promise.defer();
       aThreadClient.pauseGrip(listener.function).getDefinitionSite(aResponse => {
         if (aResponse.error) {
           const msg = "Error getting function definition site: " + aResponse.message;
           ok(false, msg);
           lDeferred.reject(msg);
           return;
         }
-        listener.function.url = aResponse.url;
+        listener.function.url = aResponse.source.url;
         lDeferred.resolve(listener);
       });
       return lDeferred.promise;
     })).then(listeners => {
       is (listeners.length, 3, "Found three event listeners.");
       for (let l of listeners) {
         let node = l.node;
         ok(node, "There is a node property.");
--- a/browser/devtools/debugger/test/browser_dbg_file-reload.js
+++ b/browser/devtools/debugger/test/browser_dbg_file-reload.js
@@ -15,17 +15,17 @@ function test() {
     let gSources = gDebugger.DebuggerView.Sources;
     let gControllerSources = gDebugger.DebuggerController.SourceScripts;
 
     Task.spawn(function() {
       yield waitForSourceShown(aPanel, JS_URL);
 
       is(gSources.itemCount, 1,
         "There should be one source displayed in the view.")
-      is(gSources.selectedValue, JS_URL,
+      is(getSelectedSourceURL(gSources), JS_URL,
         "The correct source is currently selected in the view.");
       ok(gEditor.getText().contains("bacon"),
         "The currently shown source contains bacon. Mmm, delicious!");
 
       let { source } = gSources.selectedItem.attachment;
       let [, firstText] = yield gControllerSources.getText(source);
       let firstNumber = parseFloat(firstText.match(/\d\.\d+/)[0]);
 
@@ -33,17 +33,17 @@ function test() {
         "gControllerSources.getText() returned the expected contents.");
       ok(firstNumber <= 1 && firstNumber >= 0,
         "The generated number seems to be created correctly.");
 
       yield reloadActiveTab(aPanel, gDebugger.EVENTS.SOURCE_SHOWN);
 
       is(gSources.itemCount, 1,
         "There should be one source displayed in the view after reloading.")
-      is(gSources.selectedValue, JS_URL,
+      is(getSelectedSourceURL(gSources), JS_URL,
         "The correct source is currently selected in the view after reloading.");
       ok(gEditor.getText().contains("bacon"),
         "The newly shown source contains bacon. Mmm, delicious!");
 
       ({ source } = gSources.selectedItem.attachment);
       let [, secondText] = yield gControllerSources.getText(source);
       let secondNumber = parseFloat(secondText.match(/\d\.\d+/)[0]);
 
--- a/browser/devtools/debugger/test/browser_dbg_iframes.js
+++ b/browser/devtools/debugger/test/browser_dbg_iframes.js
@@ -38,17 +38,17 @@ function test() {
       "The source editor caret position was incorrect.");
     is(gFrames.itemCount, 0,
       "Should have only no frames.");
 
     is(gSources.itemCount, 1,
       "Found the expected number of entries in the sources widget.");
     is(gEditor.getText().indexOf("debugger"), 348,
       "The correct source was loaded initially.");
-    is(gSources.selectedValue, EXAMPLE_URL + "doc_inline-debugger-statement.html",
+    is(getSelectedSourceURL(gSources), EXAMPLE_URL + "doc_inline-debugger-statement.html",
       "The currently selected source value is incorrect (0).");
     is(gSources.selectedValue, gSources.values[0],
       "The currently selected source value is incorrect (1).");
   }
 
   function checkIframePause() {
     // Spin the event loop before causing the debuggee to pause, to allow
     // this function to return first.
--- a/browser/devtools/debugger/test/browser_dbg_interrupts.js
+++ b/browser/devtools/debugger/test/browser_dbg_interrupts.js
@@ -35,19 +35,19 @@ function test() {
   });
 
   function failOnPause() {
     ok (false, "A pause was sent, but it shouldn't have been");
   }
 
   function addBreakpoints() {
     return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[0], line: 5 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 6 }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.values[1], line: 7 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
+      .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
       .then(() => ensureThreadClientState(gPanel, "resumed"));
   }
 
   function resume() {
     let onceResumed = gTarget.once("thread-resumed");
     gThreadClient.resume();
     return onceResumed;
   }
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-03-new.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-03-new.js
@@ -32,17 +32,17 @@ function testLocationChange() {
       "Should not be paused after a tab navigation.");
 
     is(gFrames.itemCount, 0,
       "Should have no frames.");
 
     is(gSources.itemCount, 1,
       "Found the expected number of entries in the sources widget.");
 
-    is(gSources.selectedValue, EXAMPLE_URL + "doc_inline-debugger-statement.html",
+    is(getSelectedSourceURL(gSources), EXAMPLE_URL + "doc_inline-debugger-statement.html",
       "There should be a selected source value.");
     isnot(gEditor.getText().length, 0,
       "The source editor should have some text displayed.");
     is(gEditor.getText(), gDebugger.L10N.getStr("loadingText"),
       "The source editor text should not be 'Loading...'");
 
     is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-text").length, 0,
       "The sources widget should not display any notice at this point.");
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-04-breakpoint.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-04-breakpoint.js
@@ -30,34 +30,34 @@ function addBreakpoint() {
   waitForSourceAndCaret(gPanel, ".js", 5).then(() => {
     ok(true,
       "Switched to the desired function when adding a breakpoint " +
       "but not passing { noEditorUpdate: true } as an option.");
 
     testResume();
   });
 
-  gPanel.addBreakpoint({ url: SOURCE_URL, line: 5 });
+  gPanel.addBreakpoint({ actor: getSourceActor(gSources, SOURCE_URL), line: 5 });
 }
 
 function testResume() {
   is(gDebugger.gThreadClient.state, "paused",
     "The breakpoint wasn't hit yet (1).");
-  is(gSources.selectedValue, SOURCE_URL,
+  is(getSelectedSourceURL(gSources), SOURCE_URL,
     "The currently shown source is incorrect (1).");
   ok(isCaretPos(gPanel, 5),
     "The source editor caret position is incorrect (1).");
 
   gDebugger.gThreadClient.resume(testClick);
 }
 
 function testClick() {
   isnot(gDebugger.gThreadClient.state, "paused",
     "The breakpoint wasn't hit yet (2).");
-  is(gSources.selectedValue, SOURCE_URL,
+  is(getSelectedSourceURL(gSources), SOURCE_URL,
     "The currently shown source is incorrect (2).");
   ok(isCaretPos(gPanel, 5),
     "The source editor caret position is incorrect (2).");
 
   gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
     is(aPacket.why.type, "breakpoint",
       "Execution has advanced to the breakpoint.");
     isnot(aPacket.why.type, "debuggerStatement",
@@ -68,18 +68,18 @@ function testClick() {
 
   EventUtils.sendMouseEvent({ type: "click" },
     gDebuggee.document.querySelector("button"),
     gDebuggee);
 }
 
 function afterBreakpointHit() {
   is(gDebugger.gThreadClient.state, "paused",
-    "The breakpoint was hit (3).");
-  is(gSources.selectedValue, SOURCE_URL,
+     "The breakpoint was hit (3).");
+  is(getSelectedSourceURL(gSources), SOURCE_URL,
     "The currently shown source is incorrect (3).");
   ok(isCaretPos(gPanel, 5),
     "The source editor caret position is incorrect (3).");
 
   gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
     is(aPacket.why.type, "debuggerStatement",
       "Execution has advanced to the next line.");
     isnot(aPacket.why.type, "breakpoint",
@@ -89,34 +89,34 @@ function afterBreakpointHit() {
   });
 
   gDebugger.gThreadClient.resume();
 }
 
 function afterDebuggerStatementHit() {
   is(gDebugger.gThreadClient.state, "paused",
     "The debugger statement was hit (4).");
-  is(gSources.selectedValue, SOURCE_URL,
+  is(getSelectedSourceURL(gSources), SOURCE_URL,
     "The currently shown source is incorrect (4).");
   ok(isCaretPos(gPanel, 6),
     "The source editor caret position is incorrect (4).");
 
   promise.all([
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE),
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCES_ADDED),
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN),
     reloadActiveTab(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_EDITOR),
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_PANE)
   ]).then(testClickAgain);
 }
 
 function testClickAgain() {
   isnot(gDebugger.gThreadClient.state, "paused",
     "The breakpoint wasn't hit yet (5).");
-  is(gSources.selectedValue, SOURCE_URL,
+  is(getSelectedSourceURL(gSources), SOURCE_URL,
     "The currently shown source is incorrect (5).");
   ok(isCaretPos(gPanel, 1),
     "The source editor caret position is incorrect (5).");
 
   gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
     is(aPacket.why.type, "breakpoint",
       "Execution has advanced to the breakpoint.");
     isnot(aPacket.why.type, "debuggerStatement",
@@ -128,17 +128,17 @@ function testClickAgain() {
   EventUtils.sendMouseEvent({ type: "click" },
     gDebuggee.document.querySelector("button"),
     gDebuggee);
 }
 
 function afterBreakpointHitAgain() {
   is(gDebugger.gThreadClient.state, "paused",
     "The breakpoint was hit (6).");
-  is(gSources.selectedValue, SOURCE_URL,
+  is(getSelectedSourceURL(gSources), SOURCE_URL,
     "The currently shown source is incorrect (6).");
   ok(isCaretPos(gPanel, 5),
     "The source editor caret position is incorrect (6).");
 
   gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
     is(aPacket.why.type, "debuggerStatement",
       "Execution has advanced to the next line.");
     isnot(aPacket.why.type, "breakpoint",
@@ -148,17 +148,17 @@ function afterBreakpointHitAgain() {
   });
 
   gDebugger.gThreadClient.resume();
 }
 
 function afterDebuggerStatementHitAgain() {
   is(gDebugger.gThreadClient.state, "paused",
     "The debugger statement was hit (7).");
-  is(gSources.selectedValue, SOURCE_URL,
+  is(getSelectedSourceURL(gSources), SOURCE_URL,
     "The currently shown source is incorrect (7).");
   ok(isCaretPos(gPanel, 6),
     "The source editor caret position is incorrect (7).");
 
   showSecondSource();
 }
 
 function showSecondSource() {
--- a/browser/devtools/debugger/test/browser_dbg_optimized-out-vars.js
+++ b/browser/devtools/debugger/test/browser_dbg_optimized-out-vars.js
@@ -8,17 +8,18 @@ function test() {
     const TAB_URL = EXAMPLE_URL + "doc_closure-optimized-out.html";
     let gDebugger, sources;
 
     let [tab,, panel] = yield initDebugger(TAB_URL);
     gDebugger = panel.panelWin;
     sources = gDebugger.DebuggerView.Sources;
 
     yield waitForSourceShown(panel, ".html");
-    yield panel.addBreakpoint({ url: sources.values[0], line: 18 });
+    yield panel.addBreakpoint({ actor: sources.values[0],
+                                line: 18 });
     yield ensureThreadClientState(panel, "resumed");
 
     // Spin the event loop before causing the debuggee to pause, to allow
     // this function to return first.
     sendMouseClickToTab(tab, content.document.querySelector("button"));
 
     yield waitForDebuggerEvents(panel, gDebugger.EVENTS.FETCHED_SCOPES);
     let gVars = gDebugger.DebuggerView.Variables;
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-05.js
@@ -22,17 +22,17 @@ function test() {
     Task.spawn(function() {
       yield waitForSourceShown(gPanel, TAB_URL);
 
       // From this point onward, the source editor's text should never change.
       gEditor.once("change", () => {
         ok(false, "The source editor text shouldn't have changed.");
       });
 
-      is(gSources.selectedValue, TAB_URL,
+      is(getSelectedSourceURL(gSources), TAB_URL,
         "The correct source is currently selected.");
       ok(gEditor.getText().contains("myFunction"),
         "The source shouldn't be pretty printed yet.");
 
       clickPrettyPrintButton();
 
       let { source } = gSources.selectedItem.attachment;
       try {
@@ -40,34 +40,37 @@ function test() {
         ok(false, "The promise for a prettified source should be rejected!");
       } catch ([source, error]) {
         is(error, "Can't prettify non-javascript files.",
           "The promise was correctly rejected with a meaningful message.");
       }
 
       let text;
       [source, text] = yield gControllerSources.getText(source);
-      is(gSources.selectedValue, TAB_URL,
+      is(getSelectedSourceURL(gSources), TAB_URL,
         "The correct source is still selected.");
       ok(gEditor.getText().contains("myFunction"),
         "The displayed source hasn't changed.");
       ok(text.contains("myFunction"),
         "The cached source text wasn't altered in any way.");
 
       yield closeDebuggerAndFinish(gPanel);
     });
   });
 }
 
 function clickPrettyPrintButton() {
   gDebugger.document.getElementById("pretty-print").click();
 }
 
 function prepareDebugger(aPanel) {
-  aPanel._view.Sources.preferredSource = TAB_URL;
+  aPanel._view.Sources.preferredSource = getSourceActor(
+    aPanel.panelWin.DebuggerView.Sources,
+    TAB_URL
+  );
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-06.js
@@ -38,17 +38,17 @@ function test() {
     Task.spawn(function() {
       yield waitForSourceShown(gPanel, JS_URL);
 
       // From this point onward, the source editor's text should never change.
       gEditor.once("change", () => {
         ok(false, "The source editor text shouldn't have changed.");
       });
 
-      is(gSources.selectedValue, JS_URL,
+      is(getSelectedSourceURL(gSources), JS_URL,
         "The correct source is currently selected.");
       ok(gEditor.getText().contains("myFunction"),
         "The source shouldn't be pretty printed yet.");
 
       clickPrettyPrintButton();
 
       let { source } = gSources.selectedItem.attachment;
       try {
@@ -56,17 +56,17 @@ function test() {
         ok(false, "The promise for a prettified source should be rejected!");
       } catch ([source, error]) {
         ok(error.contains("prettyPrintError"),
           "The promise was correctly rejected with a meaningful message.");
       }
 
       let text;
       [source, text] = yield gControllerSources.getText(source);
-      is(gSources.selectedValue, JS_URL,
+      is(getSelectedSourceURL(gSources), JS_URL,
         "The correct source is still selected.");
       ok(gEditor.getText().contains("myFunction"),
         "The displayed source hasn't changed.");
       ok(text.contains("myFunction"),
         "The cached source text wasn't altered in any way.");
 
       is(gPrettyPrinted, true,
         "The hijacked pretty print method was executed.");
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-08.js
@@ -14,30 +14,28 @@ function test() {
     gPanel = aPanel;
     gClient = gPanel.panelWin.gClient;
     gThreadClient = gPanel.panelWin.DebuggerController.activeThread;
 
     findSource();
   });
 }
 
-let CODE_URL;
-
 const BP_LOCATION = {
   line: 5,
   column: 11
 };
 
 function findSource() {
   gThreadClient.getSources(({ error, sources }) => {
-    ok(!error);
+    ok(!error, "error should exist");
     sources = sources.filter(s => s.url.contains("code_ugly-3.js"));
-    is(sources.length, 1);
+    is(sources.length, 1, "sources.length should be 1");
     [gSource] = sources;
-    CODE_URL = BP_LOCATION.url = gSource.url;
+    BP_LOCATION.actor = gSource.actor;
 
     prettyPrintSource(sources[0]);
   });
 }
 
 function prettyPrintSource(source) {
   gThreadClient.source(gSource).prettyPrint(2, runCode);
 }
@@ -45,47 +43,51 @@ function prettyPrintSource(source) {
 function runCode({ error }) {
   ok(!error);
   gClient.addOneTimeListener("paused", testDbgStatement);
   callInTab(gTab, "main3");
 }
 
 function testDbgStatement(event, { why, frame }) {
   is(why.type, "debuggerStatement");
-  const { url, line, column } = frame.where;
-  is(url, CODE_URL);
-  is(line, 3);
+  const { source, line, column } = frame.where;
+  is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor");
+  is(line, 3, "the line should be 3");
   setBreakpoint();
 }
 
 function setBreakpoint() {
-  gThreadClient.setBreakpoint(BP_LOCATION, ({ error, actualLocation }) => {
-    ok(!error);
-    ok(!actualLocation);
-    testStepping();
-  });
+  gThreadClient.source(gSource).setBreakpoint(
+    { line: BP_LOCATION.line,
+      column: BP_LOCATION.column },
+    ({ error, actualLocation }) => {
+      ok(!error, "error should not exist");
+      ok(!actualLocation, "actualLocation should not exist");
+      testStepping();
+    }
+  );
 }
 
 function testStepping() {
   gClient.addOneTimeListener("paused", (event, { why, frame }) => {
     is(why.type, "resumeLimit");
-    const { url, line } = frame.where;
-    is(url, CODE_URL);
-    is(line, 4);
+    const { source, line } = frame.where;
+    is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor");
+    is(line, 4, "the line should be 4");
     testHitBreakpoint();
   });
   gThreadClient.stepIn();
 }
 
 function testHitBreakpoint() {
   gClient.addOneTimeListener("paused", (event, { why, frame }) => {
     is(why.type, "breakpoint");
-    const { url, line } = frame.where;
-    is(url, CODE_URL);
-    is(line, BP_LOCATION.line);
+    const { source, line } = frame.where;
+    is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor");
+    is(line, BP_LOCATION.line, "the line should the right line");
 
     resumeDebuggerThenCloseAndFinish(gPanel);
   });
   gThreadClient.resume();
 }
 
 registerCleanupFunction(function() {
   gTab = gPanel = gClient = gThreadClient = gSource = null;
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-09.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-09.js
@@ -48,18 +48,18 @@ function prettyPrint() {
 function runCode({ error }) {
   ok(!error);
   gClient.addOneTimeListener("paused", testDbgStatement);
   callInTab(gTab, "a");
 }
 
 function testDbgStatement(event, { frame, why }) {
   is(why.type, "debuggerStatement");
-  const { url, line } = frame.where;
-  is(url, B_URL);
+  const { source, line } = frame.where;
+  is(source.url, B_URL);
   is(line, 2);
 
   disablePrettyPrint();
 }
 
 function disablePrettyPrint() {
   gThreadClient.source(gSource).disablePrettyPrint(testUgly);
 }
@@ -70,18 +70,18 @@ function testUgly({ error, source }) {
   getFrame();
 }
 
 function getFrame() {
   gThreadClient.getFrames(0, 1, testFrame);
 }
 
 function testFrame({ frames: [frame] }) {
-  const { url, line } = frame.where;
-  is(url, B_URL);
+  const { source, line } = frame.where;
+  is(source.url, B_URL);
   is(line, 1);
 
   resumeDebuggerThenCloseAndFinish(gPanel);
 }
 
 registerCleanupFunction(function() {
   gTab = gPanel = gClient = gThreadClient = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-12.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-12.js
@@ -16,18 +16,18 @@ function test() {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceShown(gPanel, "")
       .then(() => {
-        let shown = ensureSourceIs(gPanel, TAB_URL, true)
-        gSources.selectedValue = TAB_URL;
+        let shown = ensureSourceIs(gPanel, TAB_URL, true);
+        gSources.selectedValue = getSourceActor(gSources, TAB_URL);
         return shown;
       })
       .then(clickPrettyPrintButton)
       .then(testButtonIsntChecked)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
       });
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
@@ -20,29 +20,30 @@ function test(){
     gThreadClient = gDebugger.gThreadClient;
     gSources = gDebugger.DebuggerView.Sources;
 
     Task.spawn(function* () {
       try {
         yield ensureSourceIs(gPanel, "code_script-switching-02.js", true);
 
         yield doInterrupt(gPanel);
-        yield rdpInvoke(gThreadClient, gThreadClient.setBreakpoint, {
-          url: gSources.selectedValue,
+
+        let source = gThreadClient.source(getSourceForm(gSources, SECOND_SOURCE_VALUE));
+        yield rdpInvoke(source, source.setBreakpoint, {
           line: 6
         });
         yield doResume(gPanel);
 
         const bpHit = waitForCaretAndScopes(gPanel, 6);
         callInTab(gTab, "secondCall");
         yield bpHit;
 
         info("Switch to the second source.");
         const sourceShown = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE);
-        gSources.selectedValue = SECOND_SOURCE_VALUE;
+        gSources.selectedValue = getSourceActor(gSources, SECOND_SOURCE_VALUE);
         yield sourceShown;
 
         info("Pretty print the source.");
         const prettyPrinted = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE);
         gDebugger.document.getElementById("pretty-print").click();
         yield prettyPrinted;
 
         yield resumeDebuggerThenCloseAndFinish(gPanel);
--- a/browser/devtools/debugger/test/browser_dbg_reload-preferred-script-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_reload-preferred-script-01.js
@@ -23,26 +23,27 @@ function test() {
     waitForSourceShown(gPanel, PREFERRED_URL).then(finishTest);
   });
 }
 
 function finishTest() {
   info("Currently preferred source: " + gSources.preferredValue);
   info("Currently selected source: " + gSources.selectedValue);
 
-  is(gSources.preferredValue, PREFERRED_URL,
+  is(getSourceURL(gSources, gSources.preferredValue), PREFERRED_URL,
     "The preferred source url wasn't set correctly.");
-  is(gSources.selectedValue, PREFERRED_URL,
+  is(getSourceURL(gSources, gSources.selectedValue), PREFERRED_URL,
     "The selected source isn't the correct one.");
 
   closeDebuggerAndFinish(gPanel);
 }
 
 function prepareDebugger(aPanel) {
-  aPanel._view.Sources.preferredSource = PREFERRED_URL;
+  let sources = aPanel._view.Sources;
+  sources.preferredSource = getSourceActor(sources, PREFERRED_URL);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
--- a/browser/devtools/debugger/test/browser_dbg_reload-preferred-script-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_reload-preferred-script-02.js
@@ -15,27 +15,27 @@ let gSources;
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceShown(gPanel, PREFERRED_URL).then(finishTest);
-    gSources.preferredSource = PREFERRED_URL;
+    gSources.preferredSource = getSourceActor(gSources, PREFERRED_URL);
   });
 }
 
 function finishTest() {
   info("Currently preferred source: " + gSources.preferredValue);
   info("Currently selected source: " + gSources.selectedValue);
 
-  is(gSources.preferredValue, PREFERRED_URL,
+  is(getSourceURL(gSources, gSources.preferredValue), PREFERRED_URL,
     "The preferred source url wasn't set correctly.");
-  is(gSources.selectedValue, PREFERRED_URL,
+  is(getSourceURL(gSources, gSources.selectedValue), PREFERRED_URL,
     "The selected source isn't the correct one.");
 
   closeDebuggerAndFinish(gPanel);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
--- a/browser/devtools/debugger/test/browser_dbg_reload-preferred-script-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_reload-preferred-script-03.js
@@ -16,41 +16,41 @@ let gSources;
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceShown(gPanel, FIRST_URL)
-      .then(() => testSource("", FIRST_URL))
+      .then(() => testSource(undefined, FIRST_URL))
       .then(() => switchToSource(SECOND_URL))
       .then(() => testSource(SECOND_URL))
       .then(() => switchToSource(FIRST_URL))
       .then(() => testSource(FIRST_URL))
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testSource(aPreferredUrl, aSelectedUrl = aPreferredUrl) {
   info("Currently preferred source: " + gSources.preferredValue);
   info("Currently selected source: " + gSources.selectedValue);
 
-  is(gSources.preferredValue, aPreferredUrl,
+  is(getSourceURL(gSources, gSources.preferredValue), aPreferredUrl,
     "The preferred source url wasn't set correctly.");
-  is(gSources.selectedValue, aSelectedUrl,
+  is(getSourceURL(gSources, gSources.selectedValue), aSelectedUrl,
     "The selected source isn't the correct one.");
 }
 
 function switchToSource(aUrl) {
   let finished = waitForSourceShown(gPanel, aUrl);
-  gSources.preferredSource = aUrl;
+  gSources.preferredSource = getSourceActor(gSources, aUrl);
   return finished;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
--- a/browser/devtools/debugger/test/browser_dbg_reload-same-script.js
+++ b/browser/devtools/debugger/test/browser_dbg_reload-same-script.js
@@ -24,17 +24,17 @@ function test() {
     gStep = 0;
 
     waitForSourceShown(gPanel, FIRST_URL).then(performTest);
   });
 
   function performTest() {
     switch (gStep++) {
       case 0:
-        testCurrentSource(FIRST_URL, "");
+        testCurrentSource(FIRST_URL, null);
         reload().then(performTest);
         break;
       case 1:
         testCurrentSource(FIRST_URL);
         reload().then(performTest);
         break;
       case 2:
         testCurrentSource(FIRST_URL);
@@ -56,22 +56,22 @@ function test() {
   }
 
   function reload() {
     return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCES_ADDED);
   }
 
   function switchAndReload(aUrl) {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(reload);
-    gSources.selectedValue = aUrl;
+    gSources.selectedValue = getSourceActor(gSources, aUrl);
     return finished;
   }
 
   function testCurrentSource(aUrl, aExpectedUrl = aUrl) {
     info("Currently preferred source: '" + gSources.preferredValue + "'.");
     info("Currently selected source: '" + gSources.selectedValue + "'.");
 
-    is(gSources.preferredValue, aExpectedUrl,
+    is(getSourceURL(gSources, gSources.preferredValue), aExpectedUrl,
       "The preferred source url wasn't set correctly (" + gStep + ").");
-    is(gSources.selectedValue, aUrl,
+    is(getSourceURL(gSources, gSources.selectedValue), aUrl,
       "The selected source isn't the correct one (" + gStep + ").");
   }
 }
--- a/browser/devtools/debugger/test/browser_dbg_scripts-switching-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-switching-01.js
@@ -46,62 +46,62 @@ function testSourcesDisplay() {
 
   is(gSources.items[0].target.querySelector(".dbg-source-item").getAttribute("tooltiptext"),
     EXAMPLE_URL + "code_script-switching-01.js",
     "The correct tooltip text is displayed for the first source.");
   is(gSources.items[1].target.querySelector(".dbg-source-item").getAttribute("tooltiptext"),
     EXAMPLE_URL + "code_script-switching-02.js",
     "The correct tooltip text is displayed for the second source.");
 
-  ok(gSources.containsValue(EXAMPLE_URL + gLabel1),
+  ok(getSourceActor(gSources, EXAMPLE_URL + gLabel1),
     "First source url is incorrect.");
-  ok(gSources.containsValue(EXAMPLE_URL + gLabel2),
+  ok(getSourceActor(gSources, EXAMPLE_URL + gLabel2),
     "Second source url is incorrect.");
 
   ok(gSources.getItemForAttachment(e => e.label == gLabel1),
     "First source label is incorrect.");
   ok(gSources.getItemForAttachment(e => e.label == gLabel2),
     "Second source label is incorrect.");
 
   ok(gSources.selectedItem,
     "There should be a selected item in the sources pane.");
-  is(gSources.selectedValue, EXAMPLE_URL + gLabel2,
+  is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), -1,
     "The first source is not displayed.");
-  is(gEditor.getText().search(/debugger/), 172,
+  is(gEditor.getText().search(/debugger/), 166,
     "The second source is displayed.");
 
   ok(gDebugger.document.title.endsWith(EXAMPLE_URL + gLabel2),
     "Title with second source is correct.");
 
   ok(isCaretPos(gPanel, 1),
     "Editor caret location is correct.");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
-    is(gEditor.getDebugLocation(), 0,
+    is(gEditor.getDebugLocation(), 5,
       "Editor debugger location is correct.");
-    ok(gEditor.hasLineClass(0, "debug-line"),
+    ok(gEditor.hasLineClass(5, "debug-line"),
       "The debugged line is highlighted appropriately (1).");
 
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
     gSources.selectedIndex = 0;
   });
 
   return deferred.promise;
 }
 
 function testSwitchPaused1() {
   let deferred = promise.defer();
 
   ok(gSources.selectedItem,
     "There should be a selected item in the sources pane.");
-  is(gSources.selectedValue, EXAMPLE_URL + gLabel1,
+  is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), 118,
     "The first source is displayed.");
   is(gEditor.getText().search(/debugger/), -1,
     "The second source is not displayed.");
 
   // The editor's debug location takes a tick to update.
@@ -120,67 +120,64 @@ function testSwitchPaused1() {
   return deferred.promise;
 }
 
 function testSwitchPaused2() {
   let deferred = promise.defer();
 
   ok(gSources.selectedItem,
     "There should be a selected item in the sources pane.");
-  is(gSources.selectedValue, EXAMPLE_URL + gLabel2,
+  is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), -1,
     "The first source is not displayed.");
-  is(gEditor.getText().search(/debugger/), 172,
+  is(gEditor.getText().search(/debugger/), 166,
     "The second source is displayed.");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
-    ok(isCaretPos(gPanel, 1),
+    ok(isCaretPos(gPanel, 6),
       "Editor caret location is correct.");
-    is(gEditor.getDebugLocation(), 0,
+    is(gEditor.getDebugLocation(), 5,
       "Editor debugger location is correct.");
-    ok(gEditor.hasLineClass(0, "debug-line"),
+    ok(gEditor.hasLineClass(5, "debug-line"),
       "The debugged line is highlighted appropriately (2).");
 
-    // Step out three times.
+    // Step out twice.
     waitForThreadEvents(gPanel, "paused").then(() => {
-      waitForThreadEvents(gPanel, "paused").then(() => {
-        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
-        gDebugger.gThreadClient.stepOut();
-      });
+      waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
       gDebugger.gThreadClient.stepOut();
-    });
+    })
     gDebugger.gThreadClient.stepOut();
   });
 
   return deferred.promise;
 }
 
 function testSwitchRunning() {
   let deferred = promise.defer();
 
   ok(gSources.selectedItem,
     "There should be a selected item in the sources pane.");
-  is(gSources.selectedValue, EXAMPLE_URL + gLabel1,
+  is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), 118,
     "The first source is displayed.");
   is(gEditor.getText().search(/debugger/), -1,
     "The second source is not displayed.");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
-    ok(isCaretPos(gPanel, 1),
+    ok(isCaretPos(gPanel, 5),
       "Editor caret location is correct.");
-    is(gEditor.getDebugLocation(), 0,
+    is(gEditor.getDebugLocation(), 4,
       "Editor debugger location is correct.");
-    ok(gEditor.hasLineClass(0, "debug-line"),
+    ok(gEditor.hasLineClass(4, "debug-line"),
       "The debugged line is highlighted appropriately (3).");
 
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
--- a/browser/devtools/debugger/test/browser_dbg_scripts-switching-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-switching-02.js
@@ -37,59 +37,59 @@ let gLabel2 = "code_script-switching-02.
 let gParams = "?foo=bar,baz|lol";
 
 function testSourcesDisplay() {
   let deferred = promise.defer();
 
   is(gSources.itemCount, 2,
     "Found the expected number of sources.");
 
-  ok(gSources.containsValue(EXAMPLE_URL + gLabel1),
+  ok(getSourceActor(gSources, EXAMPLE_URL + gLabel1),
     "First source url is incorrect.");
-  ok(gSources.containsValue(EXAMPLE_URL + gLabel2 + gParams),
+  ok(getSourceActor(gSources, EXAMPLE_URL + gLabel2 + gParams),
     "Second source url is incorrect.");
 
   ok(gSources.getItemForAttachment(e => e.label == gLabel1),
     "First source label is incorrect.");
   ok(gSources.getItemForAttachment(e => e.label == gLabel2),
     "Second source label is incorrect.");
 
   ok(gSources.selectedItem,
     "There should be a selected item in the sources pane.");
-  is(gSources.selectedValue, EXAMPLE_URL + gLabel2 + gParams,
+  is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2 + gParams,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), -1,
     "The first source is not displayed.");
-  is(gEditor.getText().search(/debugger/), 172,
+  is(gEditor.getText().search(/debugger/), 166,
     "The second source is displayed.");
 
   ok(isCaretPos(gPanel, 1),
     "Editor caret location is correct.");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
-    is(gEditor.getDebugLocation(), 0,
+    is(gEditor.getDebugLocation(), 5,
       "Editor debugger location is correct.");
-    ok(gEditor.hasLineClass(0, "debug-line"),
+    ok(gEditor.hasLineClass(5, "debug-line"),
       "The debugged line is highlighted appropriately.");
 
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
     gSources.selectedItem = e => e.attachment.label == gLabel1;
   });
 
   return deferred.promise;
 }
 
 function testSwitchPaused1() {
   let deferred = promise.defer();
 
   ok(gSources.selectedItem,
     "There should be a selected item in the sources pane.");
-  is(gSources.selectedValue, EXAMPLE_URL + gLabel1,
+  is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), 118,
     "The first source is displayed.");
   is(gEditor.getText().search(/debugger/), -1,
     "The second source is not displayed.");
 
   // The editor's debug location takes a tick to update.
@@ -109,67 +109,64 @@ function testSwitchPaused1() {
   return deferred.promise;
 }
 
 function testSwitchPaused2() {
   let deferred = promise.defer();
 
   ok(gSources.selectedItem,
     "There should be a selected item in the sources pane.");
-  is(gSources.selectedValue, EXAMPLE_URL + gLabel2 + gParams,
+  is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2 + gParams,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), -1,
     "The first source is not displayed.");
-  is(gEditor.getText().search(/debugger/), 172,
+  is(gEditor.getText().search(/debugger/), 166,
     "The second source is displayed.");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
-    ok(isCaretPos(gPanel, 1),
+    ok(isCaretPos(gPanel, 6),
       "Editor caret location is correct.");
-    is(gEditor.getDebugLocation(), 0,
+    is(gEditor.getDebugLocation(), 5,
       "Editor debugger location is correct.");
-    ok(gEditor.hasLineClass(0, "debug-line"),
+    ok(gEditor.hasLineClass(5, "debug-line"),
       "The debugged line is highlighted appropriately.");
 
     // Step out three times.
     waitForThreadEvents(gPanel, "paused").then(() => {
-      waitForThreadEvents(gPanel, "paused").then(() => {
-        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
-        gDebugger.gThreadClient.stepOut();
-      });
+      waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
       gDebugger.gThreadClient.stepOut();
     });
     gDebugger.gThreadClient.stepOut();
   });
 
   return deferred.promise;
 }
 
 function testSwitchRunning() {
   let deferred = promise.defer();
 
   ok(gSources.selectedItem,
     "There should be a selected item in the sources pane.");
-  is(gSources.selectedValue, EXAMPLE_URL + gLabel1,
+  is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), 118,
     "The first source is displayed.");
   is(gEditor.getText().search(/debugger/), -1,
     "The second source is not displayed.");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
-    ok(isCaretPos(gPanel, 1),
+    ok(isCaretPos(gPanel, 5),
       "Editor caret location is correct.");
-    is(gEditor.getDebugLocation(), 0,
+    is(gEditor.getDebugLocation(), 4,
       "Editor debugger location is correct.");
-    ok(gEditor.hasLineClass(0, "debug-line"),
+    ok(gEditor.hasLineClass(4, "debug-line"),
       "The debugged line is highlighted appropriately.");
 
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
--- a/browser/devtools/debugger/test/browser_dbg_search-basic-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-basic-02.js
@@ -100,17 +100,17 @@ function combineWithTokenColonSearch() {
     ensureSourceIs(gPanel, "-01.js"),
     ensureCaretAt(gPanel, 2, 11)
   ]));
 }
 
 function verifySourceAndCaret(aUrl, aLine, aColumn, aSelection) {
   ok(gSources.selectedItem.attachment.label.contains(aUrl),
     "The selected item's label appears to be correct.");
-  ok(gSources.selectedItem.value.contains(aUrl),
+  ok(gSources.selectedItem.attachment.source.url.contains(aUrl),
     "The selected item's value appears to be correct.");
   ok(isCaretPos(gPanel, aLine, aColumn),
     "The current caret position appears to be correct.");
   ok(isEditorSel(gPanel, aSelection),
     "The current editor selection appears to be correct.");
 }
 
 registerCleanupFunction(function() {
--- a/browser/devtools/debugger/test/browser_dbg_search-basic-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-basic-03.js
@@ -98,17 +98,17 @@ function escapeAndClear() {
   EventUtils.sendKey("ESCAPE", gDebugger);
   is(gSearchBox.getAttribute("value"), "",
     "The searchbox has properly emptied after pressing escape.");
 }
 
 function verifySourceAndCaret(aUrl, aLine, aColumn) {
   ok(gSources.selectedItem.attachment.label.contains(aUrl),
     "The selected item's label appears to be correct.");
-  ok(gSources.selectedItem.value.contains(aUrl),
+  ok(gSources.selectedItem.attachment.source.url.contains(aUrl),
     "The selected item's value appears to be correct.");
   ok(isCaretPos(gPanel, aLine, aColumn),
     "The current caret position appears to be correct.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
--- a/browser/devtools/debugger/test/browser_dbg_search-basic-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-basic-04.js
@@ -29,17 +29,17 @@ function test() {
       });
   });
 }
 
 function testLineSearch() {
   setText(gSearchBox, ":42");
   ok(isCaretPos(gPanel, 7),
     "The editor caret position appears to be correct (1.1).");
-  ok(isEditorSel(gPanel, [160, 160]),
+  ok(isEditorSel(gPanel, [151, 151]),
     "The editor selection appears to be correct (1.1).");
   is(gEditor.getSelection(), "",
     "The editor selected text appears to be correct (1.1).");
 
   backspaceText(gSearchBox, 1);
   ok(isCaretPos(gPanel, 4),
     "The editor caret position appears to be correct (1.2).");
   ok(isEditorSel(gPanel, [110, 110]),
@@ -71,53 +71,53 @@ function testLineSearch() {
     "The editor selection appears to be correct (1.5).");
   is(gEditor.getSelection(), "",
     "The editor selected text appears to be correct (1.5).");
   is(gSearchBox.value, "",
     "The searchbox should have been cleared.");
 }
 
 function testTokenSearch() {
-  setText(gSearchBox, "#;\"");
-  ok(isCaretPos(gPanel, 5, 23),
+  setText(gSearchBox, "#();");
+  ok(isCaretPos(gPanel, 5, 16),
     "The editor caret position appears to be correct (2.1).");
-  ok(isEditorSel(gPanel, [153, 155]),
+  ok(isEditorSel(gPanel, [145, 148]),
     "The editor selection appears to be correct (2.1).");
-  is(gEditor.getSelection(), ";\"",
+  is(gEditor.getSelection(), "();",
     "The editor selected text appears to be correct (2.1).");
 
   backspaceText(gSearchBox, 1);
-  ok(isCaretPos(gPanel, 5, 22),
+  ok(isCaretPos(gPanel, 4, 21),
     "The editor caret position appears to be correct (2.2).");
-  ok(isEditorSel(gPanel, [153, 154]),
+  ok(isEditorSel(gPanel, [128, 130]),
     "The editor selection appears to be correct (2.2).");
-  is(gEditor.getSelection(), ";",
+  is(gEditor.getSelection(), "()",
     "The editor selected text appears to be correct (2.2).");
 
-  backspaceText(gSearchBox, 1);
-  ok(isCaretPos(gPanel, 5, 22),
+  backspaceText(gSearchBox, 2);
+  ok(isCaretPos(gPanel, 4, 20),
     "The editor caret position appears to be correct (2.3).");
-  ok(isEditorSel(gPanel, [154, 154]),
+  ok(isEditorSel(gPanel, [129, 129]),
     "The editor selection appears to be correct (2.3).");
   is(gEditor.getSelection(), "",
     "The editor selected text appears to be correct (2.3).");
 
   setText(gSearchBox, "#;");
-  ok(isCaretPos(gPanel, 5, 22),
+  ok(isCaretPos(gPanel, 5, 16),
     "The editor caret position appears to be correct (2.4).");
-  ok(isEditorSel(gPanel, [153, 154]),
+  ok(isEditorSel(gPanel, [147, 148]),
     "The editor selection appears to be correct (2.4).");
   is(gEditor.getSelection(), ";",
     "The editor selected text appears to be correct (2.4).");
 
   gSearchBox.select();
   backspaceText(gSearchBox, 1);
-  ok(isCaretPos(gPanel, 5, 22),
+  ok(isCaretPos(gPanel, 5, 16),
     "The editor caret position appears to be correct (2.5).");
-  ok(isEditorSel(gPanel, [154, 154]),
+  ok(isEditorSel(gPanel, [148, 148]),
     "The editor selection appears to be correct (2.5).");
   is(gEditor.getSelection(), "",
     "The editor selected text appears to be correct (2.5).");
   is(gSearchBox.value, "",
     "The searchbox should have been cleared.");
 }
 
 registerCleanupFunction(function() {
--- a/browser/devtools/debugger/test/browser_dbg_search-global-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-01.js
@@ -43,22 +43,22 @@ function firstSearch() {
     "The global search pane shouldn't be visible yet.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible yet.");
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
     // to avoid blocking UI, thus giving the impression of faster searching.
     executeSoon(() => {
-      info("Current source url:\n" + gSources.selectedValue);
+      info("Current source url:\n" + getSelectedSourceURL(gSources));
       info("Debugger editor text:\n" + gEditor.getText());
 
-      ok(isCaretPos(gPanel, 1),
+      ok(isCaretPos(gPanel, 6),
         "The editor shouldn't have jumped to a matching line yet.");
-      ok(gSources.selectedValue.contains("-02.js"),
+      ok(getSelectedSourceURL(gSources).contains("-02.js"),
         "The current source shouldn't have changed after a global search.");
       is(gSources.visibleItems.length, 2,
         "Not all the sources are shown after the global search.");
 
       let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
       is(sourceResults.length, 2,
         "There should be matches found in two sources.");
 
@@ -129,19 +129,19 @@ function firstSearch() {
 
       is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 1,
         "The second result for the second source doesn't have the correct number of matches in a line.");
       is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "de",
         "The second result for the second source doesn't have the correct match in a line.");
 
       is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 2,
         "The second result for the second source doesn't have the correct number of non-matches in a line.");
-      is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), '  eval("',
+      is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), '  ',
         "The second result for the second source doesn't have the correct non-matches in a line.");
-      is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), 'bugger;");',
+      is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), 'bugger;',
         "The second result for the second source doesn't have the correct non-matches in a line.");
 
       deferred.resolve();
     });
   });
 
   setText(gSearchBox, "!de");
 
@@ -157,22 +157,22 @@ function secondSearch() {
     "The global search pane should be visible from the previous search.");
   is(gSearchView._splitter.hidden, false,
     "The global search pane splitter should be visible from the previous search.");
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
     // to avoid blocking UI, thus giving the impression of faster searching.
     executeSoon(() => {
-      info("Current source url:\n" + gSources.selectedValue);
+      info("Current source url:\n" + getSelectedSourceURL(gSources));
       info("Debugger editor text:\n" + gEditor.getText());
 
-      ok(isCaretPos(gPanel, 1),
+      ok(isCaretPos(gPanel, 6),
         "The editor shouldn't have jumped to a matching line yet.");
-      ok(gSources.selectedValue.contains("-02.js"),
+      ok(getSelectedSourceURL(gSources).contains("-02.js"),
         "The current source shouldn't have changed after a global search.");
       is(gSources.visibleItems.length, 2,
         "Not all the sources are shown after the global search.");
 
       let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
       is(sourceResults.length, 2,
         "There should be matches found in two sources.");
 
--- a/browser/devtools/debugger/test/browser_dbg_search-global-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-02.js
@@ -46,158 +46,159 @@ function firstSearch() {
     "The global search pane shouldn't be visible yet.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible yet.");
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
     // to avoid blocking UI, thus giving the impression of faster searching.
     executeSoon(() => {
-      info("Current source url:\n" + gSources.selectedValue);
+      info("Current source url:\n" + getSelectedSourceURL(gSources));
       info("Debugger editor text:\n" + gEditor.getText());
 
-      ok(isCaretPos(gPanel, 1),
+      ok(isCaretPos(gPanel, 6),
         "The editor shouldn't have jumped to a matching line yet.");
-      ok(gSources.selectedValue.contains("-02.js"),
+      ok(getSelectedSourceURL(gSources).contains("-02.js"),
         "The current source shouldn't have changed after a global search.");
       is(gSources.visibleItems.length, 2,
         "Not all the sources are shown after the global search.");
 
       deferred.resolve();
     });
   });
 
-  setText(gSearchBox, "!eval");
+  setText(gSearchBox, "!function");
 
   return deferred.promise;
 }
 
 function doFirstJump() {
   let deferred = promise.defer();
 
-  waitForSourceAndCaret(gPanel, "-01.js", 1).then(() => {
-    info("Current source url:\n" + gSources.selectedValue);
+  waitForSourceAndCaret(gPanel, "-01.js", 4).then(() => {
+    info("Current source url:\n" + getSelectedSourceURL(gSources));
     info("Debugger editor text:\n" + gEditor.getText());
 
-    ok(gSources.selectedValue.contains("-01.js"),
+    ok(getSelectedSourceURL(gSources).contains("-01.js"),
       "The currently shown source is incorrect (1).");
     is(gSources.visibleItems.length, 2,
       "Not all the sources are shown after the global search (1).");
 
     // The editor's selected text takes a tick to update.
     executeSoon(() => {
-      ok(isCaretPos(gPanel, 5, 7),
+      ok(isCaretPos(gPanel, 4, 9),
         "The editor didn't jump to the correct line (1).");
-      is(gEditor.getSelection(), "eval",
+      is(gEditor.getSelection(), "function",
         "The editor didn't select the correct text (1).");
 
       deferred.resolve();
     });
   });
 
   EventUtils.sendKey("DOWN", gDebugger);
 
   return deferred.promise;
 }
 
 function doSecondJump() {
   let deferred = promise.defer();
 
-  waitForSourceAndCaret(gPanel, "-02.js", 1).then(() => {
-    info("Current source url:\n" + gSources.selectedValue);
+  waitForSourceAndCaret(gPanel, "-02.js", 4).then(() => {
+    info("Current source url:\n" + getSelectedSourceURL(gSources));
     info("Debugger editor text:\n" + gEditor.getText());
 
-    ok(gSources.selectedValue.contains("-02.js"),
+    ok(getSelectedSourceURL(gSources).contains("-02.js"),
       "The currently shown source is incorrect (2).");
     is(gSources.visibleItems.length, 2,
       "Not all the sources are shown after the global search (2).");
 
     // The editor's selected text takes a tick to update.
     executeSoon(() => {
-      ok(isCaretPos(gPanel, 6, 7),
+      ok(isCaretPos(gPanel, 4, 9),
         "The editor didn't jump to the correct line (2).");
-      is(gEditor.getSelection(), "eval",
+      is(gEditor.getSelection(), "function",
         "The editor didn't select the correct text (2).");
 
       deferred.resolve();
     });
   });
 
   EventUtils.sendKey("DOWN", gDebugger);
 
   return deferred.promise;
 }
 
 function doWrapAroundJump() {
   let deferred = promise.defer();
 
-  waitForSourceAndCaret(gPanel, "-01.js", 1).then(() => {
-    info("Current source url:\n" + gSources.selectedValue);
+  waitForSourceAndCaret(gPanel, "-01.js", 4).then(() => {
+    info("Current source url:\n" + getSelectedSourceURL(gSources));
     info("Debugger editor text:\n" + gEditor.getText());
 
-    ok(gSources.selectedValue.contains("-01.js"),
+    ok(getSelectedSourceURL(gSources).contains("-01.js"),
       "The currently shown source is incorrect (3).");
     is(gSources.visibleItems.length, 2,
       "Not all the sources are shown after the global search (3).");
 
     // The editor's selected text takes a tick to update.
     executeSoon(() => {
-      ok(isCaretPos(gPanel, 5, 7),
+      ok(isCaretPos(gPanel, 4, 9),
         "The editor didn't jump to the correct line (3).");
-      is(gEditor.getSelection(), "eval",
+      is(gEditor.getSelection(), "function",
         "The editor didn't select the correct text (3).");
 
       deferred.resolve();
     });
   });
 
   EventUtils.sendKey("DOWN", gDebugger);
+  EventUtils.sendKey("DOWN", gDebugger);
 
   return deferred.promise;
 }
 
 function doBackwardsWrapAroundJump() {
   let deferred = promise.defer();
 
-  waitForSourceAndCaret(gPanel, "-02.js", 1).then(() => {
-    info("Current source url:\n" + gSources.selectedValue);
+  waitForSourceAndCaret(gPanel, "-02.js", 7).then(() => {
+    info("Current source url:\n" + getSelectedSourceURL(gSources));
     info("Debugger editor text:\n" + gEditor.getText());
 
-    ok(gSources.selectedValue.contains("-02.js"),
+    ok(getSelectedSourceURL(gSources).contains("-02.js"),
       "The currently shown source is incorrect (4).");
     is(gSources.visibleItems.length, 2,
       "Not all the sources are shown after the global search (4).");
 
     // The editor's selected text takes a tick to update.
     executeSoon(() => {
-      ok(isCaretPos(gPanel, 6, 7),
+      ok(isCaretPos(gPanel, 7, 11),
         "The editor didn't jump to the correct line (4).");
-      is(gEditor.getSelection(), "eval",
+      is(gEditor.getSelection(), "function",
         "The editor didn't select the correct text (4).");
 
       deferred.resolve();
     });
   });
 
   EventUtils.sendKey("UP", gDebugger);
 
   return deferred.promise;
 }
 
 function testSearchTokenEmpty() {
   backspaceText(gSearchBox, 4);
 
-  info("Current source url:\n" + gSources.selectedValue);
+  info("Current source url:\n" + getSelectedSourceURL(gSources));
   info("Debugger editor text:\n" + gEditor.getText());
 
-  ok(gSources.selectedValue.contains("-02.js"),
+  ok(getSelectedSourceURL(gSources).contains("-02.js"),
     "The currently shown source is incorrect (4).");
   is(gSources.visibleItems.length, 2,
     "Not all the sources are shown after the global search (4).");
-  ok(isCaretPos(gPanel, 6, 7),
+  ok(isCaretPos(gPanel, 7, 11),
     "The editor didn't remain at the correct line (4).");
   is(gEditor.getSelection(), "",
     "The editor shouldn't keep the previous text selected (4).");
 
   is(gSearchView.itemCount, 0,
     "The global search pane shouldn't have any child nodes after clearing.");
   is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible after clearing.");
--- a/browser/devtools/debugger/test/browser_dbg_search-global-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-03.js
@@ -42,47 +42,47 @@ function firstSearch() {
     "The global search pane shouldn't be visible yet.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible yet.");
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
     // to avoid blocking UI, thus giving the impression of faster searching.
     executeSoon(() => {
-      info("Current source url:\n" + gSources.selectedValue);
+      info("Current source url:\n" + getSelectedSourceURL(gSources));
       info("Debugger editor text:\n" + gEditor.getText());
 
-      ok(isCaretPos(gPanel, 1),
+      ok(isCaretPos(gPanel, 6),
         "The editor shouldn't have jumped to a matching line yet.");
-      ok(gSources.selectedValue.contains("-02.js"),
+      ok(getSelectedSourceURL(gSources).contains("-02.js"),
         "The current source shouldn't have changed after a global search.");
       is(gSources.visibleItems.length, 2,
         "Not all the sources are shown after the global search.");
 
       deferred.resolve();
     });
   });
 
-  setText(gSearchBox, "!eval");
+  setText(gSearchBox, "!function");
 
   return deferred.promise;
 }
 
 function performTest() {
   let deferred = promise.defer();
 
   is(gSearchView.itemCount, 2,
     "The global search pane should have some entries from the previous search.");
   is(gSearchView.widget._parent.hidden, false,
     "The global search pane should be visible from the previous search.");
   is(gSearchView._splitter.hidden, false,
     "The global search pane splitter should be visible from the previous search.");
 
   reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => {
-    info("Current source url:\n" + gSources.selectedValue);
+    info("Current source url:\n" + getSelectedSourceURL(gSources));
     info("Debugger editor text:\n" + gEditor.getText());
 
     is(gSearchView.itemCount, 0,
       "The global search pane shouldn't have any entries after a page navigation.");
     is(gSearchView.widget._parent.hidden, true,
       "The global search pane shouldn't be visible after a page navigation.");
     is(gSearchView._splitter.hidden, true,
       "The global search pane splitter shouldn't be visible after a page navigation.");
--- a/browser/devtools/debugger/test/browser_dbg_search-global-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-04.js
@@ -35,45 +35,45 @@ function test() {
 
 function firstSearch() {
   let deferred = promise.defer();
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
     // to avoid blocking UI, thus giving the impression of faster searching.
     executeSoon(() => {
-      info("Current source url:\n" + gSources.selectedValue);
+      info("Current source url:\n" + getSelectedSourceURL(gSources));
       info("Debugger editor text:\n" + gEditor.getText());
 
-      ok(isCaretPos(gPanel, 1),
+      ok(isCaretPos(gPanel, 6),
         "The editor shouldn't have jumped to a matching line yet.");
-      ok(gSources.selectedValue.contains("-02.js"),
+      ok(getSelectedSourceURL(gSources).contains("-02.js"),
         "The current source shouldn't have changed after a global search.");
       is(gSources.visibleItems.length, 2,
         "Not all the sources are shown after the global search.");
 
       deferred.resolve();
     });
   });
 
-  setText(gSearchBox, "!eval");
+  setText(gSearchBox, "!function");
 
   return deferred.promise;
 }
 
 function secondSearch() {
   let deferred = promise.defer();
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_NOT_FOUND, () => {
-    info("Current source url:\n" + gSources.selectedValue);
+    info("Current source url:\n" + getSelectedSourceURL(gSources));
     info("Debugger editor text:\n" + gEditor.getText());
 
-    ok(isCaretPos(gPanel, 1),
+    ok(isCaretPos(gPanel, 6),
       "The editor shouldn't have jumped to a matching line yet.");
-    ok(gSources.selectedValue.contains("-02.js"),
+    ok(getSelectedSourceURL(gSources).contains("-02.js"),
       "The current source shouldn't have changed after a global search.");
     is(gSources.visibleItems.length, 2,
       "Not all the sources are shown after the global search.");
 
     deferred.resolve();
   });
 
   typeText(gSearchBox, "/");
--- a/browser/devtools/debugger/test/browser_dbg_search-global-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-05.js
@@ -38,22 +38,22 @@ function test() {
 
 function doSearch() {
   let deferred = promise.defer();
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
     // to avoid blocking UI, thus giving the impression of faster searching.
     executeSoon(() => {
-      info("Current source url:\n" + gSources.selectedValue);
+      info("Current source url:\n" + getSelectedSourceURL(gSources));
       info("Debugger editor text:\n" + gEditor.getText());
 
-      ok(isCaretPos(gPanel, 1),
+      ok(isCaretPos(gPanel, 6),
         "The editor shouldn't have jumped to a matching line yet.");
-      ok(gSources.selectedValue.contains("-02.js"),
+      ok(getSelectedSourceURL(gSources).contains("-02.js"),
         "The current source shouldn't have changed after a global search.");
       is(gSources.visibleItems.length, 2,
         "Not all the sources are shown after the global search.");
 
       deferred.resolve();
     });
   });
 
@@ -89,24 +89,24 @@ function testExpandCollapse() {
 function testClickLineToJump() {
   let deferred = promise.defer();
 
   let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
   let firstHeader = sourceResults[0].querySelector(".dbg-results-header");
   let firstLine = sourceResults[0].querySelector(".dbg-results-line-contents");
 
   waitForSourceAndCaret(gPanel, "-01.js", 1, 1).then(() => {
-    info("Current source url:\n" + gSources.selectedValue);
+    info("Current source url:\n" + getSelectedSourceURL(gSources));
     info("Debugger editor text:\n" + gEditor.getText());
 
     ok(isCaretPos(gPanel, 1, 1),
       "The editor didn't jump to the correct line (1).");
     is(gEditor.getSelection(), "",
       "The editor didn't select the correct text (1).");
-    ok(gSources.selectedValue.contains("-01.js"),
+    ok(getSelectedSourceURL(gSources).contains("-01.js"),
       "The currently shown source is incorrect (1).");
     is(gSources.visibleItems.length, 2,
       "Not all the sources are shown after the global search (1).");
 
     deferred.resolve();
   });
 
   EventUtils.sendMouseEvent({ type: "click" }, firstLine);
@@ -118,24 +118,24 @@ function testClickMatchToJump() {
   let deferred = promise.defer();
 
   let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
   let secondHeader = sourceResults[1].querySelector(".dbg-results-header");
   let secondMatches = sourceResults[1].querySelectorAll(".dbg-results-line-contents-string[match=true]");
   let lastMatch = Array.slice(secondMatches).pop();
 
   waitForSourceAndCaret(gPanel, "-02.js", 1, 1).then(() => {
-    info("Current source url:\n" + gSources.selectedValue);
+    info("Current source url:\n" + getSelectedSourceURL(gSources));
     info("Debugger editor text:\n" + gEditor.getText());
 
     ok(isCaretPos(gPanel, 1, 1),
       "The editor didn't jump to the correct line (2).");
     is(gEditor.getSelection(), "",
       "The editor didn't select the correct text (2).");
-    ok(gSources.selectedValue.contains("-02.js"),
+    ok(getSelectedSourceURL(gSources).contains("-02.js"),
       "The currently shown source is incorrect (2).");
     is(gSources.visibleItems.length, 2,
       "Not all the sources are shown after the global search (2).");
 
     deferred.resolve();
   });
 
   EventUtils.sendMouseEvent({ type: "click" }, lastMatch);
--- a/browser/devtools/debugger/test/browser_dbg_search-global-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-06.js
@@ -44,22 +44,22 @@ function doSearch() {
     "The global search pane shouldn't be visible yet.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible yet.");
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
     // to avoid blocking UI, thus giving the impression of faster searching.
     executeSoon(() => {
-      info("Current source url:\n" + gSources.selectedValue);
+      info("Current source url:\n" + getSelectedSourceURL(gSources));
       info("Debugger editor text:\n" + gEditor.getText());
 
-      ok(isCaretPos(gPanel, 1),
+      ok(isCaretPos(gPanel, 6),
         "The editor shouldn't have jumped to a matching line yet.");
-      ok(gSources.selectedValue.contains("-02.js"),
+      ok(getSelectedSourceURL(gSources).contains("-02.js"),
         "The current source shouldn't have changed after a global search.");
       is(gSources.visibleItems.length, 2,
         "Not all the sources are shown after the global search.");
 
       deferred.resolve();
     });
   });
 
@@ -73,17 +73,17 @@ function testFocusLost() {
     "The global search pane should have some entries from the previous search.");
   is(gSearchView.widget._parent.hidden, false,
     "The global search pane should be visible from the previous search.");
   is(gSearchView._splitter.hidden, false,
     "The global search pane splitter should be visible from the previous search.");
 
   gDebugger.DebuggerView.editor.focus();
 
-  info("Current source url:\n" + gSources.selectedValue);
+  info("Current source url:\n" + getSelectedSourceURL(gSources));
   info("Debugger editor text:\n" + gEditor.getText());
 
   is(gSearchView.itemCount, 0,
     "The global search pane shouldn't have any child nodes after clearing.");
   is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible after clearing.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible after clearing.");
--- a/browser/devtools/debugger/test/browser_dbg_search-popup-jank.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-popup-jank.js
@@ -41,16 +41,17 @@ function test() {
       .then(() => pressKeyToHide("RETURN"))
       .then(() => ensureSourceIs(aPanel, "doc_editor-mode"))
       .then(() => ensureCaretAt(aPanel, 1))
 
       .then(superAccurateFileSearch)
       .then(() => ensureSourceIs(aPanel, "doc_editor-mode"))
       .then(() => ensureCaretAt(aPanel, 1))
       .then(() => typeText(gSearchBox, ":"))
+      .then(() => waitForSourceShown(gPanel, "code_test-editor-mode"))
       .then(() => ensureSourceIs(aPanel, "code_test-editor-mode", true))
       .then(() => ensureCaretAt(aPanel, 1))
       .then(() => typeText(gSearchBox, "5"))
       .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
       .then(() => ensureCaretAt(aPanel, 5))
       .then(() => pressKey("DOWN"))
       .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
       .then(() => ensureCaretAt(aPanel, 6))
--- a/browser/devtools/debugger/test/browser_dbg_search-sources-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-sources-01.js
@@ -103,21 +103,21 @@ function thirdSearch() {
     once(gDebugger, "popupshown"),
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
     waitForSourceShown(gPanel, "-02.js")
   ])
   .then(() => {
     let finished = promise.all([
       once(gDebugger, "popupshown"),
       waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
-      waitForCaretUpdated(gPanel, 6, 12)
+      waitForCaretUpdated(gPanel, 6, 6)
     ])
     .then(() => promise.all([
       ensureSourceIs(gPanel, "-02.js"),
-      ensureCaretAt(gPanel, 6, 12),
+      ensureCaretAt(gPanel, 6, 6),
       verifyContents({ itemCount: 1, hidden: false })
     ]));
 
     typeText(gSearchBox, "#deb");
     return finished;
   });
 
   setText(gSearchBox, ".*-02\.js");
--- a/browser/devtools/debugger/test/browser_dbg_search-symbols.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-symbols.js
@@ -47,17 +47,17 @@ function htmlSearch() {
   once(gDebugger, "popupshown").then(() => {
     writeInfo();
 
     is(gFilteredFunctions.selectedIndex, 0,
       "An item should be selected in the filtered functions view (1).");
     ok(gFilteredFunctions.selectedItem,
       "An item should be selected in the filtered functions view (2).");
 
-    if (gSources.selectedValue.indexOf(".html") != -1) {
+    if (gSources.selectedItem.attachment.source.url.indexOf(".html") != -1) {
       let expectedResults = [
         ["inline", ".html", "", 19, 16],
         ["arrow", ".html", "", 20, 11],
         ["foo", ".html", "", 22, 11],
         ["foo2", ".html", "", 23, 11],
         ["bar2", ".html", "", 23, 18]
       ];
 
@@ -112,17 +112,17 @@ function firstJsSearch() {
   once(gDebugger, "popupshown").then(() => {
     writeInfo();
 
     is(gFilteredFunctions.selectedIndex, 0,
       "An item should be selected in the filtered functions view (1).");
     ok(gFilteredFunctions.selectedItem,
       "An item should be selected in the filtered functions view (2).");
 
-    if (gSources.selectedValue.indexOf("-01.js") != -1) {
+    if (gSources.selectedItem.attachment.source.url.indexOf("-01.js") != -1) {
       let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
       let expectedResults = [
         ["test", "-01.js", "", 4, 10],
         ["anonymousExpression", "-01.js", "test.prototype", 9, 3],
         ["namedExpression" + s + "NAME", "-01.js", "test.prototype", 11, 3],
         ["a_test", "-01.js", "foo", 22, 3],
         ["n_test" + s + "x", "-01.js", "foo", 24, 3],
         ["a_test", "-01.js", "foo.sub", 27, 5],
@@ -183,17 +183,17 @@ function secondJsSearch() {
   once(gDebugger, "popupshown").then(() => {
     writeInfo();
 
     is(gFilteredFunctions.selectedIndex, 0,
       "An item should be selected in the filtered functions view (1).");
     ok(gFilteredFunctions.selectedItem,
       "An item should be selected in the filtered functions view (2).");
 
-    if (gSources.selectedValue.indexOf("-02.js") != -1) {
+    if (gSources.selectedItem.attachment.source.url.indexOf("-02.js") != -1) {
       let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
       let expectedResults = [
         ["test2", "-02.js", "", 4, 5],
         ["test3" + s + "test3_NAME", "-02.js", "", 8, 5],
         ["test4_SAME_NAME", "-02.js", "", 11, 5],
         ["x" + s + "X", "-02.js", "test.prototype", 14, 1],
         ["y" + s + "Y", "-02.js", "test.prototype.sub", 16, 1],
         ["z" + s + "Z", "-02.js", "test.prototype.sub.sub", 18, 1],
@@ -254,17 +254,17 @@ function thirdJsSearch() {
   once(gDebugger, "popupshown").then(() => {
     writeInfo();
 
     is(gFilteredFunctions.selectedIndex, 0,
       "An item should be selected in the filtered functions view (1).");
     ok(gFilteredFunctions.selectedItem,
       "An item should be selected in the filtered functions view (2).");
 
-    if (gSources.selectedValue.indexOf("-03.js") != -1) {
+    if (gSources.selectedItem.attachment.source.url.indexOf("-03.js") != -1) {
       let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
       let expectedResults = [
         ["namedEventListener", "-03.js", "", 4, 43],
         ["a" + s + "A", "-03.js", "bar", 10, 5],
         ["b" + s + "B", "-03.js", "bar.alpha", 15, 5],
         ["c" + s + "C", "-03.js", "bar.alpha.beta", 20, 5],
         ["d" + s + "D", "-03.js", "this.theta", 25, 5],
         ["fun", "-03.js", "", 29, 7],
@@ -325,17 +325,17 @@ function filterSearch() {
   once(gDebugger, "popupshown").then(() => {
     writeInfo();
 
     is(gFilteredFunctions.selectedIndex, 0,
       "An item should be selected in the filtered functions view (1).");
     ok(gFilteredFunctions.selectedItem,
       "An item should be selected in the filtered functions view (2).");
 
-    if (gSources.selectedValue.indexOf("-03.js") != -1) {
+    if (gSources.selectedItem.attachment.source.url.indexOf("-03.js") != -1) {
       let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
       let expectedResults = [
         ["namedEventListener", "-03.js", "", 4, 43],
         ["bar", "-03.js", "", 29, 19],
         ["w_bar" + s + "baz", "-03.js", "window", 29, 38],
         ["anonymousExpression", "-01.js", "test.prototype", 9, 3],
         ["namedExpression" + s + "NAME", "-01.js", "test.prototype", 11, 3],
         ["arrow", ".html", "", 20, 11],
@@ -447,17 +447,17 @@ function saveSearch() {
       gFilteredFunctions.selectedItem.target,
       gDebugger);
   }
 
   return finished;
 }
 
 function writeInfo() {
-  info("Current source url:\n" + gSources.selectedValue);
+  info("Current source url:\n" + getSelectedSourceURL(gSources));
   info("Debugger editor text:\n" + gEditor.getText());
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
--- a/browser/devtools/debugger/test/browser_dbg_searchbox-help-popup-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_searchbox-help-popup-02.js
@@ -58,19 +58,19 @@ function focusEditor() {
   // Focusing the editor takes a tick to update the caret and selection.
   gEditor.focus();
   executeSoon(deferred.resolve);
 
   return deferred.promise;
 }
 
 function testFocusLost() {
-  ok(isCaretPos(gPanel, 1, 1),
+  ok(isCaretPos(gPanel, 6, 1),
     "The editor caret position appears to be correct after gaining focus.");
-  ok(isEditorSel(gPanel, [1, 1]),
+  ok(isEditorSel(gPanel, [165, 165]),
     "The editor selection appears to be correct after gaining focus.");
   is(gEditor.getSelection(), "",
     "The editor selected text appears to be correct after gaining focus.");
 
   is(gSearchBoxPanel.state, "closed",
     "The search box panel should still not be visible.");
 }
 
--- a/browser/devtools/debugger/test/browser_dbg_server-conditional-bp-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_server-conditional-bp-01.js
@@ -49,65 +49,65 @@ function test() {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "ermahgerd");
   });
 
   function addBreakpoints() {
     return promise.resolve(null)
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 18,
                                          condition: "undefined"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 19,
                                          condition: "null"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 20,
                                          condition: "42"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 21,
                                          condition: "true"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 22,
                                          condition: "'nasu'"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 23,
                                          condition: "/regexp/"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 24,
                                          condition: "({})"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 25,
                                          condition: "(function() {})"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 26,
                                          condition: "(function() { return false; })()"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 27,
                                          condition: "a"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 28,
                                          condition: "a !== undefined"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 29,
                                          condition: "a !== null"
                                        }))
-      .then(() => gPanel.addBreakpoint({ url: gSources.selectedValue,
+      .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
                                          line: 30,
                                          condition: "b"
                                        }));
   }
 
   function initialChecks() {
     is(gDebugger.gThreadClient.state, "paused",
       "Should only be getting stack frames while paused.");
@@ -167,57 +167,57 @@ function test() {
 
     return finished;
   }
 
   function testBreakpoint(aLine, aHighlightBreakpoint) {
     // Highlight the breakpoint only if required.
     if (aHighlightBreakpoint) {
       let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
-      gSources.highlightBreakpoint({ url: gSources.selectedValue, line: aLine });
+      gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: aLine });
       return finished;
     }
 
-    let selectedUrl = gSources.selectedValue;
+    let selectedActor = gSources.selectedValue;
     let selectedBreakpoint = gSources._selectedBreakpointItem;
 
-    ok(selectedUrl,
+    ok(selectedActor,
       "There should be a selected item in the sources pane.");
     ok(selectedBreakpoint,
       "There should be a selected brekapoint in the sources pane.");
 
-    is(selectedBreakpoint.attachment.url, selectedUrl,
+    is(selectedBreakpoint.attachment.actor, selectedActor,
       "The breakpoint on line " + aLine + " wasn't added on the correct source.");
     is(selectedBreakpoint.attachment.line, aLine,
       "The breakpoint on line " + aLine + " wasn't found.");
     is(!!selectedBreakpoint.attachment.disabled, false,
       "The breakpoint on line " + aLine + " should be enabled.");
     is(!!selectedBreakpoint.attachment.openPopup, false,
       "The breakpoint on line " + aLine + " should not have opened a popup.");
     is(gSources._conditionalPopupVisible, false,
       "The breakpoint conditional expression popup should not have been shown.");
 
     return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
-      is(aBreakpointClient.location.url, selectedUrl,
+      is(aBreakpointClient.location.actor, selectedActor,
         "The breakpoint's client url is correct");
       is(aBreakpointClient.location.line, aLine,
         "The breakpoint's client line is correct");
       isnot(aBreakpointClient.condition, undefined,
         "The breakpoint on line " + aLine + " should have a conditional expression.");
 
       ok(isCaretPos(gPanel, aLine),
         "The editor caret position is not properly set.");
     });
   }
 
   function testAfterReload() {
-    let selectedUrl = gSources.selectedValue;
+    let selectedActor = getSelectedSourceURL(gSources);
     let selectedBreakpoint = gSources._selectedBreakpointItem;
 
-    ok(selectedUrl,
+    ok(selectedActor,
       "There should be a selected item in the sources pane after reload.");
     ok(!selectedBreakpoint,
       "There should be no selected brekapoint in the sources pane after reload.");
 
     return promise.resolve(null)
       .then(() => testBreakpoint(18, true))
       .then(() => testBreakpoint(19, true))
       .then(() => testBreakpoint(20, true))
--- a/browser/devtools/debugger/test/browser_dbg_server-conditional-bp-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_server-conditional-bp-02.js
@@ -72,25 +72,25 @@ function test() {
 
     is(gBreakpointsAdded.size, 0,
       "No breakpoints currently added.");
     is(gBreakpointsRemoving.size, 0,
       "No breakpoints currently being removed.");
     is(gEditor.getBreakpoints().length, 0,
       "No breakpoints currently shown in the editor.");
 
-    ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
+    ok(!gBreakpoints._getAdded({ actor: "foo", line: 3 }),
       "_getAdded('foo', 3) returns falsey.");
-    ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
+    ok(!gBreakpoints._getRemoving({ actor: "bar", line: 3 }),
       "_getRemoving('bar', 3) returns falsey.");
   }
 
   function addBreakpoint1() {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
-    gPanel.addBreakpoint({ url: gSources.selectedValue, line: 18 });
+    gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 18 });
     return finished;
   }
 
   function addBreakpoint2() {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
     setCaretPosition(19);
     gSources._onCmdAddBreakpoint();
     return finished;
@@ -127,52 +127,52 @@ function test() {
   function delBreakpoint4() {
     let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
     setCaretPosition(21);
     gSources._onCmdAddBreakpoint();
     return finished;
   }
 
   function testBreakpoint(aLine, aOpenPopupFlag, aPopupVisible, aConditionalExpression) {
-    let selectedUrl = gSources.selectedValue;
+    let selectedActor = gSources.selectedValue;
     let selectedBreakpoint = gSources._selectedBreakpointItem;
 
-    ok(selectedUrl,
+    ok(selectedActor,
       "There should be a selected item in the sources pane.");
     ok(selectedBreakpoint,
       "There should be a selected brekapoint in the sources pane.");
 
-    is(selectedBreakpoint.attachment.url, selectedUrl,
+    is(selectedBreakpoint.attachment.actor, selectedActor,
       "The breakpoint on line " + aLine + " wasn't added on the correct source.");
     is(selectedBreakpoint.attachment.line, aLine,
       "The breakpoint on line " + aLine + " wasn't found.");
     is(!!selectedBreakpoint.attachment.disabled, false,
       "The breakpoint on line " + aLine + " should be enabled.");
     is(!!selectedBreakpoint.attachment.openPopup, aOpenPopupFlag,
       "The breakpoint on line " + aLine + " should have a correct popup state (1).");
     is(gSources._conditionalPopupVisible, aPopupVisible,
       "The breakpoint on line " + aLine + " should have a correct popup state (2).");
 
     return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
-      is(aBreakpointClient.location.url, selectedUrl,
+      is(aBreakpointClient.location.actor, selectedActor,
         "The breakpoint's client url is correct");
       is(aBreakpointClient.location.line, aLine,
         "The breakpoint's client line is correct");
       is(aBreakpointClient.condition, aConditionalExpression,
         "The breakpoint on line " + aLine + " should have a correct conditional expression.");
       is("condition" in aBreakpointClient, !!aConditionalExpression,
         "The breakpoint on line " + aLine + " should have a correct conditional state.");
 
       ok(isCaretPos(gPanel, aLine),
         "The editor caret position is not properly set.");
     });
   }
 
   function testNoBreakpoint(aLine) {
-    let selectedUrl = gSources.selectedValue;
+    let selectedUrl = getSelectedSourceURL(gSources);
     let selectedBreakpoint = gSources._selectedBreakpointItem;
 
     ok(selectedUrl,
       "There should be a selected item in the sources pane for line " + aLine + ".");
     ok(!selectedBreakpoint,
       "There should be no selected brekapoint in the sources pane for line " + aLine + ".");
 
     ok(isCaretPos(gPanel, aLine),
--- a/browser/devtools/debugger/test/browser_dbg_server-conditional-bp-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_server-conditional-bp-03.js
@@ -14,17 +14,17 @@ function test() {
 
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gBreakpoints = gDebugger.DebuggerController.Breakpoints;
 
-    gLocation = { url: gSources.selectedValue, line: 18 };
+    gLocation = { actor: gSources.selectedValue, line: 18 };
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
       .then(addBreakpoint)
       .then(setConditional)
       .then(() => {
         let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
         toggleBreakpoint();
         return finished;
--- a/browser/devtools/debugger/test/browser_dbg_server-conditional-bp-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_server-conditional-bp-04.js
@@ -15,17 +15,17 @@ function test() {
 
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gBreakpoints = gDebugger.DebuggerController.Breakpoints;
 
-    gLocation = { url: gSources.selectedValue, line: 18 };
+    gLocation = { actor: gSources.selectedValue, line: 18 };
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
       .then(addBreakpoint)
       .then(setDummyConditional)
       .then(() => {
         let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
         toggleBreakpoint();
         return finished;
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-01.js
@@ -40,53 +40,57 @@ function checkSourceMapsEnabled() {
     "The source maps functionality should be enabled by default.");
   is(gDebugger.Prefs.sourceMapsEnabled, true,
     "The source maps pref should be true from startup.");
   is(gDebugger.DebuggerView.Options._showOriginalSourceItem.getAttribute("checked"), "true",
     "Source maps should be enabled from startup.")
 }
 
 function checkInitialSource() {
-  isnot(gSources.selectedValue.indexOf(".coffee"), -1,
+  isnot(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1,
     "The debugger should show the source mapped coffee source file.");
   is(gSources.selectedValue.indexOf(".js"), -1,
     "The debugger should not show the generated js source file.");
   is(gEditor.getText().indexOf("isnt"), 218,
     "The debugger's editor should have the coffee source source displayed.");
   is(gEditor.getText().indexOf("function"), -1,
     "The debugger's editor should not have the JS source displayed.");
 }
 
 function testSetBreakpoint() {
   let deferred = promise.defer();
+  let sourceForm = getSourceForm(gSources, COFFEE_URL);
 
   gDebugger.gThreadClient.interrupt(aResponse => {
-    gDebugger.gThreadClient.setBreakpoint({ url: COFFEE_URL, line: 5 }, aResponse => {
+    let source = gDebugger.gThreadClient.source(sourceForm);
+    source.setBreakpoint({ line: 5 }, aResponse => {
       ok(!aResponse.error,
         "Should be able to set a breakpoint in a coffee source file.");
       ok(!aResponse.actualLocation,
         "Should be able to set a breakpoint on line 5.");
 
       deferred.resolve();
     });
   });
 
   return deferred.promise;
 }
 
 function testSetBreakpointBlankLine() {
   let deferred = promise.defer();
+  let sourceForm = getSourceForm(gSources, COFFEE_URL);
 
-  gDebugger.gThreadClient.setBreakpoint({ url: COFFEE_URL, line: 3 }, aResponse => {
+  let source = gDebugger.gThreadClient.source(sourceForm);
+  source.setBreakpoint({ line: 3 }, aResponse => {
     ok(!aResponse.error,
       "Should be able to set a breakpoint in a coffee source file on a blank line.");
     ok(aResponse.actualLocation,
       "Because 3 is empty, we should have an actualLocation.");
-    is(aResponse.actualLocation.url, COFFEE_URL,
-      "actualLocation.url should be source mapped to the coffee file.");
+    is(aResponse.actualLocation.source.url, COFFEE_URL,
+      "actualLocation.actor should be source mapped to the coffee file.");
     is(aResponse.actualLocation.line, 2,
       "actualLocation.line should be source mapped back to 2.");
 
     deferred.resolve();
   });
 
   return deferred.promise;
 }
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-02.js
@@ -36,19 +36,19 @@ function test() {
 
 function testToggleGeneratedSource() {
   let finished = waitForSourceShown(gPanel, ".js").then(() => {
     is(gPrefs.sourceMapsEnabled, false,
       "The source maps pref should have been set to false.");
     is(gOptions._showOriginalSourceItem.getAttribute("checked"), "false",
       "Source maps should now be disabled.")
 
-    is(gSources.selectedValue.indexOf(".coffee"), -1,
+    is(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1,
       "The debugger should not show the source mapped coffee source file.");
-    isnot(gSources.selectedValue.indexOf(".js"), -1,
+    isnot(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1,
       "The debugger should show the generated js source file.");
 
     is(gEditor.getText().indexOf("isnt"), -1,
       "The debugger's editor should not have the coffee source source displayed.");
     is(gEditor.getText().indexOf("function"), 36,
       "The debugger's editor should have the JS source displayed.");
   });
 
@@ -56,18 +56,20 @@ function testToggleGeneratedSource() {
   gOptions._toggleShowOriginalSource();
   gOptions._onPopupHidden();
 
   return finished;
 }
 
 function testSetBreakpoint() {
   let deferred = promise.defer();
+  let sourceForm = getSourceForm(gSources, JS_URL);
+  let source = gDebugger.gThreadClient.source(sourceForm);
 
-  gDebugger.gThreadClient.setBreakpoint({ url: JS_URL, line: 7 }, aResponse => {
+  source.setBreakpoint({ line: 7 }, aResponse => {
     ok(!aResponse.error,
       "Should be able to set a breakpoint in a js file.");
 
     gDebugger.gClient.addOneTimeListener("resumed", () => {
       waitForCaretAndScopes(gPanel, 7).then(() => {
         // Make sure that we have JavaScript stack frames.
         is(gFrames.itemCount, 1,
           "Should have only one frame.");
@@ -90,19 +92,19 @@ function testSetBreakpoint() {
 
 function testToggleOnPause() {
   let finished = waitForSourceAndCaretAndScopes(gPanel, ".coffee", 5).then(() => {
     is(gPrefs.sourceMapsEnabled, true,
       "The source maps pref should have been set to true.");
     is(gOptions._showOriginalSourceItem.getAttribute("checked"), "true",
       "Source maps should now be enabled.")
 
-    isnot(gSources.selectedValue.indexOf(".coffee"), -1,
+    isnot(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1,
       "The debugger should show the source mapped coffee source file.");
-    is(gSources.selectedValue.indexOf(".js"), -1,
+    is(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1,
       "The debugger should not show the generated js source file.");
 
     is(gEditor.getText().indexOf("isnt"), 218,
       "The debugger's editor should have the coffee source source displayed.");
     is(gEditor.getText().indexOf("function"), -1,
       "The debugger's editor should not have the JS source displayed.");
 
     // Make sure that we have coffee source stack frames.
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-03.js
@@ -26,29 +26,31 @@ function test() {
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function checkInitialSource() {
-  isnot(gSources.selectedValue.indexOf(".js"), -1,
+  isnot(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1,
     "The debugger should not show the minified js file.");
-  is(gSources.selectedValue.indexOf(".min.js"), -1,
+  is(gSources.selectedItem.attachment.source.url.indexOf(".min.js"), -1,
     "The debugger should show the original js file.");
   is(gEditor.getText().split("\n").length, 46,
     "The debugger's editor should have the original source displayed, " +
     "not the whitespace stripped minified version.");
 }
 
 function testSetBreakpoint() {
   let deferred = promise.defer();
+  let sourceForm = getSourceForm(gSources, JS_URL);
+  let source = gDebugger.gThreadClient.source(sourceForm);
 
-  gDebugger.gThreadClient.setBreakpoint({ url: JS_URL, line: 30, column: 21 }, aResponse => {
+  source.setBreakpoint({ line: 30, column: 21 }, aResponse => {
     ok(!aResponse.error,
       "Should be able to set a breakpoint in a js file.");
     ok(!aResponse.actualLocation,
       "Should be able to set a breakpoint on line 30 and column 10.");
 
     gDebugger.gClient.addOneTimeListener("resumed", () => {
       waitForCaretAndScopes(gPanel, 30).then(() => {
         // Make sure that we have the right stack frames.
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-04.js
@@ -41,17 +41,17 @@ function test() {
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function checkInitialSource() {
-  isnot(gSources.selectedValue.indexOf("code_math_bogus_map.js"), -1,
+  isnot(gSources.selectedItem.attachment.source.url.indexOf("code_math_bogus_map.js"), -1,
     "The debugger should show the minified js file.");
 }
 
 function enablePauseOnExceptions() {
   let deferred = promise.defer();
 
   gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
     is(gPrefs.pauseOnExceptions, true,
@@ -81,18 +81,20 @@ function disableIgnoreCaughtExceptions()
   gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false");
   gOptions._toggleIgnoreCaughtExceptions();
 
   return deferred.promise;
 }
 
 function testSetBreakpoint() {
   let deferred = promise.defer();
+  let sourceForm = getSourceForm(gSources, JS_URL);
+  let source = gDebugger.gThreadClient.source(sourceForm);
 
-  gDebugger.gThreadClient.setBreakpoint({ url: JS_URL, line: 3, column: 61 }, aResponse => {
+  source.setBreakpoint({ line: 3, column: 61 }, aResponse => {
     ok(!aResponse.error,
       "Should be able to set a breakpoint in a js file.");
     ok(!aResponse.actualLocation,
       "Should be able to set a breakpoint on line 3 and column 61.");
 
     deferred.resolve();
   });
 
@@ -110,17 +112,27 @@ function testHitBreakpoint() {
 
   gDebugger.gThreadClient.resume(aResponse => {
     ok(!aResponse.error, "Shouldn't get an error resuming.");
     is(aResponse.type, "resumed", "Type should be 'resumed'.");
 
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
       is(gFrames.itemCount, 1, "Should have one frame.");
 
-      gDebugger.gThreadClient.resume(deferred.resolve);
+      // This is weird, but we need to let the debugger a chance to
+      // update first
+      executeSoon(() => {
+        gDebugger.gThreadClient.resume(() => {
+          // We also need to make sure the next step doesn't add a
+          // "resumed" handler until this is completely finished
+          executeSoon(() => {
+            deferred.resolve();
+          });
+        });
+      });
     });
   });
 
   return deferred.promise;
 }
 
 function enableIgnoreCaughtExceptions() {
   let deferred = promise.defer();
--- a/browser/devtools/debugger/test/browser_dbg_sources-cache.js
+++ b/browser/devtools/debugger/test/browser_dbg_sources-cache.js
@@ -35,49 +35,49 @@ function test() {
   });
 }
 
 function initialChecks() {
   ok(gEditor.getText().contains("First source!"),
     "Editor text contents appears to be correct.");
   is(gSources.selectedItem.attachment.label, "code_function-search-01.js",
     "The currently selected label in the sources container is correct.");
-  ok(gSources.selectedValue.contains("code_function-search-01.js"),
+  ok(getSelectedSourceURL(gSources).contains("code_function-search-01.js"),
     "The currently selected value in the sources container appears to be correct.");
 
   is(gSources.itemCount, TOTAL_SOURCES,
     "There should be " + TOTAL_SOURCES + " sources present in the sources list.");
   is(gSources.visibleItems.length, TOTAL_SOURCES,
     "There should be " + TOTAL_SOURCES + " sources visible in the sources list.");
   is(gSources.attachments.length, TOTAL_SOURCES,
     "There should be " + TOTAL_SOURCES + " attachments stored in the sources container model.")
   is(gSources.values.length, TOTAL_SOURCES,
     "There should be " + TOTAL_SOURCES + " values stored in the sources container model.")
 
   info("Source labels: " + gSources.attachments.toSource());
   info("Source values: " + gSources.values.toSource());
 
   is(gSources.attachments[0].label, "code_function-search-01.js",
     "The first source label is correct.");
-  ok(gSources.values[0].contains("code_function-search-01.js"),
+  ok(gSources.attachments[0].source.url.contains("code_function-search-01.js"),
     "The first source value appears to be correct.");
 
   is(gSources.attachments[1].label, "code_function-search-02.js",
     "The second source label is correct.");
-  ok(gSources.values[1].contains("code_function-search-02.js"),
+  ok(gSources.attachments[1].source.url.contains("code_function-search-02.js"),
     "The second source value appears to be correct.");
 
   is(gSources.attachments[2].label, "code_function-search-03.js",
     "The third source label is correct.");
-  ok(gSources.values[2].contains("code_function-search-03.js"),
+  ok(gSources.attachments[2].source.url.contains("code_function-search-03.js"),
     "The third source value appears to be correct.");
 
   is(gSources.attachments[3].label, "doc_function-search.html",
     "The third source label is correct.");
-  ok(gSources.values[3].contains("doc_function-search.html"),
+  ok(gSources.attachments[3].source.url.contains("doc_function-search.html"),
     "The third source value appears to be correct.");
 
   is(gDebugger.SourceUtils._labelsCache.size, TOTAL_SOURCES,
     "There should be " + TOTAL_SOURCES + " labels cached.");
   is(gDebugger.SourceUtils._groupsCache.size, TOTAL_SOURCES,
     "There should be " + TOTAL_SOURCES + " groups cached.");
 }
 
@@ -87,27 +87,27 @@ function getTextForSourcesAndCheckIntegr
 
 function performReloadAndTestState() {
   gDebugger.gTarget.once("will-navigate", testStateBeforeReload);
   gDebugger.gTarget.once("navigate", testStateAfterReload);
   return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
 }
 
 function testCacheIntegrity(aSources) {
-  for (let [url, contents] of aSources) {
+  for (let [actor, contents] of aSources) {
     // Sources of a debugee don't always finish fetching consecutively. D'uh.
-    let index = gSources.values.indexOf(url);
+    let index = gSources.values.indexOf(actor);
 
     ok(index >= 0 && index <= TOTAL_SOURCES,
-      "Found a source url cached correctly (" + index + ").");
+      "Found a source actor cached correctly (" + index + ").");
     ok(contents.contains(
       ["First source!", "Second source!", "Third source!", "Peanut butter jelly time!"][index]),
       "Found a source's text contents cached correctly (" + index + ").");
 
-    info("Cached source url at " + index + ": " + url);
+    info("Cached source actor at " + index + ": " + actor);
     info("Cached source text at " + index + ": " + contents);
   }
 }
 
 function testStateBeforeReload() {
   is(gSources.itemCount, 0,
     "There should be no sources present in the sources list during reload.");
   is(gDebugger.SourceUtils._labelsCache, gPrevLabelsCache,
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_sources-eval.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure eval scripts appear in the source list
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
+
+function test() {
+  let gTab, gPanel, gDebugger;
+  let gSources, gBreakpoints;
+
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+    gTab = aTab;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gSources = gDebugger.DebuggerView.Sources;
+    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+    waitForSourceShown(gPanel, "-eval.js")
+      .then(run)
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+  });
+
+  function run() {
+    return Task.spawn(function*() {
+      is(gSources.values.length, 1, "Should have 1 source");
+
+      let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
+      callInTab(gTab, "evalSource");
+      yield newSource;
+
+      is(gSources.values.length, 2, "Should have 2 sources");
+
+      yield closeDebuggerAndFinish(gPanel);
+    });
+  }
+}
--- a/browser/devtools/debugger/test/browser_dbg_sources-labels.js
+++ b/browser/devtools/debugger/test/browser_dbg_sources-labels.js
@@ -67,56 +67,49 @@ function test() {
     ];
 
     is(gSources.itemCount, 1,
       "Should contain the original source label in the sources widget.");
     is(gSources.selectedIndex, 0,
       "The first item in the sources widget should be selected (1).");
     is(gSources.selectedItem.attachment.label, "doc_recursion-stack.html",
       "The first item in the sources widget should be selected (2).");
-    is(gSources.selectedValue, TAB_URL,
+    is(getSelectedSourceURL(gSources), TAB_URL,
       "The first item in the sources widget should be selected (3).");
 
-    gSources.empty();
-
-    is(gSources.itemCount, 0,
-      "Should contain no items in the sources widget after emptying.");
-    is(gSources.selectedIndex, -1,
-      "No item in the sources widget should be selected (1).");
-    is(gSources.selectedItem, null,
-      "No item in the sources widget should be selected (2).");
-    is(gSources.selectedValue, "",
-      "No item in the sources widget should be selected (3).");
-
+    let id = 0;
     for (let { href, leaf } of urls) {
       let url = href + leaf;
+      let actor = 'actor' + id++;
       let label = gUtils.trimUrlLength(gUtils.getSourceLabel(url));
       let group = gUtils.getSourceGroup(url);
       let dummy = document.createElement("label");
+      dummy.setAttribute('value', label);
 
-      gSources.push([dummy, url], {
+      gSources.push([dummy, actor], {
         attachment: {
+          source: { actor: actor, url: url },
           label: label,
           group: group
         }
       });
     }
 
     info("Source locations:");
     info(gSources.values.toSource());
 
     info("Source attachments:");
     info(gSources.attachments.toSource());
 
     for (let { href, leaf, dupe } of urls) {
       let url = href + leaf;
       if (dupe) {
-        ok(!gSources.containsValue(url), "Shouldn't contain source: " + url);
+        ok(!gSources.containsValue(getSourceActor(gSources, url)), "Shouldn't contain source: " + url);
       } else {
-        ok(gSources.containsValue(url), "Should contain source: " + url);
+        ok(gSources.containsValue(getSourceActor(gSources, url)), "Should contain source: " + url);
       }
     }
 
     ok(gSources.getItemForAttachment(e => e.label == "random/subrandom/"),
       "Source (0) label is incorrect.");
     ok(gSources.getItemForAttachment(e => e.label == "random/suprandom/?a=1"),
       "Source (1) label is incorrect.");
     ok(gSources.getItemForAttachment(e => e.label == "random/?a=1"),
@@ -146,28 +139,28 @@ function test() {
     ok(gSources.getItemForAttachment(e => e.label == "script_t3_2.js"),
       "Source (13) label is incorrect.");
     ok(gSources.getItemForAttachment(e => e.label == "script_t3_3.js"),
       "Source (14) label is incorrect.");
 
     ok(gSources.getItemForAttachment(e => e.label == nananana + "Batman!" + ellipsis),
       "Source (15) label is incorrect.");
 
-    is(gSources.itemCount, urls.filter(({ dupe }) => !dupe).length,
+    is(gSources.itemCount, urls.filter(({ dupe }) => !dupe).length + 1,
       "Didn't get the correct number of sources in the list.");
 
-    is(gSources.getItemByValue("http://some.address.com/random/subrandom/").attachment.label,
+    is(gSources.getItemByValue(getSourceActor(gSources, "http://some.address.com/random/subrandom/")).attachment.label,
       "random/subrandom/",
       "gSources.getItemByValue isn't functioning properly (0).");
-    is(gSources.getItemByValue("http://some.address.com/random/suprandom/?a=1").attachment.label,
+    is(gSources.getItemByValue(getSourceActor(gSources, "http://some.address.com/random/suprandom/?a=1")).attachment.label,
       "random/suprandom/?a=1",
       "gSources.getItemByValue isn't functioning properly (1).");
 
-    is(gSources.getItemForAttachment(e => e.label == "random/subrandom/").value,
-      "http://some.address.com/random/subrandom/",
+    is(gSources.getItemForAttachment(e => e.label == "random/subrandom/").attachment.source.url,
+       "http://some.address.com/random/subrandom/",
       "gSources.getItemForAttachment isn't functioning properly (0).");
-    is(gSources.getItemForAttachment(e => e.label == "random/suprandom/?a=1").value,
+    is(gSources.getItemForAttachment(e => e.label == "random/suprandom/?a=1").attachment.source.url,
       "http://some.address.com/random/suprandom/?a=1",
       "gSources.getItemForAttachment isn't functioning properly (1).");
 
     closeDebuggerAndFinish(gPanel);
   });
 }
--- a/browser/devtools/debugger/test/browser_dbg_sources-sorting.js
+++ b/browser/devtools/debugger/test/browser_dbg_sources-sorting.js
@@ -13,29 +13,25 @@ let gSources, gUtils;
 function test() {
   initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gUtils = gDebugger.SourceUtils;
 
-    waitForSourceShown(gPanel, ".html").then(() => {
-      addSourceAndCheckOrder(1, () => {
-        addSourceAndCheckOrder(2, () => {
-          addSourceAndCheckOrder(3, () => {
-            closeDebuggerAndFinish(gPanel);
-          });
-        });
-      });
-    });
+    waitForSourceShown(gPanel, ".html")
+      .then(addSourceAndCheckOrder.bind(null, 1))
+      .then(addSourceAndCheckOrder.bind(null, 2))
+      .then(addSourceAndCheckOrder.bind(null, 3))
+      .then(() => { closeDebuggerAndFinish(gPanel); });
   });
 }
 
-function addSourceAndCheckOrder(aMethod, aCallback) {
+function addSourceAndCheckOrder(aMethod) {
   gSources.empty();
   gSources.suppressSelectionEvents = true;
 
   let urls = [
     { href: "ici://some.address.com/random/", leaf: "subrandom/" },
     { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" },
     { href: "san://interesting.address.gro/random/", leaf: "script.js" },
     { href: "si://interesting.address.moc/random/", leaf: "script.js" },
@@ -44,79 +40,84 @@ function addSourceAndCheckOrder(aMethod,
     { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" },
     { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" }
   ];
 
   urls.sort(function(a, b) {
     return Math.random() - 0.5;
   });
 
+  let id = 0;
+
   switch (aMethod) {
     case 1:
       for (let { href, leaf } of urls) {
         let url = href + leaf;
+        let actor = 'actor' + id++;
         let label = gUtils.getSourceLabel(url);
         let dummy = document.createElement("label");
-        gSources.push([dummy, url], {
+        gSources.push([dummy, actor], {
           staged: true,
           attachment: {
             label: label
           }
         });
       }
       gSources.commit({ sorted: true });
       break;
 
     case 2:
       for (let { href, leaf } of urls) {
         let url = href + leaf;
+        let actor = 'actor' + id++;
         let label = gUtils.getSourceLabel(url);
         let dummy = document.createElement("label");
-        gSources.push([dummy, url], {
+        gSources.push([dummy, actor], {
           staged: false,
           attachment: {
             label: label
           }
         });
       }
       break;
 
     case 3:
       let i = 0
       for (; i < urls.length / 2; i++) {
         let { href, leaf } = urls[i];
         let url = href + leaf;
+        let actor = 'actor' + id++;
         let label = gUtils.getSourceLabel(url);
         let dummy = document.createElement("label");
-        gSources.push([dummy, url], {
+        gSources.push([dummy, actor], {
           staged: true,
           attachment: {
             label: label
           }
         });
       }
       gSources.commit({ sorted: true });
 
       for (; i < urls.length; i++) {
         let { href, leaf } = urls[i];
         let url = href + leaf;
+        let actor = 'actor' + id++;
         let label = gUtils.getSourceLabel(url);
         let dummy = document.createElement("label");
-        gSources.push([dummy, url], {
+        gSources.push([dummy, actor], {
           staged: false,
           attachment: {
             label: label
           }
         });
       }
       break;
   }
 
   checkSourcesOrder(aMethod);
-  aCallback();
 }
 
 function checkSourcesOrder(aMethod) {
   let attachments = gSources.attachments;
 
   for (let i = 0; i < attachments.length - 1; i++) {
     let first = attachments[i].label;
     let second = attachments[i + 1].label;
--- a/browser/devtools/debugger/test/browser_dbg_split-console-paused-reload.js
+++ b/browser/devtools/debugger/test/browser_dbg_split-console-paused-reload.js
@@ -6,23 +6,24 @@
  * the pending navigation.
  */
 
 function test() {
   Task.spawn(runTests);
 }
 
 function* runTests() {
-  const TAB_URL = EXAMPLE_URL + "doc_split-console-paused-reload.html";
+  let TAB_URL = EXAMPLE_URL + "doc_split-console-paused-reload.html";
   let [,, panel] = yield initDebugger(TAB_URL);
   let dbgWin = panel.panelWin;
+  let sources = dbgWin.DebuggerView.Sources;
   let frames = dbgWin.DebuggerView.StackFrames;
   let toolbox = gDevTools.getToolbox(panel.target);
 
-  yield panel.addBreakpoint({ url: TAB_URL, line: 16 });
+  yield panel.addBreakpoint({ actor: getSourceActor(sources, TAB_URL), line: 16 });
   info("Breakpoint was set.");
   dbgWin.DebuggerController._target.activeTab.reload();
   info("Page reloaded.");
   yield waitForSourceAndCaretAndScopes(panel, ".html", 16);
   yield ensureThreadClientState(panel, "paused");
   info("Breakpoint was hit.");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     frames.selectedItem.target,
--- a/browser/devtools/debugger/test/browser_dbg_stack-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-02.js
@@ -36,17 +36,17 @@ function performTest() {
   is(gFrames.getItemAtIndex(0).attachment.url,
     TAB_URL, "Oldest frame url should be correct.");
   is(gClassicFrames.getItemAtIndex(0).attachment.depth,
     0, "Oldest frame name is mirrored correctly.");
 
   is(gFrames.getItemAtIndex(1).attachment.title,
     "(eval)", "Newest frame name should be correct.");
   is(gFrames.getItemAtIndex(1).attachment.url,
-    TAB_URL, "Newest frame url should be correct.");
+     TAB_URL, "Newest frame url should be correct.");
   is(gClassicFrames.getItemAtIndex(1).attachment.depth,
     1, "Newest frame name is mirrored correctly.");
 
   is(gFrames.selectedIndex, 1,
     "Newest frame should be selected by default.");
   is(gClassicFrames.selectedIndex, 0,
     "Newest frame should be selected by default in the mirrored view.");
 
--- a/browser/devtools/debugger/test/browser_dbg_stack-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-05.js
@@ -18,122 +18,84 @@ function test() {
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
     gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(initialChecks)
-      .then(testNewestTwoFrames)
-      .then(testOldestTwoFrames)
+      .then(testNewestFrame)
+      .then(testOldestFrame)
       .then(testAfterResume)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function initialChecks() {
   is(gDebugger.gThreadClient.state, "paused",
     "Should only be getting stack frames while paused.");
-  is(gFrames.itemCount, 4,
+  is(gFrames.itemCount, 2,
     "Should have four frames.");
-  is(gClassicFrames.itemCount, 4,
+  is(gClassicFrames.itemCount, 2,
     "Should also have four frames in the mirrored view.");
 }
 
-function testNewestTwoFrames() {
+function testNewestFrame() {
   let deferred = promise.defer();
 
-  is(gFrames.selectedIndex, 3,
+  is(gFrames.selectedIndex, 1,
     "Newest frame should be selected by default.");
   is(gClassicFrames.selectedIndex, 0,
     "Newest frame should be selected in the mirrored view as well.");
   is(gSources.selectedIndex, 1,
     "The second source is selected in the widget.");
   ok(isCaretPos(gPanel, 1),
     "Editor caret location is correct (1).");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
-    is(gEditor.getDebugLocation(), 0,
+    is(gEditor.getDebugLocation(), 5,
       "Editor debug location is correct.");
 
-    EventUtils.sendMouseEvent({ type: "mousedown" },
-      gFrames.getItemAtIndex(2).target,
-      gDebugger);
+    deferred.resolve();
+  });
+
+  return deferred.promise;
+}
+
+function testOldestFrame() {
+  let deferred = promise.defer();
 
-    is(gFrames.selectedIndex, 2,
-      "Third frame should be selected after click.");
+  waitForSourceAndCaret(gPanel, "-01.js", 1).then(waitForTick).then(() => {
+    is(gFrames.selectedIndex, 0,
+      "Second frame should be selected after click.");
     is(gClassicFrames.selectedIndex, 1,
-      "Third frame should be selected in the mirrored view as well.");
-    is(gSources.selectedIndex, 1,
-      "The second source is still selected in the widget.");
-    ok(isCaretPos(gPanel, 6),
-      "Editor caret location is correct (2).");
+      "Second frame should be selected in the mirrored view as well.");
+    is(gSources.selectedIndex, 0,
+      "The first source is now selected in the widget.");
+    ok(isCaretPos(gPanel, 5),
+      "Editor caret location is correct (3).");
 
     // The editor's debug location takes a tick to update.
     executeSoon(() => {
-      is(gEditor.getDebugLocation(), 5,
+      is(gEditor.getDebugLocation(), 4,
         "Editor debug location is correct.");
 
       deferred.resolve();
     });
   });
 
-  return deferred.promise;
-}
-
-function testOldestTwoFrames() {
-  let deferred = promise.defer();
-
-  waitForSourceAndCaret(gPanel, "-01.js", 1).then(waitForTick).then(() => {
-    is(gFrames.selectedIndex, 1,
-      "Second frame should be selected after click.");
-    is(gClassicFrames.selectedIndex, 2,
-      "Second frame should be selected in the mirrored view as well.");
-    is(gSources.selectedIndex, 0,
-      "The first source is now selected in the widget.");
-    ok(isCaretPos(gPanel, 1),
-      "Editor caret location is correct (3).");
-
-    // The editor's debug location takes a tick to update.
-    executeSoon(() => {
-      is(gEditor.getDebugLocation(), 0,
-        "Editor debug location is correct.");
-
-      EventUtils.sendMouseEvent({ type: "mousedown" },
-        gFrames.getItemAtIndex(0).target,
-        gDebugger);
-
-      is(gFrames.selectedIndex, 0,
-        "Oldest frame should be selected after click.");
-      is(gClassicFrames.selectedIndex, 3,
-        "Oldest frame should be selected in the mirrored view as well.");
-      is(gSources.selectedIndex, 0,
-        "The first source is still selected in the widget.");
-      ok(isCaretPos(gPanel, 5),
-        "Editor caret location is correct (4).");
-
-      // The editor's debug location takes a tick to update.
-      executeSoon(() => {
-        is(gEditor.getDebugLocation(), 4,
-          "Editor debug location is correct.");
-
-        deferred.resolve();
-      });
-    });
-  });
-
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    gDebugger.document.querySelector("#stackframe-2"),
+    gDebugger.document.querySelector("#stackframe-1"),
     gDebugger);
 
   return deferred.promise;
 }
 
 function testAfterResume() {
   let deferred = promise.defer();
 
--- a/browser/devtools/debugger/test/browser_dbg_stack-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-06.js
@@ -22,61 +22,61 @@ function test() {
     gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
     callInTab(gTab, "firstCall");
   });
 }
 
 function performTest() {
-  is(gFrames.selectedIndex, 3,
+  is(gFrames.selectedIndex, 1,
     "Newest frame should be selected by default.");
   is(gClassicFrames.selectedIndex, 0,
     "Newest frame should also be selected in the mirrored view.");
   is(gSources.selectedIndex, 1,
     "The second source is selected in the widget.");
   is(gEditor.getText().search(/firstCall/), -1,
     "The first source is not displayed.");
-  is(gEditor.getText().search(/debugger/), 172,
+  is(gEditor.getText().search(/debugger/), 166,
     "The second source is displayed.");
 
   waitForSourceAndCaret(gPanel, "-01.js", 1).then(waitForTick).then(() => {
     is(gFrames.selectedIndex, 0,
       "Oldest frame should be selected after click.");
-    is(gClassicFrames.selectedIndex, 3,
+    is(gClassicFrames.selectedIndex, 1,
       "Oldest frame should also be selected in the mirrored view.");
     is(gSources.selectedIndex, 0,
       "The first source is now selected in the widget.");
     is(gEditor.getText().search(/firstCall/), 118,
       "The first source is displayed.");
     is(gEditor.getText().search(/debugger/), -1,
       "The second source is not displayed.");
 
     waitForSourceAndCaret(gPanel, "-02.js", 1).then(waitForTick).then(() => {
-      is(gFrames.selectedIndex, 3,
+      is(gFrames.selectedIndex, 1,
         "Newest frame should be selected again after click.");
       is(gClassicFrames.selectedIndex, 0,
         "Newest frame should also be selected again in the mirrored view.");
       is(gSources.selectedIndex, 1,
         "The second source is selected in the widget.");
       is(gEditor.getText().search(/firstCall/), -1,
         "The first source is not displayed.");
-      is(gEditor.getText().search(/debugger/), 172,
+      is(gEditor.getText().search(/debugger/), 166,
         "The second source is displayed.");
 
       resumeDebuggerThenCloseAndFinish(gPanel);
     });
 
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gDebugger.document.querySelector("#classic-stackframe-0"),
       gDebugger);
   });
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    gDebugger.document.querySelector("#stackframe-3"),
+    gDebugger.document.querySelector("#stackframe-1"),
     gDebugger);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
--- a/browser/devtools/debugger/test/browser_dbg_stack-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-07.js
@@ -26,32 +26,32 @@ function test() {
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
     callInTab(gTab, "firstCall");
   });
 }
 
 function performTest() {
   return Task.spawn(function() {
     yield selectBottomFrame();
-    testBottomFrame(0);
+    testBottomFrame(4);
 
     yield performStep("StepOver");
-    testTopFrame(3);
+    testTopFrame(1);
 
     yield selectBottomFrame();
     testBottomFrame(4);
 
     yield performStep("StepIn");
-    testTopFrame(2);
+    testTopFrame(1);
 
     yield selectBottomFrame();
     testBottomFrame(4);
 
     yield performStep("StepOut");
-    testTopFrame(2);
+    testTopFrame(1);
 
     yield resumeDebuggerThenCloseAndFinish(gPanel);
   });
 
   function selectBottomFrame() {
     let updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
     gClassicFrames.selectedIndex = gClassicFrames.itemCount - 1;
     return updated.then(waitForTick);
@@ -85,17 +85,17 @@ function performTest() {
     is(gFrames.selectedIndex, frameIndex,
       "Topmost frame should be selected after click.");
     is(gClassicFrames.selectedIndex, gFrames.itemCount - frameIndex - 1,
       "Topmost frame should also be selected in the mirrored view.");
     is(gSources.selectedIndex, 1,
       "The second source is now selected in the widget.");
     is(gEditor.getText().search(/firstCall/), -1,
       "The second source is displayed.");
-    is(gEditor.getText().search(/debugger/), 172,
+    is(gEditor.getText().search(/debugger/), 166,
       "The first source is not displayed.");
   }
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
--- a/browser/devtools/debugger/test/browser_dbg_tracing-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_tracing-03.js
@@ -1,35 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+SimpleTest.requestCompleteLog();
+
 /**
  * Test that we can jump to function definitions by clicking on logs.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
 
-let gTab, gPanel, gDebugger;
+let gTab, gPanel, gDebugger, gSources;
 
 function test() {
   SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
     initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
       gTab = aTab;
       gPanel = aPanel;
       gDebugger = gPanel.panelWin;
+      gSources = gDebugger.DebuggerView.Sources;
 
       waitForSourceShown(gPanel, "code_tracing-01.js")
         .then(() => startTracing(gPanel))
-        .then(clickButton)
+        .then(() => clickButton())
         .then(() => waitForClientEvents(aPanel, "traces"))
         .then(() => {
           // Switch away from the JS file so we can make sure that clicking on a
           // log will switch us back to the correct JS file.
-          aPanel.panelWin.DebuggerView.Sources.selectedValue = TAB_URL;
-          return ensureSourceIs(aPanel, TAB_URL, true);
+          gSources.selectedValue = getSourceActor(gSources, TAB_URL);
+          return ensureSourceIs(aPanel, getSourceActor(gSources, TAB_URL), true);
         })
         .then(() => {
           const finished = waitForSourceShown(gPanel, "code_tracing-01.js");
           clickTraceLog();
           return finished;
         })
         .then(testCorrectLine)
         .then(() => stopTracing(gPanel))
@@ -58,9 +61,10 @@ function testCorrectLine() {
   is(gDebugger.DebuggerView.editor.getCursor().line, 18,
      "The editor should have the function definition site's line selected.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gPanel = null;
   gDebugger = null;
+  gSources = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-reexpand-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-reexpand-01.js
@@ -35,17 +35,17 @@ function test() {
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function addBreakpoint() {
-  return gBreakpoints.addBreakpoint({ url: gSources.selectedValue, line: 21 });
+  return gBreakpoints.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
 }
 
 function pauseDebuggee() {
   sendMouseClickToTab(gTab, content.document.querySelector("button"));
 
   // The first 'with' scope should be expanded by default, but the
   // variables haven't been fetched yet. This is how 'with' scopes work.
   return promise.all([
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-reexpand-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-reexpand-02.js
@@ -36,17 +36,17 @@ function test() {
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function addBreakpoint() {
-  return gBreakpoints.addBreakpoint({ url: gSources.selectedValue, line: 21 });
+  return gBreakpoints.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
 }
 
 function pauseDebuggee() {
   sendMouseClickToTab(gTab, content.document.querySelector("button"));
 
   // The first 'with' scope should be expanded by default, but the
   // variables haven't been fetched yet. This is how 'with' scopes work.
   return promise.all([
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-reexpand-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-reexpand-03.js
@@ -35,17 +35,17 @@ function test() {
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function addBreakpoint() {
-  return gBreakpoints.addBreakpoint({ url: gSources.selectedValue, line: 18 });
+  return gBreakpoints.addBreakpoint({ actor: gSources.selectedValue, line: 18 });
 }
 
 function pauseDebuggee() {
   callInTab(gTab, "test");
 
   return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
 }
 
--- a/browser/devtools/debugger/test/code_math_bogus_map.js
+++ b/browser/devtools/debugger/test/code_math_bogus_map.js
@@ -1,4 +1,4 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 function stopMe(){throw Error("boom");}try{stopMe();var a=1;a=a*2;}catch(e){};
-//# sourceMappingURL=bogus.map
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/code_script-eval.js
@@ -0,0 +1,6 @@
+
+var bar;
+
+function evalSource() {
+  eval('bar = function() {\nvar x = 5;\n}');
+}
--- a/browser/devtools/debugger/test/code_script-switching-01.js
+++ b/browser/devtools/debugger/test/code_script-switching-01.js
@@ -1,6 +1,6 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function firstCall() {
-  eval("secondCall();");
+  secondCall();
 }
--- a/browser/devtools/debugger/test/code_script-switching-02.js
+++ b/browser/devtools/debugger/test/code_script-switching-02.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function secondCall() {
   // This comment is useful: ☺
-  eval("debugger;");
+  debugger;
   function foo() {}
   if (x) {
     foo();
   }
 }
 
 var x = true;
--- a/browser/devtools/debugger/test/code_test-editor-mode
+++ b/browser/devtools/debugger/test/code_test-editor-mode
@@ -1,6 +1,6 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function secondCall() {
-  eval("debugger;");
+  debugger;
 }
--- a/browser/devtools/debugger/test/doc_cmd-break.html
+++ b/browser/devtools/debugger/test/doc_cmd-break.html
@@ -6,17 +6,17 @@
   <head>
     <meta charset="utf-8"/>
     <title>Debugger test page</title>
   </head>
 
   <body>
     <script type="text/javascript">
       function firstCall() {
-        eval("window.gLineNumber = Error().lineNumber; secondCall();");
+        window.gLineNumber = Error().lineNumber; secondCall();
       }
       function secondCall() {
-        eval("debugger;");
+        debugger;
       }
     </script>
   </body>
 
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_script-eval.html
@@ -0,0 +1,17 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger test page</title>
+  </head>
+
+  <body>
+    <button onclick="evalSource()">Click me!</button>
+
+    <script type="text/javascript" src="code_script-eval.js"></script>
+  </body>
+
+</html>
--- a/browser/devtools/debugger/test/doc_tracing-01.html
+++ b/browser/devtools/debugger/test/doc_tracing-01.html
@@ -3,10 +3,18 @@
   <head>
     <meta charset="utf-8"/>
     <title>Debugger Tracer test page</title>
   </head>
 
   <body>
     <script src="code_tracing-01.js"></script>
     <button onclick="main()">Click me!</button>
+
+    <script type="text/javascript">
+      // Have an inline script to make sure the HTML file is listed
+      // in the sources. We want to switch between them.
+      function foo() {
+        return 5;
+      }
+    </script>
   </body>
 </html>
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -241,40 +241,43 @@ function waitForTick() {
 function waitForTime(aDelay) {
   let deferred = promise.defer();
   setTimeout(deferred.resolve, aDelay);
   return deferred.promise;
 }
 
 function waitForSourceShown(aPanel, aUrl) {
   return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.SOURCE_SHOWN).then(aSource => {
-    let sourceUrl = aSource.url;
+    let sourceUrl = aSource.url || aSource.introductionUrl;
     info("Source shown: " + sourceUrl);
 
     if (!sourceUrl.contains(aUrl)) {
       return waitForSourceShown(aPanel, aUrl);
     } else {
       ok(true, "The correct source has been shown.");
     }
   });
 }
 
 function waitForEditorLocationSet(aPanel) {
   return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET);
 }
 
-function ensureSourceIs(aPanel, aUrl, aWaitFlag = false) {
-  if (aPanel.panelWin.DebuggerView.Sources.selectedValue.contains(aUrl)) {
-    ok(true, "Expected source is shown: " + aUrl);
+function ensureSourceIs(aPanel, aUrlOrSource, aWaitFlag = false) {
+  let sources = aPanel.panelWin.DebuggerView.Sources;
+
+  if (sources.selectedValue === aUrlOrSource ||
+      sources.selectedItem.attachment.source.url.contains(aUrlOrSource)) {
+    ok(true, "Expected source is shown: " + aUrlOrSource);
     return promise.resolve(null);
   }
   if (aWaitFlag) {
-    return waitForSourceShown(aPanel, aUrl);
+    return waitForSourceShown(aPanel, aUrlOrSource);
   }
-  ok(false, "Expected source was not already shown: " + aUrl);
+  ok(false, "Expected source was not already shown: " + aUrlOrSource);
   return promise.reject(null);
 }
 
 function waitForCaretUpdated(aPanel, aLine, aCol = 1) {
   return waitForEditorEvents(aPanel, "cursorActivity").then(() => {
     let cursor = aPanel.panelWin.DebuggerView.editor.getCursor();
     info("Caret updated: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
 
@@ -653,17 +656,17 @@ AddonDebugger.prototype = {
         };
 
         labelmap.set(l.value, source);
         group.sources.push(source);
       }
     }
 
     for (let source of sources) {
-      let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.url).attachment;
+      let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.actor).attachment;
 
       if (!groupmap.has(group)) {
         ok(false, "Saw a source group not in the UI: " + group);
         continue;
       }
 
       if (!groupmap.get(group).has(label)) {
         ok(false, "Saw a source label not in the UI: " + label);
@@ -771,23 +774,24 @@ function toggleBlackBoxing(aPanel, aSour
     ensureSourceIs(aPanel, aSource, true).then(clickBlackBoxButton);
   } else {
     clickBlackBoxButton();
   }
 
   return blackBoxChanged;
 }
 
-function selectSourceAndGetBlackBoxButton(aPanel, aSource) {
+function selectSourceAndGetBlackBoxButton(aPanel, aUrl) {
   function returnBlackboxButton() {
     return getBlackBoxButton(aPanel);
   }
 
-  aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource;
-  return ensureSourceIs(aPanel, aSource, true).then(returnBlackboxButton);
+  let sources = aPanel.panelWin.DebuggerView.Sources;
+  sources.selectedValue = getSourceActor(sources, aUrl);
+  return ensureSourceIs(aPanel, aUrl, true).then(returnBlackboxButton);
 }
 
 // Variables view inspection popup helpers
 
 function openVarPopup(aPanel, aCoords, aWaitForFetchedProperties) {
   let events = aPanel.panelWin.EVENTS;
   let editor = aPanel.panelWin.DebuggerView.editor;
   let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
@@ -977,8 +981,30 @@ function evalInTab(tab, string) {
 
 function sendMouseClickToTab(tab, target) {
   info("Sending mouse click to tab.");
 
   sendMessageToTab(tab, "test:click", undefined, {
     target: target
   });
 }
+
+// Source helpers
+
+function getSelectedSourceURL(aSources) {
+  return (aSources.selectedItem &&
+          aSources.selectedItem.attachment.source.url);
+}
+
+function getSourceURL(aSources, aActor) {
+  let item = aSources.getItemByValue(aActor);
+  return item && item.attachment.source.url;
+}
+
+function getSourceActor(aSources, aURL) {
+  let item = aSources.getItemForAttachment(a => a.source.url === aURL);
+  return item && item.value;
+}
+
+function getSourceForm(aSources, aURL) {
+  let item = aSources.getItemByValue(getSourceActor(gSources, aURL));
+  return item.attachment.source;
+}
--- a/browser/devtools/fontinspector/font-inspector.css
+++ b/browser/devtools/fontinspector/font-inspector.css
@@ -10,8 +10,16 @@
 #template {
   display: none;
 }
 
 .font.is-remote .font-is-remote,
 .font.is-local .font-is-local {
   display: inline;
 }
+
+.font-format::before {
+  content: "(";
+}
+
+.font-format::after {
+  content: ")";
+}
--- a/browser/devtools/fontinspector/font-inspector.js
+++ b/browser/devtools/fontinspector/font-inspector.js
@@ -4,19 +4,26 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+  "resource://gre/modules/devtools/Console.jsm");
+
 function FontInspector(inspector, window)
 {
   this.inspector = inspector;
+  this.pageStyle = this.inspector.pageStyle;
   this.chromeDoc = window.document;
   this.init();
 }
 
 FontInspector.prototype = {
   init: function FI_init() {
     this.update = this.update.bind(this);
     this.onNewNode = this.onNewNode.bind(this);
@@ -46,17 +53,16 @@ FontInspector.prototype = {
     this.showAllButton.removeEventListener("click", this.showAll);
   },
 
   /**
    * Selection 'new-node' event handler.
    */
   onNewNode: function FI_onNewNode() {
     if (this.isActive() &&
-        this.inspector.selection.isLocal() &&
         this.inspector.selection.isConnected() &&
         this.inspector.selection.isElementNode()) {
       this.undim();
       this.update();
     } else {
       this.dim();
     }
   },
@@ -71,130 +77,103 @@ FontInspector.prototype = {
 
   /**
    * Show the font list. A node is selected.
    */
   undim: function FI_undim() {
     this.chromeDoc.body.classList.remove("dim");
   },
 
-  /**
-   * Retrieve all the font related info we have for the selected
-   * node and display them.
-   */
-  update: function FI_update() {
-    if (!this.isActive() ||
+ /**
+  * Retrieve all the font info for the selected node and display it.
+  */
+  update: Task.async(function*() {
+    let node = this.inspector.selection.nodeFront;
+
+    if (!node ||
+        !this.isActive() ||
         !this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode() ||
         this.chromeDoc.body.classList.contains("dim")) {
       return;
     }
 
-    let node = this.inspector.selection.node;
-    let contentDocument = node.ownerDocument;
+    this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
+
+    let fillStyle = (Services.prefs.getCharPref("devtools.theme") == "light") ?
+        "black" : "white";
+    let options = {
+      includePreviews: true,
+      previewFillStyle: fillStyle
+    }
+
+    let fonts = yield this.pageStyle.getUsedFontFaces(node, options)
+                      .then(null, console.error);
+    if (!fonts) {
+      return;
+    }
 
-    // We don't get fonts for a node, but for a range
-    let rng = contentDocument.createRange();
-    rng.selectNodeContents(node);
-    let fonts = DOMUtils.getUsedFontFaces(rng);
-    let fontsArray = [];
-    for (let i = 0; i < fonts.length; i++) {
-      fontsArray.push(fonts.item(i));
+    for (let font of fonts) {
+      font.previewUrl = yield font.preview.data.string();
+    }
+
+    // in case we've been destroyed in the meantime
+    if (!this.chromeDoc) {
+      return;
     }
-    fontsArray = fontsArray.sort(function(a, b) {
-      return a.srcIndex < b.srcIndex;
-    });
+
+    // clear again in case an update got in right before us
     this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
-    for (let f of fontsArray) {
-      this.render(f, contentDocument);
+
+    for (let font of fonts) {
+      this.render(font);
     }
-  },
+
+    this.inspector.emit("fontinspector-updated");
+  }),
 
   /**
    * Display the information of one font.
    */
-  render: function FI_render(font, document) {
+  render: function FI_render(font) {
     let s = this.chromeDoc.querySelector("#template > section");
     s = s.cloneNode(true);
 
     s.querySelector(".font-name").textContent = font.name;
     s.querySelector(".font-css-name").textContent = font.CSSFamilyName;
-    s.querySelector(".font-format").textContent = font.format;
 
-    if (font.srcIndex == -1) {
+    if (font.URI) {
+      s.classList.add("is-remote");
+    } else {
       s.classList.add("is-local");
+    }
+
+    let formatElem = s.querySelector(".font-format");
+    if (font.format) {
+      formatElem.textContent = font.format;
     } else {
-      s.classList.add("is-remote");
+      formatElem.hidden = true;
     }
 
     s.querySelector(".font-url").value = font.URI;
 
-    let iframe = s.querySelector(".font-preview");
     if (font.rule) {
       // This is the @font-face{…} code.
-      let cssText = font.rule.style.parentRule.cssText;
+      let cssText = font.ruleText;
 
       s.classList.add("has-code");
       s.querySelector(".font-css-code").textContent = cssText;
-
-      // We guess the base URL of the stylesheet to make
-      // sure the font will be accessible in the preview.
-      // If the font-face is in an inline <style>, we get
-      // the location of the page.
-      let origin = font.rule.style.parentRule.parentStyleSheet.href;
-      if (!origin) { // Inline stylesheet
-        origin = document.location.href;
-      }
-      // We remove the last part of the URL to get a correct base.
-      let base = origin.replace(/\/[^\/]*$/,"/")
-
-      // From all this information, we build a preview.
-      this.buildPreview(iframe, font.CSSFamilyName, cssText, base);
-    } else {
-      this.buildPreview(iframe, font.CSSFamilyName, "", "");
     }
+    let preview = s.querySelector(".font-preview");
+    preview.src = font.previewUrl;
 
     this.chromeDoc.querySelector("#all-fonts").appendChild(s);
   },
 
   /**
-   * Show a preview of the font in an iframe.
-   */
-  buildPreview: function FI_buildPreview(iframe, name, cssCode, base) {
-    /* The HTML code of the preview is:
-     *   <!DOCTYPE HTML>
-     *   <head>
-     *    <base href="{base}"></base>
-     *   </head>
-     *   <style>
-     *   p {font-family: {name};}
-     *   * {font-size: 40px;line-height:60px;padding:0 10px;margin:0};
-     *   </style>
-     *   <p contenteditable spellcheck='false'>Abc</p>
-     */
-    let extraCSS = "* {padding:0;margin:0}";
-    extraCSS += ".theme-dark {color: white}";
-    extraCSS += "p {font-size: 40px;line-height:60px;padding:0 10px;margin:0;}";
-    cssCode += extraCSS;
-    let src = "data:text/html;charset=utf-8,<!DOCTYPE HTML><head><base></base></head><style></style><p contenteditable spellcheck='false'>Abc</p>";
-    iframe.addEventListener("load", function onload() {
-      iframe.removeEventListener("load", onload, true);
-      let doc = iframe.contentWindow.document;
-      // We could have done that earlier, but we want to avoid any URL-encoding
-      // nightmare.
-      doc.querySelector("base").href = base;
-      doc.querySelector("style").textContent = cssCode;
-      doc.querySelector("p").style.fontFamily = name;
-      // Forward theme
-      doc.documentElement.className = document.documentElement.className;
-    }, true);
-    iframe.src = src;
-  },
-
-  /**
    * Select the <body> to show all the fonts included in the document.
    */
   showAll: function FI_showAll() {
     if (!this.isActive() ||
         !this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       return;
     }
--- a/browser/devtools/fontinspector/font-inspector.xhtml
+++ b/browser/devtools/fontinspector/font-inspector.xhtml
@@ -19,24 +19,24 @@
   <body class="theme-sidebar devtools-monospace" role="application">
     <script type="application/javascript;version=1.8" src="font-inspector.js"></script>
     <div id="root">
       <ul id="all-fonts"></ul>
       <button id="showall">&showAllFonts;</button>
     </div>
     <div id="template">
       <section class="font">
-        <iframe sandbox="" class="font-preview"></iframe>
+        <img class="font-preview"></img>
         <div class="font-info">
           <h1 class="font-name"></h1>
           <span class="font-is-local">&system;</span>
           <span class="font-is-remote">&remote;</span>
           <p class="font-format-url">
             <input readonly="readonly" class="font-url"></input>
-            (<span class="font-format"></span>)
+            <span class="font-format"></span>
           </p>
           <p class="font-css">&usedAs; "<span class="font-css-name"></span>"</p>
           <pre class="font-css-code"></pre>
         </div>
       </section>
     </div>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/test/OstrichLicense.txt
@@ -0,0 +1,41 @@
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
+
+"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
+
+5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
\ No newline at end of file
--- a/browser/devtools/fontinspector/test/browser.ini
+++ b/browser/devtools/fontinspector/test/browser.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
-skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
 subsuite = devtools
 support-files =
-  browser_font.woff
   browser_fontinspector.html
+  ostrich-black.woff
+  ostrich-regular.woff
+  head.js
 
 [browser_fontinspector.js]
-skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
-
deleted file mode 100644
index e8440843b48adbc99a20c5e0a1642a36ca7178de..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/devtools/fontinspector/test/browser_fontinspector.html
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.html
@@ -1,20 +1,30 @@
 <!DOCTYPE html>
 
 <style>
   @font-face {
     font-family: bar;
-    src: url(bad/font/name.ttf), url(browser_font.woff) format("woff");
+    src: url(bad/font/name.ttf), url(ostrich-regular.woff) format("woff");
   }
+  @font-face {
+    font-family: bar;
+    font-weight: 800;
+    src: url(ostrich-black.woff);
+  }
+
   body{
     font-family:Arial;
   }
   div {
     font-family:Arial;
     font-family:bar;
   }
+  .bold-text {
+    font-family: bar;
+    font-weight: 800;
+  }
 </style>
 
 <body>
   BODY
   <div>DIV</div>
 </body>
--- a/browser/devtools/fontinspector/test/browser_fontinspector.js
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.js
@@ -1,130 +1,91 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let tempScope = {};
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 
-let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+let TEST_URI = "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_fontinspector.html";
 
-function test() {
-  waitForExplicitFinish();
+let view, viewDoc;
 
-  let doc;
-  let view;
-  let viewDoc;
-  let inspector;
+let test = asyncTest(function*() {
+  yield loadTab(TEST_URI);
+  let {toolbox, inspector} = yield openInspector();
 
-  gDevTools.testing = true;
-  SimpleTest.registerCleanupFunction(() => {
-    gDevTools.testing = false;
-  });
+  info("Selecting the test node");
+  yield selectNode("body", inspector);
 
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    doc = content.document;
-    waitForFocus(setupTest, content);
-  }, true);
+  let updated = inspector.once("fontinspector-updated");
+  inspector.sidebar.select("fontinspector");
+  yield updated;
 
-  content.location = "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_fontinspector.html";
+  info("Font Inspector ready");
 
-  function setupTest() {
-    let rng = doc.createRange();
-    rng.selectNode(doc.body);
-    let fonts = DOMUtils.getUsedFontFaces(rng);
-    if (fonts.length != 2) {
-      // Fonts are not loaded yet.
-      // Let try again in a couple of milliseconds (hacky, but
-      // there's not better way to do it. See bug 835247).
-      setTimeout(setupTest, 500);
-    } else {
-      let target = TargetFactory.forTab(gBrowser.selectedTab);
-      gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
-        openFontInspector(toolbox.getCurrentPanel());
-      });
-    }
-  }
+  view = inspector.sidebar.getWindowForTab("fontinspector");
+  viewDoc = view.document;
+
+  ok(!!view.fontInspector, "Font inspector document is alive.");
+
+  yield testBodyFonts(inspector);
+
+  yield testDivFonts(inspector);
 
-  function openFontInspector(aInspector) {
-    info("Inspector open");
-    inspector = aInspector;
-
-    inspector.selection.setNode(doc.body);
-    inspector.sidebar.select("fontinspector");
-    inspector.sidebar.once("fontinspector-ready", testBodyFonts);
-  }
+  yield testShowAllFonts(inspector);
 
-  function testBodyFonts() {
-    info("Font Inspector ready");
+  view = viewDoc = null;
+});
 
-    view = inspector.sidebar.getWindowForTab("fontinspector");
-    viewDoc = view.document;
-
-    ok(!!view.fontInspector, "Font inspector document is alive.");
-
-    let s = viewDoc.querySelectorAll("#all-fonts > section");
-    is(s.length, 2, "Found 2 fonts");
+function* testBodyFonts(inspector) {
+  let s = viewDoc.querySelectorAll("#all-fonts > section");
+  is(s.length, 2, "Found 2 fonts");
 
-    is(s[0].querySelector(".font-name").textContent,
-       "DeLarge Bold", "font 0: Right font name");
-    ok(s[0].classList.contains("is-remote"),
-       "font 0: is remote");
-    is(s[0].querySelector(".font-url").value,
-       "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_font.woff",
-       "font 0: right url");
-    is(s[0].querySelector(".font-format").textContent,
-       "woff", "font 0: right font format");
-    is(s[0].querySelector(".font-css-name").textContent,
-       "bar", "font 0: right css name");
+  // test first web font
+  is(s[0].querySelector(".font-name").textContent,
+     "Ostrich Sans Medium", "font 0: Right font name");
+  ok(s[0].classList.contains("is-remote"),
+     "font 0: is remote");
+  is(s[0].querySelector(".font-url").value,
+     "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/ostrich-regular.woff",
+     "font 0: right url");
+  is(s[0].querySelector(".font-format").textContent,
+     "woff", "font 0: right font format");
+  is(s[0].querySelector(".font-css-name").textContent,
+     "bar", "font 0: right css name");
 
-    let font1Name = s[1].querySelector(".font-name").textContent;
-    let font1CssName = s[1].querySelector(".font-css-name").textContent;
-
-    // On Linux test machines, the Arial font doesn't exist.
-    // The fallback is "Liberation Sans"
-
-    ok((font1Name == "Arial") || (font1Name == "Liberation Sans"),
-       "font 1: Right font name");
-    ok(s[1].classList.contains("is-local"), "font 1: is local");
-    ok((font1CssName == "Arial") || (font1CssName == "Liberation Sans"),
-       "Arial", "font 1: right css name");
-
-    testDivFonts();
-  }
+  // test system font
+  let font2Name = s[1].querySelector(".font-name").textContent;
+  let font2CssName = s[1].querySelector(".font-css-name").textContent;
 
-  function testDivFonts() {
-    inspector.selection.setNode(doc.querySelector("div"));
-    inspector.once("inspector-updated", () => {
-      let s = viewDoc.querySelectorAll("#all-fonts > section");
-      is(s.length, 1, "Found 1 font on DIV");
-      is(s[0].querySelector(".font-name").textContent, "DeLarge Bold",
-        "The DIV font has the right name");
+  // On Linux test machines, the Arial font doesn't exist.
+  // The fallback is "Liberation Sans"
+  ok((font2Name == "Arial") || (font2Name == "Liberation Sans"),
+     "font 1: Right font name");
+  ok(s[1].classList.contains("is-local"), "font 2: is local");
+  ok((font2CssName == "Arial") || (font2CssName == "Liberation Sans"),
+     "Arial", "font 2: right css name");
+}
 
-      testShowAllFonts();
-    });
-  }
+function* testDivFonts(inspector) {
+  let updated = inspector.once("fontinspector-updated");
+  yield selectNode("div", inspector);
+  yield updated;
 
-  function testShowAllFonts() {
-    viewDoc.querySelector("#showall").click();
-    inspector.once("inspector-updated", () => {
-      is(inspector.selection.node, doc.body, "Show all fonts selected the body node");
-      let s = viewDoc.querySelectorAll("#all-fonts > section");
-      is(s.length, 2, "And font-inspector still shows 2 fonts for body");
+  let sections1 = viewDoc.querySelectorAll("#all-fonts > section");
+  is(sections1.length, 1, "Found 1 font on DIV");
+  is(sections1[0].querySelector(".font-name").textContent, "Ostrich Sans Medium",
+    "The DIV font has the right name");
+}
 
-      finishUp();
-    });
-  }
+function* testShowAllFonts(inspector) {
+  info("testing showing all fonts");
 
-  function finishUp() {
-    executeSoon(function() {
-      gDevTools.once("toolbox-destroyed", () => {
-        doc = view = viewDoc = inspector = null;
-        gBrowser.removeCurrentTab();
-        finish();
-      });
-      inspector._toolbox.destroy();
-    });
-  }
+  let updated = inspector.once("fontinspector-updated");
+  viewDoc.querySelector("#showall").click();
+  yield updated;
+
+  is(inspector.selection.nodeFront.nodeName, "BODY", "Show all fonts selected the body node");
+  let sections = viewDoc.querySelectorAll("#all-fonts > section");
+  is(sections.length, 2, "And font-inspector still shows 2 fonts for body");
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/fontinspector/test/head.js
@@ -0,0 +1,144 @@
+ /* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let TargetFactory = devtools.TargetFactory;
+
+// All test are asynchronous
+waitForExplicitFinish();
+
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
+registerCleanupFunction(function*() {
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  yield gDevTools.closeToolbox(target);
+
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+});
+
+/**
+ * Define an async test based on a generator function
+ */
+function asyncTest(generator) {
+  return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
+}
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url The url to be loaded in the new tab
+ * @return a promise that resolves to the tab object when the url is loaded
+ */
+function loadTab(url) {
+  let deferred = promise.defer();
+
+  let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+  let browser = gBrowser.getBrowserForTab(tab);
+
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    deferred.resolve({tab: tab, browser: browser});
+  }, true);
+
+  return deferred.promise;
+}
+
+/**
+ * Open the toolbox, with the inspector tool visible.
+ * @param {Function} cb Optional callback, if you don't want to use the returned
+ * promise
+ * @return a promise that resolves when the inspector is ready
+ */
+let openInspector = Task.async(function*(cb) {
+  info("Opening the inspector");
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  let inspector, toolbox;
+
+  // Checking if the toolbox and the inspector are already loaded
+  // The inspector-updated event should only be waited for if the inspector
+  // isn't loaded yet
+  toolbox = gDevTools.getToolbox(target);
+  if (toolbox) {
+    inspector = toolbox.getPanel("inspector");
+    if (inspector) {
+      info("Toolbox and inspector already open");
+      if (cb) {
+        return cb(inspector, toolbox);
+      } else {
+        return {
+          toolbox: toolbox,
+          inspector: inspector
+        };
+      }
+    }
+  }
+
+  info("Opening the toolbox");
+  toolbox = yield gDevTools.showToolbox(target, "inspector");