merge m-c to fx-team
authorTim Taubert <tim.taubert@gmx.de>
Thu, 29 Mar 2012 11:44:00 +0200
changeset 93836 563ea372f00035e543029ed4f9a4f4ed37540e5b
parent 93823 93f86e0dd4427dc700daab228f7752d9c818aa10 (current diff)
parent 93835 5759f9c0d1eb11316d8044cff2dc7e0e4e6b888e (diff)
child 93920 ff3521bc655946ec545b49bc64bc7167de8ee709
child 93998 b1455bac2454968911912b94e195e284e4d251a7
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone14.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 m-c to fx-team
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -2085,19 +2085,16 @@ SessionStoreService.prototype = {
    * go through all tabs and store the current scroll positions
    * and innerHTML content of WYSIWYG editors
    * @param aWindow
    *        Window reference
    */
   _updateTextAndScrollData: function sss_updateTextAndScrollData(aWindow) {
     var browsers = aWindow.gBrowser.browsers;
     this._windows[aWindow.__SSi].tabs.forEach(function (tabData, i) {
-      if (browsers[i].__SS_data &&
-          browsers[i].__SS_tabStillLoading)
-        return; // ignore incompletely initialized tabs
       try {
         this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
       }
       catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
     }, this);
   },
 
   /**
@@ -2109,16 +2106,20 @@ SessionStoreService.prototype = {
    *        single browser reference
    * @param aTabData
    *        tabData object to add the information to
    * @param aFullData
    *        always return privacy sensitive data (use with care)
    */
   _updateTextAndScrollDataForTab:
     function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
+    // we shouldn't update data for incompletely initialized tabs
+    if (aBrowser.__SS_data && aBrowser.__SS_tabStillLoading)
+      return;
+
     var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
     // entry data needn't exist for tabs just initialized with an incomplete session state
     if (!aTabData.entries[tabIndex])
       return;
     
     let selectedPageStyle = aBrowser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
                             this._getSelectedPageStyle(aBrowser.contentWindow);
     if (selectedPageStyle)
@@ -2729,32 +2730,43 @@ SessionStoreService.prototype = {
         tabbrowser.unpinTab(tabbrowser.tabs[t]);
     }
 
     // make sure that the selected tab won't be closed in order to
     // prevent unnecessary flickering
     if (aOverwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
       tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1);
 
+    let numVisibleTabs = 0;
+
     for (var t = 0; t < newTabCount; t++) {
       tabs.push(t < openTabCount ?
                 tabbrowser.tabs[t] :
                 tabbrowser.addTab("about:blank", {skipAnimation: true}));
       // when resuming at startup: add additionally requested pages to the end
       if (!aOverwriteTabs && root._firstTabs) {
         tabbrowser.moveTabTo(tabs[t], t);
       }
 
       if (winData.tabs[t].pinned)
         tabbrowser.pinTab(tabs[t]);
 
-      if (winData.tabs[t].hidden)
+      if (winData.tabs[t].hidden) {
         tabbrowser.hideTab(tabs[t]);
-      else
+      }
+      else {
         tabbrowser.showTab(tabs[t]);
+        numVisibleTabs++;
+      }
+    }
+
+    // if all tabs to be restored are hidden, make the first one visible
+    if (!numVisibleTabs && winData.tabs.length) {
+      winData.tabs[0].hidden = false;
+      tabbrowser.showTab(tabs[0]);
     }
 
     // If overwriting tabs, we want to reset each tab's "restoring" state. Since
     // we're overwriting those tabs, they should no longer be restoring. The
     // tabs will be rebuilt and marked if they need to be restored after loading
     // state (in restoreHistoryPrecursor).
     if (aOverwriteTabs) {
       for (let i = 0; i < tabbrowser.tabs.length; i++) {
@@ -2874,20 +2886,17 @@ SessionStoreService.prototype = {
       // this is normally done in restoreHistory() but as we're returning early
       // here we need to take care of it.
       this._setWindowStateReady(aWindow);
       return;
     }
 
     let unhiddenTabs = aTabData.filter(function (aData) !aData.hidden).length;
 
-    // if all tabs to be restored are hidden, make the first one visible
-    if (unhiddenTabs == 0) {
-      aTabData[0].hidden = false;
-    } else if (aTabs.length > 1) {
+    if (unhiddenTabs && aTabs.length > 1) {
       // Load hidden tabs last, by pushing them to the end of the list
       for (let t = 0, tabsToReorder = aTabs.length - unhiddenTabs; tabsToReorder > 0; ) {
         if (aTabData[t].hidden) {
           aTabs = aTabs.concat(aTabs.splice(t, 1));
           aTabData = aTabData.concat(aTabData.splice(t, 1));
           if (aSelectTab > t)
             --aSelectTab;
           --tabsToReorder;
--- a/browser/components/sessionstore/test/Makefile.in
+++ b/browser/components/sessionstore/test/Makefile.in
@@ -154,18 +154,20 @@ include $(topsrcdir)/config/rules.mk
 	browser_645428.js \
 	browser_659591.js \
 	browser_662812.js \
 	browser_665702-state_session.js \
 	browser_682507.js \
 	browser_687710.js \
 	browser_687710_2.js \
 	browser_694378.js \
+	browser_701377.js \
 	browser_705597.js \
 	browser_707862.js \
+	browser_739805.js \
 	$(NULL)
 
 ifneq ($(OS_ARCH),Darwin)
 _BROWSER_TEST_FILES += \
 	browser_597071.js \
 	browser_625016.js \
 	$(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_701377.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let state = {windows:[{tabs:[
+  {entries:[{url:"http://example.com#1"}]},
+  {entries:[{url:"http://example.com#2"}], hidden: true}
+]}]};
+
+function test() {
+  waitForExplicitFinish();
+
+  newWindowWithState(state, function (aWindow) {
+    let tab = aWindow.gBrowser.tabs[1];
+    ok(tab.hidden, "the second tab is hidden");
+
+    let tabShown = false;
+    let tabShowCallback = function () tabShown = true;
+    tab.addEventListener("TabShow", tabShowCallback, false);
+
+    let tabState = ss.getTabState(tab);
+    ss.setTabState(tab, tabState);
+
+    tab.removeEventListener("TabShow", tabShowCallback, false);
+    ok(tab.hidden && !tabShown, "tab remains hidden");
+
+    finish();
+  });
+}
+
+// ----------
+function whenWindowLoaded(aWindow, aCallback) {
+  aWindow.addEventListener("load", function onLoad() {
+    aWindow.removeEventListener("load", onLoad, false);
+    executeSoon(aCallback);
+  }, false);
+}
+
+// ----------
+function newWindowWithState(aState, aCallback) {
+  let opts = "chrome,all,dialog=no,height=800,width=800";
+  let win = window.openDialog(getBrowserURL(), "_blank", opts);
+
+  registerCleanupFunction(function () win.close());
+
+  whenWindowLoaded(win, function () {
+    ss.setWindowState(win, JSON.stringify(aState), true);
+    executeSoon(function () aCallback(win));
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_739805.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TAB_STATE_NEEDS_RESTORE = 1;
+
+let tabState = {
+  entries: [{url: "data:text/html,<input%20id='foo'>", formdata: {"#foo": "bar"}}]
+};
+
+function test() {
+  waitForExplicitFinish();
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+  registerCleanupFunction(function () {
+    if (gBrowser.tabs.length > 1)
+      gBrowser.removeTab(gBrowser.tabs[1]);
+    Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+  });
+
+  let tab = gBrowser.addTab("about:blank");
+  let browser = tab.linkedBrowser;
+
+  whenBrowserLoaded(browser, function () {
+    isnot(gBrowser.selectedTab, tab, "newly created tab is not selected");
+
+    ss.setTabState(tab, JSON.stringify(tabState));
+    is(browser.__SS_restoreState, TAB_STATE_NEEDS_RESTORE, "tab needs restoring");
+
+    let state = JSON.parse(ss.getTabState(tab));
+    let formdata = state.entries[0].formdata;
+    is(formdata && formdata["#foo"], "bar", "tab state's formdata is valid");
+
+    whenTabRestored(tab, function () {
+      let input = browser.contentDocument.getElementById("foo");
+      is(input.value, "bar", "formdata has been restored correctly");
+      finish();
+    });
+
+    // Restore the tab by selecting it.
+    gBrowser.selectedTab = tab;
+  });
+}
+
+function whenBrowserLoaded(aBrowser, aCallback) {
+  aBrowser.addEventListener("load", function onLoad() {
+    aBrowser.removeEventListener("load", onLoad, true);
+    executeSoon(aCallback);
+  }, true);
+}
+
+function whenTabRestored(aTab, aCallback) {
+  aTab.addEventListener("SSTabRestored", function onRestored() {
+    aTab.removeEventListener("SSTabRestored", onRestored);
+    executeSoon(aCallback);
+  });
+}
--- a/browser/components/tabview/storage.js
+++ b/browser/components/tabview/storage.js
@@ -119,16 +119,30 @@ let Storage = {
       // getTabValue will fail if the property doesn't exist.
       Utils.log(e);
     }
 
     return existingData;
   },
 
   // ----------
+  // Function: getTabState
+  // Returns the current state of the given tab.
+  getTabState: function Storage_getTabState(tab) {
+    Utils.assert(tab, "tab");
+    let tabState;
+
+    try {
+      tabState = JSON.parse(this._sessionStore.getTabState(tab));
+    } catch (e) {}
+
+    return tabState;
+  },
+
+  // ----------
   // Function: saveGroupItem
   // Saves the data for a single groupItem, associated with a specific window.
   saveGroupItem: function Storage_saveGroupItem(win, data) {
     var id = data.id;
     var existingData = this.readGroupItemData(win);
     existingData[id] = data;
     this._sessionStore.setWindowValue(win, this.GROUP_DATA_IDENTIFIER,
       JSON.stringify(existingData));
--- a/browser/components/tabview/tabitems.js
+++ b/browser/components/tabview/tabitems.js
@@ -91,17 +91,16 @@ function TabItem(tab, options) {
   this.defaultSize = new Point(TabItems.tabWidth, TabItems.tabHeight);
   this._hidden = false;
   this.isATabItem = true;
   this.keepProportional = true;
   this._hasBeenDrawn = false;
   this._reconnected = false;
   this.isDragging = false;
   this.isStacked = false;
-  this.url = "";
 
   // Read off the total vertical and horizontal padding on the tab container
   // and cache this value, as it must be the same for every TabItem.
   if (Utils.isEmptyObject(TabItems.tabItemPadding)) {
     TabItems.tabItemPadding.x = parseInt($div.css('padding-left'))
         + parseInt($div.css('padding-right'));
 
     TabItems.tabItemPadding.y = parseInt($div.css('padding-top'))
@@ -195,31 +194,24 @@ TabItem.prototype = Utils.extend(new Ite
   },
 
   // ----------
   // Function: showCachedData
   // Shows the cached data i.e. image and title.  Note: this method should only
   // be called at browser startup with the cached data avaliable.
   //
   // Parameters:
-  //   tabData - the tab data
   //   imageData - the image data
-  showCachedData: function TabItem_showCachedData(tabData, imageData) {
+  showCachedData: function TabItem_showCachedData(imageData) {
     this._cachedImageData = imageData;
     this.$cachedThumb.attr("src", this._cachedImageData).show();
     this.$canvas.css({opacity: 0});
-    let label = "";
-    let title;
-    if (tabData.title) {
-      label = tabData.title;
-      title = label + "\n" + tabData.url;
-    } else {
-      title = tabData.url;
-    }
-    this.$tabTitle.text(label).attr("title", title);
+
+    let {title, url} = this.getTabState();
+    this.$tabTitle.text(title).attr("title", title ? title + "\n" + url : url);
 
     this._sendToSubscribers("showingCachedData");
   },
 
   // ----------
   // Function: hideCachedData
   // Hides the cached data i.e. image and title and show the canvas.
   hideCachedData: function TabItem_hideCachedData() {
@@ -229,19 +221,17 @@ TabItem.prototype = Utils.extend(new Ite
       this._cachedImageData = null;
   },
 
   // ----------
   // Function: getStorageData
   // Get data to be used for persistent storage of this object.
   getStorageData: function TabItem_getStorageData() {
     let data = {
-      url: this.tab.linkedBrowser.currentURI.spec,
-      groupID: (this.parent ? this.parent.id : 0),
-      title: this.tab.label
+      groupID: (this.parent ? this.parent.id : 0)
     };
     if (this.parent && this.parent.getActiveTab() == this)
       data.active = true;
 
     return data;
   },
 
   // ----------
@@ -256,21 +246,55 @@ TabItem.prototype = Utils.extend(new Ite
       if (TabItems.storageSanity(data))
         Storage.saveTab(this.tab, data);
     } catch(e) {
       Utils.log("Error in saving tab value: "+e);
     }
   },
 
   // ----------
+  // Function: _getCurrentTabStateEntry
+  // Returns the current tab state's active history entry.
+  _getCurrentTabStateEntry: function TabItem__getCurrentTabStateEntry() {
+    let tabState = Storage.getTabState(this.tab);
+
+    if (tabState) {
+      let index = (tabState.index || tabState.entries.length) - 1;
+      if (index in tabState.entries)
+        return tabState.entries[index];
+    }
+
+    return null;
+  },
+
+  // ----------
+  // Function: getTabState
+  // Returns the current tab state, i.e. the title and URL of the active
+  // history entry.
+  getTabState: function TabItem_getTabState() {
+    let entry = this._getCurrentTabStateEntry();
+    let title = "";
+    let url = "";
+
+    if (entry) {
+      if (entry.title)
+        title = entry.title;
+
+      url = entry.url;
+    } else {
+      url = this.tab.linkedBrowser.currentURI.spec;
+    }
+
+    return {title: title, url: url};
+  },
+
+  // ----------
   // Function: loadThumbnail
   // Loads the tabItems thumbnail.
-  loadThumbnail: function TabItem_loadThumbnail(tabData) {
-    Utils.assert(tabData, "invalid or missing argument <tabData>");
-
+  loadThumbnail: function TabItem_loadThumbnail() {
     let self = this;
 
     function TabItem_loadThumbnail_callback(error, imageData) {
       // we could have been unlinked while waiting for the thumbnail to load
       if (!self.tab)
         return;
 
       if (error || !imageData) {
@@ -280,21 +304,21 @@ TabItem.prototype = Utils.extend(new Ite
       }
 
       self._sendToSubscribers("loadedCachedImageData");
 
       // If we have a cached image, then show it if the loaded URL matches
       // what the cache is from, OR the loaded URL is blank, which means
       // that the page hasn't loaded yet.
       let currentUrl = self.tab.linkedBrowser.currentURI.spec;
-      if (tabData.url == currentUrl || currentUrl == "about:blank")
-        self.showCachedData(tabData, imageData);
+      if (self.getTabState().url == currentUrl || currentUrl == "about:blank")
+        self.showCachedData(imageData);
     }
 
-    ThumbnailStorage.loadThumbnail(tabData.url, TabItem_loadThumbnail_callback);
+    ThumbnailStorage.loadThumbnail(this.getTabState().url, TabItem_loadThumbnail_callback);
   },
 
   // ----------
   // Function: saveThumbnail
   // Saves the tabItems thumbnail.
   saveThumbnail: function TabItem_saveThumbnail(options) {
     if (!this.tabCanvas)
       return;
@@ -365,17 +389,17 @@ TabItem.prototype = Utils.extend(new Ite
   _reconnect: function TabItem__reconnect(options) {
     Utils.assertThrow(!this._reconnected, "shouldn't already be reconnected");
     Utils.assertThrow(this.tab, "should have a xul:tab");
 
     let tabData = Storage.getTabData(this.tab);
     let groupItem;
 
     if (tabData && TabItems.storageSanity(tabData)) {
-      this.loadThumbnail(tabData);
+      this.loadThumbnail();
 
       if (this.parent)
         this.parent.remove(this, {immediately: true});
 
       if (tabData.groupID)
         groupItem = GroupItems.groupItem(tabData.groupID);
       else
         groupItem = new GroupItem([], {immediately: true, bounds: tabData.bounds});
@@ -929,17 +953,17 @@ let TabItems = {
     // If our readyState is complete, but we're showing about:blank,
     // and we're not loading about:blank, it means we haven't really
     // started loading. This can happen to the first few tabs in a
     // page.
     Utils.assertThrow(tab, "tab");
     return (
       tab.linkedBrowser.contentDocument.readyState == 'complete' &&
       !(tab.linkedBrowser.contentDocument.URL == 'about:blank' &&
-        tab._tabViewTabItem.url != 'about:blank')
+        tab._tabViewTabItem.getTabState().url != 'about:blank')
     );
   },
 
   // ----------
   // Function: update
   // Takes in a xul:tab.
   update: function TabItems_update(tab) {
     try {
@@ -1008,20 +1032,16 @@ let TabItems = {
         $name.text(label);
 
       // ___ remove from waiting list now that we have no other
       // early returns
       this._tabsWaitingForUpdate.remove(tab);
 
       // ___ URL
       let tabUrl = tab.linkedBrowser.currentURI.spec;
-      if (tabUrl != tabItem.url) {
-        tabItem.url = tabUrl;
-        tabItem.save();
-      }
       tabItem.$container.attr("title", label + "\n" + tabUrl);
 
       // ___ Make sure the tab is complete and ready for updating.
       if (!this.isComplete(tab) && (!options || !options.force)) {
         // If it's incomplete, stick it on the end of the queue
         this._tabsWaitingForUpdate.push(tab);
         return;
       }
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -72,30 +72,32 @@ include $(topsrcdir)/config/rules.mk
 	browser_dbg_script-switching.js \
 	browser_dbg_pause-resume.js \
 	browser_dbg_update-editor-mode.js \
 	$(warning browser_dbg_select-line.js temporarily disabled due to oranges, see bug 726609) \
 	browser_dbg_clean-exit.js \
 	browser_dbg_bug723069_editor-breakpoints.js \
 	browser_dbg_bug731394_editor-contextmenu.js \
 	browser_dbg_displayName.js \
+	browser_dbg_iframes.js \
 	head.js \
 	$(NULL)
 
 _BROWSER_TEST_PAGES = \
 	browser_dbg_tab1.html \
 	browser_dbg_tab2.html \
 	browser_dbg_debuggerstatement.html \
 	browser_dbg_stack.html \
 	browser_dbg_script-switching.html \
 	test-script-switching-01.js \
 	test-script-switching-02.js \
 	browser_dbg_frame-parameters.html \
 	browser_dbg_update-editor-mode.html \
 	test-editor-mode \
 	browser_dbg_displayName.html \
+	browser_dbg_iframes.html \
 	$(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs:: $(_BROWSER_TEST_PAGES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_iframes.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head><title>Browser Debugger IFrame Test Tab</title>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+
+<body>
+  <iframe src="browser_dbg_debuggerstatement.html"></iframe>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_iframes.js
@@ -0,0 +1,58 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that iframes can be added as debuggees.
+
+var gPane = null;
+var gTab = null;
+
+const TEST_URL = EXAMPLE_URL + "browser_dbg_iframes.html";
+
+function test() {
+  debug_tab_pane(TEST_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gPane = aPane;
+    let gDebugger = gPane.debuggerWindow;
+
+    is(gDebugger.StackFrames.activeThread.paused, false,
+      "Should be running after debug_tab_pane.");
+
+    gPane.activeThread.addOneTimeListener("framesadded", function() {
+      Services.tm.currentThread.dispatch({ run: function() {
+
+        let frames = gDebugger.DebuggerView.Stackframes._frames;
+        let childNodes = frames.childNodes;
+
+        is(gDebugger.StackFrames.activeThread.paused, true,
+          "Should be paused after an interrupt request.");
+
+        is(frames.querySelectorAll(".dbg-stackframe").length, 1,
+          "Should have one frame in the stack.");
+
+        gPane.activeThread.addOneTimeListener("resumed", function() {
+          Services.tm.currentThread.dispatch({ run: function() {
+            closeDebuggerAndFinish(gTab);
+          }}, 0);
+        });
+
+        EventUtils.sendMouseEvent({ type: "click" },
+          gDebugger.document.getElementById("resume"),
+          gDebugger);
+      }}, 0);
+    });
+
+    let iframe = gTab.linkedBrowser.contentWindow.wrappedJSObject.frames[0];
+
+    is(iframe.document.title, "Browser Debugger Test Tab", "Found the iframe");
+
+    iframe.runDebuggerStatement();
+  });
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+});
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -1006,32 +1006,52 @@ InspectorUI.prototype = {
     this.nodeChanged(this.ruleViewObject);
   },
 
   /**
    * When a css link is clicked this method is called in order to either:
    *   1. Open the link in view source (for element style attributes)
    *   2. Open the link in the style editor
    *
+   *   Like the style editor, we only view stylesheets contained in
+   *   document.styleSheets.
+   *
    * @param aEvent The event containing the style rule to act on
    */
   ruleViewCSSLinkClicked: function(aEvent)
   {
     if (!this.chromeWin) {
       return;
     }
 
     let rule = aEvent.detail.rule;
     let styleSheet = rule.sheet;
+    let doc = this.chromeWin.content.document;
+    let styleSheets = doc.styleSheets;
+    let contentSheet = false;
+    let line = rule.ruleLine || 0;
 
-    if (styleSheet) {
-      this.chromeWin.StyleEditor.openChrome(styleSheet, rule.ruleLine);
+    // Array.prototype.indexOf always returns -1 here so we loop through
+    // the styleSheets object instead.
+    for each (let sheet in styleSheets) {
+      if (sheet == styleSheet) {
+        contentSheet = true;
+        break;
+      }
+    }
+
+    if (contentSheet)  {
+      this.chromeWin.StyleEditor.openChrome(styleSheet, line);
     } else {
-      let href = rule.elementStyle.element.ownerDocument.location.href;
-      this.chromeWin.openUILinkIn("view-source:" + href, "window");
+      let href = styleSheet ? styleSheet.href : "";
+      if (rule.elementStyle.element) {
+        href = rule.elementStyle.element.ownerDocument.location.href;
+      }
+      let viewSourceUtils = this.chromeWin.gViewSourceUtils;
+      viewSourceUtils.viewSource(href, null, doc, line);
     }
   },
 
   /**
    * This is the mousedown handler for the rule view. We use it to track whether
    * text is currently getting selected.
    * .
    * @param aEvent The event object
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -482,17 +482,18 @@ var Scratchpad = {
 
   /**
    * Write out an error at the current insertion point as a block comment
    * @param object aValue
    *        The Error object to write out the message and stack trace
    */
   writeAsErrorComment: function SP_writeAsErrorComment(aError)
   {
-    let newComment = "Exception: " + aError.message + "\n" + aError.stack.substring(0, aError.stack.length - 1);
+    let stack = aError.stack || aError.fileName + ":" + aError.lineNumber;
+    let newComment = "Exception: " + aError.message + "\n" + stack.replace(/\n$/, "");
     
     this.writeAsComment(newComment);
   },
 
   /**
    * Open the Property Panel to inspect the given object.
    *
    * @param string aEvalString
--- a/browser/devtools/styleinspector/CssHtmlTree.jsm
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -1206,23 +1206,50 @@ SelectorView.prototype = {
     }
   },
 
   /**
    * When a css link is clicked this method is called in order to either:
    *   1. Open the link in view source (for element style attributes).
    *   2. Open the link in the style editor.
    *
+   *   Like the style editor, we only view stylesheets contained in
+   *   document.styleSheets inside the style editor.
+   *
    * @param aEvent The click event
    */
   openStyleEditor: function(aEvent)
   {
-    if (this.selectorInfo.selector._cssRule._cssSheet) {
-      let styleSheet = this.selectorInfo.selector._cssRule._cssSheet.domSheet;
-      let line = this.selectorInfo.ruleLine;
+    let rule = this.selectorInfo.selector._cssRule;
+    let doc = this.tree.win.content.document;
+    let line = this.selectorInfo.ruleLine || 0;
+    let cssSheet = rule._cssSheet;
+    let contentSheet = false;
+    let styleSheet;
+    let styleSheets;
+
+    if (cssSheet) {
+      styleSheet = cssSheet.domSheet;
+      styleSheets = doc.styleSheets;
 
+      // Array.prototype.indexOf always returns -1 here so we loop through
+      // the styleSheets array instead.
+      for each (let sheet in styleSheets) {
+        if (sheet == styleSheet) {
+          contentSheet = true;
+          break;
+        }
+      }
+    }
+
+    if (contentSheet) {
       this.tree.win.StyleEditor.openChrome(styleSheet, line);
     } else {
-      let href = this.selectorInfo.sourceElement.ownerDocument.location.href;
-      this.tree.win.openUILinkIn("view-source:" + href, "window");
+      let href = styleSheet ? styleSheet.href : "";
+      let viewSourceUtils = this.tree.win.gViewSourceUtils;
+
+      if (this.selectorInfo.sourceElement) {
+        href = this.selectorInfo.sourceElement.ownerDocument.location.href;
+      }
+      viewSourceUtils.viewSource(href, null, doc, line);
     }
   },
 };
--- a/browser/devtools/styleinspector/test/Makefile.in
+++ b/browser/devtools/styleinspector/test/Makefile.in
@@ -62,16 +62,18 @@ include $(topsrcdir)/config/rules.mk
   browser_ruleview_ui.js \
   browser_ruleview_focus.js \
   browser_bug705707_is_content_stylesheet.js \
   browser_bug722196_property_view_media_queries.js \
   browser_bug722196_rule_view_media_queries.js \
   browser_bug_592743_specificity.js \
   browser_ruleview_bug_703643_context_menu_copy.js \
   browser_computedview_bug_703643_context_menu_copy.js \
+  browser_ruleview_734259_style_editor_link.js \
+  browser_computedview_734259_style_editor_link.js \
   head.js \
   $(NULL)
 
 _BROWSER_TEST_PAGES = \
   browser_bug683672.html \
   browser_bug705707_is_content_stylesheet.html \
   browser_bug705707_is_content_stylesheet_imported.css \
   browser_bug705707_is_content_stylesheet_imported2.css \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
@@ -0,0 +1,161 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let doc;
+let win;
+let stylePanel;
+
+function createDocument()
+{
+  doc.body.innerHTML = '<style type="text/css"> ' +
+    'html { color: #000000; } ' +
+    'span { font-variant: small-caps; color: #000000; } ' +
+    '.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
+    'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
+    '<h1>Some header text</h1>\n' +
+    '<p id="salutation" style="font-size: 12pt">hi.</p>\n' +
+    '<p id="body" style="font-size: 12pt">I am a test-case. This text exists ' +
+    'solely to provide some things to <span style="color: yellow">' +
+    'highlight</span> and <span style="font-weight: bold">count</span> ' +
+    'style list-items in the box at right. If you are reading this, ' +
+    'you should go do something else instead. Maybe read a book. Or better ' +
+    'yet, write some test-cases for another bit of code. ' +
+    '<span style="font-style: italic">some text</span></p>\n' +
+    '<p id="closing">more text</p>\n' +
+    '<p>even more text</p>' +
+    '</div>';
+  doc.title = "Rule view style editor link test";
+
+  let span = doc.querySelector("span");
+  ok(span, "captain, we have the span");
+
+  stylePanel = new StyleInspector(window);
+  Services.obs.addObserver(testInlineStyle, "StyleInspector-populated", false);
+  stylePanel.createPanel(false, function() {
+    stylePanel.open(span);
+  });
+}
+
+function testInlineStyle()
+{
+  Services.obs.removeObserver(testInlineStyle, "StyleInspector-populated", false);
+
+  ok(stylePanel.isOpen(), "style inspector is open");
+
+  info("expanding property");
+  expandProperty(0, function propertyExpanded() {
+    Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
+      if (aTopic != "domwindowopened") {
+        return;
+      }
+      info("window opened");
+      win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+      win.addEventListener("load", function windowLoad() {
+        win.removeEventListener("load", windowLoad);
+        info("window load completed");
+        let windowType = win.document.documentElement.getAttribute("windowtype");
+        is(windowType, "navigator:view-source", "view source window is open");
+        info("closing window");
+        win.close();
+        Services.ww.unregisterNotification(onWindow);
+        testInlineStyleSheet();
+      });
+    });
+    let link = getLinkByIndex(0);
+    link.click();
+  });
+}
+
+function testInlineStyleSheet()
+{
+  info("clicking an inline stylesheet");
+
+  Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
+    if (aTopic != "domwindowopened") {
+      return;
+    }
+    info("window opened");
+    win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+    win.addEventListener("load", function windowLoad() {
+      win.removeEventListener("load", windowLoad);
+      info("window load completed");
+      let windowType = win.document.documentElement.getAttribute("windowtype");
+      is(windowType, "Tools:StyleEditor", "style editor window is open");
+
+      win.styleEditorChrome.addChromeListener({
+        onEditorAdded: function checkEditor(aChrome, aEditor) {
+          if (!aEditor.sourceEditor) {
+            aEditor.addActionListener({
+              onAttach: function (aEditor) {
+                aEditor.removeActionListener(this);
+                validateStyleEditorSheet(aEditor);
+              }
+            });
+          } else {
+            validateStyleEditorSheet(aEditor);
+          }
+        }
+      });
+      Services.ww.unregisterNotification(onWindow);
+    });
+  });
+  let link = getLinkByIndex(1);
+  link.click();
+}
+
+function validateStyleEditorSheet(aEditor)
+{
+  info("validating style editor stylesheet");
+
+  let sheet = doc.styleSheets[0];
+  is(aEditor.styleSheet, sheet, "loaded stylesheet matches document stylesheet");
+  info("closing window");
+  win.close();
+
+  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
+  stylePanel.close();
+}
+
+function expandProperty(aIndex, aCallback)
+{
+  let iframe = stylePanel.iframe;
+  let contentDoc = iframe.contentDocument;
+  let contentWindow = iframe.contentWindow;
+  let expando = contentDoc.querySelectorAll(".expandable")[aIndex];
+  expando.click();
+
+  // We use executeSoon to give the property time to expand.
+  executeSoon(aCallback);
+}
+
+function getLinkByIndex(aIndex)
+{
+  let contentDoc = stylePanel.iframe.contentDocument;
+  let links = contentDoc.querySelectorAll(".rule-link .link");
+  return links[aIndex];
+}
+
+function finishUp()
+{
+  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
+  ok(!stylePanel.isOpen(), "style inspector is closed");
+  doc = win = stylePanel = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
+      true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,<p>Computed view style editor link test</p>";
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
@@ -0,0 +1,181 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let win;
+let doc;
+let contentWindow;
+
+let tempScope = {};
+Cu.import("resource:///modules/Services.jsm", tempScope);
+let Services = tempScope.Services;
+
+function createDocument()
+{
+  doc.body.innerHTML = '<style type="text/css"> ' +
+    'html { color: #000000; } ' +
+    'span { font-variant: small-caps; color: #000000; } ' +
+    '.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
+    'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
+    '<h1>Some header text</h1>\n' +
+    '<p id="salutation" style="font-size: 12pt">hi.</p>\n' +
+    '<p id="body" style="font-size: 12pt">I am a test-case. This text exists ' +
+    'solely to provide some things to <span style="color: yellow">' +
+    'highlight</span> and <span style="font-weight: bold">count</span> ' +
+    'style list-items in the box at right. If you are reading this, ' +
+    'you should go do something else instead. Maybe read a book. Or better ' +
+    'yet, write some test-cases for another bit of code. ' +
+    '<span style="font-style: italic">some text</span></p>\n' +
+    '<p id="closing">more text</p>\n' +
+    '<p>even more text</p>' +
+    '</div>';
+  doc.title = "Rule view style editor link test";
+
+  openInspector();
+}
+
+function openInspector()
+{
+  ok(window.InspectorUI, "InspectorUI variable exists");
+  ok(!InspectorUI.inspecting, "Inspector is not highlighting");
+  ok(InspectorUI.store.isEmpty(), "Inspector.store is empty");
+
+  Services.obs.addObserver(inspectorUIOpen,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+  InspectorUI.openInspectorUI();
+}
+
+function inspectorUIOpen()
+{
+  Services.obs.removeObserver(inspectorUIOpen,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+
+  // Make sure the inspector is open.
+  ok(InspectorUI.inspecting, "Inspector is highlighting");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
+  ok(!InspectorUI.store.isEmpty(), "InspectorUI.store is not empty");
+  is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
+
+  // Highlight a node.
+  let div = content.document.getElementsByTagName("div")[0];
+  InspectorUI.inspectNode(div);
+  InspectorUI.stopInspecting();
+  is(InspectorUI.selection, div, "selection matches the div element");
+
+  Services.obs.addObserver(testInlineStyle,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+
+  InspectorUI.showSidebar();
+  InspectorUI.openRuleView();
+}
+
+function testInlineStyle()
+{
+  Services.obs.removeObserver(testInlineStyle,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+
+  executeSoon(function() {
+    info("clicking an inline style");
+
+    Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
+      if (aTopic != "domwindowopened") {
+        return;
+      }
+
+      win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+      win.addEventListener("load", function windowLoad() {
+        win.removeEventListener("load", windowLoad);
+        let windowType = win.document.documentElement.getAttribute("windowtype");
+        is(windowType, "navigator:view-source", "view source window is open");
+        win.close();
+        Services.ww.unregisterNotification(onWindow);
+        testInlineStyleSheet();
+      });
+    });
+    EventUtils.synthesizeMouseAtCenter(getLinkByIndex(0), { }, contentWindow);
+  });
+}
+
+function testInlineStyleSheet()
+{
+  info("clicking an inline stylesheet");
+
+  Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
+    if (aTopic != "domwindowopened") {
+      return;
+    }
+
+    win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+    win.addEventListener("load", function windowLoad() {
+      win.removeEventListener("load", windowLoad);
+
+      let windowType = win.document.documentElement.getAttribute("windowtype");
+      is(windowType, "Tools:StyleEditor", "style editor window is open");
+
+      win.styleEditorChrome.addChromeListener({
+        onEditorAdded: function checkEditor(aChrome, aEditor) {
+          if (!aEditor.sourceEditor) {
+            aEditor.addActionListener({
+              onAttach: function (aEditor) {
+                aEditor.removeActionListener(this);
+                validateStyleEditorSheet(aEditor);
+              }
+            });
+          } else {
+            validateStyleEditorSheet(aEditor);
+          }
+        }
+      });
+
+      Services.ww.unregisterNotification(onWindow);
+    });
+  });
+
+  EventUtils.synthesizeMouse(getLinkByIndex(1), 5, 5, { }, contentWindow);
+}
+
+function validateStyleEditorSheet(aEditor)
+{
+  info("validating style editor stylesheet");
+
+  let sheet = doc.styleSheets[0];
+
+  is(aEditor.styleSheet, sheet, "loaded stylesheet matches document stylesheet");
+  win.close();
+
+  finishup();
+}
+
+function getLinkByIndex(aIndex)
+{
+  let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
+  let contentDoc = ruleView.contentDocument;
+  let links = contentDoc.querySelectorAll(".ruleview-rule-source");
+  contentWindow = ruleView.contentWindow;
+  return links[aIndex];
+}
+
+function finishup()
+{
+  InspectorUI.hideSidebar();
+  InspectorUI.closeInspectorUI();
+  gBrowser.removeCurrentTab();
+  doc = contentWindow = win = null;
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
+      true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,<p>Rule view style editor link test</p>";
+}
--- a/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
@@ -182,16 +182,17 @@ function checkClipboardData(aExpectedPat
 {
   let actual = SpecialPowers.getClipboardData("text/unicode");
   let expectedRegExp = new RegExp(aExpectedPattern, "g");
   return expectedRegExp.test(actual);
 }
 
 function finishup()
 {
+  InspectorUI.hideSidebar();
   InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
   doc = null;
   finish();
 }
 
 function test()
 {
--- a/browser/devtools/styleinspector/test/browser_ruleview_focus.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_focus.js
@@ -72,28 +72,30 @@ function testFocus()
       // If we actually get this focus we're ok.
       ok(true, "We got focus.");
       aEditor.input.value = "green";
 
       // If we've retained focus, pressing return will start a new editor.
       // If not, we'll wait here until we time out.
       waitForEditorFocus(brace.parentNode, function onNewEditor(aEditor) {
         aEditor.input.blur();
-        finishTest();
+        finishUp();
       });
       EventUtils.sendKey("return");
     });
     EventUtils.sendKey("return");
   });
 
   brace.focus();
 }
 
 function finishUp()
 {
+  InspectorUI.hideSidebar();
+  InspectorUI.closeInspectorUI();
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/tilt/test/browser_tilt_picking.js
+++ b/browser/devtools/tilt/test/browser_tilt_picking.js
@@ -17,18 +17,19 @@ function test() {
   createTab(function() {
     createTilt({
       onTiltOpen: function(instance)
       {
         let presenter = instance.presenter;
         let canvas = presenter.canvas;
 
         presenter._onSetupMesh = function() {
+          let p = getPickablePoint(presenter);
 
-          presenter.pickNode(canvas.width / 2, 10, {
+          presenter.pickNode(p[0], p[1], {
             onpick: function(data)
             {
               ok(data.index > 0,
                 "Simply picking a node didn't work properly.");
 
               Services.obs.addObserver(cleanup, DESTROYED, false);
               InspectorUI.closeInspectorUI();
             }
--- a/browser/devtools/tilt/test/browser_tilt_picking_delete.js
+++ b/browser/devtools/tilt/test/browser_tilt_picking_delete.js
@@ -19,17 +19,19 @@ function test() {
   createTab(function() {
     createTilt({
       onTiltOpen: function(instance)
       {
         presenter = instance.presenter;
         Services.obs.addObserver(whenNodeRemoved, NODE_REMOVED, false);
 
         presenter._onSetupMesh = function() {
-          presenter.highlightNodeAt(presenter.canvas.width / 2, 10, {
+          let p = getPickablePoint(presenter);
+
+          presenter.highlightNodeAt(p[0], p[1], {
             onpick: function()
             {
               ok(presenter._currentSelection > 0,
                 "Highlighting a node didn't work properly.");
               ok(!presenter._highlight.disabled,
                 "After highlighting a node, it should be highlighted. D'oh.");
 
               presenter.deleteNode();
--- a/browser/devtools/tilt/test/browser_tilt_picking_highlight01-offs.js
+++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight01-offs.js
@@ -9,17 +9,16 @@ function test() {
     info("Skipping highlight test because Tilt isn't enabled.");
     return;
   }
   if (!isWebGLSupported()) {
     info("Skipping highlight test because WebGL isn't supported.");
     return;
   }
 
-  requestLongerTimeout(10);
   waitForExplicitFinish();
 
   createTab(function() {
     createTilt({
       onTiltOpen: function(instance)
       {
         presenter = instance.presenter;
         Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false);
--- a/browser/devtools/tilt/test/browser_tilt_picking_highlight01.js
+++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight01.js
@@ -18,17 +18,17 @@ function test() {
 
   createTab(function() {
     createTilt({
       onTiltOpen: function(instance)
       {
         presenter = instance.presenter;
         Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false);
 
-        presenter._onSetupMesh = function() {
+        presenter._onInitializationFinished = function() {
           let contentDocument = presenter.contentWindow.document;
           let div = contentDocument.getElementById("first-law");
 
           presenter.highlightNode(div, "moveIntoView");
         };
       }
     });
   });
--- a/browser/devtools/tilt/test/browser_tilt_picking_highlight02.js
+++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight02.js
@@ -18,34 +18,34 @@ function test() {
 
   createTab(function() {
     createTilt({
       onTiltOpen: function(instance)
       {
         presenter = instance.presenter;
         Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false);
 
-        presenter._onSetupMesh = function() {
-          presenter.highlightNodeAt(presenter.canvas.width / 2, 10);
+        presenter._onInitializationFinished = function() {
+          presenter.highlightNodeAt.apply(this, getPickablePoint(presenter));
         };
       }
     });
   });
 }
 
 function whenHighlighting() {
   ok(presenter._currentSelection > 0,
     "Highlighting a node didn't work properly.");
   ok(!presenter._highlight.disabled,
     "After highlighting a node, it should be highlighted. D'oh.");
 
   executeSoon(function() {
     Services.obs.removeObserver(whenHighlighting, HIGHLIGHTING);
     Services.obs.addObserver(whenUnhighlighting, UNHIGHLIGHTING, false);
-    presenter.highlightNodeAt(-1, -1);
+    presenter.highlightNode(null);
   });
 }
 
 function whenUnhighlighting() {
   ok(presenter._currentSelection < 0,
     "Unhighlighting a should remove the current selection.");
   ok(presenter._highlight.disabled,
     "After unhighlighting a node, it shouldn't be highlighted anymore. D'oh.");
--- a/browser/devtools/tilt/test/browser_tilt_picking_highlight03.js
+++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight03.js
@@ -18,18 +18,18 @@ function test() {
 
   createTab(function() {
     createTilt({
       onTiltOpen: function(instance)
       {
         presenter = instance.presenter;
         Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false);
 
-        presenter._onSetupMesh = function() {
-          presenter.highlightNodeFor(5); // 1 = html, 2 = body, 3 = first div
+        presenter._onInitializationFinished = function() {
+          presenter.highlightNodeFor(3); // 1 = html, 2 = body, 3 = first div
         };
       }
     });
   });
 }
 
 function whenHighlighting() {
   ok(presenter._currentSelection > 0,
--- a/browser/devtools/tilt/test/browser_tilt_picking_miv.js
+++ b/browser/devtools/tilt/test/browser_tilt_picking_miv.js
@@ -9,17 +9,16 @@ function test() {
     info("Skipping highlight test because Tilt isn't enabled.");
     return;
   }
   if (!isWebGLSupported()) {
     info("Skipping highlight test because WebGL isn't supported.");
     return;
   }
 
-  requestLongerTimeout(10);
   waitForExplicitFinish();
 
   createTab(function() {
     createTilt({
       onTiltOpen: function(instance)
       {
         presenter = instance.presenter;
         Services.obs.addObserver(whenHighlighting, HIGHLIGHTING, false);
--- a/browser/devtools/tilt/test/head.js
+++ b/browser/devtools/tilt/test/head.js
@@ -19,16 +19,17 @@ let TiltUtils = tempScope.TiltUtils;
 let TiltVisualizer = tempScope.TiltVisualizer;
 let LayoutHelpers = tempScope.LayoutHelpers;
 
 
 const DEFAULT_HTML = "data:text/html," +
   "<DOCTYPE html>" +
   "<html>" +
     "<head>" +
+      "<meta charset='utf-8'/>" +
       "<title>Three Laws</title>" +
     "</head>" +
     "<body>" +
       "<div id='first-law'>" +
         "A robot may not injure a human being or, through inaction, allow a " +
         "human being to come to harm." +
       "</div>" +
       "<div>" +
@@ -189,8 +190,21 @@ function createTilt(callbacks, close) {
         callbacks.onInspectorClose();
       }
       if ("function" === typeof callbacks.onEnd) {
         callbacks.onEnd();
       }
     });
   }
 }
+
+function getPickablePoint(presenter) {
+  let vertices = presenter._meshStacks[0].vertices.components;
+
+  let topLeft = vec3.create([vertices[0], vertices[1], vertices[2]]);
+  let bottomRight = vec3.create([vertices[6], vertices[7], vertices[8]]);
+  let center = vec3.lerp(topLeft, bottomRight, 0.5, []);
+
+  let renderer = presenter._renderer;
+  let viewport = [0, 0, renderer.width, renderer.height];
+
+  return vec3.project(center, viewport, renderer.mvMatrix, renderer.projMatrix);
+}
--- a/toolkit/devtools/debugger/server/dbg-browser-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-browser-actors.js
@@ -352,21 +352,32 @@ BrowserTabActor.prototype = {
    */
   _pushContext: function BTA_pushContext() {
     dbg_assert(!this._contextPool, "Can't push multiple contexts");
 
     this._contextPool = new ActorPool(this.conn);
     this.conn.addActorPool(this._contextPool);
 
     this.threadActor = new ThreadActor(this);
-    this.threadActor.addDebuggee(this.browser.contentWindow.wrappedJSObject);
+    this._addDebuggees(this.browser.contentWindow.wrappedJSObject);
     this._contextPool.addActor(this.threadActor);
   },
 
   /**
+   * Add the provided window and all windows in its frame tree as debuggees.
+   */
+  _addDebuggees: function BTA__addDebuggees(aWindow) {
+    this.threadActor.addDebuggee(aWindow);
+    let frames = aWindow.frames;
+    for (let i = 0; i < frames.length; i++) {
+      this._addDebuggees(frames[i]);
+    }
+  },
+
+  /**
    * Exits the current thread actor and removes the context-lifetime actor pool.
    * The content window is no longer being debugged after this call.
    */
   _popContext: function BTA_popContext() {
     dbg_assert(!!this._contextPool, "No context to pop.");
 
     this.conn.removeActorPool(this._contextPool);
     this._contextPool = null;
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -423,17 +423,17 @@ ThreadActor.prototype = {
     // Fetch the specified script in that list.
     let script = null;
     for (let i = location.line; i >= 0; i--) {
       // Stop when the first script that contains this location is found.
       if (scripts[i]) {
         // If that first script does not contain the line specified, it's no
         // good.
         if (i + scripts[i].lineCount < location.line) {
-          break;
+          continue;
         }
         script = scripts[i];
         break;
       }
     }
 
     if (!script) {
       return { error: "noScript" };