Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 15 Feb 2013 11:55:36 -0500
changeset 122041 26c3dd6332881233a07a6a85fc020b9cb666118c
parent 122040 4b19fa00a8aac5774e82e9617d2c7de708bdedaf (current diff)
parent 122029 326c5e4868fe5ec714983f4138251c66cb3be044 (diff)
child 122042 6f47066fa6fdc1b39641d72ed192fdb741dc9fea
push id24315
push userryanvm@gmail.com
push dateFri, 15 Feb 2013 21:34:37 +0000
treeherdermozilla-central@7bd555e2acfa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.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/profiler/ProfilerPanel.jsm
+++ b/browser/devtools/profiler/ProfilerPanel.jsm
@@ -1,28 +1,30 @@
 /* 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;
 
+Cu.import("resource:///modules/devtools/gDevTools.jsm");
 Cu.import("resource:///modules/devtools/ProfilerController.jsm");
 Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
 
-XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
-  Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
-  return DebuggerServer;
-});
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
+  "resource://gre/modules/devtools/dbg-server.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
 
 /**
  * An instance of a profile UI. Profile UI consists of
  * an iframe with Cleopatra loaded in it and some
  * surrounding meta-data (such as uids).
  *
  * Its main function is to talk to the Cleopatra instance
  * inside of the iframe.
@@ -100,16 +102,19 @@ function ProfileUI(uid, panel) {
           label.textContent = label.textContent.replace(/\s\*$/, "");
         }.bind(this));
         break;
       case "disabled":
         this.emit("disabled");
         break;
       case "enabled":
         this.emit("enabled");
+        break;
+      case "displaysource":
+        this.panel.displaySource(event.data.data);
     }
   }.bind(this));
 }
 
 ProfileUI.prototype = {
   show: function PUI_show() {
     this.iframe.removeAttribute("hidden");
   },
@@ -214,25 +219,41 @@ ProfilerPanel.prototype = {
   document:    null,
   target:      null,
   controller:  null,
   profiles:    null,
 
   _uid:        null,
   _activeUid:  null,
   _runningUid: null,
+  _browserWin: null,
 
   get activeProfile() {
     return this.profiles.get(this._activeUid);
   },
 
   set activeProfile(profile) {
     this._activeUid = profile.uid;
   },
 
+  get browserWindow() {
+    if (this._browserWin) {
+      return this._browserWin;
+    }
+
+    let win = this.window.top;
+    let type = win.document.documentElement.getAttribute("windowtype");
+
+    if (type !== "navigator:browser") {
+      win = Services.wm.getMostRecentWindow("navigator:browser");
+    }
+
+    return this._browserWin = win;
+  },
+
   /**
    * Open a debug connection and, on success, switch to the newly created
    * profile.
    *
    * @return Promise
    */
   open: function PP_open() {
     let deferred = Promise.defer();
@@ -412,16 +433,58 @@ ProfilerPanel.prototype = {
           task: data.task
         }), "*");
       }
       uid -= 1;
     }
   },
 
   /**
+   * Open file specified in data in either a debugger or view-source.
+   *
+   * @param object data
+   *   An object describing the file. It must have three properties:
+   *    - uri
+   *    - line
+   *    - isChrome (chrome files are opened via view-source)
+   */
+  displaySource: function PP_displaySource(data, onOpen=function() {}) {
+    let win = this.window;
+    let panelWin, timeout;
+
+    function onSourceShown(event) {
+      if (event.detail.url !== data.uri) {
+        return;
+      }
+
+      panelWin.removeEventListener("Debugger:SourceShown", onSourceShown, false);
+      panelWin.editor.setCaretPosition(data.line - 1);
+      onOpen();
+    }
+
+    if (data.isChrome) {
+      return void this.browserWindow.gViewSourceUtils.
+        viewSource(data.uri, null, this.document, data.line);
+    }
+
+    gDevTools.showToolbox(this.target, "jsdebugger").then(function (toolbox) {
+      let dbg = toolbox.getCurrentPanel();
+      panelWin = dbg.panelWin;
+
+      let view = dbg.panelWin.DebuggerView;
+      if (view.Source && view.Sources.selectedValue === data.uri) {
+        return void view.editor.setCaretPosition(data.line - 1);
+      }
+
+      panelWin.addEventListener("Debugger:SourceShown", onSourceShown, false);
+      panelWin.DebuggerView.Sources.preferredSource = data.uri;
+    }.bind(this));
+  },
+
+  /**
    * Cleanup.
    */
   destroy: function PP_destroy() {
     if (this.profiles) {
       let uid = this._uid;
 
       while (uid >= 0) {
         if (this.profiles.has(uid)) {
--- a/browser/devtools/profiler/cleopatra/js/devtools.js
+++ b/browser/devtools/profiler/cleopatra/js/devtools.js
@@ -10,25 +10,29 @@ var gInstanceUID;
  *
  * @param string status
  *   Status to send to the parent page:
  *    - loaded, when page is loaded.
  *    - start, when user wants to start profiling.
  *    - stop, when user wants to stop profiling.
  *    - disabled, when the profiler was disabled
  *    - enabled, when the profiler was enabled
+ *    - displaysource, when user wants to display source
+ * @param object data (optional)
+ *    Additional data to send to the parent page.
  */
-function notifyParent(status) {
+function notifyParent(status, data={}) {
   if (!gInstanceUID) {
     gInstanceUID = window.location.search.substr(1);
   }
 
   window.parent.postMessage({
     uid: gInstanceUID,
-    status: status
+    status: status,
+    data: data
   }, "*");
 }
 
 /**
  * A listener for incoming messages from the parent
  * page. All incoming messages must be stringified
  * JSON objects to be compatible with Cleopatra's
  * format:
@@ -192,17 +196,17 @@ function enterFinishedProfileUI() {
   gPluginView = new PluginView();
   tree.appendChild(gPluginView.getContainer());
 
   gMainArea.appendChild(cover);
   gMainArea.appendChild(pane);
 
   var currentBreadcrumb = gSampleFilters;
   gBreadcrumbTrail.add({
-    title: "Complete Profile",
+    title: gStrings["Complete Profile"],
     enterCallback: function () {
       gSampleFilters = [];
       filtersChanged();
     }
   });
 
   if (currentBreadcrumb == null || currentBreadcrumb.length == 0) {
     gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection);
--- a/browser/devtools/profiler/cleopatra/js/tree.js
+++ b/browser/devtools/profiler/cleopatra/js/tree.js
@@ -461,17 +461,18 @@ TreeView.prototype = {
     }
     return '<input type="button" value="Expand / Collapse" class="expandCollapseButton" tabindex="-1"> ' +
       '<span class="sampleCount">' + node.counter + '</span> ' +
       '<span class="samplePercentage">' + samplePercentage + '</span> ' +
       '<span class="selfSampleCount">' + node.selfCounter + '</span> ' +
       '<span class="resourceIcon" data-resource="' + node.library + '"></span> ' +
       '<span class="functionName">' + nodeName + '</span>' +
       '<span class="libraryName">' + libName + '</span>' +
-      '<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">';
+      (nodeName === '(total)' ? '' :
+        '<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">');
   },
   _resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) {
     while (div.pendingExpand != null && div.pendingExpand.length > 0) {
       var pendingExpand = div.pendingExpand.shift();
       pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue;
       this._pendingActions.push(pendingExpand);
       this._schedulePendingActionProcessing();
     }
--- a/browser/devtools/profiler/cleopatra/js/ui.js
+++ b/browser/devtools/profiler/cleopatra/js/ui.js
@@ -164,18 +164,22 @@ function ProfileTreeManager() {
     if (window.comparator_setSelection) {
       window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), frameData);
     }
   });
   this.treeView.addEventListener("contextMenuClick", function (e) {
     self._onContextMenuClick(e);
   });
   this.treeView.addEventListener("focusCallstackButtonClicked", function (frameData) {
-    var focusedCallstack = self._getCallstackUpTo(frameData);
-    focusOnCallstack(focusedCallstack, frameData.name);
+    // NOTE: Not in the original Cleopatra source code.
+    notifyParent("displaysource", {
+      line: frameData.scriptLocation.lineInformation,
+      uri: frameData.scriptLocation.scriptURI,
+      isChrome: /^otherhost_*/.test(frameData.library)
+    });
   });
   this._container = document.createElement("div");
   this._container.className = "tree";
   this._container.appendChild(this.treeView.getContainer());
 
   // If this is set when the tree changes the snapshot is immediately restored.
   this._savedSnapshot = null;
 }
@@ -1561,17 +1565,18 @@ function focusOnSymbol(focusSymbol, name
     enterCallback: function () {
       gSampleFilters = newFilterChain;
       filtersChanged();
     }
   });
 }
 
 function focusOnCallstack(focusedCallstack, name, overwriteCallstack) {
-  var invertCallback =  gInvertCallstack;
+  var invertCallstack = gInvertCallstack;
+
   if (overwriteCallstack != null) {
     invertCallstack = overwriteCallstack;
   }
   var filter = {
     type: !invertCallstack ? "FocusedCallstackPostfixSampleFilter" : "FocusedCallstackPrefixSampleFilter",
     name: name,
     focusedCallstack: focusedCallstack,
     appliesToJS: gJavascriptOnly
--- a/browser/devtools/profiler/test/Makefile.in
+++ b/browser/devtools/profiler/test/Makefile.in
@@ -5,17 +5,26 @@
 DEPTH          = @DEPTH@
 topsrcdir      = @top_srcdir@
 srcdir         = @srcdir@
 VPATH          = @srcdir@
 relativesrcdir = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-MOCHITEST_BROWSER_FILES = \
+MOCHITEST_BROWSER_TESTS = \
 		browser_profiler_run.js \
 		browser_profiler_controller.js \
 		browser_profiler_profiles.js \
 		browser_profiler_remote.js \
 		browser_profiler_bug_830664_multiple_profiles.js \
+		browser_profiler_bug_834878_source_buttons.js \
 		head.js \
+		$(NULL)
+
+MOCHITEST_BROWSER_PAGES = \
+		mock_profiler_bug_834878_page.html \
+		mock_profiler_bug_834878_script.js \
+		$(NULL)
+
+MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/profiler/test/browser_profiler_bug_834878_source_buttons.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const BASE = "http://example.com/browser/browser/devtools/profiler/test/";
+const URL = BASE + "mock_profiler_bug_834878_page.html";
+const SCRIPT = BASE + "mock_profiler_bug_834878_script.js";
+
+function test() {
+  waitForExplicitFinish();
+
+  setUp(URL, function onSetUp(tab, browser, panel) {
+    panel.once("profileCreated", function () {
+      let data = { uri: SCRIPT, line: 5, isChrome: false };
+
+      panel.displaySource(data, function onOpen() {
+        let target = TargetFactory.forTab(tab);
+        let dbg = gDevTools.getToolbox(target).getPanel("jsdebugger");
+        let view = dbg.panelWin.DebuggerView;
+
+        is(view.Sources.selectedValue, data.uri, "URI is different");
+        is(view.editor.getCaretPosition().line, data.line - 1, "Line is different");
+
+        tearDown(tab);
+      });
+    });
+
+    panel.createProfile();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/profiler/test/mock_profiler_bug_834878_page.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <title>Profiler Script Linking Test</title>
+    <script type="text/javascript" src="mock_profiler_bug_834878_script.js">
+    </script>
+  </head>
+  <body>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/profiler/test/mock_profiler_bug_834878_script.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function main() {
+  console.log("Hello, World!");
+  return 0;
+}
\ No newline at end of file
--- a/browser/locales/en-US/chrome/browser/devtools/profiler.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/profiler.properties
@@ -13,17 +13,17 @@
 # LOCALIZATION NOTE (profiler.label):
 # This string is displayed in the title of the tab when the profiler is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 profiler.label=Profiler
 
 # LOCALIZATION NOTE (profiler.commandkey, profiler.accesskey)
 # Used for the menuitem in the tool menu
 profiler.commandkey=Y
-profiler.accesskey=Y
+profiler.accesskey=P
 
 # LOCALIZATION NOTE (profiler.tooltip):
 # This string is displayed in the tooltip of the tab when the profiler is
 # displayed inside the developer tools window.
 profiler.tooltip=Profiler
 
 # LOCALIZATION NOTE (profiler.profileName):
 # This string is the default name for new profiles. Its parameter is a number.
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1435,16 +1435,47 @@ nsDOMWindowUtils::GetScrollXY(bool aFlus
 
   *aScrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
   *aScrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::GetScrollbarWidth(bool aFlushLayout, int32_t* aResult)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  *aResult = 0;
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  NS_ENSURE_STATE(window);
+
+  nsCOMPtr<nsIDocument> doc(do_QueryInterface(window->GetExtantDocument()));
+  NS_ENSURE_STATE(doc);
+
+  if (aFlushLayout) {
+    doc->FlushPendingNotifications(Flush_Layout);
+  }
+
+  nsIPresShell* presShell = doc->GetShell();
+  NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_AVAILABLE);
+
+  nsIScrollableFrame* scrollFrame = presShell->GetRootScrollFrameAsScrollable();
+  NS_ENSURE_TRUE(scrollFrame, NS_OK);
+
+  nsMargin sizes = scrollFrame->GetActualScrollbarSizes();
+  *aResult = nsPresContext::AppUnitsToIntCSSPixels(sizes.LeftRight());
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::GetRootBounds(nsIDOMClientRect** aResult)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   // Weak ref, since we addref it below
   nsClientRect* rect = new nsClientRect();
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -36,17 +36,17 @@ interface nsIDOMWindow;
 interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 
-[scriptable, uuid(020deb5a-cba6-41dd-8551-72a880d01970)]
+[scriptable, uuid(16b3bdcc-75d4-11e2-8a20-aaff78957a39)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -643,16 +643,23 @@ interface nsIDOMWindowUtils : nsISupport
    * Returns the scroll position of the window's currently loaded document.
    *
    * @param aFlushLayout flushes layout if true. Otherwise, no flush occurs.
    * @see nsIDOMWindow::scrollX/Y
    */
   void getScrollXY(in boolean aFlushLayout, out long aScrollX, out long aScrollY);
 
   /**
+   * Returns the scrollbar width of the window's scroll frame.
+   *
+   * @param aFlushLayout flushes layout if true. Otherwise, no flush occurs.
+   */
+  long getScrollbarWidth(in boolean aFlushLayout);
+
+  /**
    * Returns the bounds of the window's currently loaded document. This will
    * generally be (0, 0, pageWidth, pageHeight) but in some cases (e.g. RTL
    * documents) may have a negative left value.
    */
   nsIDOMClientRect getRootBounds();
 
   /**
    * Get IME open state. TRUE means 'Open', otherwise, 'Close'.
--- a/dom/tests/mochitest/general/Makefile.in
+++ b/dom/tests/mochitest/general/Makefile.in
@@ -24,16 +24,18 @@ MOCHITEST_FILES = \
 		file_bug628069.html \
 		test_bug631440.html \
 		test_bug653364.html \
 		test_bug629535.html \
 		test_clientRects.html \
 		test_consoleAPI.html \
 		test_domWindowUtils.html \
 		test_domWindowUtils_scrollXY.html \
+		test_domWindowUtils_scrollbarWidth.html \
+		file_domWindowUtils_scrollbarWidth.html \
 		test_offsets.html \
 		test_offsets.js \
 		test_windowProperties.html \
 		test_clipboard_events.html \
 		test_frameElementWrapping.html \
 		file_frameElementWrapping.html \
 		test_framedhistoryframes.html \
 		test_windowedhistoryframes.html \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/file_domWindowUtils_scrollbarWidth.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body style='width: 100000px; overflow: hidden;'></body>
+  <div id="float" style="float: left; overflow: scroll;">
+    <div style="width: 200px;"></div>
+  </div>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_domWindowUtils_scrollbarWidth.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>nsIDOMWindowUtils::getScrollbarWidth test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+
+<body id="body">
+  <script type="application/javascript;version=1.8">
+    function doTests() {
+      let iframe = document.getElementById("iframe");
+      let cwindow = iframe.contentWindow;
+      let utils = SpecialPowers.getDOMWindowUtils(cwindow);
+      let doc = cwindow.document;
+
+      function haveNonFloatingScrollbars() {
+        return doc.getElementById("float").offsetWidth > 200;
+      }
+
+      is(utils.getScrollbarWidth(true), 0,
+         "getScrollbarWidth returns zero without a scrollbar");
+
+      // Some platforms (esp. mobile) may have floating scrollbars that don't
+      // affect layout. Thus getScrollbarWidth() would always return 0.
+      if (haveNonFloatingScrollbars()) {
+        let body = doc.querySelector("body");
+        body.style.overflowY = "scroll";
+
+        is(utils.getScrollbarWidth(false), 0,
+           "getScrollbarWidth returns zero with a vertical scrollbar w/o flushing");
+
+        ok(utils.getScrollbarWidth(true) > 0,
+           "getScrollbarWidth returns non-zero with a vertical scrollbar with flushing");
+      }
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+  </script>
+
+  <iframe src="http://mochi.test:8888/tests/dom/tests/mochitest/general/file_domWindowUtils_scrollbarWidth.html"
+          id="iframe" onload="doTests();">
+  </iframe>
+
+</body>
+</html>