This commit is contained in:
Chris Jones 2010-09-14 14:28:39 -05:00
Родитель c97d52bdcf c998393d89
Коммит bdb986dad1
170 изменённых файлов: 3852 добавлений и 1196 удалений

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

@ -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; &gt; &privateBrowsingCmd.stop.label;, or close &brandShortName;.">
<!ENTITY privatebrowsingpage.howToStart "To start Private Browsing, you can also select &toolsMenu.label; &gt; &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; &gt; &privateBrowsingCmd.stop.label;, or close &brandShortName;.">
<!ENTITY privatebrowsingpage.howToStart2 "To start Private Browsing, you can also select &basePBMenu.label; &gt; &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
&#x05d0;a&#x05d1;
</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();
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше