зеркало из https://github.com/mozilla/pjs.git
Merge m-c --> cedar
This commit is contained in:
Коммит
bdb986dad1
|
@ -50,19 +50,7 @@
|
|||
role: ROLE_ENTRY,
|
||||
children: [
|
||||
{
|
||||
role: ROLE_TEXT_LEAF // hello1 text
|
||||
},
|
||||
{
|
||||
role: ROLE_WHITESPACE
|
||||
},
|
||||
{
|
||||
role: ROLE_TEXT_LEAF, // hello2 text
|
||||
},
|
||||
{
|
||||
role: ROLE_WHITESPACE
|
||||
},
|
||||
{
|
||||
role: ROLE_TEXT_LEAF, // whitepsace text
|
||||
role: ROLE_TEXT_LEAF // hello1\nhello2 text
|
||||
},
|
||||
{
|
||||
role: ROLE_WHITESPACE
|
||||
|
|
|
@ -73,6 +73,8 @@ pref("extensions.blocklist.level", 2);
|
|||
pref("extensions.blocklist.url", "https://addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
|
||||
pref("extensions.blocklist.detailsURL", "https://www.mozilla.com/%LOCALE%/blocklist/");
|
||||
|
||||
pref("extensions.update.autoUpdateDefault", true);
|
||||
|
||||
// Dictionary download preference
|
||||
pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/%APP%/dictionaries/");
|
||||
|
||||
|
|
|
@ -136,6 +136,20 @@ let TabView = {
|
|||
else
|
||||
this.show();
|
||||
},
|
||||
|
||||
getActiveGroupName: function Tabview_getActiveGroupName() {
|
||||
// We get the active group this way, instead of querying
|
||||
// GroupItems.getActiveGroupItem() because the tabSelect event
|
||||
// will not have happened by the time the browser tries to
|
||||
// update the title.
|
||||
let activeTab = window.gBrowser.selectedTab;
|
||||
if (activeTab.tabItem && activeTab.tabItem.parent){
|
||||
let groupName = activeTab.tabItem.parent.getTitle();
|
||||
if (groupName)
|
||||
return groupName;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// ----------
|
||||
updateContextMenu: function(tab, popup) {
|
||||
|
|
|
@ -441,7 +441,11 @@
|
|||
<hbox id="appmenu-button-container" align="start">
|
||||
<button id="appmenu-button"
|
||||
type="menu"
|
||||
#ifdef XP_WIN
|
||||
label="&brandShortName;"
|
||||
#else
|
||||
label="&appMenuButton.label;"
|
||||
#endif
|
||||
style="-moz-user-focus: ignore;">
|
||||
<menupopup id="appmenu-popup"
|
||||
onpopupshowing="updateEditUIVisibility();">
|
||||
|
|
|
@ -733,6 +733,12 @@
|
|||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (window.TabView) {
|
||||
let groupName = TabView.getActiveGroupName();
|
||||
if (groupName)
|
||||
newTitle = groupName + sep + newTitle;
|
||||
}
|
||||
|
||||
return newTitle;
|
||||
]]>
|
||||
</body>
|
||||
|
@ -745,7 +751,8 @@
|
|||
// ToDo: this will be removed when we gain ability to draw to the menu bar.
|
||||
// Bug 586175
|
||||
this.ownerDocument.title = TabView.windowTitle;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
|
||||
}
|
||||
]]>
|
||||
|
|
|
@ -882,6 +882,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
|||
addAppTab: function GroupItem_addAppTab(xulTab) {
|
||||
let self = this;
|
||||
|
||||
// add the icon
|
||||
let icon = xulTab.image || Utils.defaultFaviconURL;
|
||||
let $appTab = iQ("<img>")
|
||||
.addClass("appTabIcon")
|
||||
|
@ -897,6 +898,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
|||
UI.goToTab(iQ(this).data("xulTab"));
|
||||
});
|
||||
|
||||
// adjust the tray
|
||||
let columnWidth = $appTab.width();
|
||||
if (parseInt(this.$appTabTray.css("width")) != columnWidth) {
|
||||
this.$appTabTray.css({width: columnWidth});
|
||||
|
@ -904,6 +906,25 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
|||
}
|
||||
},
|
||||
|
||||
// ----------
|
||||
// Removes the given xul:tab as an app tab in this group's apptab tray
|
||||
removeAppTab: function GroupItem_removeAppTab(xulTab) {
|
||||
// remove the icon
|
||||
iQ(".appTabIcon", this.$appTabTray).each(function(icon) {
|
||||
let $icon = iQ(icon);
|
||||
if ($icon.data("xulTab") != xulTab)
|
||||
return;
|
||||
|
||||
$icon.remove();
|
||||
});
|
||||
|
||||
// adjust the tray
|
||||
if (!iQ(".appTabIcon", this.$appTabTray).length) {
|
||||
this.$appTabTray.css({width: 0});
|
||||
this.arrange();
|
||||
}
|
||||
},
|
||||
|
||||
// ----------
|
||||
// Function: hideExpandControl
|
||||
// Hide the control which expands a stacked groupItem into a quick-look view.
|
||||
|
@ -1502,12 +1523,14 @@ let GroupItems = {
|
|||
// ----------
|
||||
// Function: uninit
|
||||
uninit : function GroupItems_uninit () {
|
||||
// call our cleanup functions
|
||||
this._cleanupFunctions.forEach(function(func) {
|
||||
func();
|
||||
});
|
||||
|
||||
this._cleanupFunctions = [];
|
||||
|
||||
// additional clean up
|
||||
this.groupItems = null;
|
||||
},
|
||||
|
||||
|
@ -1530,6 +1553,22 @@ let GroupItems = {
|
|||
});
|
||||
},
|
||||
|
||||
// ----------
|
||||
// when a tab becomes pinned, add it to the app tab tray in all groups
|
||||
handleTabPin: function GroupItems_handleTabPin(xulTab) {
|
||||
this.groupItems.forEach(function(groupItem) {
|
||||
groupItem.addAppTab(xulTab);
|
||||
});
|
||||
},
|
||||
|
||||
// ----------
|
||||
// when a tab becomes unpinned, remove it from the app tab tray in all groups
|
||||
handleTabUnpin: function GroupItems_handleTabUnpin(xulTab) {
|
||||
this.groupItems.forEach(function(groupItem) {
|
||||
groupItem.removeAppTab(xulTab);
|
||||
});
|
||||
},
|
||||
|
||||
// ----------
|
||||
// Function: getNextID
|
||||
// Returns the next unused groupItem ID.
|
||||
|
|
|
@ -831,8 +831,8 @@ let TabItems = {
|
|||
unlink: function TabItems_unlink(tab) {
|
||||
try {
|
||||
Utils.assertThrow(tab, "tab");
|
||||
Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
|
||||
Utils.assertThrow(tab.tabItem, "should already be linked");
|
||||
// note that it's ok to unlink an app tab; see .handleTabUnpin
|
||||
|
||||
this.unregister(tab.tabItem);
|
||||
tab.tabItem._sendToSubscribers("close");
|
||||
|
@ -851,6 +851,19 @@ let TabItems = {
|
|||
}
|
||||
},
|
||||
|
||||
// ----------
|
||||
// when a tab becomes pinned, destroy its TabItem
|
||||
handleTabPin: function TabItems_handleTabPin(xulTab) {
|
||||
this.unlink(xulTab);
|
||||
},
|
||||
|
||||
// ----------
|
||||
// when a tab becomes unpinned, create a TabItem for it
|
||||
handleTabUnpin: function TabItems_handleTabUnpin(xulTab) {
|
||||
this.link(xulTab);
|
||||
this.update(xulTab);
|
||||
},
|
||||
|
||||
// ----------
|
||||
// Function: heartbeat
|
||||
// Allows us to spreadout update calls over a period of time.
|
||||
|
|
|
@ -83,6 +83,10 @@ let UI = {
|
|||
// Keeps track of event listeners added to the AllTabs object.
|
||||
_eventListeners: {},
|
||||
|
||||
// Variable: _cleanupFunctions
|
||||
// An array of functions to be called at uninit time
|
||||
_cleanupFunctions: [],
|
||||
|
||||
// ----------
|
||||
// Function: init
|
||||
// Must be called after the object is created.
|
||||
|
@ -233,6 +237,14 @@ let UI = {
|
|||
},
|
||||
|
||||
uninit: function UI_uninit() {
|
||||
// call our cleanup functions
|
||||
this._cleanupFunctions.forEach(function(func) {
|
||||
func();
|
||||
});
|
||||
|
||||
this._cleanupFunctions = [];
|
||||
|
||||
// additional clean up
|
||||
TabItems.uninit();
|
||||
GroupItems.uninit();
|
||||
Storage.uninit();
|
||||
|
@ -459,6 +471,28 @@ let UI = {
|
|||
|
||||
for (let name in this._eventListeners)
|
||||
AllTabs.register(name, this._eventListeners[name]);
|
||||
|
||||
// Start watching for tab pin events, and set up our uninit for same.
|
||||
function handleTabPin(event) {
|
||||
TabItems.handleTabPin(event.originalTarget);
|
||||
GroupItems.handleTabPin(event.originalTarget);
|
||||
}
|
||||
|
||||
gBrowser.tabContainer.addEventListener("TabPinned", handleTabPin, false);
|
||||
this._cleanupFunctions.push(function() {
|
||||
gBrowser.tabContainer.removeEventListener("TabPinned", handleTabPin, false);
|
||||
});
|
||||
|
||||
// Start watching for tab unpin events, and set up our uninit for same.
|
||||
function handleTabUnpin(event) {
|
||||
TabItems.handleTabUnpin(event.originalTarget);
|
||||
GroupItems.handleTabUnpin(event.originalTarget);
|
||||
}
|
||||
|
||||
gBrowser.tabContainer.addEventListener("TabUnpinned", handleTabUnpin, false);
|
||||
this._cleanupFunctions.push(function() {
|
||||
gBrowser.tabContainer.removeEventListener("TabUnpinned", handleTabUnpin, false);
|
||||
});
|
||||
},
|
||||
|
||||
// ----------
|
||||
|
@ -582,7 +616,7 @@ let UI = {
|
|||
|
||||
function getClosestTabBy(norm) {
|
||||
var centers =
|
||||
[[item.bounds.center(), item]
|
||||
[[item.bounds.center(), item]
|
||||
for each(item in TabItems.getItems()) if (!item.parent || !item.parent.hidden)];
|
||||
var myCenter = self.getActiveTab().bounds.center();
|
||||
var matches = centers
|
||||
|
|
|
@ -52,38 +52,77 @@ function onTabViewWindowLoaded() {
|
|||
// establish initial state
|
||||
is(contentWindow.GroupItems.groupItems.length, 1, "we start with one group (the default)");
|
||||
is(gBrowser.tabs.length, 1, "we start with one tab");
|
||||
let originalTab = gBrowser.tabs[0];
|
||||
|
||||
// create an app tab
|
||||
let appXulTab = gBrowser.loadOneTab("about:blank");
|
||||
gBrowser.pinTab(appXulTab);
|
||||
is(gBrowser.tabs.length, 2, "we now have two tabs");
|
||||
|
||||
// Create a group
|
||||
// create a group
|
||||
let box = new contentWindow.Rect(20, 20, 180, 180);
|
||||
let groupItem = new contentWindow.GroupItem([], { bounds: box });
|
||||
let groupItemOne = new contentWindow.GroupItem([], { bounds: box, title: "test1" });
|
||||
is(contentWindow.GroupItems.groupItems.length, 2, "we now have two groups");
|
||||
contentWindow.GroupItems.setActiveGroupItem(groupItemOne);
|
||||
|
||||
// create a tab
|
||||
let xulTab = gBrowser.loadOneTab("about:blank");
|
||||
is(gBrowser.tabs.length, 2, "we now have two tabs");
|
||||
is(groupItemOne._children.length, 1, "the new tab was added to the group");
|
||||
|
||||
// make sure the group has no app tabs
|
||||
let appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
|
||||
is(appTabIcons.length, 0, "there are no app tab icons");
|
||||
|
||||
// pin the tab, make sure the TabItem goes away and the icon comes on
|
||||
gBrowser.pinTab(xulTab);
|
||||
|
||||
is(groupItemOne._children.length, 0, "the app tab's TabItem was removed from the group");
|
||||
|
||||
appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
|
||||
is(appTabIcons.length, 1, "there's now one app tab icon");
|
||||
|
||||
// create a second group and make sure it gets the icon too
|
||||
box.offset(box.width + 20, 0);
|
||||
let groupItemTwo = new contentWindow.GroupItem([], { bounds: box, title: "test2" });
|
||||
is(contentWindow.GroupItems.groupItems.length, 3, "we now have three groups");
|
||||
appTabIcons = groupItemTwo.container.getElementsByClassName("appTabIcon");
|
||||
is(appTabIcons.length, 1, "there's an app tab icon in the second group");
|
||||
|
||||
// unpin the tab, make sure the icon goes away and the TabItem comes on
|
||||
gBrowser.unpinTab(xulTab);
|
||||
|
||||
is(groupItemOne._children.length, 1, "the app tab's TabItem is back");
|
||||
|
||||
appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
|
||||
is(appTabIcons.length, 0, "the icon is gone from group one");
|
||||
|
||||
appTabIcons = groupItemTwo.container.getElementsByClassName("appTabIcon");
|
||||
is(appTabIcons.length, 0, "the icon is gone from group 2");
|
||||
|
||||
// pin the tab again
|
||||
gBrowser.pinTab(xulTab);
|
||||
|
||||
// close the second group
|
||||
groupItemTwo.close();
|
||||
|
||||
// find app tab in group and hit it
|
||||
let onTabViewHidden = function() {
|
||||
window.removeEventListener("tabviewhidden", onTabViewHidden, false);
|
||||
ok(!TabView.isVisible(), "Tab View is hidden because we clicked on the app tab");
|
||||
|
||||
// clean up
|
||||
gBrowser.unpinTab(appXulTab);
|
||||
gBrowser.removeTab(appXulTab);
|
||||
gBrowser.selectedTab = originalTab;
|
||||
|
||||
gBrowser.unpinTab(xulTab);
|
||||
gBrowser.removeTab(xulTab);
|
||||
is(gBrowser.tabs.length, 1, "we finish with one tab");
|
||||
|
||||
groupItem.close();
|
||||
groupItemOne.close();
|
||||
is(contentWindow.GroupItems.groupItems.length, 1, "we finish with one group");
|
||||
|
||||
ok(!TabView.isVisible(), "Tab View is not visible");
|
||||
ok(!TabView.isVisible(), "we finish with Tab View not visible");
|
||||
|
||||
finish();
|
||||
};
|
||||
|
||||
window.addEventListener("tabviewhidden", onTabViewHidden, false);
|
||||
|
||||
let appTabButtons = groupItem.$appTabTray[0].getElementsByTagName("img");
|
||||
ok(appTabButtons.length == 1, "there is one app tab button");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, appTabButtons[0], contentWindow);
|
||||
appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, appTabIcons[0], contentWindow);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,13 @@
|
|||
%globalDTD;
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
#ifdef XP_WIN
|
||||
<!ENTITY basePBMenu.label "<span class='appMenuButton'>&brandShortName;</span><span class='toolsMenu'>&toolsMenu.label;</span>">
|
||||
#elifdef XP_MACOSX
|
||||
<!ENTITY basePBMenu.label "&toolsMenu.label;">
|
||||
#else
|
||||
<!ENTITY basePBMenu.label "<span class='appMenuButton'>&appMenuButton.label;</span><span class='toolsMenu'>&toolsMenu.label;</span>">
|
||||
#endif
|
||||
<!ENTITY % privatebrowsingpageDTD SYSTEM "chrome://browser/locale/aboutPrivateBrowsing.dtd">
|
||||
%privatebrowsingpageDTD;
|
||||
]>
|
||||
|
@ -58,6 +65,12 @@
|
|||
body.private .showNormal {
|
||||
display: none;
|
||||
}
|
||||
body.appMenuButtonVisible .toolsMenu {
|
||||
display: none;
|
||||
}
|
||||
body.appMenuButtonInvisible .appMenuButton {
|
||||
display: none;
|
||||
}
|
||||
]]></style>
|
||||
<script type="application/javascript;version=1.7"><![CDATA[
|
||||
const Cc = Components.classes;
|
||||
|
@ -113,6 +126,13 @@
|
|||
let moreInfoLink = document.getElementById("moreInfoLink");
|
||||
if (moreInfoLink)
|
||||
moreInfoLink.setAttribute("href", moreInfoURL + "private-browsing");
|
||||
|
||||
// Show the correct menu structure based on whether the App Menu button is
|
||||
// shown or not.
|
||||
var menuBar = mainWindow.document.getElementById("toolbar-menubar");
|
||||
var appMenuButtonIsVisible = menuBar.getAttribute("autohide") == "true";
|
||||
document.body.classList.add(appMenuButtonIsVisible ? "appMenuButtonVisible" :
|
||||
"appMenuButtonInvisible");
|
||||
}, false);
|
||||
|
||||
function togglePrivateBrowsing() {
|
||||
|
@ -157,8 +177,8 @@
|
|||
|
||||
<!-- Footer -->
|
||||
<div id="footerDesc">
|
||||
<p id="footerText" class="showPrivate">&privatebrowsingpage.howToStop;</p>
|
||||
<p id="footerTextNormal" class="showNormal">&privatebrowsingpage.howToStart;</p>
|
||||
<p id="footerText" class="showPrivate">&privatebrowsingpage.howToStop2;</p>
|
||||
<p id="footerTextNormal" class="showNormal">&privatebrowsingpage.howToStart2;</p>
|
||||
</div>
|
||||
|
||||
<!-- More Info -->
|
||||
|
|
|
@ -1 +1 @@
|
|||
4.0b6pre
|
||||
4.0b7pre
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
<!ENTITY privatebrowsingpage.startPrivateBrowsing.label "Start Private Browsing">
|
||||
<!ENTITY privatebrowsingpage.startPrivateBrowsing.accesskey "P">
|
||||
|
||||
<!ENTITY privatebrowsingpage.howToStop "To stop Private Browsing, select &toolsMenu.label; > &privateBrowsingCmd.stop.label;, or close &brandShortName;.">
|
||||
<!ENTITY privatebrowsingpage.howToStart "To start Private Browsing, you can also select &toolsMenu.label; > &privateBrowsingCmd.start.label;.">
|
||||
<!-- LOCALIZATION NOTE (privatebrowsingpage.howToStop2): please leave &basePBMenu.label; intact in the translation -->
|
||||
<!-- LOCALIZATION NOTE (privatebrowsingpage.howToStart2): please leave &basePBMenu.label; intact in the translation -->
|
||||
<!ENTITY privatebrowsingpage.howToStop2 "To stop Private Browsing, select &basePBMenu.label; > &privateBrowsingCmd.stop.label;, or close &brandShortName;.">
|
||||
<!ENTITY privatebrowsingpage.howToStart2 "To start Private Browsing, you can also select &basePBMenu.label; > &privateBrowsingCmd.start.label;.">
|
||||
|
||||
<!ENTITY privatebrowsingpage.moreInfo "While this computer won't have a record of your browsing history, your internet service provider or employer can still track the pages you visit.">
|
||||
<!ENTITY privatebrowsingpage.learnMore "Learn More">
|
||||
|
|
|
@ -133,6 +133,7 @@ can reach it easily. -->
|
|||
<!ENTITY bookmarksItem.title "Bookmarks">
|
||||
|
||||
<!-- Toolbar items -->
|
||||
<!ENTITY appMenuButton.label "Menu">
|
||||
<!ENTITY homeButton.label "Home">
|
||||
<!ENTITY tabViewButton2.label "Tab Groups">
|
||||
<!ENTITY tabViewButton2.tooltip "Group Your Tabs">
|
||||
|
|
|
@ -1545,17 +1545,6 @@ statusbarpanel#statusbar-display {
|
|||
-moz-image-region: auto;
|
||||
}
|
||||
|
||||
#TabsToolbar > #new-tab-button,
|
||||
#TabsToolbar > #wrapper-new-tab-button > #new-tab-button {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
#TabsToolbar > #new-tab-button > .toolbarbutton-icon,
|
||||
#TabsToolbar > #wrapper-new-tab-button > #new-tab-button > .toolbarbutton-icon {
|
||||
margin-top: -2px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
/* Tabstrip close button */
|
||||
.tabs-closebutton {
|
||||
list-style-image: url("moz-icon://stock/gtk-close?size=menu");
|
||||
|
@ -1585,6 +1574,18 @@ statusbarpanel#statusbar-display {
|
|||
-moz-transition: none;
|
||||
}
|
||||
|
||||
#TabsToolbar > toolbarbutton > .toolbarbutton-icon,
|
||||
#TabsToolbar > toolbarbutton > .toolbarbutton-menu-dropmarker,
|
||||
#TabsToolbar > toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon,
|
||||
#TabsToolbar > toolbarpaletteitem > toolbarbutton > .toolbarbutton-menu-dropmarker,
|
||||
#TabsToolbar > #bookmarks-menu-button-container > #bookmarks-menu-button > .toolbarbutton-icon,
|
||||
#TabsToolbar > #bookmarks-menu-button-container > #bookmarks-menu-button > .toolbarbutton-menu-dropmarker,
|
||||
#TabsToolbar > toolbarpaletteitem > #bookmarks-menu-button-container > #bookmarks-menu-button > .toolbarbutton-icon,
|
||||
#TabsToolbar > toolbarpaletteitem > #bookmarks-menu-button-container > #bookmarks-menu-button > .toolbarbutton-menu-dropmarker {
|
||||
margin-top: -2px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
#alltabs-button > .toolbarbutton-icon {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/alltabs.png");
|
||||
margin: 2px 0 1px;
|
||||
|
|
|
@ -79,10 +79,10 @@ browser.jar:
|
|||
skin/classic/browser/tabview/edit-light.png (tabview/edit-light.png)
|
||||
skin/classic/browser/tabview/edit.png (tabview/edit.png)
|
||||
skin/classic/browser/tabview/new-tab.png (tabview/new-tab.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
skin/classic/browser/tabview/search.png (tabview/search.png)
|
||||
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
|
||||
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
|
||||
skin/classic/browser/tabview/search.png (tabview/search.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-16-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
|
|
@ -119,10 +119,10 @@ browser.jar:
|
|||
skin/classic/browser/tabview/edit-light.png (tabview/edit-light.png)
|
||||
skin/classic/browser/tabview/edit.png (tabview/edit.png)
|
||||
skin/classic/browser/tabview/new-tab.png (tabview/new-tab.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
skin/classic/browser/tabview/search.png (tabview/search.png)
|
||||
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
|
||||
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
|
||||
skin/classic/browser/tabview/search.png (tabview/search.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-16-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
|
|
@ -98,10 +98,10 @@ browser.jar:
|
|||
skin/classic/browser/tabview/edit-light.png (tabview/edit-light.png)
|
||||
skin/classic/browser/tabview/edit.png (tabview/edit.png)
|
||||
skin/classic/browser/tabview/new-tab.png (tabview/new-tab.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
skin/classic/browser/tabview/search.png (tabview/search.png)
|
||||
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
|
||||
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
|
||||
skin/classic/browser/tabview/search.png (tabview/search.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-16-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
@ -214,9 +214,10 @@ browser.jar:
|
|||
skin/classic/aero/browser/tabview/edit-light.png (tabview/edit-light.png)
|
||||
skin/classic/aero/browser/tabview/edit.png (tabview/edit.png)
|
||||
skin/classic/aero/browser/tabview/new-tab.png (tabview/new-tab.png)
|
||||
skin/classic/aero/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
skin/classic/aero/browser/tabview/search.png (tabview/search.png)
|
||||
skin/classic/aero/browser/tabview/stack-expander.png (tabview/stack-expander.png)
|
||||
skin/classic/aero/browser/tabview/tabview.png (tabview/tabview.png)
|
||||
skin/classic/aero/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/aero/browser/sync-16-throbber.png
|
||||
skin/classic/aero/browser/sync-16.png
|
||||
|
|
|
@ -10,4 +10,4 @@
|
|||
# hardcoded milestones in the tree from these two files.
|
||||
#--------------------------------------------------------
|
||||
|
||||
2.0b6pre
|
||||
2.0b7pre
|
||||
|
|
|
@ -385,7 +385,7 @@ nsGenericDOMDataNode::SetTextInternal(PRUint32 aOffset, PRUint32 aCount,
|
|||
delete [] to;
|
||||
}
|
||||
|
||||
SetBidiStatus();
|
||||
UpdateBidiStatus(aBuffer, aLength);
|
||||
|
||||
// Notify observers
|
||||
if (aNotify) {
|
||||
|
@ -1084,7 +1084,7 @@ nsGenericDOMDataNode::AppendTextTo(nsAString& aResult)
|
|||
mText.AppendTo(aResult);
|
||||
}
|
||||
|
||||
void nsGenericDOMDataNode::SetBidiStatus()
|
||||
void nsGenericDOMDataNode::UpdateBidiStatus(const PRUnichar* aBuffer, PRUint32 aLength)
|
||||
{
|
||||
nsIDocument *document = GetCurrentDoc();
|
||||
if (document && document->GetBidiEnabled()) {
|
||||
|
@ -1092,7 +1092,7 @@ void nsGenericDOMDataNode::SetBidiStatus()
|
|||
return;
|
||||
}
|
||||
|
||||
mText.SetBidiFlag();
|
||||
mText.UpdateBidiFlag(aBuffer, aLength);
|
||||
|
||||
if (document && mText.IsBidi()) {
|
||||
document->SetBidiEnabled();
|
||||
|
|
|
@ -358,7 +358,7 @@ protected:
|
|||
nsTextFragment mText;
|
||||
|
||||
private:
|
||||
void SetBidiStatus();
|
||||
void UpdateBidiStatus(const PRUnichar* aBuffer, PRUint32 aLength);
|
||||
|
||||
already_AddRefed<nsIAtom> GetCurrentValueAtom();
|
||||
};
|
||||
|
|
|
@ -1750,3 +1750,4 @@ GK_ATOM(_moz_windows_classic, "-moz-windows-classic")
|
|||
GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
|
||||
GK_ATOM(_moz_maemo_classic, "-moz-maemo-classic")
|
||||
GK_ATOM(_moz_menubar_drag, "-moz-menubar-drag")
|
||||
GK_ATOM(_moz_device_pixel_ratio, "-moz-device-pixel-ratio")
|
||||
|
|
|
@ -372,11 +372,11 @@ nsTextFragment::Append(const PRUnichar* aBuffer, PRUint32 aLength)
|
|||
// To save time we only do this when we really want to know, not during
|
||||
// every allocation
|
||||
void
|
||||
nsTextFragment::SetBidiFlag()
|
||||
nsTextFragment::UpdateBidiFlag(const PRUnichar* aBuffer, PRUint32 aLength)
|
||||
{
|
||||
if (mState.mIs2b && !mState.mIsBidi) {
|
||||
const PRUnichar* cp = m2b;
|
||||
const PRUnichar* end = cp + mState.mLength;
|
||||
const PRUnichar* cp = aBuffer;
|
||||
const PRUnichar* end = cp + aLength;
|
||||
while (cp < end) {
|
||||
PRUnichar ch1 = *cp++;
|
||||
PRUint32 utf32Char = ch1;
|
||||
|
|
|
@ -112,7 +112,7 @@ public:
|
|||
/**
|
||||
* Return PR_TRUE if this fragment contains Bidi text
|
||||
* For performance reasons this flag is not set automatically, but
|
||||
* requires an explicit call to SetBidiFlag()
|
||||
* requires an explicit call to UpdateBidiFlag()
|
||||
*/
|
||||
PRBool IsBidi() const
|
||||
{
|
||||
|
@ -209,7 +209,7 @@ public:
|
|||
* Scan the contents of the fragment and turn on mState.mIsBidi if it
|
||||
* includes any Bidi characters.
|
||||
*/
|
||||
void SetBidiFlag();
|
||||
void UpdateBidiFlag(const PRUnichar* aBuffer, PRUint32 aLength);
|
||||
|
||||
struct FragmentBits {
|
||||
// PRUint32 to ensure that the values are unsigned, because we
|
||||
|
|
|
@ -185,14 +185,15 @@ public:
|
|||
gfxASurface* GetPrintSurface() { return mPrintSurface; }
|
||||
|
||||
// Dispatch events
|
||||
nsresult DispatchSimpleEvent(const nsAString& aName);
|
||||
nsresult DispatchProgressEvent(const nsAString& aName);
|
||||
nsresult DispatchAsyncSimpleEvent(const nsAString& aName);
|
||||
nsresult DispatchAsyncProgressEvent(const nsAString& aName);
|
||||
nsresult DispatchEvent(const nsAString& aName);
|
||||
nsresult DispatchAsyncEvent(const nsAString& aName);
|
||||
nsresult DispatchAudioAvailableEvent(float* aFrameBuffer,
|
||||
PRUint32 aFrameBufferLength,
|
||||
PRUint64 aTime);
|
||||
|
||||
// Dispatch events that were raised while in the bfcache
|
||||
nsresult DispatchPendingMediaEvents();
|
||||
|
||||
// Called by the decoder when some data has been downloaded or
|
||||
// buffering/seeking has ended. aNextFrameAvailable is true when
|
||||
// the data for the next frame is available. This method will
|
||||
|
@ -534,6 +535,10 @@ protected:
|
|||
// we're bound to when loading starts.
|
||||
nsCOMPtr<nsIDocument> mLoadBlockedDoc;
|
||||
|
||||
// Contains names of events that have been raised while in the bfcache.
|
||||
// These events get re-dispatched when the bfcache is exited.
|
||||
nsTArray<nsString> mPendingEvents;
|
||||
|
||||
// Media loading flags. See:
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/#video)
|
||||
nsMediaNetworkState mNetworkState;
|
||||
|
|
|
@ -2532,6 +2532,10 @@ nsHTMLInputElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
|
|||
void
|
||||
nsHTMLInputElement::HandleTypeChange(PRUint8 aNewType)
|
||||
{
|
||||
ValueModeType aOldValueMode = GetValueMode();
|
||||
nsAutoString aOldValue;
|
||||
GetValue(aOldValue);
|
||||
|
||||
// Only single line text inputs have a text editor state.
|
||||
PRBool isNewTypeSingleLine =
|
||||
IsSingleLineTextControlInternal(PR_FALSE, aNewType);
|
||||
|
@ -2547,15 +2551,40 @@ nsHTMLInputElement::HandleTypeChange(PRUint8 aNewType)
|
|||
|
||||
mType = aNewType;
|
||||
|
||||
// We have to sanitize the value when the type changes.
|
||||
// We could check that we are not changing to a type with the same
|
||||
// sanitization algorithm than the current one but that would be bad for
|
||||
// readability and not so helpful.
|
||||
if (IsSingleLineTextControlInternal(PR_FALSE, mType)) {
|
||||
nsAutoString value;
|
||||
GetValue(value);
|
||||
// SetValueInternal is going to sanitize the value.
|
||||
SetValueInternal(value, PR_FALSE, PR_FALSE);
|
||||
/**
|
||||
* The following code is trying to reproduce the algorithm described here:
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/complete.html#input-type-change
|
||||
*/
|
||||
switch (GetValueMode()) {
|
||||
case VALUE_MODE_DEFAULT:
|
||||
case VALUE_MODE_DEFAULT_ON:
|
||||
// If the previous value mode was value, we need to set the value content
|
||||
// attribute to the previous value.
|
||||
// There is no value sanitizing algorithm for elements in this mode.
|
||||
if (aOldValueMode == VALUE_MODE_VALUE && !aOldValue.IsEmpty()) {
|
||||
SetAttr(kNameSpaceID_None, nsGkAtoms::value, aOldValue, PR_TRUE);
|
||||
}
|
||||
break;
|
||||
case VALUE_MODE_VALUE:
|
||||
// If the previous value mode wasn't value, we have to set the value to
|
||||
// the value content attribute.
|
||||
// SetValueInternal is going to sanitize the value.
|
||||
{
|
||||
nsAutoString value;
|
||||
if (aOldValueMode != VALUE_MODE_VALUE) {
|
||||
GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
|
||||
} else {
|
||||
// We get the current value so we can sanitize it.
|
||||
GetValue(value);
|
||||
}
|
||||
SetValueInternal(value, PR_FALSE, PR_FALSE);
|
||||
}
|
||||
break;
|
||||
case VALUE_MODE_FILENAME:
|
||||
default:
|
||||
// We don't care about the value.
|
||||
// There is no value sanitizing algorithm for elements in this mode.
|
||||
break;
|
||||
}
|
||||
|
||||
// Do not notify, it will be done after if needed.
|
||||
|
|
|
@ -59,8 +59,8 @@
|
|||
#include "nsThreadUtils.h"
|
||||
#include "nsIThreadInternal.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsFrameManager.h"
|
||||
|
||||
#include "nsFrameManager.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsIXPConnect.h"
|
||||
#include "jsapi.h"
|
||||
|
@ -70,7 +70,6 @@
|
|||
|
||||
#include "nsEventDispatcher.h"
|
||||
#include "nsIDOMDocumentEvent.h"
|
||||
#include "nsIDOMProgressEvent.h"
|
||||
#include "nsMediaError.h"
|
||||
#include "nsICategoryManager.h"
|
||||
#include "nsCharSeparatedTokenizer.h"
|
||||
|
@ -192,11 +191,10 @@ class nsAsyncEventRunner : public nsMediaEvent
|
|||
{
|
||||
private:
|
||||
nsString mName;
|
||||
PRPackedBool mProgress;
|
||||
|
||||
public:
|
||||
nsAsyncEventRunner(const nsAString& aName, nsHTMLMediaElement* aElement, PRBool aProgress) :
|
||||
nsMediaEvent(aElement), mName(aName), mProgress(aProgress)
|
||||
nsAsyncEventRunner(const nsAString& aName, nsHTMLMediaElement* aElement) :
|
||||
nsMediaEvent(aElement), mName(aName)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -206,9 +204,7 @@ public:
|
|||
if (IsCancelled())
|
||||
return NS_OK;
|
||||
|
||||
return mProgress ?
|
||||
mElement->DispatchProgressEvent(mName) :
|
||||
mElement->DispatchSimpleEvent(mName);
|
||||
return mElement->DispatchEvent(mName);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -490,7 +486,7 @@ void nsHTMLMediaElement::AbortExistingLoads()
|
|||
if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
|
||||
mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
|
||||
{
|
||||
DispatchProgressEvent(NS_LITERAL_STRING("abort"));
|
||||
DispatchEvent(NS_LITERAL_STRING("abort"));
|
||||
}
|
||||
|
||||
mError = nsnull;
|
||||
|
@ -513,9 +509,9 @@ void nsHTMLMediaElement::AbortExistingLoads()
|
|||
// will now be reported as 0. The playback position was non-zero when
|
||||
// we destroyed the decoder, so fire a timeupdate event so that the
|
||||
// change will be reflected in the controls.
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
}
|
||||
DispatchSimpleEvent(NS_LITERAL_STRING("emptied"));
|
||||
DispatchEvent(NS_LITERAL_STRING("emptied"));
|
||||
}
|
||||
|
||||
// We may have changed mPaused, mAutoplaying, mNetworkState and other
|
||||
|
@ -531,7 +527,7 @@ void nsHTMLMediaElement::NoSupportedMediaSourceError()
|
|||
|
||||
mError = new nsMediaError(nsIDOMMediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
|
||||
mNetworkState = nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE;
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("error"));
|
||||
// This clears mDelayingLoadEvent, so AddRemoveSelfReference will be called
|
||||
ChangeDelayLoadStatus(PR_FALSE);
|
||||
}
|
||||
|
@ -649,7 +645,7 @@ void nsHTMLMediaElement::SelectResource()
|
|||
mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
|
||||
// Load event was delayed, and still is, so no need to call
|
||||
// AddRemoveSelfReference, since it must still be held
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
|
||||
|
||||
nsAutoString src;
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
|
@ -801,7 +797,7 @@ void nsHTMLMediaElement::SuspendLoad(nsIURI* aURI)
|
|||
{
|
||||
mLoadIsSuspended = PR_TRUE;
|
||||
mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("suspend"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
|
||||
ChangeDelayLoadStatus(PR_FALSE);
|
||||
}
|
||||
|
||||
|
@ -1037,7 +1033,7 @@ nsresult nsHTMLMediaElement::LoadWithChannel(nsIChannel *aChannel,
|
|||
return rv;
|
||||
}
|
||||
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1061,7 +1057,7 @@ NS_IMETHODIMP nsHTMLMediaElement::MozLoadFrom(nsIDOMHTMLMediaElement* aOther)
|
|||
return rv;
|
||||
}
|
||||
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1161,8 +1157,8 @@ NS_IMETHODIMP nsHTMLMediaElement::Pause()
|
|||
AddRemoveSelfReference();
|
||||
|
||||
if (!oldPaused) {
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("pause"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -1192,7 +1188,7 @@ NS_IMETHODIMP nsHTMLMediaElement::SetVolume(float aVolume)
|
|||
mAudioStream->SetVolume(mVolume);
|
||||
}
|
||||
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("volumechange"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1262,7 +1258,7 @@ NS_IMETHODIMP nsHTMLMediaElement::SetMuted(PRBool aMuted)
|
|||
mAudioStream->SetVolume(mMuted ? 0.0 : mVolume);
|
||||
}
|
||||
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("volumechange"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1385,15 +1381,15 @@ NS_IMETHODIMP nsHTMLMediaElement::Play()
|
|||
// seek to the effective start.
|
||||
// TODO: The playback rate must be set to the default playback rate.
|
||||
if (mPaused) {
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
|
||||
switch (mReadyState) {
|
||||
case nsIDOMHTMLMediaElement::HAVE_METADATA:
|
||||
case nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA:
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
|
||||
break;
|
||||
case nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA:
|
||||
case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("playing"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1949,8 +1945,8 @@ void nsHTMLMediaElement::MetadataLoaded(PRUint32 aChannels, PRUint32 aRate)
|
|||
mChannels = aChannels;
|
||||
mRate = aRate;
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("durationchange"));
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadedmetadata"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::FirstFrameLoaded(PRBool aResourceFullyLoaded)
|
||||
|
@ -1976,9 +1972,9 @@ void nsHTMLMediaElement::ResourceLoaded()
|
|||
AddRemoveSelfReference();
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
|
||||
// Ensure a progress event is dispatched at the end of download.
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
|
||||
// The download has stopped.
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("suspend"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::NetworkError()
|
||||
|
@ -2004,10 +2000,10 @@ void nsHTMLMediaElement::Error(PRUint16 aErrorCode)
|
|||
"Only use nsIDOMMediaError codes!");
|
||||
mError = new nsMediaError(aErrorCode);
|
||||
mBegun = PR_FALSE;
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("error"));
|
||||
if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
|
||||
mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("emptied"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
|
||||
} else {
|
||||
mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
|
||||
}
|
||||
|
@ -2021,20 +2017,20 @@ void nsHTMLMediaElement::PlaybackEnded()
|
|||
// We changed the state of IsPlaybackEnded which can affect AddRemoveSelfReference
|
||||
AddRemoveSelfReference();
|
||||
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("ended"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::SeekStarted()
|
||||
{
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeking"));
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
}
|
||||
|
||||
void nsHTMLMediaElement::SeekCompleted()
|
||||
{
|
||||
mPlayingBeforeSeek = PR_FALSE;
|
||||
SetPlayedOrSeeked(PR_TRUE);
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeked"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
|
||||
// We changed whether we're seeking so we need to AddRemoveSelfReference
|
||||
AddRemoveSelfReference();
|
||||
}
|
||||
|
@ -2044,7 +2040,7 @@ void nsHTMLMediaElement::DownloadSuspended()
|
|||
if (mBegun) {
|
||||
mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
|
||||
AddRemoveSelfReference();
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("suspend"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2059,7 +2055,7 @@ void nsHTMLMediaElement::DownloadResumed()
|
|||
void nsHTMLMediaElement::DownloadStalled()
|
||||
{
|
||||
if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
|
||||
DispatchAsyncProgressEvent(NS_LITERAL_STRING("stalled"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2082,7 +2078,7 @@ void nsHTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame)
|
|||
if (aNextFrame != NEXT_FRAME_AVAILABLE) {
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
|
||||
if (!mWaitingFired && aNextFrame == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
|
||||
mWaitingFired = PR_TRUE;
|
||||
}
|
||||
return;
|
||||
|
@ -2132,14 +2128,14 @@ void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
|||
// Handle raising of "waiting" event during seek (see 4.8.10.9)
|
||||
if (mPlayingBeforeSeek &&
|
||||
oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("waiting"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
|
||||
}
|
||||
|
||||
if (oldState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
|
||||
mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
|
||||
!mLoadedFirstFrame)
|
||||
{
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadeddata"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
|
||||
mLoadedFirstFrame = PR_TRUE;
|
||||
}
|
||||
|
||||
|
@ -2149,7 +2145,7 @@ void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
|||
|
||||
if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplay"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
|
||||
}
|
||||
|
||||
if (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
|
||||
|
@ -2159,12 +2155,12 @@ void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
|||
if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
IsPotentiallyPlaying()) {
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("playing"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
|
||||
}
|
||||
|
||||
if (oldState < nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA &&
|
||||
mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplaythrough"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("canplaythrough"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2187,7 +2183,7 @@ void nsHTMLMediaElement::NotifyAutoplayDataReady()
|
|||
SetPlayedOrSeeked(PR_TRUE);
|
||||
mDecoder->Play();
|
||||
}
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2244,11 +2240,18 @@ nsresult nsHTMLMediaElement::DispatchAudioAvailableEvent(float* aFrameBuffer,
|
|||
return target->DispatchEvent(event, &dummy);
|
||||
}
|
||||
|
||||
nsresult nsHTMLMediaElement::DispatchSimpleEvent(const nsAString& aName)
|
||||
nsresult nsHTMLMediaElement::DispatchEvent(const nsAString& aName)
|
||||
{
|
||||
LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching simple event %s", this,
|
||||
LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching event %s", this,
|
||||
NS_ConvertUTF16toUTF8(aName).get()));
|
||||
|
||||
// Save events that occur while in the bfcache. These will be dispatched
|
||||
// if the page comes out of the bfcache.
|
||||
if (mPausedForInactiveDocument) {
|
||||
mPendingEvents.AppendElement(aName);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return nsContentUtils::DispatchTrustedEvent(GetOwnerDoc(),
|
||||
static_cast<nsIContent*>(this),
|
||||
aName,
|
||||
|
@ -2256,53 +2259,28 @@ nsresult nsHTMLMediaElement::DispatchSimpleEvent(const nsAString& aName)
|
|||
PR_TRUE);
|
||||
}
|
||||
|
||||
nsresult nsHTMLMediaElement::DispatchAsyncSimpleEvent(const nsAString& aName)
|
||||
nsresult nsHTMLMediaElement::DispatchAsyncEvent(const nsAString& aName)
|
||||
{
|
||||
LOG_EVENT(PR_LOG_DEBUG, ("%p Queuing simple event %s", this, NS_ConvertUTF16toUTF8(aName).get()));
|
||||
LOG_EVENT(PR_LOG_DEBUG, ("%p Queuing event %s", this,
|
||||
NS_ConvertUTF16toUTF8(aName).get()));
|
||||
|
||||
nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this, PR_FALSE);
|
||||
nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsHTMLMediaElement::DispatchAsyncProgressEvent(const nsAString& aName)
|
||||
nsresult nsHTMLMediaElement::DispatchPendingMediaEvents()
|
||||
{
|
||||
LOG_EVENT(PR_LOG_DEBUG, ("%p Queuing progress event %s", this, NS_ConvertUTF16toUTF8(aName).get()));
|
||||
NS_ASSERTION(!mPausedForInactiveDocument,
|
||||
"Must not be in bfcache when dispatching pending media events");
|
||||
|
||||
nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this, PR_TRUE);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsHTMLMediaElement::DispatchProgressEvent(const nsAString& aName)
|
||||
{
|
||||
nsCOMPtr<nsIDOMDocumentEvent> docEvent(do_QueryInterface(GetOwnerDoc()));
|
||||
nsCOMPtr<nsIDOMEventTarget> target(do_QueryInterface(static_cast<nsIContent*>(this)));
|
||||
NS_ENSURE_TRUE(docEvent && target, NS_ERROR_INVALID_ARG);
|
||||
|
||||
nsCOMPtr<nsIDOMEvent> event;
|
||||
nsresult rv = docEvent->CreateEvent(NS_LITERAL_STRING("ProgressEvent"), getter_AddRefs(event));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIDOMProgressEvent> progressEvent(do_QueryInterface(event));
|
||||
NS_ENSURE_TRUE(progressEvent, NS_ERROR_FAILURE);
|
||||
|
||||
PRInt64 totalBytes = 0;
|
||||
PRUint64 downloadPosition = 0;
|
||||
if (mDecoder) {
|
||||
nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
|
||||
totalBytes = stats.mTotalBytes;
|
||||
downloadPosition = stats.mDownloadPosition;
|
||||
PRUint32 count = mPendingEvents.Length();
|
||||
for (PRUint32 i = 0; i < count; ++i) {
|
||||
DispatchAsyncEvent(mPendingEvents[i]);
|
||||
}
|
||||
rv = progressEvent->InitProgressEvent(aName, PR_TRUE, PR_TRUE,
|
||||
totalBytes >= 0, downloadPosition, totalBytes);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mPendingEvents.Clear();
|
||||
|
||||
LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching progress event %s", this,
|
||||
NS_ConvertUTF16toUTF8(aName).get()));
|
||||
|
||||
PRBool dummy;
|
||||
return target->DispatchEvent(event, &dummy);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PRBool nsHTMLMediaElement::IsPotentiallyPlaying() const
|
||||
|
@ -2355,6 +2333,7 @@ void nsHTMLMediaElement::NotifyOwnerDocumentActivityChanged()
|
|||
mDecoder->Suspend();
|
||||
} else {
|
||||
mDecoder->Resume(PR_FALSE);
|
||||
DispatchPendingMediaEvents();
|
||||
if (!mPaused && !mDecoder->IsEnded()) {
|
||||
mDecoder->Play();
|
||||
}
|
||||
|
@ -2574,7 +2553,9 @@ nsresult nsHTMLMediaElement::GetBuffered(nsIDOMTimeRanges** aBuffered)
|
|||
nsTimeRanges* ranges = new nsTimeRanges();
|
||||
NS_ADDREF(*aBuffered = ranges);
|
||||
if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA && mDecoder) {
|
||||
return mDecoder->GetBuffered(ranges);
|
||||
// If GetBuffered fails we ignore the error result and just return the
|
||||
// time ranges we found up till the error.
|
||||
mDecoder->GetBuffered(ranges);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -220,6 +220,7 @@ _TEST_FILES = \
|
|||
test_bug555840.html \
|
||||
test_bug561636.html \
|
||||
test_bug556013.html \
|
||||
test_bug590363.html \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TEST_FILES)
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=590363
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 590363</title>
|
||||
<script type="application/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=590363">Mozilla Bug 590363</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 590363 **/
|
||||
|
||||
var testData = [
|
||||
/* type to test | is the value reset when changing to file then reverting */
|
||||
[ "button", false ],
|
||||
[ "checkbox", false ],
|
||||
[ "hidden", false ],
|
||||
[ "reset", false ],
|
||||
[ "image", false ],
|
||||
[ "radio", false ],
|
||||
[ "submit", false ],
|
||||
[ "tel", true ],
|
||||
[ "text", true ],
|
||||
[ "url", true ],
|
||||
[ "email", true ],
|
||||
[ "search", true ],
|
||||
[ "password", true ],
|
||||
];
|
||||
|
||||
var length = testData.length;
|
||||
for (var i=0; i<length; ++i) {
|
||||
for (var j=0; j<length; ++j) {
|
||||
var e = document.createElement('input');
|
||||
e.type = testData[i][0];
|
||||
e.value = "foo";
|
||||
e.type = testData[j][0];
|
||||
is(e.value, "foo", ".value should still return the same value after " +
|
||||
"changing type from " + testData[i][0] + " to " + testData[j][0]);
|
||||
}
|
||||
}
|
||||
|
||||
// For type='file' .value doesn't behave the same way.
|
||||
// We are just going to check that we do not loose the value.
|
||||
for each (var data in testData) {
|
||||
var e = document.createElement('input');
|
||||
e.type = data[0];
|
||||
e.value = 'foo';
|
||||
e.type = 'file';
|
||||
e.type = data[0];
|
||||
|
||||
if (data[1]) {
|
||||
is(e.value, '', ".value should have been reset to the empty string after " +
|
||||
"changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
|
||||
} else {
|
||||
is(e.value, 'foo', ".value should still return the same value after " +
|
||||
"changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -145,6 +145,8 @@ void nsBuiltinDecoder::Shutdown()
|
|||
|
||||
mShuttingDown = PR_TRUE;
|
||||
|
||||
StopTimeUpdate();
|
||||
|
||||
// This changes the decoder state to SHUTDOWN and does other things
|
||||
// necessary to unblock the state machine thread if it's blocked, so
|
||||
// the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
|
||||
|
@ -359,7 +361,7 @@ void nsBuiltinDecoder::MetadataLoaded(PRUint32 aChannels,
|
|||
else if (mElement) {
|
||||
// Resource was loaded during metadata loading, when progress
|
||||
// events are being ignored. Fire the final progress event.
|
||||
mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
|
||||
mElement->DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
|
||||
}
|
||||
|
||||
// Only inform the element of FirstFrameLoaded if not doing a load() in order
|
||||
|
@ -387,6 +389,8 @@ void nsBuiltinDecoder::MetadataLoaded(PRUint32 aChannels,
|
|||
if (resourceIsLoaded) {
|
||||
ResourceLoaded();
|
||||
}
|
||||
|
||||
StartTimeUpdate();
|
||||
}
|
||||
|
||||
void nsBuiltinDecoder::ResourceLoaded()
|
||||
|
@ -773,7 +777,7 @@ void nsBuiltinDecoder::PlaybackPositionChanged()
|
|||
Invalidate();
|
||||
|
||||
if (mElement && lastTime != mCurrentTime) {
|
||||
mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
FireTimeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -788,7 +792,7 @@ void nsBuiltinDecoder::DurationChanged()
|
|||
|
||||
if (mElement && oldDuration != mDuration) {
|
||||
LOG(PR_LOG_DEBUG, ("%p duration changed to %lldms", this, mDuration));
|
||||
mElement->DispatchSimpleEvent(NS_LITERAL_STRING("durationchange"));
|
||||
mElement->DispatchEvent(NS_LITERAL_STRING("durationchange"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -295,7 +295,9 @@ public:
|
|||
virtual void UpdatePlaybackPosition(PRInt64 aTime) = 0;
|
||||
|
||||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered) = 0;
|
||||
|
||||
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) = 0;
|
||||
|
||||
// Causes the state machine to switch to buffering state, and to
|
||||
// immediately stop playback and buffer downloaded data. Must be called
|
||||
// with the decode monitor held. Called on the state machine thread and
|
||||
|
@ -436,6 +438,10 @@ class nsBuiltinDecoder : public nsMediaDecoder
|
|||
return mDecoderStateMachine->GetBuffered(aBuffered);
|
||||
}
|
||||
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
|
||||
return mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset);
|
||||
}
|
||||
|
||||
public:
|
||||
// Return the current state. Can be called on any thread. If called from
|
||||
// a non-main thread, the decoder monitor must be held.
|
||||
|
|
|
@ -463,6 +463,10 @@ public:
|
|||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered,
|
||||
PRInt64 aStartTime) = 0;
|
||||
|
||||
// Only used by nsWebMReader for now, so stub here rather than in every
|
||||
// reader than inherits from nsBuiltinDecoderReader.
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {}
|
||||
|
||||
protected:
|
||||
|
||||
// Pumps the decode until we reach frames/samples required to play at
|
||||
|
|
|
@ -240,6 +240,11 @@ public:
|
|||
return mReader->GetBuffered(aBuffered, mStartTime);
|
||||
}
|
||||
|
||||
void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// Returns PR_TRUE when there's decoded audio waiting to play.
|
||||
|
|
|
@ -68,12 +68,14 @@
|
|||
// Number of milliseconds of no data before a stall event is fired as defined by spec
|
||||
#define STALL_MS 3000
|
||||
|
||||
// Number of milliseconds between timeupdate events as defined by spec
|
||||
#define TIMEUPDATE_MS 250
|
||||
|
||||
nsMediaDecoder::nsMediaDecoder() :
|
||||
mElement(0),
|
||||
mRGBWidth(-1),
|
||||
mRGBHeight(-1),
|
||||
mProgressTime(),
|
||||
mDataTime(),
|
||||
mLastCurrentTime(0.0),
|
||||
mVideoUpdateLock(nsnull),
|
||||
mPixelAspectRatio(1.0),
|
||||
mFrameBufferLength(0),
|
||||
|
@ -200,7 +202,7 @@ void nsMediaDecoder::Progress(PRBool aTimer)
|
|||
now - mProgressTime >= TimeDuration::FromMilliseconds(PROGRESS_MS)) &&
|
||||
!mDataTime.IsNull() &&
|
||||
now - mDataTime <= TimeDuration::FromMilliseconds(PROGRESS_MS)) {
|
||||
mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
|
||||
mElement->DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
|
||||
mProgressTime = now;
|
||||
}
|
||||
|
||||
|
@ -235,6 +237,54 @@ nsresult nsMediaDecoder::StopProgress()
|
|||
return rv;
|
||||
}
|
||||
|
||||
static void TimeUpdateCallback(nsITimer* aTimer, void* aClosure)
|
||||
{
|
||||
nsMediaDecoder* decoder = static_cast<nsMediaDecoder*>(aClosure);
|
||||
decoder->FireTimeUpdate();
|
||||
}
|
||||
|
||||
void nsMediaDecoder::FireTimeUpdate()
|
||||
{
|
||||
if (!mElement)
|
||||
return;
|
||||
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
float time = GetCurrentTime();
|
||||
|
||||
// If TIMEUPDATE_MS has passed since the last timeupdate event fired and the time
|
||||
// has changed, fire a timeupdate event.
|
||||
if ((mTimeUpdateTime.IsNull() ||
|
||||
now - mTimeUpdateTime >= TimeDuration::FromMilliseconds(TIMEUPDATE_MS)) &&
|
||||
mLastCurrentTime != time) {
|
||||
mElement->DispatchEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
mTimeUpdateTime = now;
|
||||
mLastCurrentTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult nsMediaDecoder::StartTimeUpdate()
|
||||
{
|
||||
if (mTimeUpdateTimer)
|
||||
return NS_OK;
|
||||
|
||||
mTimeUpdateTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
return mTimeUpdateTimer->InitWithFuncCallback(TimeUpdateCallback,
|
||||
this,
|
||||
TIMEUPDATE_MS,
|
||||
nsITimer::TYPE_REPEATING_SLACK);
|
||||
}
|
||||
|
||||
nsresult nsMediaDecoder::StopTimeUpdate()
|
||||
{
|
||||
if (!mTimeUpdateTimer)
|
||||
return NS_OK;
|
||||
|
||||
nsresult rv = mTimeUpdateTimer->Cancel();
|
||||
mTimeUpdateTimer = nsnull;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void nsMediaDecoder::SetVideoData(const gfxIntSize& aSize,
|
||||
float aPixelAspectRatio,
|
||||
Image* aImage)
|
||||
|
|
|
@ -205,6 +205,10 @@ public:
|
|||
// than the result of downloaded data.
|
||||
virtual void Progress(PRBool aTimer);
|
||||
|
||||
// Fire timeupdate events if needed according to the time constraints
|
||||
// outlined in the specification.
|
||||
virtual void FireTimeUpdate();
|
||||
|
||||
// Called by nsMediaStream when the "cache suspended" status changes.
|
||||
// If nsMediaStream::IsSuspendedByCache returns true, then the decoder
|
||||
// should stop buffering or otherwise waiting for download progress and
|
||||
|
@ -220,6 +224,10 @@ public:
|
|||
// the result from OnStopRequest.
|
||||
virtual void NotifyDownloadEnded(nsresult aStatus) = 0;
|
||||
|
||||
// Called as data arrives on the stream and is read into the cache. Called
|
||||
// on the main thread only.
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) = 0;
|
||||
|
||||
// Cleanup internal data structures. Must be called on the main
|
||||
// thread by the owning object before that object disposes of this object.
|
||||
virtual void Shutdown();
|
||||
|
@ -285,6 +293,12 @@ protected:
|
|||
// Stop progress information timer.
|
||||
nsresult StopProgress();
|
||||
|
||||
// Start timer to send timeupdate event
|
||||
nsresult StartTimeUpdate();
|
||||
|
||||
// Stop timeupdate timer
|
||||
nsresult StopTimeUpdate();
|
||||
|
||||
// Ensures our media stream has been pinned.
|
||||
void PinForSeek();
|
||||
|
||||
|
@ -295,6 +309,9 @@ protected:
|
|||
// Timer used for updating progress events
|
||||
nsCOMPtr<nsITimer> mProgressTimer;
|
||||
|
||||
// Timer used for updating timeupdate events
|
||||
nsCOMPtr<nsITimer> mTimeUpdateTimer;
|
||||
|
||||
// This should only ever be accessed from the main thread.
|
||||
// It is set in Init and cleared in Shutdown when the element goes away.
|
||||
// The decoder does not add a reference the element.
|
||||
|
@ -309,6 +326,10 @@ protected:
|
|||
// main thread only.
|
||||
TimeStamp mProgressTime;
|
||||
|
||||
// Time that the last timeupdate event was fired. Read/Write from the
|
||||
// main thread only.
|
||||
TimeStamp mTimeUpdateTime;
|
||||
|
||||
// Time that data was last read from the media resource. Used for
|
||||
// computing if the download has stalled and to rate limit progress events
|
||||
// when data is arriving slower than PROGRESS_MS. A value of null indicates
|
||||
|
@ -316,6 +337,10 @@ protected:
|
|||
// more data is received. Read/Write from the main thread only.
|
||||
TimeStamp mDataTime;
|
||||
|
||||
// Media 'currentTime' value when the last timeupdate event occurred.
|
||||
// Read/Write from the main thread only.
|
||||
float mLastCurrentTime;
|
||||
|
||||
// Lock around the video RGB, width and size data. This
|
||||
// is used in the decoder backend threads and the main thread
|
||||
// to ensure that repainting the video does not use these
|
||||
|
|
|
@ -347,6 +347,9 @@ nsMediaChannelStream::CopySegmentToCache(nsIInputStream *aInStream,
|
|||
PRUint32 *aWriteCount)
|
||||
{
|
||||
CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
|
||||
|
||||
closure->mStream->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mStream->mOffset);
|
||||
|
||||
// Keep track of where we're up to
|
||||
closure->mStream->mOffset += aCount;
|
||||
closure->mStream->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
|
||||
|
|
|
@ -284,14 +284,10 @@ nsresult nsOggReader::ReadMetadata()
|
|||
// Theora spec these can be considered the 'primary' bitstreams for playback.
|
||||
// Extract the metadata needed from these streams.
|
||||
// Set a default callback period for if we have no video data
|
||||
if (mTheoraState) {
|
||||
if (mTheoraState->Init()) {
|
||||
gfxIntSize sz(mTheoraState->mInfo.pic_width,
|
||||
mTheoraState->mInfo.pic_height);
|
||||
mDecoder->SetVideoData(sz, mTheoraState->mPixelAspectRatio, nsnull);
|
||||
} else {
|
||||
mTheoraState = nsnull;
|
||||
}
|
||||
if (mTheoraState && mTheoraState->Init()) {
|
||||
gfxIntSize sz(mTheoraState->mInfo.pic_width,
|
||||
mTheoraState->mInfo.pic_height);
|
||||
mDecoder->SetVideoData(sz, mTheoraState->mPixelAspectRatio, nsnull);
|
||||
}
|
||||
if (mVorbisState) {
|
||||
mVorbisState->Init();
|
||||
|
@ -363,7 +359,7 @@ nsresult nsOggReader::DecodeVorbis(nsTArray<SoundData*>& aChunks,
|
|||
while ((samples = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
|
||||
float* buffer = new float[samples * channels];
|
||||
float* p = buffer;
|
||||
for (PRUint32 i = 0; i < samples; ++i) {
|
||||
for (PRUint32 i = 0; i < PRUint32(samples); ++i) {
|
||||
for (PRUint32 j = 0; j < channels; ++j) {
|
||||
*p++ = pcm[j][i];
|
||||
}
|
||||
|
@ -910,7 +906,7 @@ PRInt64 nsOggReader::FindEndTime(PRInt64 aEndOffset,
|
|||
// We need more data if we've not encountered a page we've seen before,
|
||||
// or we've read to the end of file.
|
||||
if (mustBackOff || readHead == aEndOffset) {
|
||||
if (endTime != -1) {
|
||||
if (endTime != -1 || readStartOffset == 0) {
|
||||
// We have encountered a page before, or we're at the end of file.
|
||||
break;
|
||||
}
|
||||
|
@ -1542,6 +1538,16 @@ nsresult nsOggReader::SeekBisection(PRInt64 aTarget,
|
|||
nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// HasAudio and HasVideo are not used here as they take a lock and cause
|
||||
// a deadlock. Accessing mInfo doesn't require a lock - it doesn't change
|
||||
// after metadata is read and GetBuffered isn't called before metadata is
|
||||
// read.
|
||||
if (!mInfo.mHasVideo && !mInfo.mHasAudio) {
|
||||
// No need to search through the file if there are no audio or video tracks
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsMediaStream* stream = mDecoder->GetCurrentStream();
|
||||
|
||||
// Traverse across the buffered byte ranges, determining the time ranges
|
||||
|
@ -1604,6 +1610,16 @@ nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
|
|||
startTime = codecState->Time(granulepos) - aStartTime;
|
||||
NS_ASSERTION(startTime > 0, "Must have positive start time");
|
||||
}
|
||||
else if(codecState) {
|
||||
// Page is for an inactive stream, skip it.
|
||||
startOffset += page.header_len + page.body_len;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// Page is for a stream we don't know about (possibly a chained
|
||||
// ogg), return an error.
|
||||
return PAGE_SYNC_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (startTime != -1) {
|
||||
|
|
|
@ -73,17 +73,8 @@ function ended(e) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Only support for buffered is available for Ogg and WAV.
|
||||
// Eventually we'll support WebM as well.
|
||||
function supportsBuffered(type) {
|
||||
var s = type.toLowerCase();
|
||||
return /ogg$/.test(s) || /wav$/.test(s)
|
||||
}
|
||||
|
||||
function startTest(test, token) {
|
||||
var v = document.createElement('video');
|
||||
if (!supportsBuffered(test.type))
|
||||
return;
|
||||
v.token = token;
|
||||
manager.started(token);
|
||||
|
||||
|
|
|
@ -17,10 +17,6 @@ var manager = new MediaTestManager;
|
|||
function do_progress(e) {
|
||||
var v = e.target;
|
||||
ok(!v._finished, "Check no progress events after completed for " + v._name);
|
||||
ok(e.lengthComputable, "Check progress lengthComputable for " + v._name);
|
||||
v._last_progress_total = e.loaded;
|
||||
ok(e.loaded <= e.total, "Check progress in bounds: " + e.loaded + " for " + v._name);
|
||||
is(e.total, v._size, "Check progress total for " + v._name);
|
||||
}
|
||||
|
||||
function do_ended(e) {
|
||||
|
@ -39,9 +35,7 @@ function startTest(test, token) {
|
|||
v.src = test.name;
|
||||
v.autoplay = true;
|
||||
v._name = test.name;
|
||||
v._size = test.size;
|
||||
v._finished = false;
|
||||
v._last_progress_total = 0;
|
||||
v.addEventListener("ended", do_ended, false);
|
||||
v.addEventListener("progress", do_progress, false);
|
||||
document.body.appendChild(v);
|
||||
|
|
|
@ -1433,6 +1433,7 @@ nsWaveDecoder::MetadataLoaded()
|
|||
} else {
|
||||
StartProgress();
|
||||
}
|
||||
StartTimeUpdate();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1552,6 +1553,7 @@ nsWaveDecoder::Shutdown()
|
|||
return;
|
||||
|
||||
mShuttingDown = PR_TRUE;
|
||||
StopTimeUpdate();
|
||||
|
||||
nsMediaDecoder::Shutdown();
|
||||
|
||||
|
@ -1678,7 +1680,7 @@ nsWaveDecoder::PlaybackPositionChanged()
|
|||
|
||||
if (mElement && lastTime != mCurrentTime) {
|
||||
UpdateReadyStateForData();
|
||||
mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
FireTimeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -241,6 +241,8 @@ class nsWaveDecoder : public nsMediaDecoder
|
|||
// are buffered and playable.
|
||||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered);
|
||||
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {}
|
||||
|
||||
private:
|
||||
// Notifies the element that seeking has started.
|
||||
void SeekingStarted();
|
||||
|
|
|
@ -51,6 +51,7 @@ EXPORTS += \
|
|||
$(NULL)
|
||||
|
||||
CPPSRCS = \
|
||||
nsWebMBufferedParser.cpp \
|
||||
nsWebMDecoder.cpp \
|
||||
nsWebMReader.cpp \
|
||||
$(NULL)
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the Mozilla Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Matthew Gregan <kinetik@flim.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsAlgorithm.h"
|
||||
#include "nsWebMBufferedParser.h"
|
||||
|
||||
static PRUint32
|
||||
VIntLength(unsigned char aFirstByte, PRUint32* aMask)
|
||||
{
|
||||
PRUint32 count = 1;
|
||||
PRUint32 mask = 1 << 7;
|
||||
while (count < 8) {
|
||||
if ((aFirstByte & mask) != 0) {
|
||||
break;
|
||||
}
|
||||
mask >>= 1;
|
||||
count += 1;
|
||||
}
|
||||
if (aMask) {
|
||||
*aMask = mask;
|
||||
}
|
||||
NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
|
||||
return count;
|
||||
}
|
||||
|
||||
void nsWebMBufferedParser::Append(const unsigned char* aBuffer, PRUint32 aLength,
|
||||
nsTArray<nsWebMTimeDataOffset>& aMapping)
|
||||
{
|
||||
static const unsigned char CLUSTER_ID[] = { 0x1f, 0x43, 0xb6, 0x75 };
|
||||
static const unsigned char TIMECODE_ID = 0xe7;
|
||||
static const unsigned char BLOCKGROUP_ID = 0xa0;
|
||||
static const unsigned char BLOCK_ID = 0xa1;
|
||||
static const unsigned char SIMPLEBLOCK_ID = 0xa3;
|
||||
|
||||
const unsigned char* p = aBuffer;
|
||||
|
||||
// Parse each byte in aBuffer one-by-one, producing timecodes and updating
|
||||
// aMapping as we go. Parser pauses at end of stream (which may be at any
|
||||
// point within the parse) and resumes parsing the next time Append is
|
||||
// called with new data.
|
||||
while (p < aBuffer + aLength) {
|
||||
switch (mState) {
|
||||
case CLUSTER_SYNC:
|
||||
if (*p++ == CLUSTER_ID[mClusterIDPos]) {
|
||||
mClusterIDPos += 1;
|
||||
} else {
|
||||
mClusterIDPos = 0;
|
||||
}
|
||||
// Cluster ID found, it's likely this is a valid sync point. If this
|
||||
// is a spurious match, the later parse steps will encounter an error
|
||||
// and return to CLUSTER_SYNC.
|
||||
if (mClusterIDPos == sizeof(CLUSTER_ID)) {
|
||||
mClusterIDPos = 0;
|
||||
mState = READ_VINT;
|
||||
mNextState = TIMECODE_SYNC;
|
||||
}
|
||||
break;
|
||||
case READ_VINT: {
|
||||
unsigned char c = *p++;
|
||||
PRUint32 mask;
|
||||
mVIntLength = VIntLength(c, &mask);
|
||||
mVIntLeft = mVIntLength - 1;
|
||||
mVInt = c & ~mask;
|
||||
mState = READ_VINT_REST;
|
||||
break;
|
||||
}
|
||||
case READ_VINT_REST:
|
||||
if (mVIntLeft) {
|
||||
mVInt <<= 8;
|
||||
mVInt |= *p++;
|
||||
mVIntLeft -= 1;
|
||||
} else {
|
||||
mState = mNextState;
|
||||
}
|
||||
break;
|
||||
case TIMECODE_SYNC:
|
||||
if (*p++ != TIMECODE_ID) {
|
||||
p -= 1;
|
||||
mState = CLUSTER_SYNC;
|
||||
break;
|
||||
}
|
||||
mClusterTimecode = 0;
|
||||
mState = READ_VINT;
|
||||
mNextState = READ_CLUSTER_TIMECODE;
|
||||
break;
|
||||
case READ_CLUSTER_TIMECODE:
|
||||
if (mVInt) {
|
||||
mClusterTimecode <<= 8;
|
||||
mClusterTimecode |= *p++;
|
||||
mVInt -= 1;
|
||||
} else {
|
||||
mState = ANY_BLOCK_SYNC;
|
||||
}
|
||||
break;
|
||||
case ANY_BLOCK_SYNC: {
|
||||
unsigned char c = *p++;
|
||||
if (c == BLOCKGROUP_ID) {
|
||||
mState = READ_VINT;
|
||||
mNextState = ANY_BLOCK_SYNC;
|
||||
} else if (c == SIMPLEBLOCK_ID || c == BLOCK_ID) {
|
||||
mBlockOffset = mCurrentOffset + (p - aBuffer) - 1;
|
||||
mState = READ_VINT;
|
||||
mNextState = READ_BLOCK;
|
||||
} else {
|
||||
PRUint32 length = VIntLength(c, nsnull);
|
||||
if (length == 4) {
|
||||
p -= 1;
|
||||
mState = CLUSTER_SYNC;
|
||||
} else {
|
||||
mState = READ_VINT;
|
||||
mNextState = SKIP_ELEMENT;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case READ_BLOCK:
|
||||
mBlockSize = mVInt;
|
||||
mBlockTimecode = 0;
|
||||
mBlockTimecodeLength = 2;
|
||||
mState = READ_VINT;
|
||||
mNextState = READ_BLOCK_TIMECODE;
|
||||
break;
|
||||
case READ_BLOCK_TIMECODE:
|
||||
if (mBlockTimecodeLength) {
|
||||
mBlockTimecode <<= 8;
|
||||
mBlockTimecode |= *p++;
|
||||
mBlockTimecodeLength -= 1;
|
||||
} else {
|
||||
// It's possible we've parsed this data before, so avoid inserting
|
||||
// duplicate nsWebMTimeDataOffset entries.
|
||||
PRUint32 idx;
|
||||
if (!aMapping.GreatestIndexLtEq(mBlockOffset, idx)) {
|
||||
nsWebMTimeDataOffset entry(mBlockOffset, mClusterTimecode + mBlockTimecode);
|
||||
aMapping.InsertElementAt(idx, entry);
|
||||
}
|
||||
|
||||
// Skip rest of block header and the block's payload.
|
||||
mBlockSize -= mVIntLength;
|
||||
mBlockSize -= 2;
|
||||
mSkipBytes = PRUint32(mBlockSize);
|
||||
mState = SKIP_DATA;
|
||||
mNextState = ANY_BLOCK_SYNC;
|
||||
}
|
||||
break;
|
||||
case SKIP_DATA:
|
||||
if (mSkipBytes) {
|
||||
PRUint32 left = aLength - (p - aBuffer);
|
||||
left = NS_MIN(left, mSkipBytes);
|
||||
p += left;
|
||||
mSkipBytes -= left;
|
||||
} else {
|
||||
mState = mNextState;
|
||||
}
|
||||
break;
|
||||
case SKIP_ELEMENT:
|
||||
mSkipBytes = PRUint32(mVInt);
|
||||
mState = SKIP_DATA;
|
||||
mNextState = ANY_BLOCK_SYNC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
|
||||
mCurrentOffset += aLength;
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the Mozilla Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Matthew Gregan <kinetik@flim.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
#if !defined(nsWebMBufferedParser_h_)
|
||||
#define nsWebMBufferedParser_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
|
||||
// Stores a stream byte offset and the scaled timecode of the block at
|
||||
// that offset. The timecode must be scaled by the stream's timecode
|
||||
// scale before use.
|
||||
struct nsWebMTimeDataOffset
|
||||
{
|
||||
nsWebMTimeDataOffset(PRInt64 aOffset, PRUint64 aTimecode)
|
||||
: mOffset(aOffset), mTimecode(aTimecode)
|
||||
{}
|
||||
|
||||
bool operator==(PRInt64 aOffset) const {
|
||||
return mOffset == aOffset;
|
||||
}
|
||||
|
||||
bool operator<(PRInt64 aOffset) const {
|
||||
return mOffset < aOffset;
|
||||
}
|
||||
|
||||
PRInt64 mOffset;
|
||||
PRUint64 mTimecode;
|
||||
};
|
||||
|
||||
// A simple WebM parser that produces data offset to timecode pairs as it
|
||||
// consumes blocks. A new parser is created for each distinct range of data
|
||||
// received and begins parsing from the first WebM cluster within that
|
||||
// range. Old parsers are destroyed when their range merges with a later
|
||||
// parser or an already parsed range. The parser may start at any position
|
||||
// within the stream.
|
||||
struct nsWebMBufferedParser
|
||||
{
|
||||
nsWebMBufferedParser(PRInt64 aOffset)
|
||||
: mStartOffset(aOffset), mCurrentOffset(aOffset), mState(CLUSTER_SYNC), mClusterIDPos(0)
|
||||
{}
|
||||
|
||||
// Steps the parser through aLength bytes of data. Always consumes
|
||||
// aLength bytes. Updates mCurrentOffset before returning.
|
||||
void Append(const unsigned char* aBuffer, PRUint32 aLength,
|
||||
nsTArray<nsWebMTimeDataOffset>& aMapping);
|
||||
|
||||
bool operator==(PRInt64 aOffset) const {
|
||||
return mCurrentOffset == aOffset;
|
||||
}
|
||||
|
||||
bool operator<(PRInt64 aOffset) const {
|
||||
return mCurrentOffset < aOffset;
|
||||
}
|
||||
|
||||
// The offset at which this parser started parsing. Used to merge
|
||||
// adjacent parsers, in which case the later parser adopts the earlier
|
||||
// parser's mStartOffset.
|
||||
PRInt64 mStartOffset;
|
||||
|
||||
// Current offset with the stream. Updated in chunks as Append() consumes
|
||||
// data.
|
||||
PRInt64 mCurrentOffset;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
// Parser start state. Scans forward searching for stream sync by
|
||||
// matching CLUSTER_ID with the curernt byte. The match state is stored
|
||||
// in mClusterIDPos. Once this reaches sizeof(CLUSTER_ID), stream may
|
||||
// have sync. The parser then advances to read the cluster size and
|
||||
// timecode.
|
||||
CLUSTER_SYNC,
|
||||
|
||||
/*
|
||||
The the parser states below assume that CLUSTER_SYNC has found a valid
|
||||
sync point within the data. If parsing fails in these states, the
|
||||
parser returns to CLUSTER_SYNC to find a new sync point.
|
||||
*/
|
||||
|
||||
// Read the first byte of a variable length integer. The first byte
|
||||
// encodes both the variable integer's length and part of the value.
|
||||
// The value read so far is stored in mVInt and the length is stored in
|
||||
// mVIntLength. The number of bytes left to read is stored in
|
||||
// mVIntLeft.
|
||||
READ_VINT,
|
||||
|
||||
// Reads the remaining mVIntLeft bytes into mVInt.
|
||||
READ_VINT_REST,
|
||||
|
||||
// Check that the next element is TIMECODE_ID. The cluster timecode is
|
||||
// required to be the first element in a cluster. Advances to READ_VINT
|
||||
// to read the timecode's length into mVInt.
|
||||
TIMECODE_SYNC,
|
||||
|
||||
// mVInt holds the length of the variable length unsigned integer
|
||||
// containing the cluster timecode. Read mVInt bytes into
|
||||
// mClusterTimecode.
|
||||
READ_CLUSTER_TIMECODE,
|
||||
|
||||
// Skips elements with a cluster until BLOCKGROUP_ID or SIMPLEBLOCK_ID
|
||||
// is found. If BLOCKGROUP_ID is found, the parser returns to
|
||||
// ANY_BLOCK_ID searching for a BLOCK_ID. Once a block or simpleblock
|
||||
// is found, the current data offset is stored in mBlockOffset. If the
|
||||
// current byte is the beginning of a four byte variant integer, it
|
||||
// indicates the parser has reached a top-level element ID and the
|
||||
// parser returns to CLUSTER_SYNC.
|
||||
ANY_BLOCK_SYNC,
|
||||
|
||||
// Start reading a block. Blocks and simpleblocks are parsed the same
|
||||
// way as the initial layouts are identical. mBlockSize is initialized
|
||||
// from mVInt (holding the element size), and mBlockTimecode(Length) is
|
||||
// initialized for parsing.
|
||||
READ_BLOCK,
|
||||
|
||||
// Reads mBlockTimecodeLength bytes of data into mBlockTimecode. When
|
||||
// mBlockTimecodeLength reaches 0, the timecode has been read. The sum
|
||||
// of mClusterTimecode and mBlockTimecode is stored as a pair with
|
||||
// mBlockOffset into the offset-to-time map.
|
||||
READ_BLOCK_TIMECODE,
|
||||
|
||||
// Skip mSkipBytes of data before resuming parse at mNextState.
|
||||
SKIP_DATA,
|
||||
|
||||
// Skip the content of an element. mVInt holds the element length.
|
||||
SKIP_ELEMENT
|
||||
};
|
||||
|
||||
// Current state machine action.
|
||||
State mState;
|
||||
|
||||
// Next state machine action. SKIP_DATA and READ_VINT_REST advance to
|
||||
// mNextState when the current action completes.
|
||||
State mNextState;
|
||||
|
||||
// Match position within CLUSTER_ID. Used to find sync within arbitrary
|
||||
// data.
|
||||
PRUint32 mClusterIDPos;
|
||||
|
||||
// Variable length integer read from data.
|
||||
PRUint64 mVInt;
|
||||
|
||||
// Encoding length of mVInt. This is the total number of bytes used to
|
||||
// encoding mVInt's value.
|
||||
PRUint32 mVIntLength;
|
||||
|
||||
// Number of bytes of mVInt left to read. mVInt is complete once this
|
||||
// reaches 0.
|
||||
PRUint32 mVIntLeft;
|
||||
|
||||
// Size of the block currently being parsed. Any unused data within the
|
||||
// block is skipped once the block timecode has been parsed.
|
||||
PRUint64 mBlockSize;
|
||||
|
||||
// Cluster-level timecode.
|
||||
PRUint64 mClusterTimecode;
|
||||
|
||||
// Start offset of the block currently being parsed. Used as the byte
|
||||
// offset for the offset-to-time mapping once the block timecode has been
|
||||
// parsed.
|
||||
PRInt64 mBlockOffset;
|
||||
|
||||
// Block-level timecode. This is summed with mClusterTimecode to produce
|
||||
// an absolute timecode for the offset-to-time mapping.
|
||||
PRInt16 mBlockTimecode;
|
||||
|
||||
// Number of bytes of mBlockTimecode left to read.
|
||||
PRUint32 mBlockTimecodeLength;
|
||||
|
||||
// Count of bytes left to skip before resuming parse at mNextState.
|
||||
// Mostly used to skip block payload data after reading a block timecode.
|
||||
PRUint32 mSkipBytes;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -42,6 +42,7 @@
|
|||
#include "nsMediaStream.h"
|
||||
#include "nsWebMReader.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "nsTimeRanges.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
|
@ -62,6 +63,8 @@ extern PRLogModuleInfo* gBuiltinDecoderLog;
|
|||
#endif
|
||||
|
||||
static const unsigned NS_PER_MS = 1000000;
|
||||
static const float NS_PER_S = 1e9;
|
||||
static const float MS_PER_S = 1e3;
|
||||
|
||||
// Functions for reading and seeking using nsMediaStream required for
|
||||
// nestegg_io. The 'user data' passed to these functions is the
|
||||
|
@ -118,8 +121,9 @@ nsWebMReader::nsWebMReader(nsBuiltinDecoder* aDecoder)
|
|||
mChannels(0),
|
||||
mVideoTrack(0),
|
||||
mAudioTrack(0),
|
||||
mAudioSamples(0),
|
||||
mAudioStartMs(-1),
|
||||
mAudioSamples(0),
|
||||
mTimecodeScale(1000000),
|
||||
mHasVideo(PR_FALSE),
|
||||
mHasAudio(PR_FALSE)
|
||||
{
|
||||
|
@ -208,6 +212,12 @@ nsresult nsWebMReader::ReadMetadata()
|
|||
mDecoder->GetStateMachine()->SetDuration(duration / NS_PER_MS);
|
||||
}
|
||||
|
||||
r = nestegg_tstamp_scale(mContext, &mTimecodeScale);
|
||||
if (r == -1) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
unsigned int ntracks = 0;
|
||||
r = nestegg_track_count(mContext, &ntracks);
|
||||
if (r == -1) {
|
||||
|
@ -418,7 +428,7 @@ PRBool nsWebMReader::DecodeAudioPacket(nestegg_packet* aPacket)
|
|||
while ((samples = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm)) > 0) {
|
||||
float* buffer = new float[samples * mChannels];
|
||||
float* p = buffer;
|
||||
for (PRUint32 i = 0; i < samples; ++i) {
|
||||
for (PRUint32 i = 0; i < PRUint32(samples); ++i) {
|
||||
for (PRUint32 j = 0; j < mChannels; ++j) {
|
||||
*p++ = pcm[j][i];
|
||||
}
|
||||
|
@ -677,7 +687,8 @@ PRBool nsWebMReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
|
|||
return PR_TRUE;
|
||||
}
|
||||
|
||||
nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime)
|
||||
nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime,
|
||||
PRInt64 aCurrentTime)
|
||||
{
|
||||
MonitorAutoEnter mon(mMonitor);
|
||||
NS_ASSERTION(mDecoder->OnStateMachineThread(),
|
||||
|
@ -686,14 +697,118 @@ nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTim
|
|||
if (NS_FAILED(ResetDecode())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
int r = nestegg_track_seek(mContext, 0, aTarget * NS_PER_MS);
|
||||
PRUint32 trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack;
|
||||
int r = nestegg_track_seek(mContext, trackToSeek, aTarget * NS_PER_MS);
|
||||
if (r != 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return DecodeToTarget(aTarget);
|
||||
}
|
||||
|
||||
void nsWebMReader::CalculateBufferedForRange(nsTimeRanges* aBuffered,
|
||||
PRInt64 aStartOffset, PRInt64 aEndOffset)
|
||||
{
|
||||
// Find the first nsWebMTimeDataOffset at or after aStartOffset.
|
||||
PRUint32 start;
|
||||
mTimeMapping.GreatestIndexLtEq(aStartOffset, start);
|
||||
if (start == mTimeMapping.Length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first nsWebMTimeDataOffset at or before aEndOffset.
|
||||
PRUint32 end;
|
||||
if (!mTimeMapping.GreatestIndexLtEq(aEndOffset, end) && end > 0) {
|
||||
// No exact match, so adjust end to be the first entry before
|
||||
// aEndOffset.
|
||||
end -= 1;
|
||||
}
|
||||
|
||||
// Range is empty.
|
||||
if (end <= start) {
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ASSERTION(mTimeMapping[start].mOffset >= aStartOffset &&
|
||||
mTimeMapping[end].mOffset <= aEndOffset,
|
||||
"Computed time range must lie within data range.");
|
||||
if (start > 0) {
|
||||
NS_ASSERTION(mTimeMapping[start - 1].mOffset <= aStartOffset,
|
||||
"Must have found least nsWebMTimeDataOffset for start");
|
||||
}
|
||||
if (end < mTimeMapping.Length() - 1) {
|
||||
NS_ASSERTION(mTimeMapping[end + 1].mOffset >= aEndOffset,
|
||||
"Must have found greatest nsWebMTimeDataOffset for end");
|
||||
}
|
||||
|
||||
float startTime = mTimeMapping[start].mTimecode * mTimecodeScale / NS_PER_S;
|
||||
float endTime = mTimeMapping[end].mTimecode * mTimecodeScale / NS_PER_S;
|
||||
aBuffered->Add(startTime, endTime);
|
||||
}
|
||||
|
||||
nsresult nsWebMReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
nsMediaStream* stream = mDecoder->GetCurrentStream();
|
||||
|
||||
// Special case completely cached files. This also handles local files.
|
||||
if (stream->IsDataCachedToEndOfStream(0)) {
|
||||
uint64_t duration = 0;
|
||||
if (mContext && nestegg_duration(mContext, &duration) == 0) {
|
||||
aBuffered->Add(aStartTime / MS_PER_S, duration / NS_PER_S);
|
||||
}
|
||||
} else {
|
||||
PRInt64 startOffset = stream->GetNextCachedData(0);
|
||||
while (startOffset >= 0) {
|
||||
PRInt64 endOffset = stream->GetCachedDataEnd(startOffset);
|
||||
NS_ASSERTION(startOffset < endOffset, "Cached range invalid");
|
||||
|
||||
CalculateBufferedForRange(aBuffered, startOffset, endOffset);
|
||||
|
||||
// Advance to the next cached data range.
|
||||
startOffset = stream->GetNextCachedData(endOffset);
|
||||
NS_ASSERTION(startOffset == -1 || startOffset > endOffset,
|
||||
"Next cached range invalid");
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsWebMReader::NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset)
|
||||
{
|
||||
PRUint32 idx;
|
||||
if (!mRangeParsers.GreatestIndexLtEq(aOffset, idx)) {
|
||||
// If the incoming data overlaps an already parsed range, adjust the
|
||||
// buffer so that we only reparse the new data. It's also possible to
|
||||
// have an overlap where the end of the incoming data is within an
|
||||
// already parsed range, but we don't bother handling that other than by
|
||||
// avoiding storing duplicate timecodes when the parser runs.
|
||||
if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) {
|
||||
// Complete overlap, skip parsing.
|
||||
if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Partial overlap, adjust the buffer to parse only the new data.
|
||||
PRInt64 adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
|
||||
NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
|
||||
aBuffer += adjust;
|
||||
aLength -= PRUint32(adjust);
|
||||
} else {
|
||||
mRangeParsers.InsertElementAt(idx, nsWebMBufferedParser(aOffset));
|
||||
}
|
||||
}
|
||||
|
||||
mRangeParsers[idx].Append(reinterpret_cast<const unsigned char*>(aBuffer), aLength, mTimeMapping);
|
||||
|
||||
// Merge parsers with overlapping regions and clean up the remnants.
|
||||
PRUint32 i = 0;
|
||||
while (i + 1 < mRangeParsers.Length()) {
|
||||
if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
|
||||
mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
|
||||
mRangeParsers.RemoveElementAt(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
|
||||
#include "nsDeque.h"
|
||||
#include "nsBuiltinDecoderReader.h"
|
||||
#include "nsWebMBufferedParser.h"
|
||||
#include "nestegg/nestegg.h"
|
||||
#include "vpx/vpx_decoder.h"
|
||||
#include "vpx/vp8dx.h"
|
||||
|
@ -126,6 +127,7 @@ public:
|
|||
virtual nsresult ReadMetadata();
|
||||
virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime);
|
||||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime);
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset);
|
||||
|
||||
private:
|
||||
// Value passed to NextPacket to determine if we are reading a video or an
|
||||
|
@ -190,6 +192,21 @@ private:
|
|||
// Number of samples we've decoded since decoding began at mAudioStartMs.
|
||||
PRUint64 mAudioSamples;
|
||||
|
||||
// Time in ns by which raw timecodes from the media must be scaled to
|
||||
// produce absolute timecodes. Used by CalculateBufferedForRange.
|
||||
PRUint64 mTimecodeScale;
|
||||
|
||||
// Update aBuffered with the time range for the given data range.
|
||||
void CalculateBufferedForRange(nsTimeRanges* aBuffered,
|
||||
PRInt64 aStartOffset, PRInt64 aEndOffset);
|
||||
|
||||
// Sorted (by offset) map of data offsets to timecodes. Populated
|
||||
// on the main thread as data is received and parsed by nsWebMBufferedParsers.
|
||||
nsTArray<nsWebMTimeDataOffset> mTimeMapping;
|
||||
|
||||
// Sorted (by offset) live parser instances. Main thread only.
|
||||
nsTArray<nsWebMBufferedParser> mRangeParsers;
|
||||
|
||||
// Booleans to indicate if we have audio and/or video data
|
||||
PRPackedBool mHasVideo;
|
||||
PRPackedBool mHasAudio;
|
||||
|
|
|
@ -78,6 +78,7 @@ EXPORTS = \
|
|||
nsPIWindowRoot.h \
|
||||
nsFocusManager.h \
|
||||
nsWrapperCache.h \
|
||||
nsContentPermissionHelper.h \
|
||||
$(NULL)
|
||||
|
||||
CPPSRCS = \
|
||||
|
@ -101,6 +102,7 @@ CPPSRCS = \
|
|||
nsScriptNameSpaceManager.cpp \
|
||||
nsDOMScriptObjectFactory.cpp \
|
||||
nsQueryContentEventResult.cpp \
|
||||
nsContentPermissionHelper.cpp \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/dom/dom-config.mk
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* The Mozilla Foundation
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Doug Turner <dougt@mozilla.com> (Original Author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsContentPermissionHelper.h"
|
||||
#include "nsIContentPermissionPrompt.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIDOMWindow.h"
|
||||
#include "nsIDOMElement.h"
|
||||
|
||||
#include "mozilla/unused.h"
|
||||
|
||||
#ifdef MOZ_IPC
|
||||
|
||||
using mozilla::unused; // <snicker>
|
||||
|
||||
nsContentPermissionRequestProxy::nsContentPermissionRequestProxy()
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsContentPermissionRequestProxy);
|
||||
}
|
||||
|
||||
nsContentPermissionRequestProxy::~nsContentPermissionRequestProxy()
|
||||
{
|
||||
MOZ_COUNT_DTOR(nsContentPermissionRequestProxy);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsContentPermissionRequestProxy::Init(const nsACString & type,
|
||||
mozilla::dom::ContentPermissionRequestParent* parent)
|
||||
{
|
||||
NS_ASSERTION(parent, "null parent");
|
||||
mParent = parent;
|
||||
mType = type;
|
||||
|
||||
nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
|
||||
if (!prompt) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
prompt->Prompt(this);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(nsContentPermissionRequestProxy, nsIContentPermissionRequest);
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsContentPermissionRequestProxy::GetType(nsACString & aType)
|
||||
{
|
||||
aType = mType;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsContentPermissionRequestProxy::GetWindow(nsIDOMWindow * *aRequestingWindow)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingWindow);
|
||||
*aRequestingWindow = nsnull; // ipc doesn't have a window
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsContentPermissionRequestProxy::GetUri(nsIURI * *aRequestingURI)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingURI);
|
||||
if (mParent == nsnull)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
NS_ADDREF(*aRequestingURI = mParent->mURI);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsContentPermissionRequestProxy::GetElement(nsIDOMElement * *aRequestingElement)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingElement);
|
||||
if (mParent == nsnull)
|
||||
return NS_ERROR_FAILURE;
|
||||
NS_ADDREF(*aRequestingElement = mParent->mElement);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsContentPermissionRequestProxy::Cancel()
|
||||
{
|
||||
NS_ASSERTION(mParent, "No parent for request");
|
||||
if (mParent == nsnull)
|
||||
return NS_ERROR_FAILURE;
|
||||
unused << mozilla::dom::ContentPermissionRequestParent::Send__delete__(mParent, false);
|
||||
mParent = nsnull;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsContentPermissionRequestProxy::Allow()
|
||||
{
|
||||
NS_ASSERTION(mParent, "No parent for request");
|
||||
if (mParent == nsnull)
|
||||
return NS_ERROR_FAILURE;
|
||||
unused << mozilla::dom::ContentPermissionRequestParent::Send__delete__(mParent, true);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
ContentPermissionRequestParent::ContentPermissionRequestParent(const nsACString& aType,
|
||||
nsIDOMElement *aElement,
|
||||
const IPC::URI& aUri)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ContentPermissionRequestParent);
|
||||
|
||||
mURI = aUri;
|
||||
mElement = aElement;
|
||||
mType = aType;
|
||||
}
|
||||
|
||||
ContentPermissionRequestParent::~ContentPermissionRequestParent()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ContentPermissionRequestParent);
|
||||
}
|
||||
|
||||
bool
|
||||
ContentPermissionRequestParent::Recvprompt()
|
||||
{
|
||||
mProxy = new nsContentPermissionRequestProxy();
|
||||
NS_ASSERTION(mProxy, "Alloc of request proxy failed");
|
||||
if (NS_FAILED(mProxy->Init(mType, this)))
|
||||
mProxy->Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
#endif
|
|
@ -35,32 +35,32 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef nsGeolocationOOP_h
|
||||
#define nsGeolocationOOP_h
|
||||
#ifndef nsContentPermissionHelper_h
|
||||
#define nsContentPermissionHelper_h
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
#include "nsIGeolocationProvider.h"
|
||||
#include "nsIContentPermissionPrompt.h"
|
||||
#include "nsString.h"
|
||||
#include "nsIDOMElement.h"
|
||||
|
||||
#include "mozilla/dom/PContentPermissionRequestParent.h"
|
||||
|
||||
class nsGeolocationRequestProxy;
|
||||
class nsContentPermissionRequestProxy;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class GeolocationRequestParent : public PContentPermissionRequestParent
|
||||
class ContentPermissionRequestParent : public PContentPermissionRequestParent
|
||||
{
|
||||
public:
|
||||
GeolocationRequestParent(nsIDOMElement *element, const IPC::URI& principal);
|
||||
virtual ~GeolocationRequestParent();
|
||||
ContentPermissionRequestParent(const nsACString& type, nsIDOMElement *element, const IPC::URI& principal);
|
||||
virtual ~ContentPermissionRequestParent();
|
||||
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
nsCOMPtr<nsIDOMElement> mElement;
|
||||
nsCOMPtr<nsGeolocationRequestProxy> mProxy;
|
||||
nsCOMPtr<nsContentPermissionRequestProxy> mProxy;
|
||||
nsCString mType;
|
||||
|
||||
private:
|
||||
virtual bool Recvprompt();
|
||||
|
@ -69,20 +69,21 @@ class GeolocationRequestParent : public PContentPermissionRequestParent
|
|||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
class nsGeolocationRequestProxy : public nsIContentPermissionRequest
|
||||
class nsContentPermissionRequestProxy : public nsIContentPermissionRequest
|
||||
{
|
||||
public:
|
||||
nsGeolocationRequestProxy();
|
||||
virtual ~nsGeolocationRequestProxy();
|
||||
nsContentPermissionRequestProxy();
|
||||
virtual ~nsContentPermissionRequestProxy();
|
||||
|
||||
nsresult Init(mozilla::dom::GeolocationRequestParent* parent);
|
||||
nsresult Init(const nsACString& type, mozilla::dom::ContentPermissionRequestParent* parent);
|
||||
|
||||
NS_DECL_ISUPPORTS;
|
||||
NS_DECL_NSICONTENTPERMISSIONREQUEST;
|
||||
|
||||
private:
|
||||
// Non-owning pointer to the GeolocationRequestParent object which owns this proxy.
|
||||
mozilla::dom::GeolocationRequestParent* mParent;
|
||||
// Non-owning pointer to the ContentPermissionRequestParent object which owns this proxy.
|
||||
mozilla::dom::ContentPermissionRequestParent* mParent;
|
||||
nsCString mType;
|
||||
};
|
||||
#endif // nsGeolocationOOP_h
|
||||
#endif // nsContentPermissionHelper_h
|
||||
|
|
@ -48,7 +48,6 @@ GRE_MODULE = 1
|
|||
|
||||
XPIDLSRCS = nsIDOMNavigatorDesktopNotification.idl \
|
||||
nsIDOMDesktopNotification.idl \
|
||||
nsIDesktopNotificationPrompt.idl \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is DesktopNotification.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Doug Turner <dougt@dougt.org> (Original Author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIURI;
|
||||
interface nsIDOMWindow;
|
||||
interface nsIDesktopNotificationPrompt;
|
||||
|
||||
%{C++
|
||||
/*
|
||||
This must be implemented by embedders to be able to support DOM DesktopNotification.
|
||||
*/
|
||||
#define NS_DOM_DESKTOP_NOTIFICATION_PROMPT_CONTRACTID "@mozilla.org/dom-desktop-notification/prompt;1"
|
||||
%}
|
||||
|
||||
/**
|
||||
* Interface allows access to a notice and is passed to
|
||||
* the nsIDesktopNotificationPrompt so that the application can approve
|
||||
* or deny the request.
|
||||
*/
|
||||
[scriptable, uuid(A23B1236-9374-4591-97BF-5413FC4813A6)]
|
||||
interface nsIDOMDesktopNotificationRequest : nsISupports {
|
||||
|
||||
/*
|
||||
* URI and window corresponding to where this request
|
||||
* originated.
|
||||
*/
|
||||
readonly attribute nsIURI requestingURI;
|
||||
readonly attribute nsIDOMWindow requestingWindow;
|
||||
|
||||
void cancel();
|
||||
void allow();
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface provides a way for the application to handle
|
||||
* the UI prompts associated with geo position.
|
||||
*/
|
||||
[scriptable, function, uuid(00D49D61-3D08-4AC6-B662-5E421F60CC2F)]
|
||||
interface nsIDesktopNotificationPrompt : nsISupports {
|
||||
/**
|
||||
* Called when a request has been made to access desktop notifications
|
||||
*/
|
||||
void prompt(in nsIDOMDesktopNotificationRequest request);
|
||||
};
|
|
@ -79,7 +79,7 @@ LOCAL_INCLUDES += \
|
|||
-I$(topsrcdir)/chrome/src \
|
||||
-I$(topsrcdir)/uriloader/exthandler \
|
||||
-I$(srcdir)/../../netwerk/base/src \
|
||||
-I$(srcdir)/../src/geolocation \
|
||||
-I$(srcdir)/../src/base \
|
||||
$(NULL)
|
||||
|
||||
DEFINES += -DBIN_SUFFIX='"$(BIN_SUFFIX)"'
|
||||
|
|
|
@ -128,7 +128,9 @@ parent:
|
|||
|
||||
__delete__();
|
||||
|
||||
PExternalHelperApp(URI uri, nsCString aMimeContentType, bool aForceSave, PRInt64 aContentLength);
|
||||
PExternalHelperApp(URI uri, nsCString aMimeContentType,
|
||||
nsCString aContentDisposition, bool aForceSave,
|
||||
PRInt64 aContentLength);
|
||||
|
||||
child:
|
||||
/**
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef PCOMContentPermissionRequestChild_h
|
||||
#define PCOMContentPermissionRequestChild_h
|
||||
|
||||
#include "mozilla/dom/PContentPermissionRequestChild.h"
|
||||
// Microsoft's API Name hackery sucks
|
||||
#undef CreateEvent
|
||||
|
@ -52,3 +55,5 @@ class PCOMContentPermissionRequestChild : public mozilla::dom::PContentPermissio
|
|||
public:
|
||||
virtual void IPDLRelease() = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1287,6 +1287,7 @@ TabChildGlobal::GetPrincipal()
|
|||
PExternalHelperAppChild*
|
||||
TabChild::AllocPExternalHelperApp(const IPC::URI& uri,
|
||||
const nsCString& aMimeContentType,
|
||||
const nsCString& aContentDisposition,
|
||||
const bool& aForceSave,
|
||||
const PRInt64& aContentLength)
|
||||
{
|
||||
|
|
|
@ -230,6 +230,7 @@ public:
|
|||
virtual PExternalHelperAppChild *AllocPExternalHelperApp(
|
||||
const IPC::URI& uri,
|
||||
const nsCString& aMimeContentType,
|
||||
const nsCString& aContentDisposition,
|
||||
const bool& aForceSave,
|
||||
const PRInt64& aContentLength);
|
||||
virtual bool DeallocPExternalHelperApp(PExternalHelperAppChild *aService);
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
#include "nsNetUtil.h"
|
||||
#include "jsarray.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsGeolocationOOP.h"
|
||||
#include "nsContentPermissionHelper.h"
|
||||
#include "nsIDOMNSHTMLFrameElement.h"
|
||||
#include "nsIDialogCreator.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
@ -534,11 +534,7 @@ TabParent::DeallocPDocumentRendererNativeID(PDocumentRendererNativeIDParent* act
|
|||
PContentPermissionRequestParent*
|
||||
TabParent::AllocPContentPermissionRequest(const nsCString& type, const IPC::URI& uri)
|
||||
{
|
||||
if (type.Equals(NS_LITERAL_CSTRING("geolocation"))) {
|
||||
return new GeolocationRequestParent(mFrameElement, uri);
|
||||
}
|
||||
|
||||
return nsnull;
|
||||
return new ContentPermissionRequestParent(type, mFrameElement, uri);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -836,12 +832,13 @@ TabParent::GetFrameLoader() const
|
|||
PExternalHelperAppParent*
|
||||
TabParent::AllocPExternalHelperApp(const IPC::URI& uri,
|
||||
const nsCString& aMimeContentType,
|
||||
const nsCString& aContentDisposition,
|
||||
const bool& aForceSave,
|
||||
const PRInt64& aContentLength)
|
||||
{
|
||||
ExternalHelperAppParent *parent = new ExternalHelperAppParent(uri, aContentLength);
|
||||
parent->AddRef();
|
||||
parent->Init(this, aMimeContentType, aForceSave);
|
||||
parent->Init(this, aMimeContentType, aContentDisposition, aForceSave);
|
||||
return parent;
|
||||
}
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ public:
|
|||
virtual PExternalHelperAppParent* AllocPExternalHelperApp(
|
||||
const IPC::URI& uri,
|
||||
const nsCString& aMimeContentType,
|
||||
const nsCString& aContentDisposition,
|
||||
const bool& aForceSave,
|
||||
const PRInt64& aContentLength);
|
||||
virtual bool DeallocPExternalHelperApp(PExternalHelperAppParent* aService);
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifdef MOZ_IPC
|
||||
#include "nsGeolocationOOP.h"
|
||||
#include "nsContentPermissionHelper.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
#include "mozilla/dom/PBrowserChild.h"
|
||||
|
@ -1143,112 +1143,3 @@ DOMCI_DATA(GeoPositionCoords, void)
|
|||
DOMCI_DATA(GeoPosition, void)
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_IPC
|
||||
nsGeolocationRequestProxy::nsGeolocationRequestProxy()
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsGeolocationRequestProxy);
|
||||
}
|
||||
|
||||
nsGeolocationRequestProxy::~nsGeolocationRequestProxy()
|
||||
{
|
||||
MOZ_COUNT_DTOR(nsGeolocationRequestProxy);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsGeolocationRequestProxy::Init(mozilla::dom::GeolocationRequestParent* parent)
|
||||
{
|
||||
NS_ASSERTION(parent, "null parent");
|
||||
mParent = parent;
|
||||
|
||||
nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
|
||||
if (!prompt) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
(void) prompt->Prompt(this);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(nsGeolocationRequestProxy, nsIContentPermissionRequest);
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGeolocationRequestProxy::GetType(nsACString & aType)
|
||||
{
|
||||
aType = "geolocation";
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGeolocationRequestProxy::GetWindow(nsIDOMWindow * *aRequestingWindow)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingWindow);
|
||||
*aRequestingWindow = nsnull;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGeolocationRequestProxy::GetUri(nsIURI * *aRequestingURI)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingURI);
|
||||
NS_ASSERTION(mParent, "No parent for request");
|
||||
|
||||
NS_ADDREF(*aRequestingURI = mParent->mURI);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGeolocationRequestProxy::GetElement(nsIDOMElement * *aRequestingElement)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingElement);
|
||||
NS_ASSERTION(mParent && mParent->mElement.get(), "No parent for request");
|
||||
NS_ADDREF(*aRequestingElement = mParent->mElement);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGeolocationRequestProxy::Cancel()
|
||||
{
|
||||
NS_ASSERTION(mParent, "No parent for request");
|
||||
unused << mozilla::dom::GeolocationRequestParent::Send__delete__(mParent, false);
|
||||
mParent = nsnull;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGeolocationRequestProxy::Allow()
|
||||
{
|
||||
NS_ASSERTION(mParent, "No parent for request");
|
||||
unused << mozilla::dom::GeolocationRequestParent::Send__delete__(mParent, true);
|
||||
mParent = nsnull;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
GeolocationRequestParent::GeolocationRequestParent(nsIDOMElement *element, const IPC::URI& uri)
|
||||
{
|
||||
MOZ_COUNT_CTOR(GeolocationRequestParent);
|
||||
|
||||
mURI = uri;
|
||||
mElement = element;
|
||||
}
|
||||
|
||||
GeolocationRequestParent::~GeolocationRequestParent()
|
||||
{
|
||||
MOZ_COUNT_DTOR(GeolocationRequestParent);
|
||||
}
|
||||
|
||||
bool
|
||||
GeolocationRequestParent::Recvprompt()
|
||||
{
|
||||
mProxy = new nsGeolocationRequestProxy();
|
||||
NS_ASSERTION(mProxy, "Alloc of request proxy failed");
|
||||
if (NS_FAILED(mProxy->Init(this)))
|
||||
mProxy->Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
#endif
|
||||
|
|
|
@ -58,9 +58,13 @@ EXTRA_DSO_LDOPTS = \
|
|||
|
||||
LOCAL_INCLUDES = \
|
||||
-I$(topsrcdir)/dom/base \
|
||||
-I$(topsrcdir)/dom/ipc \
|
||||
-I$(topsrcdir)/content/base/src \
|
||||
-I$(topsrcdir)/content/events/src \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/config.mk
|
||||
include $(topsrcdir)/ipc/chromium/chromium-config.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
DEFINES += -D_IMPL_NS_LAYOUT
|
||||
|
|
|
@ -36,6 +36,17 @@
|
|||
|
||||
#include "nsDesktopNotification.h"
|
||||
|
||||
#ifdef MOZ_IPC
|
||||
#include "nsContentPermissionHelper.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
#include "mozilla/dom/PBrowserChild.h"
|
||||
#include "TabChild.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
#endif
|
||||
|
||||
|
||||
class nsDesktopNotification;
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
@ -171,14 +182,45 @@ nsDOMDesktopNotification::HandleAlertServiceNotification(const char *aTopic)
|
|||
NS_IMETHODIMP
|
||||
nsDOMDesktopNotification::Show()
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> request;
|
||||
// If we are in testing mode (running mochitests, for example)
|
||||
// and we are suppose to allow requests, then just post an allow event.
|
||||
if (nsContentUtils::GetBoolPref("notification.prompt.testing", PR_FALSE) &&
|
||||
nsContentUtils::GetBoolPref("notification.prompt.testing.allow", PR_TRUE)) {
|
||||
request = new NotificationRequestAllowEvent(this);
|
||||
} else {
|
||||
request = new nsDesktopNotificationRequest(this);
|
||||
nsCOMPtr<nsIRunnable> request = new NotificationRequestAllowEvent(this);
|
||||
NS_DispatchToMainThread(request);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// otherwise, create a normal request.
|
||||
nsRefPtr<nsDesktopNotificationRequest> request = new nsDesktopNotificationRequest(this);
|
||||
|
||||
// if we are in the content process, then remote it to the parent.
|
||||
#ifdef MOZ_IPC
|
||||
if (XRE_GetProcessType() == GeckoProcessType_Content) {
|
||||
|
||||
// if for some reason mOwner is null, just silently
|
||||
// bail. The user will not see a notification, and that
|
||||
// is fine.
|
||||
if (!mOwner)
|
||||
return NS_OK;
|
||||
|
||||
// because owner implements nsITabChild, we can assume that it is
|
||||
// the one and only TabChild for this docshell.
|
||||
TabChild* child = GetTabChildFrom(mOwner->GetDocShell());
|
||||
|
||||
// Retain a reference so the object isn't deleted without IPDL's knowledge.
|
||||
// Corresponding release occurs in DeallocPContentPermissionRequest.
|
||||
request->AddRef();
|
||||
|
||||
nsCString type = NS_LITERAL_CSTRING("desktop-notification");
|
||||
child->SendPContentPermissionRequestConstructor(request, type, IPC::URI(mURI));
|
||||
|
||||
request->Sendprompt();
|
||||
return NS_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
// otherwise, dispatch it
|
||||
NS_DispatchToMainThread(request);
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -246,11 +288,11 @@ nsDesktopNotificationCenter::CreateNotification(const nsAString & title,
|
|||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
NS_IMPL_ISUPPORTS2(nsDesktopNotificationRequest,
|
||||
nsIDOMDesktopNotificationRequest,
|
||||
nsIContentPermissionRequest,
|
||||
nsIRunnable)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDesktopNotificationRequest::GetRequestingURI(nsIURI * *aRequestingURI)
|
||||
nsDesktopNotificationRequest::GetUri(nsIURI * *aRequestingURI)
|
||||
{
|
||||
if (!mDesktopNotification)
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
|
@ -260,7 +302,7 @@ nsDesktopNotificationRequest::GetRequestingURI(nsIURI * *aRequestingURI)
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDesktopNotificationRequest::GetRequestingWindow(nsIDOMWindow * *aRequestingWindow)
|
||||
nsDesktopNotificationRequest::GetWindow(nsIDOMWindow * *aRequestingWindow)
|
||||
{
|
||||
if (!mDesktopNotification)
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
|
@ -270,6 +312,12 @@ nsDesktopNotificationRequest::GetRequestingWindow(nsIDOMWindow * *aRequestingWin
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDesktopNotificationRequest::GetElement(nsIDOMElement * *aElement)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDesktopNotificationRequest::Cancel()
|
||||
{
|
||||
|
@ -285,3 +333,10 @@ nsDesktopNotificationRequest::Allow()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDesktopNotificationRequest::GetType(nsACString & aType)
|
||||
{
|
||||
aType = "desktop-notification";
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,11 @@
|
|||
#ifndef nsDesktopNotification_h
|
||||
#define nsDesktopNotification_h
|
||||
|
||||
#ifdef MOZ_IPC
|
||||
#include "PCOMContentPermissionRequestChild.h"
|
||||
#endif
|
||||
|
||||
|
||||
#include "nsDOMClassInfo.h"
|
||||
#include "nsIJSContextStack.h"
|
||||
|
||||
|
@ -44,7 +49,7 @@
|
|||
|
||||
#include "nsIDOMDesktopNotification.h"
|
||||
#include "nsIDOMEventTarget.h"
|
||||
#include "nsIDesktopNotificationPrompt.h"
|
||||
#include "nsIContentPermissionPrompt.h"
|
||||
|
||||
#include "nsIObserver.h"
|
||||
#include "nsString.h"
|
||||
|
@ -140,20 +145,24 @@ protected:
|
|||
/*
|
||||
* Simple Request
|
||||
*/
|
||||
class nsDesktopNotificationRequest : public nsIDOMDesktopNotificationRequest,
|
||||
class nsDesktopNotificationRequest : public nsIContentPermissionRequest,
|
||||
public nsRunnable
|
||||
#ifdef MOZ_IPC
|
||||
, public PCOMContentPermissionRequestChild
|
||||
#endif
|
||||
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIDOMDESKTOPNOTIFICATIONREQUEST
|
||||
NS_DECL_NSICONTENTPERMISSIONREQUEST
|
||||
|
||||
nsDesktopNotificationRequest(nsDOMDesktopNotification* notification)
|
||||
: mDesktopNotification(notification) {}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
nsCOMPtr<nsIDesktopNotificationPrompt> prompt =
|
||||
do_GetService(NS_DOM_DESKTOP_NOTIFICATION_PROMPT_CONTRACTID);
|
||||
nsCOMPtr<nsIContentPermissionPrompt> prompt =
|
||||
do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
|
||||
if (prompt) {
|
||||
prompt->Prompt(this);
|
||||
}
|
||||
|
@ -161,8 +170,21 @@ class nsDesktopNotificationRequest : public nsIDOMDesktopNotificationRequest,
|
|||
}
|
||||
|
||||
~nsDesktopNotificationRequest()
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef MOZ_IPC
|
||||
|
||||
bool Recv__delete__(const bool& allow)
|
||||
{
|
||||
if (allow)
|
||||
(void) Allow();
|
||||
else
|
||||
(void) Cancel();
|
||||
return true;
|
||||
}
|
||||
void IPDLRelease() { Release(); }
|
||||
#endif
|
||||
|
||||
nsRefPtr<nsDOMDesktopNotification> mDesktopNotification;
|
||||
};
|
||||
|
|
|
@ -90,6 +90,7 @@ FORCE_STATIC_LIB = 1
|
|||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
INCLUDES += \
|
||||
-I$(topsrcdir)/editor/libeditor/text \
|
||||
-I$(topsrcdir)/content/base/src \
|
||||
-I$(topsrcdir)/content/events/src \
|
||||
-I$(topsrcdir)/layout/style \
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
|
||||
#include "nsITransferable.h"
|
||||
#include "nsComputedDOMStyle.h"
|
||||
#include "nsTextEditUtils.h"
|
||||
|
||||
#include "mozilla/FunctionTimer.h"
|
||||
|
||||
|
@ -903,14 +904,27 @@ nsEditor::EndPlaceHolderTransaction()
|
|||
if (selPrivate) {
|
||||
selPrivate->SetCanCacheFrameOffset(PR_TRUE);
|
||||
}
|
||||
|
||||
// time to turn off the batch
|
||||
EndUpdateViewBatch();
|
||||
// make sure selection is in view
|
||||
|
||||
// After ScrollSelectionIntoView(), the pending notifications might be
|
||||
// flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
|
||||
ScrollSelectionIntoView(PR_FALSE);
|
||||
{
|
||||
// Hide the caret here to avoid hiding it twice, once in EndUpdateViewBatch
|
||||
// and once in ScrollSelectionIntoView.
|
||||
nsRefPtr<nsCaret> caret;
|
||||
nsCOMPtr<nsIPresShell> presShell;
|
||||
GetPresShell(getter_AddRefs(presShell));
|
||||
|
||||
if (presShell)
|
||||
caret = presShell->GetCaret();
|
||||
|
||||
StCaretHider caretHider(caret);
|
||||
|
||||
// time to turn off the batch
|
||||
EndUpdateViewBatch();
|
||||
// make sure selection is in view
|
||||
|
||||
// After ScrollSelectionIntoView(), the pending notifications might be
|
||||
// flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
|
||||
ScrollSelectionIntoView(PR_FALSE);
|
||||
}
|
||||
|
||||
// cached for frame offset are Not available now
|
||||
if (selPrivate) {
|
||||
|
@ -2273,11 +2287,75 @@ NS_IMETHODIMP nsEditor::InsertTextImpl(const nsAString& aStringToInsert,
|
|||
// class to turn off txn selection updating. Caller also turned on rules sniffing
|
||||
// if desired.
|
||||
|
||||
nsresult res;
|
||||
NS_ENSURE_TRUE(aInOutNode && *aInOutNode && aInOutOffset && aDoc, NS_ERROR_NULL_POINTER);
|
||||
if (!mInIMEMode && aStringToInsert.IsEmpty()) return NS_OK;
|
||||
nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(*aInOutNode);
|
||||
if (!nodeAsText && IsPlaintextEditor()) {
|
||||
// In some cases, aInOutNode is the anonymous DIV, and aInOutOffset is 0.
|
||||
// To avoid injecting unneeded text nodes, we first look to see if we have
|
||||
// one available. In that case, we'll just adjust aInOutNode and aInOutOffset
|
||||
// accordingly.
|
||||
if (*aInOutNode == GetRoot() && *aInOutOffset == 0) {
|
||||
nsCOMPtr<nsIDOMNode> possibleTextNode;
|
||||
res = (*aInOutNode)->GetFirstChild(getter_AddRefs(possibleTextNode));
|
||||
if (NS_SUCCEEDED(res)) {
|
||||
nodeAsText = do_QueryInterface(possibleTextNode);
|
||||
if (nodeAsText) {
|
||||
*aInOutNode = possibleTextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
// In some other cases, aInOutNode is the anonymous DIV, and aInOutOffset points
|
||||
// to the terminating mozBR. In that case, we'll adjust aInOutNode and aInOutOffset
|
||||
// to the preceding text node, if any.
|
||||
if (!nodeAsText && *aInOutNode == GetRoot() && *aInOutOffset > 0) {
|
||||
nsCOMPtr<nsIDOMNodeList> children;
|
||||
res = (*aInOutNode)->GetChildNodes(getter_AddRefs(children));
|
||||
if (NS_SUCCEEDED(res)) {
|
||||
nsCOMPtr<nsIDOMNode> possibleMozBRNode;
|
||||
res = children->Item(*aInOutOffset, getter_AddRefs(possibleMozBRNode));
|
||||
if (NS_SUCCEEDED(res) && nsTextEditUtils::IsMozBR(possibleMozBRNode)) {
|
||||
nsCOMPtr<nsIDOMNode> possibleTextNode;
|
||||
res = children->Item(*aInOutOffset - 1, getter_AddRefs(possibleTextNode));
|
||||
if (NS_SUCCEEDED(res)) {
|
||||
nodeAsText = do_QueryInterface(possibleTextNode);
|
||||
if (nodeAsText) {
|
||||
PRUint32 length;
|
||||
res = nodeAsText->GetLength(&length);
|
||||
if (NS_SUCCEEDED(res)) {
|
||||
*aInOutOffset = PRInt32(length);
|
||||
*aInOutNode = possibleTextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sometimes, aInOutNode is the mozBR element itself. In that case, we'll
|
||||
// adjust the insertion point to the previous text node, if one exists, or
|
||||
// to the parent anonymous DIV.
|
||||
if (nsTextEditUtils::IsMozBR(*aInOutNode) && *aInOutOffset == 0) {
|
||||
nsCOMPtr<nsIDOMNode> previous;
|
||||
(*aInOutNode)->GetPreviousSibling(getter_AddRefs(previous));
|
||||
nodeAsText = do_QueryInterface(previous);
|
||||
if (nodeAsText) {
|
||||
PRUint32 length;
|
||||
res = nodeAsText->GetLength(&length);
|
||||
if (NS_SUCCEEDED(res)) {
|
||||
*aInOutOffset = PRInt32(length);
|
||||
*aInOutNode = previous;
|
||||
}
|
||||
} else {
|
||||
nsCOMPtr<nsIDOMNode> parent;
|
||||
(*aInOutNode)->GetParentNode(getter_AddRefs(parent));
|
||||
if (parent == GetRoot()) {
|
||||
*aInOutNode = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PRInt32 offset = *aInOutOffset;
|
||||
nsresult res;
|
||||
if (mInIMEMode)
|
||||
{
|
||||
if (!nodeAsText)
|
||||
|
@ -3546,8 +3624,9 @@ nsEditor::IsEditable(nsIDOMNode *aNode)
|
|||
// and uses enhanced logic to find out in the HTML world.
|
||||
return IsTextInDirtyFrameVisible(aNode);
|
||||
}
|
||||
if (resultFrame->GetSize().width > 0)
|
||||
return PR_TRUE; // text node has width
|
||||
if (resultFrame->HasAnyNoncollapsedCharacters()) {
|
||||
return PR_TRUE;
|
||||
}
|
||||
resultFrame = resultFrame->GetNextContinuation();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -623,13 +623,19 @@ nsPlaintextEditor::GetTextSelectionOffsets(nsISelection *aSelection,
|
|||
}
|
||||
}
|
||||
#ifdef NS_DEBUG
|
||||
++nodeCount;
|
||||
// The post content iterator might return the parent node (which is the
|
||||
// editor's root node) as the last item. Don't count the root node itself
|
||||
// as one of its children!
|
||||
if (!SameCOMIdentity(currentNode, rootNode)) {
|
||||
++nodeCount;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (endOffset == -1) {
|
||||
NS_ASSERTION(endNode == rootNode, "failed to find the end node");
|
||||
NS_ASSERTION(endNodeOffset == nodeCount-1 || endNodeOffset == 0,
|
||||
NS_ASSERTION(IsPasswordEditor() ||
|
||||
(endNodeOffset == nodeCount-1 || endNodeOffset == 0),
|
||||
"invalid end node offset");
|
||||
endOffset = endNodeOffset == 0 ? 0 : totalLength;
|
||||
}
|
||||
|
@ -862,57 +868,55 @@ NS_IMETHODIMP nsPlaintextEditor::InsertLineBreak()
|
|||
shell->MaybeInvalidateCaretPosition();
|
||||
|
||||
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertBreak);
|
||||
ruleInfo.maxLength = mMaxTextLength;
|
||||
PRBool cancel, handled;
|
||||
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (!cancel && !handled)
|
||||
{
|
||||
// create the new BR node
|
||||
nsCOMPtr<nsIDOMNode> newNode;
|
||||
res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("br"), getter_AddRefs(newNode));
|
||||
if (!newNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
|
||||
// get the (collapsed) selection location
|
||||
nsCOMPtr<nsIDOMNode> selNode;
|
||||
PRInt32 selOffset;
|
||||
res = GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// don't put text in places that can't have it
|
||||
if (!IsTextNode(selNode) && !CanContainTag(selNode, NS_LITERAL_STRING("#text")))
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
// we need to get the doc
|
||||
nsCOMPtr<nsIDOMDocument> doc;
|
||||
res = GetDocument(getter_AddRefs(doc));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
|
||||
|
||||
// don't spaz my selection in subtransactions
|
||||
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
||||
|
||||
// insert a linefeed character
|
||||
res = InsertTextImpl(NS_LITERAL_STRING("\n"), address_of(selNode),
|
||||
&selOffset, doc);
|
||||
if (!selNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
|
||||
if (NS_SUCCEEDED(res))
|
||||
{
|
||||
// set the selection to the new node
|
||||
nsCOMPtr<nsIDOMNode>parent;
|
||||
res = newNode->GetParentNode(getter_AddRefs(parent));
|
||||
if (!parent) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
|
||||
// set the selection to the correct location
|
||||
res = selection->Collapse(selNode, selOffset);
|
||||
|
||||
if (NS_SUCCEEDED(res))
|
||||
{
|
||||
PRInt32 offsetInParent=-1; // we use the -1 as a marker to see if we need to compute this or not
|
||||
nsCOMPtr<nsIDOMNode>nextNode;
|
||||
newNode->GetNextSibling(getter_AddRefs(nextNode));
|
||||
if (nextNode)
|
||||
{
|
||||
nsCOMPtr<nsIDOMCharacterData>nextTextNode = do_QueryInterface(nextNode);
|
||||
if (!nextTextNode) {
|
||||
nextNode = do_QueryInterface(newNode); // is this QI needed?
|
||||
}
|
||||
else {
|
||||
offsetInParent=0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
nextNode = do_QueryInterface(newNode); // is this QI needed?
|
||||
}
|
||||
// see if we're at the end of the editor range
|
||||
nsCOMPtr<nsIDOMNode> endNode;
|
||||
PRInt32 endOffset;
|
||||
res = GetEndNodeAndOffset(selection, getter_AddRefs(endNode), &endOffset);
|
||||
|
||||
if (-1==offsetInParent)
|
||||
if (NS_SUCCEEDED(res) && endNode == selNode && endOffset == selOffset)
|
||||
{
|
||||
nextNode->GetParentNode(getter_AddRefs(parent));
|
||||
res = GetChildOffset(nextNode, parent, offsetInParent);
|
||||
if (NS_SUCCEEDED(res)) {
|
||||
// SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
|
||||
// We want the caret to stick to whatever is past the break. This is
|
||||
// because the break is on the same line we were on, but the next content
|
||||
// will be on the following line.
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
||||
selPriv->SetInterlinePosition(PR_TRUE);
|
||||
res = selection->Collapse(parent, offsetInParent+1); // +1 to insert just after the break
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res = selection->Collapse(nextNode, offsetInParent);
|
||||
// SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
|
||||
// We want the caret to stick to whatever is past the break. This is
|
||||
// because the break is on the same line we were on, but the next content
|
||||
// will be on the following line.
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
||||
selPriv->SetInterlinePosition(PR_TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -267,6 +267,9 @@ nsTextEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection)
|
|||
// insure trailing br node
|
||||
res = CreateTrailingBRIfNeeded();
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// collapse the selection to the trailing BR if it's at the end of our text node
|
||||
CollapseSelectionToTrailingBRIfNeeded(selection);
|
||||
|
||||
/* After inserting text the cursor Bidi level must be set to the level of the inserted text.
|
||||
* This is difficult, because we cannot know what the level is until after the Bidi algorithm
|
||||
|
@ -309,7 +312,7 @@ nsTextEditRules::WillDoAction(nsISelection *aSelection,
|
|||
switch (info->action)
|
||||
{
|
||||
case kInsertBreak:
|
||||
return WillInsertBreak(aSelection, aCancel, aHandled);
|
||||
return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength);
|
||||
case kInsertText:
|
||||
case kInsertTextIME:
|
||||
return WillInsertText(info->action,
|
||||
|
@ -421,7 +424,10 @@ nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
|
|||
}
|
||||
|
||||
nsresult
|
||||
nsTextEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
|
||||
nsTextEditRules::WillInsertBreak(nsISelection *aSelection,
|
||||
PRBool *aCancel,
|
||||
PRBool *aHandled,
|
||||
PRInt32 aMaxLength)
|
||||
{
|
||||
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
||||
CANCEL_OPERATION_IF_READONLY_OR_DISABLED
|
||||
|
@ -431,11 +437,24 @@ nsTextEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo
|
|||
}
|
||||
else
|
||||
{
|
||||
// handle docs with a max length
|
||||
// NOTE, this function copies inString into outString for us.
|
||||
NS_NAMED_LITERAL_STRING(inString, "\n");
|
||||
nsAutoString outString;
|
||||
PRBool didTruncate;
|
||||
nsresult res = TruncateInsertionIfNeeded(aSelection, &inString, &outString,
|
||||
aMaxLength, &didTruncate);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (didTruncate) {
|
||||
*aCancel = PR_TRUE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
*aCancel = PR_FALSE;
|
||||
|
||||
// if the selection isn't collapsed, delete it.
|
||||
PRBool bCollapsed;
|
||||
nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
|
||||
res = aSelection->GetIsCollapsed(&bCollapsed);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (!bCollapsed)
|
||||
{
|
||||
|
@ -455,6 +474,12 @@ nsTextEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo
|
|||
|
||||
nsresult
|
||||
nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsTextEditRules::CollapseSelectionToTrailingBRIfNeeded(nsISelection* aSelection)
|
||||
{
|
||||
// we only need to execute the stuff below if we are a plaintext editor.
|
||||
// html editors have a different mechanism for putting in mozBR's
|
||||
|
@ -463,40 +488,40 @@ nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
// if we are at the end of the document, we need to insert
|
||||
// a special mozBR following the normal br, and then set the
|
||||
// selection to stick to the mozBR.
|
||||
// if we are at the end of the textarea, we need to set the
|
||||
// selection to stick to the mozBR at the end of the textarea.
|
||||
PRInt32 selOffset;
|
||||
nsCOMPtr<nsIDOMNode> selNode;
|
||||
nsresult res;
|
||||
res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// confirm we are at end of document
|
||||
if (selOffset == 0) return NS_OK; // can't be after a br if we are at offset 0
|
||||
nsIDOMElement *rootElem = mEditor->GetRoot();
|
||||
|
||||
nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(selNode);
|
||||
if (!nodeAsText) return NS_OK; // nothing to do if we're not at a text node
|
||||
|
||||
PRUint32 length;
|
||||
res = nodeAsText->GetLength(&length);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// nothing to do if we're not at the end of the text node
|
||||
if (selOffset != length) return NS_OK;
|
||||
|
||||
nsCOMPtr<nsIDOMNode> parentNode;
|
||||
PRInt32 parentOffset;
|
||||
res = nsEditor::GetNodeLocation(selNode, address_of(parentNode),
|
||||
&parentOffset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
nsIDOMElement *rootElem = mEditor->GetRoot();
|
||||
nsCOMPtr<nsIDOMNode> root = do_QueryInterface(rootElem);
|
||||
NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
|
||||
if (selNode != root) return NS_OK; // must be inside text node or somewhere other than end of root
|
||||
if (parentNode != root) return NS_OK;
|
||||
|
||||
nsCOMPtr<nsIDOMNode> temp = mEditor->GetChildAt(selNode, selOffset);
|
||||
if (temp) return NS_OK; // can't be at end if there is a node after us.
|
||||
|
||||
nsCOMPtr<nsIDOMNode> nearNode = mEditor->GetChildAt(selNode, selOffset-1);
|
||||
if (nearNode && nsTextEditUtils::IsBreak(nearNode) && !nsTextEditUtils::IsMozBR(nearNode))
|
||||
nsCOMPtr<nsIDOMNode> nextNode = mEditor->GetChildAt(parentNode,
|
||||
parentOffset + 1);
|
||||
if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
|
||||
{
|
||||
nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection));
|
||||
// need to insert special moz BR. Why? Because if we don't
|
||||
// the user will see no new line for the break. Also, things
|
||||
// like table cells won't grow in height.
|
||||
nsCOMPtr<nsIDOMNode> brNode;
|
||||
res = CreateMozBR(selNode, selOffset, address_of(brNode));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
res = nsEditor::GetNodeLocation(brNode, address_of(selNode), &selOffset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
selPrivate->SetInterlinePosition(PR_TRUE);
|
||||
res = aSelection->Collapse(selNode, selOffset);
|
||||
res = aSelection->Collapse(parentNode, parentOffset + 1);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
return res;
|
||||
|
@ -633,7 +658,7 @@ nsTextEditRules::WillInsertText(PRInt32 aAction,
|
|||
|
||||
// handle docs with a max length
|
||||
// NOTE, this function copies inString into outString for us.
|
||||
nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength);
|
||||
nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength, nsnull);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
PRUint32 start = 0;
|
||||
|
@ -748,143 +773,32 @@ nsTextEditRules::WillInsertText(PRInt32 aAction,
|
|||
nsCOMPtr<nsIDOMNode> curNode = selNode;
|
||||
PRInt32 curOffset = selOffset;
|
||||
|
||||
// is our text going to be PREformatted?
|
||||
// We remember this so that we know how to handle tabs.
|
||||
PRBool isPRE;
|
||||
res = mEditor->IsPreformatted(selNode, &isPRE);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// don't spaz my selection in subtransactions
|
||||
nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
|
||||
nsString tString(*outString);
|
||||
const PRUnichar *unicodeBuf = tString.get();
|
||||
nsCOMPtr<nsIDOMNode> unused;
|
||||
PRInt32 pos = 0;
|
||||
|
||||
// for efficiency, break out the pre case separately. This is because
|
||||
// it's a lot cheaper to search the input string for only newlines than
|
||||
// it is to search for both tabs and newlines.
|
||||
if (isPRE)
|
||||
{
|
||||
while (unicodeBuf && (pos != -1) && ((PRUint32)pos < tString.Length()))
|
||||
{
|
||||
PRInt32 oldPos = pos;
|
||||
PRInt32 subStrLen;
|
||||
pos = tString.FindChar(nsCRT::LF, oldPos);
|
||||
|
||||
if (pos != -1)
|
||||
{
|
||||
subStrLen = pos - oldPos;
|
||||
// if first char is newline, then use just it
|
||||
if (subStrLen == 0)
|
||||
subStrLen = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
subStrLen = tString.Length() - oldPos;
|
||||
pos = tString.Length();
|
||||
}
|
||||
|
||||
nsDependentSubstring subStr(tString, oldPos, subStrLen);
|
||||
|
||||
// is it a return?
|
||||
if (subStr.EqualsLiteral(LFSTR))
|
||||
{
|
||||
if (IsSingleLineEditor())
|
||||
{
|
||||
NS_ASSERTION((mEditor->mNewlineHandling == nsIPlaintextEditor::eNewlinesPasteIntact),
|
||||
"Newline improperly getting into single-line edit field!");
|
||||
res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = mEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
|
||||
|
||||
// If the newline is the last character in the string, and the BR we
|
||||
// just inserted is the last node in the content tree, we need to add
|
||||
// a mozBR so that a blank line is created.
|
||||
|
||||
if (NS_SUCCEEDED(res) && curNode && pos == (PRInt32)(tString.Length() - 1))
|
||||
{
|
||||
nsCOMPtr<nsIDOMNode> nextChild = mEditor->GetChildAt(curNode, curOffset);
|
||||
|
||||
if (!nextChild)
|
||||
{
|
||||
// We must be at the end since there isn't a nextChild.
|
||||
//
|
||||
// curNode and curOffset should be set to the position after
|
||||
// the BR we added above, so just create a mozBR at that position.
|
||||
//
|
||||
// Note that we don't update curOffset after we've created/inserted
|
||||
// the mozBR since we never want the selection to be placed after it.
|
||||
|
||||
res = CreateMozBR(curNode, curOffset, address_of(unused));
|
||||
}
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char specialChars[] = {TAB, nsCRT::LF, 0};
|
||||
while (unicodeBuf && (pos != -1) && ((PRUint32)pos < tString.Length()))
|
||||
{
|
||||
PRInt32 oldPos = pos;
|
||||
PRInt32 subStrLen;
|
||||
pos = tString.FindCharInSet(specialChars, oldPos);
|
||||
|
||||
if (pos != -1)
|
||||
{
|
||||
subStrLen = pos - oldPos;
|
||||
// if first char is newline, then use just it
|
||||
if (subStrLen == 0)
|
||||
subStrLen = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
subStrLen = tString.Length() - oldPos;
|
||||
pos = tString.Length();
|
||||
}
|
||||
|
||||
nsDependentSubstring subStr(tString, oldPos, subStrLen);
|
||||
|
||||
// is it a tab?
|
||||
if (subStr.EqualsLiteral("\t"))
|
||||
{
|
||||
res = mEditor->InsertTextImpl(NS_LITERAL_STRING(" "), address_of(curNode), &curOffset, doc);
|
||||
pos++;
|
||||
}
|
||||
// is it a return?
|
||||
else if (subStr.EqualsLiteral(LFSTR))
|
||||
{
|
||||
res = mEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
}
|
||||
outString->Assign(tString);
|
||||
res = mEditor->InsertTextImpl(*outString, address_of(curNode),
|
||||
&curOffset, doc);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
if (curNode)
|
||||
{
|
||||
aSelection->Collapse(curNode, curOffset);
|
||||
|
||||
// Make the caret attach to the inserted text, unless this text ends with a LF,
|
||||
// in which case make the caret attach to the next line.
|
||||
PRBool endsWithLF = !tString.IsEmpty() && tString.get()[tString.Length() - 1] == nsCRT::LF;
|
||||
PRBool endsWithLF =
|
||||
!outString->IsEmpty() && outString->Last() == nsCRT::LF;
|
||||
nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection));
|
||||
selPrivate->SetInterlinePosition(endsWithLF);
|
||||
|
||||
// If the last character is a linefeed character, make sure that we inject
|
||||
// a BR element for correct caret positioning.
|
||||
if (endsWithLF) {
|
||||
nsCOMPtr<nsIDOMNode> mozBR;
|
||||
res = CreateMozBR(curNode, curOffset, address_of(mozBR));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
curNode = mozBR;
|
||||
curOffset = 0;
|
||||
}
|
||||
aSelection->Collapse(curNode, curOffset);
|
||||
}
|
||||
}
|
||||
ASSERT_PASSWORD_LENGTHS_EQUAL()
|
||||
|
@ -1358,12 +1272,16 @@ nsresult
|
|||
nsTextEditRules::TruncateInsertionIfNeeded(nsISelection *aSelection,
|
||||
const nsAString *aInString,
|
||||
nsAString *aOutString,
|
||||
PRInt32 aMaxLength)
|
||||
PRInt32 aMaxLength,
|
||||
PRBool *aTruncated)
|
||||
{
|
||||
if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;}
|
||||
|
||||
nsresult res = NS_OK;
|
||||
*aOutString = *aInString;
|
||||
if (aTruncated) {
|
||||
*aTruncated = PR_FALSE;
|
||||
}
|
||||
|
||||
if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() )
|
||||
{
|
||||
|
@ -1396,6 +1314,9 @@ nsTextEditRules::TruncateInsertionIfNeeded(nsISelection *aSelection,
|
|||
if (resultingDocLength >= aMaxLength)
|
||||
{
|
||||
aOutString->Truncate();
|
||||
if (aTruncated) {
|
||||
*aTruncated = PR_TRUE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1403,6 +1324,9 @@ nsTextEditRules::TruncateInsertionIfNeeded(nsISelection *aSelection,
|
|||
if (inCount + resultingDocLength > aMaxLength)
|
||||
{
|
||||
aOutString->Truncate(aMaxLength - resultingDocLength);
|
||||
if (aTruncated) {
|
||||
*aTruncated = PR_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,7 +160,8 @@ protected:
|
|||
nsresult DidInsertText(nsISelection *aSelection, nsresult aResult);
|
||||
nsresult GetTopEnclosingPre(nsIDOMNode *aNode, nsIDOMNode** aOutPreNode);
|
||||
|
||||
nsresult WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled);
|
||||
nsresult WillInsertBreak(nsISelection *aSelection, PRBool *aCancel,
|
||||
PRBool *aHandled, PRInt32 aMaxLength);
|
||||
nsresult DidInsertBreak(nsISelection *aSelection, nsresult aResult);
|
||||
|
||||
nsresult WillInsert(nsISelection *aSelection, PRBool *aCancel);
|
||||
|
@ -218,7 +219,8 @@ protected:
|
|||
nsresult TruncateInsertionIfNeeded(nsISelection *aSelection,
|
||||
const nsAString *aInString,
|
||||
nsAString *aOutString,
|
||||
PRInt32 aMaxLength);
|
||||
PRInt32 aMaxLength,
|
||||
PRBool *aTruncated);
|
||||
|
||||
/** Remove IME composition text from password buffer */
|
||||
nsresult RemoveIMETextFromPWBuf(PRUint32 &aStart, nsAString *aIMEString);
|
||||
|
@ -233,6 +235,8 @@ protected:
|
|||
|
||||
nsresult HideLastPWInput();
|
||||
|
||||
nsresult CollapseSelectionToTrailingBRIfNeeded(nsISelection *aSelection);
|
||||
|
||||
PRBool IsPasswordEditor() const
|
||||
{
|
||||
return mEditor ? mEditor->IsPasswordEditor() : PR_FALSE;
|
||||
|
|
|
@ -47,6 +47,7 @@ include $(topsrcdir)/config/rules.mk
|
|||
_TEST_FILES = \
|
||||
test_bug471722.html \
|
||||
test_bug569988.html \
|
||||
test_bug590554.html \
|
||||
$(NULL)
|
||||
|
||||
# disables the key handling test on gtk2 because gtk2 overrides some key events
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=590554
|
||||
-->
|
||||
|
||||
<head>
|
||||
<title>Test for Bug 590554</title>
|
||||
<script type="application/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 590554 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SimpleTest.waitForFocus(function() {
|
||||
var t = document.querySelector("textarea");
|
||||
t.focus();
|
||||
synthesizeKey("VK_ENTER", {});
|
||||
is(t.value, "\n", "Pressing enter should work the first time");
|
||||
synthesizeKey("VK_ENTER", {});
|
||||
is(t.value, "\n", "Pressing enter should not work the second time");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<textarea maxlength="1"></textarea>
|
||||
</body>
|
||||
</html>
|
|
@ -292,6 +292,18 @@ function runTests()
|
|||
is(fm.focusedElement, aElement,
|
||||
aDescription + "focus moved unexpectedly (Tab)");
|
||||
|
||||
// If the editor is not tabbable, make sure that it accepts tab characters
|
||||
// even if it's empty.
|
||||
if (!aIsTabbable && !aIsReadonly) {
|
||||
reset("");
|
||||
synthesizeKey("VK_TAB", {});
|
||||
check(aDescription + "Tab on empty textarea",
|
||||
true, true, !aIsReadonly);
|
||||
is(aElement.value, "\t", aDescription + "Tab on empty textarea");
|
||||
is(fm.focusedElement, aElement,
|
||||
aDescription + "focus moved unexpectedly (Tab on empty textarea");
|
||||
}
|
||||
|
||||
reset("a");
|
||||
synthesizeKey("VK_TAB", { shiftKey: true });
|
||||
check(aDescription + "Shift+Tab", true, true, false);
|
||||
|
|
|
@ -2370,6 +2370,11 @@ nsTextServicesDocument::ClearDidSkip(nsIContentIterator* aFilteredIter)
|
|||
PRBool
|
||||
nsTextServicesDocument::IsBlockNode(nsIContent *aContent)
|
||||
{
|
||||
if (!aContent) {
|
||||
NS_ERROR("How did a null pointer get passed to IsBlockNode?");
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
nsIAtom *atom = aContent->Tag();
|
||||
|
||||
return (sAAtom != atom &&
|
||||
|
|
|
@ -47,6 +47,9 @@ namespace layers {
|
|||
|
||||
CanvasLayerD3D9::~CanvasLayerD3D9()
|
||||
{
|
||||
if (mD3DManager->deviceManager()) {
|
||||
mD3DManager->deviceManager()->mLayersWithResources.RemoveElement(this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -83,23 +86,14 @@ CanvasLayerD3D9::Initialize(const Data& aData)
|
|||
|
||||
mIsInteropTexture = false;
|
||||
|
||||
if (mD3DManager->deviceManager()->HasDynamicTextures()) {
|
||||
device()->CreateTexture(mBounds.width, mBounds.height, 1, D3DUSAGE_DYNAMIC,
|
||||
D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT,
|
||||
getter_AddRefs(mTexture), NULL);
|
||||
} else {
|
||||
// D3DPOOL_MANAGED is fine here since we require Dynamic Textures for D3D9Ex
|
||||
// devices.
|
||||
device()->CreateTexture(mBounds.width, mBounds.height, 1, 0,
|
||||
D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
|
||||
getter_AddRefs(mTexture), NULL);
|
||||
}
|
||||
CreateTexture();
|
||||
}
|
||||
|
||||
void
|
||||
CanvasLayerD3D9::Updated(const nsIntRect& aRect)
|
||||
{
|
||||
if (!mTexture) {
|
||||
CreateTexture();
|
||||
NS_WARNING("CanvasLayerD3D9::Updated called but no texture present!");
|
||||
return;
|
||||
}
|
||||
|
@ -115,7 +109,12 @@ CanvasLayerD3D9::Updated(const nsIntRect& aRect)
|
|||
if (mGLContext) {
|
||||
// WebGL reads entire surface.
|
||||
D3DLOCKED_RECT r;
|
||||
mTexture->LockRect(0, &r, NULL, 0);
|
||||
HRESULT hr = mTexture->LockRect(0, &r, NULL, 0);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
NS_WARNING("Failed to lock CanvasLayer texture.");
|
||||
return;
|
||||
}
|
||||
|
||||
PRUint8 *destination;
|
||||
if (r.Pitch != mBounds.width * 4) {
|
||||
|
@ -171,7 +170,12 @@ CanvasLayerD3D9::Updated(const nsIntRect& aRect)
|
|||
r.bottom = aRect.YMost();
|
||||
|
||||
D3DLOCKED_RECT lockedRect;
|
||||
mTexture->LockRect(0, &lockedRect, &r, 0);
|
||||
HRESULT hr = mTexture->LockRect(0, &lockedRect, &r, 0);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
NS_WARNING("Failed to lock CanvasLayer texture.");
|
||||
return;
|
||||
}
|
||||
|
||||
PRUint8 *startBits;
|
||||
PRUint32 sourceStride;
|
||||
|
@ -225,6 +229,10 @@ CanvasLayerD3D9::GetLayer()
|
|||
void
|
||||
CanvasLayerD3D9::RenderLayer()
|
||||
{
|
||||
if (!mTexture) {
|
||||
Updated(mBounds);
|
||||
}
|
||||
|
||||
float quadTransform[4][4];
|
||||
/*
|
||||
* Matrix to transform the <0.0,0.0>, <1.0,1.0> quad to the correct position
|
||||
|
@ -273,5 +281,30 @@ CanvasLayerD3D9::RenderLayer()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
CanvasLayerD3D9::CleanResources()
|
||||
{
|
||||
if (mD3DManager->deviceManager()->HasDynamicTextures()) {
|
||||
// In this case we have a texture in POOL_DEFAULT
|
||||
mTexture = nsnull;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CanvasLayerD3D9::CreateTexture()
|
||||
{
|
||||
if (mD3DManager->deviceManager()->HasDynamicTextures()) {
|
||||
device()->CreateTexture(mBounds.width, mBounds.height, 1, D3DUSAGE_DYNAMIC,
|
||||
D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT,
|
||||
getter_AddRefs(mTexture), NULL);
|
||||
} else {
|
||||
// D3DPOOL_MANAGED is fine here since we require Dynamic Textures for D3D9Ex
|
||||
// devices.
|
||||
device()->CreateTexture(mBounds.width, mBounds.height, 1, 0,
|
||||
D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
|
||||
getter_AddRefs(mTexture), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace layers */
|
||||
} /* namespace mozilla */
|
||||
|
|
|
@ -59,6 +59,7 @@ public:
|
|||
mNeedsYFlip(PR_FALSE)
|
||||
{
|
||||
mImplData = static_cast<LayerD3D9*>(this);
|
||||
aManager->deviceManager()->mLayersWithResources.AppendElement(this);
|
||||
}
|
||||
|
||||
~CanvasLayerD3D9();
|
||||
|
@ -70,6 +71,9 @@ public:
|
|||
// LayerD3D9 implementation
|
||||
virtual Layer* GetLayer();
|
||||
virtual void RenderLayer();
|
||||
virtual void CleanResources();
|
||||
|
||||
void CreateTexture();
|
||||
|
||||
protected:
|
||||
typedef mozilla::gl::GLContext GLContext;
|
||||
|
|
|
@ -518,8 +518,8 @@ DeviceManagerD3D9::VerifyReadyForRendering()
|
|||
return false;
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < mThebesLayers.Length(); i++) {
|
||||
mThebesLayers[i]->CleanResources();
|
||||
for(unsigned int i = 0; i < mLayersWithResources.Length(); i++) {
|
||||
mLayersWithResources[i]->CleanResources();
|
||||
}
|
||||
for(unsigned int i = 0; i < mSwapChains.Length(); i++) {
|
||||
mSwapChains[i]->Reset();
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace mozilla {
|
|||
namespace layers {
|
||||
|
||||
class DeviceManagerD3D9;
|
||||
class ThebesLayerD3D9;
|
||||
class LayerD3D9;
|
||||
class Nv3DVUtils;
|
||||
|
||||
/**
|
||||
|
@ -146,10 +146,10 @@ public:
|
|||
Nv3DVUtils *GetNv3DVUtils() { return mNv3DVUtils; }
|
||||
|
||||
/**
|
||||
* We keep a list of all thebes layers since we need their D3DPOOL_DEFAULT
|
||||
* surfaces to be released when we want to reset the device.
|
||||
* We keep a list of all layers here that may have hardware resource allocated
|
||||
* so we can clean their resources on reset.
|
||||
*/
|
||||
nsTArray<ThebesLayerD3D9*> mThebesLayers;
|
||||
nsTArray<LayerD3D9*> mLayersWithResources;
|
||||
private:
|
||||
friend class SwapChainD3D9;
|
||||
|
||||
|
|
|
@ -233,6 +233,11 @@ public:
|
|||
|
||||
virtual void RenderLayer() = 0;
|
||||
|
||||
/* This function may be used on device resets to clear all VRAM resources
|
||||
* that a layer might be using.
|
||||
*/
|
||||
virtual void CleanResources() {}
|
||||
|
||||
IDirect3DDevice9 *device() const { return mD3DManager->device(); }
|
||||
protected:
|
||||
LayerManagerD3D9 *mD3DManager;
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Corporation code.
|
||||
* The Original Code is NVIDIA Corporation Code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* The Initial Developer of the Original Code is NVIDIA Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Corporation code.
|
||||
* The Original Code is NVIDIA Corporation Code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* The Initial Developer of the Original Code is NVIDIA Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
|
|
|
@ -52,13 +52,13 @@ ThebesLayerD3D9::ThebesLayerD3D9(LayerManagerD3D9 *aManager)
|
|||
, mD2DSurfaceInitialized(false)
|
||||
{
|
||||
mImplData = static_cast<LayerD3D9*>(this);
|
||||
aManager->deviceManager()->mThebesLayers.AppendElement(this);
|
||||
aManager->deviceManager()->mLayersWithResources.AppendElement(this);
|
||||
}
|
||||
|
||||
ThebesLayerD3D9::~ThebesLayerD3D9()
|
||||
{
|
||||
if (mD3DManager->deviceManager()) {
|
||||
mD3DManager->deviceManager()->mThebesLayers.RemoveElement(this);
|
||||
mD3DManager->deviceManager()->mLayersWithResources.RemoveElement(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,4 +10,4 @@
|
|||
# hardcoded milestones in the tree from these two files.
|
||||
#--------------------------------------------------------
|
||||
|
||||
2.0b6pre
|
||||
2.0b7pre
|
||||
|
|
|
@ -2611,6 +2611,8 @@ x86_64*-*)
|
|||
arm*-*)
|
||||
ENABLE_TRACEJIT=1
|
||||
NANOJIT_ARCH=ARM
|
||||
ENABLE_METHODJIT=1
|
||||
ENABLE_MONOIC=1
|
||||
AC_DEFINE(JS_CPU_ARM)
|
||||
AC_DEFINE(JS_NUNBOX32)
|
||||
;;
|
||||
|
|
|
@ -205,7 +205,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32 id);
|
|||
* before deserialization of bytecode. If the saved version does not match
|
||||
* the current version, abort deserialization and invalidate the file.
|
||||
*/
|
||||
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 68)
|
||||
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 70)
|
||||
|
||||
/*
|
||||
* Library-private functions.
|
||||
|
|
|
@ -139,20 +139,20 @@ struct CallICInfo {
|
|||
JSC::CodeLocationJump funJump;
|
||||
|
||||
/* Offset to inline scripted call, from funGuard. */
|
||||
uint32 hotCallOffset : 8;
|
||||
uint32 joinPointOffset : 8;
|
||||
uint32 hotCallOffset : 16;
|
||||
uint32 joinPointOffset : 16;
|
||||
|
||||
/* Out of line slow call. */
|
||||
uint32 oolCallOffset : 8;
|
||||
uint32 oolCallOffset : 16;
|
||||
|
||||
/* Jump to patch for out-of-line scripted calls. */
|
||||
uint32 oolJumpOffset : 8;
|
||||
uint32 oolJumpOffset : 16;
|
||||
|
||||
/* Offset for deep-fun check to rejoin at. */
|
||||
uint32 hotPathOffset : 8;
|
||||
uint32 hotPathOffset : 16;
|
||||
|
||||
/* Join point for all slow call paths. */
|
||||
uint32 slowJoinOffset : 9;
|
||||
uint32 slowJoinOffset : 16;
|
||||
|
||||
RegisterID funObjReg : 5;
|
||||
RegisterID funPtrReg : 5;
|
||||
|
|
|
@ -178,7 +178,6 @@ nsCaret::nsCaret()
|
|||
#endif
|
||||
, mLastContentOffset(0)
|
||||
, mLastHint(nsFrameSelection::HINTLEFT)
|
||||
, mLastFrameOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -301,7 +300,6 @@ void nsCaret::Terminate()
|
|||
mPresShell = nsnull;
|
||||
|
||||
mLastContent = nsnull;
|
||||
mLastFrame = nsnull;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -505,8 +503,8 @@ nsIFrame * nsCaret::GetCaretFrame(PRInt32 *aOffset)
|
|||
if (!mDrawn)
|
||||
return nsnull;
|
||||
|
||||
// Recompute the frame that we're supposed to draw in if the cached frame
|
||||
// is stale (dead).
|
||||
// Recompute the frame that we're supposed to draw in to guarantee that
|
||||
// we're not going to try to draw into a stale (dead) frame.
|
||||
PRInt32 offset;
|
||||
nsIFrame *frame = nsnull;
|
||||
nsresult rv = GetCaretFrameForNodeOffset(mLastContent, mLastContentOffset,
|
||||
|
@ -714,8 +712,6 @@ nsCaret::DrawAtPositionWithHint(nsIDOMNode* aNode,
|
|||
mLastContentOffset = aOffset;
|
||||
mLastHint = aFrameHint;
|
||||
mLastBidiLevel = aBidiLevel;
|
||||
mLastFrame = theFrame;
|
||||
mLastFrameOffset = theFrameOffset;
|
||||
|
||||
// If there has been a reflow, set the caret Bidi level to the level of the current frame
|
||||
if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
|
||||
|
@ -744,16 +740,6 @@ nsCaret::GetCaretFrameForNodeOffset(nsIContent* aContentNode,
|
|||
nsIFrame** aReturnFrame,
|
||||
PRInt32* aReturnOffset)
|
||||
{
|
||||
// Try to see if we can use our cached frame
|
||||
if (mLastFrame.IsAlive() &&
|
||||
mLastContent == aContentNode &&
|
||||
mLastContentOffset == aOffset &&
|
||||
mLastHint == aFrameHint &&
|
||||
mLastBidiLevel == aBidiLevel) {
|
||||
*aReturnFrame = mLastFrame;
|
||||
*aReturnOffset = mLastFrameOffset;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//get frame selection and find out what frame to use...
|
||||
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
|
||||
|
|
|
@ -293,11 +293,8 @@ protected:
|
|||
// actually drawn (anon <BR> in text control)
|
||||
PRInt32 mLastContentOffset; // the offset for the last request
|
||||
|
||||
nsFrameSelection::HINT mLastHint; // the hint associated with the last request, see also
|
||||
// mLastBidiLevel above
|
||||
|
||||
nsWeakFrame mLastFrame; // the last frame on which the caret has been drawn.
|
||||
PRInt32 mLastFrameOffset; // the frame offset for the last caret position
|
||||
nsFrameSelection::HINT mLastHint; // the hint associated with the last request, see also
|
||||
// mLastBidiLevel below
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -92,6 +92,9 @@ _TEST_FILES = \
|
|||
bug106855-1.html \
|
||||
bug106855-2.html \
|
||||
bug106855-1-ref.html \
|
||||
bug240933-1.html \
|
||||
bug240933-2.html \
|
||||
bug240933-1-ref.html \
|
||||
bug482484.html \
|
||||
bug482484-ref.html \
|
||||
bug512295-1.html \
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML><html>
|
||||
<body>
|
||||
<textarea id="t" rows="4">
|
||||
|
||||
</textarea>
|
||||
<script>
|
||||
document.getElementById("t").focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE HTML><html><head>
|
||||
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<textarea id="t" rows="4"></textarea>
|
||||
<script>
|
||||
var area = document.getElementById('t');
|
||||
area.focus();
|
||||
|
||||
sendKey('VK_ENTER'); // press Enter once
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE HTML><html><head>
|
||||
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<textarea id="t" rows="4"></textarea>
|
||||
<script>
|
||||
var area = document.getElementById('t');
|
||||
area.focus();
|
||||
|
||||
sendKey('VK_ENTER'); // press Enter twice
|
||||
sendKey('VK_ENTER');
|
||||
sendKey('VK_BACK_SPACE'); // press Backspace once
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -88,6 +88,8 @@ function endTest() {
|
|||
var tests = [
|
||||
[ 'bug106855-1.html' , 'bug106855-1-ref.html' ] ,
|
||||
[ 'bug106855-2.html' , 'bug106855-1-ref.html' ] ,
|
||||
[ 'bug240933-1.html' , 'bug240933-1-ref.html' ] ,
|
||||
[ 'bug240933-2.html' , 'bug240933-1-ref.html' ] ,
|
||||
[ 'bug482484.html' , 'bug482484-ref.html' ] ,
|
||||
[ 'bug512295-1.html' , 'bug512295-1-ref.html' ] ,
|
||||
[ 'bug512295-2.html' , 'bug512295-2-ref.html' ] ,
|
||||
|
|
|
@ -998,51 +998,27 @@ nsTextControlFrame::DOMPointToOffset(nsIDOMNode* aNode,
|
|||
if (!length || aNodeOffset < 0)
|
||||
return NS_OK;
|
||||
|
||||
PRInt32 i, textOffset = 0;
|
||||
PRInt32 lastIndex = (PRInt32)length - 1;
|
||||
NS_ASSERTION(length <= 2, "We should have one text node and one mozBR at most");
|
||||
|
||||
for (i = 0; i < (PRInt32)length; i++) {
|
||||
if (rootNode == aNode && i == aNodeOffset) {
|
||||
*aResult = textOffset;
|
||||
return NS_OK;
|
||||
}
|
||||
nsCOMPtr<nsIDOMNode> firstNode;
|
||||
rv = nodeList->Item(0, getter_AddRefs(firstNode));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(firstNode);
|
||||
|
||||
nsCOMPtr<nsIDOMNode> item;
|
||||
rv = nodeList->Item(i, getter_AddRefs(item));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(item, NS_ERROR_FAILURE);
|
||||
|
||||
nsCOMPtr<nsIDOMText> domText(do_QueryInterface(item));
|
||||
|
||||
if (domText) {
|
||||
PRUint32 textLength = 0;
|
||||
|
||||
rv = domText->GetLength(&textLength);
|
||||
nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(aNode);
|
||||
if (nodeAsText || (aNode == rootNode && aNodeOffset == 0)) {
|
||||
// Selection is somewhere inside the text node; the offset is aNodeOffset
|
||||
*aResult = aNodeOffset;
|
||||
} else {
|
||||
// Selection is on the mozBR node, so offset should be set to the length
|
||||
// of the text node.
|
||||
if (textNode) {
|
||||
rv = textNode->GetLength(&length);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (item == aNode) {
|
||||
NS_ASSERTION((aNodeOffset >= 0 && aNodeOffset <= (PRInt32)textLength),
|
||||
"Invalid aNodeOffset!");
|
||||
*aResult = textOffset + aNodeOffset;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
textOffset += textLength;
|
||||
}
|
||||
else {
|
||||
// Must be a BR node. If it's not the last BR node
|
||||
// under the root, count it as a newline.
|
||||
|
||||
if (i != lastIndex)
|
||||
++textOffset;
|
||||
*aResult = PRInt32(length);
|
||||
}
|
||||
}
|
||||
|
||||
NS_ASSERTION((aNode == rootNode && aNodeOffset == (PRInt32)length),
|
||||
"Invalid node offset!");
|
||||
|
||||
*aResult = textOffset;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1074,71 +1050,25 @@ nsTextControlFrame::OffsetToDOMPoint(PRInt32 aOffset,
|
|||
rv = nodeList->GetLength(&length);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!length || aOffset < 0) {
|
||||
NS_ASSERTION(length <= 2, "We should have one text node and one mozBR at most");
|
||||
|
||||
nsCOMPtr<nsIDOMNode> firstNode;
|
||||
rv = nodeList->Item(0, getter_AddRefs(firstNode));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(firstNode);
|
||||
|
||||
if (length == 0 || aOffset < 0) {
|
||||
NS_IF_ADDREF(*aResult = rootNode);
|
||||
*aPosition = 0;
|
||||
} else if (textNode) {
|
||||
NS_IF_ADDREF(*aResult = firstNode);
|
||||
*aPosition = aOffset;
|
||||
} else {
|
||||
NS_IF_ADDREF(*aResult = rootNode);
|
||||
*aPosition = 0;
|
||||
*aResult = rootNode;
|
||||
NS_ADDREF(*aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PRInt32 textOffset = 0;
|
||||
PRUint32 lastIndex = length - 1;
|
||||
|
||||
for (PRUint32 i=0; i<length; i++) {
|
||||
nsCOMPtr<nsIDOMNode> item;
|
||||
rv = nodeList->Item(i, getter_AddRefs(item));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(item, NS_ERROR_FAILURE);
|
||||
|
||||
nsCOMPtr<nsIDOMText> domText(do_QueryInterface(item));
|
||||
|
||||
if (domText) {
|
||||
PRUint32 textLength = 0;
|
||||
|
||||
rv = domText->GetLength(&textLength);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Check if aOffset falls within this range.
|
||||
if (aOffset >= textOffset && aOffset <= textOffset+(PRInt32)textLength) {
|
||||
*aPosition = aOffset - textOffset;
|
||||
*aResult = item;
|
||||
NS_ADDREF(*aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
textOffset += textLength;
|
||||
|
||||
// If there aren't any more siblings after this text node,
|
||||
// return the point at the end of this text node!
|
||||
|
||||
if (i == lastIndex) {
|
||||
*aPosition = textLength;
|
||||
*aResult = item;
|
||||
NS_ADDREF(*aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Must be a BR node, count it as a newline.
|
||||
|
||||
if (aOffset == textOffset || i == lastIndex) {
|
||||
// We've found the correct position, or aOffset takes us
|
||||
// beyond the last child under rootNode, just return the point
|
||||
// under rootNode that is in front of this br.
|
||||
|
||||
*aPosition = i;
|
||||
*aResult = rootNode;
|
||||
NS_ADDREF(*aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
++textOffset;
|
||||
}
|
||||
}
|
||||
|
||||
NS_ERROR("We should never get here!");
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -5071,14 +5071,61 @@ nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
|
|||
nsIFrame* aFindFrame, PRBool* aFoundValidLine)
|
||||
: mFrame(aFrame), mInOverflowLines(nsnull)
|
||||
{
|
||||
mLine = aFrame->begin_lines();
|
||||
|
||||
*aFoundValidLine = PR_FALSE;
|
||||
|
||||
nsIFrame* child = FindChildContaining(aFrame, aFindFrame);
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
// Try to use the cursor if it exists, otherwise fall back to the first line
|
||||
nsLineBox* cursor = static_cast<nsLineBox*>
|
||||
(aFrame->Properties().Get(LineCursorProperty()));
|
||||
if (!cursor) {
|
||||
line_iterator iter = aFrame->begin_lines();
|
||||
if (iter != aFrame->end_lines()) {
|
||||
cursor = iter;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
// Perform a simultaneous forward and reverse search starting from the
|
||||
// line cursor.
|
||||
nsBlockFrame::line_iterator line = aFrame->line(cursor);
|
||||
nsBlockFrame::reverse_line_iterator rline = aFrame->rline(cursor);
|
||||
nsBlockFrame::line_iterator line_end = aFrame->end_lines();
|
||||
nsBlockFrame::reverse_line_iterator rline_end = aFrame->rend_lines();
|
||||
for (--rline;;) {
|
||||
if (line == line_end && rline == rline_end) {
|
||||
// Didn't find the line
|
||||
break;
|
||||
}
|
||||
if (line != line_end) {
|
||||
if (line->Contains(child)) {
|
||||
*aFoundValidLine = PR_TRUE;
|
||||
mLine = line;
|
||||
return;
|
||||
}
|
||||
++line;
|
||||
}
|
||||
if (rline != rline_end) {
|
||||
if (rline->Contains(child)) {
|
||||
*aFoundValidLine = PR_TRUE;
|
||||
mLine = rline;
|
||||
return;
|
||||
}
|
||||
++rline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, it means that we have not been able to find the
|
||||
// desired frame in our in-flow lines. So we should start looking at
|
||||
// our overflow lines. In order to do that, we set mLine to the end
|
||||
// iterator so that FindValidLine starts to look at overflow lines,
|
||||
// if any.
|
||||
|
||||
mLine = aFrame->end_lines();
|
||||
|
||||
if (!FindValidLine())
|
||||
return;
|
||||
|
||||
|
|
|
@ -156,6 +156,8 @@ public:
|
|||
reverse_line_iterator rend_lines() { return mLines.rend(); }
|
||||
const_reverse_line_iterator rbegin_lines() const { return mLines.rbegin(); }
|
||||
const_reverse_line_iterator rend_lines() const { return mLines.rend(); }
|
||||
line_iterator line(nsLineBox* aList) { return mLines.begin(aList); }
|
||||
reverse_line_iterator rline(nsLineBox* aList) { return mLines.rbegin(aList); }
|
||||
|
||||
friend nsIFrame* NS_NewBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aFlags);
|
||||
|
||||
|
|
|
@ -5348,6 +5348,7 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
|
|||
{
|
||||
PRBool eatingNonRenderableWS = PR_FALSE;
|
||||
PRBool done = PR_FALSE;
|
||||
PRBool jumpedLine = PR_FALSE;
|
||||
|
||||
while (!done) {
|
||||
PRBool movingInFrameDirection =
|
||||
|
@ -5359,7 +5360,6 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
|
|||
done = current->PeekOffsetCharacter(movingInFrameDirection, &offset);
|
||||
|
||||
if (!done) {
|
||||
PRBool jumpedLine;
|
||||
result =
|
||||
current->GetFrameFromDirection(aPos->mDirection, aPos->mVisual,
|
||||
aPos->mJumpLines, aPos->mScrollViewStop,
|
||||
|
@ -5380,6 +5380,15 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
|
|||
aPos->mResultContent = range.content;
|
||||
// Output offset is relative to content, not frame
|
||||
aPos->mContentOffset = offset < 0 ? range.end : range.start + offset;
|
||||
// If we're dealing with a text frame and moving backward positions us at
|
||||
// the end of that line, decrease the offset by one to make sure that
|
||||
// we're placed before the linefeed character on the previous line.
|
||||
if (offset < 0 && jumpedLine &&
|
||||
aPos->mDirection == eDirPrevious &&
|
||||
current->GetStyleText()->NewlineIsSignificant() &&
|
||||
current->HasTerminalNewline()) {
|
||||
--aPos->mContentOffset;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1704,6 +1704,14 @@ public:
|
|||
PRUint32 aSkippedMaxLength = PR_UINT32_MAX)
|
||||
{ return NS_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
/**
|
||||
* Returns true if the frame contains any non-collapsed characters.
|
||||
* This method is only available for text frames, and it will return false
|
||||
* for all other frame types.
|
||||
*/
|
||||
virtual PRBool HasAnyNoncollapsedCharacters()
|
||||
{ return PR_FALSE; }
|
||||
|
||||
/**
|
||||
* Accessor functions to get/set the associated view object
|
||||
*
|
||||
|
|
|
@ -1226,6 +1226,16 @@ class nsLineList {
|
|||
return rv;
|
||||
}
|
||||
|
||||
reverse_iterator rbegin(nsLineBox* aLine)
|
||||
{
|
||||
reverse_iterator rv;
|
||||
rv.mCurrent = aLine;
|
||||
#ifdef DEBUG
|
||||
rv.mListLink = &mLink;
|
||||
#endif
|
||||
return rv;
|
||||
}
|
||||
|
||||
const_reverse_iterator rend() const
|
||||
{
|
||||
const_reverse_iterator rv;
|
||||
|
|
|
@ -460,6 +460,8 @@ protected:
|
|||
PRBool aForInsertionPoint);
|
||||
|
||||
void ClearFrameOffsetCache();
|
||||
|
||||
virtual PRBool HasAnyNoncollapsedCharacters();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5062,6 +5062,13 @@ nsTextFrame::GetCharacterOffsetAtFramePointInternal(const nsPoint &aPoint,
|
|||
// intrinsic widths.
|
||||
selectedOffset =
|
||||
provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
|
||||
// If we're at the end of a preformatted line which has a terminating
|
||||
// linefeed, we want to reduce the offset by one to make sure that the
|
||||
// selection is placed before the linefeed character.
|
||||
if (GetStyleText()->NewlineIsSignificant() &&
|
||||
HasTerminalNewline()) {
|
||||
--selectedOffset;
|
||||
}
|
||||
}
|
||||
|
||||
offsets.content = GetContent();
|
||||
|
@ -5425,8 +5432,7 @@ IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, gfxTextRun* aTextRu
|
|||
PRUint32 index = aIter.GetSkippedOffset();
|
||||
if (!aTextRun->IsClusterStart(index))
|
||||
return PR_FALSE;
|
||||
return !(aFrame->GetStyleText()->NewlineIsSignificant() &&
|
||||
aTextRun->GetChar(index) == '\n');
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
PRBool
|
||||
|
@ -5451,8 +5457,8 @@ nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
|
|||
PRInt32 startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
|
||||
|
||||
if (!aForward) {
|
||||
PRInt32 i;
|
||||
for (i = NS_MIN(trimmed.GetEnd(), startOffset) - 1;
|
||||
// If at the beginning of the line, look at the previous continuation
|
||||
for (PRInt32 i = NS_MIN(trimmed.GetEnd(), startOffset) - 1;
|
||||
i >= trimmed.mStart; --i) {
|
||||
iter.SetOriginalOffset(i);
|
||||
if (IsAcceptableCaretPosition(iter, mTextRun, this)) {
|
||||
|
@ -5462,16 +5468,19 @@ nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
|
|||
}
|
||||
*aOffset = 0;
|
||||
} else {
|
||||
PRInt32 i;
|
||||
for (i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
|
||||
iter.SetOriginalOffset(i);
|
||||
// XXX we can't necessarily stop at the end of this frame,
|
||||
// but we really have no choice right now. We need to do a deeper
|
||||
// fix/restructuring of PeekOffsetCharacter
|
||||
if (i == trimmed.GetEnd() ||
|
||||
IsAcceptableCaretPosition(iter, mTextRun, this)) {
|
||||
*aOffset = i - mContentOffset;
|
||||
return PR_TRUE;
|
||||
// If we're at the end of a line, look at the next continuation
|
||||
iter.SetOriginalOffset(startOffset);
|
||||
if (iter.GetSkippedOffset() <= PRUint32(trimmed.GetEnd()) &&
|
||||
!(iter.GetSkippedOffset() < PRUint32(trimmed.GetEnd()) &&
|
||||
GetStyleText()->NewlineIsSignificant() &&
|
||||
mTextRun->GetChar(iter.GetSkippedOffset()) == '\n')) {
|
||||
for (PRInt32 i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
|
||||
iter.SetOriginalOffset(i);
|
||||
if (i == trimmed.GetEnd() ||
|
||||
IsAcceptableCaretPosition(iter, mTextRun, this)) {
|
||||
*aOffset = i - mContentOffset;
|
||||
return PR_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
*aOffset = contentLength;
|
||||
|
@ -7122,3 +7131,14 @@ nsTextFrame::IsAtEndOfLine() const
|
|||
{
|
||||
return (GetStateBits() & TEXT_END_OF_LINE) != 0;
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsTextFrame::HasAnyNoncollapsedCharacters()
|
||||
{
|
||||
gfxSkipCharsIterator iter = EnsureTextRun();
|
||||
PRInt32 offset = GetContentOffset(),
|
||||
offsetEnd = GetContentEnd();
|
||||
PRInt32 skippedOffset = iter.ConvertOriginalToSkipped(offset);
|
||||
PRInt32 skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
|
||||
return skippedOffset != skippedOffsetEnd;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ _TEST_FILES = \
|
|||
plugin_clipping_lib.js \
|
||||
plugin_focus_helper.html \
|
||||
test_backspace_delete.xul \
|
||||
test_bug240933.html \
|
||||
test_bug263683.html \
|
||||
test_bug288789.html \
|
||||
test_bug290397.html \
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=240933
|
||||
-->
|
||||
|
||||
<head>
|
||||
<title>Test for Bug 240933</title>
|
||||
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=240933">
|
||||
Mozilla Bug 240933
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 240933 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SimpleTest.waitForFocus(function() {
|
||||
var t = document.getElementById("t");
|
||||
synthesizeMouse(t, t.clientWidth / 2, 5, {}, window);
|
||||
is(t.selectionStart, 3, "The selection should be set before the newline");
|
||||
is(t.selectionEnd, 3, "The selection should be set before the newline");
|
||||
|
||||
t = document.getElementById("ta");
|
||||
t.focus();
|
||||
var val = t.value;
|
||||
synthesizeKey("VK_ENTER", {});
|
||||
is(t.value, val + "\n", "Pressing enter right after focusing the textarea should work");
|
||||
|
||||
t = document.getElementById("tb");
|
||||
t.focus();
|
||||
synthesizeKey("VK_ENTER", {});
|
||||
is(t.value, "\n", "Pressing enter for the first time should work");
|
||||
synthesizeKey("VK_ENTER", {});
|
||||
is(t.value, "\n\n", "Pressing enter for the second time should work");
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
is(t.value, "\n", "Pressing backspace for the first time should work");
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
is(t.value, "", "Pressing backspace for the second time should work");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
||||
<textarea id="t" rows="10" cols="10">abc
|
||||
</textarea>
|
||||
<textarea id="ta" rows="10" cols="10">
|
||||
test
|
||||
|
||||
</textarea>
|
||||
<textarea id="tb" rows="10" cols="10"></textarea>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -18,6 +18,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=288789
|
|||
|
||||
אaב
|
||||
|
||||
</textarea>
|
||||
<textarea id="tb">
|
||||
|
||||
abc
|
||||
|
||||
</textarea>
|
||||
</div>
|
||||
<pre id="test">
|
||||
|
@ -50,9 +55,59 @@ function test() {
|
|||
|
||||
textarea.focus();
|
||||
collapse(0);
|
||||
testLeft(1);
|
||||
collapse(5);
|
||||
testRight(4);
|
||||
ok(true, "Testing forward movement in RTL mode");
|
||||
for (var i = 0; i < textarea.textContent.length; ++i) {
|
||||
if (i == 0) {
|
||||
testRight(i);
|
||||
}
|
||||
if (textarea.textContent[i] == 'a') {
|
||||
testLeft(i);
|
||||
} else {
|
||||
testLeft(i + 1);
|
||||
}
|
||||
if (i == textarea.textContent.length - 1) {
|
||||
testLeft(i + 1);
|
||||
}
|
||||
}
|
||||
ok(true, "Testing backward movement in RTL mode");
|
||||
for (var i = textarea.textContent.length; i > 0; --i) {
|
||||
if (i == textarea.textContent.length) {
|
||||
testLeft(i);
|
||||
}
|
||||
if (i > 0 && textarea.textContent[i - 1] == 'a') {
|
||||
testRight(i);
|
||||
} else {
|
||||
testRight(i - 1);
|
||||
}
|
||||
if (i == 1) {
|
||||
testRight(i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
textarea = $("tb");
|
||||
textarea.focus();
|
||||
collapse(0);
|
||||
ok(true, "Testing forward movement in LTR mode");
|
||||
for (var i = 0; i < textarea.textContent.length; ++i) {
|
||||
if (i == 0) {
|
||||
testLeft(i);
|
||||
}
|
||||
testRight(i + 1);
|
||||
if (i == textarea.textContent.length - 1) {
|
||||
testRight(i + 1);
|
||||
}
|
||||
}
|
||||
ok(true, "Testing backward movement in LTR mode");
|
||||
for (var i = textarea.textContent.length; i > 0; --i) {
|
||||
if (i == textarea.textContent.length) {
|
||||
testRight(i);
|
||||
}
|
||||
testLeft(i - 1);
|
||||
if (i == 1) {
|
||||
testLeft(i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче