This commit is contained in:
Tim Taubert 2011-11-09 10:53:12 +01:00
Родитель 3239e5b9c1 16c85d10ca
Коммит e354fcec6e
7 изменённых файлов: 296 добавлений и 127 удалений

Просмотреть файл

@ -117,11 +117,6 @@ const CAPABILITIES = [
"DNSPrefetch", "Auth", "WindowControl"
];
// These keys are for internal use only - they shouldn't be part of the JSON
// that gets saved to disk nor part of the strings returned by the API.
const INTERNAL_KEYS = ["_tabStillLoading", "_hosts", "_formDataSaved",
"_shouldRestore", "_host", "_scheme"];
// These are tab events that we listen to.
const TAB_EVENTS = ["TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide",
"TabPinned", "TabUnpinned"];
@ -216,6 +211,9 @@ SessionStoreService.prototype = {
// states for all currently opened windows
_windows: {},
// internal states for all open windows (data we need to associate, but not write to disk)
_internalWindows: {},
// states for all recently closed windows
_closedWindows: [],
@ -379,6 +377,11 @@ SessionStoreService.prototype = {
// We don't want to minimize and then open a window at startup.
if (this._initialState.windows[0].sizemode == "minimized")
this._initialState.windows[0].sizemode = "normal";
// clear any lastSessionWindowID attributes since those don't matter
// during normal restore
this._initialState.windows.forEach(function(aWindow) {
delete aWindow.__lastSessionWindowID;
});
}
}
catch (ex) { debug("The session file is invalid: " + ex); }
@ -550,6 +553,9 @@ SessionStoreService.prototype = {
this._forEachBrowserWindow(function(aWindow) {
Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
delete aTab.linkedBrowser.__SS_data;
delete aTab.linkedBrowser.__SS_tabStillLoading;
delete aTab.linkedBrowser.__SS_formDataSaved;
delete aTab.linkedBrowser.__SS_hostSchemeData;
if (aTab.linkedBrowser.__SS_restoreState)
this._resetTabRestoringState(aTab);
});
@ -557,10 +563,13 @@ SessionStoreService.prototype = {
});
// also clear all data about closed tabs and windows
for (let ix in this._windows) {
if (ix in openWindows)
if (ix in openWindows) {
this._windows[ix]._closedTabs = [];
else
}
else {
delete this._windows[ix];
delete this._internalWindows[ix];
}
}
// also clear all data about closed windows
this._closedWindows = [];
@ -797,6 +806,10 @@ SessionStoreService.prototype = {
// and create its data object
this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false };
// and create its internal data object
this._internalWindows[aWindow.__SSi] = { hosts: {} }
if (!this._isWindowLoaded(aWindow))
this._windows[aWindow.__SSi]._restoring = true;
if (!aWindow.toolbar.visible)
@ -968,7 +981,9 @@ SessionStoreService.prototype = {
winData.title = aWindow.content.document.title || tabbrowser.selectedTab.label;
winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
tabbrowser.selectedTab);
this._updateCookies([winData]);
let windows = {};
windows[aWindow.__SSi] = winData;
this._updateCookies(windows);
}
#ifndef XP_MACOSX
@ -989,6 +1004,7 @@ SessionStoreService.prototype = {
// clear this window from the list
delete this._windows[aWindow.__SSi];
delete this._internalWindows[aWindow.__SSi];
// save the state without this window to disk
this.saveStateDelayed();
@ -1046,6 +1062,9 @@ SessionStoreService.prototype = {
browser.removeEventListener("DOMAutoComplete", this, true);
delete browser.__SS_data;
delete browser.__SS_tabStillLoading;
delete browser.__SS_formDataSaved;
delete browser.__SS_hostSchemeData;
// If this tab was in the middle of restoring or still needs to be restored,
// we need to reset that state. If the tab was restoring, we will attempt to
@ -1125,6 +1144,8 @@ SessionStoreService.prototype = {
}
delete aBrowser.__SS_data;
delete aBrowser.__SS_tabStillLoading;
delete aBrowser.__SS_formDataSaved;
this.saveStateDelayed(aWindow);
// attempt to update the current URL we send in a crash report
@ -1139,9 +1160,9 @@ SessionStoreService.prototype = {
* Browser reference
*/
onTabInput: function sss_onTabInput(aWindow, aBrowser) {
if (aBrowser.__SS_data)
delete aBrowser.__SS_data._formDataSaved;
// deleting __SS_formDataSaved will cause us to recollect form data
delete aBrowser.__SS_formDataSaved;
this.saveStateDelayed(aWindow, 3000);
},
@ -1708,7 +1729,7 @@ SessionStoreService.prototype = {
if (!browser || !browser.currentURI)
// can happen when calling this function right after .addTab()
return tabData;
else if (browser.__SS_data && browser.__SS_data._tabStillLoading) {
else if (browser.__SS_data && browser.__SS_tabStillLoading) {
// use the data to be restored when the tab hasn't been completely loaded
tabData = browser.__SS_data;
if (aTab.pinned)
@ -1743,10 +1764,11 @@ SessionStoreService.prototype = {
tabData.index = history.index + 1;
}
else if (history && history.count > 0) {
browser.__SS_hostSchemeData = [];
try {
for (var j = 0; j < history.count; j++) {
let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
aFullData, aTab.pinned);
aFullData, aTab.pinned, browser.__SS_hostSchemeData);
tabData.entries.push(entry);
}
// If we make it through the for loop, then we're ok and we should clear
@ -1835,15 +1857,16 @@ SessionStoreService.prototype = {
* always return privacy sensitive data (use with care)
* @param aIsPinned
* the tab is pinned and should be treated differently for privacy
* @param aHostSchemeData
* an array of objects with host & scheme keys
* @returns object
*/
_serializeHistoryEntry:
function sss_serializeHistoryEntry(aEntry, aFullData, aIsPinned) {
function sss_serializeHistoryEntry(aEntry, aFullData, aIsPinned, aHostSchemeData) {
var entry = { url: aEntry.URI.spec };
try {
entry._host = aEntry.URI.host;
entry._scheme = aEntry.URI.scheme;
aHostSchemeData.push({ host: aEntry.URI.host, scheme: aEntry.URI.scheme });
}
catch (ex) {
// We just won't attempt to get cookies for this entry.
@ -1946,7 +1969,7 @@ SessionStoreService.prototype = {
var child = aEntry.GetChildAt(i);
if (child) {
entry.children.push(this._serializeHistoryEntry(child, aFullData,
aIsPinned));
aIsPinned, aHostSchemeData));
}
else { // to maintain the correct frame order, insert a dummy entry
entry.children.push({ url: "about:blank" });
@ -2044,7 +2067,7 @@ SessionStoreService.prototype = {
var browsers = aWindow.gBrowser.browsers;
this._windows[aWindow.__SSi].tabs.forEach(function (tabData, i) {
if (browsers[i].__SS_data &&
browsers[i].__SS_data._tabStillLoading)
browsers[i].__SS_tabStillLoading)
return; // ignore incompletely initialized tabs
try {
this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
@ -2081,9 +2104,9 @@ SessionStoreService.prototype = {
this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
aTabData.entries[tabIndex],
!aTabData._formDataSaved, aFullData,
!aBrowser.__SS_formDataSaved, aFullData,
!!aTabData.pinned);
aTabData._formDataSaved = true;
aBrowser.__SS_formDataSaved = true;
if (aBrowser.currentURI.spec == "about:config")
aTabData.entries[tabIndex].formdata = {
"#textbox": aBrowser.contentDocument.getElementById("textbox").value
@ -2260,8 +2283,8 @@ SessionStoreService.prototype = {
* is the entry we're evaluating for a pinned tab; used only if
* aCheckPrivacy
*/
_extractHostsForCookies:
function sss__extractHostsForCookies(aEntry, aHosts, aCheckPrivacy, aIsPinned) {
_extractHostsForCookiesFromEntry:
function sss__extractHostsForCookiesFromEntry(aEntry, aHosts, aCheckPrivacy, aIsPinned) {
let host = aEntry._host,
scheme = aEntry._scheme;
@ -2275,23 +2298,11 @@ SessionStoreService.prototype = {
let uri = this._getURIFromString(aEntry.url);
host = uri.host;
scheme = uri.scheme;
this._extractHostsForCookiesFromHostScheme(host, scheme, aHosts, aCheckPrivacy, aIsPinned);
}
catch(ex) { }
}
// host and scheme may not be set (for about: urls for example), in which
// case testing scheme will be sufficient.
if (/https?/.test(scheme) && !aHosts[host] &&
(!aCheckPrivacy ||
this._checkPrivacyLevel(scheme == "https", aIsPinned))) {
// By setting this to true or false, we can determine when looking at
// the host in _updateCookies if we should check for privacy.
aHosts[host] = aIsPinned;
}
else if (scheme == "file") {
aHosts[host] = true;
}
if (aEntry.children) {
aEntry.children.forEach(function(entry) {
this._extractHostsForCookies(entry, aHosts, aCheckPrivacy, aIsPinned);
@ -2299,25 +2310,63 @@ SessionStoreService.prototype = {
}
},
/**
* extract the base domain from a host & scheme
* @param aHost
* the host of a uri (usually via nsIURI.host)
* @param aScheme
* the scheme of a uri (usually via nsIURI.scheme)
* @param aHosts
* the hash that will be used to store hosts eg, { hostname: true }
* @param aCheckPrivacy
* should we check the privacy level for https
* @param aIsPinned
* is the entry we're evaluating for a pinned tab; used only if
* aCheckPrivacy
*/
_extractHostsForCookiesFromHostScheme:
function sss__extractHostsForCookiesFromHostScheme(aHost, aScheme, aHosts, aCheckPrivacy, aIsPinned) {
// host and scheme may not be set (for about: urls for example), in which
// case testing scheme will be sufficient.
if (/https?/.test(aScheme) && !aHosts[aHost] &&
(!aCheckPrivacy ||
this._checkPrivacyLevel(aScheme == "https", aIsPinned))) {
// By setting this to true or false, we can determine when looking at
// the host in _updateCookies if we should check for privacy.
aHosts[aHost] = aIsPinned;
}
else if (aScheme == "file") {
aHosts[aHost] = true;
}
},
/**
* store all hosts for a URL
* @param aWindow
* Window reference
*/
_updateCookieHosts: function sss_updateCookieHosts(aWindow) {
var hosts = this._windows[aWindow.__SSi]._hosts = {};
var hosts = this._internalWindows[aWindow.__SSi].hosts = {};
this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) {
aTabData.entries.forEach(function(entry) {
this._extractHostsForCookies(entry, hosts, true, !!aTabData.pinned);
}, this);
}, this);
// Since _updateCookiesHosts is only ever called for open windows during a
// session, we can call into _extractHostsForCookiesFromHostScheme directly
// using data that is attached to each browser.
for (let i = 0; i < aWindow.gBrowser.tabs.length; i++) {
let tab = aWindow.gBrowser.tabs[i];
let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || [];
for (let j = 0; j < hostSchemeData.length; j++) {
this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host,
hostSchemeData[j].scheme,
hosts, true, tab.pinned);
}
}
},
/**
* Serialize cookie data
* @param aWindows
* array of Window references
* JS object containing window data references
* { id: winData, etc. }
*/
_updateCookies: function sss_updateCookies(aWindows) {
function addCookieToHash(aHash, aHost, aPath, aName, aCookie) {
@ -2330,18 +2379,17 @@ SessionStoreService.prototype = {
aHash[aHost][aPath][aName] = aCookie;
}
// collect the cookies per window
for (var i = 0; i < aWindows.length; i++)
aWindows[i].cookies = [];
var jscookies = {};
var _this = this;
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
var MAX_EXPIRY = Math.pow(2, 62);
aWindows.forEach(function(aWindow) {
if (!aWindow._hosts)
for (let [id, window] in Iterator(aWindows)) {
window.cookies = [];
let internalWindow = this._internalWindows[id];
if (!internalWindow.hosts)
return;
for (var [host, isPinned] in Iterator(aWindow._hosts)) {
for (var [host, isPinned] in Iterator(internalWindow.hosts)) {
let list;
try {
list = CookieSvc.getCookiesFromHost(host);
@ -2351,7 +2399,7 @@ SessionStoreService.prototype = {
}
while (list && list.hasMoreElements()) {
var cookie = list.getNext().QueryInterface(Ci.nsICookie2);
// aWindow._hosts will only have hosts with the right privacy rules,
// window._hosts will only have hosts with the right privacy rules,
// so there is no need to do anything special with this call to
// _checkPrivacyLevel.
if (cookie.isSession && _this._checkPrivacyLevel(cookie.isSecure, isPinned)) {
@ -2370,16 +2418,15 @@ SessionStoreService.prototype = {
addCookieToHash(jscookies, cookie.host, cookie.path, cookie.name, jscookie);
}
aWindow.cookies.push(jscookies[cookie.host][cookie.path][cookie.name]);
window.cookies.push(jscookies[cookie.host][cookie.path][cookie.name]);
}
}
}
});
// don't include empty cookie sections
for (i = 0; i < aWindows.length; i++)
if (aWindows[i].cookies.length == 0)
delete aWindows[i].cookies;
// don't include empty cookie sections
if (!window.cookies.length)
delete window.cookies;
}
},
/**
@ -2438,18 +2485,19 @@ SessionStoreService.prototype = {
}
// collect the data for all windows
var total = [], windows = [];
var total = [], windows = {}, ids = [];
var nonPopupCount = 0;
var ix;
for (ix in this._windows) {
if (this._windows[ix]._restoring) // window data is still in _statesToRestore
continue;
total.push(this._windows[ix]);
windows.push(ix);
ids.push(ix);
windows[ix] = this._windows[ix];
if (!this._windows[ix].isPopup)
nonPopupCount++;
}
this._updateCookies(total);
this._updateCookies(windows);
// collect the data for all windows yet to be restored
for (ix in this._statesToRestore) {
@ -2500,7 +2548,7 @@ SessionStoreService.prototype = {
if (activeWindow) {
this.activeWindowSSiCache = activeWindow.__SSi || "";
}
ix = windows.indexOf(this.activeWindowSSiCache);
ix = ids.indexOf(this.activeWindowSSiCache);
// We don't want to restore focus to a minimized window or a window which had all its
// tabs stripped out (doesn't exist).
if (ix != -1 && total[ix] && total[ix].sizemode == "minimized")
@ -2539,10 +2587,12 @@ SessionStoreService.prototype = {
this._collectWindowData(aWindow);
}
var total = [this._windows[aWindow.__SSi]];
this._updateCookies(total);
var winData = this._windows[aWindow.__SSi];
let windows = {};
windows[aWindow.__SSi] = winData;
this._updateCookies(windows);
return { windows: total };
return { windows: [winData] };
},
_collectWindowData: function sss_collectWindowData(aWindow) {
@ -2864,7 +2914,7 @@ SessionStoreService.prototype = {
for (let name in tabData.attributes)
this.xulAttributes[name] = true;
tabData._tabStillLoading = true;
browser.__SS_tabStillLoading = true;
// keep the data around to prevent dataloss in case
// a tab gets closed before it's been properly restored
@ -2931,7 +2981,7 @@ SessionStoreService.prototype = {
restoreHistory:
function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap) {
var _this = this;
while (aTabs.length > 0 && (!aTabData[0]._tabStillLoading || !aTabs[0].parentNode)) {
while (aTabs.length > 0 && (!aTabs[0].linkedBrowser.__SS_tabStillLoading || !aTabs[0].parentNode)) {
aTabs.shift(); // this tab got removed before being completely restored
aTabData.shift();
}
@ -3327,31 +3377,53 @@ SessionStoreService.prototype = {
if (!node)
continue;
let eventType;
let value = aData[key];
if (typeof value == "string" && node.type != "file") {
if (node.value == value)
continue; // don't dispatch an input event for no change
node.value = value;
let event = aDocument.createEvent("UIEvents");
event.initUIEvent("input", true, true, aDocument.defaultView, 0);
node.dispatchEvent(event);
eventType = "input";
}
else if (typeof value == "boolean")
else if (typeof value == "boolean") {
if (node.checked == value)
continue; // don't dispatch a change event for no change
node.checked = value;
else if (typeof value == "number")
eventType = "change";
}
else if (typeof value == "number") {
// We saved the value blindly since selects take more work to determine
// default values. So now we should check to avoid unnecessary events.
if (node.selectedIndex == value)
continue;
try {
node.selectedIndex = value;
eventType = "change";
} catch (ex) { /* throws for invalid indices */ }
else if (value && value.fileList && value.type == "file" && node.type == "file")
}
else if (value && value.fileList && value.type == "file" && node.type == "file") {
node.mozSetFileNameArray(value.fileList, value.fileList.length);
eventType = "input";
}
else if (value && typeof value.indexOf == "function" && node.options) {
Array.forEach(node.options, function(aOpt, aIx) {
aOpt.selected = value.indexOf(aIx) > -1;
// Only fire the event here if this wasn't selected by default
if (!aOpt.defaultSelected)
eventType = "change";
});
}
// NB: dispatching "change" events might have unintended side-effects
// Fire events for this node if applicable
if (eventType) {
let event = aDocument.createEvent("UIEvents");
event.initUIEvent(eventType, true, true, aDocument.defaultView, 0);
node.dispatchEvent(event);
}
}
}
@ -3563,6 +3635,7 @@ SessionStoreService.prototype = {
while (oState._closedWindows.length) {
let i = oState._closedWindows.length - 1;
if (oState._closedWindows[i]._shouldRestore) {
delete oState._closedWindows[i]._shouldRestore;
oState.windows.unshift(oState._closedWindows.pop());
}
else {
@ -4031,7 +4104,7 @@ SessionStoreService.prototype = {
let cookieHosts = {};
aTargetWinState.tabs.forEach(function(tab) {
tab.entries.forEach(function(entry) {
this._extractHostsForCookies(entry, cookieHosts, false)
this._extractHostsForCookiesFromEntry(entry, cookieHosts, false);
}, this);
}, this);
@ -4062,18 +4135,7 @@ SessionStoreService.prototype = {
* @returns the object's JSON representation
*/
_toJSONString: function sss_toJSONString(aJSObject) {
// We never want to save __lastSessionWindowID across sessions, but we do
// want it exported to consumers when running (eg. Private Browsing).
let internalKeys = INTERNAL_KEYS;
if (this._loadState == STATE_QUITTING) {
internalKeys = internalKeys.slice();
internalKeys.push("__lastSessionWindowID");
}
function exclude(key, value) {
// returning undefined results in the exclusion of that key
return internalKeys.indexOf(key) == -1 ? value : undefined;
}
return JSON.stringify(aJSObject, exclude);
return JSON.stringify(aJSObject);
},
_sendRestoreCompletedNotifications: function sss_sendRestoreCompletedNotifications() {

Просмотреть файл

@ -50,6 +50,8 @@ include $(topsrcdir)/config/rules.mk
_BROWSER_TEST_FILES = \
head.js \
browser_form_restore_events.js \
browser_form_restore_events_sample.html \
browser_248970_a.js \
browser_248970_b.js \
browser_248970_b_sample.html \
@ -98,8 +100,6 @@ _BROWSER_TEST_FILES = \
browser_465223.js \
browser_466937.js \
browser_466937_sample.html \
browser_476161.js \
browser_476161_sample.html \
browser_477657.js \
browser_480148.js \
browser_480893.js \

Просмотреть файл

@ -1,24 +0,0 @@
<!DOCTYPE html>
<title>Test for bug 476161</title>
<script>
document.addEventListener("input", function(aEvent) {
var inputEl = aEvent.originalTarget;
var changedEl = document.getElementById("changed");
changedEl.textContent += " " + inputEl.id;
}, false);
</script>
<h3>Text fields with changed text</h3>
<input type="text" id="modify1">
<input type="text" id="modify2" value="preset value">
<h3>Text fields with unchanged text</h3>
<input type="text" id="unchanged1">
<input type="text" id="unchanged2" value="preset value">
<h3>Changed field IDs</h3>
<div id="changed"></div>

Просмотреть файл

@ -50,7 +50,7 @@ function test() {
// Undo pinning
gBrowser.unpinTab(tab1);
is(tab1.linkedBrowser.__SS_data._tabStillLoading, true,
is(tab1.linkedBrowser.__SS_tabStillLoading, true,
"_tabStillLoading should be true.");
// Close and restore tab

Просмотреть файл

@ -35,33 +35,64 @@
* ***** END LICENSE BLOCK ***** */
function test() {
/** Test for Bug 476161 **/
/** Originally a test for Bug 476161, but then expanded to include all input types in bug 640136 **/
waitForExplicitFinish();
let file = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("TmpD", Components.interfaces.nsIFile);
let testURL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser/browser_476161_sample.html";
"browser/components/sessionstore/test/browser/browser_form_restore_events_sample.html";
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
let doc = tab.linkedBrowser.contentDocument;
doc.getElementById("modify1").value += Math.random();
doc.getElementById("modify2").value += " " + Date.now();
// text fields
doc.getElementById("modify01").value += Math.random();
doc.getElementById("modify02").value += " " + Date.now();
// textareas
doc.getElementById("modify03").value += Math.random();
doc.getElementById("modify04").value += " " + Date.now();
// file
doc.getElementById("modify05").value = file.path;
// select
doc.getElementById("modify06").selectedIndex = 1;
var multipleChange = doc.getElementById("modify07");
Array.forEach(multipleChange.options, function(option) option.selected = true);
// checkbox
doc.getElementById("modify08").checked = true;
doc.getElementById("modify09").checked = false;
// radio
// select one then another in the same group - only last one should get event on restore
doc.getElementById("modify10").checked = true;
doc.getElementById("modify11").checked = true;
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener("load", function(aEvent) {
tab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
let doc = tab2.linkedBrowser.contentDocument;
let changed = doc.getElementById("changed").textContent.trim().split();
is(changed.sort().join(" "), "modify1 modify2",
"input events were only dispatched for modified text fields");
let inputFired = doc.getElementById("inputFired").textContent.trim().split();
let changeFired = doc.getElementById("changeFired").textContent.trim().split();
is(inputFired.sort().join(" "), "modify01 modify02 modify03 modify04 modify05",
"input events were only dispatched for modified input, textarea fields");
is(changeFired.sort().join(" "), "modify06 modify07 modify08 modify09 modify11",
"change events were only dispatched for modified select, checkbox, radio fields");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
}, true);
}, true);

Просмотреть файл

@ -0,0 +1,98 @@
<!DOCTYPE html>
<title>Test for form restore events (originally bug 476161)</title>
<script>
document.addEventListener("input", function(aEvent) {
var inputEl = aEvent.originalTarget;
var changedEl = document.getElementById("inputFired");
changedEl.textContent += " " + inputEl.id;
}, false);
document.addEventListener("change", function(aEvent) {
var inputEl = aEvent.originalTarget;
var changedEl = document.getElementById("changeFired");
changedEl.textContent += " " + inputEl.id;
}, false);
</script>
<!-- input events -->
<h3>Text fields with changed text</h3>
<input type="text" id="modify1">
<input type="text" id="modify2" value="preset value">
<input type="text" id="modify01">
<input type="text" id="modify02" value="preset value">
<h3>Text fields with unchanged text</h3>
<input type="text" id="unchanged1">
<input type="text" id="unchanged2" value="preset value">
<input type="text" id="unchanged01">
<input type="text" id="unchanged02" value="preset value">
<h3>Textarea with changed text</h3>
<textarea id="modify03"></textarea>
<textarea id="modify04">preset value</textarea>
<h3>Textarea with unchanged text</h3>
<textarea id="unchanged03"></textarea>
<textarea id="unchanged04">preset value</textarea>
<h3>file field with changed value</h3>
<input type="file" id="modify05">
<h3>file field with unchanged value</h3>
<input type="file" id="unchanged05">
<!-- change events -->
<h3>Select menu with changed selection</h3>
<select id="modify06">
<option value="one">one</option>
<option value="two">two</option>
<option value="three">three</option>
</select>
<h3>Select menu with unchanged selection (change event still fires)</h3>
<select id="unchanged06">
<option value="one">one</option>
<option value="two" selected>two</option>
<option value="three">three</option>
</select>
<h3>Multiple Select menu with changed selection</h3>
<select id="modify07" multiple>
<option value="one">one</option>
<option value="two" selected>two</option>
<option value="three">three</option>
</select>
<h3>Select menu with unchanged selection</h3>
<select id="unchanged07" multiple>
<option value="one">one</option>
<option value="two" selected>two</option>
<option value="three" selected>three</option>
</select>
<h3>checkbox with changed value</h3>
<input type="checkbox" id="modify08">
<input type="checkbox" id="modify09" checked>
<h3>checkbox with unchanged value</h3>
<input type="checkbox" id="unchanged08">
<input type="checkbox" id="unchanged09" checked>
<h3>radio with changed value</h3>
<input type="radio" id="modify10" name="group">Radio 1</input>
<input type="radio" id="modify11" name="group">Radio 2</input>
<input type="radio" id="modify12" name="group" checked>Radio 3</input>
<h3>radio with unchanged value</h3>
<input type="radio" id="unchanged10" name="group2">Radio 4</input>
<input type="radio" id="unchanged11" name="group2">Radio 5</input>
<input type="radio" id="unchanged12" name="group2" checked>Radio 6</input>
<h3>Changed field IDs</h3>
<div id="changed"></div>
<div id="inputFired"></div>
<div id="changeFired"></div>

Просмотреть файл

@ -2096,6 +2096,7 @@ nsEventStateManager::GenerateDragGesture(nsPresContext* aPresContext,
if (!targetContent)
return;
sLastDragOverFrame = nsnull;
nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
// get the widget from the target frame
@ -3422,6 +3423,7 @@ nsEventStateManager::PostHandleEvent(nsPresContext* aPresContext,
targetContent, &status);
}
}
sLastDragOverFrame = nsnull;
ClearGlobalActiveContent(this);
break;
}