author | David Anderson <danderson@mozilla.com> |
Mon, 10 Sep 2012 12:16:38 -0700 | |
changeset 106784 | 28bfdee5702699b0e9313837ff125b55a9f90189 |
parent 106783 | 8bd7ad214f7a6e9cd93ad99407f43ef97da97ef3 (current diff) |
parent 104698 | ebd88961b3c3810a37412ea147d468f0f865badc (diff) |
child 106785 | 9425b626563a19d5ee8b3e37e13cc09e1bcd27f8 |
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 | 18.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/b2g/Makefile.in +++ b/b2g/Makefile.in @@ -10,12 +10,16 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk DIRS = chrome components locales ifeq ($(OS_ARCH),WINNT) DIRS += $(DEPTH)/xulrunner/tools/redit endif +ifneq ($(GAIADIR),) +DIRS += gaia +endif + DIRS += app include $(topsrcdir)/config/rules.mk include $(topsrcdir)/testing/testsuite-targets.mk
--- a/b2g/app/Makefile.in +++ b/b2g/app/Makefile.in @@ -11,17 +11,21 @@ include $(DEPTH)/config/autoconf.mk PREF_JS_EXPORTS = $(srcdir)/b2g.js ifdef ENABLE_MARIONETTE DEFINES += -DENABLE_MARIONETTE=1 endif ifndef LIBXUL_SDK +ifneq ($(GAIADIR),) +PROGRAM=$(MOZ_APP_NAME)-bin$(BIN_SUFFIX) +else PROGRAM=$(MOZ_APP_NAME)$(BIN_SUFFIX) +endif CPPSRCS = nsBrowserApp.cpp LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/base LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/build LOCAL_INCLUDES += -I$(DEPTH)/build
--- a/b2g/app/macbuild/Contents/Info.plist.in +++ b/b2g/app/macbuild/Contents/Info.plist.in @@ -1,138 +1,32 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> - <key>CFBundleDocumentTypes</key> - <array> - <dict> - <key>CFBundleTypeExtensions</key> - <array> - <string>html</string> - <string>htm</string> - <string>shtml</string> - <string>xht</string> - <string>xhtml</string> - </array> - <key>CFBundleTypeIconFile</key> - <string>document.icns</string> - <key>CFBundleTypeName</key> - <string>HTML Document</string> - <key>CFBundleTypeOSTypes</key> - <array> - <string>HTML</string> - </array> - <key>CFBundleTypeRole</key> - <string>Viewer</string> - </dict> - <dict> - <key>CFBundleTypeExtensions</key> - <array> - <string>text</string> - <string>txt</string> - <string>js</string> - <string>log</string> - <string>css</string> - <string>xul</string> - <string>rdf</string> - </array> - <key>CFBundleTypeIconFile</key> - <string>document.icns</string> - <key>CFBundleTypeName</key> - <string>Text Document</string> - <key>CFBundleTypeOSTypes</key> - <array> - <string>TEXT</string> - <string>utxt</string> - </array> - <key>CFBundleTypeRole</key> - <string>Viewer</string> - </dict> - <dict> - <key>CFBundleTypeExtensions</key> - <array> - <string>jpeg</string> - <string>jpg</string> - <string>png</string> - <string>gif</string> - </array> - <key>CFBundleTypeIconFile</key> - <string>fileBookmark.icns</string> - <key>CFBundleTypeName</key> - <string>document.icns</string> - <key>CFBundleTypeOSTypes</key> - <array> - <string>GIFf</string> - <string>JPEG</string> - <string>PNGf</string> - </array> - <key>CFBundleTypeRole</key> - <string>Viewer</string> - </dict> - </array> <key>CFBundleExecutable</key> - <string>%MOZ_APP_NAME%</string> + <string>b2g</string> <key>CFBundleGetInfoString</key> <string>%APP_NAME% %APP_VERSION%</string> <key>CFBundleIconFile</key> <string>%MOZ_APP_NAME%</string> <key>CFBundleIdentifier</key> <string>org.mozilla.b2g</string> <key>CFBundleInfoDictionaryVersion</key> <string>%MOZ_APP_VERSION%</string> <key>CFBundleName</key> <string>%APP_NAME%</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>%APP_VERSION%</string> <key>CFBundleSignature</key> <string>MOZB</string> - <key>CFBundleURLTypes</key> - <array> - <dict> - <key>CFBundleURLIconFile</key> - <string>document.icns</string> - <key>CFBundleURLName</key> - <string>http URL</string> - <key>CFBundleURLSchemes</key> - <array> - <string>http</string> - </array> - </dict> - <dict> - <key>CFBundleURLIconFile</key> - <string>document.icns</string> - <key>CFBundleURLName</key> - <string>https URL</string> - <key>CFBundleURLSchemes</key> - <array> - <string>https</string> - </array> - </dict> - <dict> - <key>CFBundleURLName</key> - <string>ftp URL</string> - <key>CFBundleURLSchemes</key> - <array> - <string>ftp</string> - </array> - </dict> - <dict> - <key>CFBundleURLName</key> - <string>file URL</string> - <key>CFBundleURLSchemes</key> - <array> - <string>file</string> - </array> - </dict> - </array> <key>CFBundleVersion</key> <string>%APP_VERSION%</string> <key>NSAppleScriptEnabled</key> <true/> <key>CGDisableCoalescedUpdates</key> <true/> </dict> </plist>
--- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -347,18 +347,17 @@ var shell = { ObjectWrapper.wrap(details, getContentWindow())); }, sendSystemMessage: function shell_sendSystemMessage(msg) { let origin = Services.io.newURI(msg.manifest, null, null).prePath; this.sendChromeEvent({ type: 'open-app', url: msg.uri, - origin: origin, - manifest: msg.manifest, + manifestURL: msg.manifest, isActivity: (msg.type == 'activity'), target: msg.target }); }, receiveMessage: function shell_receiveMessage(message) { if (message.name != 'content-handler') { return; @@ -596,17 +595,17 @@ var WebappsHelper = { DOMApplicationRegistry.getManifestFor(json.origin, function(aManifest) { if (!aManifest) return; let manifest = new DOMApplicationManifest(aManifest, json.origin); shell.sendChromeEvent({ "type": "webapps-launch", "url": manifest.fullLaunchPath(json.startPoint), - "origin": json.origin + "manifestURL": json.manifestURL }); }); break; case "webapps-ask-install": let id = this.registerInstaller(json); shell.sendChromeEvent({ type: "webapps-ask-install", id: id,
new file mode 100644 --- /dev/null +++ b/b2g/gaia/Makefile.in @@ -0,0 +1,42 @@ +# 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/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +GAIA_PATH := gaia/profile + + +# We don't have a wrapper script on Windows yet +ifneq ($(OS_ARCH),WINNT) +PROGRAM = $(MOZ_APP_NAME)$(BIN_SUFFIX) +CSRCS = run-b2g.c + +DEFINES += \ + -DB2G_NAME=\"$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)\" \ + -DGAIA_PATH=\"$(GAIA_PATH)\" \ + $(NULL) + +# This is needed to avoid making run-b2g depend on mozglue +WRAP_LDFLAGS := +endif + +GENERATED_DIRS += $(DIST)/bin/$(GAIA_PATH) + +include $(topsrcdir)/config/rules.mk + + +libs:: + # Below here is how Gaia gets built + # The Gaia build system freaks out when N > 1 for -jN + $(MAKE) -j1 -C $(GAIADIR) clean + $(MAKE) -j1 -C $(GAIADIR) profile GAIA_DOMAIN=desktop-builds.$(MOZ_APP_NAME).mozilla.org + (cd $(GAIADIR)/profile && tar $(TAR_CREATE_FLAGS) - .) | (cd $(abspath $(DIST))/bin/$(GAIA_PATH) && tar -xf -) + + +
new file mode 100644 --- /dev/null +++ b/b2g/gaia/run-b2g.c @@ -0,0 +1,47 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <libgen.h> + +#ifndef B2G_NAME +#define B2G_NAME "b2g-bin" +#endif +#ifndef GAIA_PATH +#define GAIA_PATH "gaia/profile" +#endif +#define NOMEM "Could not allocate enough memory" + +void error(char* msg){ + fprintf(stderr, "ERROR: %s\n", msg); +} + +int main(int argc, char* argv[], char* envp[]){ + char* cwd = NULL; + char* full_path = NULL; + char* full_profile_path = NULL; + printf("Starting %s\n", B2G_NAME); + cwd = realpath(dirname(argv[0]), NULL); + full_path = (char*) malloc(strlen(cwd) + strlen(B2G_NAME) + 2); + if (!full_path) { + error(NOMEM); + return -2; + } + full_profile_path = (char*) malloc(strlen(cwd) + strlen(GAIA_PATH) + 2); + if (!full_profile_path) { + error(NOMEM); + return -2; + } + sprintf(full_path, "%s/%s", cwd, B2G_NAME); + sprintf(full_profile_path, "%s/%s", cwd, GAIA_PATH); + free(cwd); + printf("Running: %s -profile %s\n", full_path, full_profile_path); + fflush(stdout); + fflush(stderr); + execle(full_path, full_path, "-profile", full_profile_path, NULL, envp); + error("unable to start"); + perror(argv[0]); + free(full_path); + free(full_profile_path); + return -1; +}
--- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -696,8 +696,14 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL @BINPATH@/components/ActivitiesGlue.js @BINPATH@/components/ProcessGlobal.js @BINPATH@/components/ContentHandler.js @BINPATH@/components/PaymentGlue.js #ifdef XP_MACOSX @BINPATH@/@DLL_PREFIX@plugin_child_interpose@DLL_SUFFIX@ #endif + +#ifdef PACKAGE_GAIA +[gaia] +@BINPATH@/gaia/* +@BINPATH@/b2g-bin@BIN_SUFFIX@ +#endif
--- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -84,17 +84,17 @@ <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/> <command id="cmd_fullZoomReset" oncommand="FullZoom.reset()"/> <command id="cmd_fullZoomToggle" oncommand="ZoomManager.toggleZoom();"/> <command id="Browser:OpenLocation" oncommand="openLocation();"/> <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/> <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/> <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/> - <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focus();" disabled="true"/> + <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/> <command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/> <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();"/> <command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true" hidden="true"/> <command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true" hidden="true"/> <command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/> <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/> <command id="Tools:StyleEditor" oncommand="StyleEditor.toggle();" disabled="true" hidden="true"/> <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
--- a/browser/base/content/browser-social.js +++ b/browser/base/content/browser-social.js @@ -215,16 +215,17 @@ let SocialFlyout = { _createFrame: function() { let panel = this.panel; if (!Social.provider || panel.firstChild) return; // create and initialize the panel for this window let iframe = document.createElement("iframe"); iframe.setAttribute("type", "content"); + iframe.setAttribute("class", "social-panel-frame"); iframe.setAttribute("flex", "1"); iframe.setAttribute("origin", Social.provider.origin); panel.appendChild(iframe); }, unload: function() { let panel = this.panel; if (!panel.firstChild) @@ -531,16 +532,17 @@ var SocialToolbar = { for each(let name in iconNames) { let icon = provider.ambientNotificationIcons[name]; let notificationFrameId = "social-status-" + icon.name; let notificationFrame = document.getElementById(notificationFrameId); if (!notificationFrame) { notificationFrame = document.createElement("iframe"); notificationFrame.setAttribute("type", "content"); + notificationFrame.setAttribute("class", "social-panel-frame"); notificationFrame.setAttribute("id", notificationFrameId); notificationFrame.setAttribute("mozbrowser", "true"); notificationFrames.appendChild(notificationFrame); } notificationFrame.setAttribute("origin", provider.origin); if (notificationFrame.getAttribute("src") != icon.contentPanel) notificationFrame.setAttribute("src", icon.contentPanel);
--- a/browser/base/content/browser-tabPreviews.js +++ b/browser/base/content/browser-tabPreviews.js @@ -653,17 +653,17 @@ var allTabs = { var matches = 0; if (filter.length && !tab.hidden) { let tabstring = tab.linkedBrowser.currentURI.spec; try { tabstring = decodeURI(tabstring); } catch (e) {} tabstring = tab.label + " " + tab.label.toLocaleLowerCase() + " " + tabstring; for (let i = 0; i < filter.length; i++) - matches += tabstring.indexOf(filter[i]) > -1; + matches += tabstring.contains(filter[i]); } if (matches < filter.length || tab.hidden) { preview.hidden = true; } else { this._visible++; this._updatePreview(preview); preview.hidden = false;
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1044,17 +1044,17 @@ var gBrowserInit = { .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIXULWindow) .XULBrowserWindow = window.XULBrowserWindow; window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess(); // set default character set if provided if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) { - if (window.arguments[1].indexOf("charset=") != -1) { + if (window.arguments[1].startsWith("charset=")) { var arrayArgComponents = window.arguments[1].split("="); if (arrayArgComponents) { //we should "inherit" the charset menu setting in a new window getMarkupDocumentViewer().defaultCharacterSet = arrayArgComponents[1]; } } } @@ -2753,17 +2753,17 @@ function getMeOutOfHere() { // Get the start page from the *default* pref branch, not the user's var prefs = Cc["@mozilla.org/preferences-service;1"] .getService(Ci.nsIPrefService).getDefaultBranch(null); var url = BROWSER_NEW_TAB_URL; try { url = prefs.getComplexValue("browser.startup.homepage", Ci.nsIPrefLocalizedString).data; // If url is a pipe-delimited set of pages, just take the first one. - if (url.indexOf("|") != -1) + if (url.contains("|")) url = url.split("|")[0]; } catch(e) { Components.utils.reportError("Couldn't get homepage pref: " + e); } content.location = url; } function BrowserFullScreen() @@ -3462,17 +3462,17 @@ function FillHistoryMenu(aParent) { aParent.appendChild(item); } return true; } function addToUrlbarHistory(aUrlToAdd) { if (aUrlToAdd && - aUrlToAdd.indexOf(" ") == -1 && + !aUrlToAdd.contains(" ") && !/[\x00-\x1F]/.test(aUrlToAdd)) PlacesUIUtils.markPageAsTyped(aUrlToAdd); } function toJavaScriptConsole() { toOpenWindowByType("global:console", "chrome://global/content/console.xul"); }
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -263,20 +263,22 @@ 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"> + <panel id="social-notification-panel" class="social-panel" + type="arrow" hidden="true" noautofocus="true"> <box id="social-notification-box" flex="1"></box> </panel> <panel id="social-flyout-panel" + class="social-panel" onpopupshown="SocialFlyout.onShown()" onpopuphidden="SocialFlyout.onHidden()" side="right" type="arrow" hidden="true" noautofocus="true" position="topcenter topright"/>
--- a/browser/base/content/pageinfo/pageInfo.xul +++ b/browser/base/content/pageinfo/pageInfo.xul @@ -59,17 +59,17 @@ <command id="cmd_cookieToggle" oncommand="onRadioClick('cookie');"/> <command id="cmd_installToggle" oncommand="onRadioClick('install');"/> <command id="cmd_fullscreenToggle" oncommand="onRadioClick('fullscreen');"/> <command id="cmd_geoToggle" oncommand="onRadioClick('geo');"/> <command id="cmd_indexedDBToggle" oncommand="onRadioClick('indexedDB');"/> <command id="cmd_pluginsToggle" oncommand="onRadioClick('plugins');"/> </commandset> - <keyset> + <keyset id="pageInfoKeySet"> <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/> <key keycode="VK_ESCAPE" command="cmd_close"/> #ifdef XP_MACOSX <key key="." modifiers="meta" command="cmd_close"/> #else <key keycode="VK_F1" command="cmd_help"/> #endif <key key="©.key;" modifiers="accel" command="cmd_copy"/> @@ -97,60 +97,60 @@ <!-- Others added by overlay --> </radiogroup> </windowdragbox> <deck id="mainDeck" flex="1"> <!-- General page information --> <vbox id="generalPanel"> <textbox class="header" readonly="true" id="titletext"/> - <grid> + <grid id="generalGrid"> <columns> <column/> <column class="gridSeparator"/> <column flex="1"/> </columns> - <rows> - <row> + <rows id="generalRows"> + <row id="generalURLRow"> <label control="urltext" value="&generalURL;"/> <separator/> <textbox readonly="true" id="urltext"/> </row> - <row> + <row id="generalSeparatorRow1"> <separator class="thin"/> </row> - <row> + <row id="generalTypeRow"> <label control="typetext" value="&generalType;"/> <separator/> <textbox readonly="true" id="typetext"/> </row> - <row> + <row id="generalModeRow"> <label control="modetext" value="&generalMode;"/> <separator/> <textbox readonly="true" crop="end" id="modetext"/> </row> - <row> + <row id="generalEncodingRow"> <label control="encodingtext" value="&generalEncoding;"/> <separator/> <textbox readonly="true" id="encodingtext"/> </row> - <row> + <row id="generalSizeRow"> <label control="sizetext" value="&generalSize;"/> <separator/> <textbox readonly="true" id="sizetext"/> </row> - <row> + <row id="generalReferrerRow"> <label control="refertext" value="&generalReferrer;"/> <separator/> <textbox readonly="true" id="refertext"/> </row> - <row> + <row id="generalSeparatorRow2"> <separator class="thin"/> </row> - <row> + <row id="generalModifiedRow"> <label control="modifiedtext" value="&generalModified;"/> <separator/> <textbox readonly="true" id="modifiedtext"/> </row> </rows> </grid> <separator class="thin"/> <groupbox id="metaTags" flex="1" class="collapsable treebox"> @@ -160,24 +160,24 @@ <treecol id="meta-name" label="&generalMetaName;" persist="width" flex="1" onclick="gMetaView.onPageMediaSort('meta-name');"/> <splitter class="tree-splitter"/> <treecol id="meta-content" label="&generalMetaContent;" persist="width" flex="4" onclick="gMetaView.onPageMediaSort('meta-content');"/> </treecols> - <treechildren flex="1"/> + <treechildren id="metatreechildren" flex="1"/> </tree> </groupbox> <groupbox id="securityBox"> <caption id="securityBoxCaption" label="&securityHeader;"/> <description id="general-security-identity" class="header"/> <description id="general-security-privacy" class="header"/> - <hbox align="right"> + <hbox id="securityDetailsButtonBox" align="right"> <button id="security-view-details" label="&generalSecurityDetails;" accesskey="&generalSecurityDetails.accesskey;" oncommand="onClickMore();"/> </hbox> </groupbox> </vbox> <!-- Media information --> @@ -200,199 +200,200 @@ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4" width="4" id="image-alt" label="&mediaAltHeader;" onclick="gImageView.onPageMediaSort('image-alt');"/> <splitter class="tree-splitter"/> <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1" width="1" id="image-count" label="&mediaCount;" onclick="gImageView.onPageMediaSort('image-count');"/> </treecols> - <treechildren flex="1"/> + <treechildren id="imagetreechildren" flex="1"/> </tree> <splitter orient="vertical" id="mediaSplitter"/> <vbox flex="1" id="mediaPreviewBox" collapsed="true"> <grid id="mediaGrid"> <columns> <column id="mediaLabelColumn"/> <column class="gridSeparator"/> <column flex="1"/> </columns> - <rows> - <row> + <rows id="mediaRows"> + <row id="mediaLocationRow"> <label control="imageurltext" value="&mediaLocation;"/> <separator/> <textbox readonly="true" id="imageurltext"/> </row> - <row> + <row id="mediaTypeRow"> <label control="imagetypetext" value="&generalType;"/> <separator/> <textbox readonly="true" id="imagetypetext"/> </row> - <row> + <row id="mediaSizeRow"> <label control="imagesizetext" value="&generalSize;"/> <separator/> <textbox readonly="true" id="imagesizetext"/> </row> - <row> + <row id="mediaDimensionRow"> <label control="imagedimensiontext" value="&mediaDimension;"/> <separator/> <textbox readonly="true" id="imagedimensiontext"/> </row> - <row> + <row id="mediaTextRow"> <label control="imagetext" value="&mediaText;"/> <separator/> <textbox readonly="true" id="imagetext"/> </row> - <row> + <row id="mediaLongdescRow"> <label control="imagelongdesctext" value="&mediaLongdesc;"/> <separator/> <textbox readonly="true" id="imagelongdesctext"/> </row> </rows> </grid> - <hbox align="end"> - <vbox> + <hbox id="imageSaveBox" align="end"> + <vbox id="blockImageBox"> <checkbox id="blockImage" hidden="true" oncommand="onBlockImage()" accesskey="&mediaBlockImage.accesskey;"/> <label control="thepreviewimage" value="&mediaPreview;" class="header"/> </vbox> - <spacer flex="1"/> + <spacer id="imageSaveBoxSpacer" flex="1"/> <button label="&mediaSaveAs;" accesskey="&mediaSaveAs.accesskey;" icon="save" id="imagesaveasbutton" oncommand="saveMedia();"/> </hbox> - <vbox class="inset iframe" flex="1" pack="center"> + <vbox id="imagecontainerbox" class="inset iframe" flex="1" pack="center"> <hbox id="theimagecontainer" pack="center"> <image id="thepreviewimage"/> </hbox> <hbox id="brokenimagecontainer" pack="center" collapsed="true"> <image id="brokenimage" src="resource://gre-resources/broken-image.png"/> </hbox> </vbox> </vbox> <hbox id="mediaSaveBox" collapsed="true"> - <spacer flex="1"/> + <spacer id="mediaSaveBoxSpacer" flex="1"/> <button label="&mediaSaveAs;" accesskey="&mediaSaveAs2.accesskey;" - icon="save" oncommand="saveMedia();"/> + icon="save" id="mediasaveasbutton" + oncommand="saveMedia();"/> </hbox> </vbox> <!-- Feeds --> <vbox id="feedPanel"> <richlistbox id="feedListbox" flex="1"/> </vbox> <!-- Permissions --> <vbox id="permPanel"> - <hbox> + <hbox id="permHostBox"> <label value="&permissionsFor;" control="hostText" /> <textbox id="hostText" class="header" readonly="true" crop="end" flex="1"/> </hbox> <vbox id="permList" flex="1"> - <vbox class="permission"> + <vbox class="permission" id="permImageRow"> <label class="permissionLabel" id="permImageLabel" value="&permImage;" control="imageRadioGroup"/> - <hbox role="group" aria-labelledby="permImageLabel"> + <hbox id="permImageBox" role="group" aria-labelledby="permImageLabel"> <checkbox id="imageDef" command="cmd_imageDef" label="&permUseDefault;"/> <spacer flex="1"/> <radiogroup id="imageRadioGroup" orient="horizontal"> <radio id="image#1" command="cmd_imageToggle" label="&permAllow;"/> <radio id="image#2" command="cmd_imageToggle" label="&permBlock;"/> </radiogroup> </hbox> </vbox> - <vbox class="permission"> + <vbox class="permission" id="permPopupRow"> <label class="permissionLabel" id="permPopupLabel" value="&permPopup;" control="popupRadioGroup"/> - <hbox role="group" aria-labelledby="permPopupLabel"> + <hbox id="permPopupBox" role="group" aria-labelledby="permPopupLabel"> <checkbox id="popupDef" command="cmd_popupDef" label="&permUseDefault;"/> <spacer flex="1"/> <radiogroup id="popupRadioGroup" orient="horizontal"> <radio id="popup#1" command="cmd_popupToggle" label="&permAllow;"/> <radio id="popup#2" command="cmd_popupToggle" label="&permBlock;"/> </radiogroup> </hbox> </vbox> - <vbox class="permission"> + <vbox class="permission" id="permCookieRow"> <label class="permissionLabel" id="permCookieLabel" value="&permCookie;" control="cookieRadioGroup"/> - <hbox role="group" aria-labelledby="permCookieLabel"> + <hbox id="permCookieBox" role="group" aria-labelledby="permCookieLabel"> <checkbox id="cookieDef" command="cmd_cookieDef" label="&permUseDefault;"/> <spacer flex="1"/> <radiogroup id="cookieRadioGroup" orient="horizontal"> <radio id="cookie#1" command="cmd_cookieToggle" label="&permAllow;"/> <radio id="cookie#8" command="cmd_cookieToggle" label="&permAllowSession;"/> <radio id="cookie#2" command="cmd_cookieToggle" label="&permBlock;"/> </radiogroup> </hbox> </vbox> - <vbox class="permission"> + <vbox class="permission" id="permInstallRow"> <label class="permissionLabel" id="permInstallLabel" value="&permInstall;" control="installRadioGroup"/> - <hbox role="group" aria-labelledby="permInstallLabel"> + <hbox id="permInstallBox" role="group" aria-labelledby="permInstallLabel"> <checkbox id="installDef" command="cmd_installDef" label="&permUseDefault;"/> <spacer flex="1"/> <radiogroup id="installRadioGroup" orient="horizontal"> <radio id="install#1" command="cmd_installToggle" label="&permAllow;"/> <radio id="install#2" command="cmd_installToggle" label="&permBlock;"/> </radiogroup> </hbox> </vbox> - <vbox class="permission"> + <vbox class="permission" id="permGeoRow" > <label class="permissionLabel" id="permGeoLabel" value="&permGeo;" control="geoRadioGroup"/> - <hbox role="group" aria-labelledby="permGeoLabel"> + <hbox id="permGeoBox" role="group" aria-labelledby="permGeoLabel"> <checkbox id="geoDef" command="cmd_geoDef" label="&permAskAlways;"/> <spacer flex="1"/> <radiogroup id="geoRadioGroup" orient="horizontal"> <radio id="geo#1" command="cmd_geoToggle" label="&permAllow;"/> <radio id="geo#2" command="cmd_geoToggle" label="&permBlock;"/> </radiogroup> </hbox> </vbox> - <vbox class="permission"> + <vbox class="permission" id="permIndexedDBRow"> <label class="permissionLabel" id="permIndexedDBLabel" value="&permIndexedDB;" control="indexedDBRadioGroup"/> - <hbox role="group" aria-labelledby="permIndexedDBLabel"> + <hbox id="permIndexedDBBox" role="group" aria-labelledby="permIndexedDBLabel"> <checkbox id="indexedDBDef" command="cmd_indexedDBDef" label="&permUseDefault;"/> <spacer flex="1"/> <radiogroup id="indexedDBRadioGroup" orient="horizontal"> <!-- Ask and Allow are purposefully reversed here! --> <radio id="indexedDB#1" command="cmd_indexedDBToggle" label="&permAskAlways;"/> <radio id="indexedDB#0" command="cmd_indexedDBToggle" label="&permAllow;"/> <radio id="indexedDB#2" command="cmd_indexedDBToggle" label="&permBlock;"/> </radiogroup> </hbox> - <hbox> + <hbox id="permIndexedDBBox2"> <spacer flex="1"/> - <vbox pack="center"> + <vbox id="permIndexedDBStatusBox" pack="center"> <label id="indexedDBStatus" control="indexedDBClear" hidden="true"/> </vbox> <button id="indexedDBClear" label="&permClearStorage;" hidden="true" accesskey="&permClearStorage.accesskey;" onclick="onIndexedDBClear();"/> </hbox> </vbox> <vbox class="permission" id="permPluginsRow"> <label class="permissionLabel" id="permPluginsLabel" value="&permPlugins;" control="pluginsRadioGroup"/> - <hbox role="group" aria-labelledby="permPluginsLabel"> + <hbox id="permPluginsBox" role="group" aria-labelledby="permPluginsLabel"> <checkbox id="pluginsDef" command="cmd_pluginsDef" label="&permAskAlways;"/> <spacer flex="1"/> <radiogroup id="pluginsRadioGroup" orient="horizontal"> <radio id="plugins#1" command="cmd_pluginsToggle" label="&permAllow;"/> <radio id="plugins#2" command="cmd_pluginsToggle" label="&permBlock;"/> </radiogroup> </hbox> </vbox> - <vbox class="permission"> + <vbox class="permission" id="permFullscreenRow"> <label class="permissionLabel" id="permFullscreenLabel" value="&permFullscreen;" control="fullscreenRadioGroup"/> - <hbox role="group" aria-labelledby="permFullscreenLabel"> + <hbox id="permFullscreenBox" role="group" aria-labelledby="permFullscreenLabel"> <checkbox id="fullscreenDef" command="cmd_fullscreenDef" label="&permUseDefault;"/> <spacer flex="1"/> <radiogroup id="fullscreenRadioGroup" orient="horizontal"> <radio id="fullscreen#0" command="cmd_fullscreenToggle" label="&permAskAlways;"/> <radio id="fullscreen#1" command="cmd_fullscreenToggle" label="&permAllow;"/> <radio id="fullscreen#2" command="cmd_fullscreenToggle" label="&permBlock;"/> </radiogroup> </hbox> @@ -400,95 +401,102 @@ </vbox> </vbox> <!-- Security & Privacy --> <vbox id="securityPanel"> <!-- Identity Section --> <groupbox id="security-identity-groupbox" flex="1"> <caption id="security-identity" label="&securityView.identity.header;"/> - <grid flex="1"> + <grid id="security-identity-grid" flex="1"> <columns> <column/> <column flex="1"/> </columns> - <rows> - <row><!-- Domain --> + <rows id="security-identity-rows"> + <!-- Domain --> + <row id="security-identity-domain-row"> <label id="security-identity-domain-label" class="fieldLabel" value="&securityView.identity.domain;" control="security-identity-domain-value"/> <textbox id="security-identity-domain-value" class="fieldValue" readonly="true"/> </row> - <row><!-- Owner --> + <!-- Owner --> + <row id="security-identity-owner-row"> <label id="security-identity-owner-label" class="fieldLabel" value="&securityView.identity.owner;" control="security-identity-owner-value"/> <textbox id="security-identity-owner-value" class="fieldValue" readonly="true"/> </row> - <row><!-- Verifier --> + <!-- Verifier --> + <row id="security-identity-verifier-row"> <label id="security-identity-verifier-label" class="fieldLabel" value="&securityView.identity.verifier;" control="security-identity-verifier-value"/> <textbox id="security-identity-verifier-value" class="fieldValue" readonly="true" /> </row> </rows> </grid> <spacer flex="1"/> - <hbox pack="end"><!-- Cert button --> + <!-- Cert button --> + <hbox id="security-view-cert-box" pack="end"> <button id="security-view-cert" label="&securityView.certView;" accesskey="&securityView.accesskey;" oncommand="security.viewCert();"/> </hbox> </groupbox> <!-- Privacy & History section --> <groupbox id="security-privacy-groupbox" flex="1"> <caption id="security-privacy" label="&securityView.privacy.header;" /> - <grid> + <grid id="security-privacy-grid"> <columns> <column flex="1"/> <column flex="1"/> </columns> - <rows> - <row><!-- History --> + <rows id="security-privacy-rows"> + <!-- History --> + <row id="security-privacy-history-row"> <label id="security-privacy-history-label" control="security-privacy-history-value" class="fieldLabel">&securityView.privacy.history;</label> <textbox id="security-privacy-history-value" class="fieldValue" value="&securityView.unknown;" readonly="true"/> </row> - <row><!-- Cookies --> + <!-- Cookies --> + <row id="security-privacy-cookies-row"> <label id="security-privacy-cookies-label" control="security-privacy-cookies-value" class="fieldLabel">&securityView.privacy.cookies;</label> - <hbox align="center"> + <hbox id="security-privacy-cookies-box" align="center"> <textbox id="security-privacy-cookies-value" class="fieldValue" value="&securityView.unknown;" flex="1" readonly="true"/> <button id="security-view-cookies" label="&securityView.privacy.viewCookies;" accesskey="&securityView.privacy.viewCookies.accessKey;" oncommand="security.viewCookies();"/> </hbox> </row> - <row><!-- Passwords --> + <!-- Passwords --> + <row id="security-privacy-passwords-row"> <label id="security-privacy-passwords-label" control="security-privacy-passwords-value" class="fieldLabel">&securityView.privacy.passwords;</label> - <hbox align="center"> + <hbox id="security-privacy-passwords-box" align="center"> <textbox id="security-privacy-passwords-value" class="fieldValue" value="&securityView.unknown;" flex="1" readonly="true"/> <button id="security-view-password" label="&securityView.privacy.viewPasswords;" accesskey="&securityView.privacy.viewPasswords.accessKey;" @@ -497,17 +505,17 @@ </row> </rows> </grid> </groupbox> <!-- Technical Details section --> <groupbox id="security-technical-groupbox" flex="1"> <caption id="security-technical" label="&securityView.technical.header;" /> - <vbox flex="1"> + <vbox id="security-technical-box" flex="1"> <label id="security-technical-shortform" class="fieldValue"/> <description id="security-technical-longform1" class="fieldLabel"/> <description id="security-technical-longform2" class="fieldLabel"/> </vbox> </groupbox> </vbox> <!-- Others added by overlay --> </deck>
--- a/browser/base/content/socialchat.xml +++ b/browser/base/content/socialchat.xml @@ -2,22 +2,20 @@ <bindings id="socialChatBindings" xmlns="http://www.mozilla.org/xbl" xmlns:xbl="http://www.mozilla.org/xbl" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <binding id="chatbox"> <content orient="vertical" mousethrough="never"> - <xul:hbox class="chat-titlebar" xbl:inherits="minimized"> + <xul:hbox class="chat-titlebar" xbl:inherits="minimized,selected" + onclick="document.getBindingParent(this).toggle();"> <xul:image class="chat-status-icon" xbl:inherits="src=image"/> <xul:label class="chat-title" flex="1" xbl:inherits="value=label,crop"/> - <xul:toolbarbutton class="chat-toggle-button chat-toolbarbutton" - xbl:inherits="minimized" - oncommand="document.getBindingParent(this).toggle();"/> <xul:toolbarbutton class="chat-close-button chat-toolbarbutton" oncommand="document.getBindingParent(this).close();"/> </xul:hbox> <xul:iframe anonid="iframe" class="chat-frame" flex="1" xbl:inherits="src,origin,collapsed=minimized" type="content"/> </content> <implementation implements="nsIDOMEventListener"> @@ -127,17 +125,29 @@ </field> <property name="emptyWidth"> <getter> return document.getAnonymousElementByAttribute(this, "anonid", "spacer").boxObject.width; </getter> </property> - <field name="selectedChat"/> + <property name="selectedChat"> + <getter><![CDATA[ + return this._selectedChat; + ]]></getter> + <setter><![CDATA[ + if (this._selectedChat) + this._selectedChat.removeAttribute("selected"); + this._selectedChat = val; + if (val) { + this._selectedChat.setAttribute("selected", "true"); + } + ]]></setter> + </property> <field name="menuitemMap">new WeakMap()</field> <field name="chatboxForURL">new Map();</field> <property name="firstCollapsedChild"> <getter><![CDATA[ let child = this.lastChild; while (child && !child.collapsed) {
--- a/browser/base/content/sync/aboutSyncTabs.js +++ b/browser/base/content/sync/aboutSyncTabs.js @@ -52,18 +52,18 @@ let RemoteTabViewer = { let val = event.target.value.toLowerCase(); let numTabs = this._tabsList.getRowCount(); let clientTabs = 0; let currentClient = null; for (let i = 0;i < numTabs;i++) { let item = this._tabsList.getItemAtIndex(i); let hide = false; if (item.getAttribute("type") == "tab") { - if (item.getAttribute("url").toLowerCase().indexOf(val) == -1 && - item.getAttribute("title").toLowerCase().indexOf(val) == -1) + if (!item.getAttribute("url").toLowerCase().contains(val) && + !item.getAttribute("title").toLowerCase().contains(val)) hide = true; else clientTabs++; } else if (item.getAttribute("type") == "client") { if (currentClient) { if (clientTabs == 0) currentClient.hidden = true;
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -790,17 +790,17 @@ newTitle += sep; } newTitle += modifier; // If location bar is hidden and the URL type supports a host, // add the scheme and host to the title to prevent spoofing. // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239 try { - if (docElement.getAttribute("chromehidden").indexOf("location") != -1) { + if (docElement.getAttribute("chromehidden").contains("location")) { var uri = this.mURIFixup.createExposableURI( aBrowser.currentURI); if (uri.scheme == "about") newTitle = uri.spec + sep + newTitle; else newTitle = uri.prePath + sep + newTitle; } } catch (e) {} @@ -3745,17 +3745,17 @@ } else { // Pass true to disallow dropping javascript: or data: urls let url; try { url = browserDragAndDrop.drop(event, { }, true); } catch (ex) {} // valid urls don't contain spaces ' '; if we have a space it isn't a valid url. - if (!url || url.indexOf(" ") != -1) + if (!url || url.contains(" ")) return; let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); if (event.shiftKey) bgLoad = !bgLoad; let tab = this._getDragTargetTab(event);
--- a/browser/base/content/test/browser_bug555224.js +++ b/browser/base/content/test/browser_bug555224.js @@ -14,17 +14,17 @@ function afterZoomAndLoad(aCallback, aTa executeSoon(aCallback); }, true); let oldAPTS = FullZoom._applyPrefToSetting; FullZoom._applyPrefToSetting = function(value, browser) { if (!value) value = undefined; oldAPTS.call(FullZoom, value, browser); // Don't reset _applyPrefToSetting until we've seen the about:blank load(s) - if (browser && (browser.currentURI.spec.indexOf("http:") != -1)) { + if (browser && browser.currentURI.spec.startsWith("http:")) { FullZoom._applyPrefToSetting = oldAPTS; didZoom = true; } if (didLoad && didZoom) executeSoon(aCallback); }; }
--- a/browser/base/content/test/browser_homeDrop.js +++ b/browser/base/content/test/browser_homeDrop.js @@ -23,17 +23,17 @@ function test() { ok(true, "dialog appeared in response to home button drop"); domwindow.document.documentElement.cancelDialog(); Services.wm.removeListener(dialogListener); // Now trigger the invalid URI test executeSoon(function () { let consoleListener = { observe: function (m) { - if (m.message.indexOf("NS_ERROR_DOM_BAD_URI") > -1) { + if (m.message.contains("NS_ERROR_DOM_BAD_URI")) { ok(true, "drop was blocked"); executeSoon(finish); } } } Services.console.registerListener(consoleListener); registerCleanupFunction(function () { Services.console.unregisterListener(consoleListener);
--- a/browser/base/content/test/browser_popupNotification.js +++ b/browser/base/content/test/browser_popupNotification.js @@ -751,17 +751,17 @@ function triggerSecondaryCommand(popup, for (let i = 0; i <= index; i++) EventUtils.synthesizeKey("VK_DOWN", {}); // Activate EventUtils.synthesizeKey("VK_ENTER", {}); }, false); // One down event to open the popup - EventUtils.synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) }); + EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.contains("Mac") }); } function loadURI(uri, callback) { if (callback) { gBrowser.addEventListener("load", function() { // Ignore the about:blank load if (gBrowser.currentURI.spec != uri) return;
--- a/browser/base/content/test/browser_social_chatwindow.js +++ b/browser/base/content/test/browser_social_chatwindow.js @@ -86,27 +86,29 @@ var tests = { } } let num = numToOpen; while (num-- > 0) { port.postMessage({topic: "test-chatbox-open", data: { id: num }}); } }, testWorkerChatWindow: function(next) { + const chatUrl = "https://example.com/browser/browser/base/content/test/social_chat.html"; let port = Social.provider.port; ok(port, "provider has a port"); port.onmessage = function (e) { let topic = e.data.topic; switch (topic) { case "got-chatbox-message": ok(true, "got a chat window opened"); let chats = document.getElementById("pinnedchats"); while (chats.selectedChat) { chats.selectedChat.close(); } ok(!chats.selectedChat, "chats are all closed"); + ensureSocialUrlNotRemembered(chatUrl); next(); break; } } - port.postMessage({topic: "test-worker-chat" }); + port.postMessage({topic: "test-worker-chat", data: chatUrl}); } }
--- a/browser/base/content/test/browser_social_shareButton.js +++ b/browser/base/content/test/browser_social_shareButton.js @@ -138,25 +138,25 @@ function checkShortcutWorked(keyTarget) function checkOKButton() { let okButton = document.getElementById("editSharePopupOkButton"); let undoButton = document.getElementById("editSharePopupUndoButton"); is(document.activeElement, okButton, "ok button should be focused by default"); // This rest of particular test doesn't really apply on Mac, since buttons // aren't focusable by default. - if (navigator.platform.indexOf("Mac") != -1) { + if (navigator.platform.contains("Mac")) { executeSoon(testCloseBySpace); return; } let displayName = document.getElementById("socialUserDisplayName"); // Linux has the buttons in the [unshare] [ok] order, so displayName will come first. - if (navigator.platform.indexOf("Linux") != -1) { + if (navigator.platform.contains("Linux")) { checkNextInTabOrder(displayName, function () { checkNextInTabOrder(undoButton, function () { checkNextInTabOrder(okButton, testCloseBySpace); }); }); } else { checkNextInTabOrder(undoButton, function () { checkNextInTabOrder(displayName, function () {
--- a/browser/base/content/test/social_worker.js +++ b/browser/base/content/test/social_worker.js @@ -59,17 +59,17 @@ onconnect = function(e) { break; case "flyout-message": testPort.postMessage({topic:"got-flyout-message", result: event.data.result}); break; case "flyout-visibility": testPort.postMessage({topic:"got-flyout-visibility", result: event.data.result}); break; case "test-worker-chat": - apiPort.postMessage({topic: "social.request-chat", data: "https://example.com/browser/browser/base/content/test/social_chat.html" }); + apiPort.postMessage({topic: "social.request-chat", data: event.data.data }); break; case "social.initialize": // This is the workerAPI port, respond and set up a notification icon. apiPort = port; port.postMessage({topic: "social.initialize-response"}); let profile = { portrait: "https://example.com/portrait.jpg", userName: "trickster",
--- a/browser/base/content/test/test_offline_gzip.html +++ b/browser/base/content/test/test_offline_gzip.html @@ -71,17 +71,17 @@ function handleMessageEvents(event) { // Sometimes document.body may not exist, and trying to access // it will throw an exception, so handle this case. try { var bodyInnerHTML = frames.testFrame.document.body.innerHTML; } catch (e) { var bodyInnerHTML = ""; } - if (cacheCount == 2 || (bodyInnerHTML.indexOf("error") != -1)) { + if (cacheCount == 2 || bodyInnerHTML.contains("error")) { clearInterval(intervalID); is(cacheCount, 2, "frame not reloaded successfully"); if (cacheCount != 2) { finishTest(); } } }, 100); break;
--- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -461,17 +461,17 @@ var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd); // If the selection doesn't start at the beginning or doesn't span the full domain or // the URL bar is modified, nothing else to do here. if (this.selectionStart > 0 || this.valueIsTyped) return selectedVal; // The selection doesn't span the full domain if it doesn't contain a slash and is // followed by some character other than a slash. - if (selectedVal.indexOf("/") == -1) { + if (!selectedVal.contains("/")) { let remainder = inputVal.replace(selectedVal, ""); if (remainder != "" && remainder[0] != "/") return selectedVal; } let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup); let uri;
--- a/browser/components/thumbnails/PageThumbs.jsm +++ b/browser/components/thumbnails/PageThumbs.jsm @@ -1,15 +1,15 @@ /* 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"; -let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage", "PageThumbsCache"]; +let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage"]; const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version"; const LATEST_STORAGE_VERSION = 2; @@ -528,68 +528,8 @@ let PageThumbsHistoryObserver = { onEndUpdateBatch: function () {}, onVisit: function () {}, onBeforeDeleteURI: function () {}, onPageChanged: function () {}, onDeleteVisits: function () {}, QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver]) }; - -/** - * A singleton handling the storage of page thumbnails. - */ -let PageThumbsCache = { - /** - * Calls the given callback with a cache entry opened for reading. - * @param aKey The key identifying the desired cache entry. - * @param aCallback The callback that is called when the cache entry is ready. - */ - getReadEntry: function Cache_getReadEntry(aKey, aCallback) { - // Try to open the desired cache entry. - this._openCacheEntry(aKey, Ci.nsICache.ACCESS_READ, aCallback); - }, - - /** - * Opens the cache entry identified by the given key. - * @param aKey The key identifying the desired cache entry. - * @param aAccess The desired access mode (see nsICache.ACCESS_* constants). - * @param aCallback The function to be called when the cache entry was opened. - */ - _openCacheEntry: function Cache_openCacheEntry(aKey, aAccess, aCallback) { - function onCacheEntryAvailable(aEntry, aAccessGranted, aStatus) { - let validAccess = aAccess == aAccessGranted; - let validStatus = Components.isSuccessCode(aStatus); - - // Check if a valid entry was passed and if the - // access we requested was actually granted. - if (aEntry && !(validAccess && validStatus)) { - aEntry.close(); - aEntry = null; - } - - aCallback(aEntry); - } - - let listener = this._createCacheListener(onCacheEntryAvailable); - this._cacheSession.asyncOpenCacheEntry(aKey, aAccess, listener); - }, - - /** - * Returns a cache listener implementing the nsICacheListener interface. - * @param aCallback The callback to be called when the cache entry is available. - * @return The new cache listener. - */ - _createCacheListener: function Cache_createCacheListener(aCallback) { - return { - onCacheEntryAvailable: aCallback, - QueryInterface: XPCOMUtils.generateQI([Ci.nsICacheListener]) - }; - } -}; - -/** - * Define a lazy getter for the cache session. - */ -XPCOMUtils.defineLazyGetter(PageThumbsCache, "_cacheSession", function () { - return Services.cache.createSession(PageThumbs.scheme, - Ci.nsICache.STORE_ON_DISK, true); -});
--- a/browser/components/thumbnails/PageThumbsProtocol.js +++ b/browser/components/thumbnails/PageThumbsProtocol.js @@ -69,331 +69,33 @@ Protocol.prototype = { /** * Constructs a new channel from the given URI for this protocol handler. * @param aURI The URI for which to construct a channel. * @return The newly created channel. */ newChannel: function Proto_newChannel(aURI) { let {url} = parseURI(aURI); let file = PageThumbsStorage.getFileForURL(url); - - if (file.exists()) { - let fileuri = Services.io.newFileURI(file); - return Services.io.newChannelFromURI(fileuri); - } - - return new Channel(aURI); + let fileuri = Services.io.newFileURI(file); + return Services.io.newChannelFromURI(fileuri); }, /** * Decides whether to allow a blacklisted port. * @return Always false, we'll never allow ports. */ allowPort: function () false, classID: Components.ID("{5a4ae9b5-f475-48ae-9dce-0b4c1d347884}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]) }; let NSGetFactory = XPCOMUtils.generateNSGetFactory([Protocol]); /** - * A channel implementation responsible for delivering cached thumbnails. - */ -function Channel(aURI) { - this._uri = aURI; - - // nsIChannel - this.originalURI = aURI; - - // nsIHttpChannel - this._responseHeaders = {"content-type": PageThumbs.contentType}; -} - -Channel.prototype = { - _uri: null, - _referrer: null, - _canceled: false, - _status: Cr.NS_OK, - _isPending: false, - _wasOpened: false, - _responseText: "OK", - _responseStatus: 200, - _responseHeaders: null, - _requestMethod: "GET", - _requestStarted: false, - _allowPipelining: true, - _requestSucceeded: true, - - /* :::::::: nsIChannel ::::::::::::::: */ - - get URI() this._uri, - owner: null, - notificationCallbacks: null, - get securityInfo() null, - - contentType: PageThumbs.contentType, - contentCharset: null, - contentLength: -1, - - get contentDisposition() { - throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE); - }, - - get contentDispositionFilename() { - throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE); - }, - - get contentDispositionHeader() { - throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE); - }, - - open: function Channel_open() { - throw (Components.returnCode = Cr.NS_ERROR_NOT_IMPLEMENTED); - }, - - asyncOpen: function Channel_asyncOpen(aListener, aContext) { - if (this._isPending) - throw (Components.returnCode = Cr.NS_ERROR_IN_PROGRESS); - - if (this._wasOpened) - throw (Components.returnCode = Cr.NS_ERROR_ALREADY_OPENED); - - if (this._canceled) - return (Components.returnCode = this._status); - - this._isPending = true; - this._wasOpened = true; - - this._listener = aListener; - this._context = aContext; - - if (this.loadGroup) - this.loadGroup.addRequest(this, null); - - if (this._canceled) - return; - - let {url} = parseURI(this._uri); - if (!url) { - this._serveThumbnailNotFound(); - return; - } - - PageThumbsCache.getReadEntry(url, function (aEntry) { - let inputStream = aEntry && aEntry.openInputStream(0); - if (!inputStream || !inputStream.available()) { - if (aEntry) - aEntry.close(); - this._serveThumbnailNotFound(); - return; - } - - this._entry = aEntry; - this._pump = Cc["@mozilla.org/network/input-stream-pump;1"]. - createInstance(Ci.nsIInputStreamPump); - - this._pump.init(inputStream, -1, -1, 0, 0, true); - this._pump.asyncRead(this, null); - - this._trackThumbnailHitOrMiss(true); - }.bind(this)); - }, - - /** - * Serves a "404 Not Found" if we didn't find the requested thumbnail. - */ - _serveThumbnailNotFound: function Channel_serveThumbnailNotFound() { - this._responseStatus = 404; - this._responseText = "Not Found"; - this._requestSucceeded = false; - - this.onStartRequest(this, null); - this.onStopRequest(this, null, Cr.NS_OK); - - this._trackThumbnailHitOrMiss(false); - }, - - /** - * Implements telemetry tracking for thumbnail cache hits and misses. - * @param aFound Whether the thumbnail was found. - */ - _trackThumbnailHitOrMiss: function Channel_trackThumbnailHitOrMiss(aFound) { - Services.telemetry.getHistogramById("FX_THUMBNAILS_HIT_OR_MISS") - .add(aFound); - }, - - /* :::::::: nsIStreamListener ::::::::::::::: */ - - onStartRequest: function Channel_onStartRequest(aRequest, aContext) { - if (!this.canceled && Components.isSuccessCode(this._status)) - this._status = aRequest.status; - - this._requestStarted = true; - this._listener.onStartRequest(this, this._context); - }, - - onDataAvailable: function Channel_onDataAvailable(aRequest, aContext, - aInStream, aOffset, aCount) { - this._listener.onDataAvailable(this, this._context, aInStream, aOffset, aCount); - }, - - onStopRequest: function Channel_onStopRequest(aRequest, aContext, aStatus) { - this._isPending = false; - this._status = aStatus; - - this._listener.onStopRequest(this, this._context, aStatus); - this._listener = null; - this._context = null; - - if (this._entry) - this._entry.close(); - - if (this.loadGroup) - this.loadGroup.removeRequest(this, null, aStatus); - }, - - /* :::::::: nsIRequest ::::::::::::::: */ - - get status() this._status, - get name() this._uri.spec, - isPending: function Channel_isPending() this._isPending, - - loadFlags: Ci.nsIRequest.LOAD_NORMAL, - loadGroup: null, - - cancel: function Channel_cancel(aStatus) { - if (this._canceled) - return; - - this._canceled = true; - this._status = aStatus; - - if (this._pump) - this._pump.cancel(aStatus); - }, - - suspend: function Channel_suspend() { - if (this._pump) - this._pump.suspend(); - }, - - resume: function Channel_resume() { - if (this._pump) - this._pump.resume(); - }, - - /* :::::::: nsIHttpChannel ::::::::::::::: */ - - get referrer() this._referrer, - - set referrer(aReferrer) { - if (this._wasOpened) - throw (Components.returnCode = Cr.NS_ERROR_IN_PROGRESS); - - this._referrer = aReferrer; - }, - - get requestMethod() this._requestMethod, - - set requestMethod(aMethod) { - if (this._wasOpened) - throw (Components.returnCode = Cr.NS_ERROR_IN_PROGRESS); - - this._requestMethod = aMethod.toUpperCase(); - }, - - get allowPipelining() this._allowPipelining, - - set allowPipelining(aAllow) { - if (this._wasOpened) - throw (Components.returnCode = Cr.NS_ERROR_FAILURE); - - this._allowPipelining = aAllow; - }, - - redirectionLimit: 10, - - get responseStatus() { - if (this._requestStarted) - throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE); - - return this._responseStatus; - }, - - get responseStatusText() { - if (this._requestStarted) - throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE); - - return this._responseText; - }, - - get requestSucceeded() { - if (this._requestStarted) - throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE); - - return this._requestSucceeded; - }, - - isNoCacheResponse: function Channel_isNoCacheResponse() false, - isNoStoreResponse: function Channel_isNoStoreResponse() false, - - getRequestHeader: function Channel_getRequestHeader() { - throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE); - }, - - setRequestHeader: function Channel_setRequestHeader() { - if (this._wasOpened) - throw (Components.returnCode = Cr.NS_ERROR_IN_PROGRESS); - }, - - visitRequestHeaders: function Channel_visitRequestHeaders() {}, - - getResponseHeader: function Channel_getResponseHeader(aHeader) { - let name = aHeader.toLowerCase(); - if (name in this._responseHeaders) - return this._responseHeaders[name]; - - throw (Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE); - }, - - setResponseHeader: function Channel_setResponseHeader(aHeader, aValue, aMerge) { - let name = aHeader.toLowerCase(); - if (!aValue && !aMerge) - delete this._responseHeaders[name]; - else - this._responseHeaders[name] = aValue; - }, - - visitResponseHeaders: function Channel_visitResponseHeaders(aVisitor) { - for (let name in this._responseHeaders) { - let value = this._responseHeaders[name]; - - try { - aVisitor.visitHeader(name, value); - } catch (e) { - // The visitor can throw to stop the iteration. - return; - } - } - }, - - /* :::::::: nsIHttpChannelInternal ::::::::::::::: */ - - documentURI: null, - get canceled() this._canceled, - allowSpdy: false, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, - Ci.nsIHttpChannel, - Ci.nsIHttpChannelInternal, - Ci.nsIRequest]) -} - -/** * Parses a given URI and extracts all parameters relevant to this protocol. * @param aURI The URI to parse. * @return The parsed parameters. */ function parseURI(aURI) { let {scheme, staticHost} = PageThumbs; let re = new RegExp("^" + scheme + "://" + staticHost + ".*?\\?"); let query = aURI.spec.replace(re, "");
--- a/browser/devtools/commandline/CmdScreenshot.jsm +++ b/browser/devtools/commandline/CmdScreenshot.jsm @@ -36,17 +36,17 @@ gcli.addCommand({ }, { name: "fullpage", type: "boolean", description: gcli.lookup("screenshotFullPageDesc"), manual: gcli.lookup("screenshotFullPageManual") }, { - name: "node", + name: "selector", type: "node", defaultValue: null, description: gcli.lookup("inspectNodeDesc"), manual: gcli.lookup("inspectNodeManual") } ], exec: function Command_screenshot(args, context) { var document = context.environment.contentDocument; @@ -54,17 +54,17 @@ gcli.addCommand({ var promise = context.createPromise(); document.defaultView.setTimeout(function Command_screenshotDelay() { let reply = this.grabScreen(document, args.filename); promise.resolve(reply); }.bind(this), args.delay * 1000); return promise; } else { - return this.grabScreen(document, args.filename, args.fullpage, args.node); + return this.grabScreen(document, args.filename, args.fullpage, args.selector); } }, grabScreen: function Command_screenshotGrabScreen(document, filename, fullpage, node) { let window = document.defaultView; let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); let left = 0; let top = 0;
--- a/browser/devtools/commandline/gcli.jsm +++ b/browser/devtools/commandline/gcli.jsm @@ -1702,17 +1702,17 @@ exports.ArrayArgument = ArrayArgument; define('gcli/types/selection', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell'], function(require, exports, module) { var l10n = require('gcli/l10n'); var types = require('gcli/types'); var Type = require('gcli/types').Type; var Status = require('gcli/types').Status; var Conversion = require('gcli/types').Conversion; -var Speller = require('gcli/types/spell').Speller; +var spell = require('gcli/types/spell'); /** * Registration and de-registration. */ exports.startup = function() { types.registerType(SelectionType); }; @@ -1883,23 +1883,24 @@ SelectionType.prototype._findPredictions 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; + if (predictions.length === 0) { + var names = []; + lookup.forEach(function(opt) { + if (!opt.value.hidden) { + names.push(opt.name); + } }); - speller.train(names); - var corrected = speller.correct(match); + var corrected = spell.correct(match, names); if (corrected) { lookup.forEach(function(opt) { if (opt.name === corrected) { predictions.push(opt); } }, this); } } @@ -2000,163 +2001,126 @@ SelectionType.prototype._findValue = fun SelectionType.prototype.name = 'selection'; exports.SelectionType = SelectionType; }); /* - * Copyright (c) 2009 Panagiotis Astithas - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * Copyright 2012, Mozilla Foundation and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ define('gcli/types/spell', ['require', 'exports', 'module' ], function(require, exports, module) { - -/** - * A spell-checker based on the statistical algorithm described by Peter Norvig - * in http://norvig.com/spell-correct.html, and converted to JavaScript by Past - * http://past.github.com/speller/ - * - * Usage requires a two-step process: - * 1) call speller.train() one or more times with a large text to train the - * language model - * 2) call speller.correct(word) to retrieve the correction for the specified - * word - */ -function Speller() { - // A map of words to the count of times they were encountered during training. - this._nWords = {}; -} - -Speller.letters = "abcdefghijklmnopqrstuvwxyz".split(""); - -/** - * A function that trains the language model with the words in the supplied - * text. Multiple invocation of this function can extend the training of the - * model. - */ -Speller.prototype.train = function(words) { - words.forEach(function(word) { - word = word.toLowerCase(); - this._nWords[word] = this._nWords.hasOwnProperty(word) ? - this._nWords[word] + 1 : - 1; - }, this); +/* + * A spell-checker based on Damerau-Levenshtein distance. + */ + +var INSERTION_COST = 1; +var DELETION_COST = 1; +var SWAP_COST = 1; +var SUBSTITUTION_COST = 2; +var MAX_EDIT_DISTANCE = 4; + +/** + * Compute Damerau-Levenshtein Distance + * @see http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance + */ +function damerauLevenshteinDistance(wordi, wordj) { + var N = wordi.length; + var M = wordj.length; + + // We only need to store three rows of our dynamic programming matrix. + // (Without swap, it would have been two.) + var row0 = new Array(N+1); + var row1 = new Array(N+1); + var row2 = new Array(N+1); + var tmp; + + var i, j; + + // The distance between the empty string and a string of size i is the cost + // of i insertions. + for (i = 0; i <= N; i++) { + row1[i] = i * INSERTION_COST; + } + + // Row-by-row, we're computing the edit distance between substrings wordi[0..i] + // and wordj[0..j]. + for (j = 1; j <= M; j++) + { + // Edit distance between wordi[0..0] and wordj[0..j] is the cost of j + // insertions. + row0[0] = j * INSERTION_COST; + + for (i = 1; i <= N; i++) + { + // Handle deletion, insertion and substitution: we can reach each cell + // from three other cells corresponding to those three operations. We + // want the minimum cost. + row0[i] = Math.min( + row0[i-1] + DELETION_COST, + row1[i] + INSERTION_COST, + row1[i-1] + (wordi[i-1] === wordj[j-1] ? 0 : SUBSTITUTION_COST)); + // We handle swap too, eg. distance between help and hlep should be 1. If + // we find such a swap, there's a chance to update row0[1] to be lower. + if (i > 1 && j > 1 && wordi[i-1] === wordj[j-2] && wordj[j-1] === wordi[i-2]) { + row0[i] = Math.min(row0[i], row2[i-2] + SWAP_COST); + } + } + + tmp = row2; + row2 = row1; + row1 = row0; + row0 = tmp; + } + + return row1[N]; }; /** * A function that returns the correction for the specified word. */ -Speller.prototype.correct = function(word) { - if (this._nWords.hasOwnProperty(word)) { - return word; - } - - var candidates = {}; - var list = this._edits(word); - list.forEach(function(edit) { - if (this._nWords.hasOwnProperty(edit)) { - candidates[this._nWords[edit]] = edit; - } - }, this); - - if (this._countKeys(candidates) > 0) { - return candidates[this._max(candidates)]; - } - - list.forEach(function(edit) { - this._edits(edit).forEach(function(w) { - if (this._nWords.hasOwnProperty(w)) { - candidates[this._nWords[w]] = w; - } - }, this); - }, this); - - return this._countKeys(candidates) > 0 ? - candidates[this._max(candidates)] : - null; -}; - -/** - * A helper function that counts the keys in the supplied object. - */ -Speller.prototype._countKeys = function(object) { - // return Object.keys(object).length; ? - var count = 0; - for (var attr in object) { - if (object.hasOwnProperty(attr)) { - count++; - } - } - return count; -}; - -/** - * A helper function that returns the word with the most occurrences in the - * language model, among the supplied candidates. - * @param candidates - */ -Speller.prototype._max = function(candidates) { - var arr = []; - for (var candidate in candidates) { - if (candidates.hasOwnProperty(candidate)) { - arr.push(candidate); - } - } - return Math.max.apply(null, arr); -}; - -/** - * A function that returns the set of possible corrections of the specified - * word. The edits can be deletions, insertions, alterations or transpositions. - */ -Speller.prototype._edits = function(word) { - var results = []; - - // Deletion - for (var i = 0; i < word.length; i++) { - results.push(word.slice(0, i) + word.slice(i + 1)); - } - - // Transposition - for (i = 0; i < word.length - 1; i++) { - results.push(word.slice(0, i) + word.slice(i + 1, i + 2) - + word.slice(i, i + 1) + word.slice(i + 2)); - } - - // Alteration - for (i = 0; i < word.length; i++) { - Speller.letters.forEach(function(l) { - results.push(word.slice(0, i) + l + word.slice(i + 1)); - }, this); - } - - // Insertion - for (i = 0; i <= word.length; i++) { - Speller.letters.forEach(function(l) { - results.push(word.slice(0, i) + l + word.slice(i)); - }, this); - } - - return results; -}; - -exports.Speller = Speller; +exports.correct = function(word, names) { + var distance = {}; + var sorted_candidates; + + names.forEach(function(candidate) { + distance[candidate] = damerauLevenshteinDistance(word, candidate); + }); + + sorted_candidates = names.sort(function(worda, wordb) { + if (distance[worda] !== distance[wordb]) { + return distance[worda] - distance[wordb]; + } else { + // if the score is the same, always return the first string + // in the lexicographical order + return worda < wordb; + } + }); + + if (distance[sorted_candidates[0]] <= MAX_EDIT_DISTANCE) { + return sorted_candidates[0]; + } else { + return undefined; + } +}; }); /* * Copyright 2012, Mozilla Foundation and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -4063,17 +4027,19 @@ if (typeof document !== 'undefined') { */ exports._empty = []; /** * Setter for the document that contains the nodes we're matching */ exports.setDocument = function(document) { doc = document; - exports._empty = doc.querySelectorAll('x>:root'); + if (doc != null) { + exports._empty = doc.querySelectorAll('x>:root'); + } }; /** * Undo the effects of setDocument() */ exports.unsetDocument = function() { doc = undefined; }; @@ -5122,17 +5088,17 @@ define('gcli/ui/domtemplate', ['require' define('gcli/cli', ['require', 'exports', 'module' , 'gcli/util', 'gcli/ui/view', 'gcli/l10n', 'gcli/canon', 'gcli/promise', 'gcli/types', 'gcli/types/basic', 'gcli/argument'], function(require, exports, module) { var util = require('gcli/util'); var view = require('gcli/ui/view'); var l10n = require('gcli/l10n'); var canon = require('gcli/canon'); -var Promise = require('gcli/promise').Promise; +var Q = require('gcli/promise'); var Status = require('gcli/types').Status; var Conversion = require('gcli/types').Conversion; var ArrayType = require('gcli/types/basic').ArrayType; var StringType = require('gcli/types/basic').StringType; var BooleanType = require('gcli/types/basic').BooleanType; var NumberType = require('gcli/types/basic').NumberType; @@ -5185,18 +5151,16 @@ function Assignment(param, paramIndex) { this.conversion = undefined; // The index of this parameter in the parent Requisition. paramIndex === -1 // is the command assignment although this should not be relied upon, it is // better to test param instanceof CommandAssignment this.paramIndex = paramIndex; this.onAssignmentChange = util.createEvent('Assignment.onAssignmentChange'); - - this.setBlank(); } /** * Easy accessor for conversion.arg. * This is a read-only property because writes to arg should be done through * the 'conversion' property. */ Object.defineProperty(Assignment.prototype, 'arg', { @@ -5271,49 +5235,16 @@ Assignment.prototype.getPredictionAt = f * to take this into account. */ Assignment.prototype.isInName = function() { return this.conversion.arg.type === 'NamedArgument' && this.conversion.arg.prefix.slice(-1) !== ' '; }; /** - * Report on the status of the last parse() conversion. - * We force mutations to happen through this method rather than have - * setValue and setArgument functions to help maintain integrity when we - * have ArrayArguments and don't want to get confused. This way assignments - * are just containers for a conversion rather than things that store - * a connection between an arg/value. - * @see types.Conversion - */ -Assignment.prototype.setConversion = function(conversion) { - var oldConversion = this.conversion; - - this.conversion = conversion; - this.conversion.assign(this); - - if (this.conversion.equals(oldConversion)) { - return; - } - - this.onAssignmentChange({ - assignment: this, - conversion: this.conversion, - oldConversion: oldConversion - }); -}; - -/** - * Setup an empty value for the conversion by parsing an empty argument. - */ -Assignment.prototype.setBlank = function() { - this.setConversion(this.param.type.getBlank()); -}; - -/** * Make sure that there is some content for this argument by using an * Argument of '' if needed. */ Assignment.prototype.ensureVisibleArgument = function() { // It isn't clear if we should be sending events from this method. // It should only be called when structural changes are happening in which // case we're going to ignore the event anyway. But on the other hand // perhaps this function shouldn't need to know how it is used, and should @@ -5449,18 +5380,16 @@ function CommandAssignment() { value.description : 'The command to execute'; }, enumerable: true }); this.param = new canon.Parameter(commandParamMetadata); this.paramIndex = -1; this.onAssignmentChange = util.createEvent('CommandAssignment.onAssignmentChange'); - - this.setBlank(); } CommandAssignment.prototype = Object.create(Assignment.prototype); CommandAssignment.prototype.getStatus = function(arg) { return Status.combine( Assignment.prototype.getStatus.call(this, arg), this.conversion.value && this.conversion.value.exec ? @@ -5535,16 +5464,17 @@ function Requisition(environment, doc) { catch (ex) { // Ignore } } // The command that we are about to execute. // @see setCommandConversion() this.commandAssignment = new CommandAssignment(); + this._setAssignment(this.commandAssignment, null, true); // The object that stores of Assignment objects that we are filling out. // The Assignment objects are stored under their param.name for named // lookup. Note: We make use of the property of Javascript objects that // they are not just hashmaps, but linked-list hashmaps which iterate in // insertion order. // _assignments excludes the commandAssignment. this._assignments = {}; @@ -5621,16 +5551,17 @@ Requisition.prototype._commandAssignment this._assignments = {}; var command = this.commandAssignment.value; if (command) { for (var i = 0; i < command.params.length; i++) { var param = command.params[i]; var assignment = new Assignment(param, i); + this._setAssignment(assignment, null, true); assignment.onAssignmentChange.add(this._assignmentChanged, this); this._assignments[param.name] = assignment; } } this.assignmentCount = Object.keys(this._assignments).length; }; /** @@ -5743,58 +5674,99 @@ Requisition.prototype.getAssignments = f } Object.keys(this._assignments).forEach(function(name) { assignments.push(this.getAssignment(name)); }, this); return assignments; }; /** - * Alter the given assignment using the given arg. This function is better than - * calling assignment.setConversion(assignment.param.type.parse(arg)) because - * it adjusts the args in this requisition to keep things up to date + * Alter the given assignment using the given arg. + * @param assignment The assignment to alter + * @param arg The new value for the assignment. An instance of Argument, or an + * instance of Conversion, or null to set the blank value. */ Requisition.prototype.setAssignment = function(assignment, arg) { - var originalArgs = assignment.arg.getArgs(); - var conversion = assignment.param.type.parse(arg); - assignment.setConversion(conversion); - - var replacementArgs = arg.getArgs(); - var maxLen = Math.max(originalArgs.length, replacementArgs.length); - for (var i = 0; i < maxLen; i++) { - // If there are no more original args, or if the original arg was blank - // (i.e. not typed by the user), we'll just need to add at the end - if (i >= originalArgs.length || originalArgs[i].type === 'BlankArgument') { - this._args.push(replacementArgs[i]); - continue; - } - - var index = this._args.indexOf(originalArgs[i]); - if (index === -1) { - console.error('Couldn\'t find ', originalArgs[i], ' in ', this._args); - throw new Error('Couldn\'t find ' + originalArgs[i]); - } - - // If there are no more replacement args, we just remove the original args - // Otherwise swap original args and replacements - if (i >= replacementArgs.length) { - this._args.splice(index, 1); - } - else { - this._args[index] = replacementArgs[i]; - } - } + this._setAssignment(assignment, arg, false); +}; + +/** + * Internal function to alter the given assignment using the given arg. + * @param assignment The assignment to alter + * @param arg The new value for the assignment. An instance of Argument, or an + * instance of Conversion, or null to set the blank value. + * @param skipArgUpdate (default=false) Adjusts the args in this requisition to + * keep things up to date. Args should only be skipped when setAssignment is + * being called as part of the update process. + */ +Requisition.prototype._setAssignment = function(assignment, arg, skipArgUpdate) { + if (!skipArgUpdate) { + var originalArgs = assignment.arg.getArgs(); + + // Update the args array + var replacementArgs = arg.getArgs(); + var maxLen = Math.max(originalArgs.length, replacementArgs.length); + for (var i = 0; i < maxLen; i++) { + // If there are no more original args, or if the original arg was blank + // (i.e. not typed by the user), we'll just need to add at the end + if (i >= originalArgs.length || originalArgs[i].type === 'BlankArgument') { + this._args.push(replacementArgs[i]); + continue; + } + + var index = this._args.indexOf(originalArgs[i]); + if (index === -1) { + console.error('Couldn\'t find ', originalArgs[i], ' in ', this._args); + throw new Error('Couldn\'t find ' + originalArgs[i]); + } + + // If there are no more replacement args, we just remove the original args + // Otherwise swap original args and replacements + if (i >= replacementArgs.length) { + this._args.splice(index, 1); + } + else { + this._args[index] = replacementArgs[i]; + } + } + } + + var conversion; + if (arg == null) { + conversion = assignment.param.type.getBlank(); + } + else if (typeof arg.getStatus === 'function') { + conversion = arg; + } + else { + conversion = assignment.param.type.parse(arg); + } + + var oldConversion = assignment.conversion; + + assignment.conversion = conversion; + assignment.conversion.assign(assignment); + + if (assignment.conversion.equals(oldConversion)) { + return; + } + + assignment.onAssignmentChange({ + assignment: assignment, + conversion: assignment.conversion, + oldConversion: oldConversion + }); }; /** * Reset all the assignments to their default values */ Requisition.prototype.setBlankArguments = function() { this.getAssignments().forEach(function(assignment) { - assignment.setBlank(); + this._setAssignment(assignment, null, true); }, this); }; /** * Complete the argument at <tt>cursor</tt>. * Basically the same as: * assignment = getAssignmentAt(cursor); * assignment.value = assignment.conversion.predictions[0]; @@ -6143,18 +6115,18 @@ Requisition.prototype.getAssignmentAt = * argument values as passed to command.exec. This method is significantly * faster, and designed for use from keyboard shortcuts. * In addition to these properties, the input parameter can contain a 'hidden' * property which can be set to true to hide the output from the * CommandOutputManager. * @param input (optional) The command to execute. See above. */ Requisition.prototype.exec = function(input) { - var command; - var args; + var command = null; + var args = null; var hidden = false; if (input && input.hidden) { hidden = true; } if (input) { if (typeof input === 'string') { this.update(input); @@ -6195,44 +6167,69 @@ Requisition.prototype.exec = function(in args: args, typed: typed, canonical: this.toCanonicalString(), hidden: hidden }); this.commandOutputManager.onOutput({ output: output }); + var onDone = function(data) { + output.complete(data); + }; + + var onError = function(error) { + console.error(error); + output.error = true; + output.complete(error); + }; + try { var context = exports.createExecutionContext(this); var reply = command.exec(args, context); - if (reply != null && typeof reply.then === 'function') { - reply.then( - function(data) { output.complete(data); }, - function(error) { output.error = true; output.complete(error); }); - - output.promise = reply; - // Add progress to our promise and add a handler for it here - // See bug 659300 - } - else { - output.complete(reply); - } + this._then(reply, onDone, onError); } catch (ex) { - console.error(ex); - output.error = true; - output.complete(ex); + onError(ex); } this.update(''); return output; }; /** + * Different types of promise have different ways of doing 'then'. This is a + * catch-all so we can ignore the differences. It also handles concrete values + * and calls onDone directly if thing is not a promise. + * @param thing The value to test for 'promiseness' + * @param onDone The action to take if thing is resolved + * @param onError The action to take if thing is rejected + */ +Requisition.prototype._then = function(thing, onDone, onError) { + var then = null; + if (thing != null && typeof thing.then === 'function') { + // Old GCLI style / simple promises with a then function + then = thing.then; + } + else if (thing != null && thing.promise != null && + typeof thing.promise.then === 'function') { + // Q / Mozilla add-ons style + then = thing.promise.then; + } + + if (then != null) { + then(onDone, onError); + } + else { + onDone(thing); + } +}; + +/** * Called by the UI when ever the user interacts with a command line input * @param typed The contents of the input field */ Requisition.prototype.update = function(typed) { this._structuralChangeInProgress = true; this._args = this._tokenize(typed); var args = this._args.slice(0); // i.e. clone @@ -6512,26 +6509,26 @@ Requisition.prototype._split = function( // Handle the special case of the user typing { javascript(); } // We use the hidden 'eval' command directly rather than shift()ing one of // the parameters, and parse()ing it. var conversion; if (args[0].type === 'ScriptArgument') { // Special case: if the user enters { console.log('foo'); } then we need to // use the hidden 'eval' command conversion = new Conversion(evalCommand, new ScriptArgument()); - this.commandAssignment.setConversion(conversion); + this._setAssignment(this.commandAssignment, conversion, true); return; } var argsUsed = 1; while (argsUsed <= args.length) { var arg = (argsUsed === 1) ? - args[0] : - new MergedArgument(args, 0, argsUsed); + args[0] : + new MergedArgument(args, 0, argsUsed); conversion = this.commandAssignment.param.type.parse(arg); // We only want to carry on if this command is a parent command, // which means that there is a commandAssignment, but not one with // an exec function. if (!conversion.value || conversion.value.exec) { break; } @@ -6539,17 +6536,17 @@ Requisition.prototype._split = function( // Previously we needed a way to hide commands depending context. // We have not resurrected that feature yet, but if we do we should // insert code here to ignore certain commands depending on the // context/environment argsUsed++; } - this.commandAssignment.setConversion(conversion); + this._setAssignment(this.commandAssignment, conversion, true); for (var i = 0; i < argsUsed; i++) { args.shift(); } // This could probably be re-written to consume args as we go }; @@ -6585,21 +6582,18 @@ Requisition.prototype._assign = function return; } // Special case: if there is only 1 parameter, and that's of type // text, then we put all the params into the first param if (this.assignmentCount === 1) { var assignment = this.getAssignment(0); if (assignment.param.type instanceof StringType) { - var arg = (args.length === 1) ? - args[0] : - new MergedArgument(args); - var conversion = assignment.param.type.parse(arg); - assignment.setConversion(conversion); + var arg = (args.length === 1) ? args[0] : new MergedArgument(args); + this._setAssignment(assignment, arg, true); return; } } // Positional arguments can still be specified by name, but if they are // then we need to ignore them when working them out positionally var unassignedParams = this.getParameterNames(); @@ -6634,77 +6628,74 @@ Requisition.prototype._assign = function var arrayArg = arrayArgs[assignment.param.name]; if (!arrayArg) { arrayArg = new ArrayArgument(); arrayArgs[assignment.param.name] = arrayArg; } arrayArg.addArgument(arg); } else { - var conversion = assignment.param.type.parse(arg); - assignment.setConversion(conversion); + this._setAssignment(assignment, arg, true); } } else { // Skip this parameter and handle as a positional parameter i++; } } }, this); // What's left are positional parameters assign in order unassignedParams.forEach(function(name) { var assignment = this.getAssignment(name); // If not set positionally, and we can't set it non-positionally, // we have to default it to prevent previous values surviving if (!assignment.param.isPositionalAllowed) { - assignment.setBlank(); + this._setAssignment(assignment, null, true); return; } // If this is a positional array argument, then it swallows the // rest of the arguments. if (assignment.param.type instanceof ArrayType) { var arrayArg = arrayArgs[assignment.param.name]; if (!arrayArg) { arrayArg = new ArrayArgument(); arrayArgs[assignment.param.name] = arrayArg; } arrayArg.addArguments(args); args = []; } else { if (args.length === 0) { - assignment.setBlank(); + this._setAssignment(assignment, null, true); } else { var arg = args.splice(0, 1)[0]; // --foo and -f are named parameters, -4 is a number. So '-' is either // the start of a named parameter or a number depending on the context var isIncompleteName = assignment.param.type instanceof NumberType ? /-[-a-zA-Z_]/.test(arg.text) : arg.text.charAt(0) === '-'; if (isIncompleteName) { this._unassigned.push(new UnassignedAssignment(this, arg)); } else { - var conversion = assignment.param.type.parse(arg); - assignment.setConversion(conversion); + this._setAssignment(assignment, arg, true); } } } }, this); // Now we need to assign the array argument (if any) Object.keys(arrayArgs).forEach(function(name) { var assignment = this.getAssignment(name); - var conversion = assignment.param.type.parse(arrayArgs[name]); - assignment.setConversion(conversion); + this._setAssignment(assignment, arrayArgs[name], true); }, this); // What's left is can't be assigned, but we need to extract this._addUnassignedArgs(args); }; exports.Requisition = Requisition; @@ -6724,27 +6715,41 @@ function Output(options) { this.error = false; this.start = new Date(); this.onClose = util.createEvent('Output.onClose'); this.onChange = util.createEvent('Output.onChange'); } /** - * Called when there is data to display - * @param data - */ -Output.prototype.complete = function(data) { + * Called when there is data to display, but the command is still executing + * @param data The new data. If the data structure has been altered but the + * root object is still the same, The same root object should be passed in the + * data parameter. + * @param ev Optional additional event data, for example to explain how the + * data structure has changed + */ +Output.prototype.changed = function(data, ev) { this.data = data; + ev = ev || {}; + ev.output = this; + this.onChange(ev); +}; + +/** + * Called when there is data to display, and the command has finished executing + * See changed() for details on parameters. + */ +Output.prototype.complete = function(data, ev) { this.end = new Date(); this.duration = this.end.getTime() - this.start.getTime(); this.completed = true; - this.onChange({ output: this }); + this.changed(data, ev); }; /** * Convert to a DOM element for display. * @param element The DOM node to which the data should be written. Existing * content of 'element' will be removed before 'outputData' is added. */ Output.prototype.toDom = function(element) { @@ -6825,18 +6830,25 @@ exports.Output = Output; */ exports.createExecutionContext = function(requisition) { return { exec: requisition.exec.bind(requisition), update: requisition.update.bind(requisition), document: requisition.document, environment: requisition.environment, createView: view.createView, + defer: function() { + return Q.defer(); + }, + /** + * @deprecated Use defer() instead, which does the same thing, but is not + * confusingly named + */ createPromise: function() { - return new Promise(); + return Q.defer(); } }; }; }); /* * Copyright 2012, Mozilla Foundation and contributors @@ -6851,18 +6863,23 @@ exports.createExecutionContext = functio * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ define('gcli/promise', ['require', 'exports', 'module' ], function(require, exports, module) { - Components.utils.import("resource:///modules/devtools/Promise.jsm"); - exports.Promise = Promise; + var imported = {}; + Components.utils.import("resource://gre/modules/commonjs/promise/core.js", + imported); + + exports.defer = imported.Promise.defer; + exports.resolve = imported.Promise.resolve; + exports.reject = imported.Promise.reject; }); define("text!gcli/ui/intro.html", [], "\n" + "<div>\n" + " <p>${l10n.introTextOpening}</p>\n" + "\n" + " <p>\n" + " ${l10n.introTextCommands}\n" + @@ -7232,17 +7249,17 @@ FocusManager.prototype._checkShow = func var showOutput = this._shouldShowOutput(); if (this.isOutputVisible !== showOutput.visible) { ev.outputVisible = this.isOutputVisible = showOutput.visible; fire = true; } if (fire) { if (this._debug) { - console.debug('FocusManager.onVisibilityChange', ev); + console.log('FocusManager.onVisibilityChange', ev); } this.onVisibilityChange(ev); } }; /** * Calculate if we should be showing or hidden taking into account all the * available inputs @@ -7730,16 +7747,19 @@ Field.prototype.element = undefined; /** * Indicates that this field should drop any resources that it has created */ Field.prototype.destroy = function() { delete this.messageElement; }; +// Note: We could/should probably change Fields from working with Conversions +// to working with Arguments (Tokens), which makes for less calls to parse() + /** * Update this field display with the value from this conversion. * Subclasses should provide an implementation of this function. */ Field.prototype.setConversion = function(conversion) { throw new Error('Field should not be used directly'); }; @@ -9388,17 +9408,19 @@ Inputter.prototype.textChanged = functio } var newStr = this.requisition.toString(); var input = this.getInputState(); input.typed = newStr; this._processCaretChange(input); - this.element.value = newStr; + if (this.element.value !== newStr) { + this.element.value = newStr; + } this.onInputChange({ inputState: input }); }; /** * Various ways in which we need to manipulate the caret/selection position. * A value of null means we're not expecting a change */ var Caret = { @@ -9464,18 +9486,22 @@ Inputter.prototype._processCaretChange = start = (start > input.typed.length) ? input.typed.length : start; end = (end > input.typed.length) ? input.typed.length : end; var newInput = { typed: input.typed, cursor: { start: start, end: end } }; - this.element.selectionStart = start; - this.element.selectionEnd = end; + if (this.element.selectionStart !== start) { + this.element.selectionStart = start; + } + if (this.element.selectionEnd !== end) { + this.element.selectionEnd = end; + } this._checkAssignment(start); this._caretChange = null; return newInput; }; /** @@ -9600,17 +9626,17 @@ Inputter.prototype.onKeyUp = function(ev else if (this.element.value === '' || this._scrollingThroughHistory) { this._scrollingThroughHistory = true; this.requisition.update(this.history.backward()); } else { // If the user is on a valid value, then we increment the value, but if // they've typed something that's not right we page through predictions if (this.assignment.getStatus() === Status.VALID) { - this.requisition.increment(assignment); + this.requisition.increment(this.assignment); // See notes on focusManager.onInputChange in onKeyDown if (this.focusManager) { this.focusManager.onInputChange(); } } else { this.changeChoice(-1); } @@ -9624,17 +9650,17 @@ Inputter.prototype.onKeyUp = function(ev } else if (this.element.value === '' || this._scrollingThroughHistory) { this._scrollingThroughHistory = true; this.requisition.update(this.history.forward()); } else { // See notes above for the UP key if (this.assignment.getStatus() === Status.VALID) { - this.requisition.decrement(assignment); + this.requisition.decrement(this.assignment); // See notes on focusManager.onInputChange in onKeyDown if (this.focusManager) { this.focusManager.onInputChange(); } } else { this.changeChoice(+1); } @@ -10309,17 +10335,17 @@ Tooltip.prototype.selectChoice = functio } return false; }; /** * Called by the onFieldChange event on the current Field */ Tooltip.prototype.fieldChanged = function(ev) { - this.assignment.setConversion(ev.conversion); + this.requisition.setAssignment(this.assignment, ev.conversion.arg); var isError = ev.conversion.message != null && ev.conversion.message !== ''; this.focusManager.setError(isError); // Nasty hack, the inputter won't know about the text change yet, so it will // get it's calculations wrong. We need to wait until the current set of // changes has had a chance to propagate this.document.defaultView.setTimeout(function() {
--- a/browser/devtools/commandline/test/browser_cmd_pref.js +++ b/browser/devtools/commandline/test/browser_cmd_pref.js @@ -158,18 +158,18 @@ function testPrefStatus() { setting: { arg: ' devtools.editor.tabsize' }, value: { value: 4 }, } }); helpers.setInput('pref list'); helpers.check({ input: 'pref list', - hints: '', - markup: 'EEEEVEEEE', + hints: ' -> pref set', + markup: 'IIIIVIIII', status: 'ERROR' }); } function testPrefSetEnable() { DeveloperToolbarTest.exec({ typed: "pref set devtools.editor.tabsize 9", args: {
--- a/browser/devtools/commandline/test/browser_gcli_web.js +++ b/browser/devtools/commandline/test/browser_gcli_web.js @@ -103,33 +103,33 @@ define('gclitest/index', ['require', 'ex }; /** * A simple proxy to examiner.run, for convenience - this is run from the * top level. * @param options Lookup of options that customize test running. Includes: * - window (default=undefined) A reference to the DOM window. If left * undefined then a reduced set of tests will run. - * - isNode (default=false) Are we running under NodeJS, specifically, are we - * using JSDom, which isn't a 100% complete DOM implementation. + * - isJsdom (default=false) Are we running under JSDom, specifically, which + * isn't a 100% complete DOM implementation. * Some tests are skipped when using NodeJS. * - display (default=undefined) A reference to a Display implementation. * A reduced set of tests will run if left undefined * - detailedResultLog (default=false) do we output a test summary to * |console.log| on test completion. * - hideExec (default=false) Set the |hidden| property in calls to * |requisition.exec()| which prevents the display from becoming messed up, * however use of hideExec restricts the set of tests that are run */ - exports.run = function(options) { + exports.runAsync = function(options, callback) { options = options || {}; examiner.mergeDefaultOptions(options); examiner.reset(); - examiner.run(options); + examiner.runAsync(options, callback); // A better set of default than those specified above, come from the set // that are passed to run(). examiner.defaultOptions = { window: options.window, display: options.display, hideExec: options.hideExec }; @@ -150,23 +150,16 @@ define('gclitest/index', ['require', 'ex settings.setDefaults(options.settings); } window.display = new Display(options); var requisition = window.display.requisition; // setTimeout keeps stack traces clear of RequireJS frames window.setTimeout(function() { - var options = { - window: window, - display: window.display, - hideExec: true - }; - exports.run(options); - window.createDebugCheck = function() { require([ 'gclitest/helpers' ], function(helpers) { helpers.setup(options); console.log(helpers._createDebugCheck()); helpers.shutdown(options); }); }; @@ -193,17 +186,25 @@ define('gclitest/index', ['require', 'ex } }, true); window.testCommands = function() { require([ 'gclitest/mockCommands' ], function(mockCommands) { mockCommands.setup(); }); }; - window.testCommands(); + + var options = { + window: window, + display: window.display, + hideExec: true + }; + exports.runAsync(options, function() { + window.testCommands(); + }); }, 10); return { /** * The exact shape of the object returned by exec is likely to change in * the near future. If you do use it, please expect your code to break. */ exec: requisition.exec.bind(requisition), @@ -322,35 +323,16 @@ examiner.mergeDefaultOptions = function( Object.keys(examiner.defaultOptions).forEach(function(name) { if (options[name] == null) { options[name] = examiner.defaultOptions[name]; } }); }; /** - * Run the tests defined in the test suite synchronously - */ -examiner.run = function(options) { - Object.keys(examiner.suites).forEach(function(suiteName) { - var suite = examiner.suites[suiteName]; - suite.run(options); - }.bind(this)); - - if (options.detailedResultLog) { - examiner.detailedResultLog(); - } - else { - console.log('Completed test suite'); - } - - return examiner.suites; -}; - -/** * Run all the tests asynchronously */ examiner.runAsync = function(options, callback) { this._runAsyncInternal(0, options, callback); }; /** * Run all the test suits asynchronously @@ -473,31 +455,16 @@ function Suite(suiteName, suite) { */ Suite.prototype.reset = function() { Object.keys(this.tests).forEach(function(testName) { this.tests[testName].reset(); }, this); }; /** - * Run all the tests in this suite synchronously - */ -Suite.prototype.run = function(options) { - if (!this._setup(options)) { - return; - } - - Object.keys(this.tests).forEach(function(testName) { - this.tests[testName].run(options); - }, this); - - this._shutdown(options); -}; - -/** * Run all the tests in this suite asynchronously */ Suite.prototype.runAsync = function(options, callback) { if (!this._setup(options)) { if (typeof callback === 'function') { callback(); } return; @@ -631,35 +598,42 @@ Suite.prototype._logToAllTests = functio * A test represents data about a single test function */ function Test(suite, name, func) { this.suite = suite; this.name = name; this.func = func; this.title = name.replace(/^test/, '').replace(/([A-Z])/g, ' $1'); + this.outstanding = []; + this.callback = undefined; + this.failures = []; this.status = stati.notrun; this.checks = 0; } /** * Reset the test to its original state */ Test.prototype.reset = function() { + this.outstanding = []; + this.callback = undefined; + this.failures = []; this.status = stati.notrun; this.checks = 0; }; /** - * Run just a single test + * Run all the tests in this suite asynchronously */ -Test.prototype.run = function(options) { +Test.prototype.runAsync = function(options, callback) { assert.currentTest = this; + this.callback = callback; this.status = stati.executing; this.failures = []; this.checks = 0; try { this.func.apply(this.suite, [ options ]); } catch (ex) { @@ -670,28 +644,30 @@ Test.prototype.run = function(options) { } } if (this.status === stati.executing) { this.status = stati.pass; } assert.currentTest = null; + + this.checkFinish(); }; /** - * Run all the tests in this suite asynchronously + * Check to see if the currently executing test is completed (i.e. the list of + * outstanding tasks has all been completed) */ -Test.prototype.runAsync = function(options, callback) { - setTimeout(function() { - this.run(options); - if (typeof callback === 'function') { - callback(); +Test.prototype.checkFinish = function() { + if (this.outstanding.length == 0) { + if (typeof this.callback === 'function') { + this.callback(); } - }.bind(this), delay); + } }; /** * Create a JSON object suitable for serialization */ Test.prototype.toRemote = function() { return { name: this.name, @@ -887,31 +863,35 @@ define('gclitest/testCanon', ['require', * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -define('gclitest/helpers', ['require', 'exports', 'module' , 'test/assert', 'gcli/util'], function(require, exports, module) { +define('gclitest/helpers', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) { var test = require('test/assert'); -var util = require('gcli/util'); - +// A copy of this code exists in firefox mochitests; when updated here it +// should be updated there too. Hence the use of an exports synonym for non +// AMD contexts. var helpers = exports; helpers._display = undefined; +helpers._options = undefined; helpers.setup = function(options) { + helpers._options = options; helpers._display = options.display; }; helpers.shutdown = function(options) { + helpers._options = undefined; helpers._display = undefined; }; /** * Various functions to return the actual state of the command line */ helpers._actual = { input: function() { @@ -1020,16 +1000,26 @@ helpers._createDebugCheck = function() { * and check() which ensures that things are in the right place afterwards. */ helpers.setInput = function(typed, cursor) { helpers._display.inputter.setInput(typed); if (cursor) { helpers._display.inputter.setCursor({ start: cursor, end: cursor }); } + else { + // This is a hack because jsdom appears to not handle cursor updates + // in the same way as most browsers. + if (helpers._options.isJsdom) { + helpers._display.inputter.setCursor({ + start: typed.length, + end: typed.length + }); + } + } helpers._display.focusManager.onInputChange(); }; /** * Simulate focusing the input field */ helpers.focusInput = function() { @@ -1584,17 +1574,17 @@ exports.testSingleNumber = function() { exports.testElement = function(options) { update({ typed: 'tse', cursor: { start: 3, end: 3 } }); test.is( 'VVV', statuses); test.is(Status.ERROR, status); test.is('tse', requ.commandAssignment.value.name); test.ok(assign1.arg.type === 'BlankArgument'); test.is(undefined, assign1.value); - if (!options.isNode) { + if (!options.isJsdom) { update({ typed: 'tse :root', cursor: { start: 9, end: 9 } }); test.is( 'VVVVVVVVV', statuses); test.is(Status.VALID, status); test.is('tse', requ.commandAssignment.value.name); test.is(':root', assign1.arg.text); if (!options.window.isFake) { test.is(options.window.document.documentElement, assign1.value); } @@ -1620,17 +1610,17 @@ exports.testElement = function(options) // digging into the CSS engine we can't tell that so we default to incomplete test.is( 'VVVVIIIIIIIIIIIII', statuses); test.is(Status.ERROR, status); test.is('tse', requ.commandAssignment.value.name); test.is('#gcli-nomatch', assign1.arg.text); test.is(undefined, assign1.value); } else { - test.log('Skipping :root test due to jsdom (from isNode)'); + test.log('Skipping :root test due to jsdom'); } update({ typed: 'tse #', cursor: { start: 5, end: 5 } }); test.is( 'VVVVE', statuses); test.is(Status.ERROR, status); test.is('tse', requ.commandAssignment.value.name); test.is('#', assign1.arg.text); test.is(undefined, assign1.value); @@ -2355,17 +2345,17 @@ exports.testActivate = function(options) helpers.setInput('tsg b'); helpers.check({ hints: 'bb [options]' }); helpers.setInput('tsg d'); helpers.check({ - hints: ' [options]' + hints: ' [options] -> ccc' }); helpers.setInput('tsg aa'); helpers.check({ hints: 'a [options]' }); helpers.setInput('tsg aaa'); @@ -2769,33 +2759,39 @@ var mockDoc = { }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -define('gclitest/testFocus', ['require', 'exports', 'module' , 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) { - - +define('gclitest/testFocus', ['require', 'exports', 'module' , 'test/assert', 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) { + + +var test = require('test/assert'); var helpers = require('gclitest/helpers'); var mockCommands = require('gclitest/mockCommands'); exports.setup = function(options) { mockCommands.setup(); helpers.setup(options); }; exports.shutdown = function(options) { mockCommands.shutdown(); helpers.shutdown(options); }; exports.testBasic = function(options) { + if (options.isJsdom) { + test.log('jsdom does not pass on focus events properly, skipping testBasic'); + return; + } + helpers.focusInput(); helpers.exec(options, 'help'); helpers.setInput('tsn deep'); helpers.check({ input: 'tsn deep', hints: '', markup: 'IIIVIIII', @@ -3338,19 +3334,18 @@ exports.testIncomplete = function(option test.is(requisition._unassigned[0].param.type.isIncompleteName, true, 'unassigned.isIncompleteName: tsg -'); }; exports.testHidden = function(options) { helpers.setInput('tshidde'); helpers.check({ input: 'tshidde', - markup: 'EEEEEEE', - status: 'ERROR', - hints: '', + hints: ' -> tse', + status: 'ERROR' }); helpers.setInput('tshidden'); helpers.check({ input: 'tshidden', hints: ' [options]', markup: 'VVVVVVVV', status: 'VALID', @@ -3465,32 +3460,33 @@ exports.testHidden = function(options) { * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -define('gclitest/testIntro', ['require', 'exports', 'module' , 'gclitest/helpers', 'test/assert'], function(require, exports, module) { +define('gclitest/testIntro', ['require', 'exports', 'module' , 'gclitest/helpers', 'test/assert', 'gcli/canon'], function(require, exports, module) { var helpers = require('gclitest/helpers'); var test = require('test/assert'); + var canon = require('gcli/canon'); exports.setup = function(options) { helpers.setup(options); }; exports.shutdown = function(options) { helpers.shutdown(options); }; exports.testIntroStatus = function(options) { - if (options.isFirefox) { - test.log('Skipping testIntroStatus in Firefox.'); + if (canon.getCommand('intro') == null) { + test.log('Skipping testIntroStatus; missing intro command.'); return; } helpers.setInput('intro'); helpers.check({ typed: 'intro', markup: 'VVVVV', status: 'VALID', @@ -3502,18 +3498,18 @@ define('gclitest/testIntro', ['require', typed: 'intro foo', markup: 'VVVVVVEEE', status: 'ERROR', hints: '' }); }; exports.testIntroExec = function(options) { - if (options.isFirefox) { - test.log('Skipping testIntroExec in Firefox.'); + if (canon.getCommand('intro') == null) { + test.log('Skipping testIntroStatus; missing intro command.'); return; } helpers.exec(options, { typed: 'intro', args: { }, outputMatch: [ /command\s*line/, @@ -3678,17 +3674,20 @@ exports.testBasic = function(options) { check('VVVVVVVVVVVVVVVVVVVVVVV', Status.VALID, 'window.document.title', 0); input('{ d'); check('VVI', Status.ERROR, 'd', 'document'); input('{ document.title'); check('VVVVVVVVVVVVVVVV', Status.VALID, 'document.title', 0); - test.ok('donteval' in options.window, 'donteval exists'); + if (!options.isJsdom) { + // jsdom causes an eval here, maybe that's node/v8? + test.ok('donteval' in options.window, 'donteval exists'); + } input('{ don'); check('VVIII', Status.ERROR, 'don', 'donteval'); input('{ donteval'); check('VVVVVVVVVV', Status.VALID, 'donteval', 0); /* @@ -3827,22 +3826,22 @@ exports.testComplete = function(options) if (!canon.getCommand('{')) { test.log('Skipping exec tests because { is not registered'); } else { check('{ wind', COMPLETES_TO, '{ window', 0); check('{ window.docum', COMPLETES_TO, '{ window.document', 0); - // Bug 717228: This fails under node - if (!options.isNode) { + // Bug 717228: This fails under jsdom + if (!options.isJsdom) { check('{ window.document.titl', COMPLETES_TO, '{ window.document.title ', 0); } else { - test.log('Running under Node. Skipping tests due to bug 717228.'); + test.log('Skipping tests due to jsdom and bug 717228.'); } } }; exports.testInternalComplete = function(options) { // Bug 664377 // check('tsela 1', COMPLETES_TO, 'tselarr 1', 0, 3, 8); }; @@ -4003,71 +4002,76 @@ exports.testNode = function(options) { status: 'ERROR', message: 'Syntax error in CSS query' }, nodes: { status: 'VALID' }, nodes2: { status: 'VALID' } } }); - helpers.setInput('tse :root'); - helpers.check({ - input: 'tse :root', - hints: ' [options]', - markup: 'VVVVVVVVV', - cursor: 9, - current: 'node', - status: 'VALID', - args: { - command: { name: 'tse' }, - node: { arg: ' :root', status: 'VALID' }, - nodes: { status: 'VALID' }, - nodes2: { status: 'VALID' } - } - }); - - helpers.setInput('tse :root '); - helpers.check({ - input: 'tse :root ', - hints: '[options]', - markup: 'VVVVVVVVVV', - cursor: 10, - current: 'node', - status: 'VALID', - args: { - command: { name: 'tse' }, - node: { arg: ' :root ', status: 'VALID' }, - nodes: { status: 'VALID' }, - nodes2: { status: 'VALID' } - } - }); - test.is(requisition.getAssignment('node').value.tagName, - 'HTML', - 'root id'); - - helpers.setInput('tse #gcli-nomatch'); - helpers.check({ - input: 'tse #gcli-nomatch', - hints: ' [options]', - markup: 'VVVVIIIIIIIIIIIII', - cursor: 17, - current: 'node', - status: 'ERROR', - args: { - command: { name: 'tse' }, - node: { - value: undefined, - arg: ' #gcli-nomatch', - status: 'INCOMPLETE', - message: 'No matches' - }, - nodes: { status: 'VALID' }, - nodes2: { status: 'VALID' } - } - }); + if (options.isJsdom) { + test.log('skipping node tests because jsdom'); + } + else { + helpers.setInput('tse :root'); + helpers.check({ + input: 'tse :root', + hints: ' [options]', + markup: 'VVVVVVVVV', + cursor: 9, + current: 'node', + status: 'VALID', + args: { + command: { name: 'tse' }, + node: { arg: ' :root', status: 'VALID' }, + nodes: { status: 'VALID' }, + nodes2: { status: 'VALID' } + } + }); + + helpers.setInput('tse :root '); + helpers.check({ + input: 'tse :root ', + hints: '[options]', + markup: 'VVVVVVVVVV', + cursor: 10, + current: 'node', + status: 'VALID', + args: { + command: { name: 'tse' }, + node: { arg: ' :root ', status: 'VALID' }, + nodes: { status: 'VALID' }, + nodes2: { status: 'VALID' } + } + }); + test.is(requisition.getAssignment('node').value.tagName, + 'HTML', + 'root id'); + + helpers.setInput('tse #gcli-nomatch'); + helpers.check({ + input: 'tse #gcli-nomatch', + hints: ' [options]', + markup: 'VVVVIIIIIIIIIIIII', + cursor: 17, + current: 'node', + status: 'ERROR', + args: { + command: { name: 'tse' }, + node: { + value: undefined, + arg: ' #gcli-nomatch', + status: 'INCOMPLETE', + message: 'No matches' + }, + nodes: { status: 'VALID' }, + nodes2: { status: 'VALID' } + } + }); + } helpers.setInput('tse #'); helpers.check({ input: 'tse #', hints: ' [options]', markup: 'VVVVE', cursor: 5, current: 'node', @@ -4126,16 +4130,21 @@ exports.testNode = function(options) { nodes2: { status: 'VALID' } } }); }; exports.testNodes = function(options) { var requisition = options.display.requisition; + if (options.isJsdom) { + test.log('skipping node tests because jsdom'); + return; + } + helpers.setInput('tse :root --nodes *'); helpers.check({ input: 'tse :root --nodes *', hints: ' [options]', markup: 'VVVVVVVVVVVVVVVVVVV', current: 'nodes', status: 'VALID', args: { @@ -4231,23 +4240,24 @@ exports.testNodes = function(options) { * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ -define('gclitest/testPref', ['require', 'exports', 'module' , 'gcli/commands/pref', 'gclitest/helpers', 'gclitest/mockSettings', 'test/assert'], function(require, exports, module) { +define('gclitest/testPref', ['require', 'exports', 'module' , 'gcli/commands/pref', 'gclitest/helpers', 'gclitest/mockSettings', 'test/assert', 'gcli/canon'], function(require, exports, module) { var pref = require('gcli/commands/pref'); var helpers = require('gclitest/helpers'); var mockSettings = require('gclitest/mockSettings'); var test = require('test/assert'); +var canon = require('gcli/canon'); exports.setup = function(options) { helpers.setup(options); if (!options.isFirefox) { mockSettings.setup(); } @@ -4265,16 +4275,21 @@ exports.shutdown = function(options) { }; exports.testPrefShowStatus = function(options) { if (options.isFirefox) { test.log('Skipping testPrefShowStatus in Firefox.'); return; } + if (canon.getCommand('intro') == null) { + test.log('Skipping testIntroStatus; missing intro command.'); + return; + } + helpers.setInput('pref s'); helpers.check({ typed: 'pref s', hints: 'et', markup: 'IIIIVI', status: 'ERROR' }); @@ -4328,16 +4343,21 @@ exports.testPrefShowStatus = function(op }; exports.testPrefSetStatus = function(options) { if (options.isFirefox) { test.log('Skipping testPrefSetStatus in Firefox.'); return; } + if (canon.getCommand('intro') == null) { + test.log('Skipping testIntroStatus; missing intro command.'); + return; + } + helpers.setInput('pref s'); helpers.check({ typed: 'pref s', hints: 'et', markup: 'IIIIVI', status: 'ERROR', }); @@ -4347,17 +4367,17 @@ exports.testPrefSetStatus = function(opt hints: ' <setting> <value>', markup: 'VVVVVVVV', status: 'ERROR' }); helpers.setInput('pref xxx'); helpers.check({ typed: 'pref xxx', - markup: 'EEEEVEEE', + markup: 'IIIIVIII', status: 'ERROR' }); helpers.setInput('pref set '); helpers.check({ typed: 'pref set ', hints: 'allowSet <value>', markup: 'VVVVVVVVV', @@ -4390,16 +4410,21 @@ exports.testPrefSetStatus = function(opt }; exports.testPrefExec = function(options) { if (options.isFirefox) { test.log('Skipping testPrefExec in Firefox.'); return; } + if (canon.getCommand('intro') == null) { + test.log('Skipping testIntroStatus; missing intro command.'); + return; + } + var initialAllowSet = pref.allowSet.value; pref.allowSet.value = false; test.is(mockSettings.tempNumber.value, 42, 'set to 42'); helpers.exec(options, { typed: 'pref set tempNumber 4', args: { @@ -4739,22 +4764,21 @@ exports.testPredictions = function(optio test.ok(options2.length > 1, 'have resources'); options2.forEach(function(prediction) { checkPrediction(resource2, prediction); }); var resource3 = types.getType({ name: 'resource', include: 'text/css' }); var options3 = resource3.getLookup(); // jsdom fails to support digging into stylesheets - if (!options.isNode) { + if (!options.isJsdom) { test.ok(options3.length >= 1, 'have resources'); } else { - test.log('Running under Node. ' + - 'Skipping checks due to jsdom document.stylsheets support.'); + test.log('Skipping checks due to jsdom document.stylsheets support.'); } options3.forEach(function(prediction) { checkPrediction(resource3, prediction); }); var resource4 = types.getType({ name: 'resource' }); var options4 = resource4.getLookup(); @@ -5013,39 +5037,38 @@ exports.testChange = function(options) { * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ define('gclitest/testSpell', ['require', 'exports', 'module' , 'test/assert', 'gcli/types/spell'], function(require, exports, module) { var test = require('test/assert'); -var Speller = require('gcli/types/spell').Speller; +var spell = require('gcli/types/spell'); exports.setup = function() { }; exports.shutdown = function() { }; exports.testSpellerSimple = function(options) { if (options.isFirefox) { test.log('Skipping testPref in Firefox.'); return; } - var speller = new Speller(); - speller.train(Object.keys(options.window)); - - test.is(speller.correct('document'), 'document'); - test.is(speller.correct('documen'), 'document'); - test.is(speller.correct('ocument'), 'document'); - test.is(speller.correct('odcument'), 'document'); - - test.is(speller.correct('========='), null); + var alternatives = Object.keys(options.window); + + test.is(spell.correct('document', alternatives), 'document'); + test.is(spell.correct('documen', alternatives), 'document'); + test.is(spell.correct('ocument', alternatives), 'document'); + test.is(spell.correct('odcument', alternatives), 'document'); + + test.is(spell.correct('=========', alternatives), undefined); }; }); /* * Copyright 2012, Mozilla Foundation and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -5453,17 +5476,17 @@ function type(typed, tests, options) { var inputter = options.display.inputter; var tooltip = options.display.tooltip; inputter.setInput(typed); if (tests.cursor) { inputter.setCursor({ start: tests.cursor, end: tests.cursor }); } - if (!options.isNode) { + if (!options.isJsdom) { if (tests.important) { test.ok(tooltip.field.isImportant, 'Important for ' + typed); } else { test.ok(!tooltip.field.isImportant, 'Not important for ' + typed); } if (tests.options) { @@ -5483,39 +5506,37 @@ function type(typed, tests, options) { } exports.testActivate = function(options) { if (!options.display) { test.log('No display. Skipping activate tests'); return; } - if (options.isNode) { - test.log('Running under Node. Reduced checks due to JSDom.textContent'); + if (options.isJsdom) { + test.log('Reduced checks due to JSDom.textContent'); } type(' ', { }, options); type('tsb ', { important: true, options: [ 'false', 'true' ] }, options); type('tsb t', { important: true, options: [ 'true' ] }, options); type('tsb tt', { important: true, - options: [ ], - error: 'Can\'t use \'tt\'.' + options: [ 'true' ] }, options); - type('asdf', { important: false, options: [ ], error: 'Can\'t use \'asdf\'.' }, options); type('', { }, options); }; @@ -5567,19 +5588,18 @@ function forEachType(options, callback) } var type = types.getType(options); callback(type); }); } exports.testDefault = function(options) { - if (options.isNode) { - test.log('Running under Node. ' + - 'Skipping tests due to issues with resource type.'); + if (options.isJsdom) { + test.log('Skipping tests due to issues with resource type.'); return; } forEachType({}, function(type) { var blank = type.getBlank().value; // boolean and array types are exempt from needing undefined blank values if (type.name === 'boolean') {
--- a/browser/devtools/debugger/test/browser_dbg_displayName.html +++ b/browser/devtools/debugger/test/browser_dbg_displayName.html @@ -9,18 +9,21 @@ var a = function() { return function() { debugger; } } var anon = a(); anon.displayName = "anonFunc"; +var inferred = a(); + function evalCall() { eval("anon();"); + eval("inferred();"); } </script> </head> <body></body> </html>
--- a/browser/devtools/debugger/test/browser_dbg_displayName.js +++ b/browser/devtools/debugger/test/browser_dbg_displayName.js @@ -1,13 +1,16 @@ /* * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +// Tests that anonymous functions appear in the stack frame list with either +// their displayName property or a SpiderMonkey-inferred name. + var gPane = null; var gTab = null; var gDebuggee = null; var gDebugger = null; const TAB_URL = EXAMPLE_URL + "browser_dbg_displayName.html"; function test() { @@ -31,21 +34,43 @@ function testAnonCall() { "Should only be getting stack frames while paused."); is(frames.querySelectorAll(".dbg-stackframe").length, 3, "Should have three frames."); is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").getAttribute("value"), "anonFunc", "Frame name should be anonFunc"); + testInferredName(); + }}, 0); + }); + + gDebuggee.evalCall(); +} + +function testInferredName() { + gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() { + Services.tm.currentThread.dispatch({ run: function() { + + let frames = gDebugger.DebuggerView.StackFrames._frames; + + is(gDebugger.DebuggerController.activeThread.state, "paused", + "Should only be getting stack frames while paused."); + + is(frames.querySelectorAll(".dbg-stackframe").length, 3, + "Should have three frames."); + + is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").getAttribute("value"), + "a/<", "Frame name should be a/<"); + resumeAndFinish(); }}, 0); }); - gDebuggee.evalCall(); + gDebugger.DebuggerController.activeThread.resume(); } function resumeAndFinish() { gDebugger.DebuggerController.activeThread.resume(function() { removeTab(gTab); gPane = null; gDebuggee = null; finish();
--- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -1,19 +1,19 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; let tempScope = {}; -Cu.import("resource:///modules/devtools/dbg-server.jsm", tempScope); -Cu.import("resource:///modules/devtools/dbg-client.jsm", tempScope); -Cu.import("resource:///modules/Services.jsm", tempScope); +Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope); +Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope); +Cu.import("resource://gre/modules/Services.jsm", tempScope); let DebuggerServer = tempScope.DebuggerServer; let DebuggerTransport = tempScope.DebuggerTransport; let DebuggerClient = tempScope.DebuggerClient; let Services = tempScope.Services; const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/"; const TAB1_URL = EXAMPLE_URL + "browser_dbg_tab1.html";
--- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -30,16 +30,17 @@ Cu.import("resource://gre/modules/jsdebu const SCRATCHPAD_CONTEXT_CONTENT = 1; const SCRATCHPAD_CONTEXT_BROWSER = 2; const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties"; const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax"; const BUTTON_POSITION_SAVE = 0; const BUTTON_POSITION_CANCEL = 1; const BUTTON_POSITION_DONT_SAVE = 2; +const BUTTON_POSITION_REVERT=0; let keysbundle = Services.strings.createBundle("chrome://global-platform/locale/platformKeys.properties"); function SP_Pretty_Key(aElemKey) { let elemString = ""; let elemMod = aElemKey.getAttribute("modifiers"); @@ -918,16 +919,82 @@ var Scratchpad = { if (aCallback) { aCallback(aStatus); } }); } }, /** + * Restore content from saved version of current file. + * + * @param function aCallback + * Optional function you want to call when file is saved + */ + revertFile: function SP_revertFile(aCallback) + { + + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + file.initWithPath(this.filename); + + if (!file.exists()) { + return; + } + + this.importFromFile(file, false, function(aStatus, aContent) { + if (aCallback) { + aCallback(aStatus); + } + }); + }, + + /** + * Prompt to revert scratchpad if it has unsaved changes. + * + * @param function aCallback + * Optional function you want to call when file is saved. The callback + * receives three arguments: + * - aRevert (boolean) - tells if the file has been reverted. + * - status (number) - the file revert status result (if the file was + * saved). + */ + promptRevert: function SP_promptRervert(aCallback) + { + if (this.filename) { + let ps = Services.prompt; + let flags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_REVERT + + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL; + + let button = ps.confirmEx(window, + this.strings.GetStringFromName("confirmRevert.title"), + this.strings.GetStringFromName("confirmRevert"), + flags, null, null, null, null, {}); + if (button == BUTTON_POSITION_CANCEL) { + if (aCallback) { + aCallback(false); + } + + return; + } + if (button == BUTTON_POSITION_REVERT) { + this.revertFile(function(aStatus) { + if(aCallback){ + aCallback(true, aStatus); + } + }); + + return; + } + } + if (aCallback) { + aCallback(false); + } + }, + + /** * Open the Error Console. */ openErrorConsole: function SP_openErrorConsole() { this.browserWindow.toJavaScriptConsole(); }, /** @@ -1102,16 +1169,24 @@ var Scratchpad = { * @private * @see SourceEditor.EVENTS.DIRTY_CHANGED * @param object aEvent * The DirtyChanged event object. */ _onDirtyChanged: function SP__onDirtyChanged(aEvent) { Scratchpad._updateTitle(); + if (Scratchpad.filename) { + if (Scratchpad.editor.dirty) { + document.getElementById("sp-cmd-revert").removeAttribute("disabled"); + } + else { + document.getElementById("sp-cmd-revert").setAttribute("disabled", true); + } + } }, /** * Undo the last action of the user. */ undo: function SP_undo() { this.editor.undo();
--- a/browser/devtools/scratchpad/scratchpad.xul +++ b/browser/devtools/scratchpad/scratchpad.xul @@ -28,16 +28,17 @@ <commandset id="sourceEditorCommands"/> <commandset id="sp-commandset"> <command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/> <command id="sp-cmd-openFile" oncommand="Scratchpad.openFile();"/> <command id="sp-cmd-clearRecentFiles" oncommand="Scratchpad.clearRecentFiles();"/> <command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/> <command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/> + <command id="sp-cmd-revert" oncommand="Scratchpad.promptRevert();" disabled="true"/> <!-- TODO: bug 650340 - implement printFile() <command id="sp-cmd-printFile" oncommand="Scratchpad.printFile();" disabled="true"/> --> <command id="sp-cmd-close" oncommand="Scratchpad.close();"/> <command id="sp-cmd-run" oncommand="Scratchpad.run();"/> <command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/> @@ -127,16 +128,20 @@ label="&saveFileCmd.label;" accesskey="&saveFileCmd.accesskey;" key="sp-key-save" command="sp-cmd-save"/> <menuitem id="sp-menu-saveas" label="&saveFileAsCmd.label;" accesskey="&saveFileAsCmd.accesskey;" command="sp-cmd-saveas"/> + <menuitem id="sp-menu-revert" + label="&revertCmd.label;" + accesskey="&revertCmd.accesskey;" + command="sp-cmd-revert"/> <menuseparator/> <!-- TODO: bug 650340 - implement printFile <menuitem id="sp-menu-print" label="&printCmd.label;" accesskey="&printCmd.accesskey;" command="sp-cmd-printFile"/> <menuseparator/>
--- a/browser/devtools/scratchpad/test/Makefile.in +++ b/browser/devtools/scratchpad/test/Makefile.in @@ -28,11 +28,12 @@ MOCHITEST_BROWSER_FILES = \ browser_scratchpad_bug_653427_confirm_close.js \ browser_scratchpad_bug684546_reset_undo.js \ browser_scratchpad_bug690552_display_outputs_errors.js \ browser_scratchpad_bug650345_find_ui.js \ browser_scratchpad_bug714942_goto_line_ui.js \ browser_scratchpad_bug_650760_help_key.js \ browser_scratchpad_bug_651942_recent_files.js \ browser_scratchpad_bug756681_display_non_error_exceptions.js \ + browser_scratchpad_bug_751744_revert_to_saved.js \ head.js \ include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_751744_revert_to_saved.js @@ -0,0 +1,137 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let tempScope = {}; +Cu.import("resource://gre/modules/NetUtil.jsm", tempScope); +Cu.import("resource://gre/modules/FileUtils.jsm", tempScope); +let NetUtil = tempScope.NetUtil; +let FileUtils = tempScope.FileUtils; + +// Reference to the Scratchpad object. +let gScratchpad; + +// Reference to the temporary nsIFiles. +let gFile; + +// Temporary file name. +let gFileName = "testFileForBug751744.tmp" + + +// Content for the temporary file. +let gFileContent = "/* this file is already saved */\n" + + "function foo() { alert('bar') }"; +let gLength = gFileContent.length; + +// Reference to the menu entry. +let menu; + +function startTest() +{ + gScratchpad = gScratchpadWindow.Scratchpad; + menu = gScratchpadWindow.document.getElementById("sp-menu-revert"); + createAndLoadTemporaryFile(); +} + +function testAfterSaved() { + // Check if the revert menu is disabled as the file is at saved state. + ok(menu.hasAttribute("disabled"), "The revert menu entry is disabled."); + + // chancging the text in the file + gScratchpad.setText("\nfoo();", gLength, gLength); + // Checking the text got changed + is(gScratchpad.getText(), gFileContent + "\nfoo();", + "The text changed the first time."); + + // Revert menu now should be enabled. + ok(!menu.hasAttribute("disabled"), + "The revert menu entry is enabled after changing text first time"); + + // reverting back to last saved state. + gScratchpad.revertFile(testAfterRevert); +} + +function testAfterRevert() { + // Check if the file's text got reverted + is(gScratchpad.getText(), gFileContent, + "The text reverted back to original text."); + // The revert menu should be disabled again. + ok(menu.hasAttribute("disabled"), + "The revert menu entry is disabled after reverting."); + + // chancging the text in the file again + gScratchpad.setText("\nalert(foo.toSource());", gLength, gLength); + // Saving the file. + gScratchpad.saveFile(testAfterSecondSave); +} + +function testAfterSecondSave() { + // revert menu entry should be disabled. + ok(menu.hasAttribute("disabled"), + "The revert menu entry is disabled after saving."); + + // changing the text. + gScratchpad.setText("\nfoo();", gLength + 23, gLength + 23); + + // revert menu entry should get enabled yet again. + ok(!menu.hasAttribute("disabled"), + "The revert menu entry is enabled after changing text third time"); + + // reverting back to last saved state. + gScratchpad.revertFile(testAfterSecondRevert); +} + +function testAfterSecondRevert() { + // Check if the file's text got reverted + is(gScratchpad.getText(), gFileContent + "\nalert(foo.toSource());", + "The text reverted back to the changed saved text."); + // The revert menu should be disabled again. + ok(menu.hasAttribute("disabled"), + "Revert menu entry is disabled after reverting to changed saved state."); + gFile.remove(false); + gFile = null; + gScratchpad = null; +} + +function createAndLoadTemporaryFile() +{ + // Create a temporary file. + gFile = FileUtils.getFile("TmpD", [gFileName]); + gFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); + + // Write the temporary file. + let fout = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + fout.init(gFile.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20, + 0644, fout.DEFER_OPEN); + + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + let fileContentStream = converter.convertToInputStream(gFileContent); + + NetUtil.asyncCopy(fileContentStream, fout, tempFileSaved); +} + +function tempFileSaved(aStatus) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file was saved successfully"); + + // Import the file into Scratchpad. + gScratchpad.setFilename(gFile.path); + gScratchpad.importFromFile(gFile.QueryInterface(Ci.nsILocalFile), true, + testAfterSaved); +} + +function test() +{ + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(startTest); + }, true); + + content.location = "data:text/html,<p>test reverting to last saved state of" + + " a file </p>"; +}
--- a/browser/devtools/shared/DeveloperToolbar.jsm +++ b/browser/devtools/shared/DeveloperToolbar.jsm @@ -10,16 +10,18 @@ const NS_XHTML = "http://www.w3.org/1999 const WEBCONSOLE_CONTENT_SCRIPT_URL = "chrome://browser/content/devtools/HUDService-content.js"; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource:///modules/devtools/Commands.jsm"); +const Node = Components.interfaces.nsIDOMNode; + XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "gcli", "resource:///modules/devtools/gcli.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands", "resource:///modules/devtools/CmdCmd.jsm"); @@ -133,16 +135,38 @@ DeveloperToolbar.prototype.focus = funct if (this.visible) { this._input.focus(); } else { this.show(true); } }; /** + * Called from browser.xul in response to menu-click or keyboard shortcut to + * toggle the toolbar + */ +DeveloperToolbar.prototype.focusToggle = function DT_focusToggle() +{ + if (this.visible) { + // If we have focus then the active element is the HTML input contained + // inside the xul input element + var active = this._chromeWindow.document.activeElement; + var position = this._input.compareDocumentPosition(active); + if (position & Node.DOCUMENT_POSITION_CONTAINED_BY) { + this.hide(); + } + else { + this._input.focus(); + } + } else { + this.show(true); + } +}; + +/** * Even if the user has not clicked on 'Got it' in the intro, we only show it * once per session. * Warning this is slightly messed up because this.DeveloperToolbar is not the * same as this.DeveloperToolbar when in browser.js context. */ DeveloperToolbar.introShownThisSession = false; /**
--- a/browser/devtools/styleeditor/StyleEditor.jsm +++ b/browser/devtools/styleeditor/StyleEditor.jsm @@ -754,16 +754,97 @@ StyleEditor.prototype = { break; default: this._loadSourceFromCache(this.styleSheet.href); break; } }, /** + * Decode a CSS source string to unicode according to the character set rules + * defined in <http://www.w3.org/TR/CSS2/syndata.html#charset>. + * + * @param string aString + * Source of a CSS stylesheet, loaded from file or cache. + * @param string aChannelCharset + * Charset of the source string if set by the HTTP channel. + * @return string + * The CSS string, in unicode. + */ + _decodeCSSCharset: function SE__decodeCSSCharset(aString, aChannelCharset) + { + // StyleSheet's charset can be specified from multiple sources + + if (aChannelCharset.length > 0) { + // step 1 of syndata.html: charset given in HTTP header. + return this._convertToUnicode(aString, aChannelCharset); + } + + let sheet = this.styleSheet; + if (sheet) { + // Do we have a @charset rule in the stylesheet? + // step 2 of syndata.html (without the BOM check). + if (sheet.cssRules) { + let rules = sheet.cssRules; + if (rules.length + && rules.item(0).type == Ci.nsIDOMCSSRule.CHARSET_RULE) { + return this._convertToUnicode(aString, rules.item(0).encoding); + } + } + + if (sheet.ownerNode) { + // step 3: see <link charset="…"> + let linkCharset = sheet.ownerNode.getAttribute("charset"); + if (linkCharset != null) { + return this._convertToUnicode(aString, linkCharset); + } + } + + // step 4 (1 of 2): charset of referring stylesheet. + let parentSheet = sheet.parentStyleSheet; + if (parentSheet && parentSheet.cssRules && + parentSheet.cssRules[0].type == Ci.nsIDOMCSSRule.CHARSET_RULE) { + return this._convertToUnicode(aString, + parentSheet.cssRules[0].encoding); + } + + // step 4 (2 of 2): charset of referring document. + if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) { + return this._convertToUnicode(aString, + sheet.ownerNode.ownerDocument.characterSet); + } + } + + // step 5: default to utf-8. + return this._convertToUnicode(aString, "UTF-8"); + }, + + /** + * Convert a given string, encoded in a given character set, to unicode. + * @param string aString + * A string. + * @param string aCharset + * A character set. + * @return string + * A unicode string. + */ + _convertToUnicode: function SE__convertToUnicode(aString, aCharset) { + // Decoding primitives. + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Ci.nsIScriptableUnicodeConverter); + + try { + converter.charset = aCharset; + return converter.ConvertToUnicode(aString); + } catch(e) { + return aString; + } + }, + + /** * Load source from a file or file-like resource. * * @param string aHref * URL for the stylesheet. */ _loadSourceFromFile: function SE__loadSourceFromFile(aHref) { try { @@ -785,45 +866,55 @@ StyleEditor.prototype = { * * @param string aHref * URL for the stylesheet. */ _loadSourceFromCache: function SE__loadSourceFromCache(aHref) { let channel = Services.io.newChannel(aHref, null, null); let chunks = []; + let channelCharset = ""; let streamListener = { // nsIStreamListener inherits nsIRequestObserver onStartRequest: function (aRequest, aContext, aStatusCode) { if (!Components.isSuccessCode(aStatusCode)) { return this._signalError(LOAD_ERROR); } }.bind(this), onDataAvailable: function (aRequest, aContext, aStream, aOffset, aCount) { + let channel = aRequest.QueryInterface(Ci.nsIChannel); + if (!channelCharset) { + channelCharset = channel.contentCharset; + } chunks.push(NetUtil.readInputStreamToString(aStream, aCount)); }, onStopRequest: function (aRequest, aContext, aStatusCode) { if (!Components.isSuccessCode(aStatusCode)) { return this._signalError(LOAD_ERROR); } - this._onSourceLoad(chunks.join("")); + this._onSourceLoad(chunks.join(""), channelCharset); }.bind(this) }; channel.loadFlags = channel.LOAD_FROM_CACHE; channel.asyncOpen(streamListener, null); }, /** * Called when source has been loaded. * * @param string aSourceText + * @param string aCharset + * Optional. The character set to use. The default is to detect the + * character set following the standard (see + * <http://www.w3.org/TR/CSS2/syndata.html#charset>). */ - _onSourceLoad: function SE__onSourceLoad(aSourceText) + _onSourceLoad: function SE__onSourceLoad(aSourceText, aCharset) { + aSourceText = this._decodeCSSCharset(aSourceText, aCharset || ""); this._restoreExpando(); this._state.text = prettifyCSS(aSourceText); this._loaded = true; this._triggerAction("Load"); }, /** * Create a new style sheet and append it to the content document.
--- a/browser/devtools/styleeditor/test/browser_styleeditor_init.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js @@ -66,16 +66,20 @@ function testEditorAdded(aChrome, aEdito if (gEditorAddedCount == 2) { finish(); } } function testFirstStyleSheetEditor(aChrome, aEditor) { + // Note: the html <link> contains charset="UTF-8". + ok(aEditor._state.text.indexOf("\u263a") >= 0, + "stylesheet is unicode-aware."); + //testing TESTCASE's simple.css stylesheet is(aEditor.styleSheetIndex, 0, "first stylesheet is at index 0"); is(aEditor, aChrome.editors[0], "first stylesheet corresponds to StyleEditorChrome.editors[0]"); ok(!aEditor.hasFlag("inline"),
--- a/browser/devtools/styleeditor/test/simple.css +++ b/browser/devtools/styleeditor/test/simple.css @@ -1,8 +1,9 @@ /* vim: set ts=2 et sw=2 tw=80: */ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ +/* ☺ */ body { margin: 0; }
--- a/browser/devtools/styleeditor/test/simple.html +++ b/browser/devtools/styleeditor/test/simple.html @@ -1,13 +1,13 @@ <!doctype html> <html> <head> <title>simple testcase</title> - <link rel="stylesheet" type="text/css" media="screen" href="simple.css"/> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="simple.css"/> <style type="text/css"> body { background: white; } div { font-size: 4em; }
--- a/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js @@ -2,17 +2,17 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ let win; let doc; let contentWindow; let tempScope = {}; -Cu.import("resource:///modules/Services.jsm", tempScope); +Cu.import("resource://gre/modules/Services.jsm", tempScope); let Services = tempScope.Services; function createDocument() { doc.body.innerHTML = '<style type="text/css"> ' + 'html { color: #000000; } ' + 'span { font-variant: small-caps; color: #000000; } ' + '.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
--- a/browser/devtools/styleinspector/test/head.js +++ b/browser/devtools/styleinspector/test/head.js @@ -1,17 +1,17 @@ /* vim:set ts=2 sw=2 sts=2 et: */ /* 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 tempScope = {}; Cu.import("resource:///modules/devtools/CssLogic.jsm", tempScope); Cu.import("resource:///modules/devtools/CssHtmlTree.jsm", tempScope); -Cu.import("resource://gre/modules/HUDService.jsm", tempScope); +Cu.import("resource:///modules/HUDService.jsm", tempScope); let HUDService = tempScope.HUDService; let ConsoleUtils = tempScope.ConsoleUtils; let CssLogic = tempScope.CssLogic; let CssHtmlTree = tempScope.CssHtmlTree; function log(aMsg) { dump("*** WebConsoleTest: " + aMsg + "\n");
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js @@ -43,22 +43,24 @@ function testMenuFilterButton(aCategory) } menuItem = menuItem.nextSibling; } // Turn all the filters on; make sure the button gets checked. menuItem = firstMenuItem; let prefKey; while (menuItem) { - prefKey = menuItem.getAttribute("prefKey"); - chooseMenuItem(menuItem); - ok(isChecked(menuItem), "menu item " + prefKey + " for category " + - aCategory + " is checked after clicking it"); - ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " + - "on after clicking the appropriate menu item"); + if (menuItem.hasAttribute("prefKey")) { + prefKey = menuItem.getAttribute("prefKey"); + chooseMenuItem(menuItem); + ok(isChecked(menuItem), "menu item " + prefKey + " for category " + + aCategory + " is checked after clicking it"); + ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " + + "on after clicking the appropriate menu item"); + } menuItem = menuItem.nextSibling; } ok(isChecked(button), "the button for category " + aCategory + " is " + "checked after turning on all its menu items"); // Turn one filter off; make sure the button is still checked. prefKey = firstMenuItem.getAttribute("prefKey"); chooseMenuItem(firstMenuItem); @@ -98,21 +100,23 @@ function testMenuFilterButton(aCategory) // Turn all the filters on by clicking the main part of the button. clickButton(subbutton); ok(isChecked(button), "the button for category " + aCategory + " is " + "checked after clicking its main part"); menuItem = firstMenuItem; while (menuItem) { - let prefKey = menuItem.getAttribute("prefKey"); - ok(isChecked(menuItem), "menu item " + prefKey + " for category " + - aCategory + " is checked after clicking the button"); - ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " + - "on after clicking the button"); + if (menuItem.hasAttribute("prefKey")) { + let prefKey = menuItem.getAttribute("prefKey"); + ok(isChecked(menuItem), "menu item " + prefKey + " for category " + + aCategory + " is checked after clicking the button"); + ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " + + "on after clicking the button"); + } menuItem = menuItem.nextSibling; } // Uncheck the main button by unchecking all the filters menuItem = firstMenuItem; while (menuItem) { chooseMenuItem(menuItem); menuItem = menuItem.nextSibling;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js @@ -3,17 +3,17 @@ * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ * * Contributor(s): * Mihai Șucan <mihai.sucan@gmail.com> * * ***** END LICENSE BLOCK ***** */ -let menuitems = [], menupopups = [], huds = [], tabs = []; +let menuitems = [], menupopups = [], huds = [], tabs = [], runCount = 0; function test() { // open tab 1 addTab("data:text/html;charset=utf-8,Web Console test for bug 602572: log bodies checkbox. tab 1"); tabs.push(tab); browser.addEventListener("load", function onLoad1(aEvent) { @@ -38,23 +38,28 @@ function test() }, true); }); }, true); } function startTest() { // Find the relevant elements in the Web Console of tab 2. - let win2 = tabs[1].linkedBrowser.contentWindow; + let win2 = tabs[runCount*2 + 1].linkedBrowser.contentWindow; let hudId2 = HUDService.getHudIdByWindow(win2); huds[1] = HUDService.hudReferences[hudId2]; HUDService.disableAnimation(hudId2); - menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodies"); - menupopups[1] = huds[1].ui.rootElement.querySelector("menupopup"); + if (runCount == 0) { + menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodies"); + } + else { + menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodiesContextMenu"); + } + menupopups[1] = menuitems[1].parentNode; // Open the context menu from tab 2. menupopups[1].addEventListener("popupshown", onpopupshown2, false); executeSoon(function() { menupopups[1].openPopup(); }); } @@ -90,33 +95,33 @@ function onpopupshown2b(aEvent) { menupopups[1].removeEventListener(aEvent.type, onpopupshown2b, false); is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked"); menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) { menupopups[1].removeEventListener(aEvent.type, _onhidden, false); // Switch to tab 1 and open the Web Console context menu from there. - gBrowser.selectedTab = tabs[0]; + gBrowser.selectedTab = tabs[runCount*2]; waitForFocus(function() { // Find the relevant elements in the Web Console of tab 1. - let win1 = tabs[0].linkedBrowser.contentWindow; + let win1 = tabs[runCount*2].linkedBrowser.contentWindow; let hudId1 = HUDService.getHudIdByWindow(win1); huds[0] = HUDService.hudReferences[hudId1]; HUDService.disableAnimation(hudId1); info("iframe1 height " + huds[0].iframe.clientHeight); info("iframe1 root height " + huds[0].ui.rootElement.clientHeight); menuitems[0] = huds[0].ui.rootElement.querySelector("#saveBodies"); menupopups[0] = huds[0].ui.rootElement.querySelector("menupopup"); menupopups[0].addEventListener("popupshown", onpopupshown1, false); menupopups[0].openPopup(); - }, tabs[0].linkedBrowser.contentWindow); + }, tabs[runCount*2].linkedBrowser.contentWindow); }, false); executeSoon(function() { menupopups[1].hidePopup(); }); } function onpopupshown1(aEvent) @@ -129,42 +134,55 @@ function onpopupshown1(aEvent) // Enable body logging for tab 1 as well. huds[0].ui.saveRequestAndResponseBodies = true; // Close the menu, and switch back to tab 2. menupopups[0].addEventListener("popuphidden", function _onhidden(aEvent) { menupopups[0].removeEventListener(aEvent.type, _onhidden, false); - gBrowser.selectedTab = tabs[1]; + gBrowser.selectedTab = tabs[runCount*2 + 1]; waitForFocus(function() { // Reopen the context menu from tab 2. menupopups[1].addEventListener("popupshown", onpopupshown2c, false); menupopups[1].openPopup(); - }, tabs[1].linkedBrowser.contentWindow); + }, tabs[runCount*2 + 1].linkedBrowser.contentWindow); }, false); executeSoon(function() { menupopups[0].hidePopup(); }); } function onpopupshown2c(aEvent) { menupopups[1].removeEventListener(aEvent.type, onpopupshown2c, false); is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked"); menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) { menupopups[1].removeEventListener(aEvent.type, _onhidden, false); - // Done! - huds = menuitems = menupopups = tabs = null; + // Done if on second run closeConsole(gBrowser.selectedTab, function() { - gBrowser.removeCurrentTab(); - executeSoon(finishTest); + if (runCount == 0) { + runCount++; + executeSoon(test); + } + else { + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = tabs[2]; + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = tabs[1]; + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = tabs[0]; + gBrowser.removeCurrentTab(); + huds = menuitems = menupopups = tabs = null; + executeSoon(finishTest); + } }); + }, false); executeSoon(function() { menupopups[1].hidePopup(); }); }
--- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -338,21 +338,40 @@ WebConsoleFrame.prototype = { this.inputNode.style.fontSize = fontSize + "px"; } let saveBodies = doc.getElementById("saveBodies"); saveBodies.addEventListener("command", function() { this.saveRequestAndResponseBodies = !this.saveRequestAndResponseBodies; }.bind(this)); saveBodies.setAttribute("checked", this.saveRequestAndResponseBodies); - - let contextMenuId = this.outputNode.getAttribute("context"); - let contextMenu = doc.getElementById(contextMenuId); - contextMenu.addEventListener("popupshowing", function() { + saveBodies.disabled = !this.getFilterState("networkinfo") && + !this.getFilterState("network"); + + saveBodies.parentNode.addEventListener("popupshowing", function() { saveBodies.setAttribute("checked", this.saveRequestAndResponseBodies); + saveBodies.disabled = !this.getFilterState("networkinfo") && + !this.getFilterState("network"); + }.bind(this)); + + // Remove this part when context menu entry is removed. + let saveBodiesContextMenu = doc.getElementById("saveBodiesContextMenu"); + saveBodiesContextMenu.addEventListener("command", function() { + this.saveRequestAndResponseBodies = !this.saveRequestAndResponseBodies; + }.bind(this)); + saveBodiesContextMenu.setAttribute("checked", + this.saveRequestAndResponseBodies); + saveBodiesContextMenu.disabled = !this.getFilterState("networkinfo") && + !this.getFilterState("network"); + + saveBodiesContextMenu.parentNode.addEventListener("popupshowing", function() { + saveBodiesContextMenu.setAttribute("checked", + this.saveRequestAndResponseBodies); + saveBodiesContextMenu.disabled = !this.getFilterState("networkinfo") && + !this.getFilterState("network"); }.bind(this)); this.closeButton = doc.getElementById("webconsole-close-button"); this.closeButton.addEventListener("command", this.owner.onCloseButton.bind(this.owner)); let clearButton = doc.getElementsByClassName("webconsole-clear-console-button")[0]; clearButton.addEventListener("command", @@ -681,23 +700,32 @@ WebConsoleFrame.prototype = { case "menuitem": { let state = target.getAttribute("checked") !== "true"; target.setAttribute("checked", state); let prefKey = target.getAttribute("prefKey"); this.setFilterState(prefKey, state); + // Disable the log response and request body if network logging is off. + if (prefKey == "networkinfo" || prefKey == "network") { + let checkState = !this.getFilterState("networkinfo") && + !this.getFilterState("network"); + this.document.getElementById("saveBodies").disabled = checkState; + this.document.getElementById("saveBodiesContextMenu").disabled = checkState; + } + // Adjust the state of the button appropriately. let menuPopup = target.parentNode; let someChecked = false; let menuItem = menuPopup.firstChild; while (menuItem) { - if (menuItem.getAttribute("checked") === "true") { + if (menuItem.hasAttribute("prefKey") && + menuItem.getAttribute("checked") === "true") { someChecked = true; break; } menuItem = menuItem.nextSibling; } let toolbarButton = menuPopup.parentNode; toolbarButton.setAttribute("checked", someChecked); break;
--- a/browser/devtools/webconsole/webconsole.xul +++ b/browser/devtools/webconsole/webconsole.xul @@ -34,17 +34,17 @@ <key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/> <key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/> <key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/> </keyset> <keyset id="editMenuKeys"/> <popupset id="mainPopupSet"> <menupopup id="output-contextmenu"> - <menuitem id="saveBodies" type="checkbox" label="&saveBodies.label;" + <menuitem id="saveBodiesContextMenu" type="checkbox" label="&saveBodies.label;" accesskey="&saveBodies.accesskey;"/> <menuitem id="menu_copy"/> <menuitem id="menu_selectAll"/> </menupopup> </popupset> <vbox class="hud-outer-wrapper" flex="1"> <vbox class="hud-console-wrapper" flex="1"> @@ -58,16 +58,19 @@ <toolbarbutton label="&btnPageNet.label;" type="menu-button" category="net" class="devtools-toolbarbutton webconsole-filter-button" tooltiptext="&btnPageNet.tooltip;"> <menupopup> <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false" prefKey="network"/> <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false" prefKey="networkinfo"/> + <menuseparator id="saveBodiesSeparator" /> + <menuitem id="saveBodies" type="checkbox" label="&saveBodies.label;" + accesskey="&saveBodies.accesskey;"/> </menupopup> </toolbarbutton> <toolbarbutton label="&btnPageCSS.label;" type="menu-button" category="css" class="devtools-toolbarbutton webconsole-filter-button" tooltiptext="&btnPageCSS.tooltip;"> <menupopup> <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false" prefKey="csserror"/>
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd +++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd @@ -31,16 +31,19 @@ <!ENTITY openFileCmd.label "Open File…"> <!ENTITY openFileCmd.accesskey "O"> <!ENTITY openFileCmd.commandkey "o"> <!ENTITY openRecentMenu.label "Open Recent"> <!ENTITY openRecentMenu.accesskey "R"> +<!ENTITY revertCmd.label "Revert…"> +<!ENTITY revertCmd.accesskey "t"> + <!ENTITY saveFileCmd.label "Save"> <!ENTITY saveFileCmd.accesskey "S"> <!ENTITY saveFileCmd.commandkey "s"> <!ENTITY saveFileAsCmd.label "Save As…"> <!ENTITY saveFileAsCmd.accesskey "A"> <!ENTITY closeCmd.label "Close">
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties +++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties @@ -49,16 +49,24 @@ saveFile.failed=The file save operation # LOCALIZATION NOTE (confirmClose): This is message in the prompt dialog when # you try to close a scratchpad with unsaved changes. confirmClose=Do you want to save the changes you made to this scratchpad? # LOCALIZATION NOTE (confirmClose.title): This is title of the prompt dialog when # you try to close a scratchpad with unsaved changes. confirmClose.title=Unsaved Changes +# LOCALIZATION NOTE (confirmRevert): This is message in the prompt dialog when +# you try to revert unsaved content of scratchpad. +confirmRevert=Do you want to revert the changes you made to this scratchpad? + +# LOCALIZATION NOTE (confirmRevert.title): This is title of the prompt dialog when +# you try to revert unsaved content of scratchpad. +confirmRevert.title=Revert Changes + # LOCALIZATION NOTE (scratchpadIntro): This is a multi-line comment explaining # how to use the Scratchpad. Note that this should be a valid JavaScript # comment inside /* and */. scratchpadIntro1=/*\n * This is a JavaScript Scratchpad.\n *\n * Enter some JavaScript, then Right Click or choose from the Execute Menu:\n * 1. Run to evaluate the selected text (%1$S),\n * 2. Inspect to bring up an Object Inspector on the result (%2$S), or,\n * 3. Display to insert the result in a comment after the selection. (%3$S)\n */\n\n # LOCALIZATION NOTE (notification.browserContext): This is the message displayed # over the top of the editor when the user has switched to browser context. browserContext.notification=This scratchpad executes in the Browser context.
--- a/browser/themes/gnomestripe/browser.css +++ b/browser/themes/gnomestripe/browser.css @@ -2483,18 +2483,17 @@ html|*#gcli-output-frame { .gcli-in-closebrace { color: hsl(0,0%,80%); } /* Responsive Mode */ .browserContainer[responsivemode] { - background: #2a3643 url("chrome://browser/skin/devtools/responsive-background.png"); - box-shadow: 0 0 7px black inset; + background: #222 url("chrome://browser/skin/devtools/responsive-background.png"); padding: 0 20px 20px 20px; } .browserStack[responsivemode] { box-shadow: 0 0 7px black; } .devtools-responsiveui-toolbar { @@ -2709,16 +2708,20 @@ html|*#gcli-output-frame { cursor: pointer; min-width: 0; margin: 0 6px; } #social-statusarea-username:hover { text-decoration: underline; } +.social-panel > .panel-arrowcontainer > .panel-arrowcontent { + padding: 0; +} + .chat-status-icon { max-height: 16px; max-width: 16px; padding: 0; } .chat-toolbarbutton { -moz-appearance: none; @@ -2728,87 +2731,75 @@ html|*#gcli-output-frame { background: none; } .chat-toolbarbutton > .toolbarbutton-text { display: none; } .chat-close-button { - list-style-image: url("chrome://global/skin/icons/close.png"); - -moz-image-region: rect(0, 16px, 16px, 0); -} - -.chat-close-button:hover { - -moz-image-region: rect(0, 32px, 16px, 16px); + list-style-image: url('chrome://browser/skin/social/chat-close.png'); + -moz-image-region: rect(0, 14px, 14px, 0); } .chat-close-button:hover:active { - -moz-image-region: rect(0, 48px, 16px, 32px); -} - - -.chat-toggle-button { - /* XXX get a real image for this */ - list-style-image: url("chrome://global/skin/icons/checkbox.png"); - -moz-image-region: rect(0, 32px, 16px, 16px); -} - -.chat-toggle-button:hover { - -moz-image-region: rect(0, 16px, 16px, 0); -} - -.chat-toggle-button[minimized="true"] { - -moz-image-region: rect(0, 16px, 16px, 0); -} - -.chat-toggle-button[minimized="true"]:hover { - -moz-image-region: rect(0, 32px, 16px, 16px); + -moz-image-region: rect(14px, 14px, 28px, 0); +} + +.chat-close-button:hover { + -moz-image-region: rect(28px, 14px, 42px, 0); } .chat-title { font-weight: bold; color: -moz-dialogtext; + cursor: inherit; } .chat-titlebar { - background-image: linear-gradient(white, #ddd); + background-color: #d9d9d9; height: 20px; min-height: 20px; width: 100%; margin: 0; padding: 2px; border: none; border-bottom: 1px solid gray; + cursor: pointer; } .chat-titlebar[minimized="true"] { border-bottom: none; } +.chat-titlebar[selected] { + background-color: #f0f0f0; +} + .chat-frame { padding: 0; margin: 0; overflow: hidden; } .chatbar-button { - /* XXX get a real image for this */ + background-color: #d9d9d9; list-style-image: url("chrome://browser/skin/social/social.png"); border: none; margin: 0; padding: 2px; height: 21px; width: 21px; border-top: 1px solid gray; -moz-border-end: 1px solid gray; } .chatbar-button[open="true"], .chatbar-button:active:hover { + background-color: #f0f0f0; box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); } .chatbar-button > .toolbarbutton-text, .chatbar-button > .toolbarbutton-menu-dropmarker { display: none; }
--- a/browser/themes/gnomestripe/jar.mn +++ b/browser/themes/gnomestripe/jar.mn @@ -80,16 +80,18 @@ browser.jar: skin/classic/browser/preferences/Options.png (preferences/Options.png) #ifdef MOZ_SERVICES_SYNC skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png) #endif * skin/classic/browser/preferences/preferences.css (preferences/preferences.css) skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) + skin/classic/browser/social/social.png (social/social.png) + skin/classic/browser/social/chat-close.png (social/chat-close.png) skin/classic/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png) skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png) skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png) skin/classic/browser/tabbrowser/tab.png (tabbrowser/tab.png) skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png) skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png) skin/classic/browser/tabview/edit-light.png (tabview/edit-light.png) skin/classic/browser/tabview/search.png (tabview/search.png)
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cc773cbc1c45756ba57d191fa2c41afedd420287 GIT binary patch literal 405 zc$@*10c!q<P)<h;3K|Lk000e1NJLTq000gE001fo1^@s6gLP{000048Nkl<ZNQuSL zv2KGf5C-6T6~6+{<Iy`y9)Ju{mMmdO=15ggQ&sI7bgMhFm<hZC@`l86oh%vqOlVqB zBrnFF?Emfn{_RsraUlekQgZ*Fl#*{Yn_wRR0E7@6061f8f~Az4F~$LaXIU0v_khLj zUXmodEKB-UA1^`_UW_Q{XTFP)BndJvLS<QUs$c=M?eH_XnCzueil=Ex7Gib)*88fe z!bjHcVO`f~j6tn+a7b&7UDwgj7=wt2<2X*R)*8ogL`1~4ZA0iDu-M%jh5?(Vp)>k8 zhREW$IEEHMKl9zE4#N;+CLvV_s$kJ}_?cYn_Hr@+eczLXm>q!iX7%Iq3m;j3125Mi zda!tTrQzjT;C8pi!!Zxg;h5ugx5wMtJAI=);9|R%pU(3cT^vKv;#d$viy)PG_Nn>l zOlF=fj{CeA4%$Iwve@nA<i@Ax+s_AJwORcJc}JrE9N5!t00000NkvXXu0mjf@*cTh
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..14916fb074c79b18c732f41661110027983c5d03 GIT binary patch literal 1654 zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKV3x~tb`J1#c2)=|%1_J8No8QD zU~CP!pM5z%<ln8-xf>pKTcrzf+GR}4O9?J<p0h1GeAn5iS7zH4J!UV<jhS^!<!#*t z_M6@LR!7S}vfKS+=egOOUp<NUm`LJ}H62Pm-;_4bn{DE&9?ZP+`|kTc-&v|Ze-gLg zXJOQ35q*A<&|v?ghyEOSB69x|W1Nh$_MZ&j?lv(mHFpR8%bP0SZ~n|DcPyvmUH<*m z|B|oHn#v>iK}KQU6w@P04<i?3##|{8esHbx{ai!SLuY-8BbKvp%BNf8CpSLq=~bSV zW@73vi}MisQ70?!9S#;VG9P^Ay>YHl<cYf4%1L_NHmhBO^q;1<ZsPg&LB6>8Q~ZV% zQ(n$q`&^(mcb25z#hR{r@|Lv)J<k}f9<+~_*z34sZb5|E<He?RPG?ot399XV{;ni* z#@p0c(<3#7RAp55B!#Rq&dGen&D=Tb+2yxi0w!&kZ?fOSIP&x3w*gaGUK<|$`pw(z z%dWlGZ%>@e{@B!Rx#qQchktv&e=X{IS~=;%>n(D<SHDD0vI@I>`^nj5%7>4X2ih}x zI5qXjq-Z>t=){vaEBVN(A3kD|N?uDYWtN0gZnC&$tucApTN(F?lkt2z;>5m2Z`$$g z!S8u@Rj>E$5p6xXeesRJw_6)u?%E_gOS--^ap~f`hvqw<&X&$y&CL<`uj}bXkGRxR z{!M|`OU<552%i3@l>5BVly|CSLJ>T>)_jQjp_Rks-zt7+`u-~?&fQxpc)Fob$8Pn! z;Iqfxs9y59^g3n7{;sc1$0UAEFm#TZyHCY;PM77UTLIkaY~@X_pFN7vzs^2y_nFv- zzjku0)hkl&J0$+qwMOy7KI46M@!Rt|5|`iJcRA(tV#BD{yDv}NwP<72l>Xc5-)#F< z2}^F;xNl=!7>D+jMafxSn@ju|m{oKArhjIg{9dqyyQX8EZfF1Mr0@y;+FbcFdE%z? zc&_)jskgzp|1I;j;A^qZGUJOaKWi1dl>U48wVrt>Tb$&9i^Y243ye#jaBirH(|Ij= zTi#~g_J)bpx0Bb}<|gjywhLQ*yF@Qqj&V2t>Z|k5uey5o`ICC?ip77}Rw{E{JG3y~ z2$*3w3p^r=85p>QL70(Y)*J~21}3@8kcg59UmvUF{9L`nl>DSry^7od1`x2RumUo3 zQ%e#RDspr3imfVamB1>jfNYSkzLEl1NlCV?QiN}Sf^&XRs)C80iJpOy9hZWFf=y9M znpKdC8&o@xXRDM^Qc_^0uU}qXu2*iXmtT~wZ)j<0sc&GUZ)BtkRH0j3nOBlnp_^B% z3^4>|j!SBBa#3bMNoIbY0*IfOlwVq6tE2=~0|5|=Qj+2J6qFX_fNe=h)=$kz%}vcK zDb_dCGt`G04l+L_*{ZlSDJwO(#16$cu*FV^>H5fu^)Xf3=%cxiT$cj_6Yli9VrYB- zV@AIsu>k0715{@NRa-e1r6!i-7lq{K=h!)dlq-1VCZ?wbr6#6S+UTRJ!0TR!C@{Y4 zxPUrgac{>JuxDWiFgaZDba4!^5ZpS+k*nE2!1eg0&o^3yHs09FE&eET(-v_d@p%gu zU164NUFp0s<xxlLrACKOm6e_+Dmpm!U#ZM54iq^P6FOCW<Ghk)oAZXJjgFc7GMC2Y zt32YpWYStWZ)SthN|%EszCzk*{{<zIk6d2zL8Ue7wYBFP$C*dEnpkztS~S^e{EjN{ z7TV&!+%mPVq2=i^{&j^tUYhN-`oH(JyWidvm(ZlqWx&H!_*uP%kGUjvyVni#xElGs zss@SOGY-79lfE;1v${^ePIjxGr*<;6TgUBVI3k(zKwqXI1(e=BUHx3vIVCg!0Cn%A AApigX
--- a/browser/themes/pinstripe/browser.css +++ b/browser/themes/pinstripe/browser.css @@ -3201,18 +3201,17 @@ html|*#gcli-output-frame { .gcli-in-closebrace { color: hsl(0,0%,80%); } /* Responsive Mode */ .browserContainer[responsivemode] { - background: #2a3643 url("chrome://browser/skin/devtools/responsive-background.png"); - box-shadow: 0 0 7px black inset; + background: #222 url("chrome://browser/skin/devtools/responsive-background.png"); padding: 0 20px 20px 20px; } .browserStack[responsivemode] { box-shadow: 0 0 7px black; } .devtools-responsiveui-toolbar { @@ -3393,16 +3392,25 @@ html|*#gcli-output-frame { min-width: 0; margin: 0 6px; } #social-statusarea-username:hover { text-decoration: underline; } +.social-panel > .panel-arrowcontainer > .panel-arrowcontent { + padding: 0; +} + +#social-notification-box, +.social-panel-frame { + border-radius: inherit; +} + /* === end of social toolbar provider menu === */ .chat-status-icon { max-height: 16px; max-width: 16px; padding: 0; } @@ -3414,88 +3422,75 @@ html|*#gcli-output-frame { background: none; } .chat-toolbarbutton > .toolbarbutton-text { display: none; } .chat-close-button { - list-style-image: url("chrome://global/skin/icons/close.png"); - -moz-image-region: rect(0, 16px, 16px, 0); -} - -.chat-close-button:hover { - -moz-image-region: rect(0, 32px, 16px, 16px); + list-style-image: url('chrome://browser/skin/social/chat-close.png'); + -moz-image-region: rect(0, 14px, 14px, 0); } .chat-close-button:hover:active { - -moz-image-region: rect(0, 48px, 16px, 32px); -} - - -.chat-toggle-button { - /* XXX get a real image for this */ - list-style-image: url("chrome://global/skin/icons/checkbox.png"); - -moz-image-region: rect(0, 32px, 16px, 16px); -} - -.chat-toggle-button:hover { - -moz-image-region: rect(0, 16px, 16px, 0); -} - -.chat-toggle-button[minimized="true"] { - -moz-image-region: rect(0, 16px, 16px, 0); -} - -.chat-toggle-button[minimized="true"]:hover { - -moz-image-region: rect(0, 32px, 16px, 16px); + -moz-image-region: rect(14px, 14px, 28px, 0); +} + +.chat-close-button:hover { + -moz-image-region: rect(28px, 14px, 42px, 0); } .chat-title { font-weight: bold; color: -moz-dialogtext; + cursor: inherit; } .chat-titlebar { - background-image: linear-gradient(white, #ddd); + background-color: #d9d9d9; height: 20px; min-height: 20px; width: 100%; margin: 0; padding: 2px; border: none; border-bottom: 1px solid #404040; + cursor: pointer; } .chat-titlebar[minimized="true"] { border-bottom: none; } +.chat-titlebar[selected] { + background-color: #f0f0f0; +} + .chat-frame { padding: 0; margin: 0; overflow: hidden; } .chatbar-button { - /* XXX get a real image for this */ - background-image: linear-gradient(white, #ddd); + background-color: #d9d9d9; list-style-image: url("chrome://browser/skin/social/social.png"); border: none; margin: 0; padding: 2px; height: 21px; width: 21px; border-top: 1px solid #404040; -moz-border-end: 1px solid #404040; } .chatbar-button[open="true"], .chatbar-button:active:hover { + background-color: #f0f0f0; box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); } .chatbar-button > .toolbarbutton-text, .chatbar-button > .toolbarbutton-menu-dropmarker { display: none; }
--- a/browser/themes/pinstripe/jar.mn +++ b/browser/themes/pinstripe/jar.mn @@ -103,16 +103,17 @@ browser.jar: skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png) #endif skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png) * skin/classic/browser/preferences/preferences.css (preferences/preferences.css) skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/browser/social/social.png (social/social.png) + skin/classic/browser/social/chat-close.png (social/chat-close.png) skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png (tabbrowser/alltabs-box-bkgnd-icon.png) skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png) skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png) skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png) skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png) skin/classic/browser/tabbrowser/tab-arrow-right.png (tabbrowser/tab-arrow-right.png) skin/classic/browser/tabbrowser/tabbar-bottom-bg-active.png (tabbrowser/tabbar-bottom-bg-active.png) skin/classic/browser/tabbrowser/tabbar-bottom-bg-inactive.png (tabbrowser/tabbar-bottom-bg-inactive.png)
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cc773cbc1c45756ba57d191fa2c41afedd420287 GIT binary patch literal 405 zc$@*10c!q<P)<h;3K|Lk000e1NJLTq000gE001fo1^@s6gLP{000048Nkl<ZNQuSL zv2KGf5C-6T6~6+{<Iy`y9)Ju{mMmdO=15ggQ&sI7bgMhFm<hZC@`l86oh%vqOlVqB zBrnFF?Emfn{_RsraUlekQgZ*Fl#*{Yn_wRR0E7@6061f8f~Az4F~$LaXIU0v_khLj zUXmodEKB-UA1^`_UW_Q{XTFP)BndJvLS<QUs$c=M?eH_XnCzueil=Ex7Gib)*88fe z!bjHcVO`f~j6tn+a7b&7UDwgj7=wt2<2X*R)*8ogL`1~4ZA0iDu-M%jh5?(Vp)>k8 zhREW$IEEHMKl9zE4#N;+CLvV_s$kJ}_?cYn_Hr@+eczLXm>q!iX7%Iq3m;j3125Mi zda!tTrQzjT;C8pi!!Zxg;h5ugx5wMtJAI=);9|R%pU(3cT^vKv;#d$viy)PG_Nn>l zOlF=fj{CeA4%$Iwve@nA<i@Ax+s_AJwORcJc}JrE9N5!t00000NkvXXu0mjf@*cTh
--- a/browser/themes/winstripe/browser.css +++ b/browser/themes/winstripe/browser.css @@ -3163,18 +3163,17 @@ html|*#gcli-output-frame { .gcli-in-closebrace { color: hsl(0,0%,80%); } /* Responsive Mode */ .browserContainer[responsivemode] { - background: #2a3643 url("chrome://browser/skin/devtools/responsive-background.png"); - box-shadow: 0 0 7px black inset; + background: #222 url("chrome://browser/skin/devtools/responsive-background.png"); padding: 0 20px 20px 20px; } .browserStack[responsivemode] { box-shadow: 0 0 7px black; } .devtools-responsiveui-toolbar { @@ -3412,16 +3411,25 @@ html|*#gcli-output-frame { cursor: pointer; min-width: 0; margin: 0 6px; } #social-statusarea-username:hover { text-decoration: underline; } +.social-panel > .panel-arrowcontainer > .panel-arrowcontent { + padding: 0; +} + +#social-notification-box, +.social-panel-frame { + border-radius: inherit; +} + .chat-status-icon { max-height: 16px; max-width: 16px; padding: 0; } .chat-toolbarbutton { -moz-appearance: none; @@ -3431,77 +3439,65 @@ html|*#gcli-output-frame { background: none; } .chat-toolbarbutton > .toolbarbutton-text { display: none; } .chat-close-button { - list-style-image: url("chrome://global/skin/icons/Close.gif"); - padding: 2px 4px; -} - -.chat-close-button:hover { - box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); + list-style-image: url('chrome://browser/skin/social/chat-close.png'); + -moz-image-region: rect(0, 14px, 14px, 0); } .chat-close-button:hover:active { - box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); -} - - -.chat-toggle-button { - /* XXX get a real image for this */ - list-style-image: url("chrome://global/skin/icons/expand.png"); -} - -.chat-toggle-button:hover { - box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); -} - -.chat-toggle-button[minimized="true"] { - list-style-image: url("chrome://global/skin/icons/collapse.png"); -} - -.chat-toggle-button[minimized="true"]:hover { - box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); + -moz-image-region: rect(14px, 14px, 28px, 0); +} + +.chat-close-button:hover { + -moz-image-region: rect(28px, 14px, 42px, 0); } .chat-title { font-weight: bold; color: -moz-dialogtext; + cursor: inherit; } .chat-titlebar { - background-image: linear-gradient(white, #ddd); + background-color: #c4cfde; height: 20px; min-height: 20px; width: 100%; margin: 0; padding: 2px; border: none; border-bottom: 1px solid gray; + cursor: pointer; } .chat-titlebar[minimized="true"] { border-bottom: none; } +.chat-titlebar[selected] { + background-color: #dae3f0; +} + .chat-frame { padding: 0; margin: 0; overflow: hidden; } .chatbar-button { /* XXX get a real image for this */ -moz-appearance: none; list-style-image: url("chrome://browser/skin/social/social.png"); - background-image: linear-gradient(white, #ddd); + background-color: #c4cfde; border: none; margin: 0; padding: 2px; height: 21px; width: 21px; border-top: 1px solid gray; -moz-border-end: 1px solid gray; } @@ -3510,16 +3506,17 @@ html|*#gcli-output-frame { max-height: 16px; max-width: 16px; padding: 2px; } .chatbar-button[open="true"], .chatbar-button:hover, .chatbar-button:active:hover { + background-color: #dae3f0; box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2); } .chatbar-button > .toolbarbutton-text, .chatbar-button > .toolbarbutton-menu-dropmarker { display: none; }
--- a/browser/themes/winstripe/jar.mn +++ b/browser/themes/winstripe/jar.mn @@ -100,16 +100,17 @@ browser.jar: skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png) #endif skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png) * skin/classic/browser/preferences/preferences.css (preferences/preferences.css) skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/browser/social/social.png (social/social.png) + skin/classic/browser/social/chat-close.png (social/chat-close.png) skin/classic/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png) skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png) skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png) skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png) skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png) skin/classic/browser/tabbrowser/tab.png (tabbrowser/tab.png) skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png) skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png) @@ -302,16 +303,17 @@ browser.jar: skin/classic/aero/browser/preferences/Options-sync.png (preferences/Options-sync.png) #endif skin/classic/aero/browser/preferences/saveFile.png (preferences/saveFile-aero.png) * skin/classic/aero/browser/preferences/preferences.css (preferences/preferences.css) skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) skin/classic/aero/browser/preferences/applications.css (preferences/applications.css) skin/classic/aero/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/aero/browser/social/social.png (social/social.png) + skin/classic/aero/browser/social/chat-close.png (social/chat-close.png) skin/classic/aero/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png) skin/classic/aero/browser/tabbrowser/newtab.png (tabbrowser/newtab.png) skin/classic/aero/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png) skin/classic/aero/browser/tabbrowser/connecting.png (tabbrowser/connecting.png) skin/classic/aero/browser/tabbrowser/loading.png (tabbrowser/loading.png) skin/classic/aero/browser/tabbrowser/tab.png (tabbrowser/tab.png) skin/classic/aero/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png) skin/classic/aero/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5260f1019668f8c9c6c64f1ec61f5346609cc5c3 GIT binary patch literal 419 zc$@*F0bKrxP)<h;3K|Lk000e1NJLTq000gE001fo1^@s6gLP{00004MNkl<ZNQuSL zu}T9$5C-6Rm3@YLopiY_fhz{C2x$(|q>)%C5>T+w7w{1jwowljlx+kthf#uPLdcP3 ztQPimXD?@z^I)63{n-6y2H@X5tu@ARj8aN;|4At^O;c|l0081RMgTw|L<LJJQ3!zm zfLWIL*gjyey%$9hDy8^aUEBz9xH00On|UXSqR7kK2q~qIE0_mOJKRh*W_zi%#w1DD zLevgGd*Ai_6YaBp4{sk{Xueodt_$x_t_zyIee!U=SP~J@rrcI=t_#|fTOuNQe*N&F zeZXLQZ?!Jz?s3Lvba4o=#j$Y+HG*#Doljk@OD{7Exk7LS3%0|}WMjLRl>sQ;zt}?5 z4nTXedU<t2`>el#<H<C5@bKiEhvUhVI^90?aOj~P4yn`a)A8vAf1^2IW3z{YQGG@m zhfuIM282)}$Yrj5Dh@_$=Gx-e&kJF%9b6`Z?Os-HJUBZ0`2e&wtKW~RZs^>=v#$UE N002ovPDHLkV1mgzxiJ6$
--- a/build/autoconf/compiler-opts.m4 +++ b/build/autoconf/compiler-opts.m4 @@ -29,27 +29,23 @@ case "$target" in if test -z "$MIDL"; then MIDL=midl; fi # need override this flag since we don't use $(LDFLAGS) for this. if test -z "$HOST_LDFLAGS" ; then HOST_LDFLAGS=" " fi ;; *-darwin*) - # we prefer gcc-4.2 over gcc on older darwin, so - # use that specific version if it's available. - # On newer versions of darwin, gcc is llvm-gcc while gcc-4.2 is the plain - # one, so we also try that first. If that fails, we fall back to clang - # as llvm-gcc is an unsupported dead end. - MOZ_PATH_PROGS(CC, $CC gcc-4.2 clang gcc) - MOZ_PATH_PROGS(CXX, $CXX g++-4.2 clang++ g++) - IS_LLVM_GCC=$($CC -v 2>&1 | grep llvm-gcc) - if test -n "$IS_LLVM_GCC" + # GCC on darwin is based on gcc 4.2 and we don't support it anymore. + MOZ_PATH_PROGS(CC, $CC clang) + MOZ_PATH_PROGS(CXX, $CXX clang++) + IS_GCC=$($CC -v 2>&1 | grep gcc) + if test -n "$IS_GCC" then - echo llvm-gcc is known to be broken, please use gcc-4.2 or clang. + echo gcc is known to be broken on OS X, please use clang. exit 1 fi ;; esac fi ]) dnl ============================================================================
deleted file mode 100644 --- a/build/mobile/b2gemulator.py +++ /dev/null @@ -1,87 +0,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/. - - -import os -import platform - -from emulator import Emulator - - -class B2GEmulator(Emulator): - - def __init__(self, homedir=None, noWindow=False, logcat_dir=None, arch="x86", - emulatorBinary=None, res='480x800', userdata=None, - memory='512', partition_size='512'): - super(B2GEmulator, self).__init__(noWindow=noWindow, logcat_dir=logcat_dir, - arch=arch, emulatorBinary=emulatorBinary, - res=res, userdata=userdata, - memory=memory, partition_size=partition_size) - self.homedir = homedir - if self.homedir is not None: - self.homedir = os.path.expanduser(homedir) - - def _check_file(self, filePath): - if not os.path.exists(filePath): - raise Exception(('File not found: %s; did you pass the B2G home ' - 'directory as the homedir parameter, or set ' - 'B2G_HOME correctly?') % filePath) - - def _check_for_adb(self, host_dir): - if self._default_adb() == 0: - return - adb_paths = [os.path.join(self.homedir,'glue','gonk','out','host', - host_dir ,'bin','adb'),os.path.join(self.homedir, 'out', - 'host', host_dir,'bin','adb'),os.path.join(self.homedir, - 'bin','adb')] - for option in adb_paths: - if os.path.exists(option): - self.adb = option - return - raise Exception('adb not found!') - - def _locate_files(self): - if self.homedir is None: - self.homedir = os.getenv('B2G_HOME') - if self.homedir is None: - raise Exception('Must define B2G_HOME or pass the homedir parameter') - self._check_file(self.homedir) - - if self.arch not in ("x86", "arm"): - raise Exception("Emulator architecture must be one of x86, arm, got: %s" % - self.arch) - - host_dir = "linux-x86" - if platform.system() == "Darwin": - host_dir = "darwin-x86" - - host_bin_dir = os.path.join("out", "host", host_dir, "bin") - - if self.arch == "x86": - binary = os.path.join(host_bin_dir, "emulator-x86") - kernel = "prebuilts/qemu-kernel/x86/kernel-qemu" - sysdir = "out/target/product/generic_x86" - self.tail_args = [] - else: - binary = os.path.join(host_bin_dir, "emulator") - kernel = "prebuilts/qemu-kernel/arm/kernel-qemu-armv7" - sysdir = "out/target/product/generic" - self.tail_args = ["-cpu", "cortex-a8"] - - self._check_for_adb(host_dir) - - if not self.binary: - self.binary = os.path.join(self.homedir, binary) - - self._check_file(self.binary) - - self.kernelImg = os.path.join(self.homedir, kernel) - self._check_file(self.kernelImg) - - self.sysDir = os.path.join(self.homedir, sysdir) - self._check_file(self.sysDir) - - if not self.dataImg: - self.dataImg = os.path.join(self.sysDir, 'userdata.img') - self._check_file(self.dataImg)
deleted file mode 100644 --- a/build/mobile/devicemanager-utils.py +++ /dev/null @@ -1,56 +0,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/. - -import devicemanager -import sys -import os - -def copy(dm, gre_path): - file = sys.argv[2] - if len(sys.argv) >= 4: - path = sys.argv[3] - else: - path = gre_path - if os.path.isdir(file): - dm.pushDir(file, path.replace('\\','/')) - else: - dm.pushFile(file, path.replace('\\','/')) - -def delete(dm, gre_path): - file = sys.argv[2] - dm.removeFile(file) - -def main(): - ip_addr = os.environ.get("DEVICE_IP") - port = os.environ.get("DEVICE_PORT") - gre_path = os.environ.get("REMOTE_GRE_PATH").replace('\\','/') - - if ip_addr == None: - print "Error: please define the environment variable DEVICE_IP before running this test" - sys.exit(1) - - if port == None: - print "Error: please define the environment variable DEVICE_PORT before running this test" - sys.exit(1) - - if gre_path == None: - print "Error: please define the environment variable REMOTE_GRE_PATH before running this test" - sys.exit(1) - - dm = devicemanager.DeviceManager(ip_addr, int(port)) - dm.sendCMD(['cd '+ gre_path], sleep = 1) - dm.debug = 0 - - if len(sys.argv) < 3: - print "usage: python devicemanager-utils.py <cmd> <path> [arg]" - cmd = sys.argv[1] - if (cmd == 'copy'): - sys.exit(copy(dm, gre_path)) - if (cmd == 'delete'): - sys.exit(delete(dm, gre_path)) - print "unrecognized command. supported commands are copy and delete" - sys.exit(-1) - -if __name__ == '__main__': - main()
deleted file mode 100755 --- a/build/mobile/devicemanager.py +++ /dev/null @@ -1,637 +0,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/. - -import hashlib -import socket -import os -import re -import StringIO - -class FileError(Exception): - " Signifies an error which occurs while doing a file operation." - - def __init__(self, msg = ''): - self.msg = msg - - def __str__(self): - return self.msg - -class DMError(Exception): - "generic devicemanager exception." - - def __init__(self, msg= ''): - self.msg = msg - - def __str__(self): - return self.msg - -def abstractmethod(method): - line = method.func_code.co_firstlineno - filename = method.func_code.co_filename - def not_implemented(*args, **kwargs): - raise NotImplementedError('Abstract method %s at File "%s", line %s ' - 'should be implemented by a concrete class' % - (repr(method), filename,line)) - return not_implemented - -class DeviceManager: - - @abstractmethod - def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None): - """ - executes shell command on device - - timeout is specified in seconds, and if no timeout is given, - we will run until the script returns - returns: - success: Return code from command - failure: None - """ - - @abstractmethod - def pushFile(self, localname, destname): - """ - external function - returns: - success: True - failure: False - """ - - @abstractmethod - def mkDir(self, name): - """ - external function - returns: - success: directory name - failure: None - """ - - def mkDirs(self, filename): - """ - make directory structure on the device - WARNING: does not create last part of the path - external function - returns: - success: directory structure that we created - failure: None - """ - parts = filename.split('/') - name = "" - for part in parts: - if (part == parts[-1]): break - if (part != ""): - name += '/' + part - if (not self.dirExists(name)): - if (self.mkDir(name) == None): - print "failed making directory: " + str(name) - return None - return name - - @abstractmethod - def pushDir(self, localDir, remoteDir): - """ - push localDir from host to remoteDir on the device - external function - returns: - success: remoteDir - failure: None - """ - - @abstractmethod - def dirExists(self, dirname): - """ - external function - returns: - success: True - failure: False - """ - - @abstractmethod - def fileExists(self, filepath): - """ - Because we always have / style paths we make this a lot easier with some - assumptions - external function - returns: - success: True - failure: False - """ - - @abstractmethod - def listFiles(self, rootdir): - """ - list files on the device, requires cd to directory first - external function - returns: - success: array of filenames, ['file1', 'file2', ...] - failure: None - """ - - @abstractmethod - def removeFile(self, filename): - """ - external function - returns: - success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt" - failure: None - """ - - @abstractmethod - def removeDir(self, remoteDir): - """ - does a recursive delete of directory on the device: rm -Rf remoteDir - external function - returns: - success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt" - failure: None - """ - - @abstractmethod - def getProcessList(self): - """ - external function - returns: - success: array of process tuples - failure: None - """ - - @abstractmethod - def fireProcess(self, appname, failIfRunning=False): - """ - external function - DEPRECATED: Use shell() or launchApplication() for new code - returns: - success: pid - failure: None - """ - - @abstractmethod - def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False): - """ - external function - DEPRECATED: Use shell() or launchApplication() for new code - returns: - success: output filename - failure: None - """ - - def processExist(self, appname): - """ - iterates process list and returns pid if exists, otherwise None - external function - returns: - success: pid - failure: None - """ - - pid = None - - #filter out extra spaces - parts = filter(lambda x: x != '', appname.split(' ')) - appname = ' '.join(parts) - - #filter out the quoted env string if it exists - #ex: '"name=value;name2=value2;etc=..." process args' -> 'process args' - parts = appname.split('"') - if (len(parts) > 2): - appname = ' '.join(parts[2:]).strip() - - pieces = appname.split(' ') - parts = pieces[0].split('/') - app = parts[-1] - - procList = self.getProcessList() - if (procList == []): - return None - - for proc in procList: - procName = proc[1].split('/')[-1] - if (procName == app): - pid = proc[0] - break - return pid - - - @abstractmethod - def killProcess(self, appname, forceKill=False): - """ - external function - returns: - success: True - failure: False - """ - - @abstractmethod - def catFile(self, remoteFile): - """ - external function - returns: - success: filecontents - failure: None - """ - - @abstractmethod - def pullFile(self, remoteFile): - """ - external function - returns: - success: output of pullfile, string - failure: None - """ - - @abstractmethod - def getFile(self, remoteFile, localFile = ''): - """ - copy file from device (remoteFile) to host (localFile) - external function - returns: - success: output of pullfile, string - failure: None - """ - - @abstractmethod - def getDirectory(self, remoteDir, localDir, checkDir=True): - """ - copy directory structure from device (remoteDir) to host (localDir) - external function - checkDir exists so that we don't create local directories if the - remote directory doesn't exist but also so that we don't call isDir - twice when recursing. - returns: - success: list of files, string - failure: None - """ - - @abstractmethod - def isDir(self, remotePath): - """ - external function - returns: - success: True - failure: False - Throws a FileError exception when null (invalid dir/filename) - """ - - @abstractmethod - def validateFile(self, remoteFile, localFile): - """ - true/false check if the two files have the same md5 sum - external function - returns: - success: True - failure: False - """ - - @abstractmethod - def getRemoteHash(self, filename): - """ - return the md5 sum of a remote file - internal function - returns: - success: MD5 hash for given filename - failure: None - """ - - def getLocalHash(self, filename): - """ - return the md5 sum of a file on the host - internal function - returns: - success: MD5 hash for given filename - failure: None - """ - - file = open(filename, 'rb') - if (file == None): - return None - - try: - mdsum = hashlib.md5() - except: - return None - - while 1: - data = file.read(1024) - if not data: - break - mdsum.update(data) - - file.close() - hexval = mdsum.hexdigest() - if (self.debug >= 3): print "local hash returned: '" + hexval + "'" - return hexval - - @abstractmethod - def getDeviceRoot(self): - """ - Gets the device root for the testing area on the device - For all devices we will use / type slashes and depend on the device-agent - to sort those out. The agent will return us the device location where we - should store things, we will then create our /tests structure relative to - that returned path. - Structure on the device is as follows: - /tests - /<fennec>|<firefox> --> approot - /profile - /xpcshell - /reftest - /mochitest - external - returns: - success: path for device root - failure: None - """ - - @abstractmethod - def getAppRoot(self): - """ - Either we will have /tests/fennec or /tests/firefox but we will never have - both. Return the one that exists - TODO: ensure we can support org.mozilla.firefox - external function - returns: - success: path for app root - failure: None - """ - - def getTestRoot(self, type): - """ - Gets the directory location on the device for a specific test type - Type is one of: xpcshell|reftest|mochitest - external function - returns: - success: path for test root - failure: None - """ - - devroot = self.getDeviceRoot() - if (devroot == None): - return None - - if (re.search('xpcshell', type, re.I)): - self.testRoot = devroot + '/xpcshell' - elif (re.search('?(i)reftest', type)): - self.testRoot = devroot + '/reftest' - elif (re.search('?(i)mochitest', type)): - self.testRoot = devroot + '/mochitest' - return self.testRoot - - def signal(self, processID, signalType, signalAction): - """ - Sends a specific process ID a signal code and action. - For Example: SIGINT and SIGDFL to process x - """ - #currently not implemented in device agent - todo - - pass - - def getReturnCode(self, processID): - """Get a return code from process ending -- needs support on device-agent""" - # TODO: make this real - - return 0 - - @abstractmethod - def unpackFile(self, file_path, dest_dir=None): - """ - external function - returns: - success: output of unzip command - failure: None - """ - - @abstractmethod - def reboot(self, ipAddr=None, port=30000): - """ - external function - returns: - success: status from test agent - failure: None - """ - - def validateDir(self, localDir, remoteDir): - """ - validate localDir from host to remoteDir on the device - external function - returns: - success: True - failure: False - """ - - if (self.debug >= 2): print "validating directory: " + localDir + " to " + remoteDir - for root, dirs, files in os.walk(localDir): - parts = root.split(localDir) - for file in files: - remoteRoot = remoteDir + '/' + parts[1] - remoteRoot = remoteRoot.replace('/', '/') - if (parts[1] == ""): remoteRoot = remoteDir - remoteName = remoteRoot + '/' + file - if (self.validateFile(remoteName, os.path.join(root, file)) <> True): - return False - return True - - @abstractmethod - def getInfo(self, directive=None): - """ - Returns information about the device: - Directive indicates the information you want to get, your choices are: - os - name of the os - id - unique id of the device - uptime - uptime of the device - uptimemillis - uptime of the device in milliseconds (NOT supported on all - implementations) - systime - system time of the device - screen - screen resolution - memory - memory stats - process - list of running processes (same as ps) - disk - total, free, available bytes on disk - power - power status (charge, battery temp) - all - all of them - or call it with no parameters to get all the information - returns: - success: dict of info strings by directive name - failure: None - """ - - @abstractmethod - def installApp(self, appBundlePath, destPath=None): - """ - external function - returns: - success: output from agent for inst command - failure: None - """ - - @abstractmethod - def uninstallAppAndReboot(self, appName, installPath=None): - """ - external function - returns: - success: True - failure: None - """ - - @abstractmethod - def updateApp(self, appBundlePath, processName=None, - destPath=None, ipAddr=None, port=30000): - """ - external function - returns: - success: text status from command or callback server - failure: None - """ - - @abstractmethod - def getCurrentTime(self): - """ - external function - returns: - success: time in ms - failure: None - """ - - def recordLogcat(self): - """ - external function - returns: - success: file is created in <testroot>/logcat.log - failure: - """ - #TODO: spawn this off in a separate thread/process so we can collect all the logcat information - - # Right now this is just clearing the logcat so we can only see what happens after this call. - buf = StringIO.StringIO() - self.shell(['/system/bin/logcat', '-c'], buf, root=True) - - def getLogcat(self): - """ - external function - returns: data from the local file - success: file is in 'filename' - failure: None - """ - buf = StringIO.StringIO() - if self.shell(["/system/bin/logcat", "-d", "dalvikvm:S", "ConnectivityService:S", "WifiMonitor:S", "WifiStateTracker:S", "wpa_supplicant:S", "NetworkStateTracker:S"], buf, root=True) != 0: - return None - - return str(buf.getvalue()[0:-1]).rstrip().split('\r') - - @abstractmethod - def chmodDir(self, remoteDir): - """ - external function - returns: - success: True - failure: False - """ - - @staticmethod - def _escapedCommandLine(cmd): - """ Utility function to return escaped and quoted version of command line """ - quotedCmd = [] - - for arg in cmd: - arg.replace('&', '\&') - - needsQuoting = False - for char in [ ' ', '(', ')', '"', '&' ]: - if arg.find(char) >= 0: - needsQuoting = True - break - if needsQuoting: - arg = '\'%s\'' % arg - - quotedCmd.append(arg) - - return " ".join(quotedCmd) - - -class NetworkTools: - def __init__(self): - pass - - # Utilities to get the local ip address - def getInterfaceIp(self, ifname): - if os.name != "nt": - import fcntl - import struct - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - return socket.inet_ntoa(fcntl.ioctl( - s.fileno(), - 0x8915, # SIOCGIFADDR - struct.pack('256s', ifname[:15]) - )[20:24]) - else: - return None - - def getLanIp(self): - try: - ip = socket.gethostbyname(socket.gethostname()) - except socket.gaierror: - ip = socket.gethostbyname(socket.gethostname() + ".local") # for Mac OS X - if ip.startswith("127.") and os.name != "nt": - interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"] - for ifname in interfaces: - try: - ip = self.getInterfaceIp(ifname) - break; - except IOError: - pass - return ip - - # Gets an open port starting with the seed by incrementing by 1 each time - def findOpenPort(self, ip, seed): - try: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - connected = False - if isinstance(seed, basestring): - seed = int(seed) - maxportnum = seed + 5000 # We will try at most 5000 ports to find an open one - while not connected: - try: - s.bind((ip, seed)) - connected = True - s.close() - break - except: - if seed > maxportnum: - print "Could not find open port after checking 5000 ports" - raise - seed += 1 - except: - print "Socket error trying to find open port" - - return seed - -def _pop_last_line(file): - ''' - Utility function to get the last line from a file (shared between ADB and - SUT device managers). Function also removes it from the file. Intended to - strip off the return code from a shell command. - ''' - bytes_from_end = 1 - file.seek(0, 2) - length = file.tell() + 1 - while bytes_from_end < length: - file.seek((-1)*bytes_from_end, 2) - data = file.read() - - if bytes_from_end == length-1 and len(data) == 0: # no data, return None - return None - - if data[0] == '\n' or bytes_from_end == length-1: - # found the last line, which should have the return value - if data[0] == '\n': - data = data[1:] - - # truncate off the return code line - file.truncate(length - bytes_from_end) - file.seek(0,2) - file.write('\0') - - return data - - bytes_from_end += 1 - - return None
deleted file mode 100644 --- a/build/mobile/devicemanagerADB.py +++ /dev/null @@ -1,900 +0,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/. - -import subprocess -from devicemanager import DeviceManager, DMError, _pop_last_line -import re -import os -import tempfile -import time - -class DeviceManagerADB(DeviceManager): - - def __init__(self, host=None, port=20701, retrylimit=5, packageName='fennec', - adbPath='adb', deviceSerial=None, deviceRoot=None): - self.host = host - self.port = port - self.retrylimit = retrylimit - self.retries = 0 - self._sock = None - self.useRunAs = False - self.haveRoot = False - self.useDDCopy = False - self.useZip = False - self.packageName = None - self.tempDir = None - self.deviceRoot = deviceRoot - - # the path to adb, or 'adb' to assume that it's on the PATH - self.adbPath = adbPath - - # The serial number of the device to use with adb, used in cases - # where multiple devices are being managed by the same adb instance. - self.deviceSerial = deviceSerial - - if packageName == 'fennec': - if os.getenv('USER'): - self.packageName = 'org.mozilla.fennec_' + os.getenv('USER') - else: - self.packageName = 'org.mozilla.fennec_' - elif packageName: - self.packageName = packageName - - # verify that we can run the adb command. can't continue otherwise - self.verifyADB() - - # try to connect to the device over tcp/ip if we have a hostname - if self.host: - self.connectRemoteADB() - - # verify that we can connect to the device. can't continue - self.verifyDevice() - - # set up device root - self.setupDeviceRoot() - - # Can we use run-as? (currently not required) - try: - self.verifyRunAs() - except DMError: - pass - - # Can we run things as root? (currently not required) - useRunAsTmp = self.useRunAs - self.useRunAs = False - try: - self.verifyRoot() - except DMError: - try: - self.checkCmd(["root"]) - # The root command does not fail even if ADB cannot get - # root rights (e.g. due to production builds), so we have - # to check again ourselves that we have root now. - self.verifyRoot() - except DMError: - if useRunAsTmp: - print "restarting as root failed, but run-as available" - else: - print "restarting as root failed" - self.useRunAs = useRunAsTmp - - # can we use zip to speed up some file operations? (currently not - # required) - try: - self.verifyZip() - except DMError: - pass - - def __del__(self): - if self.host: - self.disconnectRemoteADB() - - # external function: executes shell command on device. - # timeout is specified in seconds, and if no timeout is given, - # we will run until the script returns - # returns: - # success: <return code> - # failure: None - def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False): - # FIXME: this function buffers all output of the command into memory, - # always. :( - - # Getting the return code is more complex than you'd think because adb - # doesn't actually return the return code from a process, so we have to - # capture the output to get it - if root: - cmdline = "su -c \"%s\"" % self._escapedCommandLine(cmd) - else: - cmdline = self._escapedCommandLine(cmd) - cmdline += "; echo $?" - - # prepend cwd and env to command if necessary - if cwd: - cmdline = "cd %s; %s" % (cwd, cmdline) - if env: - envstr = '; '.join(map(lambda x: 'export %s=%s' % (x[0], x[1]), env.iteritems())) - cmdline = envstr + "; " + cmdline - - # all output should be in stdout - args=[self.adbPath] - if self.deviceSerial: - args.extend(['-s', self.deviceSerial]) - args.extend(["shell", cmdline]) - proc = subprocess.Popen(args, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if timeout: - timeout = int(timeout) - start_time = time.time() - ret_code = proc.poll() - while ((time.time() - start_time) <= timeout) and ret_code == None: - time.sleep(1) - ret_code = proc.poll() - if ret_code == None: - proc.kill() - raise DMError("Timeout exceeded for shell call") - (stdout, stderr) = proc.communicate() - outputfile.write(stdout.rstrip('\n')) - - lastline = _pop_last_line(outputfile) - if lastline: - m = re.search('([0-9]+)', lastline) - if m: - return_code = m.group(1) - outputfile.seek(-2, 2) - outputfile.truncate() # truncate off the return code - return int(return_code) - - return None - - def connectRemoteADB(self): - self.checkCmd(["connect", self.host + ":" + str(self.port)]) - - def disconnectRemoteADB(self): - self.checkCmd(["disconnect", self.host + ":" + str(self.port)]) - - # external function - # returns: - # success: True - # failure: False - def pushFile(self, localname, destname): - try: - if (os.name == "nt"): - destname = destname.replace('\\', '/') - if (self.useRunAs): - remoteTmpFile = self.getTempDir() + "/" + os.path.basename(localname) - self.checkCmd(["push", os.path.realpath(localname), remoteTmpFile]) - if self.useDDCopy: - self.checkCmdAs(["shell", "dd", "if=" + remoteTmpFile, "of=" + destname]) - else: - self.checkCmdAs(["shell", "cp", remoteTmpFile, destname]) - self.checkCmd(["shell", "rm", remoteTmpFile]) - else: - self.checkCmd(["push", os.path.realpath(localname), destname]) - if (self.isDir(destname)): - destname = destname + "/" + os.path.basename(localname) - return True - except: - return False - - # external function - # returns: - # success: directory name - # failure: None - def mkDir(self, name): - try: - result = self.runCmdAs(["shell", "mkdir", name]).stdout.read() - if 'read-only file system' in result.lower(): - return None - if 'file exists' in result.lower(): - return name - return name - except: - return None - - # push localDir from host to remoteDir on the device - # external function - # returns: - # success: remoteDir - # failure: None - def pushDir(self, localDir, remoteDir): - # adb "push" accepts a directory as an argument, but if the directory - # contains symbolic links, the links are pushed, rather than the linked - # files; we either zip/unzip or push file-by-file to get around this - # limitation - try: - if (not self.dirExists(remoteDir)): - self.mkDirs(remoteDir+"/x") - if (self.useZip): - try: - localZip = tempfile.mktemp()+".zip" - remoteZip = remoteDir + "/adbdmtmp.zip" - subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir) - self.pushFile(localZip, remoteZip) - os.remove(localZip) - data = self.runCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir]).stdout.read() - self.checkCmdAs(["shell", "rm", remoteZip]) - if (re.search("unzip: exiting", data) or re.search("Operation not permitted", data)): - raise Exception("unzip failed, or permissions error") - except: - print "zip/unzip failure: falling back to normal push" - self.useZip = False - self.pushDir(localDir, remoteDir) - else: - for root, dirs, files in os.walk(localDir, followlinks=True): - relRoot = os.path.relpath(root, localDir) - for file in files: - localFile = os.path.join(root, file) - remoteFile = remoteDir + "/" - if (relRoot!="."): - remoteFile = remoteFile + relRoot + "/" - remoteFile = remoteFile + file - self.pushFile(localFile, remoteFile) - for dir in dirs: - targetDir = remoteDir + "/" - if (relRoot!="."): - targetDir = targetDir + relRoot + "/" - targetDir = targetDir + dir - if (not self.dirExists(targetDir)): - self.mkDir(targetDir) - return remoteDir - except: - print "pushing " + localDir + " to " + remoteDir + " failed" - return None - - # external function - # returns: - # success: True - # failure: False - def dirExists(self, dirname): - return self.isDir(dirname) - - # Because we always have / style paths we make this a lot easier with some - # assumptions - # external function - # returns: - # success: True - # failure: False - def fileExists(self, filepath): - p = self.runCmd(["shell", "ls", "-a", filepath]) - data = p.stdout.readlines() - if (len(data) == 1): - if (data[0].rstrip() == filepath): - return True - return False - - def removeFile(self, filename): - return self.runCmd(["shell", "rm", filename]).stdout.read() - - # does a recursive delete of directory on the device: rm -Rf remoteDir - # external function - # returns: - # success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt" - # failure: None - def removeSingleDir(self, remoteDir): - return self.runCmd(["shell", "rmdir", remoteDir]).stdout.read() - - # does a recursive delete of directory on the device: rm -Rf remoteDir - # external function - # returns: - # success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt" - # failure: None - def removeDir(self, remoteDir): - out = "" - if (self.isDir(remoteDir)): - files = self.listFiles(remoteDir.strip()) - for f in files: - if (self.isDir(remoteDir.strip() + "/" + f.strip())): - out += self.removeDir(remoteDir.strip() + "/" + f.strip()) - else: - out += self.removeFile(remoteDir.strip() + "/" + f.strip()) - out += self.removeSingleDir(remoteDir.strip()) - else: - out += self.removeFile(remoteDir.strip()) - return out - - def isDir(self, remotePath): - p = self.runCmd(["shell", "ls", "-a", remotePath + '/']) - - data = p.stdout.readlines() - if len(data) == 1: - res = data[0] - if "Not a directory" in res or "No such file or directory" in res: - return False - - return True - - def listFiles(self, rootdir): - p = self.runCmd(["shell", "ls", "-a", rootdir]) - data = p.stdout.readlines() - data[:] = [item.rstrip('\r\n') for item in data] - if (len(data) == 1): - if (data[0] == rootdir): - return [] - if (data[0].find("No such file or directory") != -1): - return [] - if (data[0].find("Not a directory") != -1): - return [] - if (data[0].find("Permission denied") != -1): - return [] - if (data[0].find("opendir failed") != -1): - return [] - return data - - # external function - # returns: - # success: array of process tuples - # failure: [] - def getProcessList(self): - p = self.runCmd(["shell", "ps"]) - # first line is the headers - p.stdout.readline() - proc = p.stdout.readline() - ret = [] - while (proc): - els = proc.split() - ret.append(list([els[1], els[len(els) - 1], els[0]])) - proc = p.stdout.readline() - return ret - - # external function - # DEPRECATED: Use shell() or launchApplication() for new code - # returns: - # success: pid - # failure: None - def fireProcess(self, appname, failIfRunning=False): - #strip out env vars - parts = appname.split('"'); - if (len(parts) > 2): - parts = parts[2:] - return self.launchProcess(parts, failIfRunning) - - # external function - # DEPRECATED: Use shell() or launchApplication() for new code - # returns: - # success: output filename - # failure: None - def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False): - if cmd[0] == "am": - self.checkCmd(["shell"] + cmd) - return outputFile - - acmd = ["shell", "am", "start", "-W"] - cmd = ' '.join(cmd).strip() - i = cmd.find(" ") - # SUT identifies the URL by looking for :\\ -- another strategy to consider - re_url = re.compile('^[http|file|chrome|about].*') - last = cmd.rfind(" ") - uri = "" - args = "" - if re_url.match(cmd[last:].strip()): - args = cmd[i:last].strip() - uri = cmd[last:].strip() - else: - args = cmd[i:].strip() - acmd.append("-n") - acmd.append(cmd[0:i] + "/.App") - if args != "": - acmd.append("--es") - acmd.append("args") - acmd.append(args) - if env != '' and env != None: - envCnt = 0 - # env is expected to be a dict of environment variables - for envkey, envval in env.iteritems(): - acmd.append("--es") - acmd.append("env" + str(envCnt)) - acmd.append(envkey + "=" + envval); - envCnt += 1 - if uri != "": - acmd.append("-d") - acmd.append(''.join(['\'',uri, '\''])); - print acmd - self.checkCmd(acmd) - return outputFile - - # external function - # returns: - # success: True - # failure: False - def killProcess(self, appname, forceKill=False): - procs = self.getProcessList() - didKillProcess = False - for (pid, name, user) in procs: - if name == appname: - args = ["shell", "kill"] - if forceKill: - args.append("-9") - args.append(pid) - p = self.runCmdAs(args) - p.communicate() - if p.returncode == 0: - didKillProcess = True - - return didKillProcess - - # external function - # returns: - # success: filecontents - # failure: None - def catFile(self, remoteFile): - #p = self.runCmd(["shell", "cat", remoteFile]) - #return p.stdout.read() - return self.getFile(remoteFile) - - # external function - # returns: - # success: output of pullfile, string - # failure: None - def pullFile(self, remoteFile): - #return self.catFile(remoteFile) - return self.getFile(remoteFile) - - # copy file from device (remoteFile) to host (localFile) - # external function - # returns: - # success: output of pullfile, string - # failure: None - def getFile(self, remoteFile, localFile = 'tmpfile_dm_adb'): - # TODO: add debug flags and allow for printing stdout - # self.runCmd(["pull", remoteFile, localFile]) - try: - - # First attempt to pull file regularly - outerr = self.runCmd(["pull", remoteFile, localFile]).communicate() - - # Now check stderr for errors - if outerr[1]: - errl = outerr[1].splitlines() - if (len(errl) == 1): - if (((errl[0].find("Permission denied") != -1) - or (errl[0].find("does not exist") != -1)) - and self.useRunAs): - # If we lack permissions to read but have run-as, then we should try - # to copy the file to a world-readable location first before attempting - # to pull it again. - remoteTmpFile = self.getTempDir() + "/" + os.path.basename(remoteFile) - self.checkCmdAs(["shell", "dd", "if=" + remoteFile, "of=" + remoteTmpFile]) - self.checkCmdAs(["shell", "chmod", "777", remoteTmpFile]) - self.runCmd(["pull", remoteTmpFile, localFile]).stdout.read() - # Clean up temporary file - self.checkCmdAs(["shell", "rm", remoteTmpFile]) - - f = open(localFile) - ret = f.read() - f.close() - return ret - except: - return None - - # copy directory structure from device (remoteDir) to host (localDir) - # external function - # checkDir exists so that we don't create local directories if the - # remote directory doesn't exist but also so that we don't call isDir - # twice when recursing. - # returns: - # success: list of files, string - # failure: None - def getDirectory(self, remoteDir, localDir, checkDir=True): - ret = [] - p = self.runCmd(["pull", remoteDir, localDir]) - p.stdout.readline() - line = p.stdout.readline() - while (line): - els = line.split() - f = els[len(els) - 1] - i = f.find(localDir) - if (i != -1): - if (localDir[len(localDir) - 1] != '/'): - i = i + 1 - f = f[i + len(localDir):] - i = f.find("/") - if (i > 0): - f = f[0:i] - ret.append(f) - line = p.stdout.readline() - #the last line is a summary - if (len(ret) > 0): - ret.pop() - return ret - - - - # true/false check if the two files have the same md5 sum - # external function - # returns: - # success: True - # failure: False - def validateFile(self, remoteFile, localFile): - return self.getRemoteHash(remoteFile) == self.getLocalHash(localFile) - - # return the md5 sum of a remote file - # internal function - # returns: - # success: MD5 hash for given filename - # failure: None - def getRemoteHash(self, filename): - data = self.runCmd(["shell", "ls", "-l", filename]).stdout.read() - return data.split()[3] - - def getLocalHash(self, filename): - data = subprocess.Popen(["ls", "-l", filename], stdout=subprocess.PIPE).stdout.read() - return data.split()[4] - - # Internal method to setup the device root and cache its value - def setupDeviceRoot(self): - # if self.deviceRoot is already set, create it if necessary, and use it - if self.deviceRoot: - if not self.dirExists(self.deviceRoot): - if not self.mkDir(self.deviceRoot): - raise DMError("Unable to create device root %s" % self.deviceRoot) - return - - # /mnt/sdcard/tests is preferred to /data/local/tests, but this can be - # over-ridden by creating /data/local/tests - testRoot = "/data/local/tests" - if (self.dirExists(testRoot)): - self.deviceRoot = testRoot - return - - for (basePath, subPath) in [('/mnt/sdcard', 'tests'), - ('/data/local', 'tests')]: - if self.dirExists(basePath): - testRoot = os.path.join(basePath, subPath) - if self.mkDir(testRoot): - self.deviceRoot = testRoot - return - - raise DMError("Unable to set up device root as /mnt/sdcard/tests " - "or /data/local/tests") - - # Gets the device root for the testing area on the device - # For all devices we will use / type slashes and depend on the device-agent - # to sort those out. The agent will return us the device location where we - # should store things, we will then create our /tests structure relative to - # that returned path. - # Structure on the device is as follows: - # /tests - # /<fennec>|<firefox> --> approot - # /profile - # /xpcshell - # /reftest - # /mochitest - # - # external function - # returns: - # success: path for device root - # failure: None - def getDeviceRoot(self): - return self.deviceRoot - - # Gets the temporary directory we are using on this device - # base on our device root, ensuring also that it exists. - # - # internal function - # returns: - # success: path for temporary directory - # failure: None - def getTempDir(self): - # Cache result to speed up operations depending - # on the temporary directory. - if self.tempDir == None: - self.tempDir = self.getDeviceRoot() + "/tmp" - if (not self.dirExists(self.tempDir)): - return self.mkDir(self.tempDir) - - return self.tempDir - - # Either we will have /tests/fennec or /tests/firefox but we will never have - # both. Return the one that exists - # TODO: ensure we can support org.mozilla.firefox - # external function - # returns: - # success: path for app root - # failure: None - def getAppRoot(self, packageName): - devroot = self.getDeviceRoot() - if (devroot == None): - return None - - if (packageName and self.dirExists('/data/data/' + packageName)): - self.packageName = packageName - return '/data/data/' + packageName - elif (self.packageName and self.dirExists('/data/data/' + self.packageName)): - return '/data/data/' + self.packageName - - # Failure (either not installed or not a recognized platform) - print "devicemanagerADB: getAppRoot failed" - return None - - # Gets the directory location on the device for a specific test type - # Type is one of: xpcshell|reftest|mochitest - # external function - # returns: - # success: path for test root - # failure: None - def getTestRoot(self, type): - devroot = self.getDeviceRoot() - if (devroot == None): - return None - - if (re.search('xpcshell', type, re.I)): - self.testRoot = devroot + '/xpcshell' - elif (re.search('?(i)reftest', type)): - self.testRoot = devroot + '/reftest' - elif (re.search('?(i)mochitest', type)): - self.testRoot = devroot + '/mochitest' - return self.testRoot - - - # external function - # returns: - # success: status from test agent - # failure: None - def reboot(self, wait = False): - ret = self.runCmd(["reboot"]).stdout.read() - if (not wait): - return "Success" - countdown = 40 - while (countdown > 0): - countdown - try: - self.checkCmd(["wait-for-device", "shell", "ls", "/sbin"]) - return ret - except: - try: - self.checkCmd(["root"]) - except: - time.sleep(1) - print "couldn't get root" - return "Success" - - # external function - # returns: - # success: text status from command or callback server - # failure: None - def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000): - return self.runCmd(["install", "-r", appBundlePath]).stdout.read() - - # external function - # returns: - # success: time in ms - # failure: None - def getCurrentTime(self): - timestr = self.runCmd(["shell", "date", "+%s"]).stdout.read().strip() - if (not timestr or not timestr.isdigit()): - return None - return str(int(timestr)*1000) - - # Returns information about the device: - # Directive indicates the information you want to get, your choices are: - # os - name of the os - # id - unique id of the device - # uptime - uptime of the device - # systime - system time of the device - # screen - screen resolution - # memory - memory stats - # process - list of running processes (same as ps) - # disk - total, free, available bytes on disk - # power - power status (charge, battery temp) - # all - all of them - or call it with no parameters to get all the information - ### Note that uptimemillis is NOT supported, as there is no way to get this - ### data from the shell. - # returns: - # success: dict of info strings by directive name - # failure: {} - def getInfo(self, directive="all"): - ret = {} - if (directive == "id" or directive == "all"): - ret["id"] = self.runCmd(["get-serialno"]).stdout.read() - if (directive == "os" or directive == "all"): - ret["os"] = self.runCmd(["shell", "getprop", "ro.build.display.id"]).stdout.read() - if (directive == "uptime" or directive == "all"): - utime = self.runCmd(["shell", "uptime"]).stdout.read() - if (not utime): - raise DMError("error getting uptime") - utime = utime[9:] - hours = utime[0:utime.find(":")] - utime = utime[utime[1:].find(":") + 2:] - minutes = utime[0:utime.find(":")] - utime = utime[utime[1:].find(":") + 2:] - seconds = utime[0:utime.find(",")] - ret["uptime"] = ["0 days " + hours + " hours " + minutes + " minutes " + seconds + " seconds"] - if (directive == "process" or directive == "all"): - ret["process"] = self.runCmd(["shell", "ps"]).stdout.read() - if (directive == "systime" or directive == "all"): - ret["systime"] = self.runCmd(["shell", "date"]).stdout.read() - print ret - return ret - - def runCmd(self, args): - # If we are not root but have run-as, and we're trying to execute - # a shell command then using run-as is the best we can do - finalArgs = [self.adbPath] - if self.deviceSerial: - finalArgs.extend(['-s', self.deviceSerial]) - if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"): - args.insert(1, "run-as") - args.insert(2, self.packageName) - finalArgs.extend(args) - return subprocess.Popen(finalArgs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - - def runCmdAs(self, args): - if self.useRunAs: - args.insert(1, "run-as") - args.insert(2, self.packageName) - return self.runCmd(args) - - # timeout is specified in seconds, and if no timeout is given, - # we will run until the script returns - def checkCmd(self, args, timeout=None): - # If we are not root but have run-as, and we're trying to execute - # a shell command then using run-as is the best we can do - finalArgs = [self.adbPath] - if self.deviceSerial: - finalArgs.extend(['-s', self.deviceSerial]) - if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"): - args.insert(1, "run-as") - args.insert(2, self.packageName) - finalArgs.extend(args) - if timeout: - timeout = int(timeout) - proc = subprocess.Popen(finalArgs) - start_time = time.time() - ret_code = proc.poll() - while ((time.time() - start_time) <= timeout) and ret_code == None: - time.sleep(1) - ret_code = proc.poll() - if ret_code == None: - proc.kill() - raise DMError("Timeout exceeded for checkCmd call") - return ret_code - return subprocess.check_call(finalArgs) - - def checkCmdAs(self, args, timeout=None): - if (self.useRunAs): - args.insert(1, "run-as") - args.insert(2, self.packageName) - return self.checkCmd(args, timeout) - - # external function - # returns: - # success: True - # failure: False - def chmodDir(self, remoteDir): - if (self.isDir(remoteDir)): - files = self.listFiles(remoteDir.strip()) - for f in files: - remoteEntry = remoteDir.strip() + "/" + f.strip() - if (self.isDir(remoteEntry)): - self.chmodDir(remoteEntry) - else: - self.checkCmdAs(["shell", "chmod", "777", remoteEntry]) - print "chmod " + remoteEntry - self.checkCmdAs(["shell", "chmod", "777", remoteDir]) - print "chmod " + remoteDir - else: - self.checkCmdAs(["shell", "chmod", "777", remoteDir.strip()]) - print "chmod " + remoteDir.strip() - return True - - def verifyADB(self): - # Check to see if adb itself can be executed. - if self.adbPath != 'adb': - if not os.access(self.adbPath, os.X_OK): - raise DMError("invalid adb path, or adb not executable: %s", self.adbPath) - - try: - self.checkCmd(["version"]) - except os.error, err: - raise DMError("unable to execute ADB (%s): ensure Android SDK is installed and adb is in your $PATH" % err) - except subprocess.CalledProcessError: - raise DMError("unable to execute ADB: ensure Android SDK is installed and adb is in your $PATH") - - def verifyDevice(self): - # If there is a device serial number, see if adb is connected to it - if self.deviceSerial: - deviceStatus = None - proc = subprocess.Popen([self.adbPath, "devices"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - for line in proc.stdout: - m = re.match('(.+)?\s+(.+)$', line) - if m: - if self.deviceSerial == m.group(1): - deviceStatus = m.group(2) - if deviceStatus == None: - raise DMError("device not found: %s" % self.deviceSerial) - elif deviceStatus != "device": - raise DMError("bad status for device %s: %s" % (self.deviceSerial, - deviceStatus)) - - # Check to see if we can connect to device and run a simple command - try: - self.checkCmd(["shell", "echo"]) - except subprocess.CalledProcessError: - raise DMError("unable to connect to device: is it plugged in?") - - def verifyRoot(self): - # a test to see if we have root privs - p = self.runCmd(["shell", "id"]) - response = p.stdout.readline() - response = response.rstrip() - response = response.split(' ') - if (response[0].find('uid=0') < 0 or response[1].find('gid=0') < 0): - print "NOT running as root ", response[0].find('uid=0') - raise DMError("not running as root") - - self.haveRoot = True - - def isCpAvailable(self): - # Some Android systems may not have a cp command installed, - # or it may not be executable by the user. - data = self.runCmd(["shell", "cp"]).stdout.read() - if (re.search('Usage', data)): - return True - else: - data = self.runCmd(["shell", "dd", "-"]).stdout.read() - if (re.search('unknown operand', data)): - print "'cp' not found, but 'dd' was found as a replacement" - self.useDDCopy = True - return True - print "unable to execute 'cp' on device; consider installing busybox from Android Market" - return False - - def verifyRunAs(self): - # If a valid package name is available, and certain other - # conditions are met, devicemanagerADB can execute file operations - # via the "run-as" command, so that pushed files and directories - # are created by the uid associated with the package, more closely - # echoing conditions encountered by Fennec at run time. - # Check to see if run-as can be used here, by verifying a - # file copy via run-as. - self.useRunAs = False - devroot = self.getDeviceRoot() - if (self.packageName and self.isCpAvailable() and devroot): - tmpDir = self.getTempDir() - - # The problem here is that run-as doesn't cause a non-zero exit code - # when failing because of a non-existent or non-debuggable package :( - runAsOut = self.runCmd(["shell", "run-as", self.packageName, "mkdir", devroot + "/sanity"]).communicate()[0] - if runAsOut.startswith("run-as:") and ("not debuggable" in runAsOut or - "is unknown" in runAsOut): - raise DMError("run-as failed sanity check") - - tmpfile = tempfile.NamedTemporaryFile() - self.checkCmd(["push", tmpfile.name, tmpDir + "/tmpfile"]) - if self.useDDCopy: - self.checkCmd(["shell", "run-as", self.packageName, "dd", "if=" + tmpDir + "/tmpfile", "of=" + devroot + "/sanity/tmpfile"]) - else: - self.checkCmd(["shell", "run-as", self.packageName, "cp", tmpDir + "/tmpfile", devroot + "/sanity"]) - if (self.fileExists(devroot + "/sanity/tmpfile")): - print "will execute commands via run-as " + self.packageName - self.useRunAs = True - self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"]) - self.checkCmd(["shell", "run-as", self.packageName, "rm", "-r", devroot + "/sanity"]) - - def isUnzipAvailable(self): - data = self.runCmdAs(["shell", "unzip"]).stdout.read() - if (re.search('Usage', data)): - return True - else: - return False - - def isLocalZipAvailable(self): - try: - subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except: - return False - return True - - def verifyZip(self): - # If "zip" can be run locally, and "unzip" can be run remotely, then pushDir - # can use these to push just one file per directory -- a significant - # optimization for large directories. - self.useZip = False - if (self.isUnzipAvailable() and self.isLocalZipAvailable()): - print "will use zip to push directories" - self.useZip = True - else: - raise DMError("zip not available")
deleted file mode 100644 --- a/build/mobile/devicemanagerSUT.py +++ /dev/null @@ -1,1240 +0,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/. - -import select -import socket -import SocketServer -import time -import os -import re -import posixpath -import subprocess -from threading import Thread -import StringIO -from devicemanager import DeviceManager, FileError, NetworkTools, _pop_last_line -import errno -from distutils.version import StrictVersion - -class AgentError(Exception): - "SUTAgent-specific exception." - - def __init__(self, msg= '', fatal = False): - self.msg = msg - self.fatal = fatal - - def __str__(self): - return self.msg - -class DeviceManagerSUT(DeviceManager): - host = '' - port = 0 - debug = 2 - retries = 0 - tempRoot = os.getcwd() - base_prompt = '$>' - base_prompt_re = '\$\>' - prompt_sep = '\x00' - prompt_regex = '.*(' + base_prompt_re + prompt_sep + ')' - agentErrorRE = re.compile('^##AGENT-WARNING##\ ?(.*)') - - # TODO: member variable to indicate error conditions. - # This should be set to a standard error from the errno module. - # So, for example, when an error occurs because of a missing file/directory, - # before returning, the function would do something like 'self.error = errno.ENOENT'. - # The error would be set where appropriate--so sendCMD() could set socket errors, - # pushFile() and other file-related commands could set filesystem errors, etc. - - def __init__(self, host, port = 20701, retrylimit = 5, deviceRoot = None): - self.host = host - self.port = port - self.retrylimit = retrylimit - self.retries = 0 - self._sock = None - self.deviceRoot = deviceRoot - if self.getDeviceRoot() == None: - raise BaseException("Failed to connect to SUT Agent and retrieve the device root.") - try: - verstring = self.runCmds([{ 'cmd': 'ver' }]) - self.agentVersion = re.sub('SUTAgentAndroid Version ', '', verstring) - except AgentError, err: - raise BaseException("Failed to get SUTAgent version") - - def _cmdNeedsResponse(self, cmd): - """ Not all commands need a response from the agent: - * rebt obviously doesn't get a response - * uninstall performs a reboot to ensure starting in a clean state and - so also doesn't look for a response - """ - noResponseCmds = [re.compile('^rebt'), - re.compile('^uninst .*$'), - re.compile('^pull .*$')] - - for c in noResponseCmds: - if (c.match(cmd)): - return False - - # If the command is not in our list, then it gets a response - return True - - def _stripPrompt(self, data): - ''' - internal function - take a data blob and strip instances of the prompt '$>\x00' - ''' - promptre = re.compile(self.prompt_regex + '.*') - retVal = [] - lines = data.split('\n') - for line in lines: - foundPrompt = False - try: - while (promptre.match(line)): - foundPrompt = True - pieces = line.split(self.prompt_sep) - index = pieces.index('$>') - pieces.pop(index) - line = self.prompt_sep.join(pieces) - except(ValueError): - pass - - # we don't want to append lines that are blank after stripping the - # prompt (those are basically "prompts") - if not foundPrompt or line: - retVal.append(line) - - return '\n'.join(retVal) - - def _shouldCmdCloseSocket(self, cmd): - """ Some commands need to close the socket after they are sent: - * rebt - * uninst - * quit - """ - socketClosingCmds = [re.compile('^quit.*'), - re.compile('^rebt.*'), - re.compile('^uninst .*$')] - - for c in socketClosingCmds: - if (c.match(cmd)): - return True - - return False - - def sendCmds(self, cmdlist, outputfile, timeout = None): - ''' - a wrapper for _doCmds that loops up to self.retrylimit iterations. - this allows us to move the retry logic outside of the _doCmds() to make it - easier for debugging in the future. - note that since cmdlist is a list of commands, they will all be retried if - one fails. this is necessary in particular for pushFile(), where we don't want - to accidentally send extra data if a failure occurs during data transmission. - ''' - if timeout: - raise NotImplementedError("'timeout' parameter is not yet supported") - while self.retries < self.retrylimit: - try: - self._doCmds(cmdlist, outputfile, timeout) - return - except AgentError, err: - # re-raise error if it's fatal (i.e. the device got the command but - # couldn't execute it). retry otherwise - if err.fatal: - raise err - if self.debug >= 2: - print err - self.retries += 1 - - raise AgentError("unable to connect to %s after %s attempts" % (self.host, self.retrylimit)) - - def runCmds(self, cmdlist, timeout = None): - ''' - similar to sendCmds, but just returns any output as a string instead of - writing to a file. this is normally what you want to call to send a set - of commands to the agent - ''' - outputfile = StringIO.StringIO() - self.sendCmds(cmdlist, outputfile, timeout) - outputfile.seek(0) - return outputfile.read() - - def _doCmds(self, cmdlist, outputfile, timeout): - promptre = re.compile(self.prompt_regex + '$') - shouldCloseSocket = False - recvGuard = 1000 - - if not self._sock: - try: - if self.debug >= 1: - print "reconnecting socket" - self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - except socket.error, msg: - self._sock = None - raise AgentError("unable to create socket: "+str(msg)) - - try: - self._sock.connect((self.host, int(self.port))) - self._sock.recv(1024) - except socket.error, msg: - self._sock.close() - self._sock = None - raise AgentError("unable to connect socket: "+str(msg)) - - for cmd in cmdlist: - cmdline = '%s\r\n' % cmd['cmd'] - - try: - sent = self._sock.send(cmdline) - if sent != len(cmdline): - raise AgentError("ERROR: our cmd was %s bytes and we " - "only sent %s" % (len(cmdline), sent)) - if cmd.get('data'): - sent = self._sock.send(cmd['data']) - if sent != len(cmd['data']): - raise AgentError("ERROR: we had %s bytes of data to send, but " - "only sent %s" % (len(cmd['data']), sent)) - - if (self.debug >= 4): print "sent cmd: " + str(cmd['cmd']) - except socket.error, msg: - self._sock.close() - self._sock = None - if self.debug >= 1: - print "Error sending data to socket. cmd="+str(cmd['cmd'])+"; err="+str(msg) - return False - - # Check if the command should close the socket - shouldCloseSocket = self._shouldCmdCloseSocket(cmd['cmd']) - - # Handle responses from commands - if (self._cmdNeedsResponse(cmd['cmd'])): - found = False - loopguard = 0 - data = "" - - while (found == False and (loopguard < recvGuard)): - temp = '' - if (self.debug >= 4): print "recv'ing..." - - # Get our response - try: - # Wait up to a second for socket to become ready for reading... - if select.select([self._sock], [], [], 1)[0]: - temp = self._sock.recv(1024) - if (self.debug >= 4): print "response: " + str(temp) - except socket.error, err: - self._sock.close() - self._sock = None - # This error shows up with we have our tegra rebooted. - if err[0] == errno.ECONNRESET: - raise AgentError("Automation error: Error receiving data from socket (possible reboot). cmd=%s; err=%s" % (cmd, err)) - raise AgentError("Error receiving data from socket. cmd=%s; err=%s" % (cmd, err)) - - data += temp - - # If something goes wrong in the agent it will send back a string that - # starts with '##AGENT-WARNING##' - errorMatch = self.agentErrorRE.match(data) - if errorMatch: - raise AgentError("Agent Error processing command '%s'; err='%s'" % - (cmd['cmd'], errorMatch.group(1)), fatal=True) - - for line in data.splitlines(): - if promptre.match(line): - found = True - data = self._stripPrompt(data) - break - - # periodically flush data to output file to make sure it doesn't get - # too big/unwieldly - if len(data) > 1024: - outputfile.write(data[0:1024]) - data = data[1024:] - - # If we violently lose the connection to the device, this loop tends to spin, - # this guard prevents that - if (temp == ''): - loopguard += 1 - - # Write any remaining data to outputfile - outputfile.write(data) - - if shouldCloseSocket: - try: - self._sock.close() - self._sock = None - except: - self._sock = None - raise AgentError("Error closing socket") - - # external function: executes shell command on device - # returns: - # success: <return code> - # failure: None - def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False): - cmdline = self._escapedCommandLine(cmd) - if env: - cmdline = '%s %s' % (self.formatEnvString(env), cmdline) - - haveExecSu = (StrictVersion(self.agentVersion) >= StrictVersion('1.13')) - - # Depending on agent version we send one of the following commands here: - # * exec (run as normal user) - # * execsu (run as privileged user) - # * execcwd (run as normal user from specified directory) - # * execcwdsu (run as privileged user from specified directory) - - cmd = "exec" - if cwd: - cmd += "cwd" - if root and haveExecSu: - cmd += "su" - - try: - if cwd: - self.sendCmds([{ 'cmd': '%s %s %s' % (cmd, cwd, cmdline) }], outputfile, timeout) - else: - if (not root) or haveExecSu: - self.sendCmds([{ 'cmd': '%s %s' % (cmd, cmdline) }], outputfile, timeout) - else: - # need to manually inject su -c for backwards compatibility (this may - # not work on ICS or above!!) - # (FIXME: this backwards compatibility code is really ugly and should - # be deprecated at some point in the future) - self.sendCmds([ { 'cmd': '%s su -c "%s"' % (cmd, cmdline) }], outputfile, - timeout) - except AgentError: - return None - - # dig through the output to get the return code - lastline = _pop_last_line(outputfile) - if lastline: - m = re.search('return code \[([0-9]+)\]', lastline) - if m: - return int(m.group(1)) - - # woops, we couldn't find an end of line/return value - return None - - # external function - # returns: - # success: True - # failure: False - def pushFile(self, localname, destname): - if (os.name == "nt"): - destname = destname.replace('\\', '/') - - if (self.debug >= 3): print "in push file with: " + localname + ", and: " + destname - if (self.dirExists(destname)): - if (not destname.endswith('/')): - destname = destname + '/' - destname = destname + os.path.basename(localname) - if (self.validateFile(destname, localname) == True): - if (self.debug >= 3): print "files are validated" - return True - - if self.mkDirs(destname) == None: - print "unable to make dirs: " + destname - return False - - if (self.debug >= 3): print "sending: push " + destname - - filesize = os.path.getsize(localname) - f = open(localname, 'rb') - data = f.read() - f.close() - - try: - retVal = self.runCmds([{ 'cmd': 'push ' + destname + ' ' + str(filesize), - 'data': data }]) - except AgentError, e: - print "error pushing file: %s" % e.msg - return False - - if (self.debug >= 3): print "push returned: " + str(retVal) - - validated = False - if (retVal): - retline = retVal.strip() - if (retline == None): - # Then we failed to get back a hash from agent, try manual validation - validated = self.validateFile(destname, localname) - else: - # Then we obtained a hash from push - localHash = self.getLocalHash(localname) - if (str(localHash) == str(retline)): - validated = True - else: - # We got nothing back from sendCMD, try manual validation - validated = self.validateFile(destname, localname) - - if (validated): - if (self.debug >= 3): print "Push File Validated!" - return True - else: - if (self.debug >= 2): print "Push File Failed to Validate!" - return False - - # external function - # returns: - # success: directory name - # failure: None - def mkDir(self, name): - if (self.dirExists(name)): - return name - else: - try: - retVal = self.runCmds([{ 'cmd': 'mkdr ' + name }]) - except AgentError: - retVal = None - return retVal - - # push localDir from host to remoteDir on the device - # external function - # returns: - # success: remoteDir - # failure: None - def pushDir(self, localDir, remoteDir): - if (self.debug >= 2): print "pushing directory: %s to %s" % (localDir, remoteDir) - for root, dirs, files in os.walk(localDir, followlinks=True): - parts = root.split(localDir) - for file in files: - remoteRoot = remoteDir + '/' + parts[1] - if (remoteRoot.endswith('/')): - remoteName = remoteRoot + file - else: - remoteName = remoteRoot + '/' + file - if (parts[1] == ""): remoteRoot = remoteDir - if (self.pushFile(os.path.join(root, file), remoteName) == False): - # retry once - self.removeFile(remoteName) - if (self.pushFile(os.path.join(root, file), remoteName) == False): - return None - return remoteDir - - # external function - # returns: - # success: True - # failure: False - def dirExists(self, dirname): - match = ".*" + dirname.replace('^', '\^') + "$" - dirre = re.compile(match) - try: - data = self.runCmds([ { 'cmd': 'cd ' + dirname }, { 'cmd': 'cwd' }]) - except AgentError: - return False - - found = False - for d in data.splitlines(): - if (dirre.match(d)): - found = True - - return found - - # Because we always have / style paths we make this a lot easier with some - # assumptions - # external function - # returns: - # success: True - # failure: False - def fileExists(self, filepath): - s = filepath.split('/') - containingpath = '/'.join(s[:-1]) - listfiles = self.listFiles(containingpath) - for f in listfiles: - if (f == s[-1]): - return True - return False - - # list files on the device, requires cd to directory first - # external function - # returns: - # success: array of filenames, ['file1', 'file2', ...] - # failure: [] - def listFiles(self, rootdir): - rootdir = rootdir.rstrip('/') - if (self.dirExists(rootdir) == False): - return [] - try: - data = self.runCmds([{ 'cmd': 'cd ' + rootdir }, { 'cmd': 'ls' }]) - except AgentError: - return [] - - files = filter(lambda x: x, data.splitlines()) - if len(files) == 1 and files[0] == '<empty>': - # special case on the agent: empty directories return just the string "<empty>" - return [] - return files - - # external function - # returns: - # success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt" - # failure: None - def removeFile(self, filename): - if (self.debug>= 2): print "removing file: " + filename - try: - retVal = self.runCmds([{ 'cmd': 'rm ' + filename }]) - except AgentError: - return None - - return retVal - - # does a recursive delete of directory on the device: rm -Rf remoteDir - # external function - # returns: - # success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt" - # failure: None - def removeDir(self, remoteDir): - try: - retVal = self.runCmds([{ 'cmd': 'rmdr ' + remoteDir }]) - except AgentError: - return None - - return retVal - - # external function - # returns: - # success: array of process tuples - # failure: [] - def getProcessList(self): - try: - data = self.runCmds([{ 'cmd': 'ps' }]) - except AgentError: - return [] - - files = [] - for line in data.splitlines(): - if line: - pidproc = line.strip().split() - if (len(pidproc) == 2): - files += [[pidproc[0], pidproc[1]]] - elif (len(pidproc) == 3): - #android returns <userID> <procID> <procName> - files += [[pidproc[1], pidproc[2], pidproc[0]]] - return files - - # external function - # DEPRECATED: Use shell() or launchApplication() for new code - # returns: - # success: pid - # failure: None - def fireProcess(self, appname, failIfRunning=False): - if (not appname): - if (self.debug >= 1): print "WARNING: fireProcess called with no command to run" - return None - - if (self.debug >= 2): print "FIRE PROC: '" + appname + "'" - - if (self.processExist(appname) != None): - print "WARNING: process %s appears to be running already\n" % appname - if (failIfRunning): - return None - - try: - self.runCmds([{ 'cmd': 'exec ' + appname }]) - except AgentError: - return None - - # The 'exec' command may wait for the process to start and end, so checking - # for the process here may result in process = None. - process = self.processExist(appname) - if (self.debug >= 4): print "got pid: %s for process: %s" % (process, appname) - - return process - - # external function - # DEPRECATED: Use shell() or launchApplication() for new code - # returns: - # success: output filename - # failure: None - def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False): - if not cmd: - if (self.debug >= 1): print "WARNING: launchProcess called without command to run" - return None - - cmdline = subprocess.list2cmdline(cmd) - if (outputFile == "process.txt" or outputFile == None): - outputFile = self.getDeviceRoot(); - if outputFile is None: - return None - outputFile += "/process.txt" - cmdline += " > " + outputFile - - # Prepend our env to the command - cmdline = '%s %s' % (self.formatEnvString(env), cmdline) - - if self.fireProcess(cmdline, failIfRunning) is None: - return None - return outputFile - - # external function - # returns: - # success: True - # failure: False - def killProcess(self, appname, forceKill=False): - if forceKill: - print "WARNING: killProcess(): forceKill parameter unsupported on SUT" - try: - self.runCmds([{ 'cmd': 'kill ' + appname }]) - except AgentError: - return False - - return True - - # external function - # returns: - # success: tmpdir, string - # failure: None - def getTempDir(self): - try: - data = self.runCmds([{ 'cmd': 'tmpd' }]) - except AgentError: - return None - - return data.strip() - - # external function - # returns: - # success: filecontents - # failure: None - def catFile(self, remoteFile): - try: - data = self.runCmds([{ 'cmd': 'cat ' + remoteFile }]) - except AgentError: - return None - - return data - - # external function - # returns: - # success: output of pullfile, string - # failure: None - def pullFile(self, remoteFile): - """Returns contents of remoteFile using the "pull" command. - The "pull" command is different from other commands in that DeviceManager - has to read a certain number of bytes instead of just reading to the - next prompt. This is more robust than the "cat" command, which will be - confused if the prompt string exists within the file being catted. - However it means we can't use the response-handling logic in sendCMD(). - """ - - def err(error_msg): - err_str = 'error returned from pull: %s' % error_msg - print err_str - self._sock = None - raise FileError(err_str) - - # FIXME: We could possibly move these socket-reading functions up to - # the class level if we wanted to refactor sendCMD(). For now they are - # only used to pull files. - - def uread(to_recv, error_msg): - """ unbuffered read """ - try: - data = self._sock.recv(to_recv) - if not data: - err(error_msg) - return None - return data - except: - err(error_msg) - return None - - def read_until_char(c, buffer, error_msg): - """ read until 'c' is found; buffer rest """ - while not '\n' in buffer: - data = uread(1024, error_msg) - if data == None: - err(error_msg) - return ('', '', '') - buffer += data - return buffer.partition(c) - - def read_exact(total_to_recv, buffer, error_msg): - """ read exact number of 'total_to_recv' bytes """ - while len(buffer) < total_to_recv: - to_recv = min(total_to_recv - len(buffer), 1024) - data = uread(to_recv, error_msg) - if data == None: - return None - buffer += data - return buffer - - prompt = self.base_prompt + self.prompt_sep - buffer = '' - - # expected return value: - # <filename>,<filesize>\n<filedata> - # or, if error, - # <filename>,-1\n<error message> - try: - # just send the command first, we read the response inline below - self.runCmds([{ 'cmd': 'pull ' + remoteFile }]) - except AgentError: - return None - - # read metadata; buffer the rest - metadata, sep, buffer = read_until_char('\n', buffer, 'could not find metadata') - if not metadata: - return None - if self.debug >= 3: - print 'metadata: %s' % metadata - - filename, sep, filesizestr = metadata.partition(',') - if sep == '': - err('could not find file size in returned metadata') - return None - try: - filesize = int(filesizestr) - except ValueError: - err('invalid file size in returned metadata') - return None - - if filesize == -1: - # read error message - error_str, sep, buffer = read_until_char('\n', buffer, 'could not find error message') - if not error_str: - return None - # prompt should follow - read_exact(len(prompt), buffer, 'could not find prompt') - print "DeviceManager: error pulling file '%s': %s" % (remoteFile, error_str) - return None - - # read file data - total_to_recv = filesize + len(prompt) - buffer = read_exact(total_to_recv, buffer, 'could not get all file data') - if buffer == None: - return None - if buffer[-len(prompt):] != prompt: - err('no prompt found after file data--DeviceManager may be out of sync with agent') - return buffer - return buffer[:-len(prompt)] - - # copy file from device (remoteFile) to host (localFile) - # external function - # returns: - # success: output of pullfile, string - # failure: None - def getFile(self, remoteFile, localFile = ''): - if localFile == '': - localFile = os.path.join(self.tempRoot, "temp.txt") - - try: - retVal = self.pullFile(remoteFile) - except: - return None - - if (retVal is None): - return None - - fhandle = open(localFile, 'wb') - fhandle.write(retVal) - fhandle.close() - if not self.validateFile(remoteFile, localFile): - print 'failed to validate file when downloading %s!' % remoteFile - return None - return retVal - - # copy directory structure from device (remoteDir) to host (localDir) - # external function - # checkDir exists so that we don't create local directories if the - # remote directory doesn't exist but also so that we don't call isDir - # twice when recursing. - # returns: - # success: list of files, string - # failure: None - def getDirectory(self, remoteDir, localDir, checkDir=True): - if (self.debug >= 2): print "getting files in '" + remoteDir + "'" - if checkDir: - try: - is_dir = self.isDir(remoteDir) - except FileError: - return None - if not is_dir: - return None - - filelist = self.listFiles(remoteDir) - if (self.debug >= 3): print filelist - if not os.path.exists(localDir): - os.makedirs(localDir) - - for f in filelist: - if f == '.' or f == '..': - continue - remotePath = remoteDir + '/' + f - localPath = os.path.join(localDir, f) - try: - is_dir = self.isDir(remotePath) - except FileError: - print 'isdir failed on file "%s"; continuing anyway...' % remotePath - continue - if is_dir: - if (self.getDirectory(remotePath, localPath, False) == None): - print 'failed to get directory "%s"' % remotePath - return None - else: - # It's sometimes acceptable to have getFile() return None, such as - # when the agent encounters broken symlinks. - # FIXME: This should be improved so we know when a file transfer really - # failed. - if self.getFile(remotePath, localPath) == None: - print 'failed to get file "%s"; continuing anyway...' % remotePath - return filelist - - # external function - # returns: - # success: True - # failure: False - # Throws a FileError exception when null (invalid dir/filename) - def isDir(self, remotePath): - try: - data = self.runCmds([{ 'cmd': 'isdir ' + remotePath }]) - except AgentError: - # normally there should be no error here; a nonexistent file/directory will - # return the string "<filename>: No such file or directory". - # However, I've seen AGENT-WARNING returned before. - return False - - retVal = data.strip() - if not retVal: - raise FileError('isdir returned null') - return retVal == 'TRUE' - - # true/false check if the two files have the same md5 sum - # external function - # returns: - # success: True - # failure: False - def validateFile(self, remoteFile, localFile): - remoteHash = self.getRemoteHash(remoteFile) - localHash = self.getLocalHash(localFile) - - if (remoteHash == None): - return False - - if (remoteHash == localHash): - return True - - return False - - # return the md5 sum of a remote file - # internal function - # returns: - # success: MD5 hash for given filename - # failure: None - def getRemoteHash(self, filename): - try: - data = self.runCmds([{ 'cmd': 'hash ' + filename }]) - except AgentError: - return None - - retVal = None - if data: - retVal = data.strip() - if self.debug >= 3: - print "remote hash returned: '%s'" % retVal - return retVal - - # Gets the device root for the testing area on the device - # For all devices we will use / type slashes and depend on the device-agent - # to sort those out. The agent will return us the device location where we - # should store things, we will then create our /tests structure relative to - # that returned path. - # Structure on the device is as follows: - # /tests - # /<fennec>|<firefox> --> approot - # /profile - # /xpcshell - # /reftest - # /mochitest - # - # external function - # returns: - # success: path for device root - # failure: None - def getDeviceRoot(self): - if self.deviceRoot: - deviceRoot = self.deviceRoot - else: - try: - data = self.runCmds([{ 'cmd': 'testroot' }]) - except: - return None - - deviceRoot = data.strip() + '/tests' - - if (not self.dirExists(deviceRoot)): - if (self.mkDir(deviceRoot) == None): - return None - - self.deviceRoot = deviceRoot - return self.deviceRoot - - def getAppRoot(self, packageName): - try: - data = self.runCmds([{ 'cmd': 'getapproot '+packageName }]) - except: - return None - - return data.strip() - - # external function - # returns: - # success: output of unzip command - # failure: None - def unpackFile(self, file_path, dest_dir=None): - devroot = self.getDeviceRoot() - if (devroot == None): - return None - - # if no dest_dir is passed in just set it to file_path's folder - if not dest_dir: - dest_dir = posixpath.dirname(file_path) - - if dest_dir[-1] != '/': - dest_dir += '/' - - try: - data = self.runCmds([{ 'cmd': 'unzp %s %s' % (file_path, dest_dir)}]) - except AgentError: - return None - - return data - - # external function - # returns: - # success: status from test agent - # failure: None - def reboot(self, ipAddr=None, port=30000): - cmd = 'rebt' - - if (self.debug > 3): print "INFO: sending rebt command" - - if (ipAddr is not None): - #create update.info file: - try: - destname = '/data/data/com.mozilla.SUTAgentAndroid/files/update.info' - data = "%s,%s\rrebooting\r" % (ipAddr, port) - self.runCmds([{ 'cmd': 'push %s %s' % (destname, len(data)), - 'data': data }]) - except AgentError: - return None - - ip, port = self.getCallbackIpAndPort(ipAddr, port) - cmd += " %s %s" % (ip, port) - # Set up our callback server - callbacksvr = callbackServer(ip, port, self.debug) - - try: - status = self.runCmds([{ 'cmd': cmd }]) - except AgentError: - return None - - if (ipAddr is not None): - status = callbacksvr.disconnect() - - if (self.debug > 3): print "INFO: rebt- got status back: " + str(status) - return status - - # Returns information about the device: - # Directive indicates the information you want to get, your choices are: - # os - name of the os - # id - unique id of the device - # uptime - uptime of the device - # uptimemillis - uptime of the device in milliseconds (SUTAgent 1.11+) - # systime - system time of the device - # screen - screen resolution - # rotation - rotation of the device (in degrees) - # memory - memory stats - # process - list of running processes (same as ps) - # disk - total, free, available bytes on disk - # power - power status (charge, battery temp) - # all - all of them - or call it with no parameters to get all the information - # returns: - # success: dict of info strings by directive name - # failure: {} - def getInfo(self, directive=None): - data = None - result = {} - collapseSpaces = re.compile(' +') - - directives = ['os','id','uptime','uptimemillis','systime','screen', - 'rotation','memory','process','disk','power'] - if (directive in directives): - directives = [directive] - - for d in directives: - try: - data = self.runCmds([{ 'cmd': 'info ' + d }]) - except AgentError: - return result - - if (data is None): - continue - data = collapseSpaces.sub(' ', data) - result[d] = data.split('\n') - - # Get rid of any 0 length members of the arrays - for k, v in result.iteritems(): - result[k] = filter(lambda x: x != '', result[k]) - - # Format the process output - if 'process' in result: - proclist = [] - for l in result['process']: - if l: - proclist.append(l.split('\t')) - result['process'] = proclist - - if (self.debug >= 3): print "results: " + str(result) - return result - - """ - Installs the application onto the device - Application bundle - path to the application bundle on the device - Destination - destination directory of where application should be - installed to (optional) - Returns None for success, or output if known failure - """ - # external function - # returns: - # success: output from agent for inst command - # failure: None - def installApp(self, appBundlePath, destPath=None): - cmd = 'inst ' + appBundlePath - if destPath: - cmd += ' ' + destPath - try: - data = self.runCmds([{ 'cmd': cmd }]) - except AgentError: - return None - - f = re.compile('Failure') - for line in data.split(): - if (f.match(line)): - return data - return None - - """ - Uninstalls the named application from device and causes a reboot. - Takes an optional argument of installation path - the path to where the application - was installed. - Returns True, but it doesn't mean anything other than the command was sent, - the reboot happens and we don't know if this succeeds or not. - """ - # external function - # returns: - # success: True - # failure: None - def uninstallAppAndReboot(self, appName, installPath=None): - cmd = 'uninst ' + appName - if installPath: - cmd += ' ' + installPath - try: - data = self.runCmds([{ 'cmd': cmd }]) - except AgentError: - return None - - if (self.debug > 3): print "uninstallAppAndReboot: " + str(data) - return True - - """ - Updates the application on the device. - Application bundle - path to the application bundle on the device - Process name of application - used to end the process if the applicaiton is - currently running - Destination - Destination directory to where the application should be - installed (optional) - ipAddr - IP address to await a callback ping to let us know that the device has updated - properly - defaults to current IP. - port - port to await a callback ping to let us know that the device has updated properly - defaults to 30000, and counts up from there if it finds a conflict - Returns True if succeeds, False if not - """ - # external function - # returns: - # success: text status from command or callback server - # failure: None - def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000): - status = None - cmd = 'updt ' - if (processName == None): - # Then we pass '' for processName - cmd += "'' " + appBundlePath - else: - cmd += processName + ' ' + appBundlePath - - if (destPath): - cmd += " " + destPath - - if (ipAddr is not None): - ip, port = self.getCallbackIpAndPort(ipAddr, port) - cmd += " %s %s" % (ip, port) - # Set up our callback server - callbacksvr = callbackServer(ip, port, self.debug) - - if (self.debug >= 3): print "INFO: updateApp using command: " + str(cmd) - - try: - status = self.runCmds([{ 'cmd': cmd }]) - except AgentError: - return None - - if ipAddr is not None: - status = callbacksvr.disconnect() - - if (self.debug >= 3): print "INFO: updateApp: got status back: " + str(status) - - return status - - """ - return the current time on the device - """ - # external function - # returns: - # success: time in ms - # failure: None - def getCurrentTime(self): - try: - data = self.runCmds([{ 'cmd': 'clok' }]) - except AgentError: - return None - - return data.strip() - - """ - Connect the ipaddress and port for a callback ping. Defaults to current IP address - And ports starting at 30000. - NOTE: the detection for current IP address only works on Linux! - """ - def getCallbackIpAndPort(self, aIp, aPort): - ip = aIp - nettools = NetworkTools() - if (ip == None): - ip = nettools.getLanIp() - if (aPort != None): - port = nettools.findOpenPort(ip, aPort) - else: - port = nettools.findOpenPort(ip, 30000) - return ip, port - - """ - Returns a properly formatted env string for the agent. - Input - env, which is either None, '', or a dict - Output - a quoted string of the form: '"envvar1=val1,envvar2=val2..."' - If env is None or '' return '' (empty quoted string) - """ - def formatEnvString(self, env): - if (env == None or env == ''): - return '' - - retVal = '"%s"' % ','.join(map(lambda x: '%s=%s' % (x[0], x[1]), env.iteritems())) - if (retVal == '""'): - return '' - - return retVal - - """ - adjust the screen resolution on the device, REBOOT REQUIRED - NOTE: this only works on a tegra ATM - success: True - failure: False - - supported resolutions: 640x480, 800x600, 1024x768, 1152x864, 1200x1024, 1440x900, 1680x1050, 1920x1080 - """ - def adjustResolution(self, width=1680, height=1050, type='hdmi'): - if self.getInfo('os')['os'][0].split()[0] != 'harmony-eng': - if (self.debug >= 2): print "WARNING: unable to adjust screen resolution on non Tegra device" - return False - - results = self.getInfo('screen') - parts = results['screen'][0].split(':') - if (self.debug >= 3): print "INFO: we have a current resolution of %s, %s" % (parts[1].split()[0], parts[2].split()[0]) - - #verify screen type is valid, and set it to the proper value (https://bugzilla.mozilla.org/show_bug.cgi?id=632895#c4) - screentype = -1 - if (type == 'hdmi'): - screentype = 5 - elif (type == 'vga' or type == 'crt'): - screentype = 3 - else: - return False - - #verify we have numbers - if not (isinstance(width, int) and isinstance(height, int)): - return False - - if (width < 100 or width > 9999): - return False - - if (height < 100 or height > 9999): - return False - - if (self.debug >= 3): print "INFO: adjusting screen resolution to %s, %s and rebooting" % (width, height) - try: - self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width) }]) - self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height) }]) - except AgentError: - return False - - return True - - # external function - # returns: - # success: True - # failure: False - def chmodDir(self, remoteDir): - try: - self.runCmds([{ 'cmd': "chmod "+remoteDir }]) - except AgentError: - return False - return True - -gCallbackData = '' - -class myServer(SocketServer.TCPServer): - allow_reuse_address = True - -class callbackServer(): - def __init__(self, ip, port, debuglevel): - global gCallbackData - if (debuglevel >= 1): print "DEBUG: gCallbackData is: %s on port: %s" % (gCallbackData, port) - gCallbackData = '' - self.ip = ip - self.port = port - self.connected = False - self.debug = debuglevel - if (self.debug >= 3): print "Creating server with " + str(ip) + ":" + str(port) - self.server = myServer((ip, port), self.myhandler) - self.server_thread = Thread(target=self.server.serve_forever) - self.server_thread.setDaemon(True) - self.server_thread.start() - - def disconnect(self, step = 60, timeout = 600): - t = 0 - if (self.debug >= 3): print "Calling disconnect on callback server" - while t < timeout: - if (gCallbackData): - # Got the data back - if (self.debug >= 3): print "Got data back from agent: " + str(gCallbackData) - break - else: - if (self.debug >= 0): print '.', - time.sleep(step) - t += step - - try: - if (self.debug >= 3): print "Shutting down server now" - self.server.shutdown() - except: - if (self.debug >= 1): print "Unable to shutdown callback server - check for a connection on port: " + str(self.port) - - #sleep 1 additional step to ensure not only we are online, but all our services are online - time.sleep(step) - return gCallbackData - - class myhandler(SocketServer.BaseRequestHandler): - def handle(self): - global gCallbackData - gCallbackData = self.request.recv(1024) - #print "Callback Handler got data: " + str(gCallbackData) - self.request.send("OK") -
deleted file mode 100644 --- a/build/mobile/droid.py +++ /dev/null @@ -1,83 +0,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/. - -from devicemanagerADB import DeviceManagerADB -from devicemanagerSUT import DeviceManagerSUT -import StringIO - -class DroidMixin(object): - """Mixin to extend DeviceManager with Android-specific functionality""" - - def launchApplication(self, appName, activityName, intent, url=None, - extras=None): - """ - Launches an Android application - returns: - success: True - failure: False - """ - # only one instance of an application may be running at once - if self.processExist(appName): - return False - - acmd = [ "am", "start", "-W", "-n", "%s/%s" % (appName, activityName)] - - if intent: - acmd.extend(["-a", intent]) - - if extras: - for (key, val) in extras.iteritems(): - if type(val) is int: - extraTypeParam = "--ei" - elif type(val) is bool: - extraTypeParam = "--ez" - else: - extraTypeParam = "--es" - acmd.extend([extraTypeParam, str(key), str(val)]) - - if url: - acmd.extend(["-d", url]) - - # shell output not that interesting and debugging logs should already - # show what's going on here... so just create an empty memory buffer - # and ignore - shellOutput = StringIO.StringIO() - if self.shell(acmd, shellOutput) == 0: - return True - - return False - - def launchFennec(self, appName, intent="android.intent.action.VIEW", - mozEnv=None, extraArgs=None, url=None): - """ - Convenience method to launch Fennec on Android with various debugging - arguments - WARNING: FIXME: This would go better in mozrunner. Please do not - use this method if you are not comfortable with it going away sometime - in the near future - returns: - success: True - failure: False - """ - extras = {} - - if mozEnv: - # mozEnv is expected to be a dictionary of environment variables: Fennec - # itself will set them when launched - for (envCnt, (envkey, envval)) in enumerate(mozEnv.iteritems()): - extras["env" + str(envCnt)] = envkey + "=" + envval - - # Additional command line arguments that fennec will read and use (e.g. - # with a custom profile) - if extraArgs: - extras['args'] = " ".join(extraArgs) - - return self.launchApplication(appName, ".App", intent, url=url, - extras=extras) - -class DroidADB(DeviceManagerADB, DroidMixin): - pass - -class DroidSUT(DeviceManagerSUT, DroidMixin): - pass
deleted file mode 100644 --- a/build/mobile/emulator.py +++ /dev/null @@ -1,296 +0,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/. - -from abc import abstractmethod -import datetime -from mozprocess import ProcessHandlerMixin -import multiprocessing -import os -import re -import shutil -import socket -import subprocess -from telnetlib import Telnet -import tempfile -import time - -from emulator_battery import EmulatorBattery - - -class LogcatProc(ProcessHandlerMixin): - """Process handler for logcat which saves all output to a logfile. - """ - - def __init__(self, logfile, cmd, **kwargs): - self.logfile = logfile - kwargs.setdefault('processOutputLine', []).append(self.log_output) - ProcessHandlerMixin.__init__(self, cmd, **kwargs) - - def log_output(self, line): - f = open(self.logfile, 'a') - f.write(line + "\n") - f.flush() - - -class Emulator(object): - - deviceRe = re.compile(r"^emulator-(\d+)(\s*)(.*)$") - - def __init__(self, noWindow=False, logcat_dir=None, arch="x86", - emulatorBinary=None, res='480x800', userdata=None, - memory='512', partition_size='512'): - self.port = None - self._emulator_launched = False - self.proc = None - self.local_port = None - self.telnet = None - self._tmp_userdata = None - self._adb_started = False - self.logcat_dir = logcat_dir - self.logcat_proc = None - self.arch = arch - self.binary = emulatorBinary - self.memory = str(memory) - self.partition_size = str(partition_size) - self.res = res - self.battery = EmulatorBattery(self) - self.noWindow = noWindow - self.dataImg = userdata - self.copy_userdata = self.dataImg is None - - def __del__(self): - if self.telnet: - self.telnet.write('exit\n') - self.telnet.read_all() - - @property - def args(self): - qemuArgs = [self.binary, - '-kernel', self.kernelImg, - '-sysdir', self.sysDir, - '-data', self.dataImg] - if self.noWindow: - qemuArgs.append('-no-window') - qemuArgs.extend(['-memory', self.memory, - '-partition-size', self.partition_size, - '-verbose', - '-skin', self.res, - '-gpu', 'on', - '-qemu'] + self.tail_args) - return qemuArgs - - @property - def is_running(self): - if self._emulator_launched: - return self.proc is not None and self.proc.poll() is None - else: - return self.port is not None - - def _default_adb(self): - adb = subprocess.Popen(['which', 'adb'], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - retcode = adb.wait() - if retcode == 0: - self.adb = adb.stdout.read().strip() # remove trailing newline - return retcode - - def _check_for_adb(self): - if not os.path.exists(self.adb): - if self._default_adb() != 0: - raise Exception('adb not found!') - - def _run_adb(self, args): - args.insert(0, self.adb) - adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - retcode = adb.wait() - if retcode: - raise Exception('adb terminated with exit code %d: %s' - % (retcode, adb.stdout.read())) - return adb.stdout.read() - - def _get_telnet_response(self, command=None): - output = [] - assert(self.telnet) - if command is not None: - self.telnet.write('%s\n' % command) - while True: - line = self.telnet.read_until('\n') - output.append(line.rstrip()) - if line.startswith('OK'): - return output - elif line.startswith('KO:'): - raise Exception('bad telnet response: %s' % line) - - def _run_telnet(self, command): - if not self.telnet: - self.telnet = Telnet('localhost', self.port) - self._get_telnet_response() - return self._get_telnet_response(command) - - def close(self): - if self.is_running and self._emulator_launched: - self.proc.terminate() - self.proc.wait() - if self._adb_started: - self._run_adb(['kill-server']) - self._adb_started = False - if self.proc: - retcode = self.proc.poll() - self.proc = None - if self._tmp_userdata: - os.remove(self._tmp_userdata) - self._tmp_userdata = None - return retcode - if self.logcat_proc: - self.logcat_proc.kill() - return 0 - - def _get_adb_devices(self): - offline = set() - online = set() - output = self._run_adb(['devices']) - for line in output.split('\n'): - m = self.deviceRe.match(line) - if m: - if m.group(3) == 'offline': - offline.add(m.group(1)) - else: - online.add(m.group(1)) - return (online, offline) - - def restart(self): - if not self._emulator_launched: - return - self.close() - self.start() - - def start_adb(self): - result = self._run_adb(['start-server']) - # We keep track of whether we've started adb or not, so we know - # if we need to kill it. - if 'daemon started successfully' in result: - self._adb_started = True - else: - self._adb_started = False - - def connect(self): - self._check_for_adb() - self.start_adb() - - online, offline = self._get_adb_devices() - now = datetime.datetime.now() - while online == set([]): - time.sleep(1) - if datetime.datetime.now() - now > datetime.timedelta(seconds=60): - raise Exception('timed out waiting for emulator to be available') - online, offline = self._get_adb_devices() - self.port = int(list(online)[0]) - - @abstractmethod - def _locate_files(self): - pass - - def start(self): - self._locate_files() - self.start_adb() - - qemu_args = self.args[:] - if self.copy_userdata: - # Make a copy of the userdata.img for this instance of the emulator - # to use. - self._tmp_userdata = tempfile.mktemp(prefix='emulator') - shutil.copyfile(self.dataImg, self._tmp_userdata) - qemu_args[qemu_args.index('-data') + 1] = self._tmp_userdata - - original_online, original_offline = self._get_adb_devices() - - self.proc = subprocess.Popen(qemu_args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - online, offline = self._get_adb_devices() - now = datetime.datetime.now() - while online - original_online == set([]): - time.sleep(1) - if datetime.datetime.now() - now > datetime.timedelta(seconds=60): - raise Exception('timed out waiting for emulator to start') - online, offline = self._get_adb_devices() - self.port = int(list(online - original_online)[0]) - self._emulator_launched = True - - if self.logcat_dir: - self.save_logcat() - - # setup DNS fix for networking - self._run_adb(['-s', 'emulator-%d' % self.port, - 'shell', 'setprop', 'net.dns1', '10.0.2.3']) - - def _save_logcat_proc(self, filename, cmd): - self.logcat_proc = LogcatProc(filename, cmd) - self.logcat_proc.run() - self.logcat_proc.waitForFinish() - self.logcat_proc = None - - def rotate_log(self, srclog, index=1): - """ Rotate a logfile, by recursively rotating logs further in the sequence, - deleting the last file if necessary. - """ - destlog = os.path.join(self.logcat_dir, 'emulator-%d.%d.log' % (self.port, index)) - if os.path.exists(destlog): - if index == 3: - os.remove(destlog) - else: - self.rotate_log(destlog, index + 1) - shutil.move(srclog, destlog) - - def save_logcat(self): - """ Save the output of logcat to a file. - """ - filename = os.path.join(self.logcat_dir, "emulator-%d.log" % self.port) - if os.path.exists(filename): - self.rotate_log(filename) - cmd = [self.adb, '-s', 'emulator-%d' % self.port, 'logcat'] - - # We do this in a separate process because we call mozprocess's - # waitForFinish method to process logcat's output, and this method - # blocks. - proc = multiprocessing.Process(target=self._save_logcat_proc, args=(filename, cmd)) - proc.daemon = True - proc.start() - - def setup_port_forwarding(self, remote_port): - """ Set up TCP port forwarding to the specified port on the device, - using any availble local port, and return the local port. - """ - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(("", 0)) - local_port = s.getsockname()[1] - s.close() - - self._run_adb(['-s', 'emulator-%d' % self.port, 'forward', - 'tcp:%d' % local_port, - 'tcp:%d' % remote_port]) - - self.local_port = local_port - - return local_port - - def wait_for_port(self, timeout=300): - assert(self.local_port) - starttime = datetime.datetime.now() - while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout): - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(('localhost', self.local_port)) - data = sock.recv(16) - sock.close() - if '"from"' in data: - return True - except: - import traceback - print traceback.format_exc() - time.sleep(1) - return False
deleted file mode 100644 --- a/build/mobile/emulator_battery.py +++ /dev/null @@ -1,52 +0,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/. - -class EmulatorBattery(object): - - def __init__(self, emulator): - self.emulator = emulator - - def get_state(self): - status = {} - state = {} - - response = self.emulator._run_telnet('power display') - for line in response: - if ':' in line: - field, value = line.split(':') - value = value.strip() - if value == 'true': - value = True - elif value == 'false': - value = False - elif field == 'capacity': - value = float(value) - status[field] = value - - state['level'] = status.get('capacity', 0.0) / 100 - if status.get('AC') == 'online': - state['charging'] = True - else: - state['charging'] = False - - return state - - def get_charging(self): - return self.get_state()['charging'] - - def get_level(self): - return self.get_state()['level'] - - def set_level(self, level): - self.emulator._run_telnet('power capacity %d' % (level * 100)) - - def set_charging(self, charging): - if charging: - cmd = 'power ac on' - else: - cmd = 'power ac off' - self.emulator._run_telnet(cmd) - - charging = property(get_charging, set_charging) - level = property(get_level, set_level)
--- a/build/mobile/sutagent/android/AndroidManifest.xml +++ b/build/mobile/sutagent/android/AndroidManifest.xml @@ -12,18 +12,17 @@ android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".SUTStartupIntentReceiver"> <intent-filter> - <action android:value="android.intent.action.BOOT_COMPLETED" android:name="android.intent.action.BOOT_COMPLETED"/> - <category android:value="android.intent.category.HOME" android:name="android.intent.category.HOME"/> + <action android:name="android.intent.action.MEDIA_MOUNTED"></action> </intent-filter> </receiver> <service android:name=".service.ASMozStub"> <intent-filter> <action android:name="com.mozilla.SUTAgentAndroid.service.LISTENER_SERVICE" /> </intent-filter> </service> </application> @@ -53,9 +52,9 @@ <uses-permission android:name="android.permission.STATUS_BAR"></uses-permission> <uses-permission android:name="android.permission.VIBRATE"></uses-permission> <uses-permission android:name="android.permission.SET_TIME"></uses-permission> <uses-permission android:name="android.permission.SET_TIME_ZONE"></uses-permission> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"></uses-permission> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"></uses-permission> -</manifest> \ No newline at end of file +</manifest>
--- a/build/mobile/sutagent/android/watcher/WatcherService.java +++ b/build/mobile/sutagent/android/watcher/WatcherService.java @@ -186,19 +186,25 @@ public class WatcherService extends Serv { sComp = e.toString(); } return (sRet); } private void handleCommand(Intent intent) { - String sCmd = intent.getStringExtra("command"); + // Note: intent can be null "if the service is being restarted after its process + // has gone away". In this case, we will consider that to be equivalent to a start + // http://developer.android.com/reference/android/app/Service.html#onStartCommand%28android.content.Intent,%20int,%20int%29 -// Debug.waitForDebugger(); + String sCmd = "start"; + if (intent != null) + { + sCmd = intent.getStringExtra("command"); + } if (sCmd != null) { if (sCmd.equalsIgnoreCase("updt")) { String sPkgName = intent.getStringExtra("pkgName"); String sPkgFile = intent.getStringExtra("pkgFile"); String sOutFile = intent.getStringExtra("outFile");
--- a/build/pymake/pymake/process.py +++ b/build/pymake/pymake/process.py @@ -259,18 +259,18 @@ class PythonJob(Job): print >>sys.stderr, e return e.exitcode except: e = sys.exc_info()[1] if isinstance(e, SystemExit) and (e.code == 0 or e.code is None): pass # sys.exit(0) is not a failure else: print >>sys.stderr, e - print >>sys.stderr, traceback.print_exc() - return (e.code if isinstance(e.code, int) else 1) + traceback.print_exc() + return -127 finally: os.environ.clear() os.environ.update(oldenv) return 0 def job_runner(job): """ Run a job. Called in a Process pool.
new file mode 100644 --- /dev/null +++ b/build/pymake/tests/native-command-raise.mk @@ -0,0 +1,9 @@ +#T gmake skip +#T returncode: 2 +#T grep-for: "Exception: info-exception" + +CMD = %pycmd asplode_raise +PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir + +all: + @$(CMD) info-exception
--- a/build/pymake/tests/pycmd.py +++ b/build/pymake/tests/pycmd.py @@ -25,8 +25,11 @@ def convertasplode(arg): def asplode(args): arg0 = convertasplode(args[0]) sys.exit(arg0) def asplode_return(args): arg0 = convertasplode(args[0]) return arg0 + +def asplode_raise(args): + raise Exception(args[0])
--- a/caps/include/nsScriptSecurityManager.h +++ b/caps/include/nsScriptSecurityManager.h @@ -387,17 +387,17 @@ private: // GetScriptSecurityManager is the only call that can make one nsScriptSecurityManager(); virtual ~nsScriptSecurityManager(); static JSBool CheckObjectAccess(JSContext *cx, JSHandleObject obj, JSHandleId id, JSAccessMode mode, - jsval *vp); + JSMutableHandleValue vp); // Decides, based on CSP, whether or not eval() and stuff can be executed. static JSBool ContentSecurityPolicyPermitsJSAction(JSContext *cx); // Returns null if a principal cannot be found; generally callers // should error out at that point. static nsIPrincipal* doGetObjectPrincipal(JSObject *obj);
--- a/caps/src/nsScriptSecurityManager.cpp +++ b/caps/src/nsScriptSecurityManager.cpp @@ -527,35 +527,35 @@ nsScriptSecurityManager::ContentSecurity return evalOK; } JSBool nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSHandleObject obj, JSHandleId id, JSAccessMode mode, - jsval *vp) + JSMutableHandleValue vp) { // Get the security manager nsScriptSecurityManager *ssm = nsScriptSecurityManager::GetScriptSecurityManager(); NS_ASSERTION(ssm, "Failed to get security manager service"); if (!ssm) return JS_FALSE; // Get the object being accessed. We protect these cases: // 1. The Function.prototype.caller property's value, which might lead // an attacker up a call-stack to a function or another object from // a different trust domain. // 2. A user-defined getter or setter function accessible on another // trust domain's window or document object. - // *vp can be a primitive, in that case, we use obj as the target + // vp can be a primitive, in that case, we use obj as the target // object. - JSObject* target = JSVAL_IS_PRIMITIVE(*vp) ? obj : JSVAL_TO_OBJECT(*vp); + JSObject* target = JSVAL_IS_PRIMITIVE(vp) ? obj : JSVAL_TO_OBJECT(vp); // Do the same-origin check -- this sets a JS exception if the check fails. // Pass the parent object's class name, as we have no class-info for it. nsresult rv = ssm->CheckPropertyAccess(cx, target, js::GetObjectClass(obj)->name, id, (mode & JSACC_WRITE) ? (int32_t)nsIXPCSecurityManager::ACCESS_SET_PROPERTY : (int32_t)nsIXPCSecurityManager::ACCESS_GET_PROPERTY);
--- a/chrome/src/nsChromeRegistry.cpp +++ b/chrome/src/nsChromeRegistry.cpp @@ -81,19 +81,19 @@ nsChromeRegistry::LogMessageWithContext( va_end(args); if (!formatted) return; nsCString spec; if (aURL) aURL->GetSpec(spec); - rv = error->Init(NS_ConvertUTF8toUTF16(formatted).get(), - NS_ConvertUTF8toUTF16(spec).get(), - nullptr, + rv = error->Init(NS_ConvertUTF8toUTF16(formatted), + NS_ConvertUTF8toUTF16(spec), + EmptyString(), aLineNumber, 0, flags, "chrome registration"); PR_smprintf_free(formatted); if (NS_FAILED(rv)) return; console->LogMessage(error); }
--- a/configure.in +++ b/configure.in @@ -147,16 +147,21 @@ fi AC_SUBST(L10NBASEDIR) dnl Check for Perl first -- needed for win32 SDK checks MOZ_PATH_PROGS(PERL, $PERL perl5 perl ) if test -z "$PERL" -o "$PERL" = ":"; then AC_MSG_ERROR([perl not found in \$PATH]) fi +AC_SUBST(GAIADIR) +if test -n "$GAIADIR" ; then + AC_DEFINE(PACKAGE_GAIA) +fi + MOZ_ARG_WITH_STRING(gonk, [ --with-gonk=DIR location of gonk dir], gonkdir=$withval) MOZ_ARG_WITH_STRING(gonk-toolchain-prefix, [ --with-gonk-toolchain-prefix=DIR prefix to gonk toolchain commands],