Merge mozilla-central to tracemonkey.

This commit is contained in:
Robert Sayre 2010-09-26 12:49:11 -04:00
commit b95fd2bd6c
95 changed files with 3496 additions and 649 deletions

View File

@ -88,6 +88,11 @@ pref("app.update.timer", 600000);
// 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);

View File

@ -52,13 +52,34 @@
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 {

View File

@ -21,6 +21,7 @@
# 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
@ -65,7 +66,7 @@ function init(aEvent)
}
#ifdef MOZ_UPDATER
initUpdates();
gAppUpdater = new appUpdater();
#endif
#ifdef XP_MACOSX
@ -76,14 +77,463 @@ function init(aEvent)
}
#ifdef MOZ_UPDATER
/**
* Sets up "Check for Updates..." button.
*/
function initUpdates()
{
var browserBundle = Services.strings.
createBundle("chrome://browser/locale/browser.properties");
var checkForUpdates = document.getElementById("checkForUpdatesButton");
setupCheckForUpdates(checkForUpdates, browserBundle);
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 =
{
// 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

View File

@ -23,6 +23,7 @@
# 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
@ -57,6 +58,9 @@
id="aboutDialog"
windowtype="Browser:About"
onload="init(event);"
#ifdef MOZ_UPDATER
onunload="onUnload(event);"
#endif
#ifdef XP_MACOSX
inwindowmenu="false"
#else
@ -64,7 +68,6 @@
#endif
>
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<script type="application/javascript" src="chrome://browser/content/aboutDialog.js"/>
<vbox>
@ -74,12 +77,38 @@
#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>

View File

@ -137,6 +137,10 @@ toolbar[mode="icons"] > #reload-button[displaystop] {
-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);
@ -421,3 +425,14 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-m
#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;
}

View File

@ -489,6 +489,11 @@
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"

View File

@ -434,7 +434,6 @@
this.mTab.setAttribute("busy", "true");
this.mTab.setAttribute("progresspercent", "0");
this._startStalledTimer();
this.mTabBrowser.updateIcon(this.mTab);
this.mTabBrowser.setTabTitleLoading(this.mTab);
}
@ -463,7 +462,6 @@
this.mTab.removeAttribute("progresspercent");
this.mTab.removeAttribute("stalled");
this._cancelStalledTimer();
this.mTabBrowser.updateIcon(this.mTab);
var location = aRequest.QueryInterface(nsIChannel).URI;
@ -602,7 +600,13 @@
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]);
]]>
@ -619,20 +623,6 @@
</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>
@ -650,31 +640,29 @@
<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>
@ -974,7 +962,6 @@
this.mCurrentTab.setAttribute("busy", "true");
this.mIsBusy = true;
this.setTabTitleLoading(this.mCurrentTab);
this.updateIcon(this.mCurrentTab);
} else {
this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
}
@ -1725,7 +1712,6 @@
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.

View File

@ -39,8 +39,9 @@ var progressListener = {
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);
}
};

View File

@ -853,15 +853,7 @@ SessionStoreService.prototype = {
// 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);
@ -2202,6 +2194,24 @@ SessionStoreService.prototype = {
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.
@ -2287,61 +2297,12 @@ SessionStoreService.prototype = {
}
}
}
// 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)
@ -2379,6 +2340,55 @@ SessionStoreService.prototype = {
}
}
// 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];
@ -2485,6 +2495,7 @@ SessionStoreService.prototype = {
},
restoreTab: function(aTab) {
let window = aTab.ownerDocument.defaultView;
let browser = aTab.linkedBrowser;
let tabData = browser.__SS_data;
@ -2496,6 +2507,9 @@ SessionStoreService.prototype = {
// 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;
@ -2576,9 +2590,11 @@ SessionStoreService.prototype = {
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);
}
});
}
},
@ -3498,11 +3514,36 @@ SessionStoreService.prototype = {
* 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 .............. */
/**

View File

@ -48,12 +48,18 @@ function test() {
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 {
@ -70,7 +76,8 @@ function test_cascade() {
// 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();
@ -102,28 +109,18 @@ function test_cascade() {
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.
@ -140,7 +137,8 @@ function test_select() {
// 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);
@ -154,7 +152,7 @@ function test_select() {
{ 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
@ -171,32 +169,17 @@ function test_select() {
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)
@ -205,8 +188,12 @@ function test_select() {
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);
@ -214,6 +201,336 @@ function test_select() {
}
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,

View File

@ -21,3 +21,36 @@
<!-- 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 "">

View File

@ -141,13 +141,13 @@ 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…
@ -158,14 +158,6 @@ 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'…

View File

@ -1762,11 +1762,6 @@ 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. */

View File

@ -2262,11 +2262,6 @@ 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

View File

@ -2164,11 +2164,6 @@ 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

View File

@ -212,6 +212,8 @@ class Automation(object):
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,

View File

@ -36,7 +36,7 @@
#
# ***** END LICENSE BLOCK ***** */
import glob, logging, os, shutil, subprocess, sys
import glob, logging, os, platform, shutil, subprocess, sys
import re
from urlparse import urlparse
@ -49,6 +49,7 @@ __all__ = [
"getDebuggerInfo",
"DEBUGGER_INFO",
"replaceBackSlashes",
"wrapCommand",
]
# Map of debugging programs to information about them, like default arguments
@ -359,3 +360,16 @@ def processLeakLog(leakLogFile, leakThreshold = 0):
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

View File

@ -6001,17 +6001,19 @@ if test -n "$MOZ_WEBM"; then
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

View File

@ -1694,23 +1694,6 @@ public:
*/
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();
@ -1800,12 +1783,6 @@ private:
static nsIInterfaceRequestor* sSameOriginChecker;
static PRBool sIsHandlingKeyBoardEvent;
#ifdef MOZ_IPC
#ifdef ANDROID
static nsFrameLoader *sActiveFrameLoader;
#endif
#endif
};
#define NS_HOLD_JS_OBJECTS(obj, clazz) \

View File

@ -269,12 +269,6 @@ 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
@ -6256,17 +6250,6 @@ nsContentUtils::IsFocusedContent(nsIContent* aContent)
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

View File

@ -1047,9 +1047,6 @@ 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
@ -1670,9 +1667,6 @@ nsFrameLoader::ActivateRemoteFrame() {
#ifdef MOZ_IPC
if (mRemoteBrowser) {
mRemoteBrowser->Activate();
#ifdef ANDROID
nsContentUtils::SetActiveFrameLoader(this);
#endif
return NS_OK;
}
#endif

View File

@ -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>

View File

@ -1,2 +1,3 @@
load 360293-1.html
load 421715-1.html
load 553938-1.html

View File

@ -45,9 +45,7 @@
* ***** END LICENSE BLOCK ***** */
#ifdef MOZ_IPC
#ifdef ANDROID
#include "mozilla/dom/PBrowserParent.h"
#endif
#include "mozilla/dom/TabParent.h"
#endif
#include "nsCOMPtr.h"
@ -166,9 +164,7 @@
#endif
#ifdef MOZ_IPC
#ifdef ANDROID
#include "nsFrameLoader.h"
#endif
using namespace mozilla::dom;
#endif
//#define DEBUG_DOCSHELL_FOCUS
@ -1321,54 +1317,78 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext,
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);
}
@ -1390,17 +1410,14 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext,
}
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;
}
}
}
@ -1412,17 +1429,14 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext,
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;
}
@ -3255,49 +3269,6 @@ nsEventStateManager::PostHandleEvent(nsPresContext* aPresContext,
}
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
@ -3308,25 +3279,31 @@ nsEventStateManager::PostHandleEvent(nsPresContext* aPresContext,
}
#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)

View File

@ -62,6 +62,12 @@ class nsIDocShellTreeItem;
class imgIContainer;
class nsDOMDataTransfer;
namespace mozilla {
namespace dom {
class TabParent;
}
}
/*
* Event listener manager
*/
@ -334,10 +340,9 @@ protected:
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;

View File

@ -286,7 +286,8 @@ public:
nsresult Init(nsIWidget* aWidget,
nsPresContext* aPresContext,
nsINode* aNode);
nsINode* aNode,
PRBool aWantUpdates);
void Destroy(void);
nsCOMPtr<nsIWidget> mWidget;
@ -307,10 +308,16 @@ nsTextStateManager::nsTextStateManager()
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
@ -334,12 +341,17 @@ nsTextStateManager::Init(nsIWidget* aWidget,
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);
mRootContent = selRange->GetStartParent()->
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIRange> selRange(do_QueryInterface(selDomRange));
NS_ENSURE_TRUE(selRange && selRange->GetStartParent(),
NS_ERROR_UNEXPECTED);
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.
@ -590,6 +602,8 @@ nsIMEStateManager::OnTextStateFocus(nsPresContext* aPresContext,
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);
@ -597,7 +611,8 @@ nsIMEStateManager::OnTextStateFocus(nsPresContext* aPresContext,
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();

View File

@ -10,7 +10,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=561636
<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>
@ -48,61 +48,66 @@ 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();
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();
// Initialize
document.forms[0].addEventListener('submit', function(aEvent) {
aEvent.target.removeEventListener('submit', arguments.callee, false);
formSubmitted[0] = true;
}, false);
// Initialize
document.forms[0].addEventListener('submit', function(aEvent) {
aEvent.target.removeEventListener('submit', arguments.callee, false);
formSubmitted[0] = true;
}, false);
document.forms[1].addEventListener('submit', function(aEvent) {
aEvent.target.removeEventListener('submit', arguments.callee, false);
formSubmitted[1] = true;
}, false);
document.forms[1].addEventListener('submit', function(aEvent) {
aEvent.target.removeEventListener('submit', arguments.callee, false);
formSubmitted[1] = true;
}, false);
document.forms[2].addEventListener('submit', function(aEvent) {
aEvent.target.removeEventListener('submit', arguments.callee, false);
formSubmitted[2] = true;
}, false);
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;
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>

View File

@ -11,10 +11,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=446483
</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>
@ -31,8 +28,17 @@ function gc() {
}
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();

View File

@ -250,7 +250,7 @@ nsDOMWindowUtils::SetCSSViewport(float aWidthPx, float aHeightPx)
nscoord width = nsPresContext::CSSPixelsToAppUnits(aWidthPx);
nscoord height = nsPresContext::CSSPixelsToAppUnits(aHeightPx);
presShell->ResizeReflow(width, height);
presShell->ResizeReflowOverride(width, height);
return NS_OK;
}

View File

@ -62,11 +62,6 @@
#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;
@ -568,33 +563,6 @@ ContentParent::AfterProcessNextEvent(nsIThreadInternal *thread,
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,

View File

@ -165,11 +165,6 @@ private:
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,

View File

@ -58,6 +58,7 @@ using nsTextEvent;
using nsQueryContentEvent;
using nsSelectionEvent;
using RemoteDOMEvent;
using nsIMEUpdatePreference;
namespace mozilla {
namespace dom {
@ -90,7 +91,68 @@ parent:
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);
@ -150,8 +212,6 @@ child:
TextEvent(nsTextEvent event);
QueryContentEvent(nsQueryContentEvent event);
SelectionEvent(nsSelectionEvent event);
/**

View File

@ -104,9 +104,6 @@ parent:
// 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);

View File

@ -571,16 +571,6 @@ TabChild::RecvTextEvent(const nsTextEvent& 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)
{
@ -592,28 +582,12 @@ TabChild::RecvSelectionEvent(const nsSelectionEvent& event)
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;
}
@ -1001,7 +975,7 @@ 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;

View File

@ -192,7 +192,6 @@ public:
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);

View File

@ -71,11 +71,6 @@
#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;
@ -87,10 +82,14 @@ using namespace mozilla::layout;
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)
{
}
@ -317,28 +316,232 @@ TabParent::RecvAsyncMessage(const nsString& aMessage,
}
bool
TabParent::RecvQueryContentResult(const nsQueryContentEvent& event)
TabParent::RecvNotifyIMEFocus(const PRBool& aFocus,
nsIMEUpdatePreference* aPreference)
{
#ifdef ANDROID
if (!event.mSucceeded) {
AndroidBridge::Bridge()->ReturnIMEQueryResult(nsnull, 0, 0, 0);
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)
{
// 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);
}
#endif
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();
}
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;
}
@ -500,5 +703,19 @@ TabParent::GetFrameLoader() const
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

View File

@ -91,7 +91,20 @@ public:
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,
@ -163,6 +176,12 @@ public:
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,
@ -202,8 +221,21 @@ protected:
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

View File

@ -311,6 +311,21 @@ nsEditor::PostCreate()
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;
}

View File

@ -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();"> &#x0301;</body>
</html>

View File

@ -12,6 +12,7 @@ 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

View File

@ -272,6 +272,7 @@ class GeckoAppShell
case NOTIFY_IME_FOCUSCHANGE:
GeckoApp.surfaceView.mIMEFocus = state != 0;
IMEStateUpdater.resetIME();
break;
}

View File

@ -2641,26 +2641,97 @@ _cairo_d2d_copy_surface(cairo_d2d_surface_t *dst,
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;
}
@ -2668,7 +2739,24 @@ _cairo_d2d_try_copy(cairo_d2d_surface_t *dst,
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);
@ -2678,8 +2766,6 @@ _cairo_d2d_try_copy(cairo_d2d_surface_t *dst,
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;
@ -2712,7 +2798,7 @@ _cairo_d2d_try_copy(cairo_d2d_surface_t *dst,
}
cairo_int_status_t rv = _cairo_d2d_copy_surface(dst, d2dsrc, &translation, region);
cairo_region_destroy(region);
return rv;
@ -2926,8 +3012,9 @@ _cairo_d2d_paint(void *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;
@ -2986,6 +3073,61 @@ _cairo_d2d_mask(void *surface,
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) {
@ -3004,44 +3146,16 @@ _cairo_d2d_mask(void *surface,
}
#endif
status = (cairo_int_status_t)_cairo_surface_mask_extents (&d2dsurf->base,
op, source,
mask,
clip, &extents);
if (unlikely (status))
return status;
if (isSolidAlphaMask) {
brush->SetOpacity(solidAlphaValue);
target_rt->FillRectangle(rect,
brush);
brush->SetOpacity(1.0);
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 (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);
@ -3171,8 +3285,9 @@ _cairo_d2d_fill(void *surface,
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;

View File

@ -515,7 +515,10 @@ BasicThebesLayerBuffer::DrawTo(ThebesLayer* aLayer,
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);
@ -1800,7 +1803,7 @@ public:
mOldYResolution = 1.0;
if (IsSurfaceDescriptorValid(mFrontBufferDescriptor)) {
BasicManager()->ShadowLayerManager::DestroySharedSurface(&mFrontBufferDescriptor);
BasicManager()->ShadowLayerManager::DestroySharedSurface(&mFrontBufferDescriptor, mAllocator);
}
}
@ -1916,7 +1919,7 @@ public:
virtual void DestroyFrontBuffer()
{
if (mFrontSurface) {
BasicManager()->ShadowLayerManager::DestroySharedSurface(mFrontSurface);
BasicManager()->ShadowLayerManager::DestroySharedSurface(mFrontSurface, mAllocator);
}
mFrontSurface = nsnull;
}
@ -2000,7 +2003,7 @@ public:
virtual void DestroyFrontBuffer()
{
if (mFrontSurface) {
BasicManager()->ShadowLayerManager::DestroySharedSurface(mFrontSurface);
BasicManager()->ShadowLayerManager::DestroySharedSurface(mFrontSurface, mAllocator);
}
mFrontSurface = nsnull;
}

View File

@ -491,18 +491,20 @@ ShadowLayerForwarder::ConstructShadowFor(ShadowableLayer* 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);
}

View File

@ -326,19 +326,13 @@ 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;
@ -350,11 +344,9 @@ public:
static void PlatformSyncBeforeReplyUpdate();
protected:
ShadowLayerManager() : mForwarder(NULL) {}
ShadowLayerManager() {}
PRBool PlatformDestroySharedSurface(SurfaceDescriptor* aSurface);
PLayersParent* mForwarder;
};
@ -393,6 +385,15 @@ protected:
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");
@ -439,14 +440,27 @@ public:
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
*
@ -467,14 +481,27 @@ public:
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
*
@ -502,8 +529,12 @@ public:
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;
};

View File

@ -150,7 +150,9 @@ ShadowLayersParent::RecvUpdate(const nsTArray<Edit>& cset,
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;
}
@ -164,8 +166,10 @@ ShadowLayersParent::RecvUpdate(const nsTArray<Edit>& cset,
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: {
@ -178,7 +182,9 @@ ShadowLayersParent::RecvUpdate(const nsTArray<Edit>& cset,
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;
}

View File

@ -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>

View File

@ -132,9 +132,11 @@ FontEntry::CreateFontEntry(const gfxProxyFontEntry &aProxyEntry,
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;
}

View File

@ -370,6 +370,8 @@ Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
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
@ -395,7 +397,6 @@ Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
// 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)
@ -493,11 +494,11 @@ Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
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
@ -507,7 +508,7 @@ Shmem::Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
if (!segment)
return 0;
*PtrToSize(segment) = aNBytes;
*PtrToSize(segment) = static_cast<uint32>(aNBytes);
return segment;
}
@ -560,7 +561,7 @@ Shmem::OpenExisting(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
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;

View File

@ -122,7 +122,7 @@ public:
mSize(0),
mId(aId)
{
mSize = *PtrToSize(mSegment);
mSize = static_cast<size_t>(*PtrToSize(mSegment));
}
#else
Shmem(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead,
@ -274,12 +274,12 @@ private:
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

View File

@ -6527,7 +6527,7 @@ xml_setNamespace(JSContext *cx, uintN argc, jsval *vp)
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)

View File

@ -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>

View File

@ -292,6 +292,7 @@ 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

View File

@ -139,8 +139,8 @@ typedef struct CapturingContentInfo {
} 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
@ -350,6 +350,12 @@ public:
* 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

View File

@ -741,6 +741,7 @@ public:
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;
@ -1007,6 +1008,9 @@ protected:
// 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
@ -1147,6 +1151,8 @@ protected:
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.
@ -1662,6 +1668,7 @@ PresShell::PresShell()
mRenderFlags = 0;
mXResolution = 1.0;
mYResolution = 1.0;
mViewportOverridden = PR_FALSE;
static bool registeredReporter = false;
if (!registeredReporter) {
@ -2797,8 +2804,26 @@ 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,

View File

@ -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>

View File

@ -299,7 +299,7 @@ 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
@ -328,6 +328,7 @@ 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

View File

@ -147,11 +147,11 @@ RenderFrameParent::ShadowLayersUpdated()
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;
}
@ -254,10 +254,7 @@ RenderFrameParent::AllocPLayers()
}
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

View File

@ -19,7 +19,7 @@ skip-if(winWidget) == textarea-resize-background.html textarea-resize-background
!= 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

View File

@ -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>

View File

@ -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>

View File

@ -34,6 +34,7 @@ random-if(d2d) == 394402-1a.html 394402-1-ref.html # bug 586833
== 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

View File

@ -0,0 +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>

View File

@ -72,4 +72,5 @@ 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

View File

@ -266,15 +266,17 @@ 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

View File

@ -85,7 +85,6 @@ _MOCHITEST_FILES = \
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 \
@ -103,7 +102,6 @@ _MOCHITEST_FILES += \
endif
_MOCHICHROME_FILES = \
test_bug479979.xul \
test_npruntime.xul \
test_privatemode.xul \
test_wmode.xul \
@ -111,6 +109,8 @@ _MOCHICHROME_FILES = \
# 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 \
@ -119,11 +119,18 @@ _MOCHICHROME_FILES += \
$(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

View File

@ -2,10 +2,10 @@
<?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"

View File

@ -6,7 +6,7 @@ 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

View File

@ -320,6 +320,7 @@ class XPCShellTests(object):
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

View File

@ -186,6 +186,7 @@ src_client_linux_linux_client_unittest_SOURCES = \
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

View File

@ -86,7 +86,7 @@
#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"

View File

@ -32,6 +32,7 @@
#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>
@ -41,6 +42,7 @@
#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;
@ -126,6 +128,443 @@ TEST(ExceptionHandlerTest, ChildCrash) {
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 =

View File

@ -36,7 +36,7 @@
#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 {

View File

@ -35,7 +35,7 @@
#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;

View File

@ -46,6 +46,8 @@
#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>
@ -417,7 +419,8 @@ class MinidumpWriter {
#endif
crashing_tid_(context->tid),
crashing_tid_pc_(0),
dumper_(crashing_pid) {
dumper_(crashing_pid),
memory_blocks_(dumper_.allocator()) {
}
// case (2) above
@ -430,7 +433,8 @@ class MinidumpWriter {
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() {
@ -467,7 +471,9 @@ class MinidumpWriter {
// 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_);
@ -494,6 +500,10 @@ class MinidumpWriter {
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;
@ -705,6 +715,51 @@ class MinidumpWriter {
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;
@ -728,6 +783,8 @@ class MinidumpWriter {
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;
@ -836,6 +893,24 @@ class MinidumpWriter {
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())
@ -950,6 +1025,22 @@ class MinidumpWriter {
}
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;
@ -1200,6 +1291,10 @@ popline:
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,

View File

@ -51,6 +51,11 @@
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 */; };
@ -507,6 +512,11 @@
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; };
@ -759,6 +769,7 @@
32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */,
F92C538D0ECCE6F2009BE4BA /* client */,
F92C53600ECCE3D6009BE4BA /* common */,
D244536912426EE7009BBCE0 /* processor */,
0867D69AFE84028FC02AAC07 /* Frameworks */,
034768DFFF38A50411DB9C8B /* Products */,
F9C77DDB0F7DD5CF0045F7DB /* UnitTests-Info.plist */,
@ -780,6 +791,17 @@
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 = (
@ -813,6 +835,7 @@
F92C53600ECCE3D6009BE4BA /* common */ = {
isa = PBXGroup;
children = (
D244540A12439BA0009BBCE0 /* memory_unittest.cc */,
F92C53870ECCE6C0009BE4BA /* convert_UTF.c */,
F92C53880ECCE6C0009BE4BA /* convert_UTF.h */,
F92C53850ECCE6AD009BE4BA /* string_conversion.cc */,
@ -1632,6 +1655,10 @@
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 */,
@ -1648,6 +1675,7 @@
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;
};
@ -2100,8 +2128,10 @@
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,

View File

@ -27,6 +27,7 @@
// (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>
@ -64,7 +65,8 @@ MinidumpGenerator::MinidumpGenerator()
exception_thread_(0),
crashing_task_(mach_task_self()),
handler_thread_(mach_thread_self()),
dynamic_images_(NULL) {
dynamic_images_(NULL),
memory_blocks_(&allocator_) {
GatherSystemInformation();
}
@ -79,7 +81,8 @@ MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
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 {
@ -173,6 +176,7 @@ string MinidumpGenerator::UniqueNameInDirectory(const string &dir,
bool MinidumpGenerator::Write(const char *path) {
WriteStreamFN writers[] = {
&MinidumpGenerator::WriteThreadListStream,
&MinidumpGenerator::WriteMemoryListStream,
&MinidumpGenerator::WriteSystemInfoStream,
&MinidumpGenerator::WriteModuleListStream,
&MinidumpGenerator::WriteMiscInfoStream,
@ -514,6 +518,8 @@ bool MinidumpGenerator::WriteThreadStream(mach_port_t thread_id,
if (!WriteStack(state, &thread->stack))
return false;
memory_blocks_.push_back(thread->stack);
if (!WriteContext(state, &thread->thread_context))
return false;
@ -566,6 +572,118 @@ bool MinidumpGenerator::WriteThreadListStream(
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_);

View File

@ -37,8 +37,9 @@
#include <string>
#include "client/minidump_file_writer.h"
#include "google_breakpad/common/minidump_format.h"
#include "common/memory.h"
#include "common/mac/macho_utilities.h"
#include "google_breakpad/common/minidump_format.h"
#include "dynamic_images.h"
@ -119,6 +120,7 @@ class MinidumpGenerator {
// 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);
@ -165,6 +167,15 @@ class MinidumpGenerator {
// 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

View File

@ -29,6 +29,7 @@
// exception_handler_test.cc: Unit tests for google_breakpad::ExceptionHandler
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
@ -36,6 +37,14 @@
#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;
@ -44,6 +53,11 @@ 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;
@ -80,7 +94,6 @@ static bool MDCallback(const char *dump_dir, const char *file_name,
}
TEST_F(ExceptionHandlerTest, InProcess) {
AutoTempDir tempDir;
// Give the child process a pipe to report back on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
@ -167,8 +180,8 @@ TEST_F(ExceptionHandlerTest, DumpChildProcess) {
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,
@ -195,4 +208,390 @@ TEST_F(ExceptionHandlerTest, DumpChildProcess) {
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());
}
}

View File

@ -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

View File

@ -27,7 +27,7 @@
// (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;
@ -74,7 +74,7 @@ TEST(WastefulVectorTest, Setup) {
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);

View File

@ -27,15 +27,22 @@
// (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 {
@ -196,4 +203,4 @@ inline void* operator new(size_t nbytes,
return allocator.Alloc(nbytes);
}
#endif // CLIENT_LINUX_HANDLER_MEMORY_H_
#endif // GOOGLE_BREAKPAD_COMMON_MEMORY_H_

View File

@ -1728,16 +1728,22 @@ var gFinishedPage = {
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);

View File

@ -51,6 +51,7 @@ 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";
@ -2779,6 +2780,9 @@ 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);
},
@ -2788,7 +2792,7 @@ UpdatePrompt.prototype = {
*/
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 + ".";
@ -2805,6 +2809,9 @@ UpdatePrompt.prototype = {
* See nsIUpdateService.idl
*/
showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
if (this._getAltUpdateWindow())
return;
if (background) {
if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false))
return;
@ -2852,7 +2859,8 @@ UpdatePrompt.prototype = {
* 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:
@ -2892,6 +2900,18 @@ UpdatePrompt.prototype = {
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

View File

@ -49,6 +49,7 @@
#include "nsPrintfCString.h"
#include "prproces.h"
#include "prlog.h"
#include "prenv.h"
#include "nsVersionComparator.h"
#ifdef XP_MACOSX
@ -473,6 +474,10 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsILocalFile *statusFile,
argc = 3;
}
if (gSafeMode) {
PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
}
LOG(("spawning updater process [%s]\n", updaterPath.get()));
#if defined(USE_EXECV)

View File

@ -279,6 +279,24 @@ struct ParamTraits<nsSelectionEvent>
}
};
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__

View File

@ -73,6 +73,11 @@ namespace mozilla {
namespace layers {
class LayerManager;
}
#ifdef MOZ_IPC
namespace dom {
class PBrowserChild;
}
#endif
}
/**
@ -112,10 +117,10 @@ typedef nsEventStatus (* EVENT_CALLBACK)(nsGUIEvent *event);
#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
@ -128,6 +133,13 @@ typedef nsEventStatus (* EVENT_CALLBACK)(nsGUIEvent *event);
#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.
*/
@ -184,11 +196,47 @@ enum nsTopLevelWidgetZPlacement { // for PlaceBehind()
};
/**
* 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;
@ -1193,6 +1241,9 @@ class nsIWidget : public nsISupports {
*
* 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;
@ -1211,6 +1262,11 @@ class nsIWidget : public nsISupports {
*/
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.
@ -1262,7 +1318,7 @@ class nsIWidget : public nsISupports {
* The returned widget must still be nsIWidget::Create()d.
*/
static already_AddRefed<nsIWidget>
CreatePuppetWidget();
CreatePuppetWidget(PBrowserChild *aTabChild);
#endif
/**

View File

@ -38,7 +38,6 @@
#include <android/log.h>
#ifdef MOZ_IPC
#include "mozilla/dom/ContentChild.h"
#include "nsXULAppAPI.h"
#endif
#include <pthread.h>
@ -199,12 +198,6 @@ 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
@ -212,11 +205,6 @@ 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;
}

View File

@ -90,8 +90,6 @@ 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

View File

@ -561,19 +561,17 @@ 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;
@ -1589,6 +1587,7 @@ nsWindow::ResetInputState()
InitEvent(textEvent, nsnull);
textEvent.theText = mIMEComposingText;
DispatchEvent(&textEvent);
mIMEComposingText.Truncate(0);
nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_END, this);
InitEvent(event, nsnull);
@ -1626,6 +1625,7 @@ nsWindow::CancelIMEComposition()
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);
@ -1692,3 +1692,9 @@ nsWindow::OnIMESelectionChange(void)
return NS_OK;
}
nsIMEUpdatePreference
nsWindow::GetIMEUpdatePreference()
{
return nsIMEUpdatePreference(PR_TRUE, PR_TRUE);
}

View File

@ -159,6 +159,7 @@ public:
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();

View File

@ -6874,6 +6874,20 @@ nsWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
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);

View File

@ -38,6 +38,7 @@
*
* ***** END LICENSE BLOCK ***** */
#include "mozilla/dom/PBrowserChild.h"
#include "BasicLayers.h"
#include "gfxPlatform.h"
@ -45,6 +46,7 @@
using namespace mozilla::layers;
using namespace mozilla::widget;
using namespace mozilla::dom;
static void
InvalidateRegion(nsIWidget* aWidget, const nsIntRegion& aRegion)
@ -56,12 +58,12 @@ InvalidateRegion(nsIWidget* aWidget, const nsIntRegion& aRegion)
}
/*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();
}
@ -74,7 +76,8 @@ 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);
}
@ -107,6 +110,9 @@ PuppetWidget::Create(nsIWidget *aParent,
->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);
@ -130,7 +136,7 @@ PuppetWidget::CreateChild(const nsIntRect &aRect,
{
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,
@ -146,6 +152,7 @@ PuppetWidget::Destroy()
mPaintTask.Revoke();
mChild = nsnull;
mLayerManager = nsnull;
mTabChild = nsnull;
return NS_OK;
}
@ -243,6 +250,21 @@ PuppetWidget::Update()
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)
{
@ -253,10 +275,20 @@ PuppetWidget::DispatchEvent(nsGUIEvent* event, nsEventStatus& aStatus)
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);
}
@ -278,6 +310,161 @@ 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()
{

View File

@ -65,7 +65,7 @@ class PuppetWidget : public nsBaseWidget, public nsSupportsWeakReference
static const size_t kMaxDimension;
public:
PuppetWidget();
PuppetWidget(PBrowserChild *aTabChild);
virtual ~PuppetWidget();
NS_DECL_ISUPPORTS_INHERITED
@ -151,6 +151,8 @@ public:
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,
@ -166,12 +168,25 @@ public:
// 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
@ -181,6 +196,13 @@ 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;
@ -191,6 +213,10 @@ private:
// 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

View File

@ -151,6 +151,7 @@ public:
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>