Merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Fri, 07 Mar 2014 17:39:52 -0800
changeset 190855 8441e058510789ec3693cc63a9f74b5dc948591a
parent 190854 67cd3f605fd36daa845c3aa951034f06a5426eef (current diff)
parent 190820 d01bf8596d3bebdd119767d91aa02026e834af3f (diff)
child 190856 6e816e44735d305ef9a54a2b309c49f61ad3a7f4
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone30.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 inbound
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -4,16 +4,17 @@ support-files =
   addon2.xpi
   code_binary_search.coffee
   code_binary_search.js
   code_binary_search.map
   code_blackboxing_blackboxme.js
   code_blackboxing_one.js
   code_blackboxing_three.js
   code_blackboxing_two.js
+  code_breakpoints-break-on-last-line-of-script-on-reload.js
   code_function-search-01.js
   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.min.js
@@ -29,16 +30,17 @@ support-files =
   code_ugly-6.js
   code_ugly-7.js
   code_ugly-8
   code_ugly-8^headers^
   doc_auto-pretty-print-01.html
   doc_auto-pretty-print-02.html
   doc_binary_search.html
   doc_blackboxing.html
+  doc_breakpoints-break-on-last-line-of-script-on-reload.html
   doc_closures.html
   doc_cmd-break.html
   doc_cmd-dbg.html
   doc_conditional-breakpoints.html
   doc_domnode-variables.html
   doc_editor-mode.html
   doc_empty-tab-01.html
   doc_empty-tab-02.html
@@ -92,16 +94,17 @@ support-files =
 [browser_dbg_break-on-dom-02.js]
 [browser_dbg_break-on-dom-03.js]
 [browser_dbg_break-on-dom-04.js]
 [browser_dbg_break-on-dom-05.js]
 [browser_dbg_break-on-dom-06.js]
 [browser_dbg_break-on-dom-07.js]
 [browser_dbg_break-on-dom-08.js]
 [browser_dbg_breakpoints-actual-location.js]
+[browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
 [browser_dbg_breakpoints-button-01.js]
 [browser_dbg_breakpoints-button-02.js]
 [browser_dbg_breakpoints-contextmenu.js]
 [browser_dbg_breakpoints-disabled-reload.js]
 [browser_dbg_breakpoints-editor.js]
 [browser_dbg_breakpoints-highlight.js]
 [browser_dbg_breakpoints-new-script.js]
 [browser_dbg_breakpoints-pane.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 978019: Setting a breakpoint on the last line of a Debugger.Script and
+ * reloading should still hit the breakpoint.
+ */
+
+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";
+
+const { promiseInvoke } = require("devtools/async-utils");
+
+function test() {
+  let gPanel, gDebugger, gThreadClient, gEvents;
+
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gThreadClient = gDebugger.gThreadClient;
+    gEvents = gDebugger.EVENTS;
+
+    Task.spawn(function* () {
+      yield waitForSourceShown(gPanel, CODE_URL);
+
+      // Pause and set our breakpoints.
+      yield doInterrupt();
+      yield promise.all([
+        gPanel.addBreakpoint({
+          url: CODE_URL,
+          line: 2
+        }),
+        gPanel.addBreakpoint({
+          url: CODE_URL,
+          line: 3
+        }),
+        gPanel.addBreakpoint({
+          url: CODE_URL,
+          line: 4
+        })
+      ]);
+
+      // Should hit the first breakpoint on reload.
+      yield promise.all([
+        reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
+        waitForCaretUpdated(gPanel, 2)
+      ]);
+
+      // And should hit the other breakpoints as we resume.
+      yield promise.all([
+        doResume(),
+        waitForCaretUpdated(gPanel, 3)
+      ]);
+      yield promise.all([
+        doResume(),
+        waitForCaretUpdated(gPanel, 4)
+      ]);
+
+      yield resumeDebuggerThenCloseAndFinish(gPanel);
+    });
+  });
+
+  function rdpInvoke(obj, method) {
+    return promiseInvoke(obj, method)
+      .then(({error, message }) => {
+        if (error) {
+          throw new Error(error + ": " + message);
+        }
+      });
+  }
+
+  function doResume() {
+    return rdpInvoke(gThreadClient, gThreadClient.resume);
+  }
+
+  function doInterrupt() {
+    return rdpInvoke(gThreadClient, gThreadClient.interrupt);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/code_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -0,0 +1,5 @@
+var a = (function(){
+  var b = 9;
+  console.log("x", b);
+  return b;
+})();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_breakpoints-break-on-last-line-of-script-on-reload.html
@@ -0,0 +1,8 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<head>
+  <meta charset="utf-8"/>
+  <title>Debugger Break on Last Line of Script on Reload Test Page</title>
+</head>
+<script src="code_breakpoints-break-on-last-line-of-script-on-reload.js"></script>
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -11,16 +11,17 @@ let { Services } = Cu.import("resource:/
 // be affected by this pref.
 let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let { require } = devtools;
 let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
 let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
 let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 let Toolbox = devtools.Toolbox;
 
--- a/gfx/skia/generate_mozbuild.py
+++ b/gfx/skia/generate_mozbuild.py
@@ -55,16 +55,19 @@ DEFINES['SK_G32_SHIFT'] = 8
 DEFINES['SK_B32_SHIFT'] = 0
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk2', 'gtk3', 'qt', 'gonk', 'cocoa'):
     DEFINES['SK_USE_POSIX_THREADS'] = 1
 
 if CONFIG['INTEL_ARCHITECTURE'] and CONFIG['HAVE_TOOLCHAIN_SUPPORT_MSSSE3']:
     DEFINES['SK_BUILD_SSSE3'] = 1
 
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gonk'):
+    DEFINES['SK_FONTHOST_CAIRO_STANDALONE'] = 0
+
 if (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android') or \
    (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa') or \
    (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk') or \
    CONFIG['MOZ_WIDGET_QT'] or \
    CONFIG['MOZ_WIDGET_GTK']:
     DEFINES['SK_FONTHOST_DOES_NOT_USE_FONTMGR'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
@@ -176,16 +179,17 @@ def generate_separated_sources(platform_
       # 'trunk/src/images/SkImageRef.cpp',
       # 'trunk/src/images/SkImageRef_GlobalPool.cpp',
       # 'trunk/src/images/SkImageRefPool.cpp',
       # 'trunk/src/images/SkImageDecoder.cpp',
       # 'trunk/src/images/SkImageDecoder_Factory.cpp',
     },
     'android': {
       # 'trunk/src/ports/SkDebug_android.cpp',
+      'trunk/src/ports/SkFontHost_android_old.cpp',
       'trunk/src/ports/SkFontHost_cairo.cpp',
       # 'trunk/src/ports/SkFontHost_FreeType.cpp',
       # 'trunk/src/ports/SkFontHost_FreeType_common.cpp',
       # 'trunk/src/ports/SkThread_pthread.cpp',
       # 'trunk/src/ports/SkPurgeableMemoryBlock_android.cpp',
       # 'trunk/src/ports/SkTime_Unix.cpp',
       # 'trunk/src/utils/SkThreadUtils_pthread.cpp',
       # 'trunk/src/images/SkImageRef_ashmem.cpp',
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -127,17 +127,17 @@ abstract public class BrowserApp extends
     private BrowserSearch mBrowserSearch;
     private View mBrowserSearchContainer;
 
     public ViewFlipper mViewFlipper;
     public ActionModeCompatView mActionBar;
     private BrowserToolbar mBrowserToolbar;
     private HomePager mHomePager;
     private TabsPanel mTabsPanel;
-    private View mHomePagerContainer;
+    private ViewGroup mHomePagerContainer;
     protected Telemetry.Timer mAboutHomeStartupTimer = null;
     private ActionModeCompat mActionMode;
     private boolean mShowActionModeEndAnimation = false;
 
     private static final int GECKO_TOOLS_MENU = -1;
     private static final int ADDON_MENU_OFFSET = 1000;
     private static class MenuItemInfo {
         public int id;
@@ -475,17 +475,17 @@ abstract public class BrowserApp extends
                     } else {
                         mHomePager.requestFocus();
                     }
                 }
                 return false;
             }
         });
 
-        mHomePagerContainer = findViewById(R.id.home_pager_container);
+        mHomePagerContainer = (ViewGroup) findViewById(R.id.home_pager_container);
 
         mBrowserSearchContainer = findViewById(R.id.search_container);
         mBrowserSearch = (BrowserSearch) getSupportFragmentManager().findFragmentByTag(BROWSER_SEARCH_TAG);
         if (mBrowserSearch == null) {
             mBrowserSearch = BrowserSearch.newInstance();
             mBrowserSearch.setUserVisibleHint(false);
         }
 
@@ -1688,18 +1688,27 @@ abstract public class BrowserApp extends
         if (isDynamicToolbarEnabled() && mLayerView != null) {
             mLayerView.getLayerMarginsAnimator().showMargins(true);
         }
 
         if (mHomePager == null) {
             final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
             mHomePager = (HomePager) homePagerStub.inflate();
 
-            HomeBanner homeBanner = (HomeBanner) findViewById(R.id.home_banner);
+            final HomeBanner homeBanner = (HomeBanner) findViewById(R.id.home_banner);
             mHomePager.setBanner(homeBanner);
+
+            // Remove the banner from the view hierarchy if it is dismissed.
+            homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
+                @Override
+                public void onDismiss() {
+                    mHomePager.setBanner(null);
+                    mHomePagerContainer.removeView(homeBanner);
+                }
+            });
         }
 
         mHomePagerContainer.setVisibility(View.VISIBLE);
         mHomePager.load(getSupportLoaderManager(),
                         getSupportFragmentManager(),
                         pageId, animator);
 
         // Hide the web content so it cannot be focused by screen readers.
--- a/mobile/android/base/home/HomeBanner.java
+++ b/mobile/android/base/home/HomeBanner.java
@@ -51,16 +51,23 @@ public class HomeBanner extends LinearLa
 
     // Tracks whether the user swiped the banner down, preventing us from autoshowing when the user
     // switches back to the default page.
     private boolean mUserSwipedDown = false;
 
     private final TextView mTextView;
     private final ImageView mIconView;
 
+    // Listener that gets called when the banner is dismissed from the close button.
+    private OnDismissListener mOnDismissListener;
+
+    public interface OnDismissListener {
+        public void onDismiss();
+    }
+
     public HomeBanner(Context context) {
         this(context, null);
     }
 
     public HomeBanner(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         LayoutInflater.from(context).inflate(R.layout.home_banner, this);
@@ -79,31 +86,39 @@ public class HomeBanner extends LinearLa
 
         // The drawable should have 50% opacity.
         closeButton.getDrawable().setAlpha(127);
 
         closeButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                 HomeBanner.this.setVisibility(View.GONE);
+
                 // Send the current message id back to JS.
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Dismiss", (String) getTag()));
+
+                if (mOnDismissListener != null) {
+                    mOnDismissListener.onDismiss();
+                }
             }
         });
 
         setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
+                // Hide the banner. This does not remove the message from the rotation, so it may appear
+                // again if the JS onclick handler doesn't choose to remove it.
+                HomeBanner.this.setVisibility(View.GONE);
+
                 // Send the current message id back to JS.
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Click", (String) getTag()));
             }
         });
 
         GeckoAppShell.getEventDispatcher().registerEventListener("HomeBanner:Data", this);
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
         GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
     }
@@ -118,54 +133,65 @@ public class HomeBanner extends LinearLa
 
         super.setVisibility(visibility);
     }
 
     public void setScrollingPages(boolean scrollingPages) {
         mScrollingPages = scrollingPages;
     }
 
+    public void setOnDismissListener(OnDismissListener listener) {
+        mOnDismissListener = listener;
+    }
+
+    /**
+     * Sends a message to gecko to request a new banner message. UI is updated in handleMessage.
+     */
+    public void update() {
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
+    }
+
     @Override
     public void handleMessage(String event, JSONObject message) {
-        try {
-            // Store the current message id to pass back to JS in the view's OnClickListener.
-            setTag(message.getString("id"));
-
-            // Display styled text from an HTML string.
-            final Spanned text = Html.fromHtml(message.getString("text"));
-
-            // Update the banner message on the UI thread.
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    mTextView.setText(text);
-                    setVisibility(VISIBLE);
-                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Shown", (String) getTag()));
-
-                    // Animate the banner if it is currently active.
-                    if (mActive) {
-                        animateUp();
-                    }
-                }
-            });
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Exception handling " + event + " message", e);
-            return;
-        }
-
+        final String id = message.optString("id");
+        final String text = message.optString("text");
         final String iconURI = message.optString("iconURI");
 
-        BitmapUtils.getDrawable(getContext(), iconURI, new BitmapUtils.BitmapLoader() {
+        // Update the banner message on the UI thread.
+        ThreadUtils.postToUiThread(new Runnable() {
             @Override
-            public void onBitmapFound(final Drawable d) {
-                // Hide the image view if we don't have an icon to show.
-                if (d == null) {
-                    mIconView.setVisibility(View.GONE);
-                } else {
-                    mIconView.setImageDrawable(d);
+            public void run() {
+                // Hide the banner if the message doesn't have valid id and text.
+                if (TextUtils.isEmpty(id) || TextUtils.isEmpty(text)) {
+                    setVisibility(View.GONE);
+                    return;
+                }
+
+                // Store the current message id to pass back to JS in the view's OnClickListener.
+                setTag(id);
+                mTextView.setText(Html.fromHtml(text));
+
+                BitmapUtils.getDrawable(getContext(), iconURI, new BitmapUtils.BitmapLoader() {
+                    @Override
+                    public void onBitmapFound(final Drawable d) {
+                        // Hide the image view if we don't have an icon to show.
+                        if (d == null) {
+                            mIconView.setVisibility(View.GONE);
+                        } else {
+                            mIconView.setImageDrawable(d);
+                        }
+                    }
+                });
+
+                setVisibility(View.VISIBLE);
+                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Shown", id));
+
+                // Animate the banner if it is currently active.
+                if (mActive) {
+                    animateUp();
                 }
             }
         });
     }
 
     public void setActive(boolean active) {
         // No need to animate if not changing
         if (mActive == active) {
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -150,16 +150,21 @@ public class HomePager extends ViewPager
      * Loads and initializes the pager.
      *
      * @param fm FragmentManager for the adapter
      */
     public void load(LoaderManager lm, FragmentManager fm, String panelId, PropertyAnimator animator) {
         mLoaded = true;
         mInitialPanelId = panelId;
 
+        // Update the home banner message each time the HomePager is loaded.
+        if (mHomeBanner != null) {
+            mHomeBanner.update();
+        }
+
         // Only animate on post-HC devices, when a non-null animator is given
         final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
 
         final HomeAdapter adapter = new HomeAdapter(mContext, fm);
         adapter.setOnAddPanelListener(mAddPanelListener);
         adapter.setCanLoadHint(!shouldAnimate);
         setAdapter(adapter);
 
--- a/mobile/android/components/Snippets.js
+++ b/mobile/android/components/Snippets.js
@@ -346,16 +346,18 @@ function loadSyncPromoBanner() {
       let stringBundle = Services.strings.createBundle("chrome://browser/locale/sync.properties");
       let text = stringBundle.GetStringFromName("promoBanner.message.text");
       let link = stringBundle.GetStringFromName("promoBanner.message.link");
 
       let id = Home.banner.add({
         text: text + "<a href=\"#\">" + link + "</a>",
         icon: "drawable://sync_promo",
         onclick: function() {
+          // Remove the message, so that it won't show again for the rest of the app lifetime.
+          Home.banner.remove(id);
           Accounts.launchSetup();
         },
         ondismiss: function() {
           // Remove the sync promo message from the banner and never try to show it again.
           Home.banner.remove(id);
           Services.prefs.setBoolPref("browser.snippets.syncPromo.enabled", false);
         }
       });
--- a/mobile/android/modules/Home.jsm
+++ b/mobile/android/modules/Home.jsm
@@ -47,27 +47,23 @@ function BannerMessage(options) {
   if ("ondismiss" in options && typeof options.ondismiss === "function")
     this.ondismiss = options.ondismiss;
 }
 
 let HomeBanner = (function () {
   // Holds the messages that will rotate through the banner.
   let _messages = {};
 
-  // A queue used to keep track of which message to show next.
-  let _queue = [];
-
 
   let _handleGet = function() {
-    // Get the message from the front of the queue, then add it back
-    // to the end of the queue to show it again later.
-    let id = _queue.shift();
-    _queue.push(id);
+    // Choose a message at random.
+    let keys = Object.keys(_messages);
+    let randomId = keys[Math.floor(Math.random() * keys.length)];
+    let message = _messages[randomId];
 
-    let message = _messages[id];
     sendMessageToJava({
       type: "HomeBanner:Data",
       id: message.id,
       text: message.text,
       iconURI: message.iconURI
     });
   };
 
@@ -114,19 +110,16 @@ let HomeBanner = (function () {
      * Adds a new banner message to the rotation.
      *
      * @return id Unique identifer for the message.
      */
     add: function(options) {
       let message = new BannerMessage(options);
       _messages[message.id] = message;
 
-      // Add the new message to the end of the queue.
-      _queue.push(message.id);
-
       // If this is the first message we're adding, add
       // observers to listen for requests from the Java UI.
       if (Object.keys(_messages).length == 1) {
         Services.obs.addObserver(this, "HomeBanner:Get", false);
         Services.obs.addObserver(this, "HomeBanner:Shown", false);
         Services.obs.addObserver(this, "HomeBanner:Click", false);
         Services.obs.addObserver(this, "HomeBanner:Dismiss", false);
 
@@ -145,20 +138,16 @@ let HomeBanner = (function () {
      */
     remove: function(id) {
       if (!(id in _messages)) {
         throw "Home.banner: Can't remove message that doesn't exist: id = " + id;
       }
 
       delete _messages[id];
 
-      // Remove the message from the queue.
-      let index = _queue.indexOf(id);
-      _queue.splice(index, 1);
-
       // If there are no more messages, remove the observers.
       if (Object.keys(_messages).length == 0) {
         Services.obs.removeObserver(this, "HomeBanner:Get");
         Services.obs.removeObserver(this, "HomeBanner:Shown");
         Services.obs.removeObserver(this, "HomeBanner:Click");
         Services.obs.removeObserver(this, "HomeBanner:Dismiss");
       }
     }
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -1887,18 +1887,17 @@ ThreadActor.prototype = {
    * Debugger.Environment.
    * @param Debugger.Environment aEnvironment
    *        The lexical environment we want to extract.
    * @param object aPool
    *        The pool where the newly-created actor will be placed.
    * @return The EnvironmentActor for aEnvironment or undefined for host
    *         functions or functions scoped to a non-debuggee global.
    */
-  createEnvironmentActor:
-  function (aEnvironment, aPool) {
+  createEnvironmentActor: function (aEnvironment, aPool) {
     if (!aEnvironment) {
       return undefined;
     }
 
     if (aEnvironment.actor) {
       return aEnvironment.actor;
     }
 
@@ -1949,18 +1948,17 @@ ThreadActor.prototype = {
         return null;
     }
   },
 
   /**
    * Return a protocol completion value representing the given
    * Debugger-provided completion value.
    */
-  createProtocolCompletionValue:
-  function (aCompletion) {
+  createProtocolCompletionValue: function (aCompletion) {
     let protoValue = {};
     if ("return" in aCompletion) {
       protoValue.return = this.createValueGrip(aCompletion.return);
     } else if ("yield" in aCompletion) {
       protoValue.return = this.createValueGrip(aCompletion.yield);
     } else if ("throw" in aCompletion) {
       protoValue.throw = this.createValueGrip(aCompletion.throw);
     } else {
@@ -2189,16 +2187,24 @@ ThreadActor.prototype = {
    *
    * @param aScript Debugger.Script
    *        The source script that has been loaded into a debuggee compartment.
    * @param aGlobal Debugger.Object
    *        A Debugger.Object instance whose referent is the global object.
    */
   onNewScript: function (aScript, aGlobal) {
     this._addScript(aScript);
+
+    // |onNewScript| is only fired for top level scripts (AKA staticLevel == 0),
+    // so we have to make sure to call |_addScript| on every child script as
+    // well to restore breakpoints in those scripts.
+    for (let s of aScript.getChildScripts()) {
+      this._addScript(s);
+    }
+
     this.sources.sourcesForScript(aScript);
   },
 
   onNewSource: function (aSource) {
     this.conn.send({
       from: this.actorID,
       type: "newSource",
       source: aSource.form()