mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-13 13:25:37 +00:00
Merge mozilla-central to tracemonkey.
This commit is contained in:
commit
b95fd2bd6c
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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 .............. */
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
@ -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 "">
|
||||
|
@ -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'…
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
10
configure.in
10
configure.in
@ -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
|
||||
|
||||
|
@ -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) \
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
20
content/canvas/crashtests/553938-1.html
Normal file
20
content/canvas/crashtests/553938-1.html
Normal 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>
|
@ -1,2 +1,3 @@
|
||||
load 360293-1.html
|
||||
load 421715-1.html
|
||||
load 553938-1.html
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
15
editor/libeditor/html/crashtests/499844-1.html
Normal file
15
editor/libeditor/html/crashtests/499844-1.html
Normal 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();"> ́</body>
|
||||
</html>
|
@ -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
|
||||
|
@ -272,6 +272,7 @@ class GeckoAppShell
|
||||
|
||||
case NOTIFY_IME_FOCUSCHANGE:
|
||||
GeckoApp.surfaceView.mIMEFocus = state != 0;
|
||||
IMEStateUpdater.resetIME();
|
||||
break;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
5
layout/base/crashtests/537631-1.html
Normal file
5
layout/base/crashtests/537631-1.html
Normal 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>
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
22
layout/generic/crashtests/580504-1.xhtml
Normal file
22
layout/generic/crashtests/580504-1.xhtml
Normal 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>
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
14
layout/reftests/table-anonymous-boxes/490174-1-ref.html
Normal file
14
layout/reftests/table-anonymous-boxes/490174-1-ref.html
Normal 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>
|
13
layout/reftests/table-anonymous-boxes/490174-1.html
Normal file
13
layout/reftests/table-anonymous-boxes/490174-1.html
Normal 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>
|
@ -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
|
||||
|
1
layout/xul/base/src/crashtests/557174-1.xml
Normal file
1
layout/xul/base/src/crashtests/557174-1.xml
Normal 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>
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 =
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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_);
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
@ -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);
|
||||
|
@ -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_
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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__
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user