author | Ryan 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 id | 24315 |
push user | ryanvm@gmail.com |
push date | Fri, 15 Feb 2013 21:34:37 +0000 |
treeherder | mozilla-central@7bd555e2acfa [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 21.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
|
--- 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>