author | David Anderson <danderson@mozilla.com> |
Wed, 18 Jul 2012 19:02:32 -0700 | |
changeset 106565 | adb60cc7b1505bc91ca1c3bb846270cfd19926cb |
parent 106564 | 5d1128ed64af3daa90e95d18c06c06cc03dcac9f (current diff) |
parent 99642 | 9b876829ed322df8cee0120ec5dfe0bb33670794 (diff) |
child 106566 | e4cf8b5d1baa6bd3579e3763ea65d24a049cab67 |
push id | 23447 |
push user | danderson@mozilla.com |
push date | Tue, 11 Sep 2012 17:34:27 +0000 |
treeherder | mozilla-central@fdfaef738a00 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 17.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/.hgtags +++ b/.hgtags @@ -76,8 +76,13 @@ 462c726144bc1fb45b61e774f64ac5d61b4e047c bbc7014db2de49e2301680d2a86be8a53108a88a AURORA_BASE_20120131 bbc7014db2de49e2301680d2a86be8a53108a88a AURORA_BASE_20120131 0000000000000000000000000000000000000000 AURORA_BASE_20120131 0000000000000000000000000000000000000000 AURORA_BASE_20120131 bbc7014db2de49e2301680d2a86be8a53108a88a AURORA_BASE_20120131 b6627f28b7ec17e1b46a594df0f780d3a40847e4 FIREFOX_AURORA_13_BASE 357da346ceb705d196a46574804c7c4ec44ac186 FIREFOX_AURORA_14_BASE 26dcd1b1a20893ad99341c61c6b1239ff1523858 FIREFOX_AURORA_15_BASE +0accd12a8e7e217836ea3f1ee7c411913fc75d8e FIREFOX_AURORA_16_BASE +0000000000000000000000000000000000000000 FIREFOX_AURORA_16_BASE +9697eadafa13b4e9233b39aaeecfeac79503cb54 FIREFOX_AURORA_16_BASE +9697eadafa13b4e9233b39aaeecfeac79503cb54 FIREFOX_AURORA_16_BASE +6fdf9985acfe6f939da584b2559464ab22264fe7 FIREFOX_AURORA_16_BASE
--- a/accessible/src/base/RoleMap.h +++ b/accessible/src/base/RoleMap.h @@ -826,17 +826,17 @@ ROLE(COMBOBOX_OPTION, ATK_ROLE_MENU_ITEM, NSAccessibilityMenuItemRole, ROLE_SYSTEM_LISTITEM, ROLE_SYSTEM_LISTITEM) ROLE(IMAGE_MAP, "image map", ATK_ROLE_IMAGE, - NSAccessibilityImageRole, + NSAccessibilityUnknownRole, ROLE_SYSTEM_GRAPHIC, ROLE_SYSTEM_GRAPHIC) ROLE(OPTION, "listbox option", ATK_ROLE_LIST_ITEM, NSAccessibilityRowRole, ROLE_SYSTEM_LISTITEM,
--- a/accessible/src/base/nsAccDocManager.h +++ b/accessible/src/base/nsAccDocManager.h @@ -19,17 +19,17 @@ class DocAccessible; /** * Manage the document accessible life cycle. */ class nsAccDocManager : public nsIWebProgressListener, public nsIDOMEventListener, public nsSupportsWeakReference { public: - virtual ~nsAccDocManager() { }; + virtual ~nsAccDocManager() { } NS_DECL_ISUPPORTS NS_DECL_NSIWEBPROGRESSLISTENER NS_DECL_NSIDOMEVENTLISTENER /** * Return document accessible for the given DOM node. */ @@ -65,17 +65,17 @@ public: mDocAccessibleCache.Remove(aDocument); } #ifdef DEBUG bool IsProcessingRefreshDriverNotification() const; #endif protected: - nsAccDocManager() { }; + nsAccDocManager() { } /** * Initialize the manager. */ bool Init(); /** * Shutdown the manager.
--- a/accessible/src/mac/mozAccessible.mm +++ b/accessible/src/mac/mozAccessible.mm @@ -407,17 +407,17 @@ GetClosestInterestingAccessible(id anObj NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (NSString*)role { if (!mGeckoAccessible) return nil; -#ifdef DEBUG +#ifdef DEBUG_A11Y NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(mGeckoAccessible), "Does not support nsIAccessibleText when it should"); #endif #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role) \ case roles::geckoRole: \ return macRole;
--- a/accessible/src/mac/mozHTMLAccessible.mm +++ b/accessible/src/mac/mozHTMLAccessible.mm @@ -87,16 +87,26 @@ return; if ([action isEqualToString:NSAccessibilityPressAction]) mGeckoAccessible->DoAction(0); else [super accessibilityPerformAction:action]; } +- (NSString*)customDescription +{ + return @""; +} + +- (NSString*)value +{ + return @""; +} + - (NSURL*)url { if (!mGeckoAccessible || mGeckoAccessible->IsDefunct()) return nil; nsAutoString value; mGeckoAccessible->Value(value);
--- a/accessible/tests/mochitest/events/test_focus_autocomplete.xul +++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul @@ -225,17 +225,17 @@ var itemX = {}, itemY = {}, treeX = {}, treeY = {}; accessible.getBounds(itemX, itemY, {}, {}); tree.getBounds(treeX, treeY, {}, {}); x = itemX.value - treeX.value; y = itemY.value - treeY.value; } } - synthesizeMouse(node, x + 1, y + 1, {}, targetWindow); + synthesizeMouseAtCenter(node, {}, targetWindow); } this.getID = function selectByClick_getID() { return "select by click " + prettyName(aIDFunc.call(null, aIDFuncArg)); } }
--- a/accessible/tests/mochitest/tree/test_map.html +++ b/accessible/tests/mochitest/tree/test_map.html @@ -17,19 +17,19 @@ function doTest() { // map used as imagemap, not accessible var accTree = { SECTION: [ ] }; testAccessibleTree("imagemapcontainer", accTree); - // map group + // map group. Imagemaps are inlines by default, so TEXT_CONTAINER. accTree = - { PARAGRAPH: [ + { TEXT_CONTAINER: [ { PARAGRAPH: [ { TEXT_LEAF: [ ] }, { LINK: [ { TEXT_LEAF: [ ] } ] }, { TEXT_LEAF: [ ] }, { LINK: [ { TEXT_LEAF: [ ] }
--- a/accessible/tests/mochitest/treeupdate/test_imagemap.html +++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html @@ -422,13 +422,16 @@ <map name="atoz_map" id="map"> <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" coords="17,0,30,14" alt="b" shape="rect"> </map> <div id="container"> <img id="imgmap" width="447" height="15" usemap="#atoz_map" - src="../letters.gif"> - </div> + src="../letters.gif"><!-- + Important: no whitespace between the <img> and the </div>, so we + don't end up with textframes there, because those would be reflected + in our accessible tree in some cases. + --></div> </body> </html>
--- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -240,17 +240,18 @@ pref("editor.singleLine.pasteNewlines", // threshold where a tap becomes a drag, in 1/240" reference pixels // The names of the preferences are to be in sync with nsEventStateManager.cpp pref("ui.dragThresholdX", 25); pref("ui.dragThresholdY", 25); // Layers Acceleration pref("layers.acceleration.disabled", false); -pref("layers.offmainthreadcomposition.enabled", false); +pref("layers.offmainthreadcomposition.enabled", true); +pref("layers.async-video.enabled", true); // Web Notifications pref("notification.feature.enabled", true); // IndexedDB pref("indexedDB.feature.enabled", true); pref("dom.indexedDB.warningQuota", 5); @@ -404,16 +405,26 @@ pref("b2g.remote-js.port", 9999); // Handle hardware buttons in the b2g chrome package pref("b2g.keys.menu.enabled", true); // Screen timeout in seconds pref("power.screen.timeout", 60); pref("full-screen-api.enabled", true); +#ifndef MOZ_WIDGET_GONK +// If we're not actually on physical hardware, don't make the top level widget +// fullscreen when transitioning to fullscreen. This means in emulated +// environments (like the b2g desktop client) we won't make the client window +// fill the whole screen, we'll just make the content fill the client window, +// i.e. it won't give the impression to content that the number of device +// screen pixels changes! +pref("full-screen-api.ignore-widgets", true); +#endif + pref("media.volume.steps", 10); //Enable/disable marionette server, set listening port pref("marionette.defaultPrefs.enabled", true); pref("marionette.defaultPrefs.port", 2828); #ifdef MOZ_UPDATER pref("app.update.enabled", true); @@ -455,8 +466,14 @@ pref("media.plugins.enabled", true); // Disable printing (particularly, window.print()) pref("dom.disable_window_print", true); // Disable window.showModalDialog pref("dom.disable_window_showModalDialog", true); // Turns on gralloc-based direct texturing for Gonk pref("gfx.gralloc.enabled", false); + +// XXXX REMOVE FOR PRODUCTION. Turns on GC and CC logging +pref("javascript.options.mem.log", true); + +// Increase mark slice time from 10ms to 30ms +pref("javascript.options.mem.gc_incremental_slice_ms", 30);
--- a/b2g/chrome/content/settings.js +++ b/b2g/chrome/content/settings.js @@ -42,16 +42,25 @@ var SettingsListener = { })); this._callbacks[name] = callback; } }; SettingsListener.init(); +// =================== Audio ==================== +SettingsListener.observe('audio.volume.master', 0.5, function(value) { + let audioManager = Services.audioManager; + if (!audioManager) + return; + + audioManager.masterVolume = Math.max(0.0, Math.min(value, 1.0)); +}); + // =================== Languages ==================== SettingsListener.observe('language.current', 'en-US', function(value) { Services.prefs.setCharPref('intl.accept_languages', value); }); // =================== RIL ====================
--- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -171,69 +171,32 @@ var shell = { window.removeEventListener('sizemodechange', this); this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); #ifndef MOZ_WIDGET_GONK delete Services.audioManager; #endif }, - changeVolume: function shell_changeVolume(delta) { - let steps = 10; - try { - steps = Services.prefs.getIntPref("media.volume.steps"); - if (steps <= 0) - steps = 1; - } catch(e) {} - - let audioManager = Services.audioManager; - if (!audioManager) - return; - - let currentVolume = audioManager.masterVolume; - let newStep = Math.round(steps * Math.sqrt(currentVolume)) + delta; - let volume = (newStep / steps) * (newStep / steps); - - if (volume > 1) - volume = 1; - if (volume < 0) - volume = 0; - audioManager.masterVolume = volume; - }, - forwardKeyToContent: function shell_forwardKeyToContent(evt) { let content = shell.contentBrowser.contentWindow; let generatedEvent = content.document.createEvent('KeyboardEvent'); generatedEvent.initKeyEvent(evt.type, true, true, evt.view, evt.ctrlKey, evt.altKey, evt.shiftKey, evt.metaKey, evt.keyCode, evt.charCode); content.document.documentElement.dispatchEvent(generatedEvent); }, handleEvent: function shell_handleEvent(evt) { let content = this.contentBrowser.contentWindow; switch (evt.type) { case 'keydown': case 'keyup': case 'keypress': - // For debug purposes and because some of the APIs are not yet exposed - // to the content, let's react on some of the keyup events. - if (evt.type == 'keyup' && evt.eventPhase == evt.BUBBLING_PHASE) { - switch (evt.keyCode) { - case evt.DOM_VK_PAGE_DOWN: - this.changeVolume(-1); - break; - - case evt.DOM_VK_PAGE_UP: - this.changeVolume(1); - break; - } - } - // Redirect the HOME key to System app and stop the applications from // handling it. let rootContentEvt = (evt.target.ownerDocument.defaultView == content); if (!rootContentEvt && evt.eventPhase == evt.CAPTURING_PHASE && evt.keyCode == evt.DOM_VK_HOME) { this.forwardKeyToContent(evt); evt.preventDefault(); evt.stopImmediatePropagation();
--- a/b2g/confvars.sh +++ b/b2g/confvars.sh @@ -1,16 +1,16 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. MOZ_APP_BASENAME=B2G MOZ_APP_VENDOR=Mozilla -MOZ_APP_VERSION=16.0a1 +MOZ_APP_VERSION=17.0a1 MOZ_APP_UA_NAME=Firefox MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official # MOZ_APP_DISPLAYNAME is set by branding/configure.sh MOZ_SAFE_BROWSING= MOZ_SERVICES_SYNC=1
--- a/browser/app/blocklist.xml +++ b/browser/app/blocklist.xml @@ -1,10 +1,10 @@ <?xml version="1.0"?> -<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1341001222000"> +<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1341959296000"> <emItems> <emItem blockID="i58" id="webmaster@buzzzzvideos.info"> <versionRange minVersion="0" maxVersion="*"> </versionRange> </emItem> <emItem blockID="i86" id="{45147e67-4020-47e2-8f7a-55464fb535aa}"> <versionRange minVersion="0" maxVersion="*"> </versionRange> @@ -23,17 +23,17 @@ </emItem> <emItem blockID="i43" id="supportaccessplugin@gmail.com"> </emItem> <emItem blockID="i82" id="{8f42fb8b-b6f6-45de-81c0-d6d39f54f971}"> <versionRange minVersion="0" maxVersion="*"> </versionRange> </emItem> <emItem blockID="i107" os="WINNT" id="{ABDE892B-13A8-4d1b-88E6-365A6E755758}"> - <versionRange minVersion="0" maxVersion="15.0.4" severity="1"> + <versionRange minVersion="0" maxVersion="15.0.5" severity="1"> </versionRange> </emItem> <emItem blockID="i88" id="anttoolbar@ant.com"> <versionRange minVersion="2.4.6.4" maxVersion="2.4.6.4" severity="1"> </versionRange> </emItem> <emItem blockID="i17" id="{3252b9ae-c69a-4eaf-9502-dc9c1f6c009e}"> <versionRange minVersion="2.2" maxVersion="2.2"> @@ -210,16 +210,20 @@ <emItem blockID="i109" id="{392e123b-b691-4a5e-b52f-c4c1027e749c}"> <versionRange minVersion="0" maxVersion="*"> </versionRange> </emItem> <emItem blockID="i60" id="youtb3@youtb3.com"> <versionRange minVersion="0" maxVersion="*"> </versionRange> </emItem> + <emItem blockID="i111" os="WINNT" id="{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}"> + <versionRange minVersion="0" maxVersion="15.0.5" severity="1"> + </versionRange> + </emItem> <emItem blockID="i38" id="{B7082FAA-CB62-4872-9106-E42DD88EDE45}"> <versionRange minVersion="0.1" maxVersion="3.3.0.*"> <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"> <versionRange minVersion="3.7a1" maxVersion="*" /> </targetApplication> </versionRange> <versionRange minVersion="3.3.1" maxVersion="*"> <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"> @@ -323,17 +327,17 @@ <versionRange minVersion="0" maxVersion="*"> </versionRange> </emItem> <emItem blockID="i77" id="{fa277cfc-1d75-4949-a1f9-4ac8e41b2dfd}"> <versionRange minVersion="0" maxVersion="*"> </versionRange> </emItem> <emItem blockID="i106" os="WINNT" id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}"> - <versionRange minVersion="0" maxVersion="15.0.4" severity="1"> + <versionRange minVersion="0" maxVersion="15.0.5" severity="1"> </versionRange> </emItem> <emItem blockID="i11" id="yslow@yahoo-inc.com"> <versionRange minVersion="2.0.5" maxVersion="2.0.5"> <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"> <versionRange minVersion="3.5.7" maxVersion="*" /> </targetApplication> </versionRange>
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1179,9 +1179,9 @@ pref("pdfjs.previousHandler.preferredAct pref("pdfjs.previousHandler.alwaysAskBeforeHandling", false); // The maximum amount of decoded image data we'll willingly keep around (we // might keep around more than this, but we'll try to get down to this value). // (This is intentionally on the high side; see bug 746055.) pref("image.mem.max_decoded_image_kb", 256000); // Example social provider -pref("social.manifest.motown", "{\"origin\":\"https://motown-dev.mozillalabs.com\",\"name\":\"MoTown\",\"workerURL\":\"https://motown-dev.mozillalabs.com/social/worker.js\"}"); +pref("social.manifest.motown", "{\"origin\":\"https://motown-dev.mozillalabs.com\",\"name\":\"MoTown\",\"workerURL\":\"https://motown-dev.mozillalabs.com/social/worker.js\",\"iconURL\":\"https://motown-dev.mozillalabs.com/images/motown-icon.png\"}");
--- a/browser/base/content/aboutRobots.xhtml +++ b/browser/base/content/aboutRobots.xhtml @@ -44,17 +44,17 @@ <style type="text/css"><![CDATA[ #errorPageContainer:before { content: url('chrome://browser/content/aboutRobots-icon.png'); position: absolute; } body[dir=rtl] #icon, body[dir=rtl] #errorPageContainer:before { - -moz-transform: scaleX(-1); + transform: scaleX(-1); } ]]></style> </head> <body dir="&locale.dir;"> <!-- PAGE CONTAINER (for styling purposes only) --> <div id="errorPageContainer"> @@ -92,13 +92,13 @@ <!-- Button --> <button id="errorTryAgain" label2="&robots.dontpress;" onclick="robotButton();">&retry.label;</button> <img src="chrome://browser/content/aboutRobots-widget-left.png" style="position: absolute; bottom: -12px; left: -10px;"/> <img src="chrome://browser/content/aboutRobots-widget-left.png" - style="position: absolute; bottom: -12px; right: -10px; -moz-transform: scaleX(-1);"/> + style="position: absolute; bottom: -12px; right: -10px; transform: scaleX(-1);"/> </div> </body> </html>
--- a/browser/base/content/abouthome/aboutHome.css +++ b/browser/base/content/abouthome/aboutHome.css @@ -38,16 +38,17 @@ a { } #topSection { text-align: center; } #brandLogo { height: 154px; + width: 154px; margin: 22px 0 31px; } #searchForm, #snippets { width: 470px; } @@ -59,16 +60,18 @@ a { display: -moz-box; -moz-box-align: center; padding-top: 2px; -moz-padding-end: 8px; } #searchEngineLogo { display: inline-block; + height: 28px; + width: 70px; } #searchText { -moz-box-flex: 1; padding: 6px 8px; background: hsla(0,0%,100%,.9) padding-box; border: 1px solid; border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2); @@ -77,17 +80,18 @@ a { 0 1px 0 hsla(0,0%,100%,.2); border-radius: 2.5px 0 0 2.5px; } body[dir=rtl] #searchText { border-radius: 0 2.5px 2.5px 0; } -#searchText:focus { +#searchText:focus, +#searchText[autofocus] { border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6); } #searchSubmit { -moz-margin-start: -1px; background: -moz-linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box; padding: 0 9px; border: 1px solid; @@ -101,22 +105,24 @@ body[dir=rtl] #searchText { transition-duration: 150ms; } body[dir=rtl] #searchSubmit { border-radius: 2.5px 0 0 2.5px; } #searchText:focus + #searchSubmit, -#searchText + #searchSubmit:hover { +#searchText + #searchSubmit:hover, +#searchText[autofocus] + #searchSubmit { border-color: #59b5fc #45a3e7 #3294d5; color: white; } -#searchText:focus + #searchSubmit { +#searchText:focus + #searchSubmit, +#searchText[autofocus] + #searchSubmit { background-image: -moz-linear-gradient(#4cb1ff, #1793e5); box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset, 0 0 0 1px hsla(0,0%,100%,.1) inset, 0 1px 0 hsla(210,54%,20%,.03); } #searchText + #searchSubmit:hover { background-image: -moz-linear-gradient(#66bdff, #0d9eff); @@ -155,16 +161,22 @@ body[dir=rtl] #defaultSnippet2 { } #snippets { display: inline-block; text-align: start; margin: 12px 0; color: #3c3c3c; font-size: 75%; + /* 12px is the computed font size, 15px the computed line height of the snippets + with Segoe UI on a default Windows 7 setup. The 15/12 multiplier approximately + converts em from units of font-size to units of line-height. The goal is to + preset the height of a three-line snippet to avoid visual moving/flickering as + the snippets load. */ + min-height: -moz-calc(15/12 * 3em); } #launcher { display: -moz-box; -moz-box-align: center; -moz-box-pack: center; width: 100%; background-color: hsla(0,0%,0%,.03); @@ -251,18 +263,19 @@ body[narrow] #restorePreviousSessionSepa } body[narrow] #restorePreviousSession { font-size: 80%; } .launchButton::before { display: block; + width: 32px; height: 32px; - margin-bottom: 6px; + margin: 0 auto 6px; line-height: 0; /* remove extra vertical space due to non-zero font-size */ } #downloads::before { content: url("chrome://browser/content/abouthome/downloads.png"); } #bookmarks::before { @@ -295,17 +308,17 @@ body[narrow] #restorePreviousSession { width: 48px; display: inline-block; /* display on same line as text label */ vertical-align: middle; margin-bottom: 0; -moz-margin-end: 8px; } body[dir=rtl] #restorePreviousSession::before { - -moz-transform: scaleX(-1); + transform: scaleX(-1); } body[narrow] #restorePreviousSession::before { content: url("chrome://browser/content/abouthome/restore.png"); height: 32px; width: 32px; }
--- a/browser/base/content/abouthome/aboutHome.js +++ b/browser/base/content/abouthome/aboutHome.js @@ -160,16 +160,25 @@ function setupSearchEngine() // Add search engine logo. if (gSearchEngine.image) { let logoElt = document.getElementById("searchEngineLogo"); logoElt.src = gSearchEngine.image; logoElt.alt = gSearchEngine.name; } + // The "autofocus" attribute doesn't focus the form element + // immediately when the element is first drawn, so the + // attribute is also used for styling when the page first loads. + let searchText = document.getElementById("searchText"); + searchText.addEventListener("blur", function searchText_onBlur() { + searchText.removeEventListener("blur", searchText_onBlur); + searchText.removeAttribute("autofocus"); + }); + } function loadSnippets() { // Check last snippets update. let lastUpdate = localStorage["snippets-last-update"]; let updateURL = localStorage["snippets-update-url"]; if (updateURL && (!lastUpdate ||
--- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -100,16 +100,18 @@ <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true"/> <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/> <command id="Tools:Sanitize" oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/> <command id="Tools:PrivateBrowsing" oncommand="gPrivateBrowsingUI.toggleMode();"/> <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/> <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/> <command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/> + <command id="Social:SharePage" oncommand="SocialShareButton.sharePage();"/> + <command id="Social:UnsharePage" oncommand="SocialShareButton.unsharePage();"/> </commandset> <commandset id="placesCommands"> <command id="Browser:ShowAllBookmarks" oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/> <command id="Browser:ShowAllHistory" oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/> </commandset> @@ -332,16 +334,17 @@ <key id="manBookmarkKb" key="&bookmarksGtkCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/> #endif <key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/> #ifdef XP_WIN # Cmd+I is conventially mapped to Info on MacOS X, thus it should not be # overridden for other purposes there. <key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/> #endif + <key id="sharePage" key="&sharePageCmd.commandkey;" command="Social:SharePage" modifiers="accel,shift"/> # don't use |command="Browser:Stop"|, ESC is being used to freeze animated gifs, # even if the stop button and menuitem are disabled (see Bug 284140) <key id="key_stop" keycode="VK_ESCAPE" oncommand="BrowserStop();"/> #ifdef XP_MACOSX <key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" oncommand="BrowserStop();"/> #endif
new file mode 100644 --- /dev/null +++ b/browser/base/content/browser-social.js @@ -0,0 +1,256 @@ +// 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/. + +let SocialUI = { + // Called on delayed startup to initialize UI + init: function SocialUI_init() { + Services.obs.addObserver(this, "social:pref-changed", false); + Services.obs.addObserver(this, "social:ambient-notification-changed", false); + Services.obs.addObserver(this, "social:profile-changed", false); + + Social.init(this._providerReady.bind(this)); + }, + + // Called on window unload + uninit: function SocialUI_uninit() { + Services.obs.removeObserver(this, "social:pref-changed"); + Services.obs.removeObserver(this, "social:ambient-notification-changed"); + Services.obs.removeObserver(this, "social:profile-changed"); + }, + + showProfile: function SocialUI_showProfile() { + if (Social.provider) + openUILink(Social.provider.profile.profileURL); + }, + + observe: function SocialUI_observe(subject, topic, data) { + switch (topic) { + case "social:pref-changed": + SocialShareButton.updateButtonHiddenState(); + SocialToolbar.updateButtonHiddenState(); + break; + case "social:ambient-notification-changed": + SocialToolbar.updateButton(); + break; + case "social:profile-changed": + SocialToolbar.updateProfile(); + break; + } + }, + + // Called once Social.jsm's provider has been set + _providerReady: function SocialUI_providerReady() { + SocialToolbar.init(); + SocialShareButton.init(); + } +} + +let SocialShareButton = { + init: function SSB_init() { + this.sharePopup.hidden = false; + this.updateButtonHiddenState(); + + let profileRow = document.getElementById("editSharePopupHeader"); + let profile = Social.provider.profile; + if (profile && profile.portrait && profile.displayName) { + profileRow.hidden = false; + let portrait = document.getElementById("socialUserPortrait"); + portrait.style.listStyleImage = profile.portrait; + let displayName = document.getElementById("socialUserDisplayName"); + displayName.setAttribute("label", profile.displayName); + } else { + profileRow.hidden = true; + } + }, + + get shareButton() { + return document.getElementById("share-button"); + }, + get sharePopup() { + return document.getElementById("editSharePopup"); + }, + + dismissSharePopup: function SSB_dismissSharePopup() { + this.sharePopup.hidePopup(); + }, + + updateButtonHiddenState: function SSB_updateButtonHiddenState() { + let shareButton = this.shareButton; + if (shareButton) + shareButton.hidden = !Social.uiVisible; + }, + + onClick: function SSB_onClick(aEvent) { + if (aEvent.button != 0) + return; + + // Don't bubble to the textbox, to avoid unwanted selection of the address. + aEvent.stopPropagation(); + + this.sharePage(); + }, + + panelShown: function SSB_panelShown(aEvent) { + let sharePopupOkButton = document.getElementById("editSharePopupOkButton"); + if (sharePopupOkButton) + sharePopupOkButton.focus(); + }, + + sharePage: function SSB_sharePage() { + let uri = gBrowser.currentURI; + if (!Social.isPageShared(uri)) { + Social.sharePage(uri); + this.updateShareState(); + } else { + this.sharePopup.openPopup(this.shareButton, "bottomcenter topright"); + } + }, + + unsharePage: function SSB_unsharePage() { + Social.unsharePage(gBrowser.currentURI); + this.updateShareState(); + this.dismissSharePopup(); + }, + + updateShareState: function SSB_updateShareState() { + let currentPageShared = Social.isPageShared(gBrowser.currentURI); + + // Provide a11y-friendly notification of share. + let status = document.getElementById("share-button-status"); + if (status) { + let statusString = currentPageShared ? + gNavigatorBundle.getString("social.pageShared.label") : ""; + status.setAttribute("value", statusString); + } + + // Update the share button, if present + let shareButton = this.shareButton; + if (!shareButton) + return; + + if (currentPageShared) { + shareButton.setAttribute("shared", "true"); + shareButton.setAttribute("tooltiptext", gNavigatorBundle.getString("social.shareButton.sharedtooltip")); + } else { + shareButton.removeAttribute("shared"); + shareButton.setAttribute("tooltiptext", gNavigatorBundle.getString("social.shareButton.tooltip")); + } + } +}; + +var SocialToolbar = { + // Called once, after window load, when the Social.provider object is initialized + init: function SocialToolbar_init() { + document.getElementById("social-provider-image").setAttribute("image", Social.provider.iconURL); + + // handle button state + document.getElementById("social-statusarea-popup").addEventListener("popupshowing", function(e) { + document.getElementById("social-toolbar-button").setAttribute("open", "true"); + }, false); + document.getElementById("social-statusarea-popup").addEventListener("popuphiding", function(e) { + document.getElementById("social-toolbar-button").removeAttribute("open"); + }, false); + + this.updateButton(); + this.updateProfile(); + }, + + updateButtonHiddenState: function SocialToolbar_updateButtonHiddenState() { + let toolbarbutton = document.getElementById("social-toolbar-button"); + toolbarbutton.hidden = !Social.uiVisible; + }, + + updateProfile: function SocialToolbar_updateProfile() { + // Profile may not have been initialized yet, since it depends on a worker + // response. In that case we'll be called again when it's available, via + // social:profile-changed + let profile = Social.provider.profile || {}; + let userPortrait = profile.portrait || "chrome://browser/skin/social/social.png"; + document.getElementById("social-statusarea-user-portrait").setAttribute("src", userPortrait); + + let notLoggedInLabel = document.getElementById("social-statusarea-notloggedin"); + let userNameBtn = document.getElementById("social-statusarea-username"); + if (profile.userName) { + notLoggedInLabel.hidden = true; + userNameBtn.hidden = false; + userNameBtn.label = profile.userName; + } else { + notLoggedInLabel.hidden = false; + userNameBtn.hidden = true; + } + }, + + updateButton: function SocialToolbar_updateButton() { + this.updateButtonHiddenState(); + + let provider = Social.provider; + // if there are no ambient icons, we collapse them in the following loop + let iconNames = Object.keys(provider.ambientNotificationIcons); + let iconBox = document.getElementById("social-status-iconbox"); + for (var i = 0; i < iconBox.childNodes.length; i++) { + let iconContainer = iconBox.childNodes[i]; + if (i > iconNames.length - 1) { + iconContainer.collapsed = true; + continue; + } + + iconContainer.collapsed = false; + let icon = provider.ambientNotificationIcons[iconNames[i]]; + let iconImage = iconContainer.firstChild; + let iconCounter = iconImage.nextSibling; + + iconImage.setAttribute("contentPanel", icon.contentPanel); + iconImage.setAttribute("src", icon.iconURL); + + if (iconCounter.firstChild) + iconCounter.removeChild(iconCounter.firstChild); + + if (icon.counter) { + iconCounter.appendChild(document.createTextNode(icon.counter)); + iconCounter.collapsed = false; + } else { + iconCounter.collapsed = true; + } + } + }, + + showAmbientPopup: function SocialToolbar_showAmbientPopup(iconContainer) { + let iconImage = iconContainer.firstChild; + let panel = document.getElementById("social-notification-panel"); + let notifBrowser = document.getElementById("social-notification-browser"); + + panel.hidden = false; + + function sizePanelToContent() { + // XXX Maybe we can use nsIDOMWindowUtils.getRootBounds() here? + // XXX need to handle dynamic sizing + let doc = notifBrowser.contentDocument; + // XXX "notif" is an implementation detail that we should get rid of + // eventually + let body = doc.getElementById("notif") || doc.body.firstChild; + if (!body) + return; + let h = body.scrollHeight > 0 ? body.scrollHeight : 300; + notifBrowser.style.width = body.scrollWidth + "px"; + notifBrowser.style.height = h + "px"; + } + + notifBrowser.addEventListener("DOMContentLoaded", function onload() { + notifBrowser.removeEventListener("DOMContentLoaded", onload); + sizePanelToContent(); + }); + + panel.addEventListener("popuphiding", function onpopuphiding() { + panel.removeEventListener("popuphiding", onpopuphiding); + // unload the panel + document.getElementById("social-toolbar-button").removeAttribute("open"); + notifBrowser.setAttribute("src", "about:blank"); + }); + + notifBrowser.service = Social.provider; + notifBrowser.setAttribute("src", iconImage.getAttribute("contentPanel")); + document.getElementById("social-toolbar-button").setAttribute("open", "true"); + panel.openPopup(iconImage, "bottomcenter topleft", 0, 0, false, false); + } +}
--- a/browser/base/content/browser-syncui.js +++ b/browser/base/content/browser-syncui.js @@ -136,17 +136,17 @@ let gSyncUI = { onLoginFinish: function SUI_onLoginFinish() { // Clear out any login failure notifications let title = this._stringBundle.GetStringFromName("error.login.title"); this.clearError(title); }, onSetupComplete: function SUI_onSetupComplete() { - onLoginFinish(); + this.onLoginFinish(); }, onLoginError: function SUI_onLoginError() { // if login fails, any other notifications are essentially moot Weave.Notifications.removeAll(); // if we haven't set up the client, don't show errors if (this._needsSetup()) {
--- a/browser/base/content/browser-tabPreviews.js +++ b/browser/base/content/browser-tabPreviews.js @@ -92,17 +92,16 @@ var tabPreviews = { var tabPreviewPanelHelper = { opening: function (host) { host.panel.hidden = false; var handler = this._generateHandler(host); host.panel.addEventListener("popupshown", handler, false); host.panel.addEventListener("popuphiding", handler, false); - host.panel.addEventListener("popuphidden", handler, false); host._prevFocus = document.commandDispatcher.focusedElement; }, _generateHandler: function (host) { var self = this; return function (event) { if (event.target == host.panel) { host.panel.removeEventListener(event.type, arguments.callee, false); @@ -125,21 +124,16 @@ var tabPreviewPanelHelper = { host._prevFocus = null; } else gBrowser.selectedBrowser.focus(); if (host.tabToSelect) { gBrowser.selectedTab = host.tabToSelect; host.tabToSelect = null; } - }, - _popuphidden: function (host) { - // Destroy the widget in order to prevent outdated content - // when re-opening the panel. - host.panel.hidden = true; } }; /** * Ctrl-Tab panel */ var ctrlTab = { get panel () {
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -129,30 +129,37 @@ XPCOMUtils.defineLazyGetter(this, "Debug }); XPCOMUtils.defineLazyGetter(this, "Tilt", function() { let tmp = {}; Cu.import("resource:///modules/devtools/Tilt.jsm", tmp); return new tmp.Tilt(window); }); +XPCOMUtils.defineLazyGetter(this, "Social", function() { + let tmp = {}; + Cu.import("resource:///modules/Social.jsm", tmp); + return tmp.Social; +}); + let gInitialPages = [ "about:blank", "about:newtab", "about:home", "about:privatebrowsing", "about:sessionrestore" ]; #include browser-addons.js #include browser-feeds.js #include browser-fullScreen.js #include browser-fullZoom.js #include browser-places.js #include browser-plugins.js +#include browser-social.js #include browser-tabPreviews.js #include browser-tabview.js #include browser-thumbnails.js #ifdef MOZ_SERVICES_SYNC #include browser-syncui.js #endif @@ -1241,16 +1248,17 @@ var gBrowserInit = { Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false); Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false); Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false); BrowserOffline.init(); OfflineApps.init(); IndexedDBPromptHelper.init(); gFormSubmitObserver.init(); + SocialUI.init(); AddonManager.addAddonListener(AddonsMgrListener); gBrowser.addEventListener("pageshow", function(evt) { setTimeout(pageShowEventHandlers, 0, evt); }, true); // Ensure login manager is up and running. Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); if (mustLoadSidebar) { @@ -1612,16 +1620,17 @@ var gBrowserInit = { } catch (ex) { Cu.reportError(ex); } BrowserOffline.uninit(); OfflineApps.uninit(); IndexedDBPromptHelper.uninit(); AddonManager.removeAddonListener(AddonsMgrListener); + SocialUI.uninit(); } // Final window teardown, do this last. window.XULBrowserWindow.destroy(); window.XULBrowserWindow = null; window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner @@ -3557,16 +3566,17 @@ function BrowserToolboxCustomizeDone(aTo UpdateUrlbarSearchSplitterState(); setUrlAndSearchBarWidthForConditionalForwardButton(); // Update the urlbar if (gURLBar) { URLBarSetURI(); XULBrowserWindow.asyncUpdateUI(); PlacesStarButton.updateState(); + SocialShareButton.updateShareState(); } TabsInTitlebar.allowedBy("customizing-toolbars", true); // Re-enable parts of the UI we disabled during the dialog var menubar = document.getElementById("main-menubar"); for (var i = 0; i < menubar.childNodes.length; ++i) menubar.childNodes[i].setAttribute("disabled", false); @@ -4035,16 +4045,17 @@ var XULBrowserWindow = { let uri = aLocationURI; try { uri = this._uriFixup.createExposableURI(uri); } catch (e) {} URLBarSetURI(uri); // Update starring UI PlacesStarButton.updateState(); + SocialShareButton.updateShareState(); } // Show or hide browser chrome based on the whitelist if (this.hideChromeForLocation(location)) { document.documentElement.setAttribute("disablechrome", "true"); } else { let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); if (ss.getTabValue(gBrowser.selectedTab, "appOrigin"))
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -177,16 +177,72 @@ class="editBookmarkPanelBottomButton" label="&editBookmark.done.label;" default="true" oncommand="StarUI.panel.hidePopup();"/> #endif </hbox> </panel> + <panel id="editSharePopup" + type="arrow" + orient="vertical" + ignorekeys="true" + hidden="true" + onpopupshown="SocialShareButton.panelShown(event);" + consumeoutsideclicks="true" + level="top"> + <row id="editSharePopupHeader" align="center"> + <vbox align="center"> + <image id="socialUserPortrait" onclick="SocialUI.showProfile();" + aria-label="&social.sharePopup.portrait.arialabel;"/> + </vbox> + <vbox id="editSharePopupText"> + <button id="socialUserDisplayName" + oncommand="SocialUI.showProfile();"/> + <spacer flex="1"/> + <label id="socialUserRecommendedText" + value="&social.sharePopup.shared.label;"/> + </vbox> + </row> + <hbox id="editSharePopupBottomButtons" pack="end"> +#ifdef XP_UNIX + <button id="editSharePopupUndoButton" + class="editSharePopupBottomButton" + label="&social.sharePopup.undo.label;" + accesskey="&social.sharePopup.undo.accesskey;" + command="Social:UnsharePage"/> + <button id="editSharePopupOkButton" + class="editSharePopupBottomButton" + default="true" + autofocus="autofocus" + label="&social.sharePopup.ok.label;" + accesskey="&social.sharePopup.ok.accesskey;" + oncommand="SocialShareButton.dismissSharePopup();"/> +#else + <button id="editSharePopupOkButton" + class="editSharePopupBottomButton" + default="true" + autofocus="autofocus" + label="&social.sharePopup.ok.label;" + accesskey="&social.sharePopup.ok.accesskey;" + oncommand="SocialShareButton.dismissSharePopup();"/> + <button id="editSharePopupUndoButton" + class="editSharePopupBottomButton" + label="&social.sharePopup.undo.label;" + accesskey="&social.sharePopup.undo.accesskey;" + command="Social:UnsharePage"/> +#endif + </hbox> + </panel> + + <panel id="social-notification-panel" type="arrow" hidden="true" noautofocus="true"> + <browser id="social-notification-browser" type="content" flex="1"/> + </panel> + <menupopup id="inspector-node-popup"> <menuitem id="inspectorHTMLCopyInner" label="&inspectorHTMLCopyInner.label;" accesskey="&inspectorHTMLCopyInner.accesskey;" command="Inspector:CopyInner"/> <menuitem id="inspectorHTMLCopyOuter" label="&inspectorHTMLCopyOuter.label;" accesskey="&inspectorHTMLCopyOuter.accesskey;" @@ -459,16 +515,17 @@ autocompletesearch="urlinline history" autocompletesearchparam="enable-actions" autocompletepopup="PopupAutoCompleteRichResult" completeselectedindex="true" tabscrolling="true" showcommentcolumn="true" showimagecolumn="true" enablehistory="true" + maxrows="6" newlines="stripsurroundingwhitespace" oninput="gBrowser.userTypedValue = this.value;" ontextentered="this.handleCommand(param);" ontextreverted="return this.handleRevert();" pageproxystate="invalid" onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'" onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);"> <box id="notification-popup-box" hidden="true" align="center"> @@ -502,16 +559,22 @@ </box> <hbox id="urlbar-icons"> <image id="page-report-button" class="urlbar-icon" hidden="true" tooltiptext="&pageReportIcon.tooltip;" onclick="gPopupBlockerObserver.onReportButtonClick(event);"/> + <label id="share-button-status" collapsed="true" role="status"/> + <image id="share-button" + class="urlbar-icon" + hidden="true" + onclick="SocialShareButton.onClick(event);"/> + <image id="star-button" class="urlbar-icon" onclick="PlacesStarButton.onClick(event);"/> <image id="go-button" class="urlbar-icon" tooltiptext="&goEndCap.tooltip;" onclick="gURLBar.handleCommand(event);"/> </hbox> @@ -553,16 +616,55 @@ label="&homeButton.label;" ondragover="homeButtonObserver.onDragOver(event)" ondragenter="homeButtonObserver.onDragOver(event)" ondrop="homeButtonObserver.onDrop(event)" ondragexit="homeButtonObserver.onDragExit(event)" onclick="BrowserGoHome(event);" aboutHomeOverrideTooltip="&abouthome.pageTitle;"/> + <toolbaritem id="social-toolbar-button" + class="toolbarbutton-1 chromeclass-toolbar-additional" + removable="false" + title="&socialToolbar.title;" + hidden="true"> + <hbox id="social-toolbar-button-box" class="social-statusarea-container"> + <button id="social-provider-image" type="menu"> + <menupopup id="social-statusarea-popup"> + <hbox id="social-statusarea-user" pack="left" align="center"> + <image id="social-statusarea-user-portrait"/> + <vbox> + <label id="social-statusarea-notloggedin" + value="&social.notLoggedIn.label;"/> + <button id="social-statusarea-username" + oncommand="SocialUI.showProfile(); document.getElementById('social-statusarea-popup').hidePopup();"/> + </vbox> + </hbox> + </menupopup> + </button> + <hbox id="social-status-iconbox" flex="1"> + <box class="social-notification-icon-container" collapsed="true" + onclick="SocialToolbar.showAmbientPopup(this);"> + <image class="social-notification-icon-image"/> + <box class="social-notification-icon-counter" collapsed="true"/> + </box> + <box class="social-notification-icon-container" collapsed="true" + onclick="SocialToolbar.showAmbientPopup(this);"> + <image class="social-notification-icon-image"/> + <box class="social-notification-icon-counter" collapsed="true"/> + </box> + <box class="social-notification-icon-container" collapsed="true" + onclick="SocialToolbar.showAmbientPopup(this);"> + <image class="social-notification-icon-image"/> + <box class="social-notification-icon-counter" collapsed="true"/> + </box> + </hbox> + </hbox> + </toolbaritem> + <toolbaritem id="bookmarks-menu-button-container" class="chromeclass-toolbar-additional" removable="true" title="&bookmarksMenuButton.label;"> <toolbarbutton id="bookmarks-menu-button" type="menu" class="toolbarbutton-1" label="&bookmarksMenuButton.label;"
--- a/browser/base/content/newtab/drag.js +++ b/browser/base/content/newtab/drag.js @@ -102,22 +102,27 @@ let gDrag = { }, /** * Checks whether we're responsible for a given drag event. * @param aEvent The drag event to check. * @return Whether we should handle this drag and drop operation. */ isValid: function Drag_isValid(aEvent) { - let dt = aEvent.dataTransfer; - let mimeType = "text/x-moz-url"; + let link = gDragDataHelper.getLinkFromDragEvent(aEvent); // Check that the drag data is non-empty. // Can happen when dragging places folders. - return dt && dt.types.contains(mimeType) && dt.getData(mimeType); + if (!link || !link.url) { + return false; + } + + // Check that we're not accepting URLs which would inherit the caller's + // principal (such as javascript: or data:). + return gLinkChecker.checkLoadURI(link.url); }, /** * Initializes the drag data for the current drag operation. * @param aSite The site that's being dragged. * @param aEvent The 'dragstart' event. */ _setDragData: function Drag_setDragData(aSite, aEvent) {
new file mode 100644 --- /dev/null +++ b/browser/base/content/newtab/dragDataHelper.js @@ -0,0 +1,22 @@ +#ifdef 0 +/* 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/. */ +#endif + +let gDragDataHelper = { + get mimeType() { + return "text/x-moz-url"; + }, + + getLinkFromDragEvent: function DragDataHelper_getLinkFromDragEvent(aEvent) { + let dt = aEvent.dataTransfer; + if (!dt || !dt.types.contains(this.mimeType)) { + return null; + } + + let data = dt.getData(this.mimeType) || ""; + let [url, title] = data.split(/[\r\n]+/); + return {url: url, title: title}; + } +};
--- a/browser/base/content/newtab/drop.js +++ b/browser/base/content/newtab/drop.js @@ -85,24 +85,24 @@ let gDrop = { let index = aCell.index; let draggedSite = gDrag.draggedSite; if (draggedSite) { // Pin the dragged site at its new place. if (aCell != draggedSite.cell) draggedSite.pin(index); } else { - // A new link was dragged onto the grid. Create it by pinning its URL. - let dt = aEvent.dataTransfer; - let [url, title] = dt.getData("text/x-moz-url").split(/[\r\n]+/); - let link = {url: url, title: title}; - gPinnedLinks.pin(link, index); + let link = gDragDataHelper.getLinkFromDragEvent(aEvent); + if (link) { + // A new link was dragged onto the grid. Create it by pinning its URL. + gPinnedLinks.pin(link, index); - // Make sure the newly added link is not blocked. - gBlockedLinks.unblock(link); + // Make sure the newly added link is not blocked. + gBlockedLinks.unblock(link); + } } }, /** * Time a rearrange with a little delay. * @param aCell The drop target cell. */ _delayedRearrange: function Drop_delayedRearrange(aCell) {
--- a/browser/base/content/newtab/newTab.js +++ b/browser/base/content/newtab/newTab.js @@ -13,16 +13,17 @@ Cu.import("resource:///modules/PageThumb Cu.import("resource:///modules/NewTabUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm"); let { links: gLinks, allPages: gAllPages, + linkChecker: gLinkChecker, pinnedLinks: gPinnedLinks, blockedLinks: gBlockedLinks } = NewTabUtils; XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() { return Services.strings. createBundle("chrome://browser/locale/newTab.properties"); }); @@ -48,15 +49,16 @@ const HTML_NAMESPACE = "http://www.w3.or #include batch.js #include transformations.js #include page.js #include grid.js #include cells.js #include sites.js #include drag.js +#include dragDataHelper.js #include drop.js #include dropTargetShim.js #include dropPreview.js #include updater.js // Everything is loaded. Initialize the New Tab Page. gPage.init();
--- a/browser/base/content/sync/utils.js +++ b/browser/base/content/sync/utils.js @@ -192,18 +192,16 @@ let gSyncUtils = { if (!el2) valid = val1.length >= Weave.MIN_PASS_LENGTH; else if (val1 && val1 == Weave.Identity.username) error = "change.password.pwSameAsUsername"; else if (val1 && val1 == Weave.Identity.account) error = "change.password.pwSameAsEmail"; else if (val1 && val1 == Weave.Identity.basicPassword) error = "change.password.pwSameAsPassword"; - else if (val1 && val1 == Weave.Identity.syncKey) - error = "change.password.pwSameAsRecoveryKey"; else if (val1 && val2) { if (val1 == val2 && val1.length >= Weave.MIN_PASS_LENGTH) valid = true; else if (val1.length < Weave.MIN_PASS_LENGTH) error = "change.password.tooShort"; else if (val1 != val2) error = "change.password.mismatch"; }
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -3492,17 +3492,17 @@ } ind.collapsed = false; newMargin += ind.clientWidth / 2; if (!ltr) newMargin *= -1; - ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)"; + ind.style.transform = "translate(" + Math.round(newMargin) + "px)"; ind.style.MozMarginStart = (-ind.clientWidth) + "px"; ]]></handler> <handler event="drop"><![CDATA[ var dt = event.dataTransfer; var dropEffect = dt.dropEffect; var draggedTab; if (dropEffect != "link") { // copy or move
--- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -57,16 +57,18 @@ endif # browser_sanitizeDialog_treeView.js is disabled until the tree view is added # back to the clear recent history dialog (sanitize.xul), if it ever is (bug # 480169) # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638. # browser_bug321000.js is disabled because newline handling is shaky (bug 592528) +# browser_pageInfo.js + feed_tab.html is disabled for leaking (bug 767896) + _BROWSER_FILES = \ head.js \ browser_typeAheadFind.js \ browser_keywordSearch.js \ browser_allTabsPanel.js \ browser_alltabslistener.js \ browser_bug304198.js \ title_test.svg \ @@ -149,16 +151,17 @@ endif browser_bug710878.js \ browser_bug719271.js \ browser_bug724239.js \ browser_bug735471.js \ browser_bug743421.js \ browser_bug749738.js \ browser_bug763468.js \ browser_bug767836.js \ + browser_shareButton.js \ browser_canonizeURL.js \ browser_customize.js \ browser_findbarClose.js \ browser_homeDrop.js \ browser_keywordBookmarklets.js \ browser_contextSearchTabPosition.js \ browser_ctrlTab.js \ browser_customize_popupNotification.js \ @@ -166,17 +169,16 @@ endif browser_discovery.js \ browser_duplicateIDs.js \ browser_gestureSupport.js \ browser_getshortcutoruri.js \ browser_hide_removing.js \ browser_overflowScroll.js \ browser_locationBarCommand.js \ browser_locationBarExternalLoad.js \ - browser_pageInfo.js \ browser_page_style_menu.js \ browser_pinnedTabs.js \ browser_plainTextLinks.js \ browser_pluginnotification.js \ browser_relatedTabs.js \ browser_sanitize-passwordDisabledHosts.js \ browser_sanitize-sitepermissions.js \ browser_sanitize-timespans.js \ @@ -214,17 +216,16 @@ endif disablechrome.html \ discovery.html \ domplate_test.js \ moz.png \ video.ogg \ test_bug435035.html \ test_bug462673.html \ page_style_sample.html \ - feed_tab.html \ plugin_unknown.html \ plugin_test.html \ plugin_test2.html \ plugin_test3.html \ plugin_alternate_content.html \ plugin_both.html \ plugin_both2.html \ plugin_bug743421.html \ @@ -250,16 +251,17 @@ endif authenticate.sjs \ browser_minimize.js \ browser_aboutSyncProgress.js \ browser_middleMouse_inherit.js \ redirect_bug623155.sjs \ browser_tabDrop.js \ browser_lastAccessedTab.js \ browser_bug734076.js \ + browser_social_toolbar.js \ $(NULL) ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT)) _BROWSER_FILES += \ browser_bug462289.js \ $(NULL) else _BROWSER_FILES += \
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/browser_shareButton.js @@ -0,0 +1,160 @@ +/* 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/. */ + +let prefName = "social.enabled", + shareButton, + sharePopup, + okButton, + undoButton; + +function test() { + waitForExplicitFinish(); + + // Need to load a non-empty page for the social share button to appear + let tab = gBrowser.selectedTab = gBrowser.addTab("about:", {skipAnimation: true}); + tab.linkedBrowser.addEventListener("load", function tabLoad(event) { + tab.linkedBrowser.removeEventListener("load", tabLoad, true); + executeSoon(tabLoaded); + }, true); + + // Enable the service to start + Services.prefs.setBoolPref(prefName, true); + + registerCleanupFunction(function() { + Services.prefs.clearUserPref(prefName); + gBrowser.removeTab(tab); + }); +} + +function tabLoaded() { + ok(Social, "Social module loaded"); + + // If the UI is already active, run the test immediately, otherwise wait + // for initialization. + if (Social.provider) { + executeSoon(testInitial); + } else { + Services.obs.addObserver(function obs() { + Services.obs.removeObserver(obs, "test-social-ui-ready"); + executeSoon(testInitial); + }, "test-social-ui-ready", false); + } +} + +function testInitial() { + ok(Social.provider, "Social provider is active"); + ok(Social.provider.enabled, "Social provider is enabled"); + ok(Social.provider.port, "Social provider has a port to its FrameWorker"); + + shareButton = SocialShareButton.shareButton; + sharePopup = SocialShareButton.sharePopup; + ok(shareButton, "share button exists"); + ok(sharePopup, "share popup exists"); + ok(!sharePopup.hidden, "share popup is not hidden"); + + okButton = document.getElementById("editSharePopupOkButton"); + undoButton = document.getElementById("editSharePopupUndoButton"); + + is(shareButton.hidden, false, "share button should be visible"); + + // Test clicking the share button + shareButton.addEventListener("click", function listener() { + shareButton.removeEventListener("click", listener); + is(shareButton.hasAttribute("shared"), true, "Share button should have 'shared' attribute after share button is clicked"); + executeSoon(testSecondClick.bind(window, testPopupOKButton)); + }); + EventUtils.synthesizeMouseAtCenter(shareButton, {}); +} + +function testSecondClick(nextTest) { + sharePopup.addEventListener("popupshown", function listener() { + sharePopup.removeEventListener("popupshown", listener); + ok(true, "popup was shown after second click"); + executeSoon(nextTest); + }); + EventUtils.synthesizeMouseAtCenter(shareButton, {}); +} + +function testPopupOKButton() { + sharePopup.addEventListener("popuphidden", function listener() { + sharePopup.removeEventListener("popuphidden", listener); + is(shareButton.hasAttribute("shared"), true, "Share button should still have 'shared' attribute after OK button is clicked"); + executeSoon(testSecondClick.bind(window, testPopupUndoButton)); + }); + EventUtils.synthesizeMouseAtCenter(okButton, {}); +} + +function testPopupUndoButton() { + sharePopup.addEventListener("popuphidden", function listener() { + sharePopup.removeEventListener("popuphidden", listener); + is(shareButton.hasAttribute("shared"), false, "Share button should not have 'shared' attribute after Undo button is clicked"); + executeSoon(testShortcut); + }); + EventUtils.synthesizeMouseAtCenter(undoButton, {}); +} + +function testShortcut() { + let keyTarget = window; + keyTarget.addEventListener("keyup", function listener() { + keyTarget.removeEventListener("keyup", listener); + executeSoon(checkShortcutWorked.bind(window, keyTarget)); + }); + EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget); +} + +function checkShortcutWorked(keyTarget) { + is(shareButton.hasAttribute("shared"), true, "Share button should be in the 'shared' state after keyboard shortcut is used"); + + // Test a second invocation of the shortcut + sharePopup.addEventListener("popupshown", function listener() { + sharePopup.removeEventListener("popupshown", listener); + ok(true, "popup was shown after second use of keyboard shortcut"); + executeSoon(checkOKButton); + }); + EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget); +} + +function checkOKButton() { + is(document.activeElement, okButton, "ok button should be focused by default"); + checkNextInTabOrder(undoButton, function () { + checkNextInTabOrder(okButton, testCloseBySpace); + }); +} + +function checkNextInTabOrder(element, next) { + // This particular test doesn't really apply on Mac, since buttons aren't + // focusable by default. + if (navigator.platform.indexOf("Mac") != -1) { + executeSoon(next); + return; + } + + function listener() { + element.removeEventListener("focus", listener); + is(document.activeElement, element, element.id + " should be next in tab order"); + executeSoon(next); + } + element.addEventListener("focus", listener); + // Register a cleanup function to remove the listener in case this test fails + registerCleanupFunction(function () { + element.removeEventListener("focus", listener); + }); + EventUtils.synthesizeKey("VK_TAB", {}); +} + +function testCloseBySpace() { + is(document.activeElement.id, okButton.id, "testCloseBySpace, the ok button should be focused"); + sharePopup.addEventListener("popuphidden", function listener() { + sharePopup.removeEventListener("popuphidden", listener); + ok(true, "space closed the share popup"); + executeSoon(testDisable); + }); + EventUtils.synthesizeKey("VK_SPACE", {}); +} + +function testDisable() { + Services.prefs.setBoolPref(prefName, false); + is(shareButton.hidden, true, "Share button should be hidden when pref is disabled"); + finish(); +}
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/browser_social_toolbar.js @@ -0,0 +1,140 @@ +/* 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/. */ + +let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService; +let gProvider; + +function test() { + waitForExplicitFinish(); + + Services.prefs.setBoolPref("social.enabled", true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref("social.enabled"); + }); + + let oldProvider; + function saveOldProviderAndStartTestWith(provider) { + oldProvider = Social.provider; + registerCleanupFunction(function () { + Social.provider = oldProvider; + }); + Social.provider = gProvider = provider; + runTests(tests, undefined, undefined, function () { + SocialService.removeProvider(provider.origin, finish); + }); + } + + let manifest = { // normal provider + name: "provider 1", + origin: "https://example1.com", + workerURL: "https://example1.com/worker.js", + iconURL: "chrome://branding/content/icon48.png" + }; + SocialService.addProvider(manifest, function(provider) { + // If the UI is already active, run the test immediately, otherwise wait + // for initialization. + if (Social.provider) { + saveOldProviderAndStartTestWith(provider); + } else { + Services.obs.addObserver(function obs() { + Services.obs.removeObserver(obs, "test-social-ui-ready"); + saveOldProviderAndStartTestWith(provider); + }, "test-social-ui-ready", false); + } + }); +} + +var tests = { + testProfileSet: function(next) { + let profile = { + portrait: "chrome://branding/content/icon48.png", + userName: "trickster", + displayName: "Kuma Lisa", + profileURL: "http://en.wikipedia.org/wiki/Kuma_Lisa" + } + gProvider.updateUserProfile(profile); + // check dom values + let portrait = document.getElementById("social-statusarea-user-portrait").getAttribute("src"); + is(portrait, profile.portrait, "portrait is set"); + let userButton = document.getElementById("social-statusarea-username"); + ok(!userButton.hidden, "username is visible"); + is(userButton.label, profile.userName, "username is set"); + next(); + }, + testAmbientNotifications: function(next) { + let ambience = { + name: "testIcon", + iconURL: "chrome://branding/content/icon48.png", + contentPanel: "about:blank", + counter: 42 + }; + gProvider.setAmbientNotification(ambience); + + let statusIcons = document.getElementById("social-status-iconbox"); + ok(!statusIcons.firstChild.collapsed, "status icon is visible"); + ok(!statusIcons.firstChild.lastChild.collapsed, "status value is visible"); + is(statusIcons.firstChild.lastChild.textContent, "42", "status value is correct"); + + ambience.counter = 0; + gProvider.setAmbientNotification(ambience); + ok(statusIcons.firstChild.lastChild.collapsed, "status value is not visible"); + is(statusIcons.firstChild.lastChild.textContent, "", "status value is correct"); + next(); + }, + testProfileUnset: function(next) { + gProvider.updateUserProfile({}); + // check dom values + let portrait = document.getElementById("social-statusarea-user-portrait").getAttribute("src"); + is(portrait, "chrome://browser/skin/social/social.png", "portrait is generic"); + let userButton = document.getElementById("social-statusarea-username"); + ok(userButton.hidden, "username is not visible"); + let ambience = document.getElementById("social-status-iconbox").firstChild; + while (ambience) { + ok(ambience.collapsed, "ambient icon is collapsed"); + ambience = ambience.nextSibling; + } + + next(); + } +} + +function runTests(tests, cbPreTest, cbPostTest, cbFinish) { + let testIter = Iterator(tests); + + if (cbPreTest === undefined) { + cbPreTest = function(cb) {cb()}; + } + if (cbPostTest === undefined) { + cbPostTest = function(cb) {cb()}; + } + + function runNextTest() { + let name, func; + try { + [name, func] = testIter.next(); + } catch (err if err instanceof StopIteration) { + // out of items: + (cbFinish || finish)(); + return; + } + // We run on a timeout as the frameworker also makes use of timeouts, so + // this helps keep the debug messages sane. + executeSoon(function() { + function cleanupAndRunNextTest() { + info("sub-test " + name + " complete"); + cbPostTest(runNextTest); + } + cbPreTest(function() { + info("sub-test " + name + " starting"); + try { + func.call(tests, cleanupAndRunNextTest); + } catch (ex) { + ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack); + cleanupAndRunNextTest(); + } + }) + }); + } + runNextTest(); +}
--- a/browser/base/content/test/newtab/Makefile.in +++ b/browser/base/content/test/newtab/Makefile.in @@ -23,13 +23,14 @@ include $(topsrcdir)/config/rules.mk browser_newtab_unpin.js \ browser_newtab_bug721442.js \ browser_newtab_bug722273.js \ browser_newtab_bug723102.js \ browser_newtab_bug723121.js \ browser_newtab_bug725996.js \ browser_newtab_bug734043.js \ browser_newtab_bug735987.js \ + browser_newtab_bug765628.js \ head.js \ $(NULL) libs:: $(_BROWSER_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug765628.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff"; +const GOOD_DRAG_DATA = "http://example.com/#99\nsite 99"; + +function runTests() { + yield setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield addNewTabPageTab(); + checkGrid("0,1,2,3,4,5,6,7,8"); + + sendDropEvent(0, BAD_DRAG_DATA); + sendDropEvent(1, GOOD_DRAG_DATA); + + yield whenPagesUpdated(); + checkGrid("0,99p,1,2,3,4,5,6,7"); +} + +function sendDropEvent(aCellIndex, aDragData) { + let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor); + let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils); + + let event = createDragEvent("drop", aDragData); + windowUtils.dispatchDOMEventViaPresShell(getCell(aCellIndex).node, event, true); +}
--- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -620,18 +620,16 @@ case "underflow": this._contentIsCropped = false; this._hideURLTooltip(); break; } ]]></body> </method> - <property name="maxRows" onget="return this.popup.maxResults;"/> - <property name="textValue" onget="return this.value;"> <setter> <![CDATA[ try { val = losslessDecodeURI(makeURI(val)); } catch (ex) { }
--- a/browser/branding/nightly/pref/firefox-branding.js +++ b/browser/branding/nightly/pref/firefox-branding.js @@ -1,21 +1,21 @@ /* 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/. */ pref("startup.homepage_override_url","http://www.mozilla.org/projects/%APP%/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%"); pref("startup.homepage_welcome_url","http://www.mozilla.org/projects/%APP%/%VERSION%/firstrun/"); // The time interval between checks for a new version (in seconds) -pref("app.update.interval", 3600); // 1 hour +pref("app.update.interval", 7200); // 2 hours // The time interval between the downloading of mar file chunks in the // background (in seconds) pref("app.update.download.backgroundInterval", 60); -// Give the user x seconds to react before showing the big UI. default=1 hour -pref("app.update.promptWaitTime", 3600); +// Give the user x seconds to react before showing the big UI. default=12 hours +pref("app.update.promptWaitTime", 43200); // URL user can browse to manually if for some reason all update installation // attempts fail. pref("app.update.url.manual", "http://nightly.mozilla.org/"); // A default value for the "More information about this update" link // supplied in the "An update is available" page of the update wizard. pref("app.update.url.details", "http://www.mozilla.org/projects/%APP%/"); // Release notes and vendor URLs
--- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -116,25 +116,20 @@ AboutRedirector::NewChannel(nsIURI *aURI rv = ioService->NewChannel(nsDependentCString(kRedirMap[i].url), nsnull, nsnull, getter_AddRefs(tempChannel)); NS_ENSURE_SUCCESS(rv, rv); tempChannel->SetOriginalURI(aURI); // Keep the page from getting unnecessary privileges unless it needs them if (kRedirMap[i].flags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) { - nsCOMPtr<nsIScriptSecurityManager> securityManager = - do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsIPrincipal> principal; - rv = securityManager->GetCodebasePrincipal(aURI, getter_AddRefs(principal)); - NS_ENSURE_SUCCESS(rv, rv); - - rv = tempChannel->SetOwner(principal); + // Setting the owner to null means that we'll go through the normal + // path in GetChannelPrincipal and create a codebase principal based + // on the channel's originalURI + rv = tempChannel->SetOwner(nsnull); NS_ENSURE_SUCCESS(rv, rv); } NS_ADDREF(*result = tempChannel); return rv; } }
--- a/browser/components/downloads/content/downloadsOverlay.xul +++ b/browser/components/downloads/content/downloadsOverlay.xul @@ -93,16 +93,17 @@ <menuseparator/> <menuitem command="downloadsCmd_clearList" label="&cmd.clearList.label;" accesskey="&cmd.clearList.accesskey;"/> </menupopup> <richlistbox id="downloadsListBox" + style="width: &downloads.width;" class="plain" flex="1" context="downloadsContextMenu" onkeypress="DownloadsView.onDownloadKeyPress(event);" oncontextmenu="DownloadsView.onDownloadContextMenu(event);" ondragstart="DownloadsView.onDownloadDragStart(event);"/> <button id="downloadsHistory"
--- a/browser/components/places/content/browserPlacesViews.js +++ b/browser/components/places/content/browserPlacesViews.js @@ -1599,17 +1599,17 @@ PlacesToolbar.prototype = { translateX += this._rootElt.lastChild.getBoundingClientRect().right; else { translateX += this._rootElt.childNodes[dropPoint.beforeIndex] .getBoundingClientRect().left; } } } - ind.style.MozTransform = "translate(" + Math.round(translateX) + "px)"; + ind.style.transform = "translate(" + Math.round(translateX) + "px)"; ind.style.MozMarginStart = (-ind.clientWidth) + "px"; ind.collapsed = false; // Clear out old folder information. this._clearOverFolder(); } aEvent.preventDefault();
--- a/browser/components/shell/src/nsWindowsShellService.cpp +++ b/browser/components/shell/src/nsWindowsShellService.cpp @@ -189,29 +189,26 @@ static SETTING gDDESettings[] = { { MAKE_KEY_NAME1("Software\\Classes\\FirefoxURL", SOD) }, // Protocol Handlers { MAKE_KEY_NAME1("Software\\Classes\\FTP", SOD) }, { MAKE_KEY_NAME1("Software\\Classes\\HTTP", SOD) }, { MAKE_KEY_NAME1("Software\\Classes\\HTTPS", SOD) } }; -// See Bug 770883 -#if 0 #if defined(MOZ_MAINTENANCE_SERVICE) #define ONLY_SERVICE_LAUNCHING #include "updatehelper.h" #include "updatehelper.cpp" static const char *kPrefetchClearedPref = "app.update.service.lastVersionPrefetchCleared"; static nsCOMPtr<nsIThread> sThread; #endif -#endif nsresult GetHelperPath(nsAutoString& aPath) { nsresult rv; nsCOMPtr<nsIProperties> directoryService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); @@ -999,18 +996,16 @@ nsWindowsShellService::SetDesktopBackgro NS_ENSURE_SUCCESS(rv, rv); return regKey->Close(); } nsWindowsShellService::nsWindowsShellService() : mCheckedThisSession(false) { -// See Bug 770883 -#if 0 #if defined(MOZ_MAINTENANCE_SERVICE) // Check to make sure the service is installed PRUint32 installed = 0; nsCOMPtr<nsIWindowsRegKey> regKey = do_CreateInstance("@mozilla.org/windows-registry-key;1"); if (!regKey || NS_FAILED(regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, @@ -1045,38 +1040,32 @@ nsWindowsShellService::nsWindowsShellSer // service command. mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); if (mTimer) { mTimer->InitWithFuncCallback( nsWindowsShellService::LaunchPrefetchClearCommand, nsnull, CLEAR_PREFETCH_TIMEOUT_MS, nsITimer::TYPE_ONE_SHOT); } #endif -#endif } nsWindowsShellService::~nsWindowsShellService() { -// See Bug 770883 -#if 0 #if defined(MOZ_MAINTENANCE_SERVICE) if (mTimer) { mTimer->Cancel(); mTimer = nsnull; } if (sThread) { sThread->Shutdown(); sThread = nsnull; } #endif -#endif } -// See Bug 770883 -#if 0 #if defined(MOZ_MAINTENANCE_SERVICE) class ClearPrefetchEvent : public nsRunnable { public: ClearPrefetchEvent() { } @@ -1089,29 +1078,26 @@ public: // If this command fails, it is not critical as prefetch will be cleared // on the next software update. StartServiceCommand(NS_ARRAY_LENGTH(updaterServiceArgv), updaterServiceArgv); return NS_OK; } }; #endif -#endif /** * For faster startup we attempt to clear the prefetch if the maintenance * service is installed. Please see the definition of ClearPrefetch() * in toolkit/components/maintenanceservice/prefetch.cpp for more info. * For now the only application that gets prefetch cleaned is Firefox * since we have not done performance checking for other applications. * This is done on every update but also there is a one time operation done * from within the program for first time installs. */ -// See Bug 770883 -#if 0 #if defined(MOZ_MAINTENANCE_SERVICE) void nsWindowsShellService::LaunchPrefetchClearCommand(nsITimer *aTimer, void*) { // Make sure we don't call this again from the application, it will be // called on each application update instead. nsCOMPtr<nsIPrefBranch> prefBranch; nsCOMPtr<nsIPrefService> prefs = @@ -1126,17 +1112,16 @@ nsWindowsShellService::LaunchPrefetchCle // main thread, so start an event on another thread to handle the operation NS_NewThread(getter_AddRefs(sThread)); if (sThread) { nsCOMPtr<nsIRunnable> prefetchEvent = new ClearPrefetchEvent(); sThread->Dispatch(prefetchEvent, NS_DISPATCH_NORMAL); } } #endif -#endif NS_IMETHODIMP nsWindowsShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI) { nsresult rv; nsCOMPtr<nsIProcess> process = do_CreateInstance("@mozilla.org/process/util;1", &rv);
--- a/browser/components/tabview/groupitems.js +++ b/browser/components/tabview/groupitems.js @@ -614,17 +614,17 @@ GroupItem.prototype = Utils.extend(new I GroupItems.updateGroupCloseButtons(); } if (this.hidden || (options && options.immediately)) { destroyGroup(); } else { iQ(this.container).animate({ opacity: 0, - "-moz-transform": "scale(.3)", + "transform": "scale(.3)", }, { duration: 170, complete: destroyGroup }); } this.deleteData(); }, @@ -636,17 +636,17 @@ GroupItem.prototype = Utils.extend(new I if (this._children.length > 0) { this._unfreezeItemSize(); this._children.forEach(function(child) { iQ(child.container).hide(); }); iQ(this.container).animate({ opacity: 0, - "-moz-transform": "scale(.3)", + "transform": "scale(.3)", }, { duration: 170, complete: function() { iQ(this).hide(); } }); this.droppable(false); @@ -722,24 +722,24 @@ GroupItem.prototype = Utils.extend(new I UI.setActive(self); self._sendToSubscribers("groupShown"); }; let $container = iQ(this.container).show(); if (!options || !options.immediately) { $container.animate({ - "-moz-transform": "scale(1)", + "transform": "scale(1)", "opacity": 1 }, { duration: 170, complete: finalize }); } else { - $container.css({"-moz-transform": "none", opacity: 1}); + $container.css({"transform": "none", opacity: 1}); finalize(); } GroupItems.updateGroupCloseButtons(); }, // ---------- // Function: closeHidden @@ -870,25 +870,25 @@ GroupItem.prototype = Utils.extend(new I let undoClose = iQ("<span/>") .addClass("close") .attr("title", tabviewString("groupItem.discardClosedGroup")) .appendTo(this.$undoContainer); this.$undoContainer.css({ left: this.bounds.left + this.bounds.width/2 - iQ(self.$undoContainer).width()/2, top: this.bounds.top + this.bounds.height/2 - iQ(self.$undoContainer).height()/2, - "-moz-transform": "scale(.1)", + "transform": "scale(.1)", opacity: 0 }); this.hidden = true; // hide group item and show undo container. setTimeout(function() { self.$undoContainer.animate({ - "-moz-transform": "scale(1)", + "transform": "scale(1)", "opacity": 1 }, { easing: "tabviewBounce", duration: 170, complete: function() { self._sendToSubscribers("groupHidden"); } });
--- a/browser/components/tabview/items.js +++ b/browser/components/tabview/items.js @@ -249,17 +249,17 @@ Item.prototype = { return this.zIndex; }, // ---------- // Function: setRotation // Rotates the object to the given number of degrees. setRotation: function Item_setRotation(degrees) { var value = degrees ? "rotate(%deg)".replace(/%/, degrees) : null; - iQ(this.container).css({"-moz-transform": value}); + iQ(this.container).css({"transform": value}); }, // ---------- // Function: setParent // Sets the receiver's parent to the given <Item>. setParent: function Item_setParent(parent) { this.parent = parent; this.removeTrenches();
--- a/browser/components/tabview/tabitems.js +++ b/browser/components/tabview/tabitems.js @@ -530,17 +530,17 @@ TabItem.prototype = Utils.extend(new Ite UI.setActive(this); TabItems._update(this.tab, {force: true}); // Zoom in! let tab = this.tab; function onZoomDone() { - $canvas.css({ '-moz-transform': null }); + $canvas.css({ 'transform': null }); $tabEl.removeClass("front"); UI.goToTab(tab); // tab might not be selected because hideTabView() is invoked after // UI.goToTab() so we need to setup everything for the gBrowser.selectedTab if (tab != gBrowser.selectedTab) { UI.onTabSelect(gBrowser.selectedTab); @@ -558,18 +558,18 @@ TabItem.prototype = Utils.extend(new Ite if (animateZoom) { let transform = this.getZoomTransform(); TabItems.pausePainting(); if (this.parent && this.parent.expanded) $tabEl.removeClass("stack-trayed"); $tabEl.addClass("front"); $canvas - .css({ '-moz-transform-origin': transform.transformOrigin }) - .animate({ '-moz-transform': transform.transform }, { + .css({ 'transform-origin': transform.transformOrigin }) + .animate({ 'transform': transform.transform }, { duration: 230, easing: 'fast', complete: function() { onZoomDone(); setTimeout(function() { TabItems.resumePainting(); }, 0); @@ -588,17 +588,17 @@ TabItem.prototype = Utils.extend(new Ite // Parameters: // complete - a function to call after the zoom down animation zoomOut: function TabItem_zoomOut(complete) { let $tab = this.$container, $canvas = this.$canvas; var self = this; let onZoomDone = function onZoomDone() { $tab.removeClass("front"); - $canvas.css("-moz-transform", null); + $canvas.css("transform", null); if (typeof complete == "function") complete(); }; UI.setActive(this); TabItems._update(this.tab, {force: true}); @@ -607,21 +607,21 @@ TabItem.prototype = Utils.extend(new Ite let animateZoom = gPrefBranch.getBoolPref("animate_zoom"); if (animateZoom) { // The scaleCheat of 2 here is a clever way to speed up the zoom-out // code. See getZoomTransform() below. let transform = this.getZoomTransform(2); TabItems.pausePainting(); $canvas.css({ - '-moz-transform': transform.transform, - '-moz-transform-origin': transform.transformOrigin + 'transform': transform.transform, + 'transform-origin': transform.transformOrigin }); - $canvas.animate({ "-moz-transform": "scale(1.0)" }, { + $canvas.animate({ "transform": "scale(1.0)" }, { duration: 300, easing: 'cubic-bezier', // note that this is legal easing, even without parameters complete: function() { TabItems.resumePainting(); onZoomDone(); } }); } else {
--- a/browser/components/tabview/test/browser_tabview_bug624931.js +++ b/browser/components/tabview/test/browser_tabview_bug624931.js @@ -33,26 +33,26 @@ function onTabViewWindowLoaded() { function checkForFrontAddition(aEvent) { if (aEvent.attrName == "class" && aEvent.target.classList.contains("front")) { frontChanged = true; } } function checkForTransformAddition(aEvent) { - if (aEvent.attrName == "style" && aEvent.target.style.MozTransform) { + if (aEvent.attrName == "style" && aEvent.target.style.transform) { transformChanged = true; } } function onTabViewHidden() { window.removeEventListener("tabviewhidden", onTabViewHidden, false); ok(frontChanged, "the CSS class 'front' was added while zooming in"); - ok(transformChanged, "the CSS class '-moz-transform' was modified while " + + ok(transformChanged, "the CSS class 'transform' was modified while " + "zooming in"); frontChanged = transformChanged = false; tab.$container[0].removeEventListener("DOMAttrModified", checkForFrontAddition, false); tab.$container[0].addEventListener("DOMAttrModified", checkForFrontRemoval, false);
--- a/browser/config/mozconfigs/macosx-universal/nightly +++ b/browser/config/mozconfigs/macosx-universal/nightly @@ -18,12 +18,11 @@ export MOZILLA_OFFICIAL=1 export MOZ_TELEMETRY_REPORTING=1 mk_add_options MOZ_MAKE_FLAGS="-j12" ac_add_options --with-macbundlename-prefix=Firefox # Treat warnings as errors in directories with FAIL_ON_WARNINGS. ac_add_options --enable-warnings-as-errors -ac_add_options --with-ccache # Package js shell. export MOZ_PACKAGE_JSSHELL=1
--- a/browser/config/mozconfigs/macosx32/debug +++ b/browser/config/mozconfigs/macosx32/debug @@ -6,12 +6,11 @@ ENABLE_MARIONETTE=1 # Enable parallel compiling mk_add_options MOZ_MAKE_FLAGS="-j12" # Needed to enable breakpad in application.ini export MOZILLA_OFFICIAL=1 ac_add_options --with-macbundlename-prefix=Firefox -ac_add_options --with-ccache # Package js shell. export MOZ_PACKAGE_JSSHELL=1
--- a/browser/config/mozconfigs/macosx64/debug +++ b/browser/config/mozconfigs/macosx64/debug @@ -11,12 +11,11 @@ mk_add_options MOZ_MAKE_FLAGS="-j12" # Needed to enable breakpad in application.ini export MOZILLA_OFFICIAL=1 ac_add_options --with-macbundlename-prefix=Firefox # Treat warnings as errors in directories with FAIL_ON_WARNINGS. ac_add_options --enable-warnings-as-errors -ac_add_options --with-ccache # Package js shell. export MOZ_PACKAGE_JSSHELL=1
--- a/browser/config/tooltool-manifests/linux32/clang.manifest +++ b/browser/config/tooltool-manifests/linux32/clang.manifest @@ -1,15 +1,17 @@ [ -{"clang_version": "r159509"}, +{ +"clang_version": "r160364" +}, { "size": 47, "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa", "algorithm": "sha512", "filename": "setup.sh" }, { -"size": 74071397, -"digest": "390e499161e8b5e91c179f3352ecbb07431e3d5a190298de7f338b48fe3807f3ddbeca72d8df39d11f89864fb1135f14500471faa741d3886b875b8e2b1d4416", +"size": 65680370, +"digest": "5d343ea80cb0ace0f2a1683466015679dfacd0ae5584a89f001710c6d665f9fbd757edef5b1bd440f234553f9dbad06c8d1eed74b71a3d11e04e7f4e2c929628", "algorithm": "sha512", "filename": "clang.tar.bz2" } ]
--- a/browser/config/tooltool-manifests/linux64/clang.manifest +++ b/browser/config/tooltool-manifests/linux64/clang.manifest @@ -1,15 +1,17 @@ [ -{"clang_version": "r159509"}, +{ +"clang_version": "r160364" +}, { "size": 47, "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa", "algorithm": "sha512", "filename": "setup.sh" }, { -"size": 72518043, -"digest": "447dac319a8d7fa902cc065b758440bf6d4a7a98104362162fbdb0479a44d9b84c5878506f09be4398053a6ddc07935c7fb4f71e59b178073876fb5f90a45219", +"size": 61020656, +"digest": "822dc6f076309b1e45ef0ea77f88b9f5b73c8f1d0bb9c52147a2f99c4bdb67272442c9e89ab88bdadc94e2dead5e5cafc5ccea8211f42919b5ff9242bf2844d5", "algorithm": "sha512", "filename": "clang.tar.bz2" } ]
old mode 120000 new mode 100644 --- a/browser/config/tooltool-manifests/macosx32/clang.manifest +++ b/browser/config/tooltool-manifests/macosx32/clang.manifest @@ -1,1 +1,17 @@ -../macosx64/clang.manifest \ No newline at end of file +[ +{ +"clang_version": "r160364" +}, +{ +"size": 47, +"digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa", +"algorithm": "sha512", +"filename": "setup.sh" +}, +{ +"size": 54422251, +"digest": "8208645d24ac87975a091ff66a90c20589ff8945936ed9b16ca81976c59bf1166ed9f79709698d435480774fba8ed9f9f178dc189305c86162acac8fda19830e", +"algorithm": "sha512", +"filename": "clang.tar.bz2" +} +]
--- a/browser/config/tooltool-manifests/macosx32/releng.manifest +++ b/browser/config/tooltool-manifests/macosx32/releng.manifest @@ -1,1 +1,17 @@ -[] +[ +{ +"clang_version": "r160364" +}, +{ +"size": 47, +"digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa", +"algorithm": "sha512", +"filename": "setup.sh" +}, +{ +"size": 54422251, +"digest": "8208645d24ac87975a091ff66a90c20589ff8945936ed9b16ca81976c59bf1166ed9f79709698d435480774fba8ed9f9f178dc189305c86162acac8fda19830e", +"algorithm": "sha512", +"filename": "clang.tar.bz2" +} +]
--- a/browser/config/tooltool-manifests/macosx64/clang.manifest +++ b/browser/config/tooltool-manifests/macosx64/clang.manifest @@ -1,15 +1,17 @@ [ -{"clang_version": "r159509"}, +{ +"clang_version": "r160364" +}, { "size": 47, "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa", "algorithm": "sha512", "filename": "setup.sh" }, { -"size": 63679229, -"digest": "5257503e537b8d440b17e40aa06f0f70f1b124129c02f10e45b46ac642fc4170bfa77ae737a8bcac3ed7602ccd934a88cbe349986eb971d66a6fb553ae31f13c", +"size": 54422251, +"digest": "8208645d24ac87975a091ff66a90c20589ff8945936ed9b16ca81976c59bf1166ed9f79709698d435480774fba8ed9f9f178dc189305c86162acac8fda19830e", "algorithm": "sha512", "filename": "clang.tar.bz2" } ]
--- a/browser/config/tooltool-manifests/macosx64/releng.manifest +++ b/browser/config/tooltool-manifests/macosx64/releng.manifest @@ -1,1 +1,17 @@ -[] +[ +{ +"clang_version": "r160364" +}, +{ +"size": 47, +"digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa", +"algorithm": "sha512", +"filename": "setup.sh" +}, +{ +"size": 54422251, +"digest": "8208645d24ac87975a091ff66a90c20589ff8945936ed9b16ca81976c59bf1166ed9f79709698d435480774fba8ed9f9f178dc189305c86162acac8fda19830e", +"algorithm": "sha512", +"filename": "clang.tar.bz2" +} +]
--- a/browser/devtools/commandline/GcliCommands.jsm +++ b/browser/devtools/commandline/GcliCommands.jsm @@ -2,16 +2,19 @@ * 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/. */ let EXPORTED_SYMBOLS = [ "GcliCommands" ]; const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +const XMLHttpRequest = + Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1"); + Cu.import("resource:///modules/devtools/gcli.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "HUDService", "resource:///modules/HUDService.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", @@ -21,16 +24,29 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource:///modules/devtools/LayoutHelpers.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "console", "resource:///modules/devtools/Console.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "js_beautify", + "resource:///modules/devtools/Jsbeautify.jsm"); + +XPCOMUtils.defineLazyGetter(this, "Debugger", function() { + let JsDebugger = {}; + Components.utils.import("resource://gre/modules/jsdebugger.jsm", JsDebugger); + + let global = Components.utils.getGlobalForObject({}); + JsDebugger.addDebuggerToGlobal(global); + + return global.Debugger; +}); + let prefSvc = "@mozilla.org/preferences-service;1"; XPCOMUtils.defineLazyGetter(this, "prefBranch", function() { let prefService = Cc[prefSvc].getService(Ci.nsIPrefService); return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2); }); Cu.import("resource:///modules/devtools/GcliTiltCommands.jsm", {}); Cu.import("resource:///modules/devtools/GcliCookieCommands.jsm", {}); @@ -120,24 +136,26 @@ function loadCommandFile(aFile, aSandbox } /** * 'cmd' command */ gcli.addCommand({ name: "cmd", description: gcli.lookup("cmdDesc"), + hidden: true }); /** * 'cmd refresh' command */ gcli.addCommand({ name: "cmd refresh", description: gcli.lookup("cmdRefreshDesc"), + hidden: true, exec: function Command_cmdRefresh(args, context) { GcliCommands.refreshAutoCommands(context.environment.chromeDocument.defaultView); } }); /** * 'echo' command */ @@ -147,16 +165,17 @@ gcli.addCommand({ params: [ { name: "message", type: "string", description: gcli.lookup("echoMessageDesc") } ], returnType: "string", + hidden: true, exec: function Command_echo(args, context) { return args.message; } }); /** * 'screenshot' command @@ -276,16 +295,97 @@ gcli.addCommand({ let source = ioService.newURI(data, "UTF8", null); persist.saveURI(source, null, null, null, null, file); return "Saved to " + filename; } }); +let callLogDebuggers = []; + +/** + * 'calllog' command + */ +gcli.addCommand({ + name: "calllog", + description: gcli.lookup("calllogDesc") +}) + +/** + * 'calllog start' command + */ +gcli.addCommand({ + name: "calllog start", + description: gcli.lookup("calllogStartDesc"), + + exec: function(args, context) { + let contentWindow = context.environment.contentDocument.defaultView; + + let dbg = new Debugger(contentWindow); + dbg.onEnterFrame = function(frame) { + // BUG 773652 - Make the output from the GCLI calllog command nicer + contentWindow.console.log("Method call: " + this.callDescription(frame)); + }.bind(this); + + callLogDebuggers.push(dbg); + + let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab; + HUDService.activateHUDForContext(tab); + + return gcli.lookup("calllogStartReply"); + }, + + callDescription: function(frame) { + let name = "<anonymous>"; + if (frame.callee.name) { + name = frame.callee.name; + } + else { + let desc = frame.callee.getOwnPropertyDescriptor("displayName"); + if (desc && desc.value && typeof desc.value == "string") { + name = desc.value; + } + } + + let args = frame.arguments.map(this.valueToString).join(", "); + return name + "(" + args + ")"; + }, + + valueToString: function(value) { + if (typeof value !== "object" || value === null) { + return uneval(value); + } + return "[object " + value.class + "]"; + } +}); + +/** + * 'calllog stop' command + */ +gcli.addCommand({ + name: "calllog stop", + description: gcli.lookup("calllogStopDesc"), + + exec: function(args, context) { + let numDebuggers = callLogDebuggers.length; + if (numDebuggers == 0) { + return gcli.lookup("calllogStopNoLogging"); + } + + for (let dbg of callLogDebuggers) { + dbg.onEnterFrame = undefined; + } + callLogDebuggers = []; + + return gcli.lookupFormat("calllogStopReply", [ numDebuggers ]); + } +}); + + /** * 'console' command */ gcli.addCommand({ name: "console", description: gcli.lookup("consoleDesc"), manual: gcli.lookup("consoleManual") }); @@ -992,16 +1092,143 @@ gcli.addCommand({ // been finished. let promise = context.createPromise(); let types = aArgs.type == "all" ? null : [aArgs.type]; AddonManager.getAddonsByTypes(types, list.bind(promise, aArgs.type)); return promise; } }); + +/** + * 'dbg' command + */ +gcli.addCommand({ + name: "dbg", + description: gcli.lookup("dbgDesc"), + manual: gcli.lookup("dbgManual") +}); + + +/** + * 'dbg interrupt' command + */ +gcli.addCommand({ + name: "dbg interrupt", + description: gcli.lookup("dbgInterrupt"), + params: [], + exec: function(args, context) { + let win = context.environment.chromeDocument.defaultView; + let dbg = win.DebuggerUI.getDebugger(); + + if (dbg) { + let controller = dbg.contentWindow.DebuggerController; + let thread = controller.activeThread; + if (!thread.paused) { + thread.interrupt(); + } + } + } +}); + +/** + * 'dbg continue' command + */ +gcli.addCommand({ + name: "dbg continue", + description: gcli.lookup("dbgContinue"), + params: [], + exec: function(args, context) { + let win = context.environment.chromeDocument.defaultView; + let dbg = win.DebuggerUI.getDebugger(); + + if (dbg) { + let controller = dbg.contentWindow.DebuggerController; + let thread = controller.activeThread; + if (thread.paused) { + thread.resume(); + } + } + } +}); + + +/** + * 'dbg step' command + */ +gcli.addCommand({ + name: "dbg step", + description: gcli.lookup("dbgStepDesc"), + manual: gcli.lookup("dbgStepManual") +}); + + +/** + * 'dbg step over' command + */ +gcli.addCommand({ + name: "dbg step over", + description: gcli.lookup("dbgStepOverDesc"), + params: [], + exec: function(args, context) { + let win = context.environment.chromeDocument.defaultView; + let dbg = win.DebuggerUI.getDebugger(); + + if (dbg) { + let controller = dbg.contentWindow.DebuggerController; + let thread = controller.activeThread; + if (thread.paused) { + thread.stepOver(); + } + } + } +}); + +/** + * 'dbg step in' command + */ +gcli.addCommand({ + name: 'dbg step in', + description: gcli.lookup("dbgStepInDesc"), + params: [], + exec: function(args, context) { + let win = context.environment.chromeDocument.defaultView; + let dbg = win.DebuggerUI.getDebugger(); + + if (dbg) { + let controller = dbg.contentWindow.DebuggerController; + let thread = controller.activeThread; + if (thread.paused) { + thread.stepIn(); + } + } + } +}); + +/** + * 'dbg step over' command + */ +gcli.addCommand({ + name: 'dbg step out', + description: gcli.lookup("dbgStepOutDesc"), + params: [], + exec: function(args, context) { + let win = context.environment.chromeDocument.defaultView; + let dbg = win.DebuggerUI.getDebugger(); + + if (dbg) { + let controller = dbg.contentWindow.DebuggerController; + let thread = controller.activeThread; + if (thread.paused) { + thread.stepOut(); + } + } + } +}); + // We need a list of addon names for the enable and disable commands. Because // getting the name list is async we do not add the commands until we have the // list. AddonManager.getAllAddons(function addonAsync(aAddons) { // We listen for installs to keep our addon list up to date. There is no need // to listen for uninstalls because uninstalled addons are simply disabled // until restart (to enable undo functionality). AddonManager.addAddonListener({ @@ -1192,8 +1419,131 @@ AddonManager.getAllAddons(function addon name: 'height', type: 'number', description: gcli.lookup("resizePageArgHeightDesc"), }, ], exec: gcli_cmd_resize }); })(); + +/** + * jsb command. + */ +gcli.addCommand({ + name: 'jsb', + description: gcli.lookup('jsbDesc'), + returnValue:'string', + hidden: true, + params: [ + { + name: 'url', + type: 'string', + description: gcli.lookup('jsbUrlDesc'), + manual: 'The URL of the JS to prettify' + }, + { + name: 'indentSize', + type: 'number', + description: gcli.lookup('jsbIndentSizeDesc'), + manual: gcli.lookup('jsbIndentSizeManual'), + defaultValue: 2 + }, + { + name: 'indentChar', + type: { + name: 'selection', + lookup: [{name: "space", value: " "}, {name: "tab", value: "\t"}] + }, + description: gcli.lookup('jsbIndentCharDesc'), + manual: gcli.lookup('jsbIndentCharManual'), + defaultValue: ' ', + }, + { + name: 'preserveNewlines', + type: 'boolean', + description: gcli.lookup('jsbPreserveNewlinesDesc'), + manual: gcli.lookup('jsbPreserveNewlinesManual'), + defaultValue: true + }, + { + name: 'preserveMaxNewlines', + type: 'number', + description: gcli.lookup('jsbPreserveMaxNewlinesDesc'), + manual: gcli.lookup('jsbPreserveMaxNewlinesManual'), + defaultValue: -1 + }, + { + name: 'jslintHappy', + type: 'boolean', + description: gcli.lookup('jsbJslintHappyDesc'), + manual: gcli.lookup('jsbJslintHappyManual'), + defaultValue: false + }, + { + name: 'braceStyle', + type: { + name: 'selection', + data: ['collapse', 'expand', 'end-expand', 'expand-strict'] + }, + description: gcli.lookup('jsbBraceStyleDesc'), + manual: gcli.lookup('jsbBraceStyleManual'), + defaultValue: "collapse" + }, + { + name: 'spaceBeforeConditional', + type: 'boolean', + description: gcli.lookup('jsbSpaceBeforeConditionalDesc'), + manual: gcli.lookup('jsbSpaceBeforeConditionalManual'), + defaultValue: true + }, + { + name: 'unescapeStrings', + type: 'boolean', + description: gcli.lookup('jsbUnescapeStringsDesc'), + manual: gcli.lookup('jsbUnescapeStringsManual'), + defaultValue: false + } + ], + exec: function(args, context) { + let opts = { + indent_size: args.indentSize, + indent_char: args.indentChar, + preserve_newlines: args.preserveNewlines, + max_preserve_newlines: args.preserveMaxNewlines == -1 ? + undefined : args.preserveMaxNewlines, + jslint_happy: args.jslintHappy, + brace_style: args.braceStyle, + space_before_conditional: args.spaceBeforeConditional, + unescape_strings: args.unescapeStrings + } + + let xhr = new XMLHttpRequest(); + + try { + xhr.open("GET", args.url, true); + } catch(e) { + return gcli.lookup('jsbInvalidURL'); + } + + let promise = context.createPromise(); + + xhr.onreadystatechange = function(aEvt) { + if (xhr.readyState == 4) { + if (xhr.status == 200 || xhr.status == 0) { + let browserDoc = context.environment.chromeDocument; + let browserWindow = browserDoc.defaultView; + let browser = browserWindow.gBrowser; + + browser.selectedTab = browser.addTab("data:text/plain;base64," + + browserWindow.btoa(js_beautify(xhr.responseText, opts))); + promise.resolve(); + } + else { + promise.resolve("Unable to load page to beautify: " + args.url + " " + + xhr.status + " " + xhr.statusText); + } + }; + } + xhr.send(null); + return promise; + } +});
--- a/browser/devtools/commandline/GcliCookieCommands.jsm +++ b/browser/devtools/commandline/GcliCookieCommands.jsm @@ -32,19 +32,18 @@ var cookieListHtml = "" + " <th>" + gcli.lookup("cookieListOutKey") + "</th>" + " <th>" + gcli.lookup("cookieListOutValue") + "</th>" + " <th>" + gcli.lookup("cookieListOutActions") + "</th>" + " </tr>" + " <tr foreach='cookie in ${cookies}'>" + " <td>${cookie.key}</td>" + " <td>${cookie.value}</td>" + " <td>" + - " <span class='gcli-out-shortcut'" + - " onclick='${onclick}' ondblclick='${ondblclick}'" + - " data-command='cookie set ${cookie.key}'" + + " <span class='gcli-out-shortcut' onclick='${onclick}'" + + " data-command='cookie set ${cookie.key} '" + " >" + gcli.lookup("cookieListOutEdit") + "</span>" + " <span class='gcli-out-shortcut'" + " onclick='${onclick}' ondblclick='${ondblclick}'" + " data-command='cookie remove ${cookie.key}'" + " >" + gcli.lookup("cookieListOutRemove") + "</span>" + " </td>" + " </tr>" + "</table>" +
--- a/browser/devtools/commandline/gcli.jsm +++ b/browser/devtools/commandline/gcli.jsm @@ -1674,18 +1674,18 @@ exports.shutdown = function() { * - data: An array of strings - alternative to 'lookup' where the valid values * are strings. i.e. there is no mapping between what is typed and the value * that is used by the program * - stringifyProperty: Conversion from value to string is generally a process * of looking through all the valid options for a matching value, and using * the associated name. However the name maybe available directly from the * value using a property lookup. Setting 'stringifyProperty' allows * SelectionType to take this shortcut. - * - cacheable : If lookup is a function, then we normally assume that - * the values fetched can change. Setting 'cacheable' enables internal + * - cacheable: If lookup is a function, then we normally assume that + * the values fetched can change. Setting 'cacheable:true' enables internal * caching. */ function SelectionType(typeSpec) { if (typeSpec) { Object.keys(typeSpec).forEach(function(key) { this[key] = typeSpec[key]; }, this); } @@ -1769,58 +1769,67 @@ SelectionType.prototype._dataToLookup = * @param arg The initial input to match * @return A trimmed array of string:value pairs */ SelectionType.prototype._findPredictions = function(arg) { var predictions = []; var lookup = this.getLookup(); var i, option; var maxPredictions = Conversion.maxPredictions; + var match = arg.text.toLowerCase(); // If the arg has a suffix then we're kind of 'done'. Only an exact match // will do. if (arg.suffix.length > 0) { for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) { option = lookup[i]; if (option.name === arg.text) { this._addToPredictions(predictions, option, arg); } } return predictions; } + // Cache lower case versions of all the option names + for (i = 0; i < lookup.length; i++) { + option = lookup[i]; + if (option._gcliLowerName == null) { + option._gcliLowerName = option.name.toLowerCase(); + } + } + // Start with prefix matching for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) { option = lookup[i]; - if (option.name.indexOf(arg.text) === 0) { + if (option._gcliLowerName.indexOf(match) === 0) { this._addToPredictions(predictions, option, arg); } } // Try infix matching if we get less half max matched if (predictions.length < (maxPredictions / 2)) { for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) { option = lookup[i]; - if (option.name.indexOf(arg.text) !== -1) { + if (option._gcliLowerName.indexOf(match) !== -1) { if (predictions.indexOf(option) === -1) { this._addToPredictions(predictions, option, arg); } } } } // Try fuzzy matching if we don't get a prefix match if (false && predictions.length === 0) { var speller = new Speller(); var names = lookup.map(function(opt) { return opt.name; }); speller.train(names); - var corrected = speller.correct(arg.text); + var corrected = speller.correct(match); if (corrected) { lookup.forEach(function(opt) { if (opt.name === corrected) { predictions.push(opt); } }, this); } } @@ -1847,19 +1856,18 @@ SelectionType.prototype.parse = function } // This is something of a hack it basically allows us to tell the // setting type to forget its last setting hack. if (this.noMatch) { this.noMatch(); } - var value = predictions[0].value; - if (predictions[0].name === arg.text) { + var value = predictions[0].value; return new Conversion(value, arg, Status.VALID, '', predictions); } return new Conversion(undefined, arg, Status.INCOMPLETE, '', predictions); }; /** * For selections, up is down and black is white. It's like this, given a list @@ -8415,17 +8423,19 @@ function getListTemplateData(args, conte return false; } if (!args.search && command.name.indexOf(' ') != -1) { // We don't show sub commands with plain 'help' return false; } return true; }); - matchingCommands.sort(); + matchingCommands.sort(function(c1, c2) { + return c1.name.localeCompare(c2.name); + }); var heading; if (matchingCommands.length === 0) { heading = l10n.lookupFormat('helpListNone', [ args.search ]); } else if (args.search == null) { heading = l10n.lookup('helpListAll'); } @@ -8488,17 +8498,19 @@ function getManTemplateData(command, con }; Object.defineProperty(manTemplateData, 'subcommands', { get: function() { var matching = canon.getCommands().filter(function(subcommand) { return subcommand.name.indexOf(command.name) === 0 && subcommand.name !== command.name; }); - matching.sort(); + matching.sort(function(c1, c2) { + return c1.name.localeCompare(c2.name); + }); return matching; }, enumerable: true }); return manTemplateData; }
--- a/browser/devtools/commandline/test/Makefile.in +++ b/browser/devtools/commandline/test/Makefile.in @@ -9,33 +9,38 @@ srcdir = @srcdir@ VPATH = @srcdir@ relativesrcdir = browser/devtools/commandline/test include $(DEPTH)/config/autoconf.mk MOCHITEST_BROWSER_FILES = \ browser_gcli_addon.js \ browser_gcli_break.js \ + browser_gcli_calllog.js \ browser_gcli_commands.js \ browser_gcli_cookie.js \ + browser_gcli_dbg.js \ browser_gcli_edit.js \ browser_gcli_inspect.js \ browser_gcli_integrate.js \ + browser_gcli_jsb.js \ browser_gcli_pagemod_export.js \ browser_gcli_pref.js \ browser_gcli_responsivemode.js \ browser_gcli_restart.js \ browser_gcli_settings.js \ browser_gcli_web.js \ head.js \ $(NULL) MOCHITEST_BROWSER_FILES += \ browser_gcli_break.html \ browser_gcli_inspect.html \ + resources_dbg.html \ resources_inpage.js \ resources_inpage1.css \ resources_inpage2.css \ + resources_jsb_script.js \ resources.html \ $(NULL) include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_calllog.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. +* http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the calllog commands works as they should + +let imported = {}; +Components.utils.import("resource:///modules/HUDService.jsm", imported); + +const TEST_URI = "data:text/html;charset=utf-8,gcli-calllog"; + +function test() { + DeveloperToolbarTest.test(TEST_URI, function(browser, tab) { + testCallLogStatus(); + testCallLogExec(); + finish(); + }); +} + +function testCallLogStatus() { + DeveloperToolbarTest.checkInputStatus({ + typed: "calllog", + status: "ERROR" + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "calllog start", + status: "VALID", + emptyParameters: [ ] + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "calllog start", + status: "VALID", + emptyParameters: [ ] + }); +} + +function testCallLogExec() { + DeveloperToolbarTest.exec({ + typed: "calllog stop", + args: { }, + outputMatch: /No call logging/, + }); + + DeveloperToolbarTest.exec({ + typed: "calllog start", + args: { }, + outputMatch: /Call logging started/, + }); + + let hud = imported.HUDService.getHudByWindow(content); + ok(hud.hudId in imported.HUDService.hudReferences, "console open"); + + DeveloperToolbarTest.exec({ + typed: "calllog stop", + args: { }, + outputMatch: /Stopped call logging/, + }); + + DeveloperToolbarTest.exec({ + typed: "console clear", + args: {}, + blankOutput: true, + }); + + let labels = hud.jsterm.outputNode.querySelectorAll(".webconsole-msg-output"); + is(labels.length, 0, "no output in console"); + + DeveloperToolbarTest.exec({ + typed: "console close", + args: {}, + blankOutput: true, + }); +}
--- a/browser/devtools/commandline/test/browser_gcli_commands.js +++ b/browser/devtools/commandline/test/browser_gcli_commands.js @@ -14,21 +14,23 @@ function test() { testConsole(tab); imported = undefined; finish(); }); } function testEcho() { + /* DeveloperToolbarTest.exec({ typed: "echo message", args: { message: "message" }, outputMatch: /^message$/, }); + */ } function testConsole(tab) { DeveloperToolbarTest.exec({ typed: "console open", args: {}, blankOutput: true, });
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_dbg.js @@ -0,0 +1,67 @@ +function test() { + const TEST_URI = TEST_BASE_HTTP + "resources_dbg.html"; + + DeveloperToolbarTest.test(TEST_URI, function GAT_test() { + let pane = DebuggerUI.toggleDebugger(); + ok(pane, "toggleDebugger() should return a pane."); + let frame = pane._frame; + + frame.addEventListener("Debugger:Connecting", function dbgConnected(aEvent) { + frame.removeEventListener("Debugger:Connecting", dbgConnected, true); + + // Wait for the initial resume... + aEvent.target.ownerDocument.defaultView.gClient + .addOneTimeListener("resumed", function() { + + info("Starting tests."); + + let contentDoc = content.window.document; + let output = contentDoc.querySelector("input[type=text]"); + let btnDoit = contentDoc.querySelector("input[type=button]"); + + cmd("dbg interrupt", function() { + ok(true, "debugger is paused"); + pane.contentWindow.gClient.addOneTimeListener("resumed", function() { + ok(true, "debugger continued"); + pane.contentWindow.gClient.addOneTimeListener("paused", function() { + cmd("dbg step in", function() { + cmd("dbg step in", function() { + cmd("dbg step in", function() { + is(output.value, "step in", "debugger stepped in"); + cmd("dbg step over", function() { + is(output.value, "step over", "debugger stepped over"); + cmd("dbg step out", function() { + is(output.value, "step out", "debugger stepped out"); + cmd("dbg continue", function() { + cmd("dbg continue", function() { + is(output.value, "dbg continue", "debugger continued"); + pane.contentWindow.gClient.close(function() { + finish(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + EventUtils.sendMouseEvent({type:"click"}, btnDoit); + }); + DeveloperToolbarTest.exec({ + typed: "dbg continue", + blankOutput: true + }); + }); + }); + + function cmd(aTyped, aCallback) { + pane.contentWindow.gClient.addOneTimeListener("paused", aCallback); + DeveloperToolbarTest.exec({ + typed: aTyped, + blankOutput: true + }); + } + }); + }); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_jsb.js @@ -0,0 +1,48 @@ +function test() { + const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" + + "test/resources_jsb_script.js"; + + DeveloperToolbarTest.test("about:blank", function GJT_test() { + /* Commented out by bug 774057, re-enable with un-hidden jsb command + DeveloperToolbarTest.exec({ + typed: "jsb AAA", + outputMatch: /valid/ + }); + + gBrowser.addTabsProgressListener({ + onProgressChange: function GJT_onProgressChange(aBrowser) { + gBrowser.removeTabsProgressListener(this); + + let win = aBrowser._contentWindow; + let uri = win.document.location.href; + let result = win.atob(uri.replace(/.*,/, "")); + + result = result.replace(/[\r\n]]/g, "\n"); + + checkResult(result); + finish(); + } + }); + + info("Checking beautification"); + DeveloperToolbarTest.checkInputStatus({ + typed: "jsb " + TEST_URI + " 4 space true -1 false collapse true false", + status: "VALID" + }); + DeveloperToolbarTest.exec({ completed: false }); + + function checkResult(aResult) { + let correct = "function somefunc() {\n" + + " for (let n = 0; n < 500; n++) {\n" + + " if (n % 2 == 1) {\n" + + " console.log(n);\n" + + " console.log(n + 1);\n" + + " }\n" + + " }\n" + + "}"; + is(aResult, correct, "JS has been correctly prettified"); + } + */ + finish(); + }); +}
--- a/browser/devtools/commandline/test/browser_gcli_web.js +++ b/browser/devtools/commandline/test/browser_gcli_web.js @@ -2955,18 +2955,35 @@ exports.testCompleted = function(options args: { key: { value: 'key', status: 'VALID' }, value: { value: 'value', status: 'VALID' }, path: { value: 'path', status: 'VALID' }, domain: { value: 'domain', status: 'VALID' }, secure: { value: false, status: 'VALID' } } }); - - // Expand out to christmas tree command line +}; + +exports.testCase = function(options) { + helpers.setInput('tsg AA'); + helpers.check({ + input: 'tsg AA', + markup: 'VVVVII', + directTabText: '', + arrowTabText: 'aaa', + status: 'ERROR', + emptyParameters: [ ], + args: { + solo: { value: undefined, text: 'AA', status: 'INCOMPLETE' }, + txt1: { value: undefined, status: 'VALID' }, + bool: { value: undefined, status: 'VALID' }, + txt2: { value: undefined, status: 'VALID' }, + num: { value: undefined, status: 'VALID' } + } + }); }; exports.testIncomplete = function(options) { var requisition = options.display.requisition; helpers.setInput('tsm a a -'); helpers.check({ args: {
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/resources_dbg.html @@ -0,0 +1,44 @@ +<html> +<head> + <script type="application/javascript;version=1.7"/> + let output; + + function init() { + output = document.querySelector("input"); + output.value = ""; + } + + function doit() { + debugger; + stepIntoMe(); // step in + + output.value = "dbg continue"; + debugger; + } + + function stepIntoMe() { + output.value = "step in"; // step in + stepOverMe(); // step over + let x = 0; // step out + output.value = "step out"; + } + + function stepOverMe() { + output.value = "step over"; + } + </script> +</head> +<body onload="init()"> + <input type="text" value=""/> + <input type="button" value="DOIT" onclick="doit()"/> + <br /> + Use this file to test the following commands: + <ul> + <li>dbg interrupt</li> + <li>dbg continue</li> + <li>dbg step over</li> + <li>dbg step in</li> + <li>dbg step out</li> + </ul> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/resources_jsb_script.js @@ -0,0 +1,1 @@ +function somefunc(){for(let n=0;n<500;n++){if(n%2==1){console.log(n);console.log(n+1);}}}
--- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -47,16 +47,17 @@ let DebuggerController = { return; } this._isInitialized = true; window.removeEventListener("DOMContentLoaded", this._startupDebugger, true); DebuggerView.initializePanes(); DebuggerView.initializeEditor(); DebuggerView.StackFrames.initialize(); + DebuggerView.Breakpoints.initialize(); DebuggerView.Properties.initialize(); DebuggerView.Scripts.initialize(); DebuggerView.showCloseButton(!this._isRemoteDebugger && !this._isChromeDebugger); this.dispatchEvent("Debugger:Loaded"); this._connect(); }, @@ -70,16 +71,17 @@ let DebuggerController = { } this._isDestroyed = true; window.removeEventListener("unload", this._shutdownDebugger, true); DebuggerView.destroyPanes(); DebuggerView.destroyEditor(); DebuggerView.Scripts.destroy(); DebuggerView.StackFrames.destroy(); + DebuggerView.Breakpoints.destroy(); DebuggerView.Properties.destroy(); DebuggerController.Breakpoints.destroy(); DebuggerController.SourceScripts.disconnect(); DebuggerController.StackFrames.disconnect(); DebuggerController.ThreadState.disconnect(); this.dispatchEvent("Debugger:Unloaded"); @@ -108,18 +110,18 @@ let DebuggerController = { let prompt = new RemoteDebuggerPrompt(); let result = prompt.show(!!this._remoteConnectionTimeout); // If the connection was not established before the user canceled the // prompt, close the remote debugger. if (!result && !DebuggerController.activeThread) { this.dispatchEvent("Debugger:Close"); return false; } - Prefs.remoteHost = prompt.uri.host; - Prefs.remotePort = prompt.uri.port; + Prefs.remoteHost = prompt.remote.host; + Prefs.remotePort = prompt.remote.port; } // If this debugger is connecting remotely to a server, we need to check // after a while if the connection actually succeeded. this._remoteConnectionTry = ++this._remoteConnectionTry || 1; this._remoteConnectionTimeout = window.setTimeout(function() { // If we couldn't connect to any server yet, try again... if (!DebuggerController.activeThread) { @@ -255,17 +257,18 @@ let DebuggerController = { return window._remoteFlag; }, /** * Returns true if this is a chrome debugger instance. * @return boolean */ get _isChromeDebugger() { - return !window.parent.content && !this._isRemoteDebugger; + // Directly accessing window.parent.content may throw in some cases. + return !("content" in window.parent) && !this._isRemoteDebugger; }, /** * Attempts to quit the current process if allowed. */ _quitApp: function DC__quitApp() { let canceled = Cc["@mozilla.org/supports-PRBool;1"] .createInstance(Ci.nsISupportsPRBool); @@ -389,18 +392,16 @@ StackFrames.prototype = { * Watch the given thread client. * * @param function aCallback * The next function in the initialization sequence. */ connect: function SF_connect(aCallback) { window.addEventListener("Debugger:FetchedVariables", this._onFetchedVars, false); - this._onFramesCleared(); - this.activeThread.addListener("paused", this._onPaused); this.activeThread.addListener("resumed", this._onResume); this.activeThread.addListener("framesadded", this._onFrames); this.activeThread.addListener("framescleared", this._onFramesCleared); this.updatePauseOnExceptions(this.pauseOnExceptions); aCallback && aCallback(); @@ -500,22 +501,57 @@ StackFrames.prototype = { if (!frame) { return; } let url = frame.where.url; let line = frame.where.line; let editor = DebuggerView.editor; - // Move the editor's caret to the proper line. - if (DebuggerView.Scripts.isSelected(url) && line) { - editor.setDebugLocation(line - 1); - } else { - editor.setDebugLocation(-1); + this.updateEditorToLocation(url, line, true); + }, + + /** + * Update the source editor's current caret and debug location based on + * a specified url and line. + * + * @param string aUrl + * The target source url. + * @param number aLine + * The target line number in the source. + * @param boolean aNoSwitch + * Pass true to not switch to the script if not currently selected. + * @param boolean aNoCaretFlag + * Pass true to not set the caret location at the specified line. + * @param boolean aNoDebugFlag + * Pass true to not set the debug location at the specified line. + */ + updateEditorToLocation: + function SF_updateEditorToLocation(aUrl, aLine, aNoSwitch, aNoCaretFlag, aNoDebugFlag) { + let editor = DebuggerView.editor; + + function set() { + if (!aNoCaretFlag) { + editor.setCaretPosition(aLine - 1); + } + if (!aNoDebugFlag) { + editor.setDebugLocation(aLine - 1); + } } + + // Move the editor's caret to the proper url and line. + if (DebuggerView.Scripts.isSelected(aUrl)) { + return set(); + } + if (!aNoSwitch && DebuggerView.Scripts.contains(aUrl)) { + DebuggerView.Scripts.selectScript(aUrl); + return set(); + } + editor.setCaretPosition(-1); + editor.setDebugLocation(-1); }, /** * Inform the debugger client whether the debuggee should be paused whenever * an exception is thrown. * * @param boolean aFlag * The new value of the flag: true for pausing, false otherwise. @@ -544,30 +580,19 @@ StackFrames.prototype = { let frame = this.activeThread.cachedFrames[aDepth]; if (!frame) { return; } let url = frame.where.url; let line = frame.where.line; - let editor = DebuggerView.editor; // Move the editor's caret to the proper line. - if (DebuggerView.Scripts.isSelected(url) && line) { - editor.setCaretPosition(line - 1); - editor.setDebugLocation(line - 1); - } - else if (DebuggerView.Scripts.contains(url)) { - DebuggerView.Scripts.selectScript(url); - editor.setCaretPosition(line - 1); - } - else { - editor.setDebugLocation(-1); - } + this.updateEditorToLocation(url, line); // Start recording any added variables or properties in any scope. DebuggerView.Properties.createHierarchyStore(); // Clear existing scopes and create each one dynamically. DebuggerView.Properties.empty(); if (frame.environment) { @@ -749,17 +774,17 @@ StackFrames.prototype = { /** * Adds the specified stack frame to the list. * * @param Debugger.Frame aFrame * The new frame to add. */ _addFrame: function SF__addFrame(aFrame) { let depth = aFrame.depth; - let label = DebuggerController.SourceScripts._getScriptLabel(aFrame.where.url); + let label = DebuggerController.SourceScripts.getScriptLabel(aFrame.where.url); let startText = this._getFrameTitle(aFrame); let endText = label + ":" + aFrame.where.line; let frame = DebuggerView.StackFrames.addFrame(depth, startText, endText); if (frame) { frame.debuggerFrame = aFrame; } @@ -876,39 +901,44 @@ SourceScripts.prototype = { */ _onNewScript: function SS__onNewScript(aNotification, aPacket) { // Ignore scripts generated from 'clientEvaluate' packets. if (aPacket.url == "debugger eval code") { return; } this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true); - // If there are any stored breakpoints for this script, display them again. - for each (let bp in DebuggerController.Breakpoints.store) { - if (bp.location.url == aPacket.url) { - DebuggerController.Breakpoints.displayBreakpoint(bp.location); + + // If there are any stored breakpoints for this script, display them again, + // both in the editor and the pane. + for each (let breakpoint in DebuggerController.Breakpoints.store) { + if (breakpoint.location.url == aPacket.url) { + DebuggerController.Breakpoints.displayBreakpoint(breakpoint); } } }, /** * Handler for the thread client's scriptsadded notification. */ _onScriptsAdded: function SS__onScriptsAdded() { for each (let script in this.activeThread.cachedScripts) { this._addScript(script, false); } DebuggerView.Scripts.commitScripts(); + DebuggerController.Breakpoints.updatePaneBreakpoints(); }, /** * Handler for the thread client's scriptscleared notification. */ _onScriptsCleared: function SS__onScriptsCleared() { DebuggerView.Scripts.empty(); + DebuggerView.Breakpoints.emptyText(); + DebuggerView.editor.setText(""); }, /** * Sets the proper editor mode (JS or HTML) according to the specified * content type, or by determining the type from the URL. * * @param string aUrl * The script URL. @@ -961,17 +991,17 @@ SourceScripts.prototype = { * The script URL. * @param string aLabel [optional] * The resulting label at each step. * @param number aSeq [optional] * The current iteration step. * @return string * The resulting label at the final step. */ - _trimURL: function SS__trimURL(aUrl, aLabel, aSeq) { + _trimUrl: function SS__trimUrl(aUrl, aLabel, aSeq) { if (!(aUrl instanceof Ci.nsIURL)) { try { // Use an nsIURL to parse all the url path parts. aUrl = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL); } catch (e) { // This doesn't look like a url, or nsIURL can't handle it. return aUrl; } @@ -1006,65 +1036,65 @@ SourceScripts.prototype = { return aLabel; } } // Append the url query. if (aSeq === 1) { let query = aUrl.query; if (query) { - return this._trimURL(aUrl, aLabel + "?" + query, aSeq + 1); + return this._trimUrl(aUrl, aLabel + "?" + query, aSeq + 1); } aSeq++; } // Append the url reference. if (aSeq === 2) { let ref = aUrl.ref; if (ref) { - return this._trimURL(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1); + return this._trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1); } aSeq++; } // Prepend the url directory. if (aSeq === 3) { let dir = aUrl.directory; if (dir) { - return this._trimURL(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1); + return this._trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1); } aSeq++; } // Prepend the hostname and port number. if (aSeq === 4) { let host = aUrl.hostPort; if (host) { - return this._trimURL(aUrl, host + "/" + aLabel, aSeq + 1); + return this._trimUrl(aUrl, host + "/" + aLabel, aSeq + 1); } aSeq++; } // Use the whole url spec but ignoring the reference. if (aSeq === 5) { - return this._trimURL(aUrl, aUrl.specIgnoringRef, aSeq + 1); + return this._trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1); } // Give up. return aUrl.spec; }, /** * Gets a unique, simplified label from a script url. * * @param string aUrl * The script url. * @param string aHref * The content location href to be used. If unspecified, it will * default to the script url prepath. * @return string * The simplified label. */ - _getScriptLabel: function SS__getScriptLabel(aUrl, aHref) { - return this._labelsCache[aUrl] || (this._labelsCache[aUrl] = this._trimURL(aUrl)); + getScriptLabel: function SS_getScriptLabel(aUrl, aHref) { + return this._labelsCache[aUrl] || (this._labelsCache[aUrl] = this._trimUrl(aUrl)); }, /** * Clears the labels cache, populated by SS_getScriptLabel. * This should be done every time the content location changes. */ _clearLabelsCache: function SS__clearLabelsCache() { this._labelsCache = {}; @@ -1075,17 +1105,17 @@ SourceScripts.prototype = { * * @param object aScript * The script object coming from the active thread. * @param boolean aForceFlag * True to force the script to be immediately added. */ _addScript: function SS__addScript(aScript, aForceFlag) { DebuggerView.Scripts.addScript( - this._getScriptLabel(aScript.url), aScript, aForceFlag); + this.getScriptLabel(aScript.url), aScript, aForceFlag); }, /** * Load the editor with the script text if available, otherwise fire an event * to load and display the script text. * * @param object aScript * The script object coming from the active thread. @@ -1228,16 +1258,33 @@ SourceScripts.prototype = { script.text = aSourceText; script.contentType = aContentType; element.setUserData("sourceScript", script, null); this.showScript(script, aOptions); }, /** + * Gets the text in a source editor's specified line. + * + * @param number aLine [optional] + * The line to get the text from. + * If unspecified, it defaults to the current caret position line. + * @return string + * The specified line text + */ + getLineText: function SS_getLineText(aLine) { + let editor = DebuggerView.editor; + let line = aLine || editor.getCaretPosition().line; + let start = editor.getLineStart(line); + let end = editor.getLineEnd(line); + return editor.getText(start, end); + }, + + /** * Log an error message in the error console when a script fails to load. * * @param string aUrl * The URL of the source script. * @param string aStatus * The failure status code. */ _logError: function SS__logError(aUrl, aStatus) { @@ -1390,102 +1437,141 @@ Breakpoints.prototype = { if (breakpoint.location.url == url) { this.editor.addBreakpoint(breakpoint.location.line - 1); } } this._skipEditorBreakpointChange = false; }, /** + * Update the breakpoints in the pane view. This function is invoked when the + * scripts are added (typically after a page navigation). + */ + updatePaneBreakpoints: function BP_updatePaneBreakpoints() { + let url = DebuggerView.Scripts.selected; + if (!url) { + return; + } + + this._skipEditorBreakpointChange = true; + for each (let breakpoint in this.store) { + if (DebuggerView.Scripts.contains(breakpoint.location.url)) { + this.displayBreakpoint(breakpoint, true); + } + } + this._skipEditorBreakpointChange = false; + }, + + /** * Add a breakpoint. * * @param object aLocation * The location where you want the breakpoint. This object must have * two properties: * - url - the URL of the script. * - line - the line number (starting from 1). * @param function [aCallback] * Optional function to invoke once the breakpoint is added. The * callback is invoked with two arguments: * - aBreakpointClient - the BreakpointActor client object, if the * breakpoint has been added successfully. * - aResponseError - if there was any error. * @param boolean [aNoEditorUpdate=false] * Tells if you want to skip editor updates. Typically the editor is * updated to visually indicate that a breakpoint has been added. + * @param boolean [aNoPaneUpdate=false] + * Tells if you want to skip any breakpoint pane updates. */ addBreakpoint: - function BP_addBreakpoint(aLocation, aCallback, aNoEditorUpdate) { + function BP_addBreakpoint(aLocation, aCallback, aNoEditorUpdate, aNoPaneUpdate) { let breakpoint = this.getBreakpoint(aLocation.url, aLocation.line); if (breakpoint) { aCallback && aCallback(breakpoint); return; } this.activeThread.setBreakpoint(aLocation, function(aResponse, aBpClient) { this.store[aBpClient.actor] = aBpClient; - this.displayBreakpoint(aLocation, aNoEditorUpdate); + this.displayBreakpoint(aBpClient, aNoEditorUpdate, aNoPaneUpdate); aCallback && aCallback(aBpClient, aResponse.error); }.bind(this)); }, /** * Update the editor to display the specified breakpoint in the gutter. * - * @param object aLocation - * The location where you want the breakpoint. This object must have - * two properties: - * - url - the URL of the script. - * - line - the line number (starting from 1). + * @param object aBreakpoint + * The breakpoint you want to display. * @param boolean [aNoEditorUpdate=false] * Tells if you want to skip editor updates. Typically the editor is * updated to visually indicate that a breakpoint has been added. + * @param boolean [aNoPaneUpdate=false] + * Tells if you want to skip any breakpoint pane updates. */ - displayBreakpoint: function BP_displayBreakpoint(aLocation, aNoEditorUpdate) { + displayBreakpoint: + function BP_displayBreakpoint(aBreakpoint, aNoEditorUpdate, aNoPaneUpdate) { if (!aNoEditorUpdate) { let url = DebuggerView.Scripts.selected; - if (url == aLocation.url) { + if (url == aBreakpoint.location.url) { this._skipEditorBreakpointChange = true; - this.editor.addBreakpoint(aLocation.line - 1); + this.editor.addBreakpoint(aBreakpoint.location.line - 1); this._skipEditorBreakpointChange = false; } } + if (!aNoPaneUpdate) { + let { url: url, line: line } = aBreakpoint.location; + + if (!aBreakpoint.lineText || !aBreakpoint.lineInfo) { + let scripts = DebuggerController.SourceScripts; + aBreakpoint.lineText = scripts.getLineText(line - 1); + aBreakpoint.lineInfo = scripts.getScriptLabel(url) + ":" + line; + } + DebuggerView.Breakpoints.addBreakpoint( + aBreakpoint.actor, + aBreakpoint.lineInfo, + aBreakpoint.lineText, url, line); + } }, /** * Remove a breakpoint. * * @param object aBreakpoint * The breakpoint you want to remove. * @param function [aCallback] * Optional function to invoke once the breakpoint is removed. The * callback is invoked with one argument: the breakpoint location * object which holds the url and line properties. * @param boolean [aNoEditorUpdate=false] * Tells if you want to skip editor updates. Typically the editor is * updated to visually indicate that a breakpoint has been removed. + * @param boolean [aNoPaneUpdate=false] + * Tells if you want to skip any breakpoint pane updates. */ removeBreakpoint: - function BP_removeBreakpoint(aBreakpoint, aCallback, aNoEditorUpdate) { + function BP_removeBreakpoint(aBreakpoint, aCallback, aNoEditorUpdate, aNoPaneUpdate) { if (!(aBreakpoint.actor in this.store)) { aCallback && aCallback(aBreakpoint.location); return; } aBreakpoint.remove(function() { delete this.store[aBreakpoint.actor]; if (!aNoEditorUpdate) { let url = DebuggerView.Scripts.selected; if (url == aBreakpoint.location.url) { this._skipEditorBreakpointChange = true; this.editor.removeBreakpoint(aBreakpoint.location.line - 1); this._skipEditorBreakpointChange = false; } } + if (!aNoPaneUpdate) { + DebuggerView.Breakpoints.removeBreakpoint(aBreakpoint.actor); + } aCallback && aCallback(aBreakpoint.location); }.bind(this)); }, /** * Get the breakpoint object at the given location. *
--- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -1,33 +1,34 @@ /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const PROPERTY_VIEW_FLASH_DURATION = 400; // ms +const BREAKPOINT_LINE_TOOLTIP_MAX_SIZE = 1000; /** * Object mediating visual changes and event listeners between the debugger and * the html view. */ let DebuggerView = { /** * An instance of SourceEditor. */ editor: null, /** * Initializes UI properties for all the displayed panes. */ initializePanes: function DV_initializePanes() { - let stackframes = document.getElementById("stackframes"); + let stackframes = document.getElementById("stackframes+breakpoints"); stackframes.setAttribute("width", Prefs.stackframesWidth); let variables = document.getElementById("variables"); variables.setAttribute("width", Prefs.variablesWidth); }, /** * Initializes the SourceEditor instance. @@ -46,17 +47,17 @@ let DebuggerView = { this.editor = new SourceEditor(); this.editor.init(placeholder, config, this._onEditorLoad.bind(this)); }, /** * Removes the displayed panes and saves any necessary state. */ destroyPanes: function DV_destroyPanes() { - let stackframes = document.getElementById("stackframes"); + let stackframes = document.getElementById("stackframes+breakpoints"); Prefs.stackframesWidth = stackframes.getAttribute("width"); let variables = document.getElementById("variables"); Prefs.variablesWidth = variables.getAttribute("width"); }, /** * Removes the SourceEditor instance and added breakpoints. @@ -84,53 +85,55 @@ let DebuggerView = { }; /** * A simple way of displaying a "Connect to..." prompt. */ function RemoteDebuggerPrompt() { /** - * The remote uri the user wants to connect to. + * The remote host and port the user wants to connect to. */ - this.uri = null; + this.remote = {}; } RemoteDebuggerPrompt.prototype = { /** * Shows the prompt and sets the uri using the user input. * * @param boolean aIsReconnectingFlag * True to show the reconnect message instead. */ show: function RDP_show(aIsReconnectingFlag) { let check = { value: Prefs.remoteAutoConnect }; - let input = { value: "http://" + Prefs.remoteHost + - ":" + Prefs.remotePort + "/" }; + let input = { value: Prefs.remoteHost + ":" + Prefs.remotePort }; + let parts; while (true) { let result = Services.prompt.prompt(null, L10N.getStr("remoteDebuggerPromptTitle"), L10N.getStr(aIsReconnectingFlag ? "remoteDebuggerReconnectMessage" : "remoteDebuggerPromptMessage"), input, L10N.getStr("remoteDebuggerPromptCheck"), check); Prefs.remoteAutoConnect = check.value; - try { - let uri = Services.io.newURI(input.value, null, null); - let url = uri.QueryInterface(Ci.nsIURL); + if (!result) { + return false; + } + if ((parts = input.value.split(":")).length === 2) { + let [host, port] = parts; - // If a url could be successfully retrieved, then the uri is correct. - this.uri = uri; - return result; + if (host.length && port.length) { + this.remote = { host: host, port: port }; + return true; + } } - catch(e) { } } } }; /** * Functions handling the scripts UI. */ function ScriptsView() { @@ -140,16 +143,18 @@ function ScriptsView() { } ScriptsView.prototype = { /** * Removes all elements from the scripts container, leaving it empty. */ empty: function DVS_empty() { + this._scripts.selectedIndex = -1; + while (this._scripts.firstChild) { this._scripts.removeChild(this._scripts.firstChild); } }, /** * Removes the input in the searchbox and unhides all the scripts. */ @@ -410,17 +415,22 @@ ScriptsView.prototype = { return [file, line, token]; }, /** * The click listener for the scripts container. */ _onScriptsChange: function DVS__onScriptsChange() { - let script = this._scripts.selectedItem.getUserData("sourceScript"); + let selectedItem = this._scripts.selectedItem; + if (!selectedItem) { + return; + } + + let script = selectedItem.getUserData("sourceScript"); this._preferredScript = script; DebuggerController.SourceScripts.showScript(script); }, /** * The search listener for the scripts search box. */ _onScriptsSearch: function DVS__onScriptsSearch(e) { @@ -454,17 +464,17 @@ ScriptsView.prototype = { else { item.hidden = true; } } } if (line > -1) { editor.setCaretPosition(line - 1); } - if (token) { + if (token.length) { let offset = editor.find(token, { ignoreCase: true }); if (offset > -1) { editor.setSelection(offset, offset + token.length) } } }, /** @@ -473,16 +483,20 @@ ScriptsView.prototype = { _onScriptsKeyUp: function DVS__onScriptsKeyUp(e) { if (e.keyCode === e.DOM_VK_ESCAPE) { DebuggerView.editor.focus(); return; } if (e.keyCode === e.DOM_VK_RETURN || e.keyCode === e.DOM_VK_ENTER) { let token = this._getSearchboxInfo()[2]; + if (!token.length) { + return; + } + let editor = DebuggerView.editor; let offset = editor.findNext(true); if (offset > -1) { editor.setSelection(offset, offset + token.length) } } }, @@ -796,16 +810,17 @@ StackFramesView.prototype = { stepOver.addEventListener("click", this._onStepOverClick, false); stepIn.addEventListener("click", this._onStepInClick, false); stepOut.addEventListener("click", this._onStepOutClick, false); frames.addEventListener("click", this._onFramesClick, false); frames.addEventListener("scroll", this._onFramesScroll, false); window.addEventListener("resize", this._onFramesScroll, false); this._frames = frames; + this.emptyText(); }, /** * Destruction function, called when the debugger is shut down. */ destroy: function DVF_destroy() { let close = document.getElementById("close"); let pauseOnExceptions = document.getElementById("pause-exceptions"); @@ -827,25 +842,565 @@ StackFramesView.prototype = { frames.removeEventListener("scroll", this._onFramesScroll, false); window.removeEventListener("resize", this._onFramesScroll, false); this._frames = null; } }; /** + * Functions handling the breakpoints view. + */ +function BreakpointsView() { + this._onBreakpointClick = this._onBreakpointClick.bind(this); + this._onBreakpointCheckboxChange = this._onBreakpointCheckboxChange.bind(this); +} + +BreakpointsView.prototype = { + + /** + * Removes all elements from the breakpoints container, leaving it empty. + */ + empty: function DVB_empty() { + let firstChild; + while (firstChild = this._breakpoints.firstChild) { + this._destroyContextMenu(firstChild); + this._breakpoints.removeChild(firstChild); + } + }, + + /** + * Removes all elements from the breakpoints container, and adds a child node + * with an empty text note attached. + */ + emptyText: function DVB_emptyText() { + // Make sure the container is empty first. + this.empty(); + + let item = document.createElement("label"); + + // The empty node should look grayed out to avoid confusion. + item.className = "list-item empty"; + item.setAttribute("value", L10N.getStr("emptyBreakpointsText")); + + this._breakpoints.appendChild(item); + }, + + /** + * Checks whether the breakpoint with the specified script URL and line is + * among the breakpoints known to the debugger and shown in the list, and + * returns the matched result or null if nothing is found. + * + * @param string aUrl + * The original breakpoint script url. + * @param number aLine + * The original breakpoint script line. + * @return object | null + * The queried breakpoint + */ + getBreakpoint: function DVB_getBreakpoint(aUrl, aLine) { + return this._breakpoints.getElementsByAttribute("location", aUrl + ":" + aLine)[0]; + }, + + /** + * Removes a breakpoint only from the breakpoints container. + * This doesn't remove the breakpoint from the DebuggerController! + * + * @param string aId + * A breakpoint identifier specified by the debugger. + */ + removeBreakpoint: function DVB_removeBreakpoint(aId) { + let breakpoint = document.getElementById("breakpoint-" + aId); + + // Make sure we have something to remove. + if (!breakpoint) { + return; + } + this._destroyContextMenu(breakpoint); + this._breakpoints.removeChild(breakpoint); + + if (!this.count) { + this.emptyText(); + } + }, + + /** + * Adds a breakpoint to the breakpoints container. + * If the breakpoint already exists (was previously added), null is returned. + * If it's already added but disabled, it will be enabled and null is returned. + * Otherwise, the newly created element is returned. + * + * @param string aId + * A breakpoint identifier specified by the debugger. + * @param string aLineInfo + * The script line information to be displayed in the list. + * @param string aLineText + * The script line text to be displayed in the list. + * @param string aUrl + * The original breakpoint script url. + * @param number aLine + * The original breakpoint script line. + * @return object + * The newly created html node representing the added breakpoint. + */ + addBreakpoint: function DVB_addBreakpoint(aId, aLineInfo, aLineText, aUrl, aLine) { + // Make sure we don't duplicate anything. + if (document.getElementById("breakpoint-" + aId)) { + return null; + } + // Remove the empty list text if it was there. + if (!this.count) { + this.empty(); + } + + // If the breakpoint was already added but disabled, enable it now. + let breakpoint = this.getBreakpoint(aUrl, aLine); + if (breakpoint) { + breakpoint.id = "breakpoint-" + aId; + breakpoint.breakpointActor = aId; + breakpoint.getElementsByTagName("checkbox")[0].setAttribute("checked", "true"); + return; + } + + breakpoint = document.createElement("box"); + let bkpCheckbox = document.createElement("checkbox"); + let bkpLineInfo = document.createElement("label"); + let bkpLineText = document.createElement("label"); + + // Create a list item to be added to the stackframes container. + breakpoint.id = "breakpoint-" + aId; + breakpoint.className = "dbg-breakpoint list-item"; + breakpoint.setAttribute("location", aUrl + ":" + aLine); + breakpoint.breakpointUrl = aUrl; + breakpoint.breakpointLine = aLine; + breakpoint.breakpointActor = aId; + + aLineInfo = aLineInfo.trim(); + aLineText = aLineText.trim(); + + // A checkbox specifies if the breakpoint is enabled or not. + bkpCheckbox.setAttribute("checked", "true"); + bkpCheckbox.addEventListener("click", this._onBreakpointCheckboxChange, false); + + // This list should display the line info and text for the breakpoint. + bkpLineInfo.className = "dbg-breakpoint-info plain"; + bkpLineText.className = "dbg-breakpoint-text plain"; + bkpLineInfo.setAttribute("value", aLineInfo); + bkpLineText.setAttribute("value", aLineText); + bkpLineInfo.setAttribute("crop", "end"); + bkpLineText.setAttribute("crop", "end"); + bkpLineText.setAttribute("tooltiptext", aLineText.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_SIZE)); + + // Create a context menu for the breakpoint. + let menupopupId = this._createContextMenu(breakpoint); + breakpoint.setAttribute("contextmenu", menupopupId); + + let state = document.createElement("vbox"); + state.className = "state"; + state.appendChild(bkpCheckbox); + + let content = document.createElement("vbox"); + content.className = "content"; + content.setAttribute("flex", "1"); + content.appendChild(bkpLineInfo); + content.appendChild(bkpLineText); + + breakpoint.appendChild(state); + breakpoint.appendChild(content); + + this._breakpoints.appendChild(breakpoint); + + // Return the element for later use if necessary. + return breakpoint; + }, + + /** + * Enables a breakpoint. + * + * @param object aBreakpoint + * An element representing a breakpoint. + * @param function aCallback + * Optional function to invoke once the breakpoint is enabled. + * @param boolean aNoCheckboxUpdate + * Pass true to not update the checkbox checked state. + * This is usually necessary when the checked state will be updated + * automatically (e.g: on a checkbox click). + */ + enableBreakpoint: + function DVB_enableBreakpoint(aTarget, aCallback, aNoCheckboxUpdate) { + let { breakpointUrl: url, breakpointLine: line } = aTarget; + let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line) + + if (!breakpoint) { + if (!aNoCheckboxUpdate) { + aTarget.getElementsByTagName("checkbox")[0].setAttribute("checked", "true"); + } + DebuggerController.Breakpoints. + addBreakpoint({ url: url, line: line }, aCallback); + + return true; + } + return false; + }, + + /** + * Disables a breakpoint. + * + * @param object aTarget + * An element representing a breakpoint. + * @param function aCallback + * Optional function to invoke once the breakpoint is disabled. + * @param boolean aNoCheckboxUpdate + * Pass true to not update the checkbox checked state. + * This is usually necessary when the checked state will be updated + * automatically (e.g: on a checkbox click). + */ + disableBreakpoint: + function DVB_disableBreakpoint(aTarget, aCallback, aNoCheckboxUpdate) { + let { breakpointUrl: url, breakpointLine: line } = aTarget; + let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line) + + if (breakpoint) { + if (!aNoCheckboxUpdate) { + aTarget.getElementsByTagName("checkbox")[0].removeAttribute("checked"); + } + DebuggerController.Breakpoints. + removeBreakpoint(breakpoint, aCallback, false, true); + + return true; + } + return false; + }, + + /** + * Gets the current number of added breakpoints. + */ + get count() { + return this._breakpoints.getElementsByClassName("dbg-breakpoint").length; + }, + + /** + * Iterates through all the added breakpoints. + * + * @param function aCallback + * Function called for each element. + */ + _iterate: function DVB_iterate(aCallback) { + Array.forEach(Array.slice(this._breakpoints.childNodes), aCallback); + }, + + /** + * Gets the real breakpoint target when an event is handled. + * @return object + */ + _getBreakpointTarget: function DVB__getBreakpointTarget(aEvent) { + let target = aEvent.target; + + while (target) { + if (target.breakpointActor) { + return target; + } + target = target.parentNode; + } + }, + + /** + * Listener handling the breakpoint click event. + */ + _onBreakpointClick: function DVB__onBreakpointClick(aEvent) { + let target = this._getBreakpointTarget(aEvent); + let { breakpointUrl: url, breakpointLine: line } = target; + + DebuggerController.StackFrames.updateEditorToLocation(url, line, 0, 0, 1); + }, + + /** + * Listener handling the breakpoint checkbox change event. + */ + _onBreakpointCheckboxChange: function DVB__onBreakpointCheckboxChange(aEvent) { + aEvent.stopPropagation(); + + let target = this._getBreakpointTarget(aEvent); + let { breakpointUrl: url, breakpointLine: line } = target; + + if (aEvent.target.getAttribute("checked") === "true") { + this.disableBreakpoint(target, null, true); + } else { + this.enableBreakpoint(target, null, true); + } + }, + + /** + * Listener handling the "enableSelf" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onEnableSelf: function DVB__onEnableSelf(aTarget) { + if (!aTarget) { + return; + } + if (this.enableBreakpoint(aTarget)) { + aTarget.enableSelf.menuitem.setAttribute("hidden", "true"); + aTarget.disableSelf.menuitem.removeAttribute("hidden"); + } + }, + + /** + * Listener handling the "disableSelf" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onDisableSelf: function DVB__onDisableSelf(aTarget) { + if (!aTarget) { + return; + } + if (this.disableBreakpoint(aTarget)) { + aTarget.enableSelf.menuitem.removeAttribute("hidden"); + aTarget.disableSelf.menuitem.setAttribute("hidden", "true"); + } + }, + + /** + * Listener handling the "deleteSelf" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onDeleteSelf: function DVB__onDeleteSelf(aTarget) { + let { breakpointUrl: url, breakpointLine: line } = aTarget; + let breakpoint = DebuggerController.Breakpoints.getBreakpoint(url, line) + + if (aTarget) { + this.removeBreakpoint(aTarget.breakpointActor); + } + if (breakpoint) { + DebuggerController.Breakpoints.removeBreakpoint(breakpoint); + } + }, + + /** + * Listener handling the "enableOthers" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onEnableOthers: function DVB__onEnableOthers(aTarget) { + this._iterate(function(element) { + if (element !== aTarget) { + this._onEnableSelf(element); + } + }.bind(this)); + }, + + /** + * Listener handling the "disableOthers" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onDisableOthers: function DVB__onDisableOthers(aTarget) { + this._iterate(function(element) { + if (element !== aTarget) { + this._onDisableSelf(element); + } + }.bind(this)); + }, + + /** + * Listener handling the "deleteOthers" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onDeleteOthers: function DVB__onDeleteOthers(aTarget) { + this._iterate(function(element) { + if (element !== aTarget) { + this._onDeleteSelf(element); + } + }.bind(this)); + }, + + /** + * Listener handling the "disableAll" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onEnableAll: function DVB__onEnableAll(aTarget) { + this._onEnableOthers(aTarget); + this._onEnableSelf(aTarget); + }, + + /** + * Listener handling the "disableAll" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onDisableAll: function DVB__onDisableAll(aTarget) { + this._onDisableOthers(aTarget); + this._onDisableSelf(aTarget); + }, + + /** + * Listener handling the "deleteAll" menuitem command. + * + * @param object aTarget + * The corresponding breakpoint element. + */ + _onDeleteAll: function DVB__onDeleteAll(aTarget) { + this._onDeleteOthers(aTarget); + this._onDeleteSelf(aTarget); + }, + + /** + * The cached breakpoints container. + */ + _breakpoints: null, + + /** + * Creates a breakpoint context menu. + * + * @param object aBreakpoint + * An element representing a breakpoint. + * @return string + * The popup id. + */ + _createContextMenu: function DVB_createContextMenu(aBreakpoint) { + let commandsetId = "breakpointMenuCommands-" + aBreakpoint.id; + let menupopupId = "breakpointContextMenu-" + aBreakpoint.id; + + let commandsset = document.createElement("commandsset"); + commandsset.setAttribute("id", commandsetId); + + let menupopup = document.createElement("menupopup"); + menupopup.setAttribute("id", menupopupId); + + /** + * Creates a menu item specified by a name with the appropriate attributes + * (label and command handler). + * + * @param string aName + * A global identifier for the menu item. + * @param boolean aHiddenFlag + * True if this menuitem should be hidden. + */ + function createMenuItem(aName, aHiddenFlag) { + let menuitem = document.createElement("menuitem"); + let command = document.createElement("command"); + + let func = this["_on" + aName.charAt(0).toUpperCase() + aName.slice(1)]; + let label = L10N.getStr("breakpointMenuItem." + aName); + + let prefix = "bp-cMenu-"; + let commandId = prefix + aName + "-" + aBreakpoint.id + "-command"; + let menuitemId = prefix + aName + "-" + aBreakpoint.id + "-menuitem"; + + command.setAttribute("id", commandId); + command.setAttribute("label", label); + command.addEventListener("command", func.bind(this, aBreakpoint), true); + + menuitem.setAttribute("id", menuitemId); + menuitem.setAttribute("command", commandId); + menuitem.setAttribute("hidden", aHiddenFlag); + + commandsset.appendChild(command); + menupopup.appendChild(menuitem); + + aBreakpoint[aName] = { + menuitem: menuitem, + command: command + }; + } + + /** + * Creates a simple menu separator element and appends it to the current + * menupopup hierarchy. + */ + function createMenuSeparator() { + let menuseparator = document.createElement("menuseparator"); + menupopup.appendChild(menuseparator); + } + + createMenuItem.call(this, "enableSelf", true); + createMenuItem.call(this, "disableSelf"); + createMenuItem.call(this, "deleteSelf"); + createMenuSeparator(); + createMenuItem.call(this, "enableOthers"); + createMenuItem.call(this, "disableOthers"); + createMenuItem.call(this, "deleteOthers"); + createMenuSeparator(); + createMenuItem.call(this, "enableAll"); + createMenuItem.call(this, "disableAll"); + createMenuSeparator(); + createMenuItem.call(this, "deleteAll"); + + let popupset = document.getElementById("debugger-popups"); + popupset.appendChild(menupopup); + document.documentElement.appendChild(commandsset); + + return menupopupId; + }, + + /** + * Destroys a breakpoint context menu. + * + * @param object aBreakpoint + * An element representing a breakpoint. + */ + _destroyContextMenu: function DVB__destroyContextMenu(aBreakpoint) { + let commandsetId = "breakpointMenuCommands-" + aBreakpoint.id; + let menupopupId = "breakpointContextMenu-" + aBreakpoint.id; + + let commandset = document.getElementById(commandsetId); + let menupopup = document.getElementById(menupopupId); + + if (commandset) { + commandset.parentNode.removeChild(commandset); + } + if (menupopup) { + menupopup.parentNode.removeChild(menupopup); + } + }, + + /** + * Initialization function, called when the debugger is initialized. + */ + initialize: function DVB_initialize() { + let breakpoints = document.getElementById("breakpoints"); + breakpoints.addEventListener("click", this._onBreakpointClick, false); + + this._breakpoints = breakpoints; + this.emptyText(); + }, + + /** + * Destruction function, called when the debugger is shut down. + */ + destroy: function DVB_destroy() { + let breakpoints = this._breakpoints; + breakpoints.removeEventListener("click", this._onBreakpointClick, false); + + this._breakpoints = null; + } +}; + +/** * Functions handling the properties view. */ function PropertiesView() { this.addScope = this._addScope.bind(this); this._addVar = this._addVar.bind(this); this._addProperties = this._addProperties.bind(this); } PropertiesView.prototype = { + /** * A monotonically-increasing counter, that guarantees the uniqueness of scope * IDs. */ _idCount: 1, /** * Adds a scope to contain any inspected variables. @@ -1912,19 +2467,20 @@ PropertiesView.prototype = { * The cached variable properties container. */ _vars: null, /** * Initialization function, called when the debugger is initialized. */ initialize: function DVP_initialize() { - this.createHierarchyStore(); + this._vars = document.getElementById("variables"); - this._vars = document.getElementById("variables"); + this.emptyText(); + this.createHierarchyStore(); }, /** * Destruction function, called when the debugger is shut down. */ destroy: function DVP_destroy() { this._currHierarchy = null; this._prevHierarchy = null; @@ -1932,16 +2488,17 @@ PropertiesView.prototype = { } }; /** * Preliminary setup for the DebuggerView object. */ DebuggerView.Scripts = new ScriptsView(); DebuggerView.StackFrames = new StackFramesView(); +DebuggerView.Breakpoints = new BreakpointsView(); DebuggerView.Properties = new PropertiesView(); /** * Export the source editor to the global scope for easier access in tests. */ Object.defineProperty(window, "editor", { get: function() { return DebuggerView.editor; } });
--- a/browser/devtools/debugger/debugger.css +++ b/browser/devtools/debugger/debugger.css @@ -8,16 +8,30 @@ * Stack frames */ #stackframes { overflow: auto; } /** + * Breakpoints view + */ + +#breakpoints { + overflow-x: hidden; + overflow-y: auto; +} + +.dbg-breakpoint > .state, +.dbg-breakpoint > .content { + overflow: hidden; +} + +/** * Properties elements */ #variables { overflow: auto; } /**
--- a/browser/devtools/debugger/debugger.xul +++ b/browser/devtools/debugger/debugger.xul @@ -75,16 +75,20 @@ <spacer flex="1"/> #ifndef XP_MACOSX <toolbarbutton id="close" tooltiptext="&debuggerUI.closeButton.tooltip;" class="devtools-closebutton"/> #endif </toolbar> <hbox id="dbg-content" flex="1"> - <vbox id="stackframes"/> - <splitter id="stack-script-splitter" class="devtools-side-splitter"/> + <vbox id="stackframes+breakpoints"> + <vbox id="stackframes" flex="1"/> + <splitter class="devtools-horizontal-splitter"/> + <vbox id="breakpoints"/> + </vbox> + <splitter class="devtools-side-splitter"/> <vbox id="editor" flex="1"/> - <splitter id="script-properties-splitter" class="devtools-side-splitter"/> + <splitter class="devtools-side-splitter"/> <vbox id="variables"/> </hbox> </vbox> </window>
--- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -37,32 +37,35 @@ MOCHITEST_BROWSER_TESTS = \ browser_dbg_panesize.js \ browser_dbg_panesize-inner.js \ browser_dbg_stack-01.js \ browser_dbg_stack-02.js \ browser_dbg_stack-03.js \ browser_dbg_stack-04.js \ browser_dbg_stack-05.js \ browser_dbg_location-changes.js \ + browser_dbg_location-changes-blank.js \ browser_dbg_script-switching.js \ browser_dbg_scripts-sorting.js \ browser_dbg_scripts-searching-01.js \ browser_dbg_scripts-searching-02.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_bug723071_editor-breakpoints-pane.js \ browser_dbg_bug731394_editor-contextmenu.js \ browser_dbg_displayName.js \ browser_dbg_iframes.js \ browser_dbg_pause-exceptions.js \ browser_dbg_multiple-windows.js \ browser_dbg_menustatus.js \ browser_dbg_bfcache.js \ + browser_dbg_breakpoint-new-script.js \ head.js \ $(NULL) MOCHITEST_BROWSER_PAGES = \ browser_dbg_tab1.html \ browser_dbg_tab2.html \ browser_dbg_debuggerstatement.html \ browser_dbg_stack.html \ @@ -71,13 +74,14 @@ MOCHITEST_BROWSER_PAGES = \ 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 \ browser_dbg_with-frame.html \ browser_dbg_pause-exceptions.html \ + browser_dbg_breakpoint-new-script.html \ $(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/debugger/test/browser_dbg_breakpoint-new-script.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> +function runDebuggerStatement() { + debugger; +} +function myFunction() { + var a = 1; + debugger; +} +</script> +</head> +<body> + +<button type="button" onclick="myFunction()">Run</button> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_breakpoint-new-script.js @@ -0,0 +1,94 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Bug 771452: make sure that setting a breakpoint in an inline script doesn't +// add it twice. + +const TAB_URL = EXAMPLE_URL + "browser_dbg_breakpoint-new-script.html"; + +var gPane = null; +var gTab = null; +var gDebugger = null; +var gDebuggee = null; + +function test() +{ + debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { + gTab = aTab; + gPane = aPane; + gDebugger = gPane.contentWindow; + gDebuggee = aDebuggee; + + testAddBreakpoint(); + }); +} + +function testAddBreakpoint() +{ + gDebugger.addEventListener("Debugger:FetchedVariables", function test() { + gDebugger.removeEventListener("Debugger:FetchedVariables", test, false); + executeSoon(function() { + var frames = gDebugger.DebuggerView.StackFrames._frames; + + is(gDebugger.DebuggerController.activeThread.state, "paused", + "The debugger statement was reached."); + + is(frames.querySelectorAll(".dbg-stackframe").length, 1, + "Should have one frame."); + + let location = { url: TAB_URL, line: 9 }; + gPane.addBreakpoint(location, function (aResponse, bpClient) { + testResume(); + }); + }); + }, false); + + gDebuggee.runDebuggerStatement(); +} + +function testResume() +{ + gDebugger.DebuggerController.activeThread.addOneTimeListener("resumed", function test() { + gDebugger.DebuggerController.activeThread.addOneTimeListener("paused", function test() { + executeSoon(testBreakpointHit); + }, false); + + EventUtils.sendMouseEvent({ type: "click" }, + content.document.querySelector("button"), + content.window); + + }); + + gDebugger.DebuggerController.activeThread.resume(); +} + +function testBreakpointHit() +{ + var frames = gDebugger.DebuggerView.StackFrames._frames; + + is(gDebugger.DebuggerController.activeThread.state, "paused", + "The breakpoint was hit."); + + resumeAndFinish(); +} + +function resumeAndFinish() { + let thread = gDebugger.DebuggerController.activeThread; + thread.addOneTimeListener("paused", function test(aEvent, aPacket) { + is(aPacket.why.type, "debuggerStatement", "Execution has advanced to the next line."); + isnot(aPacket.why.type, "breakpoint", "No ghost breakpoint was hit."); + + closeDebuggerAndFinish(); + }); + + thread.resume(); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebugger = null; + gDebuggee = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js +++ b/browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js @@ -16,16 +16,17 @@ let gScripts = null; let gEditor = null; let gBreakpoints = null; function test() { let tempScope = {}; Cu.import("resource:///modules/source-editor.jsm", tempScope); let SourceEditor = tempScope.SourceEditor; + let scriptShown = false; let framesAdded = false; let resumed = false; let testStarted = false; debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; gDebuggee = aDebuggee;
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-pane.js @@ -0,0 +1,281 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 723071: test adding a pane to display the list of breakpoints across + * all scripts in the debuggee. + */ + +const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html"; + +let gPane = null; +let gTab = null; +let gDebuggee = null; +let gDebugger = null; +let gScripts = null; +let gBreakpoints = null; +let gBreakpointsElement = null; + +function test() +{ + let scriptShown = false; + let framesAdded = false; + let resumed = false; + let testStarted = false; + + debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.contentWindow; + resumed = true; + + gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { + framesAdded = true; + executeSoon(startTest); + }); + + executeSoon(function() { + gDebuggee.firstCall(); + }); + }); + + function onScriptShown(aEvent) + { + scriptShown = aEvent.detail.url.indexOf("-02.js") != -1; + executeSoon(startTest); + } + + window.addEventListener("Debugger:ScriptShown", onScriptShown); + + function startTest() + { + if (scriptShown && framesAdded && resumed && !testStarted) { + window.removeEventListener("Debugger:ScriptShown", onScriptShown); + testStarted = true; + Services.tm.currentThread.dispatch({ run: performTest }, 0); + } + } + + let breakpointsAdded = 0; + let breakpointsDisabled = 0; + let breakpointsRemoved = 0; + + function performTest() + { + gScripts = gDebugger.DebuggerView.Scripts; + + is(gDebugger.DebuggerController.activeThread.state, "paused", + "Should only be getting stack frames while paused."); + + is(gScripts._scripts.itemCount, 2, "Found the expected number of scripts."); + + let editor = gDebugger.editor; + + isnot(editor.getText().indexOf("debugger"), -1, + "The correct script was loaded initially."); + isnot(gScripts.selected, gScripts.scriptLocations[0], + "the correct script is selected"); + + gBreakpoints = gPane.breakpoints; + is(Object.keys(gBreakpoints), 0, "no breakpoints"); + ok(!gPane.getBreakpoint("chocolate", 3), "getBreakpoint('chocolate', 3) returns falsey"); + + is(editor.getBreakpoints().length, 0, "no breakpoints in the editor"); + + gBreakpointsElement = gDebugger.DebuggerView.Breakpoints._breakpoints; + is(gBreakpointsElement.childNodes.length, 1, + "The breakpoints pane should be empty, but showing a " + + "'no breakpoints' information message."); + is(gBreakpointsElement.childNodes.length, + gBreakpointsElement.querySelectorAll(".list-item.empty").length, + "Found junk in the breakpoints container."); + + addBreakpoints(function() { + is(breakpointsAdded, 3, + "Should have added 3 breakpoints so far."); + is(breakpointsDisabled, 0, + "Shouldn't have disabled anything so far."); + is(breakpointsRemoved, 0, + "Shouldn't have removed anything so far."); + + is(gBreakpointsElement.childNodes.length, + gBreakpointsElement.querySelectorAll(".dbg-breakpoint").length, + "Found junk in the breakpoints container."); + + disableBreakpoints(function() { + is(breakpointsAdded, 3, + "Should still have 3 breakpoints added so far."); + is(breakpointsDisabled, 3, + "Should have 3 disabled breakpoints."); + is(breakpointsRemoved, 0, + "Shouldn't have removed anything so far."); + + is(gBreakpointsElement.childNodes.length, breakpointsAdded, + "Should have the same number of breakpoints in the pane."); + is(gBreakpointsElement.childNodes.length, breakpointsDisabled, + "Should have the same number of disabled breakpoints."); + + addBreakpoints(function() { + is(breakpointsAdded, 3, + "Should still have only 3 breakpoints added so far."); + is(breakpointsDisabled, 3, + "Should still have 3 disabled breakpoints."); + is(breakpointsRemoved, 0, + "Shouldn't have removed anything so far."); + + is(gBreakpointsElement.childNodes.length, breakpointsAdded, + "Since half of the breakpoints already existed, but disabled, " + + "only half of the added breakpoints are actually in the pane."); + is(gBreakpointsElement.childNodes.length, + gBreakpointsElement.querySelectorAll(".dbg-breakpoint").length, + "Found junk in the breakpoints container."); + + removeBreakpoints(function() { + is(breakpointsRemoved, 3, + "Should have 3 removed breakpoints."); + + is(gBreakpointsElement.childNodes.length, 1, + "The breakpoints pane should be empty, but showing a " + + "'no breakpoints' information message."); + is(gBreakpointsElement.childNodes.length, + gBreakpointsElement.querySelectorAll(".list-item.empty").length, + "Found junk in the breakpoints container."); + + finish(); + }); + }); + }); + }, true); + + function addBreakpoints(callback, increment) + { + let line; + + executeSoon(function() + { + line = 4; + gPane.addBreakpoint({url: gScripts.selected, line: line}, + function(cl, err) { + onBreakpointAdd.call({ increment: increment, line: line }, cl, err); + + line = 5; + gPane.addBreakpoint({url: gScripts.selected, line: line}, + function(cl, err) { + onBreakpointAdd.call({ increment: increment, line: line }, cl, err); + + line = 6; + gPane.addBreakpoint({url: gScripts.selected, line: line}, + function(cl, err) { + onBreakpointAdd.call({ increment: increment, line: line }, cl, err); + + executeSoon(function() { + callback(); + }); + }); + }); + }); + }); + } + + function disableBreakpoints(callback) + { + let nodes = Array.slice(gBreakpointsElement.childNodes); + info("Nodes to disable: " + breakpointsAdded); + is(nodes.length, breakpointsAdded, + "The number of nodes to disable is incorrect."); + + Array.forEach(nodes, function(bkp) { + info("Disabling breakpoint: " + bkp.id); + + gDebugger.DebuggerView.Breakpoints.disableBreakpoint(bkp, function() { + if (++breakpointsDisabled !== breakpointsAdded) { + return; + } + executeSoon(function() { + callback(); + }); + }); + }); + } + + function removeBreakpoints(callback) + { + let nodes = Array.slice(gBreakpointsElement.childNodes); + info("Nodes to remove: " + breakpointsAdded); + is(nodes.length, breakpointsAdded, + "The number of nodes to remove is incorrect."); + + Array.forEach(nodes, function(bkp) { + info("Removing breakpoint: " + bkp.id); + + let [url, line, actor] = + [bkp.breakpointUrl, bkp.breakpointLine, bkp.breakpointActor]; + + gDebugger.DebuggerView.Breakpoints.removeBreakpoint(actor); + gPane.removeBreakpoint(gPane.getBreakpoint(url, line), function() { + if (++breakpointsRemoved !== breakpointsAdded) { + return; + } + executeSoon(function() { + callback(); + }); + }); + }); + } + + function onBreakpointAdd(aBreakpointClient, aResponseError) + { + if (this.increment) { + breakpointsAdded++; + } + + is(gBreakpointsElement.childNodes.length, breakpointsAdded, this.increment + ? "Should have added a breakpoint in the pane." + : "Should have the same number of breakpoints in the pane."); + + let id = "breakpoint-" + aBreakpointClient.actor; + let bkp = gDebugger.document.getElementById(id); + let info = bkp.getElementsByClassName("dbg-breakpoint-info")[0]; + let text = bkp.getElementsByClassName("dbg-breakpoint-text")[0]; + let check = bkp.querySelector("checkbox"); + + is(bkp.id, id, + "Breakpoint element " + id + " found succesfully."); + is(info.getAttribute("value"), getExpectedBreakpointInfo(this.line), + "The expected information wasn't found in the breakpoint element."); + is(text.getAttribute("value"), getExpectedLineText(this.line).trim(), + "The expected line text wasn't found in the breakpoint element."); + is(check.getAttribute("checked"), "true", + "The breakpoint enable checkbox is checked as expected."); + } + + function getExpectedBreakpointInfo(line) { + let url = gDebugger.DebuggerView.Scripts.selected; + let label = gDebugger.DebuggerController.SourceScripts.getScriptLabel(url); + return label + ":" + line; + } + + function getExpectedLineText(line) { + return gDebugger.DebuggerController.SourceScripts.getLineText(line - 1); + } + } + + registerCleanupFunction(function() { + is(Object.keys(gBreakpoints).length, 0, "no breakpoint in the debugger"); + ok(!gPane.getBreakpoint(gScripts.scriptLocations[0], 5), + "getBreakpoint(scriptLocations[0], 5) returns no breakpoint"); + + is(breakpointsAdded, 3, "correct number of breakpoints have been added"); + is(breakpointsDisabled, 3, "correct number of breakpoints have been disabled"); + is(breakpointsRemoved, 3, "correct number of breakpoints have been removed"); + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; + gScripts = null; + gBreakpoints = null; + gBreakpointsElement = null; + }); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_location-changes-blank.js @@ -0,0 +1,84 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that changing the tab location URL to a page with no scripts works. + */ + +var gPane = null; +var gTab = null; +var gDebuggee = null; +var gDebugger = null; + +function test() +{ + debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) { + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.contentWindow; + + testSimpleCall(); + }); +} + +function testSimpleCall() { + gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { + Services.tm.currentThread.dispatch({ + run: function() { + var frames = gDebugger.DebuggerView.StackFrames._frames, + childNodes = frames.childNodes; + + is(gDebugger.DebuggerController.activeThread.state, "paused", + "Should only be getting stack frames while paused."); + + is(frames.querySelectorAll(".dbg-stackframe").length, 1, + "Should have only one frame."); + + is(childNodes.length, frames.querySelectorAll(".dbg-stackframe").length, + "All children should be frames."); + + isnot(gDebugger.DebuggerView.Scripts.selected, null, + "There should be a selected script."); + isnot(gDebugger.editor.getText().length, 0, + "The source editor should have some text displayed."); + + testLocationChange(); + } + }, 0); + }); + + gDebuggee.simpleCall(); +} + +function testLocationChange() +{ + gDebugger.DebuggerController.activeThread.resume(function() { + gDebugger.DebuggerController.client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) { + ok(true, "tabNavigated event was fired."); + gDebugger.DebuggerController.client.addOneTimeListener("tabAttached", function(aEvent, aPacket) { + ok(true, "Successfully reattached to the tab again."); + + // Wait for the initial resume... + gDebugger.gClient.addOneTimeListener("resumed", function() { + is(gDebugger.DebuggerView.Scripts.selected, null, + "There should be no selected script."); + is(gDebugger.editor.getText().length, 0, + "The source editor not have any text displayed."); + + closeDebuggerAndFinish(); + }); + }); + }); + content.location = "about:blank"; + }); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_panesize-inner.js +++ b/browser/devtools/debugger/test/browser_dbg_panesize-inner.js @@ -28,17 +28,17 @@ function test() { frame.addEventListener("Debugger:Loaded", function dbgLoaded() { frame.removeEventListener("Debugger:Loaded", dbgLoaded, true); ok(content.Prefs.stackframesWidth, "The debugger preferences should have a saved stackframesWidth value."); ok(content.Prefs.variablesWidth, "The debugger preferences should have a saved variablesWidth value."); - stackframes = content.document.getElementById("stackframes"); + stackframes = content.document.getElementById("stackframes+breakpoints"); variables = content.document.getElementById("variables"); is(content.Prefs.stackframesWidth, stackframes.getAttribute("width"), "The stackframes pane width should be the same as the preferred value."); is(content.Prefs.variablesWidth, variables.getAttribute("width"), "The variables pane width should be the same as the preferred value."); stackframes.setAttribute("width", someWidth1);
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-01.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-01.js @@ -63,17 +63,17 @@ function testScriptLabelShortening() { { href: "resource://random/", leaf: "script_t3_1.js#id?a=1&b=2" }, { href: "resource://random/", leaf: "script_t3_2.js?a=1&b=2#id" }, { href: "resource://random/", leaf: "script_t3_3.js&a=1&b=2#id" } ]; urls.forEach(function(url) { executeSoon(function() { let loc = url.href + url.leaf; - vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true); + vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc }, true); }); }); executeSoon(function() { info("Script labels:"); info(vs.scriptLabels.toSource()); info("Script locations:");
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js +++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js @@ -119,16 +119,33 @@ function testScriptSearching() { write("#__i do not exist__"); ok(gEditor.getCaretPosition().line == 8 && gEditor.getCaretPosition().col == 2 + token.length, "The editor didn't remain at the correct token. (12)"); token = "debugger"; + write("#" + token); + ok(gEditor.getCaretPosition().line == 2 && + gEditor.getCaretPosition().col == 44 + token.length, + "The editor didn't jump to the correct token. (12.1)"); + + clear(); + EventUtils.sendKey("RETURN"); + ok(gEditor.getCaretPosition().line == 2 && + gEditor.getCaretPosition().col == 44 + token.length, + "The editor shouldn't jump to another token. (12.2)"); + + EventUtils.sendKey("ENTER"); + ok(gEditor.getCaretPosition().line == 2 && + gEditor.getCaretPosition().col == 44 + token.length, + "The editor shouldn't jump to another token. (12.3)"); + + write(":1:2:3:a:b:c:::12"); ok(gEditor.getCaretPosition().line == 11 && gEditor.getCaretPosition().col == 0, "The editor didn't jump to the correct line. (13)"); write("#don't#find#me#instead#find#" + token); ok(gEditor.getCaretPosition().line == 2 && gEditor.getCaretPosition().col == 44 + token.length,
--- a/browser/devtools/debugger/test/browser_dbg_scripts-sorting.js +++ b/browser/devtools/debugger/test/browser_dbg_scripts-sorting.js @@ -62,41 +62,41 @@ function addScriptsAndCheckOrder(method, urls.sort(function(a, b) { return Math.random() - 0.5; }); switch (method) { case 1: urls.forEach(function(url) { let loc = url.href + url.leaf; - vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }); + vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc }); }); vs.commitScripts(); break; case 2: urls.forEach(function(url) { let loc = url.href + url.leaf; - vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true); + vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc }, true); }); break; case 3: let i = 0 for (; i < urls.length / 2; i++) { let url = urls[i]; let loc = url.href + url.leaf; - vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }); + vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc }); } vs.commitScripts(); for (; i < urls.length; i++) { let url = urls[i]; let loc = url.href + url.leaf; - vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc }, true); + vs.addScript(ss.getScriptLabel(loc, url.href), { url: loc }, true); } break; } executeSoon(function() { checkScriptsOrder(method); callback(); });
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/Jsbeautify.jsm @@ -0,0 +1,1317 @@ +/* Any copyright is dedicated to the Public Domain. +* http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* +JS Beautifier written by Einar Lielmanis, <einar@jsbeautifier.org> + http://jsbeautifier.org/ + +Originally converted to javascript by Vital, <vital76@gmail.com> +"End braces on own line" added by Chris J. Shull, <chrisjshull@gmail.com> + +You are free to use this in any way you want, in case you find this useful or +working for you. + +Usage: +js_beautify(js_source_text); +js_beautify(js_source_text, options); + +The options are: +indent_size (default 4) — indentation size. +indent_char (default space) — character to indent with. +preserve_newlines (default true) — whether existing line breaks should be + preserved. +max_preserve_newlines (default unlimited) - maximum number of line breaks to be + preserved in one chunk. +jslint_happy (default false) — if true, then jslint-stricter mode is + enforced. + + jslint_happy !jslint_happy + --------------------------------- + function () function() + +brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | + "expand-strict" + put braces on the same line as control statements (default), or put braces + on own line (Allman / ANSI style), or just put end braces on own line. + + expand-strict: put brace on own line even in such cases: + + var a = + { + a: 5, + b: 6 + } + This mode may break your scripts - e.g "return { a: 1 }" will be broken into + two lines, so beware. + +space_before_conditional (default true) - should the space before conditional +statement be added, "if(true)" vs "if (true)", + +unescape_strings (default false) - should printable characters in +strings encoded in \xNN notation be unescaped, "example" vs +"\x65\x78\x61\x6d\x70\x6c\x65" + +e.g + +js_beautify(js_source_text, { + 'indent_size': 1, + 'indent_char': '\t' +}); +*/ + +"use strict"; + +var EXPORTED_SYMBOLS = [ "js_beautify" ]; + +function js_beautify(js_source_text, options={}) { + let input, output, token_text, last_type, last_text, last_last_text, + last_word, flags, flag_store, indent_string, whitespace, wordchar, + punct, parser_pos, line_starters, digits, prefix, token_type, + do_block_just_closed, wanted_newline, just_added_newline, n_newlines, + opt_brace_style, preindent_string = ''; + + // compatibility + if (options.space_after_anon_function !== undefined && + options.jslint_happy === undefined) { + options.jslint_happy = options.space_after_anon_function; + } + if (options.braces_on_own_line !== undefined) { + opt_brace_style = options.braces_on_own_line ? "expand" : "collapse"; + } + opt_brace_style = options.brace_style ? options.brace_style : + (opt_brace_style ? opt_brace_style : "collapse"); + + + let opt_indent_size = options.indent_size ? options.indent_size : 4; + let opt_indent_char = options.indent_char ? options.indent_char : ' '; + let opt_preserve_newlines = typeof options.preserve_newlines === 'undefined' ? + true : options.preserve_newlines; + let opt_max_preserve_newlines = + typeof options.max_preserve_newlines === 'undefined' ? + false : options.max_preserve_newlines; + let opt_jslint_happy = options.jslint_happy === 'undefined' ? + false : options.jslint_happy; + let opt_keep_array_indentation = + typeof options.keep_array_indentation === 'undefined' ? + false : options.keep_array_indentation; + let opt_space_before_conditional = + typeof options.space_before_conditional === 'undefined' ? + true : options.space_before_conditional; + let opt_indent_case = typeof options.indent_case === 'undefined' ? + false : options.indent_case; + let opt_unescape_strings = typeof options.unescape_strings === 'undefined' ? + false : options.unescape_strings; + + just_added_newline = false; + + // cache the source's length. + let input_length = js_source_text.length; + + function trim_output(eat_newlines) { + eat_newlines = typeof eat_newlines === 'undefined' ? false : eat_newlines; + while (output.length && (output[output.length - 1] === ' ' || + output[output.length - 1] === indent_string || + output[output.length - 1] === preindent_string || + (eat_newlines && (output[output.length - 1] === '\n' || + output[output.length - 1] === '\r')))) { + output.pop(); + } + } + + function trim(s) { + return s.replace(/^\s\s*|\s\s*$/, ''); + } + + function split_newlines(s) + { + return s.split(/\x0d\x0a|\x0a/); + } + + function force_newline() + { + let old_keep_array_indentation = opt_keep_array_indentation; + opt_keep_array_indentation = false; + print_newline(); + opt_keep_array_indentation = old_keep_array_indentation; + } + + function print_newline(ignore_repeated) { + + flags.eat_next_space = false; + if (opt_keep_array_indentation && is_array(flags.mode)) { + return; + } + + ignore_repeated = typeof ignore_repeated === 'undefined' ? true : ignore_repeated; + + flags.if_line = false; + trim_output(); + + if (!output.length) { + return; // no newline on start of file + } + + if (output[output.length - 1] !== "\n" || !ignore_repeated) { + just_added_newline = true; + output.push("\n"); + } + if (preindent_string) { + output.push(preindent_string); + } + for (let i = 0; i < flags.indentation_level; i += 1) { + output.push(indent_string); + } + if (flags.var_line && flags.var_line_reindented) { + output.push(indent_string); // skip space-stuffing, if indenting with a tab + } + if (flags.case_body) { + output.push(indent_string); + } + } + + function print_single_space() { + + if (last_type === 'TK_COMMENT') { + return print_newline(); + } + if (flags.eat_next_space) { + flags.eat_next_space = false; + return; + } + let last_output = ' '; + if (output.length) { + last_output = output[output.length - 1]; + } + if (last_output !== ' ' && last_output !== '\n' && + last_output !== indent_string) { // prevent occassional duplicate space + output.push(' '); + } + } + + + function print_token() { + just_added_newline = false; + flags.eat_next_space = false; + output.push(token_text); + } + + function indent() { + flags.indentation_level += 1; + } + + + function remove_indent() { + if (output.length && output[output.length - 1] === indent_string) { + output.pop(); + } + } + + function set_mode(mode) { + if (flags) { + flag_store.push(flags); + } + flags = { + previous_mode: flags ? flags.mode : 'BLOCK', + mode: mode, + var_line: false, + var_line_tainted: false, + var_line_reindented: false, + in_html_comment: false, + if_line: false, + in_case_statement: false, // switch(..){ INSIDE HERE } + in_case: false, // we're on the exact line with "case 0:" + case_body: false, // the indented case-action block + eat_next_space: false, + indentation_baseline: -1, + indentation_level: (flags ? flags.indentation_level + + (flags.case_body ? 1 : 0) + + ((flags.var_line && flags.var_line_reindented) ? 1 : 0) : 0), + ternary_depth: 0 + }; + } + + function is_array(mode) { + return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]'; + } + + function is_expression(mode) { + return in_array(mode, ['[EXPRESSION]', '(EXPRESSION)', + '(FOR-EXPRESSION)', '(COND-EXPRESSION)']); + } + + function restore_mode() { + do_block_just_closed = flags.mode === 'DO_BLOCK'; + if (flag_store.length > 0) { + let mode = flags.mode; + flags = flag_store.pop(); + flags.previous_mode = mode; + } + } + + function all_lines_start_with(lines, c) { + for (let line of lines) { + line = trim(line); + if (line.charAt(0) !== c) { + return false; + } + } + return true; + } + + function is_special_word(word) { + return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']); + } + + function in_array(what, arr) { + return arr.indexOf(what) != -1; + } + + function look_up(exclude) { + let local_pos = parser_pos; + let c = input.charAt(local_pos); + while (in_array(c, whitespace) && c != exclude) { + local_pos++; + if (local_pos >= input_length) return 0; + c = input.charAt(local_pos); + } + return c; + } + + function get_next_token() { + n_newlines = 0; + + if (parser_pos >= input_length) { + return ['', 'TK_EOF']; + } + + wanted_newline = false; + + let c = input.charAt(parser_pos); + parser_pos += 1; + + let keep_whitespace = opt_keep_array_indentation && is_array(flags.mode); + + if (keep_whitespace) { + + // + // slight mess to allow nice preservation of array indentation and + // reindent that correctly first time when we get to the arrays: + // let a = [ + // ....'something' + // we make note of whitespace_count = 4 into flags.indentation_baseline so + // we know that 4 whitespaces in original source match indent_level of + // reindented source and afterwards, when we get to + // 'something, + // .......'something else' + // we know that this should be indented to indent_level + + // (7 - indentation_baseline) spaces + // + let whitespace_count = 0; + + while (in_array(c, whitespace)) { + if (c === "\n") { + trim_output(); + output.push("\n"); + just_added_newline = true; + whitespace_count = 0; + } else { + if (c === '\t') { + whitespace_count += 4; + } else if (c === '\r') { + // nothing + } else { + whitespace_count += 1; + } + } + + if (parser_pos >= input_length) { + return ['', 'TK_EOF']; + } + + c = input.charAt(parser_pos); + parser_pos += 1; + + } + if (flags.indentation_baseline === -1) { + flags.indentation_baseline = whitespace_count; + } + + if (just_added_newline) { + for (let i = 0; i < flags.indentation_level + 1; i += 1) { + output.push(indent_string); + } + if (flags.indentation_baseline !== -1) { + for (let i = 0; i < whitespace_count - flags.indentation_baseline; i++) { + output.push(' '); + } + } + } + } else { + while (in_array(c, whitespace)) { + + if (c === "\n") { + n_newlines += ( (opt_max_preserve_newlines) ? + (n_newlines <= opt_max_preserve_newlines) ? 1: 0: 1 ); + } + + + if (parser_pos >= input_length) { + return ['', '