author | Robert Sayre <sayrer@gmail.com> |
Sun, 26 Sep 2010 12:49:11 -0400 | |
changeset 54722 | 660c0c8a0d34a334fccfdfcdad11ee1dc1dda8ce |
parent 54721 | 3e7fbdbd0b2f8464f66ed2f2fd21359f8428ebbb (current diff) |
parent 54656 | 49cc66b9f097b4b36c900eb81f9c24d738842c37 (diff) |
child 54723 | 63066ec9dd8d8fa7d7c5e0ee55ebc15dd29b908d |
push id | 16011 |
push user | rsayre@mozilla.com |
push date | Wed, 29 Sep 2010 06:01:57 +0000 |
treeherder | mozilla-central@d7e659b4f80c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 2.0b7pre |
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
|
configure.in | file | annotate | diff | comparison | revisions | |
js/src/jsxml.cpp | file | annotate | diff | comparison | revisions | |
toolkit/crashreporter/google-breakpad/src/common/linux/memory.h | file | annotate | diff | comparison | revisions |
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -83,16 +83,21 @@ pref("browser.dictionaries.download.url" // default=10 minutes pref("app.update.timer", 600000); // App-specific update preferences // The interval to check for updates (app.update.interval) is defined in // firefox-branding.js +// Alternative windowtype for an application update user interface window. When +// a window with this windowtype is open the application update service won't +// open the normal application update user interface window. +pref("app.update.altwindowtype", "Browser:About"); + // Enables some extra Application Update Logging (can reduce performance) pref("app.update.log", false); // The number of general background check failures to allow before notifying the // user of the failure. User initiated update checks always notify the user of // the failure. pref("app.update.backgroundMaxErrors", 10);
--- a/browser/base/content/aboutDialog.css +++ b/browser/base/content/aboutDialog.css @@ -47,23 +47,44 @@ #distribution, #distributionId { font-weight: bold; display: none; margin-top: 0; margin-bottom: 0; } -#checkForUpdatesButton, .text-blurb { margin-bottom: 10px; -moz-margin-start: 0; -moz-padding-start: 0; } +#updateBox { + margin-bottom: 10px; +} + +#updateButton, +#updateDeck > hbox > label { + -moz-margin-start: 0; + -moz-padding-start: 0; +} + +#updateDeck > hbox > label:not([class="text-link"]) { + color: #909090; + font-style:italic; +} + +.update-throbber { + width: 16px; + min-height: 16px; + -moz-margin-end: 3px; + list-style-image: url("chrome://global/skin/icons/loading_16.png"); +} + .trademark-label, .text-link, .text-link:focus { margin: 0px; padding: 0px; } .bottom-link,
--- a/browser/base/content/aboutDialog.js +++ b/browser/base/content/aboutDialog.js @@ -16,16 +16,17 @@ # The Initial Developer of the Original Code is # Blake Ross (blaker@netscape.com). # Portions created by the Initial Developer are Copyright (C) 2002 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Ehsan Akhgari <ehsan.akhgari@gmail.com> # Margaret Leibovic <margaret.leibovic@gmail.com> +# Robert Strong <robert.bugzilla@gmail.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -60,30 +61,479 @@ function init(aEvent) distroIdField.style.display = "block"; } } catch (e) { // Pref is unset } #ifdef MOZ_UPDATER - initUpdates(); + gAppUpdater = new appUpdater(); #endif #ifdef XP_MACOSX // it may not be sized at this point, and we need its width to calculate its position window.sizeToContent(); window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5); #endif } #ifdef MOZ_UPDATER -/** - * Sets up "Check for Updates..." button. - */ -function initUpdates() +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/DownloadUtils.jsm"); +Components.utils.import("resource://gre/modules/AddonManager.jsm"); + +var gAppUpdater; + +function onUnload(aEvent) { + if (gAppUpdater.isChecking) + gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK); + // Safe to call even when there isn't a download in progress. + gAppUpdater.removeDownloadListener(); + gAppUpdater = null; +} + + +function appUpdater() +{ + this.updateDeck = document.getElementById("updateDeck"); + + // Hide the update deck when there is already an update window open to avoid + // syncing issues between them. + if (Services.wm.getMostRecentWindow("Update:Wizard")) { + this.updateDeck.hidden = true; + return; + } + + XPCOMUtils.defineLazyServiceGetter(this, "aus", + "@mozilla.org/updates/update-service;1", + "nsIApplicationUpdateService"); + XPCOMUtils.defineLazyServiceGetter(this, "checker", + "@mozilla.org/updates/update-checker;1", + "nsIUpdateChecker"); + XPCOMUtils.defineLazyServiceGetter(this, "um", + "@mozilla.org/updates/update-manager;1", + "nsIUpdateManager"); + XPCOMUtils.defineLazyServiceGetter(this, "bs", + "@mozilla.org/extensions/blocklist;1", + "nsIBlocklistService"); + + this.bundle = Services.strings. + createBundle("chrome://browser/locale/browser.properties"); + + this.updateBtn = document.getElementById("updateButton"); + + // The button label value must be set so its height is correct. + this.setupUpdateButton("update.checkInsideButton"); + + let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual"); + let manualLink = document.getElementById("manualLink"); + manualLink.value = manualURL; + manualLink.href = manualURL; + document.getElementById("failedLink").href = manualURL; + + if (this.updateDisabledAndLocked) { + this.selectPanel("adminDisabled"); + return; + } + + if (this.isPending) { + this.setupUpdateButton("update.restart." + + (this.isMajor ? "upgradeButton" : "applyButton")); + return; + } + + if (this.isDownloading) { + this.startDownload(); + return; + } + + if (this.updateEnabled && this.updateAuto) { + this.selectPanel("checkingForUpdates"); + this.isChecking = true; + this.checker.checkForUpdates(this.updateCheckListener, true); + return; + } +} + +appUpdater.prototype = { - var browserBundle = Services.strings. - createBundle("chrome://browser/locale/browser.properties"); - var checkForUpdates = document.getElementById("checkForUpdatesButton"); - setupCheckForUpdates(checkForUpdates, browserBundle); -} + // true when there is an update check in progress. + isChecking: false, + + // true when there is an update already staged / ready to be applied. + get isPending() { + if (this.update) + return this.update.state == "pending"; + return this.um.activeUpdate && this.um.activeUpdate.state == "pending"; + }, + + // true when there is an update download in progress. + get isDownloading() { + if (this.update) + return this.update.state == "downloading"; + return this.um.activeUpdate && + this.um.activeUpdate.state == "downloading"; + }, + + // true when the update type is major. + get isMajor() { + if (this.update) + return this.update.type == "major"; + return this.um.activeUpdate.type == "major"; + }, + + // true when updating is disabled by an administrator. + get updateDisabledAndLocked() { + return !this.updateEnabled && + Services.prefs.prefIsLocked("app.update.enabled"); + }, + + // true when updating is enabled. + get updateEnabled() { + try { + return Services.prefs.getBoolPref("app.update.enabled"); + } + catch (e) { } + return true; // Firefox default is true + }, + + // true when updating is automatic. + get updateAuto() { + try { + return Services.prefs.getBoolPref("app.update.auto"); + } + catch (e) { } + return true; // Firefox default is true + }, + + /** + * Sets the deck's selected panel. + * + * @param aChildID + * The id of the deck's child to select. + */ + selectPanel: function(aChildID) { + this.updateDeck.selectedPanel = document.getElementById(aChildID); + this.updateBtn.disabled = (aChildID != "updateButtonBox"); + }, + + /** + * Sets the update button's label and accesskey. + * + * @param aKeyPrefix + * The prefix for the properties file entry to use for setting the + * label and accesskey. + */ + setupUpdateButton: function(aKeyPrefix) { + this.updateBtn.label = this.bundle.GetStringFromName(aKeyPrefix + ".label"); + this.updateBtn.accessKey = this.bundle.GetStringFromName(aKeyPrefix + ".accesskey"); + if (!document.commandDispatcher.focusedElement || + document.commandDispatcher.focusedElement.isSameNode(this.updateBtn)) + this.updateBtn.focus(); + }, + + /** + * Handles oncommand for the update button. + */ + buttonOnCommand: function() { + if (this.isPending) { + // Notify all windows that an application quit has been requested. + let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]. + createInstance(Components.interfaces.nsISupportsPRBool); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + + // Something aborted the quit process. + if (cancelQuit.data) + return; + + // If already in safe mode restart in safe mode (bug 327119) + if (Services.appinfo.inSafeMode) { + let env = Components.classes["@mozilla.org/process/environment;1"]. + getService(Components.interfaces.nsIEnvironment); + env.set("MOZ_SAFE_MODE_RESTART", "1"); + } + + Components.classes["@mozilla.org/toolkit/app-startup;1"]. + getService(Components.interfaces.nsIAppStartup). + quit(Components.interfaces.nsIAppStartup.eAttemptQuit | + Components.interfaces.nsIAppStartup.eRestart); + return; + } + + const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul"; + // Firefox no longer displays a license for updates and the licenseURL check + // is just in case a distibution does. + if (this.update && (this.update.billboardURL || this.update.licenseURL || + this.addons.length != 0)) { + var ary = null; + ary = Components.classes["@mozilla.org/supports-array;1"]. + createInstance(Components.interfaces.nsISupportsArray); + ary.AppendElement(this.update); + var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no"; + Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, "", openFeatures, ary); + window.close(); + return; + } + + this.selectPanel("checkingForUpdates"); + this.isChecking = true; + this.checker.checkForUpdates(this.updateCheckListener, true); + }, + + /** + * Implements nsIUpdateCheckListener. The methods implemented by + * nsIUpdateCheckListener have to be in a different scope from + * nsIIncrementalDownload because both nsIUpdateCheckListener and + * nsIIncrementalDownload implement onProgress. + */ + updateCheckListener: { + /** + * See nsIUpdateService.idl + */ + onProgress: function(aRequest, aPosition, aTotalSize) { + }, + + /** + * See nsIUpdateService.idl + */ + onCheckComplete: function(aRequest, aUpdates, aUpdateCount) { + gAppUpdater.isChecking = false; + gAppUpdater.update = gAppUpdater.aus. + selectUpdate(aUpdates, aUpdates.length); + if (!gAppUpdater.update) { + gAppUpdater.selectPanel("noUpdatesFound"); + return; + } + + if (!gAppUpdater.aus.canApplyUpdates) { + gAppUpdater.selectPanel("manualUpdate"); + return; + } + + // Firefox no longer displays a license for updates and the licenseURL + // check is just in case a distibution does. + if (gAppUpdater.update.billboardURL || gAppUpdater.update.licenseURL) { + gAppUpdater.selectPanel("updateButtonBox"); + gAppUpdater.setupUpdateButton("update.openUpdateUI." + + (this.isMajor ? "upgradeButton" + : "applyButton")); + return; + } + + if (!gAppUpdater.update.appVersion || + Services.vc.compare(gAppUpdater.update.appVersion, + Services.appinfo.version) == 0) { + gAppUpdater.startDownload(); + return; + } + + gAppUpdater.checkAddonCompatibility(); + }, + + /** + * See nsIUpdateService.idl + */ + onError: function(aRequest, aUpdate) { + // Errors in the update check are treated as no updates found. If the + // update check fails repeatedly without a success the user will be + // notified with the normal app update user interface so this is safe. + gAppUpdater.isChecking = false; + gAppUpdater.selectPanel("noUpdatesFound"); + return; + }, + + /** + * See nsISupports.idl + */ + QueryInterface: function(aIID) { + if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) && + !aIID.equals(Components.interfaces.nsISupports)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + } + }, + + /** + * Checks the compatibility of add-ons for the application update. + */ + checkAddonCompatibility: function() { + var self = this; + AddonManager.getAllAddons(function(aAddons) { + self.addons = []; + self.addonsCheckedCount = 0; + aAddons.forEach(function(aAddon) { + // If an add-on isn't appDisabled and isn't userDisabled then it is + // either active now or the user expects it to be active after the + // restart. If that is the case and the add-on is not installed by the + // application and is not compatible with the new application version + // then the user should be warned that the add-on will become + // incompatible. If an addon's type equals plugin it is skipped since + // checking plugins compatibility information isn't supported and + // getting the scope property of a plugin breaks in some environments + // (see bug 566787). + if (aAddon.type != "plugin" && + !aAddon.appDisabled && !aAddon.userDisabled && + aAddon.scope != AddonManager.SCOPE_APPLICATION && + aAddon.isCompatible && + !aAddon.isCompatibleWith(self.update.appVersion, + self.update.platformVersion)) + self.addons.push(aAddon); + }); + self.addonsTotalCount = self.addons.length; + if (self.addonsTotalCount == 0) { + self.startDownload(); + return; + } + + self.checkAddonsForUpdates(); + }); + }, + + /** + * Checks if there are updates for add-ons that are incompatible with the + * application update. + */ + checkAddonsForUpdates: function() { + this.addons.forEach(function(aAddon) { + aAddon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, + this.update.appVersion, + this.update.platformVersion); + }, this); + }, + + /** + * See XPIProvider.jsm + */ + onCompatibilityUpdateAvailable: function(aAddon) { + for (var i = 0; i < this.addons.length; ++i) { + if (this.addons[i].id == aAddon.id) { + this.addons.splice(i, 1); + break; + } + } + }, + + /** + * See XPIProvider.jsm + */ + onUpdateAvailable: function(aAddon, aInstall) { + if (!this.bs.isAddonBlocklisted(aAddon.id, aInstall.version, + this.update.appVersion, + this.update.platformVersion)) { + // Compatibility or new version updates mean the same thing here. + this.onCompatibilityUpdateAvailable(aAddon); + } + }, + + /** + * See XPIProvider.jsm + */ + onUpdateFinished: function(aAddon) { + ++this.addonsCheckedCount; + + if (this.addonsCheckedCount < this.addonsTotalCount) + return; + + if (this.addons.length == 0) { + // Compatibility updates or new version updates were found for all add-ons + this.startDownload(); + return; + } + + this.selectPanel("updateButtonBox"); + this.setupUpdateButton("update.openUpdateUI." + + (this.isMajor ? "upgradeButton" : "applyButton")); + }, + + /** + * Starts the download of an update mar. + */ + startDownload: function() { + if (!this.update) + this.update = this.um.activeUpdate; + this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag); + this.update.setProperty("foregroundDownload", "true"); + + this.aus.pauseDownload(); + let state = this.aus.downloadUpdate(this.update, false); + if (state == "failed") { + this.selectPanel("downloadFailed"); + return; + } + + this.downloadStatus = document.getElementById("downloadStatus"); + this.downloadStatus.value = + DownloadUtils.getTransferTotal(0, this.update.selectedPatch.size); + this.selectPanel("downloading"); + this.aus.addDownloadListener(this); + }, + + removeDownloadListener: function() { + this.aus.removeDownloadListener(this); + }, + + /** + * See nsIRequestObserver.idl + */ + onStartRequest: function(aRequest, aContext) { + }, + + /** + * See nsIRequestObserver.idl + */ + onStopRequest: function(aRequest, aContext, aStatusCode) { + switch (aStatusCode) { + case Components.results.NS_ERROR_UNEXPECTED: + if (this.update.selectedPatch.state == "download-failed" && + (this.update.isCompleteUpdate || this.update.patchCount != 2)) { + // Verification error of complete patch, informational text is held in + // the update object. + this.removeDownloadListener(); + this.selectPanel("downloadFailed"); + break; + } + // Verification failed for a partial patch, complete patch is now + // downloading so return early and do NOT remove the download listener! + break; + case Components.results.NS_BINDING_ABORTED: + // Do not remove UI listener since the user may resume downloading again. + break; + case Components.results.NS_OK: + this.removeDownloadListener(); + this.selectPanel("updateButtonBox"); + this.setupUpdateButton("update.restart." + + (this.isMajor ? "upgradeButton" : "applyButton")); + break; + default: + this.removeDownloadListener(); + this.selectPanel("downloadFailed"); + break; + } + + }, + + /** + * See nsIProgressEventSink.idl + */ + onStatus: function(aRequest, aContext, aStatus, aStatusArg) { + }, + + /** + * See nsIProgressEventSink.idl + */ + onProgress: function(aRequest, aContext, aProgress, aProgressMax) { + this.downloadStatus.value = + DownloadUtils.getTransferTotal(aProgress, aProgressMax); + }, + + /** + * See nsISupports.idl + */ + QueryInterface: function(aIID) { + if (!aIID.equals(Components.interfaces.nsIProgressEventSink) && + !aIID.equals(Components.interfaces.nsIRequestObserver) && + !aIID.equals(Components.interfaces.nsISupports)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + } +}; #endif
--- a/browser/base/content/aboutDialog.xul +++ b/browser/base/content/aboutDialog.xul @@ -18,16 +18,17 @@ # The Initial Developer of the Original Code is # Blake Ross (blaker@netscape.com). # Portions created by the Initial Developer are Copyright (C) 2002 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Ehsan Akhgari <ehsan.akhgari@gmail.com> # Margaret Leibovic <margaret.leibovic@gmail.com> +# Robert Strong <robert.bugzilla@gmail.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -52,39 +53,67 @@ <?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?> #endif <window xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="aboutDialog" windowtype="Browser:About" onload="init(event);" +#ifdef MOZ_UPDATER + onunload="onUnload(event);" +#endif #ifdef XP_MACOSX inwindowmenu="false" #else title="&aboutDialog.title;" #endif > - <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> <script type="application/javascript" src="chrome://browser/content/aboutDialog.js"/> <vbox> <hbox id="clientBox"> <vbox id="leftBox" flex="1"/> <vbox id="rightBox" flex="1"> #expand <label id="version" value="__MOZ_APP_VERSION__"/> <label id="distribution" class="text-blurb"/> <label id="distributionId" class="text-blurb"/> + <vbox id="updateBox"> #ifdef MOZ_UPDATER - <hbox> - <button id="checkForUpdatesButton" oncommand="checkForUpdates();" align="start"/> - <spacer flex="1"/> - </hbox> + <deck id="updateDeck" orient="vertical"> + <hbox id="updateButtonBox" align="center"> + <button id="updateButton" align="start" + oncommand="gAppUpdater.buttonOnCommand();"/> + <spacer flex="1"/> + </hbox> + <hbox id="checkingForUpdates" align="center"> + <image class="update-throbber"/><label>&update.checkingForUpdates;</label> + </hbox> + <hbox id="checkingAddonCompat" align="center"> + <image class="update-throbber"/><label>&update.checkingAddonCompat;</label> + </hbox> + <hbox id="downloading" align="center"> + <image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label> + </hbox> + <hbox id="downloadFailed" align="center"> + <label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label> + </hbox> + <hbox id="adminDisabled" align="center"> + <label>&update.adminDisabled;</label> + </hbox> + <hbox id="noUpdatesFound" align="center"> + <label>&update.noUpdatesFound;</label> + </hbox> + <hbox id="manualUpdate" align="center"> + <label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label> + </hbox> + </deck> #endif + </vbox> <description class="text-blurb"> &community.start2;<label class="text-link" href="http://www.mozilla.org/">&community.mozillaLink;</label>&community.middle2;<label class="text-link" href="about:credits">&community.creditsLink;</label>&community.end2; </description> <description class="text-blurb"> &contribute.start;<label class="text-link" href="http://www.mozilla.org/contribute/">&contribute.getInvolvedLink;</label>&contribute.end; </description> </vbox> </hbox>
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -132,16 +132,20 @@ toolbar[mode="icons"] > #reload-button[d } .menuitem-iconic-tooltip, .menuitem-tooltip[type="checkbox"], .menuitem-tooltip[type="radio"] { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip"); } +#appmenu_offlineModeRecovery:not([checked=true]) { + display: none; +} + /* ::::: location bar ::::: */ #urlbar { -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar); } #urlbar-progress { -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter"); } @@ -416,8 +420,19 @@ window[chromehidden~="toolbar"] toolbar: #invalid-form-popup { max-width: 280px; } #geolocation-notification { -moz-binding: url("chrome://browser/content/urlbarBindings.xml#geolocation-notification"); } + +/* override hidden="true" for the status bar compatibility shim + in case it was persisted for the real status bar */ +#status-bar { + display: -moz-box; +} + +/* Remove the resizer from the statusbar compatibility shim */ +#status-bar > .statusbar-resizerpanel { + display: none; +}
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -484,16 +484,21 @@ </hbox> <menuitem id="appmenu_privateBrowsing" class="menuitem-iconic menuitem-iconic-tooltip" label="&privateBrowsingCmd.start.label;" startlabel="&privateBrowsingCmd.start.label;" stoplabel="&privateBrowsingCmd.stop.label;" command="Tools:PrivateBrowsing" key="key_privatebrowsing"/> + <menuitem label="&goOfflineCmd.label;" + id="appmenu_offlineModeRecovery" + type="checkbox" + observes="workOfflineMenuitemState" + oncommand="BrowserOffline.toggleOfflineStatus();"/> <menuseparator class="appmenu-menuseparator"/> <hbox> <menuitem id="appmenu-edit-label" label="&appMenuEdit.label;" disabled="true"/> <toolbarbutton id="appmenu-cut" class="appmenu-edit-button" command="cmd_cut"
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -429,17 +429,16 @@ if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) this.mBrowser.userTypedClear += 2; if (!this.mBlank) { if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { this.mTab.setAttribute("busy", "true"); this.mTab.setAttribute("progresspercent", "0"); this._startStalledTimer(); - this.mTabBrowser.updateIcon(this.mTab); this.mTabBrowser.setTabTitleLoading(this.mTab); } if (this.mTab.selected) this.mTabBrowser.mIsBusy = true; } } else if (aStateFlags & nsIWebProgressListener.STATE_STOP && @@ -458,17 +457,16 @@ if (this.mBlank) this.mBlank = false; this.mTab.removeAttribute("busy"); this.mTab.removeAttribute("progresspercent"); this.mTab.removeAttribute("stalled"); this._cancelStalledTimer(); - this.mTabBrowser.updateIcon(this.mTab); var location = aRequest.QueryInterface(nsIChannel).URI; // For keyword URIs clear the user typed value since they will be changed into real URIs if (location.scheme == "keyword") this.mBrowser.userTypedValue = null; if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading")) @@ -597,47 +595,39 @@ if (aURI && this.mFaviconService) { if (!(aURI instanceof Ci.nsIURI)) aURI = makeURI(aURI); this.mFaviconService.setAndLoadFaviconForPage(browser.currentURI, aURI, false); } - this.updateIcon(aTab); + if ((browser.mIconURL || "") != aTab.getAttribute("image")) { + if (browser.mIconURL) + aTab.setAttribute("image", browser.mIconURL); + else + aTab.removeAttribute("image"); + this._tabAttrModified(aTab); + } this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]); ]]> </body> </method> <method name="getIcon"> <parameter name="aTab"/> <body> <![CDATA[ let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser; return browser.mIconURL; ]]> </body> </method> - <method name="updateIcon"> - <parameter name="aTab"/> - <body> - <![CDATA[ - var browser = this.getBrowserForTab(aTab); - if (!aTab.hasAttribute("busy") && browser.mIconURL) - aTab.setAttribute("image", browser.mIconURL); - else - aTab.removeAttribute("image"); - this._tabAttrModified(aTab); - ]]> - </body> - </method> - <method name="shouldLoadFavIcon"> <parameter name="aURI"/> <body> <![CDATA[ return (aURI && Services.prefs.getBoolPref("browser.chrome.site_icons") && Services.prefs.getBoolPref("browser.chrome.favicons") && ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https"))); @@ -645,41 +635,39 @@ </body> </method> <method name="useDefaultIcon"> <parameter name="aTab"/> <body> <![CDATA[ var browser = this.getBrowserForTab(aTab); - var docURIObject = browser.contentDocument.documentURIObject; + var docURIObject = browser.contentDocument.documentURIObject; + var icon = null; if (browser.contentDocument instanceof ImageDocument) { if (Services.prefs.getBoolPref("browser.chrome.site_icons")) { + let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size"); try { - let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size"); - if (!sz) - return; - - var req = browser.contentDocument.imageRequest; - if (!req || !req.image || - req.image.width > sz || - req.image.height > sz) - return; - - this.setIcon(aTab, browser.currentURI); + let req = browser.contentDocument.imageRequest; + if (req && + req.image && + req.image.width <= sz && + req.image.height <= sz) + icon = browser.currentURI; } catch (e) { } } } // Use documentURIObject in the check for shouldLoadFavIcon so that we // do the right thing with about:-style error pages. Bug 453442 else if (this.shouldLoadFavIcon(docURIObject)) { - var url = docURIObject.prePath + "/favicon.ico"; + let url = docURIObject.prePath + "/favicon.ico"; if (!this.isFailedIcon(url)) - this.setIcon(aTab, url); + icon = url; } + this.setIcon(aTab, icon); ]]> </body> </method> <method name="isFailedIcon"> <parameter name="aURI"/> <body> <![CDATA[ @@ -969,17 +957,16 @@ return; this.mTabbedMode = true; // Welcome to multi-tabbed mode. if (XULBrowserWindow.isBusy) { this.mCurrentTab.setAttribute("busy", "true"); this.mIsBusy = true; this.setTabTitleLoading(this.mCurrentTab); - this.updateIcon(this.mCurrentTab); } else { this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL); } var filter; if (this.mTabFilters.length > 0) { // Use the filter hooked up in our addProgressListener filter = this.mTabFilters[0]; @@ -1720,17 +1707,16 @@ ourBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL); if (isBusy) this.setTabTitleLoading(aOurTab); else this.setTabTitle(aOurTab); - this.updateIcon(aOurTab); // If the tab was already selected (this happpens in the scenario // of replaceTabWithWindow), notify onLocationChange, etc. if (aOurTab == this.selectedTab) this.updateCurrentBrowser(true); ]]> </body> </method>
--- a/browser/base/content/test/browser_bug521216.js +++ b/browser/base/content/test/browser_bug521216.js @@ -34,13 +34,14 @@ var progressListener = { onLocationChange: function onLocationChange(aBrowser) { if (aBrowser == tab.linkedBrowser) record(arguments.callee.name); }, onStateChange: function onStateChange(aBrowser) { if (aBrowser == tab.linkedBrowser) record(arguments.callee.name); }, - onLinkIconAvailable: function onLinkIconAvailable(aBrowser) { - if (aBrowser == tab.linkedBrowser) + onLinkIconAvailable: function onLinkIconAvailable(aBrowser, aIconURL) { + if (aBrowser == tab.linkedBrowser && + aIconURL == "about:logo") record(arguments.callee.name); } };
--- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -848,25 +848,17 @@ SessionStoreService.prototype = { browser.removeEventListener("change", this, true); browser.removeEventListener("input", this, true); browser.removeEventListener("DOMAutoComplete", this, true); delete browser.__SS_data; // If this tab was in the middle of restoring, we want to restore the next // tab. If the tab hasn't been restored, we want to remove it from the array. - if (browser.__SS_restoring) { - this.restoreNextTab(true); - } - else if (browser.__SS_needsRestore) { - if (aTab.hidden) - this._tabsToRestore.hidden.splice(this._tabsToRestore.hidden.indexOf(aTab)); - else - this._tabsToRestore.visible.splice(this._tabsToRestore.visible.indexOf(aTab)); - } + this._resetTabRestoringState(aTab, true); if (!aNoNotification) { this.saveStateDelayed(aWindow); } }, /** * When a tab closes, collect its properties @@ -2197,16 +2189,34 @@ SessionStoreService.prototype = { if (winData.tabs[t].pinned) tabbrowser.pinTab(tabs[t]); else tabbrowser.unpinTab(tabs[t]); tabs[t].hidden = winData.tabs[t].hidden; } + // If overwriting tabs, we want to remove __SS_restoring from the browser. + if (aOverwriteTabs) { + for (let i = 0; i < tabbrowser.tabs.length; i++) + this._resetTabRestoringState(tabbrowser.tabs[i], false); + } + + // We want to set up a counter on the window that indicates how many tabs + // in this window are unrestored. This will be used in restoreNextTab to + // determine if gRestoreTabsProgressListener should be removed from the window. + // If we aren't overwriting existing tabs, then we want to add to the existing + // count in case there are still tabs restoring. + if (!aWindow.__SS_tabsToRestore) + aWindow.__SS_tabsToRestore = 0; + if (aOverwriteTabs) + aWindow.__SS_tabsToRestore = newTabCount; + else + aWindow.__SS_tabsToRestore += newTabCount; + // We want to correlate the window with data from the last session, so // assign another id if we have one. Otherwise clear so we don't do // anything with it. delete aWindow.__SS_lastSessionWindowID; if (winData.__lastSessionWindowID) aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID; // when overwriting tabs, remove all superflous ones @@ -2282,71 +2292,22 @@ SessionStoreService.prototype = { var restoreHistoryFunc = function(self) { self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount + 1); } aWindow.setTimeout(restoreHistoryFunc, 100, this); return; } } } - - // mark the tabs as loading - for (t = 0; t < aTabs.length; t++) { - let tab = aTabs[t]; - let browser = tabbrowser.getBrowserForTab(tab); - let tabData = aTabData[t]; - - if (tabData.pinned) - tabbrowser.pinTab(tab); - else - tabbrowser.unpinTab(tab); - tab.hidden = tabData.hidden; - - tabData._tabStillLoading = true; - - // keep the data around to prevent dataloss in case - // a tab gets closed before it's been properly restored - browser.__SS_data = tabData; - browser.__SS_needsRestore = true; - - if (!tabData.entries || tabData.entries.length == 0) { - // make sure to blank out this tab's content - // (just purging the tab's history won't be enough) - browser.contentDocument.location = "about:blank"; - continue; - } - - browser.stop(); // in case about:blank isn't done yet - - tab.setAttribute("busy", "true"); - tabbrowser.updateIcon(tab); - - // wall-paper fix for bug 439675: make sure that the URL to be loaded - // is always visible in the address bar - let activeIndex = (tabData.index || tabData.entries.length) - 1; - let activePageData = tabData.entries[activeIndex] || null; - browser.userTypedValue = activePageData ? activePageData.url || null : null; - - // If the page has a title, set it. - if (activePageData) { - if (activePageData.title) { - tab.label = activePageData.title; - tab.crop = "end"; - } else if (activePageData.url != "about:blank") { - tab.label = activePageData.url; - tab.crop = "center"; - } - } - } - + if (aTabs.length > 0) { // Load hidden tabs last, by pushing them to the end of the list let unhiddenTabs = aTabs.length; for (let t = 0; t < unhiddenTabs; ) { - if (aTabs[t].hidden) { + if (aTabData[t].hidden) { aTabs = aTabs.concat(aTabs.splice(t, 1)); aTabData = aTabData.concat(aTabData.splice(t, 1)); if (aSelectTab > t) --aSelectTab; --unhiddenTabs; continue; } ++t; @@ -2374,16 +2335,65 @@ SessionStoreService.prototype = { // make sure to restore the selected tab first (if any) if (aSelectTab-- && aTabs[aSelectTab]) { aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]); aTabData.unshift(aTabData.splice(aSelectTab, 1)[0]); tabbrowser.selectedTab = aTabs[0]; } } + // Prepare the tabs so that they can be properly restored. We'll pin/unpin + // and show/hide tabs as necessary. We'll also set the labels, user typed + // value, and attach a copy of the tab's data in case we close it before + // it's been restored. + for (t = 0; t < aTabs.length; t++) { + let tab = aTabs[t]; + let browser = tabbrowser.getBrowserForTab(tab); + let tabData = aTabData[t]; + + if (tabData.pinned) + tabbrowser.pinTab(tab); + else + tabbrowser.unpinTab(tab); + tab.hidden = tabData.hidden; + + tabData._tabStillLoading = true; + + // keep the data around to prevent dataloss in case + // a tab gets closed before it's been properly restored + browser.__SS_data = tabData; + browser.__SS_needsRestore = true; + + if (!tabData.entries || tabData.entries.length == 0) { + // make sure to blank out this tab's content + // (just purging the tab's history won't be enough) + browser.contentDocument.location = "about:blank"; + continue; + } + + browser.stop(); // in case about:blank isn't done yet + + // wall-paper fix for bug 439675: make sure that the URL to be loaded + // is always visible in the address bar + let activeIndex = (tabData.index || tabData.entries.length) - 1; + let activePageData = tabData.entries[activeIndex] || null; + browser.userTypedValue = activePageData ? activePageData.url || null : null; + + // If the page has a title, set it. + if (activePageData) { + if (activePageData.title) { + tab.label = activePageData.title; + tab.crop = "end"; + } else if (activePageData.url != "about:blank") { + tab.label = activePageData.url; + tab.crop = "center"; + } + } + } + if (!this._isWindowLoaded(aWindow)) { // from now on, the data will come from the actual window delete this._statesToRestore[aWindow.__SS_restoreID]; delete aWindow.__SS_restoreID; delete this._windows[aWindow.__SSi]._restoring; } // helper hashes for ensuring unique frame IDs and unique document @@ -2480,27 +2490,31 @@ SessionStoreService.prototype = { this._tabsToRestore.hidden.push(tab); else this._tabsToRestore.visible.push(tab); this.restoreNextTab(); } }, restoreTab: function(aTab) { + let window = aTab.ownerDocument.defaultView; let browser = aTab.linkedBrowser; let tabData = browser.__SS_data; // There are cases within where we haven't actually started a load and so we // should call restoreNextTab. We don't want to do it immediately though // since we might not set userTypedValue in a timely fashion. let shouldRestoreNextTab = false; // Increase our internal count. this._tabsRestoringCount++; + // Decrement the number of tabs this window needs to restore + window.__SS_tabsToRestore--; + delete browser.__SS_needsRestore; let activeIndex = (tabData.index || tabData.entries.length) - 1; if (activeIndex >= tabData.entries.length) activeIndex = tabData.entries.length - 1; // Attach data that will be restored on "load" event, after tab is restored. if (activeIndex > -1) { @@ -2571,19 +2585,21 @@ SessionStoreService.prototype = { if (nextTabArray) { let tab = nextTabArray.shift(); this.restoreTab(tab); } else { // Remove the progress listener from windows. It will get re-added as needed. this._forEachBrowserWindow(function(aWindow) { - // This won't fail since removeTabsProgressListener just filters. It - // doesn't attempt to splice. - aWindow.gBrowser.removeTabsProgressListener(gRestoreTabsProgressListener) + if (!aWindow.__SS_tabsToRestore) { + // This won't fail since removeTabsProgressListener just filters. It + // doesn't attempt to splice. + aWindow.gBrowser.removeTabsProgressListener(gRestoreTabsProgressListener); + } }); } }, /** * expands serialized history data into a session-history-entry instance * @param aEntry * Object containing serialized history data for a URL @@ -3493,21 +3509,46 @@ SessionStoreService.prototype = { #endif this._closedWindows.splice(spliceTo); }, /** * Reset state to prepare for a new session state to be restored. */ _resetRestoringState: function sss__initRestoringState() { - // - this._tasToRestore = { visible: [], hidden: [] }; + this._tabsToRestore = { visible: [], hidden: [] }; this._tabsRestoringCount = 0; }, + _resetTabRestoringState: function sss__resetTabRestoringState(aTab, aRestoreNextTab) { + let browser = aTab.linkedBrowser; + + if (browser.__SS_restoring) { + delete browser.__SS_restoring; + if (aRestoreNextTab) { + // this._tabsRestoringCount is decremented in restoreNextTab. + this.restoreNextTab(true); + } + else { + // Even if we aren't restoring the next tab, we still need to decrement + // the restoring count. Normally it gets done within restoreNextTab. + this._tabsRestoringCount--; + } + } + else if (browser.__SS_needsRestore) { + let window = aTab.ownerDocument.defaultView; + window.__SS_tabsToRestore--; + delete browser.__SS_needsRestore; + if (aTab.hidden) + this._tabsToRestore.hidden.splice(this._tabsToRestore.hidden.indexOf(aTab)); + else + this._tabsToRestore.visible.splice(this._tabsToRestore.visible.indexOf(aTab)); + } + }, + /* ........ Storage API .............. */ /** * write file to disk * @param aFile * nsIFile * @param aData * String data
--- a/browser/components/sessionstore/test/browser/browser_586068-cascaded_restore.js +++ b/browser/components/sessionstore/test/browser/browser_586068-cascaded_restore.js @@ -43,39 +43,46 @@ let stateBackup = ss.getBrowserState(); function test() { /** Test for Bug 586068 - Cascade page loads when restoring **/ waitForExplicitFinish(); runNextTest(); } -let tests = [test_cascade, test_select]; +let tests = [test_cascade, test_select, test_multiWindowState, + test_setWindowStateNoOverwrite, test_setWindowStateOverwrite, + test_setBrowserStateInterrupted]; function runNextTest() { + // Reset the pref + try { + Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs"); + } catch (e) {} + + // set an empty state & run the next test, or finish if (tests.length) { - ss.setWindowState(window, - JSON.stringify({ windows: [{ tabs: [{ url: 'about:blank' }], }] }), - true); + ss.setBrowserState(JSON.stringify({ windows: [{ tabs: [{ url: 'about:blank' }], }] })); executeSoon(tests.shift()); } else { ss.setBrowserState(stateBackup); executeSoon(finish); } } function test_cascade() { // Set the pref to 1 so we know exactly how many tabs should be restoring at any given time Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 1); // We have our own progress listener for this test, which we'll attach before our state is set let progressListener = { onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { - if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + if (aBrowser.__SS_restoring && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) test_cascade_progressCallback(); } } let state = { windows: [{ tabs: [ { entries: [{ url: "http://example.com" }], extData: { "uniq": r() } }, @@ -97,128 +104,438 @@ function test_cascade() { [3, 1, 2], [2, 1, 3], [1, 1, 4], [0, 1, 5] ]; function test_cascade_progressCallback() { loadCount++; - // We'll get the first <length of windows[0].tabs> load events here, even - // though they are ignored by sessionstore. Those are due to explicit - // "stop" events before any restoring action takes place. We can safely - // ignore these events. - if (loadCount <= state.windows[0].tabs.length) - return; - let counts = countTabs(); - let expected = expectedCounts[loadCount - state.windows[0].tabs.length - 1]; + let expected = expectedCounts[loadCount - 1]; is(counts[0], expected[0], "test_cascade: load " + loadCount + " - # tabs that need to be restored"); is(counts[1], expected[1], "test_cascade: load " + loadCount + " - # tabs that are restoring"); is(counts[2], expected[2], "test_cascade: load " + loadCount + " - # tabs that has been restored"); - if (loadCount == state.windows[0].tabs.length * 2) { - window.gBrowser.removeTabsProgressListener(progressListener); - // Reset the pref - try { - Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs"); - } catch (e) {} - runNextTest(); - } + if (loadCount < state.windows[0].tabs.length) + return; + + window.gBrowser.removeTabsProgressListener(progressListener); + runNextTest(); } // This progress listener will get attached before the listener in session store. window.gBrowser.addTabsProgressListener(progressListener); ss.setBrowserState(JSON.stringify(state)); } function test_select() { // Set the pref to 0 so we know exactly how many tabs should be restoring at // any given time. This guarantees that a finishing load won't start another. Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 0); // We have our own progress listener for this test, which we'll attach before our state is set let progressListener = { onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { - if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + if (aBrowser.__SS_restoring && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) test_select_progressCallback(aBrowser); } } let state = { windows: [{ tabs: [ { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }, { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }, { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }, { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }, { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }, { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } } - ], selectedIndex: 1 }] }; + ], selected: 1 }] }; let loadCount = 0; // expectedCounts looks a little wierd for the test case, but it works. See // comment in test_cascade for an explanation let expectedCounts = [ [5, 1, 0], [4, 1, 1], [3, 1, 2], [2, 1, 3], [1, 1, 4], [0, 1, 5] ]; let tabOrder = [0, 5, 1, 4, 3, 2]; function test_select_progressCallback(aBrowser) { loadCount++; - // We'll get the first <length of windows[0].tabs> load events here, even - // though they are ignored by sessionstore. Those are due to explicit - // "stop" events before any restoring action takes place. We can safely - // ignore these events. - if (loadCount <= state.windows[0].tabs.length) - return; - let loadIndex = loadCount - state.windows[0].tabs.length - 1; let counts = countTabs(); - let expected = expectedCounts[loadIndex]; + let expected = expectedCounts[loadCount - 1]; is(counts[0], expected[0], "test_select: load " + loadCount + " - # tabs that need to be restored"); is(counts[1], expected[1], "test_select: load " + loadCount + " - # tabs that are restoring"); is(counts[2], expected[2], "test_select: load " + loadCount + " - # tabs that has been restored"); - if (loadCount == state.windows[0].tabs.length * 2) { - window.gBrowser.removeTabsProgressListener(progressListener); - // Reset the pref - try { - Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs"); - } catch (e) {} - runNextTest(); - } - else { + if (loadCount < state.windows[0].tabs.length) { // double check that this tab was the right one - let expectedData = state.windows[0].tabs[tabOrder[loadIndex]].extData.uniq; + let expectedData = state.windows[0].tabs[tabOrder[loadCount - 1]].extData.uniq; let tab; for (let i = 0; i < window.gBrowser.tabs.length; i++) { if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser) tab = window.gBrowser.tabs[i]; } is(ss.getTabValue(tab, "uniq"), expectedData, "test_select: load " + loadCount + " - correct tab was restored"); // select the next tab - window.gBrowser.selectTabAtIndex(tabOrder[loadIndex + 1]); + window.gBrowser.selectTabAtIndex(tabOrder[loadCount]); + return; } + + window.gBrowser.removeTabsProgressListener(progressListener); + runNextTest(); } window.gBrowser.addTabsProgressListener(progressListener); ss.setBrowserState(JSON.stringify(state)); } +function test_multiWindowState() { + // We have our own progress listener for this test, which we'll attach before our state is set + let progressListener = { + onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + // We only care about load events when the tab still has __SS_restoring on it. + // Since our listener is attached before the sessionstore one, this works out. + if (aBrowser.__SS_restoring && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) + test_multiWindowState_progressCallback(aBrowser); + } + } + + // The first window will be put into the already open window and the second + // window will be opened with _openWindowWithState, which is the source of the problem. + let state = { windows: [ + { + tabs: [ + { entries: [{ url: "http://example.org#0" }], extData: { "uniq": r() } } + ], + selected: 1 + }, + { + tabs: [ + { entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#5" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#6" }], extData: { "uniq": r() } } + ], + selected: 4 + } + ] }; + let numTabs = state.windows[0].tabs.length + state.windows[1].tabs.length; + + let loadCount = 0; + function test_multiWindowState_progressCallback(aBrowser) { + loadCount++; + + if (loadCount < numTabs) + return; + + // We don't actually care about load order in this test, just that they all + // do load. + is(loadCount, numTabs, "test_multiWindowState: all tabs were restored"); + let count = countTabs(); + is(count[0], 0, + "test_multiWindowState: there are no tabs left needing restore"); + + // Remove the progress listener from this window, it will be removed from + // theWin when that window is closed (in setBrowserState). + window.gBrowser.removeTabsProgressListener(progressListener); + runNextTest(); + } + + // We also want to catch the 2nd window, so we need to observe domwindowopened + function windowObserver(aSubject, aTopic, aData) { + let theWin = aSubject.QueryInterface(Ci.nsIDOMWindow); + if (aTopic == "domwindowopened") { + theWin.addEventListener("load", function() { + theWin.removeEventListener("load", arguments.callee, false); + + Services.ww.unregisterNotification(windowObserver); + theWin.gBrowser.addTabsProgressListener(progressListener); + }, false); + } + } + Services.ww.registerNotification(windowObserver); + + window.gBrowser.addTabsProgressListener(progressListener); + ss.setBrowserState(JSON.stringify(state)); +} + + +function test_setWindowStateNoOverwrite() { + // Set the pref to 1 so we know exactly how many tabs should be restoring at any given time + Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 1); + + // We have our own progress listener for this test, which we'll attach before our state is set + let progressListener = { + onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + // We only care about load events when the tab still has __SS_restoring on it. + // Since our listener is attached before the sessionstore one, this works out. + if (aBrowser.__SS_restoring && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) + test_setWindowStateNoOverwrite_progressCallback(aBrowser); + } + } + + // We'll use 2 states so that we can make sure calling setWindowState doesn't + // wipe out currently restoring data. + let state1 = { windows: [{ tabs: [ + { entries: [{ url: "http://example.com#1" }] }, + { entries: [{ url: "http://example.com#2" }] }, + { entries: [{ url: "http://example.com#3" }] }, + { entries: [{ url: "http://example.com#4" }] }, + { entries: [{ url: "http://example.com#5" }] }, + ] }] }; + let state2 = { windows: [{ tabs: [ + { entries: [{ url: "http://example.org#1" }] }, + { entries: [{ url: "http://example.org#2" }] }, + { entries: [{ url: "http://example.org#3" }] }, + { entries: [{ url: "http://example.org#4" }] }, + { entries: [{ url: "http://example.org#5" }] } + ] }] }; + + let numTabs = state1.windows[0].tabs.length + state2.windows[0].tabs.length; + + let loadCount = 0; + function test_setWindowStateNoOverwrite_progressCallback(aBrowser) { + loadCount++; + + // When loadCount == 2, we'll also restore state2 into the window + if (loadCount == 2) + ss.setWindowState(window, JSON.stringify(state2), false); + + if (loadCount < numTabs) + return; + + // We don't actually care about load order in this test, just that they all + // do load. + is(loadCount, numTabs, "test_setWindowStateNoOverwrite: all tabs were restored"); + is(window.__SS_tabsToRestore, 0, + "test_setWindowStateNoOverwrite: window doesn't think there are more tabs to restore"); + let count = countTabs(); + is(count[0], 0, + "test_setWindowStateNoOverwrite: there are no tabs left needing restore"); + + // Remove the progress listener from this window, it will be removed from + // theWin when that window is closed (in setBrowserState). + window.gBrowser.removeTabsProgressListener(progressListener); + + runNextTest(); + } + + window.gBrowser.addTabsProgressListener(progressListener); + ss.setWindowState(window, JSON.stringify(state1), true); +} + + +function test_setWindowStateOverwrite() { + // Set the pref to 1 so we know exactly how many tabs should be restoring at any given time + Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 1); + + // We have our own progress listener for this test, which we'll attach before our state is set + let progressListener = { + onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + // We only care about load events when the tab still has __SS_restoring on it. + // Since our listener is attached before the sessionstore one, this works out. + if (aBrowser.__SS_restoring && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) + test_setWindowStateOverwrite_progressCallback(aBrowser); + } + } + + // We'll use 2 states so that we can make sure calling setWindowState doesn't + // wipe out currently restoring data. + let state1 = { windows: [{ tabs: [ + { entries: [{ url: "http://example.com#1" }] }, + { entries: [{ url: "http://example.com#2" }] }, + { entries: [{ url: "http://example.com#3" }] }, + { entries: [{ url: "http://example.com#4" }] }, + { entries: [{ url: "http://example.com#5" }] }, + ] }] }; + let state2 = { windows: [{ tabs: [ + { entries: [{ url: "http://example.org#1" }] }, + { entries: [{ url: "http://example.org#2" }] }, + { entries: [{ url: "http://example.org#3" }] }, + { entries: [{ url: "http://example.org#4" }] }, + { entries: [{ url: "http://example.org#5" }] } + ] }] }; + + let numTabs = 2 + state2.windows[0].tabs.length; + + let loadCount = 0; + function test_setWindowStateOverwrite_progressCallback(aBrowser) { + loadCount++; + + // When loadCount == 2, we'll also restore state2 into the window + if (loadCount == 2) + ss.setWindowState(window, JSON.stringify(state2), true); + + if (loadCount < numTabs) + return; + + // We don't actually care about load order in this test, just that they all + // do load. + is(loadCount, numTabs, "test_setWindowStateOverwrite: all tabs were restored"); + is(window.__SS_tabsToRestore, 0, + "test_setWindowStateOverwrite: window doesn't think there are more tabs to restore"); + let count = countTabs(); + is(count[0], 0, + "test_setWindowStateOverwrite: there are no tabs left needing restore"); + + // Remove the progress listener from this window, it will be removed from + // theWin when that window is closed (in setBrowserState). + window.gBrowser.removeTabsProgressListener(progressListener); + + runNextTest(); + } + + window.gBrowser.addTabsProgressListener(progressListener); + ss.setWindowState(window, JSON.stringify(state1), true); +} + + +function test_setBrowserStateInterrupted() { + // Set the pref to 1 so we know exactly how many tabs should be restoring at any given time + Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 1); + + // We have our own progress listener for this test, which we'll attach before our state is set + let progressListener = { + onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + // We only care about load events when the tab still has __SS_restoring on it. + // Since our listener is attached before the sessionstore one, this works out. + if (aBrowser.__SS_restoring && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) + test_setBrowserStateInterrupted_progressCallback(aBrowser); + } + } + + // The first state will be loaded using setBrowserState, followed by the 2nd + // state also being loaded using setBrowserState, interrupting the first restore. + let state1 = { windows: [ + { + tabs: [ + { entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.org#3" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.org#4" }], extData: { "uniq": r() } } + ], + selected: 1 + }, + { + tabs: [ + { entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } }, + ], + selected: 3 + } + ] }; + let state2 = { windows: [ + { + tabs: [ + { entries: [{ url: "http://example.org#5" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.org#6" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.org#7" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.org#8" }], extData: { "uniq": r() } } + ], + selected: 3 + }, + { + tabs: [ + { entries: [{ url: "http://example.com#5" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#6" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#7" }], extData: { "uniq": r() } }, + { entries: [{ url: "http://example.com#8" }], extData: { "uniq": r() } }, + ], + selected: 1 + } + ] }; + + // interruptedAfter will be set after the selected tab from each window have loaded. + let interruptedAfter = 0; + let loadedWindow1 = false; + let loadedWindow2 = false; + let numTabs = state2.windows[0].tabs.length + state2.windows[1].tabs.length; + + let loadCount = 0; + function test_setBrowserStateInterrupted_progressCallback(aBrowser) { + loadCount++; + + if (aBrowser.currentURI.spec == state1.windows[0].tabs[2].entries[0].url) + loadedWindow1 = true; + if (aBrowser.currentURI.spec == state1.windows[1].tabs[0].entries[0].url) + loadedWindow2 = true; + + if (!interruptedAfter && loadedWindow1 && loadedWindow2) { + interruptedAfter = loadCount; + ss.setBrowserState(JSON.stringify(state2)); + return; + } + + if (loadCount < numTabs + interruptedAfter) + return; + + // We don't actually care about load order in this test, just that they all + // do load. + is(loadCount, numTabs + interruptedAfter, + "test_setBrowserStateInterrupted: all tabs were restored"); + let count = countTabs(); + is(count[0], 0, + "test_setBrowserStateInterrupted: there are no tabs left needing restore"); + + // Remove the progress listener from this window, it will be removed from + // theWin when that window is closed (in setBrowserState). + window.gBrowser.removeTabsProgressListener(progressListener); + Services.ww.unregisterNotification(windowObserver); + runNextTest(); + } + + // We also want to catch the extra windows (there should be 2), so we need to observe domwindowopened + function windowObserver(aSubject, aTopic, aData) { + let theWin = aSubject.QueryInterface(Ci.nsIDOMWindow); + if (aTopic == "domwindowopened") { + theWin.addEventListener("load", function() { + theWin.removeEventListener("load", arguments.callee, false); + + Services.ww.unregisterNotification(windowObserver); + theWin.gBrowser.addTabsProgressListener(progressListener); + }, false); + } + } + Services.ww.registerNotification(windowObserver); + + window.gBrowser.addTabsProgressListener(progressListener); + ss.setBrowserState(JSON.stringify(state1)); +} + + function countTabs() { let needsRestore = 0, isRestoring = 0, wasRestored = 0; let windowsEnum = Services.wm.getEnumerator("navigator:browser"); while (windowsEnum.hasMoreElements()) {
--- a/browser/locales/en-US/chrome/browser/aboutDialog.dtd +++ b/browser/locales/en-US/chrome/browser/aboutDialog.dtd @@ -16,8 +16,41 @@ <!-- LOCALIZATION NOTE (bottomLinks.license): This is a link title that links to about:license. --> <!ENTITY bottomLinks.license "Licensing Information"> <!-- LOCALIZATION NOTE (bottomLinks.rights): This is a link title that links to about:rights. --> <!ENTITY bottomLinks.rights "End User Rights"> <!-- LOCALIZATION NOTE (bottomLinks.privacy): This is a link title that links to http://www.mozilla.com/legal/privacy/. --> <!ENTITY bottomLinks.privacy "Privacy Policy"> + +<!-- LOCALIZATION NOTE (update.checkingForUpdates): try to make the localized text short (see bug 596813 for screenshots). --> +<!ENTITY update.checkingForUpdates "Checking for updates…"> +<!-- LOCALIZATION NOTE (update.checkingAddonCompat): try to make the localized text short (see bug 596813 for screenshots). --> +<!ENTITY update.checkingAddonCompat "Checking Add-on compatibility…"> +<!-- LOCALIZATION NOTE (update.noUpdatesFound): try to make the localized text short (see bug 596813 for screenshots). --> +<!ENTITY update.noUpdatesFound "&brandShortName; is up to date"> +<!-- LOCALIZATION NOTE (update.adminDisabled): try to make the localized text short (see bug 596813 for screenshots). --> +<!ENTITY update.adminDisabled "Updates disabled by your system administrator"> + +<!-- LOCALIZATION NOTE (update.failed.start,update.failed.linkText,update.failed.end): + update.failed.start, update.failed.linkText, and update.failed.end all go into + one line with linkText being wrapped in an anchor that links to a site to download + the latest version of Firefox (e.g. http://www.firefox.com). As this is all in + one line, try to make the localized text short (see bug 596813 for screenshots). --> +<!ENTITY update.failed.start "Update failed. "> +<!ENTITY update.failed.linkText "Download the latest version"> +<!ENTITY update.failed.end ""> + +<!-- LOCALIZATION NOTE (update.manual.start,update.manual.end): update.manual.start and update.manual.end + all go into one line and have an anchor in between with text that is the same as the link to a site + to download the latest version of Firefox (e.g. http://www.firefox.com). As this is all in one line, + try to make the localized text short (see bug 596813 for screenshots). --> +<!ENTITY update.manual.start "Updates available at "> +<!ENTITY update.manual.end ""> + +<!-- LOCALIZATION NOTE (update.downloading.start,update.downloading.end): update.downloading.start and + update.downloading.end all go into one line, with the amount downloaded inserted in between. As this + is all in one line, try to make the localized text short (see bug 596813 for screenshots). The — is + the "em dash" (long dash). + example: Downloading update — 111 KB of 13 MB --> +<!ENTITY update.downloading.start "Downloading update — "> +<!ENTITY update.downloading.end "">
--- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -136,41 +136,33 @@ updatesItem_downloading.accesskey=D updatesItem_resume=Resume Downloading %S… updatesItem_resumeFallback=Resume Downloading Update… updatesItem_resume.accesskey=D updatesItem_pending=Apply Downloaded Update Now… updatesItem_pendingFallback=Apply Downloaded Update Now… updatesItem_pending.accesskey=D # Check for Updates in the About Dialog - button labels and accesskeys -# LOCALIZATION NOTE - all of the following update buttons and labels will only -# be displayed one at a time. So, if a button is displayed no other buttons or -# labels will be displayed and if a label is displayed no other buttons or -# labels will be displayed. They will be placed directly under the Firefox -# version in the about dialog. -update.checkButton.label=Check for Updates… -update.checkButton.accesskey=C +# LOCALIZATION NOTE - all of the following update buttons labels will only be +# displayed one at a time. So, if a button is displayed nothing else will +# be displayed alongside of the button. The button when displayed is located +# directly under the Firefox version in the about dialog (see bug 596813 for +# screenshots). +update.checkInsideButton.label=Check for Updates +update.checkInsideButton.accesskey=C update.resumeButton.label=Resume Downloading %S… update.resumeButton.accesskey=D update.openUpdateUI.applyButton.label=Apply Update… update.openUpdateUI.applyButton.accesskey=A update.restart.applyButton.label=Apply Update update.restart.applyButton.accesskey=A update.openUpdateUI.upgradeButton.label=Upgrade Now… update.openUpdateUI.upgradeButton.accesskey=U update.restart.upgradeButton.label=Upgrade Now update.restart.upgradeButton.accesskey=U -# Check for Updates in the About Dialog - status labels -update.checkingForUpdate.label=Checking for updates… -update.checkingAddonCompat.label=Checking add-on compatibility… -update.noUpdateFound.label=This is the latest available version -# LOCALIZATION NOTE (update.downloading) — is the "em dash" (long dash) -# %S is the amount download -# examples: Downloading update — 111 KB of 13 MB -update.downloading=Downloading update — %S # RSS Pretty Print feedShowFeedNew=Subscribe to '%S'… menuOpenAllInTabs.label=Open All in Tabs # History menu menuRestoreAllTabs.label=Restore All Tabs
--- a/browser/themes/gnomestripe/browser/browser.css +++ b/browser/themes/gnomestripe/browser/browser.css @@ -1757,21 +1757,16 @@ listitem.style-section { color: black; font-weight: bold; } panel[dimmed="true"] { opacity: 0.5; } -/* Remove the resizer from the statusbar compatibility shim */ -#status-bar .statusbar-resizerpanel { - display: none; -} - /* Vertically-center the statusbar compatibility shim, because toolbars, even in small-icon mode, are a bit taller than statusbars. */ #status-bar { margin-top: .3em; } /* Remove all borders from statusbarpanel children of
--- a/browser/themes/pinstripe/browser/browser.css +++ b/browser/themes/pinstripe/browser/browser.css @@ -2257,21 +2257,16 @@ listitem.style-section { color: black; font-weight: bold; } panel[dimmed="true"] { opacity: 0.5; } -/* Remove the resizer from the statusbar compatibility shim */ -#status-bar .statusbar-resizerpanel { - display: none; -} - /* Vertically-center the statusbar compatibility shim, because toolbars, even in small-icon mode, are a bit taller than statusbars. Also turn off the statusbar border. On Windows we have to disable borders on statusbar *and* child statusbar elements. */ #status-bar { margin-top: 0.3em; -moz-appearance: none;
--- a/browser/themes/winstripe/browser/browser.css +++ b/browser/themes/winstripe/browser/browser.css @@ -2159,21 +2159,16 @@ listitem.style-section { color: black; font-weight: bold; } panel[dimmed="true"] { opacity: 0.5; } -/* Remove the resizer from the statusbar compatibility shim */ -#status-bar .statusbar-resizerpanel { - display: none; -} - /* Vertically-center the statusbar compatibility shim, because toolbars, even in small-icon mode, are a bit taller than statusbars. Also turn off the statusbar border. On Windows we have to disable borders on statusbar *and* child statusbar elements. */ #status-bar { margin-top: .3em; border-width: 0;
--- a/build/automation.py.in +++ b/build/automation.py.in @@ -207,16 +207,18 @@ class Automation(object): preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0): + args = automationutils.wrapCommand(args) + print "args: %s" % args subprocess.Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags) self.log = _log def kill(self):
--- a/build/automationutils.py +++ b/build/automationutils.py @@ -31,29 +31,30 @@ # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** */ -import glob, logging, os, shutil, subprocess, sys +import glob, logging, os, platform, shutil, subprocess, sys import re from urlparse import urlparse __all__ = [ "addCommonOptions", "checkForCrashes", "dumpLeakLog", "isURL", "processLeakLog", "getDebuggerInfo", "DEBUGGER_INFO", "replaceBackSlashes", + "wrapCommand", ] # Map of debugging programs to information about them, like default arguments # and whether or not they are interactive. DEBUGGER_INFO = { # gdb requires that you supply the '--args' flag in order to pass arguments # after the executable name to the executable. "gdb": { @@ -354,8 +355,21 @@ def processLeakLog(leakLogFile, leakThre m = pidRegExp.search(fileName) if m: processType = m.group(1) processPID = m.group(2) processSingleLeakFile(thisFile, processPID, processType, leakThreshold) def replaceBackSlashes(input): return input.replace('\\', '/') + +def wrapCommand(cmd): + """ + If running on OS X 10.5 or older, wrap |cmd| so that it will + be executed as an i386 binary, in case it's a 32-bit/64-bit universal + binary. + """ + if platform.system() == "Darwin" and \ + hasattr(platform, 'mac_ver') and \ + platform.mac_ver()[0][:4] < '10.6': + return ["arch", "-arch", "i386"] + cmd + # otherwise just execute the command normally + return cmd
--- a/configure.in +++ b/configure.in @@ -5996,27 +5996,29 @@ if test -n "$MOZ_WEBM"; then if test -z "$GNU_CC"; then VPX_ASFLAGS="-f x64 -rnasm -pnasm" VPX_X86_ASM=1 fi ;; WINNT:x86) if test -z "$GNU_CC"; then dnl Check for yasm 1.1 or greater. - if test "$_YASM_MAJOR_VERSION" -gt "1" -o \( "$_YASM_MAJOR_VERSION" -eq "1" -a "$_YASM_MINOR_VERSION" -ge "1" \) ; then + if test -n "$COMPILE_ENVIRONMENT" -a -z "$YASM"; then + AC_MSG_ERROR([yasm 1.1 or greater is required to build libvpx on Win32, but it appears not to be installed. Install it (included in MozillaBuild 1.5.1 and newer) or configure with --disable-webm (which disables the WebM video format). See https://developer.mozilla.org/en/YASM for more details.]) + elif test -n "$COMPILE_ENVIRONMENT" -a "$_YASM_MAJOR_VERSION" -lt "1" -o \( "$_YASM_MAJOR_VERSION" -eq "1" -a "$_YASM_MINOR_VERSION" -lt "1" \) ; then + AC_MSG_ERROR([yasm 1.1 or greater is required to build libvpx on Win32, but you appear to have version $_YASM_MAJOR_VERSION.$_YASM_MINOR_VERSION. Upgrade to the newest version (included in MozillaBuild 1.5.1 and newer) or configure with --disable-webm (which disables the WebM video format). See https://developer.mozilla.org/en/YASM for more details.]) + else VPX_ASFLAGS="-f win32 -rnasm -pnasm -DPIC" VPX_X86_ASM=1 - else - AC_MSG_ERROR([yasm 1.1 or greater is required to build libvpx on Win32, but you appear to have version $_YASM_MAJOR_VERSION.$_YASM_MINOR_VERSION. Upgrade to the newest version (included in MozillaBuild 1.5.1 and newer) or configure with --disable-webm (which disables the WebM video format). See https://developer.mozilla.org/en/YASM for more details.]) fi fi ;; esac - if test "$COMPILE_ENVIROMENT" -a -n "$VPX_X86_ASM" -a -z "$VPX_AS"; then + if test -n "$COMPILE_ENVIRONMENT" -a -n "$VPX_X86_ASM" -a -z "$VPX_AS"; then AC_MSG_ERROR([yasm is a required build tool for this architecture when webm is enabled. You may either install yasm or --disable-webm (which disables the WebM video format). See https://developer.mozilla.org/en/YASM for more details.]) fi if test -n "$VPX_X86_ASM"; then AC_DEFINE(VPX_X86_ASM) else AC_MSG_WARN([No assembler or assembly support for libvpx. Using unoptimized C routines.]) fi
--- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -1689,33 +1689,16 @@ public: /** * Determine whether a content node is focused or not, * * @param aContent the content node to check * @return true if the content node is focused, false otherwise. */ static PRBool IsFocusedContent(nsIContent *aContent); -#ifdef MOZ_IPC -#ifdef ANDROID - static void SetActiveFrameLoader(nsFrameLoader *aFrameLoader) - { - sActiveFrameLoader = aFrameLoader; - } - - static void ClearActiveFrameLoader(const nsFrameLoader *aFrameLoader) - { - if (sActiveFrameLoader == aFrameLoader) - sActiveFrameLoader = nsnull; - } - - static already_AddRefed<nsFrameLoader> GetActiveFrameLoader(); -#endif -#endif - private: static PRBool InitializeEventTable(); static nsresult EnsureStringBundle(PropertiesFile aFile); static nsIDOMScriptObjectFactory *GetDOMScriptObjectFactory(); @@ -1795,22 +1778,16 @@ private: static PRUint32 sRemovableScriptBlockerCount; static nsCOMArray<nsIRunnable>* sBlockedScriptRunners; static PRUint32 sRunnersCountAtFirstBlocker; static PRUint32 sScriptBlockerCountWhereRunnersPrevented; static nsIInterfaceRequestor* sSameOriginChecker; static PRBool sIsHandlingKeyBoardEvent; - -#ifdef MOZ_IPC -#ifdef ANDROID - static nsFrameLoader *sActiveFrameLoader; -#endif -#endif }; #define NS_HOLD_JS_OBJECTS(obj, clazz) \ nsContentUtils::HoldJSObjects(NS_CYCLE_COLLECTION_UPCAST(obj, clazz), \ &NS_CYCLE_COLLECTION_NAME(clazz)) #define NS_DROP_JS_OBJECTS(obj, clazz) \ nsContentUtils::DropJSObjects(NS_CYCLE_COLLECTION_UPCAST(obj, clazz))
--- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -264,22 +264,16 @@ JSRuntime *nsAutoGCRoot::sJSScriptRuntim PRBool nsContentUtils::sIsHandlingKeyBoardEvent = PR_FALSE; PRBool nsContentUtils::sInitialized = PR_FALSE; nsRefPtrHashtable<nsPrefObserverHashKey, nsPrefOldCallback> *nsContentUtils::sPrefCallbackTable = nsnull; -#ifdef MOZ_IPC -#ifdef ANDROID -nsFrameLoader *nsContentUtils::sActiveFrameLoader = nsnull; -#endif -#endif - static PLDHashTable sEventListenerManagersHash; class EventListenerManagerMapEntry : public PLDHashEntryHdr { public: EventListenerManagerMapEntry(const void *aKey) : mKey(aKey) { @@ -6251,27 +6245,16 @@ mozAutoRemovableBlockerRemover::~mozAuto PRBool nsContentUtils::IsFocusedContent(nsIContent* aContent) { nsFocusManager* fm = nsFocusManager::GetFocusManager(); return fm && fm->GetFocusedContent() == aContent; } -#ifdef MOZ_IPC -#ifdef ANDROID -// static -already_AddRefed<nsFrameLoader> -nsContentUtils::GetActiveFrameLoader() -{ - return nsCOMPtr<nsFrameLoader>(sActiveFrameLoader).forget(); -} -#endif -#endif - void nsContentUtils::RemoveNewlines(nsString &aString) { // strip CR/LF and null static const char badChars[] = {'\r', '\n', 0}; aString.StripChars(badChars); } void
--- a/content/base/src/nsFrameLoader.cpp +++ b/content/base/src/nsFrameLoader.cpp @@ -1042,19 +1042,16 @@ nsFrameLoader::SwapWithOtherLoader(nsFra return NS_OK; } void nsFrameLoader::DestroyChild() { #ifdef MOZ_IPC if (mRemoteBrowser) { -#ifdef ANDROID - nsContentUtils::ClearActiveFrameLoader(this); -#endif mRemoteBrowser->SetOwnerElement(nsnull); // If this fails, it's most likely due to a content-process crash, // and auto-cleanup will kick in. Otherwise, the child side will // destroy itself and send back __delete__(). unused << mRemoteBrowser->SendDestroy(); mRemoteBrowser = nsnull; } #endif @@ -1665,19 +1662,16 @@ nsFrameLoader::GetRemoteBrowser() } #endif NS_IMETHODIMP nsFrameLoader::ActivateRemoteFrame() { #ifdef MOZ_IPC if (mRemoteBrowser) { mRemoteBrowser->Activate(); -#ifdef ANDROID - nsContentUtils::SetActiveFrameLoader(this); -#endif return NS_OK; } #endif return NS_ERROR_UNEXPECTED; } NS_IMETHODIMP nsFrameLoader::SendCrossProcessMouseEvent(const nsAString& aType,
new file mode 100644 --- /dev/null +++ b/content/canvas/crashtests/553938-1.html @@ -0,0 +1,20 @@ +<!doctype html> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <title>ImageData Crash Test</title> + </head> + <body onload="crash();"> + <canvas id="c" width="10" height="10"></canvas> + <script> + function crash() { + var canvas = document.getElementById('c'); + var ctx = canvas.getContext('2d'); + var imgData = {data: new Array(10*10*4), width: 10, height: 10}; + + imgData[0] = 0; + ctx.putImageData(imgData, 0, 0); + } + </script> + </body> +</html>
--- a/content/canvas/crashtests/crashtests.list +++ b/content/canvas/crashtests/crashtests.list @@ -1,2 +1,3 @@ load 360293-1.html load 421715-1.html +load 553938-1.html
--- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -40,19 +40,17 @@ * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef MOZ_IPC -#ifdef ANDROID -#include "mozilla/dom/PBrowserParent.h" -#endif +#include "mozilla/dom/TabParent.h" #endif #include "nsCOMPtr.h" #include "nsEventStateManager.h" #include "nsEventListenerManager.h" #include "nsIMEStateManager.h" #include "nsContentEventHandler.h" #include "nsIContent.h" @@ -161,19 +159,17 @@ #include "nsICommandParams.h" #include "mozilla/Services.h" #ifdef XP_MACOSX #import <ApplicationServices/ApplicationServices.h> #endif #ifdef MOZ_IPC -#ifdef ANDROID -#include "nsFrameLoader.h" -#endif +using namespace mozilla::dom; #endif //#define DEBUG_DOCSHELL_FOCUS #define NS_USER_INTERACTION_INTERVAL 5000 // ms static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); @@ -1316,64 +1312,88 @@ nsEventStateManager::PreHandleEvent(nsPr if ((msEvent->scrollFlags & nsMouseScrollEvent::kIsHorizontal) ? mLastLineScrollConsumedX : mLastLineScrollConsumedY) { *aStatus = nsEventStatus_eConsumeNoDefault; } } break; case NS_QUERY_SELECTED_TEXT: { +#ifdef MOZ_IPC + if (RemoteQueryContentEvent(aEvent)) + break; +#endif nsContentEventHandler handler(mPresContext); handler.OnQuerySelectedText((nsQueryContentEvent*)aEvent); } break; case NS_QUERY_TEXT_CONTENT: { +#ifdef MOZ_IPC + if (RemoteQueryContentEvent(aEvent)) + break; +#endif nsContentEventHandler handler(mPresContext); handler.OnQueryTextContent((nsQueryContentEvent*)aEvent); } break; case NS_QUERY_CARET_RECT: { + // XXX remote event nsContentEventHandler handler(mPresContext); handler.OnQueryCaretRect((nsQueryContentEvent*)aEvent); } break; case NS_QUERY_TEXT_RECT: { + // XXX remote event nsContentEventHandler handler(mPresContext); handler.OnQueryTextRect((nsQueryContentEvent*)aEvent); } break; case NS_QUERY_EDITOR_RECT: { + // XXX remote event nsContentEventHandler handler(mPresContext); handler.OnQueryEditorRect((nsQueryContentEvent*)aEvent); } break; case NS_QUERY_CONTENT_STATE: { + // XXX remote event nsContentEventHandler handler(mPresContext); handler.OnQueryContentState(static_cast<nsQueryContentEvent*>(aEvent)); } break; case NS_QUERY_SELECTION_AS_TRANSFERABLE: { + // XXX remote event nsContentEventHandler handler(mPresContext); handler.OnQuerySelectionAsTransferable(static_cast<nsQueryContentEvent*>(aEvent)); } break; case NS_QUERY_CHARACTER_AT_POINT: { + // XXX remote event nsContentEventHandler handler(mPresContext); handler.OnQueryCharacterAtPoint(static_cast<nsQueryContentEvent*>(aEvent)); } break; case NS_SELECTION_SET: { +#ifdef MOZ_IPC + nsSelectionEvent *selectionEvent = + static_cast<nsSelectionEvent*>(aEvent); + if (IsTargetCrossProcess(selectionEvent)) { + // Will not be handled locally, remote the event + if (GetCrossProcessTarget()->SendSelectionEvent(*selectionEvent)) + selectionEvent->mSucceeded = PR_TRUE; + break; + } +#endif nsContentEventHandler handler(mPresContext); handler.OnSelectionEvent((nsSelectionEvent*)aEvent); } break; case NS_CONTENT_COMMAND_CUT: case NS_CONTENT_COMMAND_COPY: case NS_CONTENT_COMMAND_PASTE: case NS_CONTENT_COMMAND_DELETE: @@ -1385,49 +1405,43 @@ nsEventStateManager::PreHandleEvent(nsPr } break; case NS_CONTENT_COMMAND_SCROLL: { DoContentCommandScrollEvent(static_cast<nsContentCommandEvent*>(aEvent)); } break; #ifdef MOZ_IPC -#ifdef ANDROID case NS_TEXT_TEXT: { nsTextEvent *textEvent = static_cast<nsTextEvent*>(aEvent); if (IsTargetCrossProcess(textEvent)) { // Will not be handled locally, remote the event - mozilla::dom::PBrowserParent *remoteBrowser = GetCrossProcessTarget(); - if (remoteBrowser && - remoteBrowser->SendTextEvent(*textEvent)) { + if (GetCrossProcessTarget()->SendTextEvent(*textEvent)) { // Cancel local dispatching - *aStatus = nsEventStatus_eConsumeNoDefault; + aEvent->flags |= NS_EVENT_FLAG_STOP_DISPATCH; } } } break; case NS_COMPOSITION_START: case NS_COMPOSITION_END: { nsCompositionEvent *compositionEvent = static_cast<nsCompositionEvent*>(aEvent); if (IsTargetCrossProcess(compositionEvent)) { // Will not be handled locally, remote the event - mozilla::dom::PBrowserParent *remoteBrowser = GetCrossProcessTarget(); - if (remoteBrowser && - remoteBrowser->SendCompositionEvent(*compositionEvent)) { + if (GetCrossProcessTarget()->SendCompositionEvent(*compositionEvent)) { // Cancel local dispatching - *aStatus = nsEventStatus_eConsumeNoDefault; + aEvent->flags |= NS_EVENT_FLAG_STOP_DISPATCH; } } } break; -#endif -#endif +#endif // MOZ_IPC } return NS_OK; } static PRInt32 GetAccessModifierMask(nsISupports* aDocShell) { nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell)); @@ -3250,88 +3264,51 @@ nsEventStateManager::PostHandleEvent(nsP mCurrentTarget->GetContentForEvent(presContext, aEvent, getter_AddRefs(targetContent)); if (!NodeAllowsClickThrough(targetContent)) { *aStatus = nsEventStatus_eConsumeNoDefault; } } break; #endif - -#ifdef MOZ_IPC -#ifdef ANDROID - case NS_QUERY_SELECTED_TEXT: - case NS_QUERY_TEXT_CONTENT: - case NS_QUERY_CARET_RECT: - case NS_QUERY_TEXT_RECT: - case NS_QUERY_EDITOR_RECT: - case NS_QUERY_CONTENT_STATE: - // We don't remote nsITransferable yet - //case NS_QUERY_SELECTION_AS_TRANSFERABLE: - case NS_QUERY_CHARACTER_AT_POINT: - { - nsQueryContentEvent *queryEvent = - static_cast<nsQueryContentEvent*>(aEvent); - // If local query failed, try remote query - if (queryEvent->mSucceeded) - break; - - mozilla::dom::PBrowserParent *remoteBrowser = GetCrossProcessTarget(); - if (remoteBrowser && - remoteBrowser->SendQueryContentEvent(*queryEvent)) { - queryEvent->mWasAsync = PR_TRUE; - queryEvent->mSucceeded = PR_TRUE; - } - } - break; - case NS_SELECTION_SET: - { - nsSelectionEvent *selectionEvent = - static_cast<nsSelectionEvent*>(aEvent); - // If local handler failed, try remoting the event - if (selectionEvent->mSucceeded) - break; - - mozilla::dom::PBrowserParent *remoteBrowser = GetCrossProcessTarget(); - if (remoteBrowser && - remoteBrowser->SendSelectionEvent(*selectionEvent)) - selectionEvent->mSucceeded = PR_TRUE; - } - break; -#endif // ANDROID -#endif // MOZ_IPC } //Reset target frame to null to avoid mistargeting after reentrant event mCurrentTarget = nsnull; mCurrentTargetContent = nsnull; return ret; } #ifdef MOZ_IPC -#ifdef ANDROID -mozilla::dom::PBrowserParent* +PRBool +nsEventStateManager::RemoteQueryContentEvent(nsEvent *aEvent) +{ + nsQueryContentEvent *queryEvent = + static_cast<nsQueryContentEvent*>(aEvent); + if (!IsTargetCrossProcess(queryEvent)) { + return PR_FALSE; + } + // Will not be handled locally, remote the event + GetCrossProcessTarget()->HandleQueryContentEvent(*queryEvent); + return PR_TRUE; +} + +TabParent* nsEventStateManager::GetCrossProcessTarget() { - nsCOMPtr<nsFrameLoader> fl = nsContentUtils::GetActiveFrameLoader(); - NS_ENSURE_TRUE(fl, nsnull); - return fl->GetRemoteBrowser(); + return TabParent::GetIMETabParent(); } PRBool nsEventStateManager::IsTargetCrossProcess(nsGUIEvent *aEvent) { - nsQueryContentEvent stateEvent(PR_TRUE, NS_QUERY_CONTENT_STATE, aEvent->widget); - nsContentEventHandler handler(mPresContext); - handler.OnQueryContentState(&stateEvent); - return !stateEvent.mSucceeded; + return TabParent::GetIMETabParent() != nsnull; } #endif -#endif NS_IMETHODIMP nsEventStateManager::NotifyDestroyPresContext(nsPresContext* aPresContext) { nsIMEStateManager::OnDestroyPresContext(aPresContext); return NS_OK; }
--- a/content/events/src/nsEventStateManager.h +++ b/content/events/src/nsEventStateManager.h @@ -57,16 +57,22 @@ class nsIPresShell; class nsIDocShell; class nsIDocShellTreeNode; class nsIDocShellTreeItem; class imgIContainer; class nsDOMDataTransfer; +namespace mozilla { +namespace dom { +class TabParent; +} +} + /* * Event listener manager */ class nsEventStateManager : public nsSupportsWeakReference, public nsIEventStateManager, public nsIObserver { @@ -329,21 +335,20 @@ protected: * mCurrentTarget->GetNearestWidget(). */ void FillInEventFromGestureDown(nsMouseEvent* aEvent); nsresult DoContentCommandEvent(nsContentCommandEvent* aEvent); nsresult DoContentCommandScrollEvent(nsContentCommandEvent* aEvent); #ifdef MOZ_IPC -#ifdef ANDROID - mozilla::dom::PBrowserParent *GetCrossProcessTarget(); + PRBool RemoteQueryContentEvent(nsEvent *aEvent); + mozilla::dom::TabParent *GetCrossProcessTarget(); PRBool IsTargetCrossProcess(nsGUIEvent *aEvent); #endif -#endif PRInt32 mLockCursor; nsWeakFrame mCurrentTarget; nsCOMPtr<nsIContent> mCurrentTargetContent; nsWeakFrame mLastMouseOverFrame; nsCOMPtr<nsIContent> mLastMouseOverElement; nsWeakFrame mLastDragOverFrame;
--- a/content/events/src/nsIMEStateManager.cpp +++ b/content/events/src/nsIMEStateManager.cpp @@ -281,17 +281,18 @@ public: NS_DECL_NSISELECTIONLISTENER NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED nsresult Init(nsIWidget* aWidget, nsPresContext* aPresContext, - nsINode* aNode); + nsINode* aNode, + PRBool aWantUpdates); void Destroy(void); nsCOMPtr<nsIWidget> mWidget; nsCOMPtr<nsISelection> mSel; nsCOMPtr<nsIContent> mRootContent; nsCOMPtr<nsINode> mEditableNode; PRBool mDestroying; @@ -302,20 +303,26 @@ private: nsTextStateManager::nsTextStateManager() { mDestroying = PR_FALSE; } nsresult nsTextStateManager::Init(nsIWidget* aWidget, nsPresContext* aPresContext, - nsINode* aNode) + nsINode* aNode, + PRBool aWantUpdates) { mWidget = aWidget; + if (!aWantUpdates) { + mEditableNode = aNode; + return NS_OK; + } + nsIPresShell* presShell = aPresContext->PresShell(); // get selection and root content nsCOMPtr<nsISelectionController> selCon; if (aNode->IsNodeOfType(nsINode::eCONTENT)) { nsIFrame* frame = static_cast<nsIContent*>(aNode)->GetPrimaryFrame(); NS_ENSURE_TRUE(frame, NS_ERROR_UNEXPECTED); @@ -329,22 +336,27 @@ nsTextStateManager::Init(nsIWidget* aWid nsCOMPtr<nsISelection> sel; nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(sel)); NS_ENSURE_TRUE(sel, NS_ERROR_UNEXPECTED); nsCOMPtr<nsIDOMRange> selDomRange; rv = sel->GetRangeAt(0, getter_AddRefs(selDomRange)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr<nsIRange> selRange(do_QueryInterface(selDomRange)); - NS_ENSURE_TRUE(selRange && selRange->GetStartParent(), NS_ERROR_UNEXPECTED); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIRange> selRange(do_QueryInterface(selDomRange)); + NS_ENSURE_TRUE(selRange && selRange->GetStartParent(), + NS_ERROR_UNEXPECTED); - mRootContent = selRange->GetStartParent()-> + mRootContent = selRange->GetStartParent()-> GetSelectionRootContent(presShell); + } else { + mRootContent = aNode->GetSelectionRootContent(presShell); + } if (!mRootContent && aNode->IsNodeOfType(nsINode::eDOCUMENT)) { // The document node is editable, but there are no contents, this document // is not editable. return NS_ERROR_NOT_AVAILABLE; } NS_ENSURE_TRUE(mRootContent, NS_ERROR_UNEXPECTED); // add text change observer @@ -585,24 +597,27 @@ nsIMEStateManager::OnTextStateFocus(nsPr return NS_OK; // Sometimes, there are no widgets. } rv = widget->OnIMEFocusChange(PR_TRUE); if (rv == NS_ERROR_NOT_IMPLEMENTED) return NS_OK; NS_ENSURE_SUCCESS(rv, rv); + PRBool wantUpdates = rv != NS_SUCCESS_IME_NO_UPDATES; + // OnIMEFocusChange may cause focus and sTextStateObserver to change // In that case return and keep the current sTextStateObserver NS_ENSURE_TRUE(!sTextStateObserver, NS_OK); sTextStateObserver = new nsTextStateManager(); NS_ENSURE_TRUE(sTextStateObserver, NS_ERROR_OUT_OF_MEMORY); NS_ADDREF(sTextStateObserver); - rv = sTextStateObserver->Init(widget, aPresContext, editableNode); + rv = sTextStateObserver->Init(widget, aPresContext, + editableNode, wantUpdates); if (NS_FAILED(rv)) { sTextStateObserver->mDestroying = PR_TRUE; sTextStateObserver->Destroy(); NS_RELEASE(sTextStateObserver); widget->OnIMEFocusChange(PR_FALSE); return rv; } return NS_OK;
--- a/content/html/content/test/test_bug561636.html +++ b/content/html/content/test/test_bug561636.html @@ -5,17 +5,17 @@ https://bugzilla.mozilla.org/show_bug.cg --> <head> <title>Test for Bug 561636</title> <script type="application/javascript" src="/MochiKit/packed.js"></script> <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> </head> -<body> +<body onload="runTest();"> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=561636">Mozilla Bug 561636</a> <p id="display"></p> <iframe style='width:50px; height: 50px;' name='t'></iframe> <iframe style='width:50px; height: 50px;' name='t2' id='i'></iframe> <div id="content"> <form target='t' action='data:text/html,'> <input required> <input id='a' type='submit'> @@ -43,69 +43,74 @@ https://bugzilla.mozilla.org/show_bug.cg var formSubmitted = [ false, false ]; var invalidHandled = false; netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); var os = Components.classes['@mozilla.org/observer-service;1'] .getService(Components.interfaces.nsIObserverService); var observers = os.enumerateObservers("invalidformsubmit"); -// The following test should not be done if there is no observer for -// "invalidformsubmit" because the form submission will not be canceled in that -// case. -if (observers.hasMoreElements()) { - SimpleTest.waitForExplicitFinish(); - - // Initialize - document.forms[0].addEventListener('submit', function(aEvent) { - aEvent.target.removeEventListener('submit', arguments.callee, false); - formSubmitted[0] = true; - }, false); +function runTest() +{ + // The following test should not be done if there is no observer for + // "invalidformsubmit" because the form submission will not be canceled in that + // case. + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + if (observers.hasMoreElements()) { + SimpleTest.waitForExplicitFinish(); - document.forms[1].addEventListener('submit', function(aEvent) { - aEvent.target.removeEventListener('submit', arguments.callee, false); - formSubmitted[1] = true; - }, false); + // Initialize + document.forms[0].addEventListener('submit', function(aEvent) { + aEvent.target.removeEventListener('submit', arguments.callee, false); + formSubmitted[0] = true; + }, false); - document.forms[2].addEventListener('submit', function(aEvent) { - aEvent.target.removeEventListener('submit', arguments.callee, false); - formSubmitted[2] = true; - }, false); + document.forms[1].addEventListener('submit', function(aEvent) { + aEvent.target.removeEventListener('submit', arguments.callee, false); + formSubmitted[1] = true; + }, false); - document.forms[3].addEventListener('submit', function(aEvent) { - aEvent.target.removeEventListener('submit', arguments.callee, false); - formSubmitted[3] = true; + document.forms[2].addEventListener('submit', function(aEvent) { + aEvent.target.removeEventListener('submit', arguments.callee, false); + formSubmitted[2] = true; + }, false); + + document.forms[3].addEventListener('submit', function(aEvent) { + aEvent.target.removeEventListener('submit', arguments.callee, false); + formSubmitted[3] = true; - ok(!formSubmitted[0], "Form 1 should not have been submitted because invalid"); - ok(!formSubmitted[1], "Form 2 should not have been submitted because invalid"); - ok(!formSubmitted[2], "Form 3 should not have been submitted because invalid"); - ok(formSubmitted[3], "Form 4 should have been submitted because valid"); - }, false); + ok(!formSubmitted[0], "Form 1 should not have been submitted because invalid"); + ok(!formSubmitted[1], "Form 2 should not have been submitted because invalid"); + ok(!formSubmitted[2], "Form 3 should not have been submitted because invalid"); + ok(formSubmitted[3], "Form 4 should have been submitted because valid"); - document.forms[4].elements[0].addEventListener('invalid', function(aEvent) { - aEvent.target.removeEventListener('invalid', arguments.callee, false); - invalidHandled = true; - }, false); + // Next test. + document.forms[4].submit(); + }, false); - document.getElementById('i').addEventListener('load', function(aEvent) { - aEvent.target.removeEventListener('load', arguments.callee, false); + document.forms[4].elements[0].addEventListener('invalid', function(aEvent) { + aEvent.target.removeEventListener('invalid', arguments.callee, false); + invalidHandled = true; + }, false); - SimpleTest.executeSoon(function () { - ok(true, "Form 5 should have been submitted because submit() has been used even if invalid"); - ok(!invalidHandled, "Invalid event should not have been sent"); + document.getElementById('i').addEventListener('load', function(aEvent) { + aEvent.target.removeEventListener('load', arguments.callee, false); - SimpleTest.finish(); - }); - }, false); + SimpleTest.executeSoon(function () { + ok(true, "Form 5 should have been submitted because submit() has been used even if invalid"); + ok(!invalidHandled, "Invalid event should not have been sent"); - document.getElementById('a').click(); - document.getElementById('b').click(); - var c = document.getElementById('c'); - c.focus(); synthesizeKey("VK_RETURN", {type: "keypress"}); - document.getElementById('s2').click(); + SimpleTest.finish(); + }); + }, false); - document.forms[3].submit(); + document.getElementById('a').click(); + document.getElementById('b').click(); + var c = document.getElementById('c'); + c.focus(); synthesizeKey("VK_RETURN", {type: "keypress"}); + document.getElementById('s2').click(); + } } </script> </pre> </body> </html>
--- a/content/html/document/test/test_bug446483.html +++ b/content/html/document/test/test_bug446483.html @@ -6,38 +6,44 @@ https://bugzilla.mozilla.org/show_bug.cg <head> <title>Test for Bug 446483</title> <script type="application/javascript" src="/MochiKit/packed.js"></script> <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=446483">Mozilla Bug 446483</a> -<p id="display"> -<iframe src="bug446483-iframe.html"></iframe> -<iframe src="bug446483-iframe.html"></iframe> -</p> +<p id="display"></p> <div id="content" style="display: none"> </div> <pre id="test"> <script type="application/javascript"> /** Test for Bug 446483 **/ function gc() { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIDOMWindowUtils) .garbageCollect(); } function runTest() { + document.getElementById('display').innerHTML = + '<iframe src="bug446483-iframe.html"><\/iframe>\n' + + '<iframe src="bug446483-iframe.html"><\/iframe>\n'; + setInterval(gc, 1000); - setTimeout(function(){document.getElementById('display').innerHTML='';ok(true,''); SimpleTest.finish();}, 4000); + + setTimeout(function() { + document.getElementById('display').innerHTML = ''; + ok(true, ''); + SimpleTest.finish(); + }, 4000); } SimpleTest.waitForExplicitFinish(); addLoadEvent(runTest); </script> </pre> </body>
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -245,17 +245,17 @@ nsDOMWindowUtils::SetCSSViewport(float a nsIPresShell* presShell = GetPresShell(); if (!presShell) { return NS_ERROR_FAILURE; } nscoord width = nsPresContext::CSSPixelsToAppUnits(aWidthPx); nscoord height = nsPresContext::CSSPixelsToAppUnits(aHeightPx); - presShell->ResizeReflow(width, height); + presShell->ResizeReflowOverride(width, height); return NS_OK; } NS_IMETHODIMP nsDOMWindowUtils::SetDisplayPort(float aXPx, float aYPx, float aWidthPx, float aHeightPx) {
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -57,21 +57,16 @@ #include "nsCExternalHandlerService.h" #include "nsFrameMessageManager.h" #include "nsIAlertsService.h" #include "nsToolkitCompsCID.h" #include "nsIDOMGeoGeolocation.h" #include "mozilla/dom/ExternalHelperAppParent.h" -#ifdef ANDROID -#include "AndroidBridge.h" -using namespace mozilla; -#endif - using namespace mozilla::ipc; using namespace mozilla::net; using namespace mozilla::places; using mozilla::MonitorAutoEnter; namespace mozilla { namespace dom { @@ -563,43 +558,16 @@ ContentParent::AfterProcessNextEvent(nsI } if (mOldObserver) return mOldObserver->AfterProcessNextEvent(thread, recursionDepth); return NS_OK; } - -bool -ContentParent::RecvNotifyIMEChange(const nsString& aText, - const PRUint32& aTextLen, - const int& aStart, const int& aEnd, - const int& aNewEnd) -{ -#ifdef ANDROID - AndroidBridge::Bridge()->NotifyIMEChange(aText.get(), aTextLen, - aStart, aEnd, aNewEnd); - return true; -#else - return false; -#endif -} - -bool -ContentParent::RecvNotifyIME(const int& aType, const int& aStatus) -{ -#ifdef ANDROID - AndroidBridge::Bridge()->NotifyIME(aType, aStatus); - return true; -#else - return false; -#endif -} - bool ContentParent::RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, const nsString& aText, const PRBool& aTextClickable, const nsString& aCookie, const nsString& aName) { nsCOMPtr<nsIAlertsService> sysAlerts(do_GetService(NS_ALERTSERVICE_CONTRACTID)); if (sysAlerts) { sysAlerts->ShowAlertNotification(aImageUrl, aTitle, aText, aTextClickable,
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -160,21 +160,16 @@ private: virtual bool RecvStartVisitedQuery(const IPC::URI& uri); virtual bool RecvVisitURI(const IPC::URI& uri, const IPC::URI& referrer, const PRUint32& flags); virtual bool RecvSetURITitle(const IPC::URI& uri, const nsString& title); - - virtual bool RecvNotifyIME(const int&, const int&); - - virtual bool RecvNotifyIMEChange(const nsString&, const PRUint32&, const int&, - const int&, const int&); virtual bool RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, const nsString& aText, const PRBool& aTextClickable, const nsString& aCookie, const nsString& aName); virtual bool RecvLoadURIExternal(const IPC::URI& uri); virtual bool RecvSyncMessage(const nsString& aMsg, const nsString& aJSON,
--- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -53,16 +53,17 @@ include "IPC/nsGUIEventIPC.h"; using gfxMatrix; using IPC::URI; using nsIntSize; using nsCompositionEvent; using nsTextEvent; using nsQueryContentEvent; using nsSelectionEvent; using RemoteDOMEvent; +using nsIMEUpdatePreference; namespace mozilla { namespace dom { rpc protocol PBrowser { manager PContent; @@ -85,17 +86,78 @@ parent: Event(RemoteDOMEvent aEvent); rpc CreateWindow() returns (PBrowser window); sync SyncMessage(nsString aMessage, nsString aJSON) returns (nsString[] retval); - QueryContentResult(nsQueryContentEvent event); + /** + * Notifies chrome that there is a focus change involving an editable + * object (input, textarea, document, contentEditable. etc.) + * + * focus PR_TRUE if editable object is receiving focus + * PR_FALSE if losing focus + * preference Native widget preference for IME updates + */ + sync NotifyIMEFocus(PRBool focus) + returns (nsIMEUpdatePreference preference); + + /** + * Notifies chrome that there has been a change in text content + * One call can encompass both a delete and an insert operation + * Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates + * + * offset Starting offset of the change + * end Ending offset of the range deleted + * newEnd New ending offset after insertion + * + * for insertion, offset == end + * for deletion, offset == newEnd + */ + NotifyIMETextChange(PRUint32 offset, PRUint32 end, PRUint32 newEnd); + + /** + * Notifies chrome that there has been a change in selection + * Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates + * + * anchor Offset where the selection started + * focus Offset where the caret is + */ + NotifyIMESelection(PRUint32 anchor, PRUint32 focus); + + /** + * Notifies chrome to refresh its text cache + * Only called when NotifyIMEFocus returns PR_TRUE for mWantHints + * + * text The entire content of the text field + */ + NotifyIMETextHint(nsString text); + + /** + * Instructs chrome to end any pending composition + * + * cancel PR_TRUE if composition should be cancelled + * composition Text to commit before ending the composition + * + * if cancel is PR_TRUE, + * widget should return empty string for composition + * if cancel is PR_FALSE, + * widget should return the current composition text + */ + sync EndIMEComposition(PRBool cancel) returns (nsString composition); + + sync GetIMEEnabled() returns (PRUint32 value); + + SetIMEEnabled(PRUint32 value); + + sync GetIMEOpenState() returns (PRBool value); + + SetIMEOpenState(PRBool value); PContentPermissionRequest(nsCString aType, URI uri); PContentDialog(PRUint32 aType, nsCString aName, nsCString aFeatures, PRInt32[] aIntParams, nsString[] aStringParams); /** * Create a layout frame (encapsulating a remote layer tree) for @@ -145,18 +207,16 @@ child: PRInt32 aCharCode, PRInt32 aModifiers, bool aPreventDefault); CompositionEvent(nsCompositionEvent event); TextEvent(nsTextEvent event); - QueryContentEvent(nsQueryContentEvent event); - SelectionEvent(nsSelectionEvent event); /** * Activate event forwarding from client to parent. */ ActivateFrameEvent(nsString aType, bool capture); LoadRemoteScript(nsString aURL);
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -99,19 +99,16 @@ parent: sync GetCharPref(nsCString prefName) returns (nsCString retValue, nsresult rv); sync GetPrefLocalizedString(nsCString prefName) returns (nsString retValue, nsresult rv); sync PrefHasUserValue(nsCString prefName) returns (PRBool retValue, nsresult rv); sync PrefIsLocked(nsCString prefName) returns (PRBool retValue, nsresult rv); sync GetChildList(nsCString domain) returns (nsCString[] list, nsresult rv); // PermissionsManager messages sync TestPermission(URI uri, nsCString type, PRBool exact) returns (PRUint32 retValue); - NotifyIME(int aType, int aState); - NotifyIMEChange(nsString aText, PRUint32 aTextLen, - int aStart, int aEnd, int aNewEnd); sync SyncMessage(nsString aMessage, nsString aJSON) returns (nsString[] retval); ShowAlertNotification(nsString imageUrl, nsString title, nsString text, PRBool textClickable,
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -567,58 +567,32 @@ TabChild::RecvTextEvent(const nsTextEven { nsTextEvent localEvent(event); DispatchWidgetEvent(localEvent); IPC::ParamTraits<nsTextEvent>::Free(event); return true; } bool -TabChild::RecvQueryContentEvent(const nsQueryContentEvent& event) -{ - nsQueryContentEvent localEvent(event); - DispatchWidgetEvent(localEvent); - // Send result back even if query failed - SendQueryContentResult(localEvent); - return true; -} - -bool TabChild::RecvSelectionEvent(const nsSelectionEvent& event) { nsSelectionEvent localEvent(event); DispatchWidgetEvent(localEvent); return true; } bool TabChild::DispatchWidgetEvent(nsGUIEvent& event) { - nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(mWebNav); - NS_ENSURE_TRUE(window, false); - - nsIDocShell *docShell = window->GetDocShell(); - NS_ENSURE_TRUE(docShell, false); - - nsCOMPtr<nsIPresShell> presShell; - docShell->GetPresShell(getter_AddRefs(presShell)); - NS_ENSURE_TRUE(presShell, false); - - nsIFrame *frame = presShell->GetRootFrame(); - NS_ENSURE_TRUE(frame, false); - - nsIView *view = frame->GetView(); - NS_ENSURE_TRUE(view, false); - - nsCOMPtr<nsIWidget> widget = view->GetNearestWidget(nsnull); - NS_ENSURE_TRUE(widget, false); + if (!mWidget) + return false; nsEventStatus status; - event.widget = widget; - NS_ENSURE_SUCCESS(widget->DispatchEvent(&event, status), false); + event.widget = mWidget; + NS_ENSURE_SUCCESS(mWidget->DispatchEvent(&event, status), false); return true; } mozilla::ipc::PDocumentRendererChild* TabChild::AllocPDocumentRenderer(const PRInt32& x, const PRInt32& y, const PRInt32& w, const PRInt32& h, @@ -996,17 +970,17 @@ TabChild::InitTabChildGlobal() return true; } bool TabChild::InitWidget(const nsIntSize& size) { NS_ABORT_IF_FALSE(!mWidget && !mRemoteFrame, "CreateWidget twice?"); - mWidget = nsIWidget::CreatePuppetWidget(); + mWidget = nsIWidget::CreatePuppetWidget(this); if (!mWidget) { NS_ERROR("couldn't create fake widget"); return false; } mWidget->Create( nsnull, 0, // no parents nsIntRect(nsIntPoint(0, 0), size), nsnull, // HandleWidgetEvent
--- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -187,17 +187,16 @@ public: const bool& aIgnoreRootScrollFrame); virtual bool RecvKeyEvent(const nsString& aType, const PRInt32& aKeyCode, const PRInt32& aCharCode, const PRInt32& aModifiers, const bool& aPreventDefault); virtual bool RecvCompositionEvent(const nsCompositionEvent& event); virtual bool RecvTextEvent(const nsTextEvent& event); - virtual bool RecvQueryContentEvent(const nsQueryContentEvent& event); virtual bool RecvSelectionEvent(const nsSelectionEvent& event); virtual bool RecvActivateFrameEvent(const nsString& aType, const bool& capture); virtual bool RecvLoadRemoteScript(const nsString& aURL); virtual bool RecvAsyncMessage(const nsString& aMessage, const nsString& aJSON); virtual mozilla::ipc::PDocumentRendererChild* AllocPDocumentRenderer( const PRInt32& x, const PRInt32& y,
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -66,36 +66,35 @@ #include "nsIDOMNSHTMLFrameElement.h" #include "nsIDialogCreator.h" #include "nsThreadUtils.h" #include "nsSerializationHelper.h" #include "nsIPromptFactory.h" #include "nsIContent.h" #include "mozilla/unused.h" -#ifdef ANDROID -#include "AndroidBridge.h" -using namespace mozilla; -#endif - using namespace mozilla::dom; using namespace mozilla::ipc; using namespace mozilla::layout; // The flags passed by the webProgress notifications are 16 bits shifted // from the ones registered by webProgressListeners. #define NOTIFY_FLAG_SHIFT 16 namespace mozilla { namespace dom { +TabParent *TabParent::mIMETabParent = nsnull; + NS_IMPL_ISUPPORTS4(TabParent, nsITabParent, nsIAuthPromptProvider, nsISSLStatusProvider, nsISecureBrowserUI) TabParent::TabParent() : mSecurityState(0) + , mIMECompositionEnding(PR_FALSE) + , mIMEComposing(PR_FALSE) { } TabParent::~TabParent() { } void @@ -312,38 +311,242 @@ TabParent::RecvSyncMessage(const nsStrin bool TabParent::RecvAsyncMessage(const nsString& aMessage, const nsString& aJSON) { return ReceiveMessage(aMessage, PR_FALSE, aJSON, nsnull); } bool -TabParent::RecvQueryContentResult(const nsQueryContentEvent& event) +TabParent::RecvNotifyIMEFocus(const PRBool& aFocus, + nsIMEUpdatePreference* aPreference) +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) + return true; + + mIMETabParent = aFocus ? this : nsnull; + mIMESelectionAnchor = 0; + mIMESelectionFocus = 0; + nsresult rv = widget->OnIMEFocusChange(aFocus); + + if (aFocus) { + if (NS_SUCCEEDED(rv) && rv != NS_SUCCESS_IME_NO_UPDATES) { + *aPreference = widget->GetIMEUpdatePreference(); + } else { + aPreference->mWantUpdates = PR_FALSE; + aPreference->mWantHints = PR_FALSE; + } + } else { + mIMECacheText.Truncate(0); + } + return true; +} + +bool +TabParent::RecvNotifyIMETextChange(const PRUint32& aStart, + const PRUint32& aEnd, + const PRUint32& aNewEnd) +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) + return true; + + widget->OnIMETextChange(aStart, aEnd, aNewEnd); + return true; +} + +bool +TabParent::RecvNotifyIMESelection(const PRUint32& aAnchor, + const PRUint32& aFocus) +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) + return true; + + mIMESelectionAnchor = aAnchor; + mIMESelectionFocus = aFocus; + widget->OnIMESelectionChange(); + return true; +} + +bool +TabParent::RecvNotifyIMETextHint(const nsString& aText) { -#ifdef ANDROID - if (!event.mSucceeded) { - AndroidBridge::Bridge()->ReturnIMEQueryResult(nsnull, 0, 0, 0); + // Replace our cache with new text + mIMECacheText = aText; + return true; +} + +/** + * Try to answer query event using cached text. + * + * For NS_QUERY_SELECTED_TEXT, fail if the cache doesn't contain the whole + * selected range. (This shouldn't happen because PuppetWidget should have + * already sent the whole selection.) + * + * For NS_QUERY_TEXT_CONTENT, fail only if the cache doesn't overlap with + * the queried range. Note the difference from above. We use + * this behavior because a normal NS_QUERY_TEXT_CONTENT event is allowed to + * have out-of-bounds offsets, so that widget can request content without + * knowing the exact length of text. It's up to widget to handle cases when + * the returned offset/length are different from the queried offset/length. + */ +bool +TabParent::HandleQueryContentEvent(nsQueryContentEvent& aEvent) +{ + aEvent.mSucceeded = PR_FALSE; + aEvent.mWasAsync = PR_FALSE; + aEvent.mReply.mFocusedWidget = nsCOMPtr<nsIWidget>(GetWidget()).get(); + + switch (aEvent.message) + { + case NS_QUERY_SELECTED_TEXT: + { + aEvent.mReply.mOffset = PR_MIN(mIMESelectionAnchor, mIMESelectionFocus); + if (mIMESelectionAnchor == mIMESelectionFocus) { + aEvent.mReply.mString.Truncate(0); + } else { + if (mIMESelectionAnchor > mIMECacheText.Length() || + mIMESelectionFocus > mIMECacheText.Length()) { + break; + } + PRUint32 selLen = mIMESelectionAnchor > mIMESelectionFocus ? + mIMESelectionAnchor - mIMESelectionFocus : + mIMESelectionFocus - mIMESelectionAnchor; + aEvent.mReply.mString = Substring(mIMECacheText, + aEvent.mReply.mOffset, + selLen); + } + aEvent.mReply.mReversed = mIMESelectionFocus < mIMESelectionAnchor; + aEvent.mReply.mHasSelection = PR_TRUE; + aEvent.mSucceeded = PR_TRUE; + } + break; + case NS_QUERY_TEXT_CONTENT: + { + PRUint32 inputOffset = aEvent.mInput.mOffset, + inputEnd = inputOffset + aEvent.mInput.mLength; + + if (inputEnd > mIMECacheText.Length()) { + inputEnd = mIMECacheText.Length(); + } + if (inputEnd < inputOffset) { + break; + } + aEvent.mReply.mOffset = inputOffset; + aEvent.mReply.mString = Substring(mIMECacheText, + inputOffset, + inputEnd - inputOffset); + aEvent.mSucceeded = PR_TRUE; + } + break; + } + return true; +} + +bool +TabParent::SendCompositionEvent(const nsCompositionEvent& event) +{ + mIMEComposing = event.message == NS_COMPOSITION_START; + mIMECompositionStart = PR_MIN(mIMESelectionAnchor, mIMESelectionFocus); + if (mIMECompositionEnding) + return true; + return PBrowserParent::SendCompositionEvent(event); +} + +/** + * During ResetInputState or CancelComposition, widget usually sends a + * NS_TEXT_TEXT event to finalize or clear the composition, respectively + * + * Because the event will not reach content in time, we intercept it + * here and pass the text as the EndIMEComposition return value + */ +bool +TabParent::SendTextEvent(const nsTextEvent& event) +{ + if (mIMECompositionEnding) { + mIMECompositionText = event.theText; return true; } - switch (event.message) { - case NS_QUERY_TEXT_CONTENT: - AndroidBridge::Bridge()->ReturnIMEQueryResult( - event.mReply.mString.get(), event.mReply.mString.Length(), 0, 0); - break; - case NS_QUERY_SELECTED_TEXT: - AndroidBridge::Bridge()->ReturnIMEQueryResult( - event.mReply.mString.get(), - event.mReply.mString.Length(), - event.GetSelectionStart(), - event.GetSelectionEnd() - event.GetSelectionStart()); - break; + // We must be able to simulate the selection because + // we might not receive selection updates in time + if (!mIMEComposing) { + mIMECompositionStart = PR_MIN(mIMESelectionAnchor, mIMESelectionFocus); + } + mIMESelectionAnchor = mIMESelectionFocus = + mIMECompositionStart + event.theText.Length(); + + return PBrowserParent::SendTextEvent(event); +} + +bool +TabParent::SendSelectionEvent(const nsSelectionEvent& event) +{ + mIMESelectionAnchor = event.mOffset + (event.mReversed ? event.mLength : 0); + mIMESelectionFocus = event.mOffset + (!event.mReversed ? event.mLength : 0); + return PBrowserParent::SendSelectionEvent(event); +} + +bool +TabParent::RecvEndIMEComposition(const PRBool& aCancel, + nsString* aComposition) +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) + return true; + + mIMECompositionEnding = PR_TRUE; + + if (aCancel) { + widget->CancelIMEComposition(); + } else { + widget->ResetInputState(); } -#endif + + mIMECompositionEnding = PR_FALSE; + *aComposition = mIMECompositionText; + mIMECompositionText.Truncate(0); + return true; +} + +bool +TabParent::RecvGetIMEEnabled(PRUint32* aValue) +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) + widget->GetIMEEnabled(aValue); + return true; +} + +bool +TabParent::RecvSetIMEEnabled(const PRUint32& aValue) +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) + widget->SetIMEEnabled(aValue); + return true; +} + +bool +TabParent::RecvGetIMEOpenState(PRBool* aValue) +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) + widget->GetIMEOpenState(aValue); + return true; +} + +bool +TabParent::RecvSetIMEOpenState(const PRBool& aValue) +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) + widget->SetIMEOpenState(aValue); return true; } bool TabParent::ReceiveMessage(const nsString& aMessage, PRBool aSync, const nsString& aJSON, nsTArray<nsString>* aJSONRetVal) @@ -495,10 +698,24 @@ TabParent::ShouldDelayDialogs() already_AddRefed<nsFrameLoader> TabParent::GetFrameLoader() const { nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(mFrameElement); return frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nsnull; } +already_AddRefed<nsIWidget> +TabParent::GetWidget() const +{ + nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement); + if (!content) + return nsnull; + + nsIFrame *frame = content->GetPrimaryFrame(); + if (!frame) + return nsnull; + + return nsCOMPtr<nsIWidget>(frame->GetNearestWidget()).forget(); +} + } // namespace tabs } // namespace mozilla
--- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -86,17 +86,30 @@ public: virtual bool RecvEvent(const RemoteDOMEvent& aEvent); virtual bool AnswerCreateWindow(PBrowserParent** retval); virtual bool RecvSyncMessage(const nsString& aMessage, const nsString& aJSON, nsTArray<nsString>* aJSONRetVal); virtual bool RecvAsyncMessage(const nsString& aMessage, const nsString& aJSON); - virtual bool RecvQueryContentResult(const nsQueryContentEvent& event); + virtual bool RecvNotifyIMEFocus(const PRBool& aFocus, + nsIMEUpdatePreference* aPreference); + virtual bool RecvNotifyIMETextChange(const PRUint32& aStart, + const PRUint32& aEnd, + const PRUint32& aNewEnd); + virtual bool RecvNotifyIMESelection(const PRUint32& aAnchor, + const PRUint32& aFocus); + virtual bool RecvNotifyIMETextHint(const nsString& aText); + virtual bool RecvEndIMEComposition(const PRBool& aCancel, + nsString* aComposition); + virtual bool RecvGetIMEEnabled(PRUint32* aValue); + virtual bool RecvSetIMEEnabled(const PRUint32& aValue); + virtual bool RecvGetIMEOpenState(PRBool* aValue); + virtual bool RecvSetIMEOpenState(const PRBool& aValue); virtual PContentDialogParent* AllocPContentDialog(const PRUint32& aType, const nsCString& aName, const nsCString& aFeatures, const nsTArray<int>& aIntParams, const nsTArray<nsString>& aStringParams); virtual bool DeallocPContentDialog(PContentDialogParent* aDialog) { delete aDialog; @@ -158,16 +171,22 @@ public: JSBool GetGlobalJSObject(JSContext* cx, JSObject** globalp); NS_DECL_ISUPPORTS NS_DECL_NSIAUTHPROMPTPROVIDER NS_DECL_NSISECUREBROWSERUI NS_DECL_NSISSLSTATUSPROVIDER void HandleDelayedDialogs(); + + static TabParent *GetIMETabParent() { return mIMETabParent; } + bool HandleQueryContentEvent(nsQueryContentEvent& aEvent); + bool SendCompositionEvent(const nsCompositionEvent& event); + bool SendTextEvent(const nsTextEvent& event); + bool SendSelectionEvent(const nsSelectionEvent& event); protected: bool ReceiveMessage(const nsString& aMessage, PRBool aSync, const nsString& aJSON, nsTArray<nsString>* aJSONRetVal = nsnull); void ActorDestroy(ActorDestroyReason why); @@ -197,16 +216,29 @@ protected: virtual PRenderFrameParent* AllocPRenderFrame(); NS_OVERRIDE virtual bool DeallocPRenderFrame(PRenderFrameParent* aFrame); PRUint32 mSecurityState; nsString mSecurityTooltipText; nsCOMPtr<nsISupports> mSecurityStatusObject; + // IME + static TabParent *mIMETabParent; + nsString mIMECacheText; + PRUint32 mIMESelectionAnchor; + PRUint32 mIMESelectionFocus; + PRPackedBool mIMEComposing; + PRPackedBool mIMECompositionEnding; + // Buffer to store composition text during ResetInputState + // Compositions in almost all cases are small enough for nsAutoString + nsAutoString mIMECompositionText; + PRUint32 mIMECompositionStart; + private: already_AddRefed<nsFrameLoader> GetFrameLoader() const; + already_AddRefed<nsIWidget> GetWidget() const; }; } // namespace dom } // namespace mozilla #endif
--- a/editor/libeditor/base/nsEditor.cpp +++ b/editor/libeditor/base/nsEditor.cpp @@ -306,16 +306,31 @@ nsEditor::PostCreate() // nuke the modification count, so the doc appears unmodified // do this before we notify listeners ResetModificationCount(); // update the UI with our state NotifyDocumentListeners(eDocumentCreated); NotifyDocumentListeners(eDocumentStateChanged); + // update nsTextStateManager if we have focus + if (HasFocus()) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + NS_ASSERTION(fm, "no focus manager?"); + + nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent(); + if (focusedContent) { + nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak); + NS_ASSERTION(ps, "no pres shell even though we have focus"); + nsPresContext* pc = ps->GetPresContext(); + + nsIMEStateManager::OnTextStateBlur(pc, nsnull); + nsIMEStateManager::OnTextStateFocus(pc, focusedContent); + } + } return NS_OK; } nsresult nsEditor::CreateEventListeners() { // Don't create the handler twice if (mEventListener)
new file mode 100644 --- /dev/null +++ b/editor/libeditor/html/crashtests/499844-1.html @@ -0,0 +1,15 @@ +<html> +<head> +<script type="text/javascript"> + +function boom() +{ + document.body.contentEditable = "true"; + document.execCommand("outdent", false, null); +} + +</script> +</head> + +<body style="word-spacing: 3px;" onload="boom();"> ́</body> +</html>
--- a/editor/libeditor/html/crashtests/crashtests.list +++ b/editor/libeditor/html/crashtests/crashtests.list @@ -7,13 +7,14 @@ load 420439.html load 428489-1.html load 431086-1.xhtml load 448329-1.html load 448329-2.html asserts(8-20) load 448329-3.html load 456727-1.html load 456727-2.html asserts(1) load 467647-1.html # bug 382210 +load 499844-1.html load 503709-1.xhtml load 513375-1.xhtml load 535632-1.xhtml load 574558-1.xhtml load 582138-1.xhtml
--- a/embedding/android/GeckoAppShell.java +++ b/embedding/android/GeckoAppShell.java @@ -267,16 +267,17 @@ class GeckoAppShell break; case NOTIFY_IME_CANCELCOMPOSITION: IMEStateUpdater.resetIME(); break; case NOTIFY_IME_FOCUSCHANGE: GeckoApp.surfaceView.mIMEFocus = state != 0; + IMEStateUpdater.resetIME(); break; } } public static void notifyIMEChange(String text, int start, int end, int newEnd) { if (GeckoApp.surfaceView == null || GeckoApp.surfaceView.inputConnection == null)
--- a/gfx/cairo/cairo/src/cairo-d2d-surface.cpp +++ b/gfx/cairo/cairo/src/cairo-d2d-surface.cpp @@ -2636,55 +2636,141 @@ static cairo_int_status_t srcResource, 0, &rect); } return rv; } +static cairo_int_status_t +_cairo_d2d_blend_surface(cairo_d2d_surface_t *dst, + cairo_d2d_surface_t *src, + const cairo_matrix_t *transform, + cairo_box_t *box, + cairo_clip_t *clip, + cairo_filter_t filter, + float opacity) +{ + if (dst == src) { + // We cannot do self-blend. + return CAIRO_INT_STATUS_UNSUPPORTED; + } + cairo_int_status_t rv = CAIRO_INT_STATUS_SUCCESS; + + _begin_draw_state(dst); + _cairo_d2d_set_clip(dst, clip); + _cairo_d2d_flush(src); + D2D1_SIZE_U sourceSize = src->surfaceBitmap->GetPixelSize(); + + + double x1, x2, y1, y2; + if (box) { + _cairo_box_to_doubles(box, &x1, &y1, &x2, &y2); + } else { + x1 = y1 = 0; + x2 = dst->rt->GetSize().width; + y2 = dst->rt->GetSize().height; + } + + if (clip) { + const cairo_rectangle_int_t *clipExtent = _cairo_clip_get_extents(clip); + x1 = MAX(x1, clipExtent->x); + x2 = MIN(x2, clipExtent->x + clipExtent->width); + y1 = MAX(y1, clipExtent->y); + y2 = MIN(y2, clipExtent->y + clipExtent->height); + } + + // We should be in drawing state for this. + _begin_draw_state(dst); + _cairo_d2d_set_clip (dst, clip); + D2D1_RECT_F rectSrc; + rectSrc.left = (float)(x1 * transform->xx + transform->x0); + rectSrc.top = (float)(y1 * transform->yy + transform->y0); + rectSrc.right = (float)(x2 * transform->xx + transform->x0); + rectSrc.bottom = (float)(y2 * transform->yy + transform->y0); + + if (rectSrc.left < 0 || rectSrc.top < 0 || + rectSrc.right > sourceSize.width || rectSrc.bottom > sourceSize.height) { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + D2D1_RECT_F rectDst; + rectDst.left = (float)x1; + rectDst.top = (float)y1; + rectDst.right = (float)x2; + rectDst.bottom = (float)y2; + + D2D1_BITMAP_INTERPOLATION_MODE interpMode = + D2D1_BITMAP_INTERPOLATION_MODE_LINEAR; + + if (filter == CAIRO_FILTER_NEAREST) { + interpMode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + } + + dst->rt->DrawBitmap(src->surfaceBitmap, + rectDst, + opacity, + interpMode, + rectSrc); + + return rv; +} /** * This function will text if we can use GPU mem cpy to execute an operation with * a surface pattern. If box is NULL it will operate on the entire dst surface. */ static cairo_int_status_t -_cairo_d2d_try_copy(cairo_d2d_surface_t *dst, - cairo_surface_t *src, - cairo_box_t *box, - const cairo_matrix_t *matrix, - cairo_clip_t *clip, - cairo_operator_t op) +_cairo_d2d_try_fastblit(cairo_d2d_surface_t *dst, + cairo_surface_t *src, + cairo_box_t *box, + const cairo_matrix_t *matrix, + cairo_clip_t *clip, + cairo_operator_t op, + cairo_filter_t filter, + float opacity = 1.0f) { - if (op != CAIRO_OPERATOR_SOURCE && - !(op == CAIRO_OPERATOR_OVER && src->content == CAIRO_CONTENT_COLOR)) { - return CAIRO_INT_STATUS_UNSUPPORTED; + if (op == CAIRO_OPERATOR_OVER && src->content == CAIRO_CONTENT_COLOR) { + op = CAIRO_OPERATOR_SOURCE; } - - cairo_point_int_t translation; - if ((box && !box_is_integer(box)) || - !_cairo_matrix_is_integer_translation(matrix, &translation.x, &translation.y)) { + if (op != CAIRO_OPERATOR_SOURCE && op != CAIRO_OPERATOR_OVER) { return CAIRO_INT_STATUS_UNSUPPORTED; } /* For now we do only D2D sources */ if (src->type != CAIRO_SURFACE_TYPE_D2D) { return CAIRO_INT_STATUS_UNSUPPORTED; } + + cairo_d2d_surface_t *d2dsrc = reinterpret_cast<cairo_d2d_surface_t*>(src); + if (op == CAIRO_OPERATOR_OVER && matrix->xy == 0 && matrix->yx == 0) { + return _cairo_d2d_blend_surface(dst, d2dsrc, matrix, box, clip, filter, opacity); + } + if (op == CAIRO_OPERATOR_OVER || opacity != 1.0f) { + // Past this point we will never get into a situation where we can + // support OVER. + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + cairo_point_int_t translation; + if ((box && !box_is_integer(box)) || + !_cairo_matrix_is_integer_translation(matrix, &translation.x, &translation.y)) { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + cairo_rectangle_int_t rect; if (box) { _cairo_box_round_to_rectangle(box, &rect); } else { rect.x = rect.y = 0; rect.width = dst->rt->GetPixelSize().width; rect.height = dst->rt->GetPixelSize().height; } - cairo_d2d_surface_t *d2dsrc = reinterpret_cast<cairo_d2d_surface_t*>(src); - if (d2dsrc->device != dst->device) { // This doesn't work between different devices. return CAIRO_INT_STATUS_UNSUPPORTED; } /* Region we need to clip this operation to */ cairo_region_t *clipping_region = NULL; cairo_region_t *region; @@ -2707,17 +2793,17 @@ static cairo_int_status_t // Areas outside of the surface do not matter. cairo_rectangle_int_t surface_rect = { 0, 0, dst->rt->GetPixelSize().width, dst->rt->GetPixelSize().height }; cairo_region_intersect_rectangle(region, &surface_rect); } cairo_int_status_t rv = _cairo_d2d_copy_surface(dst, d2dsrc, &translation, region); - + cairo_region_destroy(region); return rv; } RefPtr<ID2D1RenderTarget> _cairo_d2d_get_temp_rt(cairo_d2d_surface_t *surf, cairo_clip_t *clip) { RefPtr<ID3D10Texture2D> texture = _cairo_d2d_get_buffer_texture(surf); @@ -2921,18 +3007,19 @@ static cairo_int_status_t if (op == CAIRO_OPERATOR_CLEAR) { return _cairo_d2d_clear(d2dsurf, clip); } if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { const cairo_surface_pattern_t *surf_pattern = reinterpret_cast<const cairo_surface_pattern_t*>(source); - status = _cairo_d2d_try_copy(d2dsurf, surf_pattern->surface, - NULL, &source->matrix, clip, op); + status = _cairo_d2d_try_fastblit(d2dsurf, surf_pattern->surface, + NULL, &source->matrix, clip, + op, source->filter); if (status != CAIRO_INT_STATUS_UNSUPPORTED) { return status; } } RefPtr<ID2D1RenderTarget> target_rt = d2dsurf->rt; #ifndef ALWAYS_MANUAL_COMPOSITE if (op != CAIRO_OPERATOR_OVER) { @@ -2981,16 +3068,71 @@ static cairo_int_status_t const cairo_pattern_t *mask, cairo_clip_t *clip) { cairo_d2d_surface_t *d2dsurf = static_cast<cairo_d2d_surface_t*>(surface); cairo_rectangle_int_t extents; cairo_int_status_t status; + status = (cairo_int_status_t)_cairo_surface_mask_extents (&d2dsurf->base, + op, source, + mask, + clip, &extents); + if (unlikely (status)) + return status; + + + D2D1_RECT_F rect = D2D1::RectF(0, + 0, + (FLOAT)d2dsurf->rt->GetPixelSize().width, + (FLOAT)d2dsurf->rt->GetPixelSize().height); + + rect.left = (FLOAT)extents.x; + rect.right = (FLOAT)(extents.x + extents.width); + rect.top = (FLOAT)extents.y; + rect.bottom = (FLOAT)(extents.y + extents.height); + + bool isSolidAlphaMask = false; + float solidAlphaValue = 1.0f; + + if (mask->type == CAIRO_PATTERN_TYPE_SOLID) { + cairo_solid_pattern_t *solidPattern = + (cairo_solid_pattern_t*)mask; + if (solidPattern->content = CAIRO_CONTENT_ALPHA) { + isSolidAlphaMask = true; + solidAlphaValue = solidPattern->color.alpha; + } + } + + if (isSolidAlphaMask) { + if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { + const cairo_surface_pattern_t *surf_pattern = + reinterpret_cast<const cairo_surface_pattern_t*>(source); + cairo_box_t box; + _cairo_box_from_rectangle(&box, &extents); + cairo_int_status_t rv = _cairo_d2d_try_fastblit(d2dsurf, + surf_pattern->surface, + &box, + &source->matrix, + clip, + op, + source->filter, + solidAlphaValue); + if (rv != CAIRO_INT_STATUS_UNSUPPORTED) { + return rv; + } + } + } + + RefPtr<ID2D1Brush> brush = _cairo_d2d_create_brush_for_pattern(d2dsurf, source); + if (!brush) { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + RefPtr<ID2D1RenderTarget> target_rt = d2dsurf->rt; #ifndef ALWAYS_MANUAL_COMPOSITE if (op != CAIRO_OPERATOR_OVER) { #endif target_rt = _cairo_d2d_get_temp_rt(d2dsurf, clip); if (!target_rt) { return CAIRO_INT_STATUS_UNSUPPORTED; } @@ -2999,54 +3141,26 @@ static cairo_int_status_t _begin_draw_state(d2dsurf); status = (cairo_int_status_t)_cairo_d2d_set_clip (d2dsurf, clip); if (unlikely(status)) return status; } #endif - status = (cairo_int_status_t)_cairo_surface_mask_extents (&d2dsurf->base, - op, source, - mask, - clip, &extents); - if (unlikely (status)) - return status; - - - RefPtr<ID2D1Brush> brush = _cairo_d2d_create_brush_for_pattern(d2dsurf, source); - if (!brush) { - return CAIRO_INT_STATUS_UNSUPPORTED; - } - - D2D1_RECT_F rect = D2D1::RectF(0, - 0, - (FLOAT)d2dsurf->rt->GetPixelSize().width, - (FLOAT)d2dsurf->rt->GetPixelSize().height); - - rect.left = (FLOAT)extents.x; - rect.right = (FLOAT)(extents.x + extents.width); - rect.top = (FLOAT)extents.y; - rect.bottom = (FLOAT)(extents.y + extents.height); - - - if (mask->type == CAIRO_PATTERN_TYPE_SOLID) { - cairo_solid_pattern_t *solidPattern = - (cairo_solid_pattern_t*)mask; - if (solidPattern->content = CAIRO_CONTENT_ALPHA) { - brush->SetOpacity((FLOAT)solidPattern->color.alpha); - target_rt->FillRectangle(rect, - brush); - brush->SetOpacity(1.0); - - if (target_rt.get() != d2dsurf->rt.get()) { - return _cairo_d2d_blend_temp_surface(d2dsurf, op, target_rt, clip); - } - return CAIRO_INT_STATUS_SUCCESS; + if (isSolidAlphaMask) { + brush->SetOpacity(solidAlphaValue); + target_rt->FillRectangle(rect, + brush); + brush->SetOpacity(1.0); + + if (target_rt.get() != d2dsurf->rt.get()) { + return _cairo_d2d_blend_temp_surface(d2dsurf, op, target_rt, clip); } + return CAIRO_INT_STATUS_SUCCESS; } RefPtr<ID2D1Brush> opacityBrush = _cairo_d2d_create_brush_for_pattern(d2dsurf, mask, true); if (!opacityBrush) { return CAIRO_INT_STATUS_UNSUPPORTED; } if (!d2dsurf->maskLayer) { @@ -3166,18 +3280,19 @@ static cairo_int_status_t cairo_d2d_surface_t *d2dsurf = static_cast<cairo_d2d_surface_t*>(surface); cairo_box_t box; bool is_box = _cairo_path_fixed_is_box(path, &box); if (is_box && source->type == CAIRO_PATTERN_TYPE_SURFACE) { const cairo_surface_pattern_t *surf_pattern = reinterpret_cast<const cairo_surface_pattern_t*>(source); - cairo_int_status_t rv = _cairo_d2d_try_copy(d2dsurf, surf_pattern->surface, - &box, &source->matrix, clip, op); + cairo_int_status_t rv = _cairo_d2d_try_fastblit(d2dsurf, surf_pattern->surface, + &box, &source->matrix, clip, op, + source->filter); if (rv != CAIRO_INT_STATUS_UNSUPPORTED) { return rv; } } op = _cairo_d2d_simplify_operator(op, source);
--- a/gfx/layers/basic/BasicLayers.cpp +++ b/gfx/layers/basic/BasicLayers.cpp @@ -510,17 +510,20 @@ BasicThebesLayerBuffer::DrawTo(ThebesLay aTarget->Save(); // If the entire buffer is valid, we can just draw the whole thing, // no need to clip. But we'll still clip if clipping is cheap --- // that might let us copy a smaller region of the buffer. if (!aLayer->GetValidRegion().Contains(BufferRect()) || IsClippingCheap(aTarget, aLayer->GetVisibleRegion())) { // We don't want to draw invalid stuff, so we need to clip. Might as // well clip to the smallest area possible --- the visible region. - gfxUtils::ClipToRegion(aTarget, aLayer->GetVisibleRegion()); + // Bug 599189 if there is a non-integer-translation transform in aTarget, + // we might sample pixels outside GetVisibleRegion(), which is wrong + // and may cause gray lines. + gfxUtils::ClipToRegionSnapped(aTarget, aLayer->GetVisibleRegion()); } if (aIsOpaqueContent) { aTarget->SetOperator(gfxContext::OPERATOR_SOURCE); } DrawBufferWithRotation(aTarget, aOpacity, aLayer->GetXResolution(), aLayer->GetYResolution()); aTarget->Restore(); } @@ -1795,17 +1798,17 @@ public: { mFrontBuffer.Clear(); mValidRegion.SetEmpty(); mOldValidRegion.SetEmpty(); mOldXResolution = 1.0; mOldYResolution = 1.0; if (IsSurfaceDescriptorValid(mFrontBufferDescriptor)) { - BasicManager()->ShadowLayerManager::DestroySharedSurface(&mFrontBufferDescriptor); + BasicManager()->ShadowLayerManager::DestroySharedSurface(&mFrontBufferDescriptor, mAllocator); } } virtual void Paint(gfxContext* aContext, LayerManager::DrawThebesLayerCallback aCallback, void* aCallbackData, float aOpacity); @@ -1911,17 +1914,17 @@ public: virtual PRBool Init(gfxSharedImageSurface* front, const nsIntSize& size); virtual already_AddRefed<gfxSharedImageSurface> Swap(gfxSharedImageSurface* newFront); virtual void DestroyFrontBuffer() { if (mFrontSurface) { - BasicManager()->ShadowLayerManager::DestroySharedSurface(mFrontSurface); + BasicManager()->ShadowLayerManager::DestroySharedSurface(mFrontSurface, mAllocator); } mFrontSurface = nsnull; } virtual void Paint(gfxContext* aContext, LayerManager::DrawThebesLayerCallback aCallback, void* aCallbackData, float aOpacity); @@ -1995,17 +1998,17 @@ public: {} virtual already_AddRefed<gfxSharedImageSurface> Swap(gfxSharedImageSurface* newFront); virtual void DestroyFrontBuffer() { if (mFrontSurface) { - BasicManager()->ShadowLayerManager::DestroySharedSurface(mFrontSurface); + BasicManager()->ShadowLayerManager::DestroySharedSurface(mFrontSurface, mAllocator); } mFrontSurface = nsnull; } virtual void Paint(gfxContext* aContext, LayerManager::DrawThebesLayerCallback aCallback, void* aCallbackData, float aOpacity);
--- a/gfx/layers/ipc/ShadowLayers.cpp +++ b/gfx/layers/ipc/ShadowLayers.cpp @@ -486,28 +486,30 @@ PLayerChild* ShadowLayerForwarder::ConstructShadowFor(ShadowableLayer* aLayer) { NS_ABORT_IF_FALSE(HasShadowManager(), "no manager to forward to"); return mShadowManager->SendPLayerConstructor(new ShadowLayerChild(aLayer)); } void -ShadowLayerManager::DestroySharedSurface(gfxSharedImageSurface* aSurface) +ShadowLayerManager::DestroySharedSurface(gfxSharedImageSurface* aSurface, + PLayersParent* aDeallocator) { - mForwarder->DeallocShmem(aSurface->GetShmem()); + aDeallocator->DeallocShmem(aSurface->GetShmem()); } void -ShadowLayerManager::DestroySharedSurface(SurfaceDescriptor* aSurface) +ShadowLayerManager::DestroySharedSurface(SurfaceDescriptor* aSurface, + PLayersParent* aDeallocator) { if (PlatformDestroySharedSurface(aSurface)) { return; } - DestroySharedShmemSurface(aSurface, mForwarder); + DestroySharedShmemSurface(aSurface, aDeallocator); } #if !defined(MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS) PRBool ShadowLayerForwarder::PlatformAllocDoubleBuffer(const gfxIntSize&, gfxASurface::gfxContentType,
--- a/gfx/layers/ipc/ShadowLayers.h +++ b/gfx/layers/ipc/ShadowLayers.h @@ -321,45 +321,37 @@ private: }; class ShadowLayerManager : public LayerManager { public: virtual ~ShadowLayerManager() {} - PRBool HasForwarder() { return !!mForwarder; } - - void SetForwarder(PLayersParent* aForwarder) - { - NS_ASSERTION(!aForwarder || !HasForwarder(), "stomping live forwarder?"); - mForwarder = aForwarder; - } - virtual void GetBackendName(nsAString& name) { name.AssignLiteral("Shadow"); } - void DestroySharedSurface(gfxSharedImageSurface* aSurface); + void DestroySharedSurface(gfxSharedImageSurface* aSurface, + PLayersParent* aDeallocator); - void DestroySharedSurface(SurfaceDescriptor* aSurface); + void DestroySharedSurface(SurfaceDescriptor* aSurface, + PLayersParent* aDeallocator); /** CONSTRUCTION PHASE ONLY */ virtual already_AddRefed<ShadowThebesLayer> CreateShadowThebesLayer() = 0; /** CONSTRUCTION PHASE ONLY */ virtual already_AddRefed<ShadowImageLayer> CreateShadowImageLayer() = 0; /** CONSTRUCTION PHASE ONLY */ virtual already_AddRefed<ShadowCanvasLayer> CreateShadowCanvasLayer() = 0; static void PlatformSyncBeforeReplyUpdate(); protected: - ShadowLayerManager() : mForwarder(NULL) {} + ShadowLayerManager() {} PRBool PlatformDestroySharedSurface(SurfaceDescriptor* aSurface); - - PLayersParent* mForwarder; }; /** * A ShadowableLayer is a Layer can be shared with a parent context * through a ShadowLayerForwarder. A ShadowableLayer maps to a * Shadow*Layer in a parent context. * @@ -388,16 +380,25 @@ protected: PLayerChild* mShadow; }; class ShadowThebesLayer : public ThebesLayer { public: + /** + * CONSTRUCTION PHASE ONLY + */ + void SetParent(PLayersParent* aParent) + { + NS_ABORT_IF_FALSE(!mAllocator, "Stomping parent?"); + mAllocator = aParent; + } + virtual void InvalidateRegion(const nsIntRegion& aRegion) { NS_RUNTIMEABORT("ShadowThebesLayers can't fill invalidated regions"); } /** * CONSTRUCTION PHASE ONLY */ @@ -434,26 +435,39 @@ public: * * Destroy the current front buffer. */ virtual void DestroyFrontBuffer() = 0; MOZ_LAYER_DECL_NAME("ShadowThebesLayer", TYPE_SHADOW) protected: - ShadowThebesLayer(LayerManager* aManager, void* aImplData) : - ThebesLayer(aManager, aImplData) {} + ShadowThebesLayer(LayerManager* aManager, void* aImplData) + : ThebesLayer(aManager, aImplData) + , mAllocator(nsnull) + {} + + PLayersParent* mAllocator; }; class ShadowCanvasLayer : public CanvasLayer { public: /** * CONSTRUCTION PHASE ONLY + */ + void SetParent(PLayersParent* aParent) + { + NS_ABORT_IF_FALSE(!mAllocator, "Stomping parent?"); + mAllocator = aParent; + } + + /** + * CONSTRUCTION PHASE ONLY * * Publish the remote layer's back surface to this shadow, swapping * out the old front surface (the new back surface for the remote * layer). */ virtual already_AddRefed<gfxSharedImageSurface> Swap(gfxSharedImageSurface* aNewFront) = 0; @@ -462,26 +476,39 @@ public: * * Destroy the current front buffer. */ virtual void DestroyFrontBuffer() = 0; MOZ_LAYER_DECL_NAME("ShadowCanvasLayer", TYPE_SHADOW) protected: - ShadowCanvasLayer(LayerManager* aManager, void* aImplData) : - CanvasLayer(aManager, aImplData) {} + ShadowCanvasLayer(LayerManager* aManager, void* aImplData) + : CanvasLayer(aManager, aImplData) + , mAllocator(nsnull) + {} + + PLayersParent* mAllocator; }; class ShadowImageLayer : public ImageLayer { public: /** * CONSTRUCTION PHASE ONLY + */ + void SetParent(PLayersParent* aParent) + { + NS_ABORT_IF_FALSE(!mAllocator, "Stomping parent?"); + mAllocator = aParent; + } + + /** + * CONSTRUCTION PHASE ONLY * * Initialize this with a (temporary) front surface with the given * size. This is expected to be followed with a Swap() in the same * transaction to bring in real pixels. Init() may only be called * once. */ virtual PRBool Init(gfxSharedImageSurface* aFront, const nsIntSize& aSize) = 0; @@ -497,17 +524,21 @@ public: * * Destroy the current front buffer. */ virtual void DestroyFrontBuffer() = 0; MOZ_LAYER_DECL_NAME("ShadowImageLayer", TYPE_SHADOW) protected: - ShadowImageLayer(LayerManager* aManager, void* aImplData) : - ImageLayer(aManager, aImplData) {} + ShadowImageLayer(LayerManager* aManager, void* aImplData) + : ImageLayer(aManager, aImplData) + , mAllocator(nsnull) + {} + + PLayersParent* mAllocator; }; } // namespace layers } // namespace mozilla #endif // ifndef mozilla_layers_ShadowLayers_h
--- a/gfx/layers/ipc/ShadowLayersParent.cpp +++ b/gfx/layers/ipc/ShadowLayersParent.cpp @@ -145,45 +145,51 @@ ShadowLayersParent::RecvUpdate(const nsT for (EditArray::index_type i = 0; i < cset.Length(); ++i) { const Edit& edit = cset[i]; switch (edit.type()) { // Create* ops case Edit::TOpCreateThebesLayer: { MOZ_LAYERS_LOG(("[ParentSide] CreateThebesLayer")); - nsRefPtr<ThebesLayer> layer = layer_manager()->CreateShadowThebesLayer(); + nsRefPtr<ShadowThebesLayer> layer = + layer_manager()->CreateShadowThebesLayer(); + layer->SetParent(this); AsShadowLayer(edit.get_OpCreateThebesLayer())->Bind(layer); break; } case Edit::TOpCreateContainerLayer: { MOZ_LAYERS_LOG(("[ParentSide] CreateContainerLayer")); nsRefPtr<ContainerLayer> layer = layer_manager()->CreateContainerLayer(); AsShadowLayer(edit.get_OpCreateContainerLayer())->Bind(layer); break; } case Edit::TOpCreateImageLayer: { MOZ_LAYERS_LOG(("[ParentSide] CreateImageLayer")); - AsShadowLayer(edit.get_OpCreateImageLayer())->Bind( - layer_manager()->CreateShadowImageLayer().get()); + nsRefPtr<ShadowImageLayer> layer = + layer_manager()->CreateShadowImageLayer(); + layer->SetParent(this); + AsShadowLayer(edit.get_OpCreateImageLayer())->Bind(layer); break; } case Edit::TOpCreateColorLayer: { MOZ_LAYERS_LOG(("[ParentSide] CreateColorLayer")); nsRefPtr<ColorLayer> layer = layer_manager()->CreateColorLayer(); AsShadowLayer(edit.get_OpCreateColorLayer())->Bind(layer); break; } case Edit::TOpCreateCanvasLayer: { MOZ_LAYERS_LOG(("[ParentSide] CreateCanvasLayer")); - nsRefPtr<CanvasLayer> layer = layer_manager()->CreateShadowCanvasLayer(); + nsRefPtr<ShadowCanvasLayer> layer = + layer_manager()->CreateShadowCanvasLayer(); + layer->SetParent(this); AsShadowLayer(edit.get_OpCreateCanvasLayer())->Bind(layer); break; } case Edit::TOpCreateThebesBuffer: { MOZ_LAYERS_LOG(("[ParentSide] CreateThebesBuffer")); const OpCreateThebesBuffer& otb = edit.get_OpCreateThebesBuffer(); ShadowThebesLayer* thebes = static_cast<ShadowThebesLayer*>(
--- a/gfx/tests/crashtests/467703-1.xhtml +++ b/gfx/tests/crashtests/467703-1.xhtml @@ -1,1 +1,1 @@ -<html xmlns="http://www.w3.org/1999/xhtml" style="margin: 78504em; background: url(http://www.google.com/images/logo_sm.gif); font-size: 305203ch; position: relative; left: 65em;"><head></head><body></body></html> +<html xmlns="http://www.w3.org/1999/xhtml" style="margin: 78504em; background: url(../../../testing/crashtest/images/tree.gif); font-size: 305203ch; position: relative; left: 65em;"><head></head><body></body></html>
--- a/gfx/thebes/gfxFT2Fonts.cpp +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -127,19 +127,21 @@ FontEntry::CreateFontEntry(const gfxProx FT_Error error = FT_New_Memory_Face(gfxToolkitPlatform::GetPlatform()->GetFTLibrary(), aFontData, aLength, 0, &face); if (error != FT_Err_Ok) { NS_Free((void*)aFontData); return nsnull; } FontEntry* fe = FontEntry::CreateFontEntryFromFace(face, aFontData); - fe->mItalic = aProxyEntry.mItalic; - fe->mWeight = aProxyEntry.mWeight; - fe->mStretch = aProxyEntry.mStretch; + if (fe) { + fe->mItalic = aProxyEntry.mItalic; + fe->mWeight = aProxyEntry.mWeight; + fe->mStretch = aProxyEntry.mStretch; + } return fe; } class FTUserFontData { public: FTUserFontData(FT_Face aFace, const PRUint8* aData) : mFace(aFace), mFontData(aData) {
--- a/ipc/glue/Shmem.cpp +++ b/ipc/glue/Shmem.cpp @@ -365,16 +365,18 @@ Shmem::RevokeRights(IHadBetterBeIPDLCode // static Shmem::SharedMemory* Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, size_t aNBytes, SharedMemoryType aType, bool aProtect) { + NS_ASSERTION(aNBytes <= PR_UINT32_MAX, "Will truncate shmem segment size!"); + size_t pageSize = SharedMemory::SystemPageSize(); SharedMemory* segment = nsnull; // |2*pageSize| is for the front and back sentinel size_t segmentSize = PageAlignedSize(aNBytes + 2*pageSize); if (aType == SharedMemory::TYPE_BASIC) segment = CreateSegment(segmentSize, SharedMemoryBasic::NULLHandle()); #ifdef MOZ_HAVE_SHAREDMEMORYSYSV @@ -390,17 +392,16 @@ Shmem::Alloc(IHadBetterBeIPDLCodeCalling char *frontSentinel; char *data; char *backSentinel; GetSections(segment, &frontSentinel, &data, &backSentinel); // initialize the segment with Shmem-internal information Header* header = reinterpret_cast<Header*>(frontSentinel); memcpy(header->mMagic, sMagic, sizeof(sMagic)); - NS_ASSERTION(aNBytes <= PR_UINT32_MAX, "Will truncate shmem segment size!"); header->mSize = static_cast<uint32>(aNBytes); if (aProtect) Protect(segment); return segment; } @@ -488,31 +489,31 @@ Shmem::SharedMemory* Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, size_t aNBytes, SharedMemoryType aType, bool /*unused*/) { SharedMemory *segment = nsnull; if (aType == SharedMemory::TYPE_BASIC) - segment = CreateSegment(PageAlignedSize(aNBytes + sizeof(size_t)), + segment = CreateSegment(PageAlignedSize(aNBytes + sizeof(uint32)), SharedMemoryBasic::NULLHandle()); #ifdef MOZ_HAVE_SHAREDMEMORYSYSV else if (aType == SharedMemory::TYPE_SYSV) - segment = CreateSegment(PageAlignedSize(aNBytes + sizeof(size_t)), + segment = CreateSegment(PageAlignedSize(aNBytes + sizeof(uint32)), SharedMemorySysV::NULLHandle()); #endif else // Unhandled!! NS_ABORT(); if (!segment) return 0; - *PtrToSize(segment) = aNBytes; + *PtrToSize(segment) = static_cast<uint32>(aNBytes); return segment; } // static Shmem::SharedMemory* Shmem::OpenExisting(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, const IPC::Message& aDescriptor, @@ -555,17 +556,17 @@ Shmem::OpenExisting(IHadBetterBeIPDLCode else { NS_RUNTIMEABORT("unknown shmem type"); } if (!segment) return 0; // this is the only validity check done OPT builds - if (size != *PtrToSize(segment)) + if (size != static_cast<size_t>(*PtrToSize(segment))) NS_RUNTIMEABORT("Alloc() segment size disagrees with OpenExisting()'s"); return segment; } // static void Shmem::Dealloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
--- a/ipc/glue/Shmem.h +++ b/ipc/glue/Shmem.h @@ -117,17 +117,17 @@ public: #if !defined(DEBUG) Shmem(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, SharedMemory* aSegment, id_t aId) : mSegment(aSegment), mData(aSegment->memory()), mSize(0), mId(aId) { - mSize = *PtrToSize(mSegment); + mSize = static_cast<size_t>(*PtrToSize(mSegment)); } #else Shmem(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, SharedMemory* aSegment, id_t aId); #endif ~Shmem() { @@ -269,22 +269,22 @@ private: if (0 != (mSize % sizeof(T))) NS_RUNTIMEABORT("shmem is not T-aligned"); } #if !defined(DEBUG) void AssertInvariants() const { } - static size_t* + static uint32* PtrToSize(SharedMemory* aSegment) { char* endOfSegment = reinterpret_cast<char*>(aSegment->memory()) + aSegment->Size(); - return reinterpret_cast<size_t*>(endOfSegment - sizeof(size_t)); + return reinterpret_cast<uint32*>(endOfSegment - sizeof(uint32)); } #else void AssertInvariants() const; #endif SharedMemory* mSegment; void* mData;
--- a/js/src/jsxml.cpp +++ b/js/src/jsxml.cpp @@ -6522,17 +6522,17 @@ xml_setNamespace(JSContext *cx, uintN ar ns = js_ConstructObject(cx, &js_NamespaceClass, NULL, obj, argc == 0 ? 0 : 1, Valueify(vp + 2)); if (!ns) return JS_FALSE; vp[0] = OBJECT_TO_JSVAL(ns); ns->setNamespaceDeclared(JSVAL_TRUE); - qnargv[0] = vp[2] = OBJECT_TO_JSVAL(ns); + qnargv[0] = OBJECT_TO_JSVAL(ns); qnargv[1] = OBJECT_TO_JSVAL(xml->name); qn = js_ConstructObject(cx, &js_QNameClass, NULL, NULL, 2, Valueify(qnargv)); if (!qn) return JS_FALSE; xml->name = qn; /*
new file mode 100644 --- /dev/null +++ b/layout/base/crashtests/537631-1.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<html> +<head></head> +<body style="position: fixed; -moz-column-count: 2;"><div style="position: absolute; height: 7em;"><br><br></div></body> +</html>
--- a/layout/base/crashtests/crashtests.list +++ b/layout/base/crashtests/crashtests.list @@ -287,16 +287,17 @@ load 534768-1.html load 534768-2.html load 535721-1.xhtml load 535911-1.xhtml load 536623-1.xhtml load 536720.xul load 537059-1.xhtml load 537141-1.xhtml load 537562-1.xhtml +load 537631-1.html load 538082-1.xul load 538207-1.xhtml load 538210-1.html load 540760.xul load 540771-1.xhtml load 541869-1.xhtml load 541869-2.html load 559705.xhtml
--- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -134,18 +134,18 @@ typedef struct CapturingContentInfo { nsIContent* mContent; CapturingContentInfo() : mAllowed(PR_FALSE), mRetargetToElement(PR_FALSE), mPreventDrag(PR_FALSE), mContent(nsnull) { } } CapturingContentInfo; #define NS_IPRESSHELL_IID \ - { 0x34f80395, 0xff82, 0x49fa, \ - { 0x9c, 0x83, 0xa6, 0xba, 0x49, 0xa8, 0x55, 0x4a } } + { 0xb79574cd, 0x2555, 0x4b57, \ + { 0xb3, 0xf8, 0x27, 0x57, 0x3e, 0x60, 0x74, 0x01 } } // Constants for ScrollContentIntoView() function #define NS_PRESSHELL_SCROLL_TOP 0 #define NS_PRESSHELL_SCROLL_BOTTOM 100 #define NS_PRESSHELL_SCROLL_LEFT 0 #define NS_PRESSHELL_SCROLL_RIGHT 100 #define NS_PRESSHELL_SCROLL_CENTER 50 #define NS_PRESSHELL_SCROLL_ANYWHERE -1 @@ -345,16 +345,22 @@ public: */ virtual NS_HIDDEN_(nsresult) InitialReflow(nscoord aWidth, nscoord aHeight) = 0; /** * Reflow the frame model into a new width and height. The * coordinates for aWidth and aHeight must be in standard nscoord's. */ virtual NS_HIDDEN_(nsresult) ResizeReflow(nscoord aWidth, nscoord aHeight) = 0; + /** + * Reflow, and also change presshell state so as to only permit + * reflowing off calls to ResizeReflowOverride() in the future. + * ResizeReflow() calls are ignored after ResizeReflowOverride(). + */ + virtual NS_HIDDEN_(nsresult) ResizeReflowOverride(nscoord aWidth, nscoord aHeight) = 0; /** * Reflow the frame model with a reflow reason of eReflowReason_StyleChange */ virtual NS_HIDDEN_(void) StyleChangeReflow() = 0; /** * This calls through to the frame manager to get the root frame.
--- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -736,16 +736,17 @@ public: NS_IMETHOD GetDisplaySelection(PRInt16 *aToggle); NS_IMETHOD ScrollSelectionIntoView(SelectionType aType, SelectionRegion aRegion, PRBool aIsSynchronous); NS_IMETHOD RepaintSelection(SelectionType aType); virtual NS_HIDDEN_(void) BeginObservingDocument(); virtual NS_HIDDEN_(void) EndObservingDocument(); virtual NS_HIDDEN_(nsresult) InitialReflow(nscoord aWidth, nscoord aHeight); virtual NS_HIDDEN_(nsresult) ResizeReflow(nscoord aWidth, nscoord aHeight); + virtual NS_HIDDEN_(nsresult) ResizeReflowOverride(nscoord aWidth, nscoord aHeight); virtual NS_HIDDEN_(void) StyleChangeReflow(); virtual NS_HIDDEN_(nsIPageSequenceFrame*) GetPageSequenceFrame() const; virtual NS_HIDDEN_(nsIFrame*) GetRealPrimaryFrameFor(nsIContent* aContent) const; virtual NS_HIDDEN_(nsIFrame*) GetPlaceholderFrameFor(nsIFrame* aFrame) const; virtual NS_HIDDEN_(void) FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty, nsFrameState aBitToAdd); virtual NS_HIDDEN_(void) FrameNeedsToContinueReflow(nsIFrame *aFrame); @@ -1002,16 +1003,19 @@ protected: // the last reflow was interrupted. In the interrupted case ScheduleReflow is // called off a timer, otherwise it is called directly. void MaybeScheduleReflow(); // Actually schedules a reflow. This should only be called by // MaybeScheduleReflow and the reflow timer ScheduleReflowOffTimer // sets up. void ScheduleReflow(); + // Reflow regardless of whether the override bit has been set. + nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight); + // DoReflow returns whether the reflow finished without interruption PRBool DoReflow(nsIFrame* aFrame, PRBool aInterruptible); #ifdef DEBUG void DoVerifyReflow(); void VerifyHasDirtyRootAncestor(nsIFrame* aFrame); #endif // Helper for ScrollContentIntoView @@ -1142,16 +1146,18 @@ protected: // reflow roots that need to be reflowed, as both a queue and a hashtable nsTArray<nsIFrame*> mDirtyRoots; PRPackedBool mDocumentLoading; PRPackedBool mIgnoreFrameDestruction; PRPackedBool mHaveShutDown; + PRPackedBool mViewportOverridden; + // This is used to protect ourselves from triggering reflow while in the // middle of frame construction and the like... it really shouldn't be // needed, one hopes, but it is for now. PRUint32 mChangeNestCount; nsIFrame* mCurrentEventFrame; nsCOMPtr<nsIContent> mCurrentEventContent; nsTArray<nsIFrame*> mCurrentEventFrameStack; @@ -1657,16 +1663,17 @@ PresShell::PresShell() mIsActive = PR_TRUE; mFrozen = PR_FALSE; #ifdef DEBUG mPresArenaAllocCount = 0; #endif mRenderFlags = 0; mXResolution = 1.0; mYResolution = 1.0; + mViewportOverridden = PR_FALSE; static bool registeredReporter = false; if (!registeredReporter) { NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(LayoutPresShell)); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(LayoutBidi)); registeredReporter = true; } @@ -2793,18 +2800,36 @@ PresShell::sPaintSuppressionCallback(nsI void PresShell::AsyncResizeEventCallback(nsITimer* aTimer, void* aPresShell) { static_cast<PresShell*>(aPresShell)->FireResizeEvent(); } nsresult +PresShell::ResizeReflowOverride(nscoord aWidth, nscoord aHeight) +{ + mViewportOverridden = PR_TRUE; + return ResizeReflowIgnoreOverride(aWidth, aHeight); +} + +nsresult PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight) { + if (mViewportOverridden) { + // The viewport has been overridden, and this reflow request + // didn't ask to ignore the override. Pretend it didn't happen. + return NS_OK; + } + return ResizeReflowIgnoreOverride(aWidth, aHeight); +} + +nsresult +PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight) +{ NS_PRECONDITION(!mIsReflowing, "Shouldn't be in reflow here!"); NS_PRECONDITION(aWidth != NS_UNCONSTRAINEDSIZE, "shouldn't use unconstrained widths anymore"); // If we don't have a root frame yet, that means we haven't had our initial // reflow... If that's the case, and aWidth or aHeight is unconstrained, // ignore them altogether. nsIFrame* rootFrame = FrameManager()->GetRootFrame();
new file mode 100644 --- /dev/null +++ b/layout/generic/crashtests/580504-1.xhtml @@ -0,0 +1,22 @@ +<html xmlns="http://www.w3.org/1999/xhtml" style="-moz-column-width: 1px"> +<head> + +<script> +<![CDATA[ + +function boom() +{ + document.getElementById("d").focus(); + document.execCommand("inserthtml", false, "<i><font><html><form>a</form></font>"); + document.execCommand("justifyright", false, "#ffddff"); +} + +window.addEventListener("load", boom, false); + +]]> +</script> + +</head> + +<div style="position: relative;"><div style="float: left; padding: 10px 20px 0pt;"><div contenteditable="true" style="position: absolute;" id="d"></div></div><div style="clear: both; padding: 20px 20px 15px;"></div></div> +</html>
--- a/layout/generic/crashtests/crashtests.list +++ b/layout/generic/crashtests/crashtests.list @@ -294,17 +294,17 @@ load 495875-2.html load 499862-1.html load 499857-1.html asserts(10-90) asserts-if(winWidget,18-108) load 499885-1.xhtml # nscoord_MAX assertions (bug 575011), plus bug 570436 on windows load 501535-1.html load 503961-1.xhtml load 503961-2.html load 508168-1.html load 508908-1.html -load 505912-1.html +skip-if(cocoaWidget) load 505912-1.html # Mac OS X intermittent orange bug 599391 load 509749-1.html load 511482.html load 512724-1.html load 512725-1.html load 513394-1.html load 514800-1.html load 515811-1.html load 517968.html @@ -323,15 +323,16 @@ load 542136-1.html load 547338.xul load 547843-1.xhtml load 551635-1.html load 564368-1.xhtml load 564968.xhtml load 570160.html load 571618-1.svg load 574958.xhtml +load 580504-1.xhtml load 585598-1.xhtml load 586806-1.html load 586806-2.html load 586806-3.html load 586973-1.html load 590404.html load 591141.html
--- a/layout/ipc/RenderFrameParent.cpp +++ b/layout/ipc/RenderFrameParent.cpp @@ -142,21 +142,21 @@ RenderFrameParent::~RenderFrameParent() void RenderFrameParent::ShadowLayersUpdated() { mFrameLoader->SetCurrentRemoteFrame(this); nsIFrame* docFrame = mFrameLoader->GetPrimaryFrameOfOwningContent(); if (!docFrame) { - // Bad, but nothing we can do about it (XXX/cjones: or is there?). - // When the new frame is created, we'll probably still be the - // current render frame and will get to draw our content then. - // Or, we're shutting down and this update goes to /dev/null. - NS_WARNING("RenderFrameParent just received a layer update, but our <browser> doesn't have an nsIFrame so we can't invalidate for the update"); + // Bad, but nothing we can do about it (XXX/cjones: or is there? + // maybe bug 589337?). When the new frame is created, we'll + // probably still be the current render frame and will get to draw + // our content then. Or, we're shutting down and this update goes + // to /dev/null. return; } // FIXME/cjones: we should collect the rects/regions updated for // Painted*Layer() calls and pass that region to here, then only // invalidate that rect // // We pass INVALIDATE_NO_THEBES_LAYERS here because we're @@ -249,20 +249,17 @@ RenderFrameParent::AllocPLayers() { LayerManager* lm = GetLayerManager(); if (LayerManager::LAYERS_BASIC != lm->GetBackendType()) { NS_WARNING("shadow layers no sprechen GL backend yet"); return nsnull; } BasicShadowLayerManager* bslm = static_cast<BasicShadowLayerManager*>(lm); - ShadowLayersParent* slp = new ShadowLayersParent(bslm); - bslm->SetForwarder(nsnull); // clear the previous forwarder - bslm->SetForwarder(slp); - return slp; + return new ShadowLayersParent(bslm); } bool RenderFrameParent::DeallocPLayers(PLayersParent* aLayers) { delete aLayers; return true; }
--- a/layout/reftests/forms/reftest.list +++ b/layout/reftests/forms/reftest.list @@ -14,17 +14,17 @@ HTTP(..) == text-control-baseline-1.html == textbox-setsize.xul textbox-setsize-ref.xul == textarea-resize.html textarea-resize-ref.html # an offset seems to apply to the native resizer on windows so skip this test for now skip-if(winWidget) == textarea-resize-background.html textarea-resize-background-ref.html != textarea-ltr.html textarea-rtl.html != textarea-ltr-scrollbar.html textarea-rtl-scrollbar.html != textarea-in-ltr-doc-scrollbar.html textarea-in-rtl-doc-scrollbar.html != textarea-ltr.html textarea-no-resize.html -random-if(gtk2Widget) != textarea-rtl.html textarea-no-resize.html # bug 558201 +!= textarea-rtl.html textarea-no-resize.html == textarea-setvalue-framereconstruction-1.html textarea-setvalue-framereconstruction-ref.html == radio-label-dynamic.html radio-label-dynamic-ref.html == out-of-bounds-selectedindex.html out-of-bounds-selectedindex-ref.html # test for bug 471741 != indeterminate-checked.html indeterminate-checked-notref.html != indeterminate-unchecked.html indeterminate-unchecked-notref.html != indeterminate-native-checked.html indeterminate-native-checked-notref.html != indeterminate-native-unchecked.html indeterminate-native-unchecked-notref.html
new file mode 100644 --- /dev/null +++ b/layout/reftests/table-anonymous-boxes/490174-1-ref.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head></head> +<body> + +<div style="position:absolute; top: 50px; left: 50px; border: 2px solid orange;" id="v"> + a + <select style="width: 200%"> + <option style="visibility: collapse; overflow: auto; display: table-footer-group;">X</option> + </select> +</div> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/layout/reftests/table-anonymous-boxes/490174-1.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head></head> +<body onload="document.getElementById('v').firstChild.data = ' a ';"> + +<div style="position:absolute; top: 50px; left: 50px; border: 2px solid orange;" id="v"> + <select style="width: 200%"> + <option style="visibility: collapse; overflow: auto; display: table-footer-group;">X</option> + </select> +</div> + +</body> +</html>
--- a/layout/reftests/table-anonymous-boxes/reftest.list +++ b/layout/reftests/table-anonymous-boxes/reftest.list @@ -29,16 +29,17 @@ fails == 156888-1.html 156888-1-ref.html == 373379-1.html 373379-1-ref.html random-if(d2d) == 394402-1a.html 394402-1-ref.html # bug 586833 == 394402-1b.html 394402-1-ref.html # bug 586833 == 398095-1.html 398095-1-ref.html == 407115-1.html 407115-1-ref.html == 443616-1a.xhtml 443616-1-ref.html == 443616-1b.html 443616-1-ref.html == 448111-1.html 448111-1-ref.html +== 490174-1.html 490174-1-ref.html == infer-first-row.html 3x3-ref.html == infer-first-row-and-table.html 3x3-ref.html == infer-second-row.html 3x3-ref.html == infer-second-row-and-table.html 3x3-ref.html == infer-table-around-headers-footers-1.html 3x3-ref.html == infer-table-around-headers-footers-2.html 3x3-ref.html == infer-table-around-headers-footers-3.html 3x3-ref.html == infer-rows-inside-rowgroups.html 3x3-ref.html
new file mode 100644 --- /dev/null +++ b/layout/xul/base/src/crashtests/557174-1.xml @@ -0,0 +1,1 @@ +<ther:window xmlns:ther="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" a="" e=""><HTML><ther:statusbar l="" c=""><ther:menulist d=""><ther:menu t="" i="" l=""><mat:h xmlns:mat="http://www.w3.org/1998/Math/MathML" w=""/></ther:menu><ther:menupopup p=""/><ther:menu a="" t="" l=""><ther:menuseparator u="" x=""><xht:html xmlns:xht="http://www.w3.org/1999/xhtml" x=""><xht:body d=""><xht:abbr d=""><xht:abbr p=""><xht:small s=""><xht:a s=""><xht:var e=""><xht:samp e=""><xht:code p=""><xht:b e=""><xht:b d=""><xht:del t=""><xht:h4 r=""><xht:var l=""><xht:i r=""><xht:em r=""><xht:em n=""><xht:map g=""><xht:isindex d=""/></xht:map></xht:em></xht:em></xht:i></xht:var></xht:h4></xht:del></xht:b></xht:b></xht:code></xht:samp></xht:var></xht:a></xht:small></xht:abbr></xht:abbr></xht:body></xht:html></ther:menuseparator></ther:menu></ther:menulist></ther:statusbar></HTML></ther:window> \ No newline at end of file
--- a/layout/xul/base/src/crashtests/crashtests.list +++ b/layout/xul/base/src/crashtests/crashtests.list @@ -67,9 +67,10 @@ load 472189.xul load 475133.html load 488210-1.xhtml load 495728-1.xul load 508927-1.xul load 508927-2.xul load 514300-1.xul load 536931-1.xhtml asserts(1) load 538308-1.xul +load 557174-1.xml load menulist-focused.xhtml
--- a/modules/libpr0n/src/SVGDocumentWrapper.cpp +++ b/modules/libpr0n/src/SVGDocumentWrapper.cpp @@ -261,25 +261,27 @@ SVGDocumentWrapper::OnStartRequest(nsIRe /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ NS_IMETHODIMP SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt, nsresult status) { if (mListener) { + mListener->OnStopRequest(aRequest, ctxt, status); // A few levels up the stack, imgRequest::OnStopRequest is about to tell // all of its observers that we know our size and are ready to paint. That // might not be true at this point, though -- so here, we synchronously // finish parsing & layout in our helper-document to make sure we can hold // up to this promise. nsCOMPtr<nsIParser> parser = do_QueryInterface(mListener); - parser->ContinueInterruptedParsing(); + if (!parser->IsComplete()) { + parser->ContinueInterruptedParsing(); + } FlushLayout(); - mListener->OnStopRequest(aRequest, ctxt, status); mListener = nsnull; // In a normal document, this would be called by nsDocShell - but we don't // have a nsDocShell. So we do it ourselves. (If we don't, painting will // stay suppressed for a little while longer, for no good reason). mViewer->LoadComplete(NS_OK); }
--- a/modules/plugin/test/mochitest/Makefile.in +++ b/modules/plugin/test/mochitest/Makefile.in @@ -80,17 +80,16 @@ include $(topsrcdir)/config/rules.mk test_npn_asynccall.html \ test_bug532208.html \ large-pic.jpg \ test_twostreams.html \ test_streamatclose.html \ neverending.sjs \ test_newstreamondestroy.html \ $(warning test_crashing2.html disabled due to random orange; see bug 566049) \ - test_hanging.html \ crashing_subpage.html \ test_GCrace.html \ test_propertyAndMethod.html \ test_bug539565-1.html \ test_bug539565-2.html \ test_enumerate.html \ $(NULL) @@ -98,37 +97,45 @@ include $(topsrcdir)/config/rules.mk ifeq ($(OS_ARCH),WINNT) _MOCHITEST_FILES += \ test_windowed_invalidate.html \ $(NULL) endif _MOCHICHROME_FILES = \ - test_bug479979.xul \ test_npruntime.xul \ test_privatemode.xul \ test_wmode.xul \ $(NULL) # Temporarily disable the tests on Linux, see bug 573290 and bug 583591. ifneq ($(OS_ARCH),Linux) +# Temporarily disable the tests on Mac OS X, see bug 599087. +ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT)) ifdef MOZ_CRASHREPORTER _MOCHICHROME_FILES += \ test_crash_notify.xul \ test_crash_notify_no_report.xul \ test_crash_submit.xul \ $(NULL) endif endif +endif -# Temporarily disable this test on Mac OS X. +# Temporarily disable these tests on Mac OS X, see bug 599076 and bug 599267 +# and bug 599378. ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT)) _MOCHITEST_FILES += \ test_crashing.html \ + test_hanging.html \ + $(NULL) + +_MOCHICHROME_FILES += \ + test_bug479979.xul \ $(NULL) endif ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) _MOCHICHROME_FILES += \ test_convertpoint.xul \ $(NULL)
--- a/modules/plugin/test/mochitest/test_bug479979.xul +++ b/modules/plugin/test/mochitest/test_bug479979.xul @@ -1,16 +1,16 @@ <?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin" type="text/css"?> <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> -<window title="NPAPI Private Mode Tests" +<window title="NPAPI Set Undefined Value Test" xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - <title>NPAPI Private Mode Tests</title> + <title>NPAPI Set Undefined Value Test</title> <script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script> <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> <body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> <embed id="plugin1" type="application/x-test" width="300" height="300"></embed> </body> <script class="testbody" type="application/javascript">
--- a/modules/plugin/test/reftest/reftest.list +++ b/modules/plugin/test/reftest/reftest.list @@ -1,12 +1,12 @@ # basic sanity checking random-if(!haveTestPlugin) != plugin-sanity.html about:blank fails-if(!haveTestPlugin) == plugin-sanity.html div-sanity.html fails-if(!haveTestPlugin) == plugin-alpha-zindex.html div-alpha-zindex.html fails-if(!haveTestPlugin) == plugin-alpha-opacity.html div-alpha-opacity.html fails-if(!haveTestPlugin) == windowless-clipping-1.html windowless-clipping-1-ref.html fails-if(!haveTestPlugin) == border-padding-1.html border-padding-1-ref.html fails-if(!haveTestPlugin) == border-padding-2.html border-padding-2-ref.html -asserts-if(http.oscpu.match(/Linux/),0-1) random-if(d2d) fails-if(!haveTestPlugin) skip-if(!prefs.getBoolPref("dom.ipc.plugins.enabled")) == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html # assertion is bug 585394 -asserts-if(http.oscpu.match(/Linux/),0-1) fails-if(!haveTestPlugin) skip-if(!prefs.getBoolPref("dom.ipc.plugins.enabled")) == pluginproblemui-direction-2.html pluginproblemui-direction-2-ref.html # assertion is bug 585394 +random-if(cocoaWidget||d2d) fails-if(!haveTestPlugin) skip-if(!prefs.getBoolPref("dom.ipc.plugins.enabled")) == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html +fails-if(!haveTestPlugin) skip-if(!prefs.getBoolPref("dom.ipc.plugins.enabled")) == pluginproblemui-direction-2.html pluginproblemui-direction-2-ref.html # Disabled for now to investigate Windows/Linux test failures # fails-if(!haveTestPlugin) == border-padding-3.html border-padding-3-ref.html
--- a/testing/xpcshell/runxpcshelltests.py +++ b/testing/xpcshell/runxpcshelltests.py @@ -315,16 +315,17 @@ class XPCShellTests(object): self.env["XPCOM_MEM_LEAK_LOG"] = leakLogFile return leakLogFile def launchProcess(self, cmd, stdout, stderr, env, cwd): """ Simple wrapper to launch a process. On a remote system, this is more complex and we need to overload this function. """ + cmd = wrapCommand(cmd) proc = Popen(cmd, stdout=stdout, stderr=stderr, env=env, cwd=cwd) return proc def communicate(self, proc): """ Simple wrapper to communicate with a process. On a remote system, this is overloaded to handle remote process communication.
--- a/toolkit/crashreporter/google-breakpad/Makefile.am +++ b/toolkit/crashreporter/google-breakpad/Makefile.am @@ -181,16 +181,17 @@ src_client_linux_linux_dumper_unittest_h src_client_linux_linux_dumper_unittest_helper_CC=$(PTHREAD_CC) src_client_linux_linux_client_unittest_SOURCES = \ src/client/linux/handler/exception_handler_unittest.cc \ src/client/linux/minidump_writer/directory_reader_unittest.cc \ src/client/linux/minidump_writer/line_reader_unittest.cc \ src/client/linux/minidump_writer/linux_dumper_unittest.cc \ src/client/linux/minidump_writer/minidump_writer_unittest.cc \ + src/common/memory_unittest.cc \ src/testing/gtest/src/gtest-all.cc \ src/testing/gtest/src/gtest_main.cc \ src/testing/src/gmock-all.cc src_client_linux_linux_client_unittest_CPPFLAGS = \ -I$(top_srcdir)/src/testing/include \ -I$(top_srcdir)/src/testing/gtest/include \ -I$(top_srcdir)/src/testing/gtest \
--- a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc @@ -81,17 +81,17 @@ #include <ucontext.h> #include <unistd.h> #include <algorithm> #include <vector> #include "common/linux/linux_libc_support.h" #include "common/linux/linux_syscall_support.h" -#include "common/linux/memory.h" +#include "common/memory.h" #include "client/linux/minidump_writer/minidump_writer.h" #include "common/linux/guid_creator.h" // A wrapper for the tgkill syscall: send a signal to a specific thread. static int tgkill(pid_t tgid, pid_t tid, int sig) { return syscall(__NR_tgkill, tgid, tid, sig); return 0; }
--- a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc @@ -27,25 +27,27 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <string> #include <stdint.h> #include <unistd.h> #include <signal.h> +#include <sys/mman.h> #include <sys/poll.h> #include <sys/socket.h> #include <sys/uio.h> #include "client/linux/handler/exception_handler.h" #include "client/linux/minidump_writer/minidump_writer.h" #include "common/linux/eintr_wrapper.h" #include "common/linux/linux_libc_support.h" #include "common/linux/linux_syscall_support.h" +#include "google_breakpad/processor/minidump.h" #include "breakpad_googletest_includes.h" using namespace google_breakpad; static void sigchld_handler(int signo) { } class ExceptionHandlerTest : public ::testing::Test { protected: @@ -121,16 +123,453 @@ TEST(ExceptionHandlerTest, ChildCrash) { const std::string minidump_filename = std::string("/tmp/") + filename + ".dmp"; struct stat st; ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); ASSERT_GT(st.st_size, 0u); unlink(minidump_filename.c_str()); + free(filename); +} + +// Test that memory around the instruction pointer is written +// to the dump as a MinidumpMemoryRegion. +TEST(ExceptionHandlerTest, InstructionPointerMemory) { + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const u_int32_t kMemorySize = 256; // bytes + const int kOffset = kMemorySize / 2; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1], + true); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGILL); + + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); + ASSERT_EQ(r, 1); + ASSERT_TRUE(pfd.revents & POLLIN); + + uint32_t len; + ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); + ASSERT_LT(len, (uint32_t)2048); + char* filename = reinterpret_cast<char*>(malloc(len + 1)); + ASSERT_EQ(read(fds[0], filename, len), len); + filename[len] = 0; + close(fds[0]); + + const std::string minidump_filename = std::string("/tmp/") + filename + + ".dmp"; + + struct stat st; + ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); + ASSERT_GT(st.st_size, 0u); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + u_int64_t instruction_pointer; + switch (context->GetContextCPU()) { + case MD_CONTEXT_X86: + instruction_pointer = context->GetContextX86()->eip; + break; + case MD_CONTEXT_AMD64: + instruction_pointer = context->GetContextAMD64()->rip; + break; + case MD_CONTEXT_ARM: + instruction_pointer = context->GetContextARM()->iregs[15]; + break; + default: + FAIL() << "Unknown context CPU: " << context->GetContextCPU(); + break; + } + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemorySize, region->GetSize()); + const u_int8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + u_int8_t prefix_bytes[kOffset]; + u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), + suffix_bytes, sizeof(suffix_bytes)) == 0); + + unlink(minidump_filename.c_str()); + free(filename); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the low end. +TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) { + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const u_int32_t kMemorySize = 256; // bytes + const int kOffset = 0; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1], + true); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGILL); + + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); + ASSERT_EQ(r, 1); + ASSERT_TRUE(pfd.revents & POLLIN); + + uint32_t len; + ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); + ASSERT_LT(len, (uint32_t)2048); + char* filename = reinterpret_cast<char*>(malloc(len + 1)); + ASSERT_EQ(read(fds[0], filename, len), len); + filename[len] = 0; + close(fds[0]); + + const std::string minidump_filename = std::string("/tmp/") + filename + + ".dmp"; + + struct stat st; + ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); + ASSERT_GT(st.st_size, 0u); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + u_int64_t instruction_pointer; + switch (context->GetContextCPU()) { + case MD_CONTEXT_X86: + instruction_pointer = context->GetContextX86()->eip; + break; + case MD_CONTEXT_AMD64: + instruction_pointer = context->GetContextAMD64()->rip; + break; + case MD_CONTEXT_ARM: + instruction_pointer = context->GetContextARM()->iregs[15]; + break; + default: + FAIL() << "Unknown context CPU: " << context->GetContextCPU(); + break; + } + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemorySize / 2, region->GetSize()); + const u_int8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)]; + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), + suffix_bytes, sizeof(suffix_bytes)) == 0); + + unlink(minidump_filename.c_str()); + free(filename); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the high end. +TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) { + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + // Use 4k here because the OS will hand out a single page even + // if a smaller size is requested, and this test wants to + // test the upper bound of the memory range. + const u_int32_t kMemorySize = 4096; // bytes + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + const int kOffset = kMemorySize - sizeof(instructions); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1], + true); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGILL); + + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); + ASSERT_EQ(r, 1); + ASSERT_TRUE(pfd.revents & POLLIN); + + uint32_t len; + ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); + ASSERT_LT(len, (uint32_t)2048); + char* filename = reinterpret_cast<char*>(malloc(len + 1)); + ASSERT_EQ(read(fds[0], filename, len), len); + filename[len] = 0; + close(fds[0]); + + const std::string minidump_filename = std::string("/tmp/") + filename + + ".dmp"; + + struct stat st; + ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); + ASSERT_GT(st.st_size, 0u); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + u_int64_t instruction_pointer; + switch (context->GetContextCPU()) { + case MD_CONTEXT_X86: + instruction_pointer = context->GetContextX86()->eip; + break; + case MD_CONTEXT_AMD64: + instruction_pointer = context->GetContextAMD64()->rip; + break; + case MD_CONTEXT_ARM: + instruction_pointer = context->GetContextARM()->iregs[15]; + break; + default: + FAIL() << "Unknown context CPU: " << context->GetContextCPU(); + break; + } + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + const size_t kPrefixSize = 128; // bytes + EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize()); + const u_int8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + u_int8_t prefix_bytes[kPrefixSize]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kPrefixSize, + instructions, sizeof(instructions)) == 0); + + unlink(minidump_filename.c_str()); + free(filename); +} + +// Ensure that an extra memory block doesn't get added when the +// instruction pointer is not in mapped memory. +TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) { + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1], + true); + // Try calling a NULL pointer. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(NULL); + memory_function(); + } + close(fds[1]); + + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGSEGV); + + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); + ASSERT_EQ(r, 1); + ASSERT_TRUE(pfd.revents & POLLIN); + + uint32_t len; + ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len)); + ASSERT_LT(len, (uint32_t)2048); + char* filename = reinterpret_cast<char*>(malloc(len + 1)); + ASSERT_EQ(read(fds[0], filename, len), len); + filename[len] = 0; + close(fds[0]); + + const std::string minidump_filename = std::string("/tmp/") + filename + + ".dmp"; + + struct stat st; + ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0); + ASSERT_GT(st.st_size, 0u); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_EQ((unsigned int)1, memory_list->region_count()); + + unlink(minidump_filename.c_str()); + free(filename); } static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); static bool CrashHandler(const void* crash_context, size_t crash_context_size, void* context) {
--- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h @@ -31,17 +31,17 @@ #define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ #include <elf.h> #include <linux/limits.h> #include <stdint.h> #include <sys/types.h> #include <sys/user.h> -#include "common/linux/memory.h" +#include "common/memory.h" #include "google_breakpad/common/minidump_format.h" namespace google_breakpad { typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t; // Typedef for our parsing of the auxv variables in /proc/pid/auxv. #if defined(__i386) || defined(__ARM_EABI__)
--- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest.cc @@ -30,17 +30,17 @@ #include <limits.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include "breakpad_googletest_includes.h" #include "client/linux/minidump_writer/linux_dumper.h" #include "common/linux/file_id.h" -#include "common/linux/memory.h" +#include "common/memory.h" using namespace google_breakpad; // This provides a wrapper around system calls which may be // interrupted by a signal and return EINTR. See man 7 signal. #define HANDLE_EINTR(x) ({ \ typeof(x) __eintr_result__; \ do { \
--- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc @@ -41,16 +41,18 @@ // around the system calls in linux_syscall_support.h. // * You may not malloc. There's an alternative allocator in memory.h and // a canonical instance in the LinuxDumper object. We use the placement // new form to allocate objects and we don't delete them. #include "client/linux/minidump_writer/minidump_writer.h" #include "client/minidump_file_writer-inl.h" +#include <algorithm> + #include <errno.h> #include <fcntl.h> #include <link.h> #include <stdio.h> #include <unistd.h> #include <sys/ucontext.h> #include <sys/user.h> #include <sys/utsname.h> @@ -412,30 +414,32 @@ class MinidumpWriter { #if !defined(__ARM_EABI__) float_state_(&context->float_state), #else //TODO: fix this after fixing ExceptionHandler float_state_(NULL), #endif crashing_tid_(context->tid), crashing_tid_pc_(0), - dumper_(crashing_pid) { + dumper_(crashing_pid), + memory_blocks_(dumper_.allocator()) { } // case (2) above MinidumpWriter(const char* filename, pid_t pid, pid_t blame_thread) : filename_(filename), siginfo_(NULL), // we fill this in if we find blame_thread ucontext_(NULL), float_state_(NULL), crashing_tid_(blame_thread), crashing_tid_pc_(0), // set if we find blame_thread - dumper_(pid) { + dumper_(pid), + memory_blocks_(dumper_.allocator()) { } bool Init() { return dumper_.Init() && minidump_writer_.Open(filename_) && dumper_.ThreadsAttach(); } ~MinidumpWriter() { @@ -462,17 +466,19 @@ class MinidumpWriter { r_debug = (struct r_debug*)dyn.d_un.d_ptr; continue; } else if (dyn.d_tag == DT_NULL) break; } // A minidump file contains a number of tagged streams. This is the number // of stream which we write. - const unsigned kNumWriters = 11 + !!r_debug; + unsigned kNumWriters = 12; + if (r_debug) + ++kNumWriters; TypedMDRVA<MDRawHeader> header(&minidump_writer_); TypedMDRVA<MDRawDirectory> dir(&minidump_writer_); if (!header.Allocate()) return false; if (!dir.AllocateArray(kNumWriters)) return false; memset(header.get(), 0, sizeof(MDRawHeader)); @@ -489,16 +495,20 @@ class MinidumpWriter { if (!WriteThreadListStream(&dirent)) return false; dir.CopyIndex(dir_index++, &dirent); if (!WriteMappings(&dirent)) return false; dir.CopyIndex(dir_index++, &dirent); + if (!WriteMemoryListStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + if (siginfo_ || crashing_tid_pc_) { if (!WriteExceptionStream(&dirent)) return false; dir.CopyIndex(dir_index++, &dirent); } if (!WriteSystemInfoStream(&dirent)) return false; @@ -700,16 +710,61 @@ class MinidumpWriter { UntypedMDRVA memory(&minidump_writer_); if (!memory.Allocate(stack_len)) return false; uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(stack_len); dumper_.CopyFromProcess(stack_copy, thread.thread_id, stack, stack_len); memory.Copy(stack_copy, stack_len); thread.stack.start_of_memory_range = (uintptr_t) (stack); thread.stack.memory = memory.location(); + memory_blocks_.push_back(thread.stack); + + // Copy 256 bytes around crashing instruction pointer to minidump. + const size_t kIPMemorySize = 256; + u_int64_t ip = GetInstructionPointer(); + // Bound it to the upper and lower bounds of the memory map + // it's contained within. If it's not in mapped memory, + // don't bother trying to write it. + bool ip_is_mapped = false; + MDMemoryDescriptor ip_memory_d; + for (unsigned i = 0; i < dumper_.mappings().size(); ++i) { + const MappingInfo& mapping = *dumper_.mappings()[i]; + if (ip >= mapping.start_addr && + ip < mapping.start_addr + mapping.size) { + ip_is_mapped = true; + // Try to get 128 bytes before and after the IP, but + // settle for whatever's available. + ip_memory_d.start_of_memory_range = + std::max(mapping.start_addr, + uintptr_t(ip - (kIPMemorySize / 2))); + uintptr_t end_of_range = + std::min(uintptr_t(ip + (kIPMemorySize / 2)), + uintptr_t(mapping.start_addr + mapping.size)); + ip_memory_d.memory.data_size = + end_of_range - ip_memory_d.start_of_memory_range; + break; + } + } + + if (ip_is_mapped) { + UntypedMDRVA ip_memory(&minidump_writer_); + if (!ip_memory.Allocate(ip_memory_d.memory.data_size)) + return false; + uint8_t* memory_copy = + (uint8_t*) dumper_.allocator()->Alloc(ip_memory_d.memory.data_size); + dumper_.CopyFromProcess( + memory_copy, + thread.thread_id, + reinterpret_cast<void*>(ip_memory_d.start_of_memory_range), + ip_memory_d.memory.data_size); + ip_memory.Copy(memory_copy, ip_memory_d.memory.data_size); + ip_memory_d.memory = ip_memory.location(); + memory_blocks_.push_back(ip_memory_d); + } + TypedMDRVA<RawContextCPU> cpu(&minidump_writer_); if (!cpu.Allocate()) return false; my_memset(cpu.get(), 0, sizeof(RawContextCPU)); CPUFillFromUContext(cpu.get(), ucontext_, float_state_); PopSeccompStackFrame(cpu.get(), thread, stack_copy); thread.thread_context = cpu.location(); crashing_thread_context_ = cpu.location(); @@ -723,16 +778,18 @@ class MinidumpWriter { return false; uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(info.stack_len); dumper_.CopyFromProcess(stack_copy, thread.thread_id, info.stack, info.stack_len); memory.Copy(stack_copy, info.stack_len); thread.stack.start_of_memory_range = (uintptr_t)(info.stack); thread.stack.memory = memory.location(); + memory_blocks_.push_back(thread.stack); + TypedMDRVA<RawContextCPU> cpu(&minidump_writer_); if (!cpu.Allocate()) return false; my_memset(cpu.get(), 0, sizeof(RawContextCPU)); CPUFillFromThreadInfo(cpu.get(), info); PopSeccompStackFrame(cpu.get(), thread, stack_copy); thread.thread_context = cpu.location(); @@ -831,16 +888,34 @@ class MinidumpWriter { mod.module_name_rva = ld.rva; list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); } return true; } + bool WriteMemoryListStream(MDRawDirectory* dirent) { + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (!list.AllocateObjectAndArray(memory_blocks_.size(), + sizeof(MDMemoryDescriptor))) + return false; + + dirent->stream_type = MD_MEMORY_LIST_STREAM; + dirent->location = list.location(); + + *list.get() = memory_blocks_.size(); + + for (size_t i = 0; i < memory_blocks_.size(); ++i) { + list.CopyIndexAfterObject(i, &memory_blocks_[i], + sizeof(MDMemoryDescriptor)); + } + return true; + } + bool WriteExceptionStream(MDRawDirectory* dirent) { TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_); if (!exc.Allocate()) return false; my_memset(exc.get(), 0, sizeof(MDRawExceptionStream)); dirent->stream_type = MD_EXCEPTION_STREAM; dirent->location = exc.location(); @@ -945,16 +1020,32 @@ class MinidumpWriter { dynamic_length); debug.CopyIndexAfterObject(0, dso_debug_data, dynamic_length); delete[] dso_debug_data; return true; } private: +#if defined(__i386) + uintptr_t GetInstructionPointer() { + return ucontext_->uc_mcontext.gregs[REG_EIP]; + } +#elif defined(__x86_64) + uintptr_t GetInstructionPointer() { + return ucontext_->uc_mcontext.gregs[REG_RIP]; + } +#elif defined(__ARM_EABI__) + uintptr_t GetInstructionPointer() { + return ucontext_->uc_mcontext.arm_ip; + } +#else +#error "This code has not been ported to your platform yet." +#endif + void NullifyDirectoryEntry(MDRawDirectory* dirent) { dirent->stream_type = 0; dirent->location.data_size = 0; dirent->location.rva = 0; } bool WriteCPUInformation(MDRawSystemInfo* sys_info) { char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0}; @@ -1195,16 +1286,20 @@ popline: uintptr_t crashing_tid_pc_; // set if we're dumping a live process // and find crashing_tid_. used to // write exception info. (if we're // dumping a crash, this stays 0 and we // use siginfo_) LinuxDumper dumper_; MinidumpFileWriter minidump_writer_; MDLocationDescriptor crashing_thread_context_; + // Blocks of memory written to the dump. These are all currently + // written while writing the thread list stream, but saved here + // so a memory list stream can be written afterwards. + wasteful_vector<MDMemoryDescriptor> memory_blocks_; }; bool WriteMinidump(const char* filename, pid_t crashing_process, const void* blob, size_t blob_size) { if (blob_size != sizeof(ExceptionHandler::CrashContext)) return false; const ExceptionHandler::CrashContext* context = reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
--- a/toolkit/crashreporter/google-breakpad/src/client/mac/Breakpad.xcodeproj/project.pbxproj +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/Breakpad.xcodeproj/project.pbxproj @@ -46,16 +46,21 @@ 8B31029411F0D54300FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; 8B3102E611F0D74C00FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; 8B3102EB11F0D78000FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; 8B31FC8211EFD2B800FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; 8B4BDAAF12012BC5009C7060 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B4BDAA7120124EA009C7060 /* libcrypto.dylib */; }; 8B4BDABE12012CEF009C7060 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B4BDAA7120124EA009C7060 /* libcrypto.dylib */; }; 8B4BDAC512012D05009C7060 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B4BDAA7120124EA009C7060 /* libcrypto.dylib */; }; 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; + D244536A12426F00009BBCE0 /* logging.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535112426EBB009BBCE0 /* logging.cc */; }; + D244536B12426F00009BBCE0 /* minidump.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535212426EBB009BBCE0 /* minidump.cc */; }; + D244536C12426F00009BBCE0 /* pathname_stripper.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535312426EBB009BBCE0 /* pathname_stripper.cc */; }; + D244536D12426F00009BBCE0 /* basic_code_modules.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244534F12426E98009BBCE0 /* basic_code_modules.cc */; }; + D244540B12439BA0009BBCE0 /* memory_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244540A12439BA0009BBCE0 /* memory_unittest.cc */; }; D24BBBFD121050F000F3D417 /* breakpadUtilities.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; }; D24BBD291211EDB100F3D417 /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; }; D24BBD321212CACF00F3D417 /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; }; D2A5DD301188633800081F03 /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */; }; D2A5DD401188640400081F03 /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */; }; D2A5DD411188642E00081F03 /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */; }; D2F9A3D51212F87C002747C1 /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A3D41212F87C002747C1 /* exception_handler_test.cc */; }; D2F9A43D12131F55002747C1 /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A43C12131F55002747C1 /* gmock-all.cc */; }; @@ -502,16 +507,21 @@ 8B31007011F0CD3C00FCF3E4 /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMDefines.h; path = ../../common/mac/GTMDefines.h; sourceTree = SOURCE_ROOT; }; 8B3101E911F0CDE300FCF3E4 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; 8B31022211F0CE1000FCF3E4 /* GTMGarbageCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMGarbageCollection.h; path = ../../common/mac/GTMGarbageCollection.h; sourceTree = SOURCE_ROOT; }; 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadDebug.xcconfig; path = ../../common/mac/BreakpadDebug.xcconfig; sourceTree = SOURCE_ROOT; }; 8B31027811F0D3AF00FCF3E4 /* BreakpadRelease.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadRelease.xcconfig; path = ../../common/mac/BreakpadRelease.xcconfig; sourceTree = SOURCE_ROOT; }; 8B31FFF611F0C90500FCF3E4 /* Breakpad.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Breakpad.xcconfig; path = ../../common/mac/Breakpad.xcconfig; sourceTree = SOURCE_ROOT; }; 8B4BDAA7120124EA009C7060 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; }; 8DC2EF5B0486A6940098B216 /* Breakpad.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Breakpad.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D244534F12426E98009BBCE0 /* basic_code_modules.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_code_modules.cc; path = ../../processor/basic_code_modules.cc; sourceTree = SOURCE_ROOT; }; + D244535112426EBB009BBCE0 /* logging.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = logging.cc; path = ../../processor/logging.cc; sourceTree = SOURCE_ROOT; }; + D244535212426EBB009BBCE0 /* minidump.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump.cc; path = ../../processor/minidump.cc; sourceTree = SOURCE_ROOT; }; + D244535312426EBB009BBCE0 /* pathname_stripper.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pathname_stripper.cc; path = ../../processor/pathname_stripper.cc; sourceTree = SOURCE_ROOT; }; + D244540A12439BA0009BBCE0 /* memory_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memory_unittest.cc; path = ../../common/memory_unittest.cc; sourceTree = SOURCE_ROOT; }; D2F9A3D41212F87C002747C1 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = tests/exception_handler_test.cc; sourceTree = "<group>"; }; D2F9A41512131EF0002747C1 /* libgtest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libgtest.a; sourceTree = BUILT_PRODUCTS_DIR; }; D2F9A43C12131F55002747C1 /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "../../testing/src/gmock-all.cc"; sourceTree = SOURCE_ROOT; }; D2F9A43E12131F65002747C1 /* gtest_main.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = gtest_main.cc; path = ../../testing/gtest/src/gtest_main.cc; sourceTree = "<group>"; }; D2F9A43F12131F65002747C1 /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "../../testing/gtest/src/gtest-all.cc"; sourceTree = "<group>"; }; D2F9A4C4121336C7002747C1 /* client_info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = client_info.h; path = crash_generation/client_info.h; sourceTree = "<group>"; }; D2F9A4C5121336C7002747C1 /* crash_generation_client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crash_generation_client.h; path = crash_generation/crash_generation_client.h; sourceTree = "<group>"; }; D2F9A4C6121336C7002747C1 /* crash_generation_client.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = crash_generation_client.cc; path = crash_generation/crash_generation_client.cc; sourceTree = "<group>"; }; @@ -754,16 +764,17 @@ D2F9A43812131F3B002747C1 /* gtest */, 8B31FFF611F0C90500FCF3E4 /* Breakpad.xcconfig */, 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */, 8B31027811F0D3AF00FCF3E4 /* BreakpadRelease.xcconfig */, F95BB8A3101F94C300AA053B /* Tools */, 32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */, F92C538D0ECCE6F2009BE4BA /* client */, F92C53600ECCE3D6009BE4BA /* common */, + D244536912426EE7009BBCE0 /* processor */, 0867D69AFE84028FC02AAC07 /* Frameworks */, 034768DFFF38A50411DB9C8B /* Products */, F9C77DDB0F7DD5CF0045F7DB /* UnitTests-Info.plist */, ); name = Breakpad; sourceTree = "<group>"; }; 0867D69AFE84028FC02AAC07 /* Frameworks */ = { @@ -775,16 +786,27 @@ F92C554A0ECCF530009BE4BA /* Carbon.framework */, 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */, 0867D6A5FE840307C02AAC07 /* AppKit.framework */, 0867D69BFE84028FC02AAC07 /* Foundation.framework */, ); name = Frameworks; sourceTree = "<group>"; }; + D244536912426EE7009BBCE0 /* processor */ = { + isa = PBXGroup; + children = ( + D244535112426EBB009BBCE0 /* logging.cc */, + D244535212426EBB009BBCE0 /* minidump.cc */, + D244535312426EBB009BBCE0 /* pathname_stripper.cc */, + D244534F12426E98009BBCE0 /* basic_code_modules.cc */, + ); + name = processor; + sourceTree = "<group>"; + }; D2F9A43812131F3B002747C1 /* gtest */ = { isa = PBXGroup; children = ( D2F9A43E12131F65002747C1 /* gtest_main.cc */, D2F9A43F12131F65002747C1 /* gtest-all.cc */, D2F9A43C12131F55002747C1 /* gmock-all.cc */, ); name = gtest; @@ -808,16 +830,17 @@ F92C53730ECCE3FD009BE4BA /* protected_memory_allocator.h */, ); name = handler; sourceTree = "<group>"; }; F92C53600ECCE3D6009BE4BA /* common */ = { isa = PBXGroup; children = ( + D244540A12439BA0009BBCE0 /* memory_unittest.cc */, F92C53870ECCE6C0009BE4BA /* convert_UTF.c */, F92C53880ECCE6C0009BE4BA /* convert_UTF.h */, F92C53850ECCE6AD009BE4BA /* string_conversion.cc */, F92C53860ECCE6AD009BE4BA /* string_conversion.h */, F92C53840ECCE68D009BE4BA /* mac */, ); name = common; sourceTree = "<group>"; @@ -1627,32 +1650,37 @@ F93DE2D80F82A70E00608B94 /* minidump_file_writer_unittest.cc in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F93DE3290F82C55600608B94 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D244536A12426F00009BBCE0 /* logging.cc in Sources */, + D244536B12426F00009BBCE0 /* minidump.cc in Sources */, + D244536C12426F00009BBCE0 /* pathname_stripper.cc in Sources */, + D244536D12426F00009BBCE0 /* basic_code_modules.cc in Sources */, D2F9A4E112133AE2002747C1 /* crash_generation_client.cc in Sources */, D2F9A4E212133AE2002747C1 /* crash_generation_server.cc in Sources */, D24BBD321212CACF00F3D417 /* MachIPC.mm in Sources */, D2A5DD411188642E00081F03 /* breakpad_nlist_64.cc in Sources */, F93DE3350F82C66B00608B94 /* dynamic_images.cc in Sources */, F93DE3360F82C66B00608B94 /* exception_handler.cc in Sources */, F93DE3370F82C66B00608B94 /* minidump_generator.cc in Sources */, F93DE3380F82C66B00608B94 /* minidump_file_writer.cc in Sources */, F93DE3390F82C66B00608B94 /* convert_UTF.c in Sources */, F93DE33A0F82C66B00608B94 /* string_conversion.cc in Sources */, F93DE33B0F82C66B00608B94 /* file_id.cc in Sources */, F93DE33C0F82C66B00608B94 /* macho_id.cc in Sources */, F93DE33D0F82C66B00608B94 /* macho_utilities.cc in Sources */, F93DE33E0F82C66B00608B94 /* macho_walker.cc in Sources */, F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */, D2F9A3D51212F87C002747C1 /* exception_handler_test.cc in Sources */, + D244540B12439BA0009BBCE0 /* memory_unittest.cc in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F9C44DA20EF060A8003AEBAA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F9C44DB20EF07288003AEBAA /* Controller.m in Sources */, @@ -2095,18 +2123,20 @@ }; name = Release; }; F93DE32E0F82C55700608B94 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { DEBUG_INFORMATION_FORMAT = dwarf; GCC_INLINES_ARE_PRIVATE_EXTERN = NO; + GCC_PREPROCESSOR_DEFINITIONS = "BP_LOGGING_INCLUDE=\\\"client/mac/tests/testlogging.h\\\""; GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( + ../../.., ../.., ../../testing, ../../testing/include, ../../testing/gtest, ../../testing/gtest/include, ); LIBRARY_SEARCH_PATHS = ( "$(inherited)",
--- a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc @@ -22,16 +22,17 @@ // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <algorithm> #include <cstdio> #include <mach/host_info.h> #include <mach/mach_vm.h> #include <mach/vm_statistics.h> #include <mach-o/dyld.h> #include <mach-o/loader.h> #include <sys/sysctl.h> @@ -59,32 +60,34 @@ namespace google_breakpad { MinidumpGenerator::MinidumpGenerator() : writer_(), exception_type_(0), exception_code_(0), exception_subcode_(0), exception_thread_(0), crashing_task_(mach_task_self()), handler_thread_(mach_thread_self()), - dynamic_images_(NULL) { + dynamic_images_(NULL), + memory_blocks_(&allocator_) { GatherSystemInformation(); } // constructor when generating from a different process than the // crashed process MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task, mach_port_t handler_thread) : writer_(), exception_type_(0), exception_code_(0), exception_subcode_(0), exception_thread_(0), crashing_task_(crashing_task), handler_thread_(handler_thread), - dynamic_images_(NULL) { + dynamic_images_(NULL), + memory_blocks_(&allocator_) { if (crashing_task != mach_task_self()) { dynamic_images_ = new DynamicImages(crashing_task_); } else { dynamic_images_ = NULL; } GatherSystemInformation(); } @@ -168,16 +171,17 @@ string MinidumpGenerator::UniqueNameInDi *unique_name = file_name; return path; } bool MinidumpGenerator::Write(const char *path) { WriteStreamFN writers[] = { &MinidumpGenerator::WriteThreadListStream, + &MinidumpGenerator::WriteMemoryListStream, &MinidumpGenerator::WriteSystemInfoStream, &MinidumpGenerator::WriteModuleListStream, &MinidumpGenerator::WriteMiscInfoStream, &MinidumpGenerator::WriteBreakpadInfoStream, // Exception stream needs to be the last entry in this array as it may // be omitted in the case where the minidump is written without an // exception. &MinidumpGenerator::WriteExceptionStream, @@ -509,16 +513,18 @@ bool MinidumpGenerator::WriteThreadStrea = static_cast<mach_msg_type_number_t>(sizeof(state)); if (thread_get_state(thread_id, BREAKPAD_MACHINE_THREAD_STATE, state, &state_count) == KERN_SUCCESS) { if (!WriteStack(state, &thread->stack)) return false; + memory_blocks_.push_back(thread->stack); + if (!WriteContext(state, &thread->thread_context)) return false; thread->thread_id = thread_id; } else { return false; } @@ -561,16 +567,128 @@ bool MinidumpGenerator::WriteThreadListS list.CopyIndexAfterObject(thread_idx++, &thread, sizeof(MDRawThread)); } } return true; } +bool MinidumpGenerator::WriteMemoryListStream( + MDRawDirectory *memory_list_stream) { + TypedMDRVA<MDRawMemoryList> list(&writer_); + + // If the dump has an exception, include some memory around the + // instruction pointer. + const size_t kIPMemorySize = 256; // bytes + bool have_ip_memory = false; + MDMemoryDescriptor ip_memory_d; + if (exception_thread_ && exception_type_) { + breakpad_thread_state_data_t state; + mach_msg_type_number_t stateCount + = static_cast<mach_msg_type_number_t>(sizeof(state)); + + if (thread_get_state(exception_thread_, + BREAKPAD_MACHINE_THREAD_STATE, + state, + &stateCount) == KERN_SUCCESS) { + u_int64_t ip = CurrentPCForStack(state); + // Bound it to the upper and lower bounds of the region + // it's contained within. If it's not in a known memory region, + // don't bother trying to write it. + mach_vm_address_t addr = ip; + mach_vm_size_t size; + natural_t nesting_level = 0; + vm_region_submap_info_64 info; + mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; + + kern_return_t ret = + mach_vm_region_recurse(crashing_task_, + &addr, + &size, + &nesting_level, + (vm_region_recurse_info_t)&info, + &info_count); + if (ret == KERN_SUCCESS && ip >= addr && ip < (addr + size)) { + // Try to get 128 bytes before and after the IP, but + // settle for whatever's available. + ip_memory_d.start_of_memory_range = + std::max(uintptr_t(addr), + uintptr_t(ip - (kIPMemorySize / 2))); + uintptr_t end_of_range = + std::min(uintptr_t(ip + (kIPMemorySize / 2)), + uintptr_t(addr + size)); + ip_memory_d.memory.data_size = + end_of_range - ip_memory_d.start_of_memory_range; + have_ip_memory = true; + // This needs to get appended to the list even though + // the memory bytes aren't filled in yet so the entire + // list can be written first. The memory bytes will get filled + // in after the memory list is written. + memory_blocks_.push_back(ip_memory_d); + } + } + } + + // Now fill in the memory list and write it. + unsigned memory_count = memory_blocks_.size(); + if (!list.AllocateObjectAndArray(memory_count, + sizeof(MDMemoryDescriptor))) + return false; + + memory_list_stream->stream_type = MD_MEMORY_LIST_STREAM; + memory_list_stream->location = list.location(); + + list.get()->number_of_memory_ranges = memory_count; + + unsigned int i; + for (i = 0; i < memory_count; ++i) { + list.CopyIndexAfterObject(i++, &memory_blocks_[i], + sizeof(MDMemoryDescriptor)); + } + + if (have_ip_memory) { + // Now read the memory around the instruction pointer. + UntypedMDRVA ip_memory(&writer_); + if (!ip_memory.Allocate(ip_memory_d.memory.data_size)) + return false; + + if (dynamic_images_) { + // Out-of-process. + kern_return_t kr; + + void *memory = + ReadTaskMemory( + crashing_task_, + reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range), + ip_memory_d.memory.data_size, + &kr); + + if (memory == NULL) { + return false; + } + + ip_memory.Copy(memory, ip_memory_d.memory.data_size); + free(memory); + } else { + // In-process, just copy from local memory. + ip_memory.Copy( + reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range), + ip_memory_d.memory.data_size); + } + + ip_memory_d.memory = ip_memory.location(); + // Write this again now that the data location is filled in. + list.CopyIndexAfterObject(i - 1, &ip_memory_d, + sizeof(MDMemoryDescriptor)); + } + + return true; +} + bool MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) { TypedMDRVA<MDRawExceptionStream> exception(&writer_); if (!exception.Allocate()) return false; exception_stream->stream_type = MD_EXCEPTION_STREAM;
--- a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.h +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.h @@ -32,18 +32,19 @@ #ifndef CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__ #define CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__ #include <mach/mach.h> #include <string> #include "client/minidump_file_writer.h" +#include "common/memory.h" +#include "common/mac/macho_utilities.h" #include "google_breakpad/common/minidump_format.h" -#include "common/mac/macho_utilities.h" #include "dynamic_images.h" namespace google_breakpad { using std::string; #if TARGET_CPU_X86_64 || TARGET_CPU_PPC64 @@ -114,16 +115,17 @@ class MinidumpGenerator { // the MinidumpGenerator class. static void GatherSystemInformation(); private: typedef bool (MinidumpGenerator::*WriteStreamFN)(MDRawDirectory *); // Stream writers bool WriteThreadListStream(MDRawDirectory *thread_list_stream); + bool WriteMemoryListStream(MDRawDirectory *memory_list_stream); bool WriteExceptionStream(MDRawDirectory *exception_stream); bool WriteSystemInfoStream(MDRawDirectory *system_info_stream); bool WriteModuleListStream(MDRawDirectory *module_list_stream); bool WriteMiscInfoStream(MDRawDirectory *misc_info_stream); bool WriteBreakpadInfoStream(MDRawDirectory *breakpad_info_stream); // Helpers u_int64_t CurrentPCForStack(breakpad_thread_state_data_t state); @@ -160,13 +162,22 @@ class MinidumpGenerator { // System information static char build_string_[16]; static int os_major_version_; static int os_minor_version_; static int os_build_number_; // Information about dynamically loaded code DynamicImages *dynamic_images_; + + // PageAllocator makes it possible to allocate memory + // directly from the system, even while handling an exception. + mutable PageAllocator allocator_; + + // Blocks of memory written to the dump. These are all currently + // written while writing the thread list stream, but saved here + // so a memory list stream can be written afterwards. + wasteful_vector<MDMemoryDescriptor> memory_blocks_; }; } // namespace google_breakpad #endif // CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__
--- a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc @@ -24,31 +24,45 @@ // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // exception_handler_test.cc: Unit tests for google_breakpad::ExceptionHandler +#include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include "breakpad_googletest_includes.h" #include "client/mac/handler/exception_handler.h" #include "client/mac/tests/auto_tempdir.h" #include "common/mac/MachIPC.h" +#include "google_breakpad/processor/minidump.h" + +namespace google_breakpad { +// This acts as the log sink for INFO logging from the processor +// logging code. The logging output confuses XCode and makes it think +// there are unit test failures. testlogging.h handles the overriding. +std::ostringstream info_log; +} namespace { using std::string; using google_breakpad::AutoTempDir; using google_breakpad::ExceptionHandler; using google_breakpad::MachPortSender; using google_breakpad::MachReceiveMessage; using google_breakpad::MachSendMessage; +using google_breakpad::Minidump; +using google_breakpad::MinidumpContext; +using google_breakpad::MinidumpException; +using google_breakpad::MinidumpMemoryList; +using google_breakpad::MinidumpMemoryRegion; using google_breakpad::ReceivePort; using testing::Test; class ExceptionHandlerTest : public Test { public: AutoTempDir tempDir; string lastDumpName; }; @@ -75,17 +89,16 @@ static bool MDCallback(const char *dump_ (void)write(fd, path.c_str(), path.length() + 1); close(fd); exit(0); // not reached return true; } TEST_F(ExceptionHandlerTest, InProcess) { - AutoTempDir tempDir; // Give the child process a pipe to report back on. int fds[2]; ASSERT_EQ(0, pipe(fds)); // Fork off a child process so it can crash. pid_t pid = fork(); if (pid == 0) { // In the child process. close(fds[0]); @@ -162,18 +175,18 @@ TEST_F(ExceptionHandlerTest, DumpChildPr close(fds[1]); // Read the child's task and thread ports. MachReceiveMessage child_message; ASSERT_EQ(KERN_SUCCESS, parent_recv_port.WaitForMessage(&child_message, kTimeoutMs)); mach_port_t child_task = child_message.GetTranslatedPort(0); mach_port_t child_thread = child_message.GetTranslatedPort(1); - ASSERT_NE(MACH_PORT_NULL, child_task); - ASSERT_NE(MACH_PORT_NULL, child_thread); + ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task); + ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_thread); // Write a minidump of the child process. bool result = ExceptionHandler::WriteMinidumpForChild(child_task, child_thread, tempDir.path, ChildMDCallback, this); ASSERT_EQ(true, result); @@ -190,9 +203,395 @@ TEST_F(ExceptionHandlerTest, DumpChildPr // Child process should have exited with a zero status. int ret; ASSERT_EQ(pid, waitpid(pid, &ret, 0)); EXPECT_NE(0, WIFEXITED(ret)); EXPECT_EQ(0, WEXITSTATUS(ret)); } +// Test that memory around the instruction pointer is written +// to the dump as a MinidumpMemoryRegion. +TEST_F(ExceptionHandlerTest, InstructionPointerMemory) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const u_int32_t kMemorySize = 256; // bytes + const int kOffset = kMemorySize / 2; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_NE((unsigned int)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + u_int64_t instruction_pointer; + switch (context->GetContextCPU()) { + case MD_CONTEXT_X86: + instruction_pointer = context->GetContextX86()->eip; + break; + case MD_CONTEXT_AMD64: + instruction_pointer = context->GetContextAMD64()->rip; + break; + case MD_CONTEXT_ARM: + instruction_pointer = context->GetContextARM()->iregs[15]; + break; + default: + FAIL() << "Unknown context CPU: " << context->GetContextCPU(); + break; + } + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + EXPECT_TRUE(region); + + EXPECT_EQ(kMemorySize, region->GetSize()); + const u_int8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + u_int8_t prefix_bytes[kOffset]; + u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), + suffix_bytes, sizeof(suffix_bytes)) == 0); } + +// Test that the memory region around the instruction pointer is +// bounded correctly on the low end. +TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMinBound) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const u_int32_t kMemorySize = 256; // bytes + const int kOffset = 0; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them at the start + // of the block of memory, to ensure that the memory bounding + // works properly. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_NE((unsigned int)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + u_int64_t instruction_pointer; + switch (context->GetContextCPU()) { + case MD_CONTEXT_X86: + instruction_pointer = context->GetContextX86()->eip; + break; + case MD_CONTEXT_AMD64: + instruction_pointer = context->GetContextAMD64()->rip; + break; + case MD_CONTEXT_ARM: + instruction_pointer = context->GetContextARM()->iregs[15]; + break; + default: + FAIL() << "Unknown context CPU: " << context->GetContextCPU(); + break; + } + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + EXPECT_TRUE(region); + + EXPECT_EQ(kMemorySize / 2, region->GetSize()); + const u_int8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)]; + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), + suffix_bytes, sizeof(suffix_bytes)) == 0); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the high end. +TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + // Use 4k here because the OS will hand out a single page even + // if a smaller size is requested, and this test wants to + // test the upper bound of the memory range. + const u_int32_t kMemorySize = 4096; // bytes + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + const int kOffset = kMemorySize - sizeof(instructions); + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them at the start + // of the block of memory, to ensure that the memory bounding + // works properly. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_NE((unsigned int)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + u_int64_t instruction_pointer; + switch (context->GetContextCPU()) { + case MD_CONTEXT_X86: + instruction_pointer = context->GetContextX86()->eip; + break; + case MD_CONTEXT_AMD64: + instruction_pointer = context->GetContextAMD64()->rip; + break; + case MD_CONTEXT_ARM: + instruction_pointer = context->GetContextARM()->iregs[15]; + break; + default: + FAIL() << "Unknown context CPU: " << context->GetContextCPU(); + break; + } + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + EXPECT_TRUE(region); + + const size_t kPrefixSize = 128; // bytes + EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize()); + const u_int8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + u_int8_t prefix_bytes[kPrefixSize]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kPrefixSize, + instructions, sizeof(instructions)) == 0); +} + +// Ensure that an extra memory block doesn't get added when the +// instruction pointer is not in mapped memory. +TEST_F(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL); + // Try calling a NULL pointer. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(NULL); + memory_function(); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is only one memory region + // in the memory list (the thread memory from the single thread). + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_EQ((unsigned int)1, memory_list->region_count()); +} + +}
new file mode 100644 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/testlogging.h @@ -0,0 +1,9 @@ +// This file exists to override the processor logging for unit tests, +// since it confuses XCode into thinking unit tests have failed. +#include <sstream> + +namespace google_breakpad { +extern std::ostringstream info_log; +} + +#define BPLOG_INFO_STREAM google_breakpad::info_log
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/memory_unittest.cc +++ b/toolkit/crashreporter/google-breakpad/src/common/linux/memory_unittest.cc @@ -22,17 +22,17 @@ // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "common/linux/memory.h" +#include "common/memory.h" #include "testing/gtest/include/gtest/gtest.h" using namespace google_breakpad; namespace { typedef testing::Test PageAllocatorTest; } @@ -69,16 +69,16 @@ typedef testing::Test WastefulVectorTest TEST(WastefulVectorTest, Setup) { PageAllocator allocator_; wasteful_vector<int> v(&allocator_); ASSERT_EQ(v.size(), 0u); } TEST(WastefulVectorTest, Simple) { PageAllocator allocator_; - wasteful_vector<int> v(&allocator_); + wasteful_vector<unsigned> v(&allocator_); for (unsigned i = 0; i < 256; ++i) v.push_back(i); ASSERT_EQ(v.size(), 256u); for (unsigned i = 0; i < 256; ++i) ASSERT_EQ(v[i], i); }
rename from toolkit/crashreporter/google-breakpad/src/common/linux/memory.h rename to toolkit/crashreporter/google-breakpad/src/common/memory.h --- a/toolkit/crashreporter/google-breakpad/src/common/linux/memory.h +++ b/toolkit/crashreporter/google-breakpad/src/common/memory.h @@ -22,25 +22,32 @@ // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef CLIENT_LINUX_HANDLER_MEMORY_H_ -#define CLIENT_LINUX_HANDLER_MEMORY_H_ +#ifndef GOOGLE_BREAKPAD_COMMON_MEMORY_H_ +#define GOOGLE_BREAKPAD_COMMON_MEMORY_H_ #include <stdint.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> +#ifdef __APPLE__ +#define sys_mmap mmap +#define sys_mmap2 mmap +#define sys_munmap munmap +#define MAP_ANONYMOUS MAP_ANON +#else #include "common/linux/linux_syscall_support.h" +#endif namespace google_breakpad { // This is very simple allocator which fetches pages from the kernel directly. // Thus, it can be used even when the heap may be corrupted. // // There is no free operation. The pages are only freed when the object is // destroyed. @@ -191,9 +198,9 @@ class wasteful_vector { } // namespace google_breakpad inline void* operator new(size_t nbytes, google_breakpad::PageAllocator& allocator) { return allocator.Alloc(nbytes); } -#endif // CLIENT_LINUX_HANDLER_MEMORY_H_ +#endif // GOOGLE_BREAKPAD_COMMON_MEMORY_H_
--- a/toolkit/mozapps/update/content/updates.js +++ b/toolkit/mozapps/update/content/updates.js @@ -1723,26 +1723,32 @@ var gFinishedPage = { // This prevents the user from switching back // to the Software Update dialog and clicking "Restart" or "Later" // when dealing with the "confirm close" prompts. // See bug #350299 for more details. gUpdates.wiz.getButton("finish").disabled = true; gUpdates.wiz.getButton("extra1").disabled = true; // Notify all windows that an application quit has been requested. - var os = CoC["@mozilla.org/observer-service;1"]. - getService(CoI.nsIObserverService); var cancelQuit = CoC["@mozilla.org/supports-PRBool;1"]. createInstance(CoI.nsISupportsPRBool); - os.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", + "restart"); // Something aborted the quit process. if (cancelQuit.data) return; + // If already in safe mode restart in safe mode (bug 327119) + if (Services.appinfo.inSafeMode) { + let env = CoC["@mozilla.org/process/environment;1"]. + getService(CoI.nsIEnvironment); + env.set("MOZ_SAFE_MODE_RESTART", "1"); + } + // Restart the application CoC["@mozilla.org/toolkit/app-startup;1"].getService(CoI.nsIAppStartup). quit(CoI.nsIAppStartup.eAttemptQuit | CoI.nsIAppStartup.eRestart); }, /** * When the user clicks the "Restart Later" instead of the Restart Now" button * in the wizard after an update has been downloaded.
--- a/toolkit/mozapps/update/nsUpdateService.js +++ b/toolkit/mozapps/update/nsUpdateService.js @@ -46,16 +46,17 @@ Components.utils.import("resource://gre/ Components.utils.import("resource://gre/modules/FileUtils.jsm"); Components.utils.import("resource://gre/modules/AddonManager.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; +const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype"; const PREF_APP_UPDATE_AUTO = "app.update.auto"; const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval"; const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors"; const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors"; const PREF_APP_UPDATE_CERTS_BRANCH = "app.update.certs."; const PREF_APP_UPDATE_CERT_CHECKATTRS = "app.update.cert.checkAttributes"; const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors"; const PREF_APP_UPDATE_CERT_MAXERRORS = "app.update.cert.maxErrors"; @@ -2774,42 +2775,48 @@ Downloader.prototype = { */ function UpdatePrompt() { } UpdatePrompt.prototype = { /** * See nsIUpdateService.idl */ checkForUpdates: function UP_checkForUpdates() { + if (this._getAltUpdateWindow()) + return; + this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME, null, null); }, /** * See nsIUpdateService.idl */ showUpdateAvailable: function UP_showUpdateAvailable(update) { if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) || - this._getUpdateWindow()) + this._getUpdateWindow() || this._getAltUpdateWindow()) return; var stringsPrefix = "updateAvailable_" + update.type + "."; var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", [update.name], 1); var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text"); var imageUrl = ""; this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME, "updatesavailable", update, title, text, imageUrl); }, /** * See nsIUpdateService.idl */ showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) { + if (this._getAltUpdateWindow()) + return; + if (background) { if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false)) return; var stringsPrefix = "updateDownloaded_" + update.type + "."; var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", [update.name], 1); var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text"); @@ -2847,17 +2854,18 @@ UpdatePrompt.prototype = { Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg); } }, /** * See nsIUpdateService.idl */ showUpdateError: function UP_showUpdateError(update) { - if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false)) + if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) || + this._getAltUpdateWindow()) return; // In some cases, we want to just show a simple alert dialog: if (update.state == STATE_FAILED && update.errorCode == WRITE_ERROR) { var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle"); var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg", [Services.appinfo.name, Services.appinfo.name], 2); @@ -2888,16 +2896,28 @@ UpdatePrompt.prototype = { /** * Returns the update window if present. */ _getUpdateWindow: function UP__getUpdateWindow() { return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME); }, /** + * Returns an alternative update window if present. When a window with this + * windowtype is open the application update service won't open the normal + * application update user interface window. + */ + _getAltUpdateWindow: function UP__getAltUpdateWindow() { + let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null); + if (!windowType) + return null; + return Services.wm.getMostRecentWindow(windowType); + }, + + /** * Initiate a less obtrusive UI, starting with a non-modal notification alert * @param parent * A parent window, can be null * @param uri * The URI string of the dialog to show * @param name * The Window Name of the dialog to show, in case it is already open * and can merely be focused
--- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -44,16 +44,17 @@ #include "nsXULAppAPI.h" #include "nsAppRunner.h" #include "nsILocalFile.h" #include "nsCOMPtr.h" #include "nsString.h" #include "nsPrintfCString.h" #include "prproces.h" #include "prlog.h" +#include "prenv.h" #include "nsVersionComparator.h" #ifdef XP_MACOSX #include "nsILocalFileMac.h" #include "nsCommandLineServiceMac.h" #endif #if defined(XP_WIN) @@ -468,16 +469,20 @@ ApplyUpdate(nsIFile *greDir, nsIFile *up for (int i = 1; i < appArgc; ++i) argv[4 + i] = appArgv[i]; argv[4 + appArgc] = nsnull; } else { argv[3] = nsnull; argc = 3; } + if (gSafeMode) { + PR_SetEnv("MOZ_SAFE_MODE_RESTART=1"); + } + LOG(("spawning updater process [%s]\n", updaterPath.get())); #if defined(USE_EXECV) chdir(applyToDir.get()); execv(updaterPath.get(), argv); #elif defined(XP_WIN) _wchdir(applyToDir.get());
--- a/widget/public/nsGUIEventIPC.h +++ b/widget/public/nsGUIEventIPC.h @@ -274,12 +274,30 @@ struct ParamTraits<nsSelectionEvent> ReadParam(aMsg, aIter, &aResult->mOffset) && ReadParam(aMsg, aIter, &aResult->mLength) && ReadParam(aMsg, aIter, &aResult->mReversed) && ReadParam(aMsg, aIter, &aResult->mExpandToClusterBoundary) && ReadParam(aMsg, aIter, &aResult->mSucceeded); } }; +template<> +struct ParamTraits<nsIMEUpdatePreference> +{ + typedef nsIMEUpdatePreference paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mWantUpdates); + WriteParam(aMsg, aParam.mWantHints); + } + + static bool Read(const Message* aMsg, void** aIter, paramType* aResult) + { + return ReadParam(aMsg, aIter, &aResult->mWantUpdates) && + ReadParam(aMsg, aIter, &aResult->mWantHints); + } +}; + } // namespace IPC #endif // nsGUIEventIPC_h__
--- a/widget/public/nsIWidget.h +++ b/widget/public/nsIWidget.h @@ -68,16 +68,21 @@ class imgIContainer; class gfxASurface; class nsIContent; class ViewWrapper; namespace mozilla { namespace layers { class LayerManager; } +#ifdef MOZ_IPC +namespace dom { +class PBrowserChild; +} +#endif } /** * Callback function that processes events. * * The argument is actually a subtype (subclass) of nsEvent which carries * platform specific information about the event. Platform specific code * knows how to deal with it. @@ -107,33 +112,40 @@ typedef nsEventStatus (* EVENT_CALLBACK) #define NS_NATIVE_PLUGIN_PORT_CG 101 #endif #ifdef XP_WIN #define NS_NATIVE_TSF_THREAD_MGR 100 #define NS_NATIVE_TSF_CATEGORY_MGR 101 #define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102 #endif -// 36762512-d533-4884-9ac3-4ada8594146c +// 8bd36c8c-8218-4859-bfbc-ca5d78b52f7d #define NS_IWIDGET_IID \ - { 0x36762512, 0xd533, 0x4884, \ - { 0x9a, 0xc3, 0x4a, 0xda, 0x85, 0x94, 0x14, 0x6c } } + { 0x8bd36c8c, 0x8218, 0x4859, \ + { 0xbf, 0xbc, 0xca, 0x5d, 0x78, 0xb5, 0x2f, 0x7d } } /* * Window shadow styles * Also used for the -moz-window-shadow CSS property */ #define NS_STYLE_WINDOW_SHADOW_NONE 0 #define NS_STYLE_WINDOW_SHADOW_DEFAULT 1 #define NS_STYLE_WINDOW_SHADOW_MENU 2 #define NS_STYLE_WINDOW_SHADOW_TOOLTIP 3 #define NS_STYLE_WINDOW_SHADOW_SHEET 4 /** + * nsIWidget::OnIMEFocusChange should be called during blur, + * but other OnIME*Change methods should not be called + */ +#define NS_SUCCESS_IME_NO_UPDATES \ + NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_WIDGET, 1) + +/** * Cursor types. */ enum nsCursor { ///(normal cursor, usually rendered as an arrow) eCursor_standard, ///(system is busy, usually rendered as a hourglass or watch) eCursor_wait, ///(Selecting something, usually rendered as an IBeam) @@ -180,20 +192,56 @@ enum nsCursor { ///(normal cursor, enum nsTopLevelWidgetZPlacement { // for PlaceBehind() eZPlacementBottom = 0, // bottom of the window stack eZPlacementBelow, // just below another widget eZPlacementTop // top of the window stack }; /** + * Preference for receiving IME updates + * + * If mWantUpdates is true, PuppetWidget will forward + * nsIWidget::OnIMETextChange and nsIWidget::OnIMESelectionChange to the chrome + * process. This incurs overhead from observers and IPDL. If the IME + * implementation on a particular platform doesn't care about OnIMETextChange + * and OnIMESelectionChange from content processes, they should set + * mWantUpdates to false to avoid these overheads. + * + * If mWantHints is true, PuppetWidget will forward the content of text fields + * to the chrome process to be cached. This way we return the cached content + * during query events. (see comments in bug 583976). This only makes sense + * for IME implementations that do use query events, otherwise there's a + * significant overhead. Platforms that don't use query events should set + * mWantHints to false. + */ +struct nsIMEUpdatePreference { + + nsIMEUpdatePreference() + : mWantUpdates(PR_FALSE), mWantHints(PR_FALSE) + { + } + nsIMEUpdatePreference(PRBool aWantUpdates, PRBool aWantHints) + : mWantUpdates(aWantUpdates), mWantHints(aWantHints) + { + } + PRPackedBool mWantUpdates; + PRPackedBool mWantHints; +}; + + +/** * The base class for all the widgets. It provides the interface for * all basic and necessary functionality. */ class nsIWidget : public nsISupports { +#ifdef MOZ_IPC + protected: + typedef mozilla::dom::PBrowserChild PBrowserChild; +#endif public: typedef mozilla::layers::LayerManager LayerManager; NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWIDGET_IID) nsIWidget() : mLastChild(nsnull) @@ -1188,16 +1236,19 @@ class nsIWidget : public nsISupports { /* * An editable node (i.e. input/textarea/design mode document) * is receiving or giving up focus * aFocus is true if node is receiving focus * aFocus is false if node is giving up focus (blur) * * If this returns NS_ERROR_*, OnIMETextChange and OnIMESelectionChange * and OnIMEFocusChange(PR_FALSE) will be never called. + * + * If this returns NS_SUCCESS_IME_NO_UPDATES, OnIMEFocusChange(PR_FALSE) + * will be called but OnIMETextChange and OnIMESelectionChange will NOT. */ NS_IMETHOD OnIMEFocusChange(PRBool aFocus) = 0; /* * Text content of the focused node has changed * aStart is the starting offset of the change * aOldEnd is the ending offset of the change * aNewEnd is the caret offset after the change @@ -1207,16 +1258,21 @@ class nsIWidget : public nsISupports { PRUint32 aNewEnd) = 0; /* * Selection has changed in the focused node */ NS_IMETHOD OnIMESelectionChange(void) = 0; /* + * Retrieves preference for IME updates + */ + virtual nsIMEUpdatePreference GetIMEUpdatePreference() = 0; + + /* * Call this method when a dialog is opened which has a default button. * The button's rectangle should be supplied in aButtonRect. */ NS_IMETHOD OnDefaultButtonLoaded(const nsIntRect &aButtonRect) = 0; /** * Compute the overridden system mouse scroll speed on the root content of * web pages. The widget may set the same value as aOriginalDelta. E.g., @@ -1257,17 +1313,17 @@ class nsIWidget : public nsISupports { * be fed to it. Currently used in content processes. NULL is * returned if puppet widgets aren't supported in this build * config, on this platform, or for this process type. * * This function is called "Create" to match CreateInstance(). * The returned widget must still be nsIWidget::Create()d. */ static already_AddRefed<nsIWidget> - CreatePuppetWidget(); + CreatePuppetWidget(PBrowserChild *aTabChild); #endif /** * Reparent this widget's native widget. * @param aNewParent the native widget of aNewParent is the new native * parent widget */ NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent) = 0;
--- a/widget/src/android/AndroidBridge.cpp +++ b/widget/src/android/AndroidBridge.cpp @@ -33,17 +33,16 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include <android/log.h> #ifdef MOZ_IPC -#include "mozilla/dom/ContentChild.h" #include "nsXULAppAPI.h" #endif #include <pthread.h> #include <prthread.h> #include "nsXPCOMStrings.h" #include "AndroidBridge.h" #include "nsAppShell.h" @@ -194,34 +193,23 @@ AndroidBridge::EnsureJNIThread() } void AndroidBridge::NotifyIME(int aType, int aState) { if (sBridge) JNI()->CallStaticVoidMethod(sBridge->mGeckoAppShellClass, sBridge->jNotifyIME, aType, aState); -#ifdef MOZ_IPC - // It's possible that we are in chrome process - // but sBridge is not initialized yet - else if (XRE_GetProcessType() == GeckoProcessType_Content) - mozilla::dom::ContentChild::GetSingleton()->SendNotifyIME(aType, aState); -#endif } void AndroidBridge::NotifyIMEChange(const PRUnichar *aText, PRUint32 aTextLen, int aStart, int aEnd, int aNewEnd) { if (!sBridge) { -#ifdef MOZ_IPC - mozilla::dom::ContentChild::GetSingleton()-> - SendNotifyIMEChange(nsAutoString(aText), aTextLen, - aStart, aEnd, aNewEnd); -#endif return; } jvalue args[4]; AutoLocalJNIFrame jniFrame(1); args[0].l = JNI()->NewString(aText, aTextLen); args[1].i = aStart; args[2].i = aEnd;
--- a/widget/src/android/Makefile.in +++ b/widget/src/android/Makefile.in @@ -85,18 +85,16 @@ EXTRA_DSO_LDOPTS = \ $(QCMS_LIBS) \ $(NULL) EXTRA_DSO_LDOPTS += -L$(DIST)/lib EXPORTS = AndroidBridge.h AndroidJavaWrappers.h -include $(topsrcdir)/config/config.mk -include $(topsrcdir)/ipc/chromium/chromium-config.mk include $(topsrcdir)/config/rules.mk DEFINES += -D_IMPL_NS_WIDGET #DEFINES += -DDEBUG_WIDGETS LOCAL_INCLUDES += \ -I$(topsrcdir)/widget/src/xpwidgets \ -I$(topsrcdir)/dom/system/android \
--- a/widget/src/android/nsWindow.cpp +++ b/widget/src/android/nsWindow.cpp @@ -556,29 +556,27 @@ nsWindow::DispatchEvent(nsGUIEvent *aEve } nsEventStatus nsWindow::DispatchEvent(nsGUIEvent *aEvent) { if (mEventCallback) { nsEventStatus status = (*mEventCallback)(aEvent); - // Don't track composition if event was dispatched to remote child - if (status != nsEventStatus_eConsumeNoDefault) - switch (aEvent->message) { - case NS_COMPOSITION_START: - mIMEComposing = PR_TRUE; - break; - case NS_COMPOSITION_END: - mIMEComposing = PR_FALSE; - break; - case NS_TEXT_TEXT: - mIMEComposingText = static_cast<nsTextEvent*>(aEvent)->theText; - break; - } + switch (aEvent->message) { + case NS_COMPOSITION_START: + mIMEComposing = PR_TRUE; + break; + case NS_COMPOSITION_END: + mIMEComposing = PR_FALSE; + break; + case NS_TEXT_TEXT: + mIMEComposingText = static_cast<nsTextEvent*>(aEvent)->theText; + break; + } return status; } return nsEventStatus_eIgnore; } NS_IMETHODIMP nsWindow::SetWindowClass(const nsAString& xulWinType) { @@ -1584,16 +1582,17 @@ nsWindow::ResetInputState() //ALOGIME("IME: ResetInputState: s=%d", aState); // Cancel composition on Gecko side if (mIMEComposing) { nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, this); InitEvent(textEvent, nsnull); textEvent.theText = mIMEComposingText; DispatchEvent(&textEvent); + mIMEComposingText.Truncate(0); nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_END, this); InitEvent(event, nsnull); DispatchEvent(&event); } AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_RESETINPUTSTATE, 0); return NS_OK; @@ -1621,16 +1620,17 @@ nsWindow::CancelIMEComposition() { ALOGIME("IME: CancelIMEComposition"); // Cancel composition on Gecko side if (mIMEComposing) { nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, this); InitEvent(textEvent, nsnull); DispatchEvent(&textEvent); + mIMEComposingText.Truncate(0); nsCompositionEvent compEvent(PR_TRUE, NS_COMPOSITION_END, this); InitEvent(compEvent, nsnull); DispatchEvent(&compEvent); } AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_CANCELCOMPOSITION, 0); return NS_OK; @@ -1687,8 +1687,14 @@ nsWindow::OnIMESelectionChange(void) return NS_OK; AndroidBridge::NotifyIMEChange(nsnull, 0, int(event.mReply.mOffset), int(event.mReply.mOffset + event.mReply.mString.Length()), -1); return NS_OK; } +nsIMEUpdatePreference +nsWindow::GetIMEUpdatePreference() +{ + return nsIMEUpdatePreference(PR_TRUE, PR_TRUE); +} +
--- a/widget/src/android/nsWindow.h +++ b/widget/src/android/nsWindow.h @@ -154,16 +154,17 @@ public: NS_IMETHOD ResetInputState(); NS_IMETHOD SetIMEEnabled(PRUint32 aState); NS_IMETHOD GetIMEEnabled(PRUint32* aState); NS_IMETHOD CancelIMEComposition(); NS_IMETHOD OnIMEFocusChange(PRBool aFocus); NS_IMETHOD OnIMETextChange(PRUint32 aStart, PRUint32 aOldEnd, PRUint32 aNewEnd); NS_IMETHOD OnIMESelectionChange(void); + virtual nsIMEUpdatePreference GetIMEUpdatePreference(); LayerManager* GetLayerManager(); gfxASurface* GetThebesSurface(); NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent); protected: void BringToFront(); nsWindow *FindTopLevel();
--- a/widget/src/windows/nsWindow.cpp +++ b/widget/src/windows/nsWindow.cpp @@ -6869,16 +6869,30 @@ nsWindow::ConfigureChildren(const nsTArr nsIntRect bounds; w->GetBounds(bounds); if (bounds.Size() != configuration.mBounds.Size()) { w->Resize(configuration.mBounds.x, configuration.mBounds.y, configuration.mBounds.width, configuration.mBounds.height, PR_TRUE); } else if (bounds.TopLeft() != configuration.mBounds.TopLeft()) { w->Move(configuration.mBounds.x, configuration.mBounds.y); + + + if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() == + gfxWindowsPlatform::RENDER_DIRECT2D || + GetLayerManager()->GetBackendType() != LayerManager::LAYERS_BASIC) { + // XXX - Workaround for Bug 587508. This will invalidate the part of the + // plugin window that might be touched by moving content somehow. The + // underlying problem should be found and fixed! + nsIntRegion r; + r.Sub(bounds, configuration.mBounds); + r.MoveBy(-bounds.x, + -bounds.y); + w->Invalidate(r.GetBounds(), PR_FALSE); + } } rv = w->SetWindowClipRegion(configuration.mClipRegion, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } static HRGN
--- a/widget/src/xpwidgets/PuppetWidget.cpp +++ b/widget/src/xpwidgets/PuppetWidget.cpp @@ -33,53 +33,56 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ +#include "mozilla/dom/PBrowserChild.h" #include "BasicLayers.h" #include "gfxPlatform.h" #include "PuppetWidget.h" using namespace mozilla::layers; using namespace mozilla::widget; +using namespace mozilla::dom; static void InvalidateRegion(nsIWidget* aWidget, const nsIntRegion& aRegion) { nsIntRegionRectIterator it(aRegion); while(const nsIntRect* r = it.Next()) { aWidget->Invalidate(*r, PR_FALSE/*async*/); } } /*static*/ already_AddRefed<nsIWidget> -nsIWidget::CreatePuppetWidget() +nsIWidget::CreatePuppetWidget(PBrowserChild *aTabChild) { NS_ABORT_IF_FALSE(nsIWidget::UsePuppetWidgets(), "PuppetWidgets not allowed in this configuration"); - nsCOMPtr<nsIWidget> widget = new PuppetWidget(); + nsCOMPtr<nsIWidget> widget = new PuppetWidget(aTabChild); return widget.forget(); } namespace mozilla { namespace widget { // Arbitrary, fungible. const size_t PuppetWidget::kMaxDimension = 4000; NS_IMPL_ISUPPORTS_INHERITED1(PuppetWidget, nsBaseWidget, nsISupportsWeakReference) -PuppetWidget::PuppetWidget() +PuppetWidget::PuppetWidget(PBrowserChild *aTabChild) + : mTabChild(aTabChild) { MOZ_COUNT_CTOR(PuppetWidget); } PuppetWidget::~PuppetWidget() { MOZ_COUNT_DTOR(PuppetWidget); } @@ -102,16 +105,19 @@ PuppetWidget::Create(nsIWidget *a mBounds = aRect; mEnabled = PR_TRUE; mVisible = PR_TRUE; mSurface = gfxPlatform::GetPlatform() ->CreateOffscreenSurface(gfxIntSize(1, 1), gfxASurface::ContentFromFormat(gfxASurface::ImageFormatARGB32)); + mIMEComposing = PR_FALSE; + mIMESuppressNotifySel = PR_FALSE; + PuppetWidget* parent = static_cast<PuppetWidget*>(aParent); if (parent) { parent->SetChild(this); mLayerManager = parent->GetLayerManager(); } else { Resize(mBounds.x, mBounds.y, mBounds.width, mBounds.height, PR_FALSE); } @@ -125,32 +131,33 @@ PuppetWidget::CreateChild(const nsIntRec nsIDeviceContext *aContext, nsIAppShell *aAppShell, nsIToolkit *aToolkit, nsWidgetInitData *aInitData, PRBool aForceUseIWidgetParent) { bool isPopup = aInitData && aInitData->mWindowType == eWindowType_popup; - nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(); + nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(mTabChild); return ((widget && NS_SUCCEEDED(widget->Create(isPopup ? nsnull: this, nsnull, aRect, aHandleEventFunction, aContext, aAppShell, aToolkit, aInitData))) ? widget.forget() : nsnull); } NS_IMETHODIMP PuppetWidget::Destroy() { Base::Destroy(); mPaintTask.Revoke(); mChild = nsnull; mLayerManager = nsnull; + mTabChild = nsnull; return NS_OK; } NS_IMETHODIMP PuppetWidget::Show(PRBool aState) { NS_ASSERTION(mEnabled, "does it make sense to Show()/Hide() a disabled widget?"); @@ -238,30 +245,55 @@ PuppetWidget::Update() } if (mDirtyRegion.IsEmpty()) { return NS_OK; } return DispatchPaintEvent(); } +void +PuppetWidget::InitEvent(nsGUIEvent& event, nsIntPoint* aPoint) +{ + if (nsnull == aPoint) { + event.refPoint.x = 0; + event.refPoint.y = 0; + } + else { + // use the point override if provided + event.refPoint.x = aPoint->x; + event.refPoint.y = aPoint->y; + } + event.time = PR_Now() / 1000; +} + NS_IMETHODIMP PuppetWidget::DispatchEvent(nsGUIEvent* event, nsEventStatus& aStatus) { #ifdef DEBUG debug_DumpEvent(stdout, event->widget, event, nsCAutoString("PuppetWidget"), nsnull); #endif aStatus = nsEventStatus_eIgnore; if (mEventCallback) { + if (event->message == NS_COMPOSITION_START) { + mIMEComposing = PR_TRUE; + } else if (event->message == NS_SELECTION_SET) { + mIMESuppressNotifySel = PR_TRUE; + } aStatus = (*mEventCallback)(event); - } - if (mChild) { + if (event->message == NS_COMPOSITION_END) { + mIMEComposing = PR_FALSE; + } else if (event->message == NS_SELECTION_SET) { + mIMESuppressNotifySel = PR_FALSE; + } + } else if (mChild) { + event->widget = mChild; mChild->DispatchEvent(event, aStatus); } return NS_OK; } LayerManager* PuppetWidget::GetLayerManager() @@ -274,16 +306,171 @@ PuppetWidget::GetLayerManager() gfxASurface* PuppetWidget::GetThebesSurface() { return mSurface; } nsresult +PuppetWidget::IMEEndComposition(PRBool aCancel) +{ + if (!mIMEComposing) + return NS_OK; + + nsEventStatus status; + nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, this); + InitEvent(textEvent, nsnull); + if (!mTabChild || + !mTabChild->SendEndIMEComposition(aCancel, &textEvent.theText)) { + return NS_ERROR_FAILURE; + } + DispatchEvent(&textEvent, status); + + nsCompositionEvent compEvent(PR_TRUE, NS_COMPOSITION_END, this); + InitEvent(compEvent, nsnull); + DispatchEvent(&compEvent, status); + return NS_OK; +} + +NS_IMETHODIMP +PuppetWidget::ResetInputState() +{ + return IMEEndComposition(PR_FALSE); +} + +NS_IMETHODIMP +PuppetWidget::CancelComposition() +{ + return IMEEndComposition(PR_TRUE); +} + +NS_IMETHODIMP +PuppetWidget::SetIMEOpenState(PRBool aState) +{ + if (mTabChild && + mTabChild->SendSetIMEOpenState(aState)) + return NS_OK; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +PuppetWidget::SetIMEEnabled(PRUint32 aState) +{ + if (mTabChild && + mTabChild->SendSetIMEEnabled(aState)) + return NS_OK; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +PuppetWidget::GetIMEOpenState(PRBool *aState) +{ + if (mTabChild && + mTabChild->SendGetIMEOpenState(aState)) + return NS_OK; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +PuppetWidget::GetIMEEnabled(PRUint32 *aState) +{ + if (mTabChild && + mTabChild->SendGetIMEEnabled(aState)) + return NS_OK; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +PuppetWidget::OnIMEFocusChange(PRBool aFocus) +{ + if (!mTabChild) + return NS_ERROR_FAILURE; + + if (aFocus) { + nsEventStatus status; + nsQueryContentEvent queryEvent(PR_TRUE, NS_QUERY_TEXT_CONTENT, this); + InitEvent(queryEvent, nsnull); + // Query entire content + queryEvent.InitForQueryTextContent(0, PR_UINT32_MAX); + DispatchEvent(&queryEvent, status); + + if (queryEvent.mSucceeded) { + mTabChild->SendNotifyIMETextHint(queryEvent.mReply.mString); + } + } else { + // ResetInputState might not have been called yet + ResetInputState(); + } + + mIMEPreference.mWantUpdates = PR_FALSE; + mIMEPreference.mWantHints = PR_FALSE; + if (!mTabChild->SendNotifyIMEFocus(aFocus, &mIMEPreference)) + return NS_ERROR_FAILURE; + + if (aFocus) { + if (!mIMEPreference.mWantUpdates && !mIMEPreference.mWantHints) + // call OnIMEFocusChange on blur but no other updates + return NS_SUCCESS_IME_NO_UPDATES; + OnIMESelectionChange(); // Update selection + } + return NS_OK; +} + +NS_IMETHODIMP +PuppetWidget::OnIMETextChange(PRUint32 aStart, PRUint32 aEnd, PRUint32 aNewEnd) +{ + if (!mTabChild) + return NS_ERROR_FAILURE; + + if (mIMEPreference.mWantHints) { + nsEventStatus status; + nsQueryContentEvent queryEvent(PR_TRUE, NS_QUERY_TEXT_CONTENT, this); + InitEvent(queryEvent, nsnull); + queryEvent.InitForQueryTextContent(0, PR_UINT32_MAX); + DispatchEvent(&queryEvent, status); + + if (queryEvent.mSucceeded) { + mTabChild->SendNotifyIMETextHint(queryEvent.mReply.mString); + } + } + if (mIMEPreference.mWantUpdates) { + mTabChild->SendNotifyIMETextChange(aStart, aEnd, aNewEnd); + } + return NS_OK; +} + +NS_IMETHODIMP +PuppetWidget::OnIMESelectionChange(void) +{ + if (!mTabChild) + return NS_ERROR_FAILURE; + + // When we send selection notifications during a composition or during a + // set selection event, there is a race condition where the notification + // arrives at chrome too late, which leads to chrome thinking the + // selection was elsewhere. Suppress notifications here to avoid that. + if (mIMEComposing || mIMESuppressNotifySel) + return NS_OK; + + if (mIMEPreference.mWantUpdates) { + nsEventStatus status; + nsQueryContentEvent queryEvent(PR_TRUE, NS_QUERY_SELECTED_TEXT, this); + InitEvent(queryEvent, nsnull); + DispatchEvent(&queryEvent, status); + + if (queryEvent.mSucceeded) { + mTabChild->SendNotifyIMESelection(queryEvent.GetSelectionStart(), + queryEvent.GetSelectionEnd()); + } + } + return NS_OK; +} + +nsresult PuppetWidget::DispatchPaintEvent() { NS_ABORT_IF_FALSE(!mDirtyRegion.IsEmpty(), "paint event logic messed up"); nsIntRect dirtyRect = mDirtyRegion.GetBounds(); nsPaintEvent event(PR_TRUE, NS_PAINT, this); event.refPoint.x = dirtyRect.x; event.refPoint.x = dirtyRect.y;
--- a/widget/src/xpwidgets/PuppetWidget.h +++ b/widget/src/xpwidgets/PuppetWidget.h @@ -60,17 +60,17 @@ namespace widget { class PuppetWidget : public nsBaseWidget, public nsSupportsWeakReference { typedef nsBaseWidget Base; // The width and height of the "widget" are clamped to this. static const size_t kMaxDimension; public: - PuppetWidget(); + PuppetWidget(PBrowserChild *aTabChild); virtual ~PuppetWidget(); NS_DECL_ISUPPORTS_INHERITED NS_IMETHOD Create(nsIWidget* aParent, nsNativeWidget aNativeParent, const nsIntRect& aRect, EVENT_CALLBACK aHandleEventFunction, @@ -146,54 +146,80 @@ public: // PuppetWidgets don't have any concept of titles. NS_IMETHOD SetTitle(const nsAString& aTitle) { return NS_ERROR_UNEXPECTED; } // PuppetWidgets are always at <0, 0>. virtual nsIntPoint WidgetToScreenOffset() { return nsIntPoint(0, 0); } + void InitEvent(nsGUIEvent& event, nsIntPoint* aPoint = nsnull); + NS_IMETHOD DispatchEvent(nsGUIEvent* event, nsEventStatus& aStatus); NS_IMETHOD CaptureRollupEvents(nsIRollupListener* aListener, nsIMenuRollup* aMenuRollup, PRBool aDoCapture, PRBool aConsumeRollupEvent) { return NS_ERROR_UNEXPECTED; } // // nsBaseWidget methods we override // //NS_IMETHOD CaptureMouse(PRBool aCapture); virtual LayerManager* GetLayerManager(); // virtual nsIDeviceContext* GetDeviceContext(); virtual gfxASurface* GetThebesSurface(); + NS_IMETHOD ResetInputState(); + NS_IMETHOD SetIMEOpenState(PRBool aState); + NS_IMETHOD GetIMEOpenState(PRBool *aState); + NS_IMETHOD SetIMEEnabled(PRUint32 aState); + NS_IMETHOD GetIMEEnabled(PRUint32 *aState); + NS_IMETHOD CancelComposition(); + NS_IMETHOD OnIMEFocusChange(PRBool aFocus); + NS_IMETHOD OnIMETextChange(PRUint32 aOffset, PRUint32 aEnd, + PRUint32 aNewEnd); + NS_IMETHOD OnIMESelectionChange(void); + private: nsresult DispatchPaintEvent(); nsresult DispatchResizeEvent(); void SetChild(PuppetWidget* aChild); + nsresult IMEEndComposition(PRBool aCancel); + class PaintTask : public nsRunnable { public: NS_DECL_NSIRUNNABLE PaintTask(PuppetWidget* widget) : mWidget(widget) {} void Revoke() { mWidget = nsnull; } private: PuppetWidget* mWidget; }; + // TabChild normally holds a strong reference to this PuppetWidget + // or its root ancestor, but each PuppetWidget also needs a reference + // back to TabChild (e.g. to delegate nsIWidget IME calls to chrome) + // So we hold a weak reference to TabChild (PBrowserChild) here. + // Since it's possible for TabChild to outlive the PuppetWidget, + // we clear this weak reference in Destroy() + PBrowserChild *mTabChild; // The "widget" to which we delegate events if we don't have an // event handler. nsRefPtr<PuppetWidget> mChild; nsIntRegion mDirtyRegion; nsRevocableEventPtr<PaintTask> mPaintTask; PRPackedBool mEnabled; PRPackedBool mVisible; // XXX/cjones: keeping this around until we teach LayerManager to do // retained-content-only transactions nsRefPtr<gfxASurface> mSurface; + // IME + nsIMEUpdatePreference mIMEPreference; + PRPackedBool mIMEComposing; + PRPackedBool mIMESuppressNotifySel; }; } // namespace widget } // namespace mozilla #endif // mozilla_widget_PuppetWidget_h__
--- a/widget/src/xpwidgets/nsBaseWidget.h +++ b/widget/src/xpwidgets/nsBaseWidget.h @@ -146,16 +146,17 @@ public: NS_IMETHOD CancelIMEComposition() { return NS_OK; } NS_IMETHOD SetAcceleratedRendering(PRBool aEnabled); virtual PRBool GetAcceleratedRendering(); virtual PRBool GetShouldAccelerate(); NS_IMETHOD GetToggledKeyState(PRUint32 aKeyCode, PRBool* aLEDState) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHOD OnIMEFocusChange(PRBool aFocus) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHOD OnIMETextChange(PRUint32 aStart, PRUint32 aOldEnd, PRUint32 aNewEnd) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHOD OnIMESelectionChange(void) { return NS_ERROR_NOT_IMPLEMENTED; } + virtual nsIMEUpdatePreference GetIMEUpdatePreference() { return nsIMEUpdatePreference(PR_FALSE, PR_FALSE); } NS_IMETHOD OnDefaultButtonLoaded(const nsIntRect &aButtonRect) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHOD OverrideSystemMouseScrollSpeed(PRInt32 aOriginalDelta, PRBool aIsHorizontal, PRInt32 &aOverriddenDelta); virtual already_AddRefed<nsIWidget> CreateChild(const nsIntRect &aRect, EVENT_CALLBACK aHandleEventFunction, nsIDeviceContext *aContext, nsIAppShell *aAppShell = nsnull, nsIToolkit *aToolkit = nsnull,