diff --git a/mozilla/kmeleon/build.mk b/mozilla/kmeleon/build.mk index 42da861a..b528d680 100644 --- a/mozilla/kmeleon/build.mk +++ b/mozilla/kmeleon/build.mk @@ -50,6 +50,8 @@ tier_app_dirs += $(MOZ_BRANDING_DIRECTORY) endif tier_app_dirs += \ - profile \ - embedding/config \ + kmeleon \ $(NULL) + +package: + @$(MAKE) -C kmeleon/installer diff --git a/mozilla/kmeleon/config/version.txt b/mozilla/kmeleon/config/version.txt index 0f46cd97..1757a93a 100644 --- a/mozilla/kmeleon/config/version.txt +++ b/mozilla/kmeleon/config/version.txt @@ -1 +1 @@ -1.5.0pre +1.6.0pre diff --git a/mozilla/kmeleon/confvars.sh b/mozilla/kmeleon/confvars.sh index a49ec6cf..50d0b518 100644 --- a/mozilla/kmeleon/confvars.sh +++ b/mozilla/kmeleon/confvars.sh @@ -36,18 +36,19 @@ # # ***** END LICENSE BLOCK ***** -MOZ_APP_NAME=K-Meleon -MOZ_APP_DISPLAYNAME=K-Meleon -MOZ_APP_VERSION=$MOZILLA_VERSION -MOZ_NO_XPCOM_OBSOLETE=1 -MOZ_EXTENSIONS_DEFAULT="wallet xml-rpc typeaheadfind" -MOZ_XUL_APP= -MOZ_DISABLE_PARENTAL_CONTROLS=1 -MOZ_PREF_EXTENSIONS= -MOZ_WEBSERVICES= +MOZ_APP_NAME=kmeleon +ENABLE_TESTS= ACCESSIBILITY= MOZ_JSDEBUGGER= MOZ_PROFILESHARING= -MOZ_UNIVERSALCHARDET=1 -MOZ_CRASHREPORTER= -MOZ_PLACES= \ No newline at end of file +MOZ_APP_DISPLAYNAME=K-Meleon +MOZ_APP_VERSION=$MOZILLA_VERSION +MOZ_EXTENSIONS_DEFAULT= +MOZ_PREF_EXTENSIONS=1 +MOZ_WEBSERVICES= +MOZ_NO_XPCOM_OBSOLETE=1 +MOZ_XPINSTALL= +MOZ_VIEW_SOURCE=1 +MOZ_URL_CLASSIFIER=1 +MOZ_SPELLCHECK= + diff --git a/mozilla/kmeleon/embed-replacements/content/global/about.xhtml b/mozilla/kmeleon/embed-replacements/content/global/about.xhtml new file mode 100644 index 00000000..eb6ee885 --- /dev/null +++ b/mozilla/kmeleon/embed-replacements/content/global/about.xhtml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + +]> + + + + + About &km; + + + + + +
+  Homepage · Release Notes · FAQ  + +
+ + +

+ +
+ + + +

U.S. GOVERNMENT END USERS. The Software is a "commercial item," as that term is defined +in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer software" and "commercial computer software +documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 +C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire the Software with only +those rights set forth herein.

+ + +
+ +

Our &contrib;

+ +

We would like to thank our &contrib; whose efforts make &km; possible. These people have helped by +writing code and documentation, and by testing. They have created and maintained the browser, its associated +development kits, our build tools and our web site.

+ +

To be added to the list, send mail to &mail; with your name and a sentence summarizing what you +have contributed to &km;.

+ + +
+
+ + \ No newline at end of file diff --git a/mozilla/kmeleon/embed-replacements/content/global/logo.gif b/mozilla/kmeleon/embed-replacements/content/global/logo.gif new file mode 100644 index 00000000..f209d99d Binary files /dev/null and b/mozilla/kmeleon/embed-replacements/content/global/logo.gif differ diff --git a/mozilla/kmeleon/embed-replacements/content/global/netError.xhtml b/mozilla/kmeleon/embed-replacements/content/global/netError.xhtml new file mode 100644 index 00000000..90308537 --- /dev/null +++ b/mozilla/kmeleon/embed-replacements/content/global/netError.xhtml @@ -0,0 +1,295 @@ + + + %htmlDTD; + + %netErrorDTD; + + %globalDTD; +]> + + + &loadError.label; + + + + + +
+
+

&generic.title;

+

&dnsNotFound.title;

+

&fileNotFound.title;

+

&malformedURI.title;

+

&protocolNotFound.title;

+

&connectionFailure.title;

+

&netTimeout.title;

+

&redirectLoop.title;

+

&unknownSocketType.title;

+

&netReset.title;

+

&netOffline.title;

+

&netInterrupt.title;

+

&deniedPortAccess.title;

+

&proxyResolveFailure.title;

+

&proxyConnectFailure.title;

+

&contentEncodingError.title;

+

&unsafeContentType.title;

+

&nssFailure2.title;

+

&nssBadCert.title;

+

&malwareBlocked.title;

+
+
+
&generic.longDesc;
+
&dnsNotFound.longDesc;
+
&fileNotFound.longDesc;
+
&malformedURI.longDesc;
+
&protocolNotFound.longDesc;
+
&connectionFailure.longDesc;
+
&netTimeout.longDesc;
+
&redirectLoop.longDesc;
+
&unknownSocketType.longDesc;
+
&netReset.longDesc;
+
&netOffline.longDesc;
+
&netInterrupt.longDesc;
+
&deniedPortAccess.longDesc;
+
&proxyResolveFailure.longDesc;
+
&proxyConnectFailure.longDesc;
+
&contentEncodingError.longDesc;
+
&unsafeContentType.longDesc;
+
&nssFailure2.longDesc;
+
&nssBadCert.longDesc2;
+
&malwareBlocked.longDesc;
+
+
+
+
+
+
+

+

+
+

+

+
+
+ &securityOverride.linkText; + +
+
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/mozilla/kmeleon/embed-replacements/skin/classic/global/netError.css b/mozilla/kmeleon/embed-replacements/skin/classic/global/netError.css new file mode 100644 index 00000000..6988f46a --- /dev/null +++ b/mozilla/kmeleon/embed-replacements/skin/classic/global/netError.css @@ -0,0 +1,151 @@ +.km_Color { + color: #62AC2A; +} +.km_Border { + border-color: #62AC2A; + border-style: solid; +} +.km_TextBorder { + border: 1px solid ThreeDShadow; +} +.km_SpecialBorder { + border-top-width: 0; + border-bottom-width: 2px; + border-left-width: 0; + border-right-width: 0; +} +.km_Icon, .km_Logo { + background-image: url(about:logo); + background-repeat: no-repeat; + background-position: left bottom; +} +body[dir="rtl"] .km_Icon { + background-position: right bottom; +} +.km_IconWidth, .km_LogoWidth { + -moz-margin-start: 150px; + -moz-padding-start: 32px; +} +.km_IconHeight, .km_LogoHeight { + min-height: 200px; +} +.km_Text { + color: -moz-FieldText; + font: message-box; +} +.km_TextBackground { + background-color: -moz-Field; +} +.km_PageBackground { + background-color: -moz-Dialog; +} +.km_PageContainer { + -moz-padding-start: 30px; + -moz-border-radius: 10px; + min-width: 400px; + max-width: 52em; +} +.km_Button { + margin-top: 1.4em; +} +.km_Button button { + margin-top: 0.6em; +} +a { + color: inherit; + background-color: inherit; + text-decoration: underline; +} +a:hover { + color: HighlightText; + background-color: Highlight; + text-decoration: none; +} +body { + margin: 0; + padding: 0 1em; +} +div { + margin: 0; + padding: 0; +} +p, ul, ol { + margin: 1em 0; + padding: 0; +} +h1 { + font-size: 160%; + margin: 0; + padding: 0; +} +h1 + p { + font-size: 130%; + margin: 0; + padding: 1em 0; + border-top-width: 1px; + border-bottom-width: 1px; + border-left-width: 0; + border-right-width: 0; +} +h1 + p + div { + font-size: 110%; +} +ul, ol { + -moz-margin-start: 1.5em; +} +ul { + list-style: square; +} +li { + margin-bottom: 0.5em; +} +div.km_PageContainer { + margin: 4em auto; + padding: 3em; +} +fieldset.km_PageContainer { + margin: 3.35em auto 4em; + padding: 2.35em 3em 3em; +} + +@media print { + +.km_Color, .km_Text { + color: #000; +} +.km_Border { + border-color: #000; +} +.km_TextBorder { + border: 0; +} +.km_Icon, .km_Logo, .km_TextBackground, .km_PageBackground { + background: none; +} +.km_IconWidth, .km_LogoWidth { + -moz-margin-start: 0; + -moz-padding-start: 0; +} +.km_IconHeight, .km_LogoHeight { + min-height: 0; +} +.km_PageContainer { + -moz-padding-start: 0; + -moz-border-radius: 0; + min-width: 90%; + max-width: 90%; +} +fieldset.km_PageContainer legend, .km_Button { + display: none; +} +body, .km_PageContainer { + margin: 0; + padding: 0; +} +a { + color: #000; + background: none; + text-decoration: none; +} + +} \ No newline at end of file diff --git a/mozilla/kmeleon/flashblock/Makefile.in b/mozilla/kmeleon/flashblock/Makefile.in new file mode 100644 index 00000000..7e824777 --- /dev/null +++ b/mozilla/kmeleon/flashblock/Makefile.in @@ -0,0 +1,63 @@ +# ***** 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 just a lowly Makefile. +# +# The Initial Developer of the Original Code is Google Inc. +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Mentovai (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 ***** + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +include $(topsrcdir)/config/rules.mk + +# The presence of jar.mn causes $(SOURCE_CHROME_DIR)/$(JAR_FILE).jar to be +# built and $(SOURCE_CHROME_DIR)/installed-chrome.txt to be updated. Camino +# uses $(TARGET_CHROME_DIR)/installed-chrome.txt instead, though, so copy +# the jar and update the correct installed-chrome.txt file. + +SOURCE_CHROME_DIR = $(DIST)/bin/chrome +TARGET_CHROME_DIR = $(DIST)/Embed/chrome +INSTALLED_CHROME = $(TARGET_CHROME_DIR)/installed-chrome.txt +CHROME_TYPE = content +CHROME_PKG_NAME = flashblock +JAR_FILE = $(CHROME_PKG_NAME) + +libs realchrome:: $(CHROME_DEPS) + chmod a-x $(INSTALLED_CHROME) + $(NSINSTALL) -t -m 0644 \ + $(SOURCE_CHROME_DIR)/$(JAR_FILE).jar $(TARGET_CHROME_DIR) + $(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/add-chrome.pl \ + $(INSTALLED_CHROME) 0 $(CHROME_TYPE) $(CHROME_PKG_NAME) $(JAR_FILE) diff --git a/mozilla/kmeleon/flashblock/README b/mozilla/kmeleon/flashblock/README new file mode 100644 index 00000000..85ba23ef --- /dev/null +++ b/mozilla/kmeleon/flashblock/README @@ -0,0 +1,10 @@ +Flash blocking for Camino is implemented using the Flashblock extension +from http://flashblock.mozdev.org/. Flashblock is available under the +MPL/GPL/LGPL tri-license. The files necessary for operation in Camino +are placed in the content directory and referenced by the jar.mn +manifest. A contents.rdf file is included to allow Camino to identify +the contents of flashblock.jar. + +Camino's bundled Flashblock extension is currently synchronized with +Flashblock 1.5.11 from +http://downloads.mozdev.org/flashblock/flashblock-1.5.11.xpi. diff --git a/mozilla/kmeleon/flashblock/content/authorware.png b/mozilla/kmeleon/flashblock/content/authorware.png new file mode 100644 index 00000000..fd562961 Binary files /dev/null and b/mozilla/kmeleon/flashblock/content/authorware.png differ diff --git a/mozilla/kmeleon/flashblock/content/contents.rdf b/mozilla/kmeleon/flashblock/content/contents.rdf new file mode 100644 index 00000000..97362a95 --- /dev/null +++ b/mozilla/kmeleon/flashblock/content/contents.rdf @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/mozilla/kmeleon/flashblock/content/director.png b/mozilla/kmeleon/flashblock/content/director.png new file mode 100644 index 00000000..4da6cf95 Binary files /dev/null and b/mozilla/kmeleon/flashblock/content/director.png differ diff --git a/mozilla/kmeleon/flashblock/content/flash.png b/mozilla/kmeleon/flashblock/content/flash.png new file mode 100644 index 00000000..61dc080d Binary files /dev/null and b/mozilla/kmeleon/flashblock/content/flash.png differ diff --git a/mozilla/kmeleon/flashblock/content/flashblock.css b/mozilla/kmeleon/flashblock/content/flashblock.css new file mode 100644 index 00000000..74bbae6f --- /dev/null +++ b/mozilla/kmeleon/flashblock/content/flashblock.css @@ -0,0 +1,98 @@ +/* + * Flash Click to View by Ted Mielczarek (luser_mozilla@perilith.com) + * Original code by Jesse Ruderman (jruderman@hmc.edu) + * taken from http://www.squarefree.com/userstyles/xbl.html + * + * Change XBL binding for tags, click to view flash + */ + +/* + * Flash identifiers. +*/ +object[classid*=":D27CDB6E-AE6D-11cf-96B8-444553540000"], +object[codebase*="swflash.cab"], +object[data*=".swf"], +embed[type="application/x-shockwave-flash"], +embed[src*=".swf"], +object[type="application/x-shockwave-flash"], +object[src*=".swf"] +{ -moz-binding: url("chrome://flashblock/content/flashblock.xml#flash") !important; } + +/* + * Director identifiers. +*/ +object[classid*=":166B1BCA-3F9C-11CF-8075-444553540000"], +object[codebase*="sw.cab"], +object[data*=".dcr"], +embed[type="application/x-director"], +embed[src*=".dcr"], +object[type="application/x-director"], +object[src*=".dcr"] +{ -moz-binding: url("chrome://flashblock/content/flashblock.xml#director") !important; } + +/* + * Authorware identifiers. +*/ +object[classid*=":15B782AF-55D8-11D1-B477-006097098764"], +object[codebase*="awswaxf.cab"], +object[data*=".aam"], +embed[type="application/x-authorware-map"], +embed[src*=".aam"], +object[type="application/x-authorware-map"], +object[src*=".aam"] +{ -moz-binding: url("chrome://flashblock/content/flashblock.xml#authorware") !important; } + +/* +* Java identifiers. +*/ +applet, +object[classid*=":8AD9C840-044E-11D1-B3E9-00805F499D93"], +object[classid^="clsid:CAFEEFAC-"], +object[classid^="java:"], +object[type="application/x-java-applet"], +embed[classid*=":8AD9C840-044E-11D1-B3E9-00805F499D93"], +embed[classid^="clsid:CAFEEFAC-"], +embed[classid^="java:"], +embed[type="application/x-java-applet"] +{ + -moz-binding: none !important; +} + +/* +* Silverlight identifiers. +*/ +object[classid*="32C73088-76AE-40F7-AC40-81F62CB2C1DA"], +object[type="application/ag-plugin"], +object[type="application/x-silverlight"], +object[source*=".xaml"], +object[sourceelement*="xaml"], +embed[type="application/ag-plugin"], +embed[source*=".xaml"] +{ + -moz-binding: none !important; +} + +/* + * Don't bind to objects with an explicit type="" + * e.g. type="application/x-sibelius-score". +*/ +object[type]:not([type="application/x-shockwave-flash"]):not([type="application/x-director"]):not([type="application/x-authorware-map"]):not([data*=".swf"]):not([src*=".swf"]) +{ -moz-binding: none !important; } + +/* + * Broken Objects. +*/ +object +{ -moz-binding: url("chrome://flashblock/content/flashblock.xml#brokenobject") !important; } + +/* sIFR styles */ +.sIFR-flash, .sIFR-flash object, .sIFR-flash embed, +.sIFR-replaced div[style*="chrome://flashblock"] { + -moz-binding: url("chrome://flashblock/content/flashblock.xml#sifr-replaced") !important; +} + +span.sIFR-alternate { + -moz-binding: url("chrome://flashblock/content/flashblock.xml#sifr-alternate") !important; + position: static !important; + letter-spacing: normal !important; +} diff --git a/mozilla/kmeleon/flashblock/content/flashblock.xml b/mozilla/kmeleon/flashblock/content/flashblock.xml new file mode 100644 index 00000000..419dd659 --- /dev/null +++ b/mozilla/kmeleon/flashblock/content/flashblock.xml @@ -0,0 +1,619 @@ + + + + + + + + 0) { + embeds[0].setAttribute("src", placeholder.getAttribute("embedsrc")); + } + } + } + parent.removeChild(placeholder); +} + +function flashblockIsWhitelisted() { + // Check if the page that loaded the Flash site is whitelisted + // Thanks to Neil on #mozilla for suggesting this method, I would never have + // thought of it on my own. :-) + // Lor 20041215: Use "UIEvents" to make it work in post-1.0 FF + // (thanks to Neil again) + var flashblockEvent = document.createEvent("UIEvents"); + + if(flashblockEvent) { + flashblockEvent.initEvent("flashblockCheckLoad", true, true); + document.dispatchEvent(flashblockEvent); + if(flashblockEvent.getPreventDefault() == true) { + // Whitelisted + return true; + } + } + return false; +} + +function flashblockSetImage(placeholder, type) { + var inactiveurl = "url(chrome://flashblock/content/" + type + ".png) no-repeat center"; + var activeurl = "url(chrome://flashblock/content/flashplay.png) no-repeat center"; + placeholder.setAttribute("bgInactive", inactiveurl); + placeholder.setAttribute("bgActive", activeurl); + placeholder.style.background = inactiveurl; +} + +function flashblockStylePlaceholder(flash, placeholder, isStandalone) { + // If object's size was relative, use that. + // Otherwise, set size to minimum 32x32 px + placeholder.style.setProperty("min-width", "32px", "important"); + placeholder.style.setProperty("min-height", "32px", "important"); + // The size and positioning may come from a class + var fClass = flash.getAttribute("class"); + if (fClass) + placeholder.setAttribute("class", fClass); + + var fStyle = window.getComputedStyle(flash, ""); + + var fWidth = fStyle.getPropertyValue("width"); + var width = parseInt(flash.width || flash.style.width); + if (fWidth && parseInt(fWidth) > 32) + width = fWidth; + else if(flash.width.match("%$")) + width = flash.width; + else if (width) + width = Math.max(width,32) + "px"; + else + width = "32px" + placeholder.style.width = width; + + var fHeight = fStyle.getPropertyValue("height"); + var height = parseInt(flash.height || flash.style.height); + if (fHeight && parseInt(fHeight) > 32) + height = fHeight; + else if(flash.height.match("%$")) + height = flash.height; + else if (height) + height = Math.max(height,32) + "px"; + else + height = "32px" + placeholder.style.height = height; + + // Add styling rules to placeholder div. + placeholder.style.border = "1px solid #dfdfdf"; + placeholder.style.cursor = "pointer"; + placeholder.style.overflow = "hidden"; + var disp = fStyle.getPropertyValue("display"); + placeholder.style.display = disp == "block" ? "block" : "inline-block"; + placeholder.style.setProperty("visibility", "visible", "important"); + + // The size of the placeholder should include borders too, + // otherwise scrollbars appear for blocked standalone objects + placeholder.style.MozBoxSizing = "border-box"; + + // Make some style adjustments since the placeholder is a
+ // element instead of a , , ... element. + // An will be centered if the parent has + // 'text-align: center' but a
will not. This code fixes that. + var parentStyle = window.getComputedStyle(flash.parentNode, ""); + if (parentStyle.getPropertyValue("text-align") == "center") { + placeholder.style.marginRight = "auto"; + placeholder.style.marginLeft = "auto"; + } +} + +function flashblockSetTitle(current, placeholder, isStandalone) { + // non-null "about:blank" value to prevent OS/2 crashing + var fakeURI = "about:blank"; + if (isStandalone) { + placeholder.title = current.src; + current.src = fakeURI; + } + else if (current.hasAttribute("src") || + current.hasAttribute("data") || + current.hasAttribute("movie") ) { + var srcURI = current.getAttribute("src") || + current.getAttribute("movie") || + current.getAttribute("data") ; + placeholder.title = srcURI; + if (current.hasAttribute("src")) { + placeholder.setAttribute("srcAttribute", current.getAttribute("src")); + current.setAttribute("src", fakeURI); + } + if (current.hasAttribute("data")) { + placeholder.setAttribute("dataAttribute", current.getAttribute("data")); + current.setAttribute("data", fakeURI); + } + if (current.hasAttribute("movie")) { + placeholder.setAttribute("movieAttribute", current.getAttribute("movie")); + current.setAttribute("movie", fakeURI); + } + } + else { + var params = current.getElementsByTagName("param"); + for (var ii = 0; ii < params.length; ii++) { + if (params[ii].getAttribute("name") == "movie" && + params[ii].hasAttribute("value")) { + placeholder.title = params[ii].value; + placeholder.movieParam = [ params[ii], placeholder.title ]; + params[ii].value = fakeURI; + break; + } + } + } +} + +if(flashblockIsWhitelisted()) + return; + +var current = this; +var parent = current.parentNode; + +// Check parent too (case of working EMBED inside broken OBJECT) +if (current.overrideCTV || parentNode.overrideCTV) { + return; +} +current.overrideCTV = true; + +// Create placeholder div +var isStandalone = ("src" in current && current.src == current.ownerDocument.location); +var placeholder = flashblockCreatePlaceholder(isStandalone); + +// Set placeholder image +var type = this.flashblockType ? this.flashblockType : "flash" +flashblockSetImage(placeholder, type); + +flashblockStylePlaceholder(current, placeholder, isStandalone); + +flashblockSetTitle(current, placeholder, isStandalone); + +// Replace the flash document with a placeholder. +var flashblockSetTimeout = nativeMethod(window, "setTimeout"); +flashblockSetTimeout(flashblockShowPlaceholder, 0); + +// Change the placeholder background when the mouse enters/exits the div. +placeholder.addEventListener("mouseover", +function(event) { event.target.style.background = event.target.getAttribute("bgActive"); }, +"false" +); +placeholder.addEventListener("mouseout", +function(event) { event.target.style.background = event.target.getAttribute("bgInactive"); }, +"false" +); + +// Replace the placeholder with the flash document. +placeholder.addEventListener("click", flashblockShowFlash, "false"); + +// Accessibility +placeholder.setAttribute("tabindex", "0"); +placeholder.setAttribute("role", "button"); +placeholder.addEventListener("keypress", flashblockShowFlash, "false"); + + ]]> + + + + + + + "director" + + + + + + "authorware" + + + + + + + "yes" + + + 0) { + embeds[0].setAttribute("src", placeholder.getAttribute("embedsrc")); + } + } + } + parent.removeChild(placeholder); +} + +function flashblockIsWhitelisted() { + // Check if the page that loaded the Flash site is whitelisted + // Thanks to Neil on #mozilla for suggesting this method, I would never have + // thought of it on my own. :-) + // Lor 20041215: Use "UIEvents" to make it work in post-1.0 FF + // (thanks to Neil again) + var flashblockEvent = document.createEvent("UIEvents"); + + if(flashblockEvent) { + flashblockEvent.initEvent("flashblockCheckLoad", true, true); + document.dispatchEvent(flashblockEvent); + if(flashblockEvent.getPreventDefault() == true) { + // Whitelisted + return true; + } + } + return false; +} + +function flashblockSetImage(placeholder, type) { + var inactiveurl = "url(chrome://flashblock/content/" + type + ".png) no-repeat center"; + var activeurl = "url(chrome://flashblock/content/flashplay.png) no-repeat center"; + placeholder.setAttribute("bgInactive", inactiveurl); + placeholder.setAttribute("bgActive", activeurl); + placeholder.style.background = inactiveurl; +} + +function flashblockStylePlaceholder(flash, placeholder, isStandalone) { + // If object's size was relative, use that. + // Otherwise, set size to minimum 32x32 px + placeholder.style.setProperty("min-width", "32px", "important"); + placeholder.style.setProperty("min-height", "32px", "important"); + // The size and positioning may come from a class + var fClass = flash.getAttribute("class"); + if (fClass) + placeholder.setAttribute("class", fClass); + + var fStyle = window.getComputedStyle(flash, ""); + + var fWidth = fStyle.getPropertyValue("width"); + var width = parseInt(flash.width || flash.style.width); + if (fWidth && parseInt(fWidth) > 32) + width = fWidth; + else if(flash.width.match("%$")) + width = flash.width; + else if (width) + width = Math.max(width,32) + "px"; + else + width = "32px" + placeholder.style.width = width; + + var fHeight = fStyle.getPropertyValue("height"); + var height = parseInt(flash.height || flash.style.height); + if (fHeight && parseInt(fHeight) > 32) + height = fHeight; + else if(flash.height.match("%$")) + height = flash.height; + else if (height) + height = Math.max(height,32) + "px"; + else + height = "32px" + placeholder.style.height = height; + + // Add styling rules to placeholder div. + placeholder.style.border = "1px solid #dfdfdf"; + placeholder.style.cursor = "pointer"; + placeholder.style.overflow = "hidden"; + var disp = fStyle.getPropertyValue("display"); + placeholder.style.display = disp == "block" ? "block" : "inline-block"; + placeholder.style.setProperty("visibility", "visible", "important"); + + // The size of the placeholder should include borders too, + // otherwise scrollbars appear for blocked standalone objects + placeholder.style.MozBoxSizing = "border-box"; + + // Make some style adjustments since the placeholder is a
+ // element instead of a , , ... element. + // An will be centered if the parent has + // 'text-align: center' but a
will not. This code fixes that. + var parentStyle = window.getComputedStyle(flash.parentNode, ""); + if (parentStyle.getPropertyValue("text-align") == "center") { + placeholder.style.marginRight = "auto"; + placeholder.style.marginLeft = "auto"; + } +} + +function flashblockSetTitle(current, placeholder, isStandalone) { + // non-null "about:blank" value to prevent OS/2 crashing + var fakeURI = "about:blank"; + if (isStandalone) { + placeholder.title = current.src; + current.src = fakeURI; + } + else if (current.hasAttribute("src") || + current.hasAttribute("data") || + current.hasAttribute("movie") ) { + var srcURI = current.getAttribute("src") || + current.getAttribute("movie") || + current.getAttribute("data") ; + placeholder.title = srcURI; + if (current.hasAttribute("src")) { + placeholder.setAttribute("srcAttribute", current.getAttribute("src")); + current.setAttribute("src", fakeURI); + } + if (current.hasAttribute("data")) { + placeholder.setAttribute("dataAttribute", current.getAttribute("data")); + current.setAttribute("data", fakeURI); + } + if (current.hasAttribute("movie")) { + placeholder.setAttribute("movieAttribute", current.getAttribute("movie")); + current.setAttribute("movie", fakeURI); + } + } + else { + var params = current.getElementsByTagName("param"); + for (var ii = 0; ii < params.length; ii++) { + if (params[ii].getAttribute("name") == "movie" && + params[ii].hasAttribute("value")) { + placeholder.title = params[ii].value; + placeholder.movieParam = [ params[ii], placeholder.title ]; + params[ii].value = fakeURI; + break; + } + } + } +} + +if(flashblockIsWhitelisted()) + return; + +var current = this; +var parent = current.parentNode; + +// Check parent too (case of working EMBED inside broken OBJECT) +if (current.overrideCTV || parentNode.overrideCTV) { + return; +} +current.overrideCTV = true; + +var params = this.getElementsByTagName("param"); +var isFlash = false; +for (var ii = 0; ii < params.length; ii++) { + var pvalue = params[ii].getAttribute("value"); + if (/\.swf/i.test(pvalue)) { + isFlash = true; + break; + } +} +var embeds = this.getElementsByTagName("embed"); +for (var jj = 0; jj < embeds.length; jj++) { + var embed = embeds[jj]; + if (/x-shockwave-flash/i.test(embed.getAttribute("type")) || + /\.swf/i.test(embed.getAttribute("src"))) { + isFlash = true; + break; + } +} +if (!isFlash) {return;} + +// Create placeholder div +var isStandalone = ("src" in current && current.src == current.ownerDocument.location); +var placeholder = flashblockCreatePlaceholder(isStandalone); + +// Set placeholder image +var type = this.flashblockType ? this.flashblockType : "flash" +flashblockSetImage(placeholder, type); + +flashblockStylePlaceholder(current, placeholder, isStandalone); + +flashblockSetTitle(current, placeholder, isStandalone); + +// Replace the flash document with a placeholder. +var flashblockSetTimeout = nativeMethod(window, "setTimeout"); +flashblockSetTimeout(flashblockShowPlaceholder, 0); + +// Change the placeholder background when the mouse enters/exits the div. +placeholder.addEventListener("mouseover", +function(event) { event.target.style.background = event.target.getAttribute("bgActive"); }, +"false" +); +placeholder.addEventListener("mouseout", +function(event) { event.target.style.background = event.target.getAttribute("bgInactive"); }, +"false" +); + +// Replace the placeholder with the flash document. +placeholder.addEventListener("click", flashblockShowFlash, "false"); + +// Accessibility +placeholder.setAttribute("tabindex", "0"); +placeholder.setAttribute("role", "button"); +placeholder.addEventListener("keypress", flashblockShowFlash, "false"); + + ]]> + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/flashblock/content/flashplay.png b/mozilla/kmeleon/flashblock/content/flashplay.png new file mode 100644 index 00000000..b9ffb404 Binary files /dev/null and b/mozilla/kmeleon/flashblock/content/flashplay.png differ diff --git a/mozilla/kmeleon/flashblock/jar.mn b/mozilla/kmeleon/flashblock/jar.mn new file mode 100644 index 00000000..8744d2f6 --- /dev/null +++ b/mozilla/kmeleon/flashblock/jar.mn @@ -0,0 +1,8 @@ +flashblock.jar: + content/flashblock/contents.rdf (content/contents.rdf) + content/flashblock/flashblock.xml (content/flashblock.xml) + content/flashblock/flashblock.css (content/flashblock.css) + content/flashblock/authorware.png (content/authorware.png) + content/flashblock/director.png (content/director.png) + content/flashblock/flash.png (content/flash.png) + content/flashblock/flashplay.png (content/flashplay.png) diff --git a/mozilla/kmeleon/history/Makefile.in b/mozilla/kmeleon/history/Makefile.in new file mode 100644 index 00000000..f01875d9 --- /dev/null +++ b/mozilla/kmeleon/history/Makefile.in @@ -0,0 +1,52 @@ +# +# ***** 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 +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +DIRS = public + +ifndef MOZ_PLACES +DIRS += src +endif + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/kmeleon/history/public/Makefile.in b/mozilla/kmeleon/history/public/Makefile.in new file mode 100644 index 00000000..4b533684 --- /dev/null +++ b/mozilla/kmeleon/history/public/Makefile.in @@ -0,0 +1,53 @@ +# +# ***** 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 +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Joe Hewitt (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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = history +XPIDL_MODULE = history + +XPIDLSRCS = \ + nsIBrowserHistory.idl \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/kmeleon/history/public/nsIBrowserHistory.idl b/mozilla/kmeleon/history/public/nsIBrowserHistory.idl new file mode 100644 index 00000000..c9c263a8 --- /dev/null +++ b/mozilla/kmeleon/history/public/nsIBrowserHistory.idl @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 ***** */ + +/* + * browser-specific interface to global history + */ + +#include "nsISupports.idl" +#include "nsIGlobalHistory2.idl" + +[scriptable, uuid(c43079c3-3d8d-4b7c-af14-0e30ab46865f)] +interface nsIBrowserHistory : nsIGlobalHistory2 +{ + /** + * addPageWithDetails + * Adds a page to history with specific time stamp information. This is used in + * the History migrator. + */ + void addPageWithDetails(in nsIURI aURI, in wstring aTitle, in long long aLastVisited); + + /** + * lastPageVisited + * The last page that was visited in a top-level window. + */ + readonly attribute AUTF8String lastPageVisited; + + /** + * count + * The number of entries in global history + */ + readonly attribute PRUint32 count; + + /** + * remove a page from history + */ + void removePage(in nsIURI aURI); + + /** + * removePagesFromHost + * Remove all pages from the given host. + * If aEntireDomain is true, will assume aHost is a domain, + * and remove all pages from the entire domain. + */ + void removePagesFromHost(in AUTF8String aHost, in boolean aEntireDomain); + + /** + * removeAllPages + * Remove all pages from global history + */ + void removeAllPages(); + + /** + * hidePage + * Hide the specified URL from being enumerated (and thus + * displayed in the UI) + * + * if the page hasn't been visited yet, then it will be added + * as if it was visited, and then marked as hidden + */ + void hidePage(in nsIURI aURI); + + /** + * This is just like markPageAsFollowedBookmark (in nsINavHistory, + * also implemented by the history service), but for URLs that a + * user visits from the chrome that are not bookmarks, such as a + * URL that is typed in the URL bar or clicking on a link in the + * history menu or history sidebar. It declares that the given URI + * is treated as if they typed the URL into the URL bar (which + * get more weight in our URL bar autocomplete algorithm.) + * If this URI is loaded soon after this message has been received, + * that transition will be marked as typed. + */ + void markPageAsTyped(in nsIURI aURI); +}; diff --git a/mozilla/kmeleon/history/src/Makefile.in b/mozilla/kmeleon/history/src/Makefile.in new file mode 100644 index 00000000..789823fe --- /dev/null +++ b/mozilla/kmeleon/history/src/Makefile.in @@ -0,0 +1,75 @@ +# +# ***** 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 +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Joe Hewitt (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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = history +LIBRARY_NAME = tkhstory +IS_COMPONENT = 1 +MODULE_NAME = nsToolkitHistory +LIBXUL_LIBRARY = 1 +EXPORT_LIBRARY = 1 + +REQUIRES = xpcom \ + string \ + docshell \ + rdf \ + mork \ + pref \ + necko \ + intl \ + unicharutil \ + autocomplete \ + uconv \ + toolkitcomps \ + $(NULL) + +CPPSRCS = nsGlobalHistory.cpp \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + +EXTRA_DSO_LDOPTS += \ + $(MOZ_UNICHARUTIL_LIBS) \ + $(MOZ_COMPONENT_LIBS) \ + $(NULL) diff --git a/mozilla/kmeleon/history/src/nsGlobalHistory.cpp b/mozilla/kmeleon/history/src/nsGlobalHistory.cpp new file mode 100644 index 00000000..a004bf9b --- /dev/null +++ b/mozilla/kmeleon/history/src/nsGlobalHistory.cpp @@ -0,0 +1,4642 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 Communicator client code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Waterson + * Pierre Phaneuf + * Joe Hewitt + * Blake Ross + * Chris Sears + * Michael Lowe + * + * 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 ***** */ + +/* + + A global browser history implementation that also supports the RDF + datasource interface. + + TODO + + 1) Hook up Assert() etc. so that we can delete stuff. + +*/ +#include "nsNetUtil.h" +#include "nsGlobalHistory.h" +#include "nsCRT.h" +#include "nsIEnumerator.h" +#include "nsIServiceManager.h" +#include "nsEnumeratorUtils.h" +#include "nsRDFCID.h" +#include "nsIDirectoryService.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsXPIDLString.h" +#include "plhash.h" +#include "plstr.h" +#include "prprf.h" +#include "prtime.h" +#include "rdf.h" +#include "nsCOMArray.h" +#include "nsIIOService.h" +#include "nsILocalFile.h" + +#include "nsIURL.h" +#include "nsNetCID.h" + +#include "nsInt64.h" +#include "nsMorkCID.h" +#include "nsIMdbFactoryFactory.h" + +#include "nsIPrefService.h" +#include "nsIPrefBranch2.h" + +#include "nsIObserverService.h" +#include "nsITextToSubURI.h" + +#include "nsIGenericFactory.h" +#include "nsToolkitCompsCID.h" +#include "nsDocShellCID.h" + +PRInt32 nsGlobalHistory::gRefCnt; +nsIRDFService* nsGlobalHistory::gRDFService; +nsIRDFResource* nsGlobalHistory::kNC_Page; +nsIRDFResource* nsGlobalHistory::kNC_Date; +nsIRDFResource* nsGlobalHistory::kNC_FirstVisitDate; +nsIRDFResource* nsGlobalHistory::kNC_VisitCount; +nsIRDFResource* nsGlobalHistory::kNC_AgeInDays; +nsIRDFResource* nsGlobalHistory::kNC_Name; +nsIRDFResource* nsGlobalHistory::kNC_NameSort; +nsIRDFResource* nsGlobalHistory::kNC_Hostname; +nsIRDFResource* nsGlobalHistory::kNC_Referrer; +nsIRDFResource* nsGlobalHistory::kNC_child; +nsIRDFResource* nsGlobalHistory::kNC_URL; +nsIRDFResource* nsGlobalHistory::kNC_HistoryRoot; +nsIRDFResource* nsGlobalHistory::kNC_HistoryByDateAndSite; +nsIRDFResource* nsGlobalHistory::kNC_HistoryByDate; +nsIRDFResource* nsGlobalHistory::kNC_DayFolderIndex; +nsIMdbFactory* nsGlobalHistory::gMdbFactory = nsnull; +nsIPrefBranch* nsGlobalHistory::gPrefBranch = nsnull; + +#define PREF_BRANCH_BASE "browser." +#define PREF_BROWSER_HISTORY_EXPIRE_DAYS "history_expire_days" +#define PREF_AUTOCOMPLETE_ONLY_TYPED "urlbar.matchOnlyTyped" +#define PREF_AUTOCOMPLETE_ENABLED "urlbar.autocomplete.enabled" + +#define FIND_BY_AGEINDAYS_PREFIX "find:datasource=history&match=AgeInDays&method=" + +// see bug #319004 -- clamp title and URL to generously-large but not too large +// length +#define HISTORY_URI_LENGTH_MAX 65536 +#define HISTORY_TITLE_LENGTH_MAX 4096 + +// sync history every 10 seconds +#define HISTORY_SYNC_TIMEOUT (10 * PR_MSEC_PER_SEC) +//#define HISTORY_SYNC_TIMEOUT 3000 // every 3 seconds - testing only! + +// the value of mLastNow expires every 3 seconds +#define HISTORY_EXPIRE_NOW_TIMEOUT (3 * PR_MSEC_PER_SEC) + +#define MSECS_PER_DAY (PR_MSEC_PER_SEC * 60 * 60 * 24) + +//---------------------------------------------------------------------- +// +// CIDs + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +// closure structures for RemoveMatchingRows +struct matchExpiration_t { + PRTime *expirationDate; + nsGlobalHistory *history; +}; + +struct matchHost_t { + const char *host; + PRBool entireDomain; // should we delete the entire domain? + nsGlobalHistory *history; +}; + +struct matchSearchTerm_t { + nsIMdbEnv *env; + nsIMdbStore *store; + + searchTerm *term; + PRBool haveClosure; // are the rest of the fields valid? + PRTime now; + PRInt32 intValue; +}; + +struct matchQuery_t { + searchQuery* query; + nsGlobalHistory* history; +}; + +// simple token/value struct +class tokenPair { +public: + tokenPair(const char *aName, PRUint32 aNameLen, + const char *aValue, PRUint32 aValueLen) : + tokenName(aName), tokenNameLength(aNameLen), + tokenValue(aValue), tokenValueLength(aValueLen) { MOZ_COUNT_CTOR(tokenPair); } + ~tokenPair() { MOZ_COUNT_DTOR(tokenPair); } + const char* tokenName; + PRUint32 tokenNameLength; + const char* tokenValue; + PRUint32 tokenValueLength; +}; + +// individual search term, pulled from token/value structs +class searchTerm { +public: + searchTerm(const char* aDatasource, PRUint32 aDatasourceLen, + const char *aProperty, PRUint32 aPropertyLen, + const char* aMethod, PRUint32 aMethodLen, + const char* aText, PRUint32 aTextLen): + datasource(aDatasource, aDatasource+aDatasourceLen), + property(aProperty, aProperty+aPropertyLen), + method(aMethod, aMethod+aMethodLen) + { + MOZ_COUNT_CTOR(searchTerm); + nsresult rv; + nsCOMPtr textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + textToSubURI->UnEscapeAndConvert("UTF-8", PromiseFlatCString(Substring(aText, aText + aTextLen)).get(), getter_Copies(text)); + } + ~searchTerm() { + MOZ_COUNT_DTOR(searchTerm); + } + + nsDependentCSubstring datasource; // should always be "history" ? + nsDependentCSubstring property; // AgeInDays, Hostname, etc + nsDependentCSubstring method; // is, isgreater, isless + nsXPIDLString text; // text to match + rowMatchCallback match; // matching callback if needed +}; + +// list of terms, plus an optional groupby column +struct searchQuery { + nsVoidArray terms; // array of searchTerms + mdb_column groupBy; // column to group by +}; + +static PRBool HasCell(nsIMdbEnv *aEnv, nsIMdbRow* aRow, mdb_column aCol) +{ + mdbYarn yarn; + mdb_err err = aRow->AliasCellYarn(aEnv, aCol, &yarn); + + // no cell + if (err != 0) + return PR_FALSE; + + // if we have the cell, make sure it has a value?? + return (yarn.mYarn_Fill != 0); +} + +static PRTime +NormalizeTime(PRTime aTime) +{ + // normalize both now and date to midnight of the day they occur on + PRExplodedTime explodedTime; + PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime); + + // set to midnight (0:00) + explodedTime.tm_min = + explodedTime.tm_hour = + explodedTime.tm_sec = + explodedTime.tm_usec = 0; + + return PR_ImplodeTime(&explodedTime); +} + +// pass in a pre-normalized now and a date, and we'll find +// the difference since midnight on each of the days.. +static PRInt32 +GetAgeInDays(PRTime aNormalizedNow, PRTime aDate) +{ + PRTime dateMidnight = NormalizeTime(aDate); + + PRTime diff; + LL_SUB(diff, aNormalizedNow, dateMidnight); + + // two-step process since I can't seem to load + // MSECS_PER_DAY * PR_MSEC_PER_SEC into a PRInt64 at compile time + PRInt64 msecPerSec; + LL_I2L(msecPerSec, PR_MSEC_PER_SEC); + PRInt64 ageInSeconds; + LL_DIV(ageInSeconds, diff, msecPerSec); + + PRInt32 ageSec; LL_L2I(ageSec, ageInSeconds); + + PRInt64 msecPerDay; + LL_I2L(msecPerDay, MSECS_PER_DAY); + + PRInt64 ageInDays; + LL_DIV(ageInDays, ageInSeconds, msecPerDay); + + PRInt32 retval; + LL_L2I(retval, ageInDays); + return retval; +} + + +PRBool +nsGlobalHistory::MatchExpiration(nsIMdbRow *row, PRTime* expirationDate) +{ + nsresult rv; + + // hidden and typed urls always match because they're invalid, + // so we want to expire them asap. (if they were valid, they'd + // have been unhidden -- see AddExistingPageToDatabase) + if (HasCell(mEnv, row, kToken_HiddenColumn) && HasCell(mEnv, row, kToken_TypedColumn)) + return PR_TRUE; + + PRTime lastVisitedTime; + rv = GetRowValue(row, kToken_LastVisitDateColumn, &lastVisitedTime); + + if (NS_FAILED(rv)) + return PR_FALSE; + + return LL_CMP(lastVisitedTime, <, *expirationDate); +} + +static PRBool +matchAgeInDaysCallback(nsIMdbRow *row, void *aClosure) +{ + matchSearchTerm_t *matchSearchTerm = (matchSearchTerm_t*)aClosure; + const searchTerm *term = matchSearchTerm->term; + nsIMdbEnv *env = matchSearchTerm->env; + nsIMdbStore *store = matchSearchTerm->store; + + // fill in the rest of the closure if it's not filled in yet + // this saves us from recalculating this stuff on every row + if (!matchSearchTerm->haveClosure) { + PRInt32 err; + // Need to create an nsAutoString to use ToInteger + matchSearchTerm->intValue = nsAutoString(term->text).ToInteger(&err); + matchSearchTerm->now = NormalizeTime(PR_Now()); + if (err != 0) return PR_FALSE; + matchSearchTerm->haveClosure = PR_TRUE; + } + + // XXX convert the property to a column, get the column value + + mdb_column column; + mdb_err err = store->StringToToken(env, "LastVisitDate", &column); + if (err != 0) return PR_FALSE; + + mdbYarn yarn; + err = row->AliasCellYarn(env, column, &yarn); + if (err != 0) return PR_FALSE; + + PRTime rowDate; + PR_sscanf((const char*)yarn.mYarn_Buf, "%lld", &rowDate); + + PRInt32 days = GetAgeInDays(matchSearchTerm->now, rowDate); + + if (term->method.Equals("is")) + return (days == matchSearchTerm->intValue); + else if (term->method.Equals("isgreater")) + return (days > matchSearchTerm->intValue); + else if (term->method.Equals("isless")) + return (days < matchSearchTerm->intValue); + + return PR_FALSE; +} + +static PRBool +matchExpirationCallback(nsIMdbRow *row, void *aClosure) +{ + matchExpiration_t *expires = (matchExpiration_t*)aClosure; + return expires->history->MatchExpiration(row, expires->expirationDate); +} + +static PRBool +matchAllCallback(nsIMdbRow *row, void *aClosure) +{ + return PR_TRUE; +} + +static PRBool +matchHostCallback(nsIMdbRow *row, void *aClosure) +{ + matchHost_t *hostInfo = (matchHost_t*)aClosure; + return hostInfo->history->MatchHost(row, hostInfo); +} + +static PRBool +matchQueryCallback(nsIMdbRow *row, void *aClosure) +{ + matchQuery_t *query = (matchQuery_t*)aClosure; + return query->history->RowMatches(row, query->query, PR_TRUE); +} +//---------------------------------------------------------------------- + +nsMdbTableEnumerator::nsMdbTableEnumerator() + : mEnv(nsnull), + mTable(nsnull), + mCursor(nsnull), + mCurrent(nsnull) +{ +} + + +nsresult +nsMdbTableEnumerator::Init(nsIMdbEnv* aEnv, + nsIMdbTable* aTable) +{ + NS_PRECONDITION(aEnv != nsnull, "null ptr"); + if (! aEnv) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTable != nsnull, "null ptr"); + if (! aTable) + return NS_ERROR_NULL_POINTER; + + mEnv = aEnv; + NS_ADDREF(mEnv); + + mTable = aTable; + NS_ADDREF(mTable); + + mdb_err err; + err = mTable->GetTableRowCursor(mEnv, -1, &mCursor); + if (err != 0) return NS_ERROR_FAILURE; + + return NS_OK; +} + + +nsMdbTableEnumerator::~nsMdbTableEnumerator() +{ + NS_IF_RELEASE(mCurrent); + + NS_IF_RELEASE(mCursor); + + NS_IF_RELEASE(mTable); + + NS_IF_RELEASE(mEnv); +} + + +NS_IMPL_ISUPPORTS1(nsMdbTableEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsMdbTableEnumerator::HasMoreElements(PRBool* _result) +{ + if (! mCurrent) { + mdb_err err; + + while (1) { + mdb_pos pos; + err = mCursor->NextRow(mEnv, &mCurrent, &pos); + if (err != 0) return NS_ERROR_FAILURE; + + // If there are no more rows, then bail. + if (! mCurrent) + break; + + // If this is a result, the stop. + if (IsResult(mCurrent)) + break; + + // Otherwise, drop the ref to the row we retrieved, and continue + // on to the next one. + NS_RELEASE(mCurrent); + mCurrent = nsnull; + } + } + + *_result = (mCurrent != nsnull); + return NS_OK; +} + + +NS_IMETHODIMP +nsMdbTableEnumerator::GetNext(nsISupports** _result) +{ + nsresult rv; + + PRBool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) + return NS_ERROR_UNEXPECTED; + + rv = ConvertToISupports(mCurrent, _result); + + NS_RELEASE(mCurrent); + mCurrent = nsnull; + + return rv; +} + + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// ctor dtor etc. +// + + +nsGlobalHistory::nsGlobalHistory() + : mExpireDays(9), // make default be nine days + mAutocompleteOnlyTyped(PR_FALSE), + mBatchesInProgress(0), + mNowValid(PR_FALSE), + mDirty(PR_FALSE), + mEnv(nsnull), + mStore(nsnull), + mTable(nsnull) +{ + LL_I2L(mFileSizeOnDisk, 0); + + // commonly used prefixes that should be chopped off all + // history and input urls before comparison + + mIgnoreSchemes.AppendString(NS_LITERAL_STRING("http://")); + mIgnoreSchemes.AppendString(NS_LITERAL_STRING("https://")); + mIgnoreSchemes.AppendString(NS_LITERAL_STRING("ftp://")); + mIgnoreHostnames.AppendString(NS_LITERAL_STRING("www.")); + mIgnoreHostnames.AppendString(NS_LITERAL_STRING("ftp.")); + + mTypedHiddenURIs.Init(3); +} + +nsGlobalHistory::~nsGlobalHistory() +{ + gRDFService->UnregisterDataSource(this); + + nsresult rv; + rv = CloseDB(); + + NS_IF_RELEASE(mTable); + NS_IF_RELEASE(mStore); + + if (--gRefCnt == 0) { + NS_IF_RELEASE(gRDFService); + + NS_IF_RELEASE(kNC_Page); + NS_IF_RELEASE(kNC_Date); + NS_IF_RELEASE(kNC_FirstVisitDate); + NS_IF_RELEASE(kNC_VisitCount); + NS_IF_RELEASE(kNC_AgeInDays); + NS_IF_RELEASE(kNC_Name); + NS_IF_RELEASE(kNC_NameSort); + NS_IF_RELEASE(kNC_Hostname); + NS_IF_RELEASE(kNC_Referrer); + NS_IF_RELEASE(kNC_child); + NS_IF_RELEASE(kNC_URL); + NS_IF_RELEASE(kNC_HistoryRoot); + NS_IF_RELEASE(kNC_HistoryByDateAndSite); + NS_IF_RELEASE(kNC_HistoryByDate); + NS_IF_RELEASE(kNC_DayFolderIndex); + + NS_IF_RELEASE(gMdbFactory); + NS_IF_RELEASE(gPrefBranch); + } + + NS_IF_RELEASE(mEnv); + if (mSyncTimer) + mSyncTimer->Cancel(); + + if (mExpireNowTimer) + mExpireNowTimer->Cancel(); + +} + + + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// nsISupports methods + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalHistory) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalHistory) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mObservers) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsGlobalHistory) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mObservers) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + + +NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsGlobalHistory, nsIBrowserHistory) +NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsGlobalHistory, nsIBrowserHistory) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalHistory) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIGlobalHistory2, nsIGlobalHistory3) + NS_INTERFACE_MAP_ENTRY(nsIGlobalHistory3) + NS_INTERFACE_MAP_ENTRY(nsIBrowserHistory) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFRemoteDataSource) + NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteSearch) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIBrowserHistory) +NS_INTERFACE_MAP_END + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// nsIGlobalHistory2 methods +// + +NS_IMETHODIMP +nsGlobalHistory::AddURI(nsIURI *aURI, PRBool aRedirect, PRBool aTopLevel, nsIURI *aReferrer) +{ + PRTime now = GetNow(); + + return AddPageToDatabase(aURI, aRedirect, aTopLevel, now, aReferrer); +} + +nsresult +nsGlobalHistory::AddPageToDatabase(nsIURI* aURI, PRBool aRedirect, PRBool aTopLevel, + PRTime aLastVisitDate, nsIURI *aReferrer) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aURI); + + // If history is set to expire after 0 days, + // then it's technically disabled. Don't even + // bother adding the page + if (mExpireDays == 0) { + NS_WARNING("mExpireDays == 0"); + return NS_OK; + } + + // filter out unwanted URIs such as chrome: mailbox: etc + // The model is really if we don't know differently then add which basically + // means we are suppose to try all the things we know not to allow in and + // then if we don't bail go on and allow it in. But here lets compare + // against the most common case we know to allow in and go on and say yes + // to it. + + PRBool isHTTP = PR_FALSE; + PRBool isHTTPS = PR_FALSE; + + NS_ENSURE_SUCCESS(rv = aURI->SchemeIs("http", &isHTTP), rv); + NS_ENSURE_SUCCESS(rv = aURI->SchemeIs("https", &isHTTPS), rv); + + if (!isHTTP && !isHTTPS) { + PRBool isAbout, isImap, isNews, isMailbox, isViewSource, isChrome, isData; + + rv = aURI->SchemeIs("about", &isAbout); + rv |= aURI->SchemeIs("imap", &isImap); + rv |= aURI->SchemeIs("news", &isNews); + rv |= aURI->SchemeIs("mailbox", &isMailbox); + rv |= aURI->SchemeIs("view-source", &isViewSource); + rv |= aURI->SchemeIs("chrome", &isChrome); + rv |= aURI->SchemeIs("data", &isData); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + if (isAbout || isImap || isNews || isMailbox || isViewSource || isChrome || isData) { +#ifdef DEBUG_bsmedberg + printf("Filtering out unwanted scheme.\n"); +#endif + return NS_OK; + } + } + + rv = OpenDB(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCAutoString URISpec; + rv = aURI->GetSpec(URISpec); + NS_ENSURE_SUCCESS(rv, rv); + + if (URISpec.Length() > HISTORY_URI_LENGTH_MAX) + return NS_OK; + +#ifdef DEBUG_bsmedberg + printf("AddURI: %s%s%s", + URISpec.get(), + aRedirect ? ", redirect" : "", + aTopLevel ? ", toplevel" : ""); +#endif + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row)); + + if (NS_SUCCEEDED(rv)) { + // update the database, and get the old info back + PRTime oldDate; + PRInt32 oldCount; + rv = AddExistingPageToDatabase(row, aLastVisitDate, aReferrer, &oldDate, &oldCount); + NS_ASSERTION(NS_SUCCEEDED(rv), "AddExistingPageToDatabase failed; see bug 88961"); + if (NS_FAILED(rv)) return rv; + +#ifdef DEBUG_bsmedberg + printf("Existing page succeeded.\n"); +#endif + } + else { + rv = AddNewPageToDatabase(aURI, aLastVisitDate, aRedirect, + aTopLevel, aReferrer, getter_AddRefs(row)); + NS_ASSERTION(NS_SUCCEEDED(rv), "AddNewPageToDatabase failed; see bug 88961"); + if (NS_FAILED(rv)) return rv; + +#ifdef DEBUG_bsmedberg + printf("New page succeeded.\n"); +#endif + } + + // Store last visited page if we have the pref set accordingly + if (aTopLevel) { + PRInt32 choice = 0; + if (NS_SUCCEEDED(gPrefBranch->GetIntPref("startup.page", &choice))) { + if (choice != 2) { + if (NS_SUCCEEDED(gPrefBranch->GetIntPref("windows.loadOnNewWindow", &choice))) { + if (choice != 2) { + gPrefBranch->GetIntPref("tabs.loadOnNewTab", &choice); + } + } + } + } + if (choice == 2) { + NS_ENSURE_STATE(mMetaRow); + + SetRowValue(mMetaRow, kToken_LastPageVisited, URISpec.get()); + } + } + + SetDirty(); + + return NS_OK; +} + +nsresult +nsGlobalHistory::AddExistingPageToDatabase(nsIMdbRow *row, + PRTime aDate, + nsIURI* aReferrer, + PRTime *aOldDate, + PRInt32 *aOldCount) +{ + nsresult rv; + nsCAutoString oldReferrer; + + nsCAutoString URISpec; + rv = GetRowValue(row, kToken_URLColumn, URISpec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCAutoString referrerSpec; + if (aReferrer) { + rv = aReferrer->GetSpec(referrerSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + + // if the page was typed, unhide it now because it's + // known to be valid + if (HasCell(mEnv, row, kToken_TypedColumn)) { + mTypedHiddenURIs.Remove(URISpec); + row->CutColumn(mEnv, kToken_HiddenColumn); + } + + // Update last visit date. + // First get the old date so we can update observers... + rv = GetRowValue(row, kToken_LastVisitDateColumn, aOldDate); + if (NS_FAILED(rv)) return rv; + + // get the old count, so we can update it + rv = GetRowValue(row, kToken_VisitCountColumn, aOldCount); + if (NS_FAILED(rv) || *aOldCount < 1) + *aOldCount = 1; // assume we've visited at least once + + // ...now set the new date. + SetRowValue(row, kToken_LastVisitDateColumn, aDate); + SetRowValue(row, kToken_VisitCountColumn, (*aOldCount) + 1); + + if (aReferrer) { + rv = GetRowValue(row, kToken_ReferrerColumn, oldReferrer); + // No referrer? Now there is! + if ((NS_FAILED(rv) || oldReferrer.IsEmpty())) + SetRowValue(row, kToken_ReferrerColumn, referrerSpec.get()); + } + + // Notify observers + nsCOMPtr url; + rv = gRDFService->GetResource(URISpec, getter_AddRefs(url)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr date; + rv = gRDFService->GetDateLiteral(aDate, getter_AddRefs(date)); + NS_ENSURE_SUCCESS(rv, rv); + + // visit date + nsCOMPtr oldDateLiteral; + rv = gRDFService->GetDateLiteral(*aOldDate, getter_AddRefs(oldDateLiteral)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NotifyChange(url, kNC_Date, oldDateLiteral, date); + NS_ENSURE_SUCCESS(rv, rv); + + // visit count + nsCOMPtr oldCountLiteral; + rv = gRDFService->GetIntLiteral(*aOldCount, getter_AddRefs(oldCountLiteral)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr newCountLiteral; + rv = gRDFService->GetIntLiteral(*aOldCount+1, + getter_AddRefs(newCountLiteral)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NotifyChange(url, kNC_VisitCount, oldCountLiteral, newCountLiteral); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +nsGlobalHistory::AddNewPageToDatabase(nsIURI* aURI, + PRTime aDate, + PRBool aRedirect, + PRBool aTopLevel, + nsIURI* aReferrer, + nsIMdbRow **aResult) +{ + mdb_err err; + + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_NOT_INITIALIZED); + + nsCAutoString URISpec; + nsresult rv = aURI->GetSpec(URISpec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCAutoString referrerSpec; + if (aReferrer) { + rv = aReferrer->GetSpec(referrerSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Create a new row + mdbOid rowId; + rowId.mOid_Scope = kToken_HistoryRowScope; + rowId.mOid_Id = mdb_id(-1); + + NS_PRECONDITION(mTable != nsnull, "not initialized"); + if (! mTable) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr row; + err = mTable->NewRow(mEnv, &rowId, getter_AddRefs(row)); + if (err != 0) return NS_ERROR_FAILURE; + + // Set the URL + SetRowValue(row, kToken_URLColumn, URISpec.get()); + + // Set the date. + SetRowValue(row, kToken_LastVisitDateColumn, aDate); + SetRowValue(row, kToken_FirstVisitDateColumn, aDate); + + // Set the referrer if there is one. + if (aReferrer) + SetRowValue(row, kToken_ReferrerColumn, referrerSpec.get()); + + nsCOMPtr uri; + NS_NewURI(getter_AddRefs(uri), URISpec, nsnull, nsnull); + nsCAutoString hostname; + if (uri) + uri->GetHost(hostname); + + // Strip www. + if (Substring(hostname, 0, 4).EqualsLiteral("www.")) + hostname.Cut(0, 4); + + SetRowValue(row, kToken_HostnameColumn, hostname.get()); + + *aResult = row; + NS_ADDREF(*aResult); + + PRBool isJavascript; + rv = aURI->SchemeIs("javascript", &isJavascript); + NS_ENSURE_SUCCESS(rv, rv); + + if (isJavascript || aRedirect || !aTopLevel) { + // if this is a JS url, or a redirected URI or in a frame, hide it in + // global history so that it doesn't show up in the autocomplete + // dropdown. AddExistingPageToDatabase has logic to override this + // behavior for URIs which were typed. See bug 197127 and bug 161531 + // for details. + rv = SetRowValue(row, kToken_HiddenColumn, 1); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + // Notify observers + nsCOMPtr url; + rv = gRDFService->GetResource(URISpec, getter_AddRefs(url)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr date; + rv = gRDFService->GetDateLiteral(aDate, getter_AddRefs(date)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NotifyAssert(url, kNC_Date, date); + if (NS_FAILED(rv)) return rv; + + rv = NotifyAssert(kNC_HistoryRoot, kNC_child, url); + if (NS_FAILED(rv)) return rv; + + NotifyFindAssertions(url, row); + } + + return NS_OK; +} + +nsresult +nsGlobalHistory::RemovePageInternal(const char *aSpec) +{ + if (!mTable) return NS_ERROR_NOT_INITIALIZED; + // find the old row, ignore it if we don't have it + nsCOMPtr row; + nsresult rv = FindRow(kToken_URLColumn, aSpec, getter_AddRefs(row)); + if (NS_FAILED(rv)) return NS_OK; + + // remove the row + mdb_err err = mTable->CutRow(mEnv, row); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + + // if there are batches in progress, we don't want to notify + // observers that we're deleting items. the caller promises + // to handle whatever UI updating is necessary when we're finished. + if (!mBatchesInProgress) { + // get the resource so we can do the notification + nsCOMPtr oldRowResource; + gRDFService->GetResource(nsDependentCString(aSpec), getter_AddRefs(oldRowResource)); + NotifyFindUnassertions(oldRowResource, row); + } + + // not a fatal error if we can't cut all column + err = row->CutAllColumns(mEnv); + NS_ASSERTION(err == 0, "couldn't cut all columns"); + + // Sigh. This is pretty bad - if the user selects a bunch of things then + // hits delete we'll be re-writing history over and over. Still, we will + // be whacking global history pretty hard after 1.0 so I don't feel too + // bad. + return Commit(kCompressCommit); +} + +nsresult +nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRTime& aValue) +{ + mdb_err err; + nsCAutoString val; + val.AppendInt(aValue); + + mdbYarn yarn = { (void *)val.get(), val.Length(), val.Length(), 0, 0, nsnull }; + + err = aRow->AddColumn(mEnv, aCol, &yarn); + + if ( err != 0 ) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, + const PRUnichar* aValue) +{ + mdb_err err; + + PRInt32 len = (nsCRT::strlen(aValue) * sizeof(PRUnichar)); + PRUnichar *swapval = nsnull; + + // eventually turn this on when we're confident in mork's ability + // to handle yarn forms properly +#if 0 + NS_ConvertUTF16toUTF8 utf8Value(aValue); + printf("Storing utf8 value %s\n", utf8Value.get()); + mdbYarn yarn = { (void *)utf8Value.get(), utf8Value.Length(), utf8Value.Length(), 0, 1, nsnull }; +#else + + if (mReverseByteOrder) { + // The file is other-endian. Byte-swap the value. + swapval = (PRUnichar *)malloc(len); + if (!swapval) + return NS_ERROR_OUT_OF_MEMORY; + SwapBytes(aValue, swapval, len / sizeof(PRUnichar)); + aValue = swapval; + } + mdbYarn yarn = { (void *)aValue, len, len, 0, 0, nsnull }; + +#endif + err = aRow->AddColumn(mEnv, aCol, &yarn); + if (swapval) + free(swapval); + if (err != 0) return NS_ERROR_FAILURE; + return NS_OK; +} + +nsresult +nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, + const char* aValue) +{ + mdb_err err; + PRInt32 len = PL_strlen(aValue); + mdbYarn yarn = { (void*) aValue, len, len, 0, 0, nsnull }; + err = aRow->AddColumn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRInt32 aValue) +{ + mdb_err err; + + nsCAutoString buf; buf.AppendInt(aValue); + mdbYarn yarn = { (void *)buf.get(), buf.Length(), buf.Length(), 0, 0, nsnull }; + + err = aRow->AddColumn(mEnv, aCol, &yarn); + + if (err != 0) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, + nsAString& aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + aResult.Truncate(0); + if (!yarn.mYarn_Fill) + return NS_OK; + + switch (yarn.mYarn_Form) { + case 0: // unicode + if (mReverseByteOrder) { + // The file is other-endian; we must byte-swap the result. + PRUnichar *swapval; + int len = yarn.mYarn_Fill / sizeof(PRUnichar); + swapval = (PRUnichar *)malloc(yarn.mYarn_Fill); + if (!swapval) + return NS_ERROR_OUT_OF_MEMORY; + SwapBytes((const PRUnichar *)yarn.mYarn_Buf, swapval, len); + aResult.Assign(swapval, len); + free(swapval); + } + else + aResult.Assign((const PRUnichar *)yarn.mYarn_Buf, yarn.mYarn_Fill/sizeof(PRUnichar)); + break; + + // eventually we'll be supporting this in SetRowValue() + case 1: // UTF8 + aResult.Assign(NS_ConvertUTF8toUTF16((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill)); + break; + + default: + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +// Copy an array of 16-bit values, reversing the byte order. +void +nsGlobalHistory::SwapBytes(const PRUnichar *source, PRUnichar *dest, + PRInt32 aLen) +{ + PRUint16 c; + const PRUnichar *inp; + PRUnichar *outp; + PRInt32 i; + + inp = source; + outp = dest; + for (i = 0; i < aLen; i++) { + c = *inp++; + *outp++ = (((c >> 8) & 0xff) | (c << 8)); + } + return; +} + +nsresult +nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, + PRTime *aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + *aResult = LL_ZERO; + + if (!yarn.mYarn_Fill || !yarn.mYarn_Buf) + return NS_OK; + + PR_sscanf((const char*)yarn.mYarn_Buf, "%lld", aResult); + + return NS_OK; +} + +nsresult +nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, + PRInt32 *aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + if (yarn.mYarn_Buf) + *aResult = atoi((char *)yarn.mYarn_Buf); + else + *aResult = 0; + + return NS_OK; +} + +nsresult +nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, + nsACString& aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + const char* startPtr = (const char*)yarn.mYarn_Buf; + if (startPtr) + aResult.Assign(Substring(startPtr, startPtr + yarn.mYarn_Fill)); + else + aResult.Truncate(); + + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::AddPageWithDetails(nsIURI *aURI, const PRUnichar *aTitle, + PRTime aLastVisitDate) +{ + nsresult rv = AddPageToDatabase(aURI, PR_FALSE, PR_TRUE, aLastVisitDate, nsnull); + if (NS_FAILED(rv)) return rv; + + return SetPageTitle(aURI, nsDependentString(aTitle)); +} + +NS_IMETHODIMP +nsGlobalHistory::GetCount(PRUint32* aCount) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + if (!mTable) return NS_ERROR_FAILURE; + + mdb_err err = mTable->GetCount(mEnv, aCount); + return (err == 0) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGlobalHistory::SetPageTitle(nsIURI *aURI, const nsAString& aTitle) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoString titleString(StringHead(aTitle, HISTORY_TITLE_LENGTH_MAX)); + if (titleString.Length() < aTitle.Length() && + NS_IS_HIGH_SURROGATE(titleString.Last())) + titleString.Truncate(titleString.Length()-1); + + // skip about: URIs to avoid reading in the db (about:blank, especially) + PRBool isAbout; + rv = aURI->SchemeIs("about", &isAbout); + NS_ENSURE_SUCCESS(rv, rv); + if (isAbout) return NS_OK; + + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + nsCAutoString URISpec; + rv = aURI->GetSpec(URISpec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row)); + + // if the row doesn't exist, we silently succeed + if (rv == NS_ERROR_NOT_AVAILABLE) return NS_OK; + NS_ENSURE_SUCCESS(rv, rv); + + // Get the old title so we can notify observers + nsAutoString oldtitle; + rv = GetRowValue(row, kToken_NameColumn, oldtitle); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr oldname; + if (!oldtitle.IsEmpty()) { + rv = gRDFService->GetLiteral(oldtitle.get(), getter_AddRefs(oldname)); + if (NS_FAILED(rv)) return rv; + } + + SetRowValue(row, kToken_NameColumn, titleString.get()); + + // ...and update observers + nsCOMPtr url; + rv = gRDFService->GetResource(URISpec, getter_AddRefs(url)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr name; + rv = gRDFService->GetLiteral(titleString.get(), getter_AddRefs(name)); + if (NS_FAILED(rv)) return rv; + + if (oldname) { + rv = NotifyChange(url, kNC_Name, oldname, name); + } + else { + rv = NotifyAssert(url, kNC_Name, name); + } + + return rv; +} + + +NS_IMETHODIMP +nsGlobalHistory::RemovePage(nsIURI *aURI) +{ + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_SUCCEEDED(rv)) + rv = RemovePageInternal(spec.get()); + return rv; +} + +NS_IMETHODIMP +nsGlobalHistory::RemovePagesFromHost(const nsACString &aHost, PRBool aEntireDomain) +{ + const nsCString &host = PromiseFlatCString(aHost); + + matchHost_t hostInfo; + hostInfo.history = this; + hostInfo.entireDomain = aEntireDomain; + hostInfo.host = host.get(); + + return RemoveMatchingRows(matchHostCallback, (void *)&hostInfo, PR_TRUE); +} + +PRBool +nsGlobalHistory::MatchHost(nsIMdbRow *aRow, + matchHost_t *hostInfo) +{ + mdb_err err; + nsresult rv; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, kToken_URLColumn, &yarn); + if (err != 0) return PR_FALSE; + + nsCOMPtr uri; + // do smart zero-termination + const char* startPtr = (const char *)yarn.mYarn_Buf; + rv = NS_NewURI(getter_AddRefs(uri), + Substring(startPtr, startPtr + yarn.mYarn_Fill)); + if (NS_FAILED(rv)) return PR_FALSE; + + nsCAutoString urlHost; + rv = uri->GetHost(urlHost); + if (NS_FAILED(rv)) return PR_FALSE; + + if (PL_strcmp(urlHost.get(), hostInfo->host) == 0) + return PR_TRUE; + + // now try for a domain match, if necessary + if (hostInfo->entireDomain) { + // do a reverse-search to match the end of the string + const char *domain = PL_strrstr(urlHost.get(), hostInfo->host); + + // now verify that we're matching EXACTLY the domain, and + // not some random string inside the hostname + if (domain && (PL_strcmp(domain, hostInfo->host) == 0)) + return PR_TRUE; + } + + return PR_FALSE; +} + +NS_IMETHODIMP +nsGlobalHistory::RemoveAllPages() +{ + nsresult rv; + + rv = RemoveMatchingRows(matchAllCallback, nsnull, PR_TRUE); + if (NS_FAILED(rv)) return rv; + + // Reset the file byte order. + rv = InitByteOrder(PR_TRUE); + if (NS_FAILED(rv)) return rv; + + return Commit(kCompressCommit); +} + +nsresult +nsGlobalHistory::RemoveMatchingRows(rowMatchCallback aMatchFunc, + void *aClosure, + PRBool notify) +{ + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + nsresult rv; + if (!mTable) return NS_OK; + + mdb_err err; + mdb_count count; + err = mTable->GetCount(mEnv, &count); + if (err != 0) return NS_ERROR_FAILURE; + + BeginUpdateBatch(); + + // Begin the batch. + int marker; + err = mTable->StartBatchChangeHint(mEnv, &marker); + NS_ASSERTION(err == 0, "unable to start batch"); + if (err != 0) return NS_ERROR_FAILURE; + + nsCOMPtr resource; + // XXX from here until end batch, no early returns! + for (mdb_pos pos = count - 1; pos >= 0; --pos) { + nsCOMPtr row; + err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row)); + NS_ASSERTION(err == 0, "unable to get row"); + if (err != 0) + break; + + NS_ASSERTION(row != nsnull, "no row"); + if (! row) + continue; + + // now we actually do the match. If this row doesn't match, loop again + if (!(aMatchFunc)(row, aClosure)) + continue; + + if (notify) { + // What's the URL? We need to know to properly notify our RDF + // observers. + mdbYarn yarn; + err = row->AliasCellYarn(mEnv, kToken_URLColumn, &yarn); + if (err != 0) + continue; + + const char* startPtr = (const char*) yarn.mYarn_Buf; + nsCAutoString uri(Substring(startPtr, startPtr+yarn.mYarn_Fill)); + rv = gRDFService->GetResource(uri, getter_AddRefs(resource)); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get resource"); + if (NS_FAILED(rv)) + continue; + } + // Officially cut the row *now*, before notifying any observers: + // that way, any re-entrant calls won't find the row. + err = mTable->CutRow(mEnv, row); + NS_ASSERTION(err == 0, "couldn't cut row"); + if (err != 0) + continue; + + // possibly avoid leakage + err = row->CutAllColumns(mEnv); + NS_ASSERTION(err == 0, "couldn't cut all columns"); + // we'll notify regardless of whether we could successfully + // CutAllColumns or not. + + + } + + // Finish the batch. + err = mTable->EndBatchChangeHint(mEnv, &marker); + NS_ASSERTION(err == 0, "error ending batch"); + + EndUpdateBatch(); + + if (err != 0) + return NS_ERROR_FAILURE; + + return Commit(kCompressCommit); +} + +NS_IMETHODIMP +nsGlobalHistory::IsVisited(nsIURI* aURI, PRBool *_retval) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsresult rv; + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_NOT_INITIALIZED); + + nsCAutoString URISpec; + rv = aURI->GetSpec(URISpec); + NS_ENSURE_SUCCESS(rv, rv); + + rv = FindRow(kToken_URLColumn, URISpec.get(), nsnull); + *_retval = NS_SUCCEEDED(rv); + + // Hidden, typed URIs haven't really been visited yet. They've only + // been typed in and the actual load hasn't happened yet. We maintain + // the list of hidden+typed URIs in memory in mTypedHiddenURIs because + // the list will usually be small and checking the actual Mork row + // would require several dynamic memory allocations. + if (*_retval && mTypedHiddenURIs.Contains(URISpec)) + { + *_retval = PR_FALSE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::GetLastPageVisited(nsACString& _retval) +{ + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + NS_ENSURE_STATE(mMetaRow); + + mdb_err err = GetRowValue(mMetaRow, kToken_LastPageVisited, _retval); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + + return NS_OK; +} + +// Set the byte order in the history file. The given string value should +// be either "BE" (big-endian) or "LE" (little-endian). +nsresult +nsGlobalHistory::SaveByteOrder(const char *aByteOrder) +{ + if (PL_strcmp(aByteOrder, "BE") != 0 && PL_strcmp(aByteOrder, "LE") != 0) { + NS_WARNING("Invalid byte order argument."); + return NS_ERROR_INVALID_ARG; + } + NS_ENSURE_STATE(mMetaRow); + + mdb_err err = SetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + + return NS_OK; +} + +// Get the file byte order. +nsresult +nsGlobalHistory::GetByteOrder(char **_retval) +{ + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_STATE(mMetaRow); + + nsCAutoString byteOrder; + mdb_err err = GetRowValue(mMetaRow, kToken_ByteOrder, byteOrder); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + + *_retval = ToNewCString(byteOrder); + NS_ENSURE_TRUE(*_retval, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + + +NS_IMETHODIMP +nsGlobalHistory::HidePage(nsIURI *aURI) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aURI); + + nsCAutoString URISpec; + rv = aURI->GetSpec(URISpec); + NS_ENSURE_SUCCESS(rv, rv); + + if (URISpec.Length() > HISTORY_URI_LENGTH_MAX) + return NS_OK; + +#ifdef DEBUG_bsmedberg + printf("nsGlobalHistory::HidePage: %s\n", URISpec.get()); +#endif + + nsCOMPtr row; + + rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row)); + + if (NS_FAILED(rv)) { + // it hasn't been visited yet, but if one ever comes in, we need + // to hide it when it is visited + rv = AddURI(aURI, PR_FALSE, PR_FALSE, nsnull); + if (NS_FAILED(rv)) return rv; + + rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) return rv; + } + + rv = SetRowValue(row, kToken_HiddenColumn, 1); + if (NS_FAILED(rv)) return rv; + + // now pretend as if this row was deleted + // HasAssertion() correctly checks the Hidden column to show that + // the row is hidden + nsCOMPtr urlResource; + rv = gRDFService->GetResource(URISpec, getter_AddRefs(urlResource)); + if (NS_FAILED(rv)) return rv; + return NotifyFindUnassertions(urlResource, row); +} + +NS_IMETHODIMP +nsGlobalHistory::MarkPageAsTyped(nsIURI *aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) + return rv; + + if (spec.Length() > HISTORY_URI_LENGTH_MAX) + return NS_OK; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) { + rv = AddNewPageToDatabase(aURI, GetNow(), PR_FALSE, PR_TRUE, nsnull, getter_AddRefs(row)); + NS_ENSURE_SUCCESS(rv, rv); + + // We don't know if this is a valid URI yet. Hide it until it finishes + // loading. + SetRowValue(row, kToken_HiddenColumn, 1); + mTypedHiddenURIs.Put(spec); + } + + return SetRowValue(row, kToken_TypedColumn, 1); +} + +NS_IMETHODIMP +nsGlobalHistory::AddDocumentRedirect(nsIChannel *aOldChannel, + nsIChannel *aNewChannel, + PRInt32 aFlags, + PRBool aTopLevel) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGlobalHistory::SetURIGeckoFlags(nsIURI *aURI, PRUint32 aFlags) +{ + NS_ENSURE_ARG_POINTER(aURI); + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) + return rv; + + return SetRowValue(row, kToken_GeckoFlagsColumn, (PRInt32)aFlags); +} + +NS_IMETHODIMP +nsGlobalHistory::GetURIGeckoFlags(nsIURI *aURI, PRUint32* aFlags) +{ + NS_ENSURE_ARG_POINTER(aURI); + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) + return rv; + + if (!HasCell(mEnv, row, kToken_GeckoFlagsColumn)) + return NS_ERROR_FAILURE; + + PRInt32 val; + mdb_err err = GetRowValue(row, kToken_GeckoFlagsColumn, &val); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + *aFlags = val; + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// nsIRDFDataSource methods + +NS_IMETHODIMP +nsGlobalHistory::GetURI(char* *aURI) +{ + NS_PRECONDITION(aURI != nsnull, "null ptr"); + if (! aURI) + return NS_ERROR_NULL_POINTER; + + *aURI = nsCRT::strdup("rdf:history"); + if (! *aURI) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + + +NS_IMETHODIMP +nsGlobalHistory::GetSource(nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + PRBool aTruthValue, + nsIRDFResource** aSource) +{ + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nsnull, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + *aSource = nsnull; + + if (aProperty == kNC_URL) { + // See if we have the row... + + // XXX We could be more forgiving here, and check for literal + // values as well. + nsCOMPtr target = do_QueryInterface(aTarget); + if (target && IsURLInHistory(target)) + return CallQueryInterface(aTarget, aSource); + + } + else if ((aProperty == kNC_Date) || + (aProperty == kNC_FirstVisitDate) || + (aProperty == kNC_VisitCount) || + (aProperty == kNC_Name) || + (aProperty == kNC_Hostname) || + (aProperty == kNC_Referrer)) { + // Call GetSources() and return the first one we find. + nsCOMPtr sources; + rv = GetSources(aProperty, aTarget, aTruthValue, getter_AddRefs(sources)); + if (NS_FAILED(rv)) return rv; + + PRBool hasMore; + rv = sources->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (hasMore) { + nsCOMPtr isupports; + rv = sources->GetNext(getter_AddRefs(isupports)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(isupports, aSource); + } + } + + return NS_RDF_NO_VALUE; +} + +NS_IMETHODIMP +nsGlobalHistory::GetSources(nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + PRBool aTruthValue, + nsISimpleEnumerator** aSources) +{ + // XXX TODO: make sure each URL in history is connected back to + // NC:HistoryRoot. + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nsnull, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + if (aProperty == kNC_URL) { + // Call GetSource() and return a singleton enumerator for the URL. + nsCOMPtr source; + rv = GetSource(aProperty, aTarget, aTruthValue, getter_AddRefs(source)); + if (NS_FAILED(rv)) return rv; + + return NS_NewSingletonEnumerator(aSources, source); + } + else { + // See if aProperty is something we understand, and create an + // URLEnumerator to select URLs with the appropriate value. + + mdb_column col = 0; // == "not a property that I grok" + void* value = nsnull; + PRInt32 len = 0; + + // PRInt64/date properties + if (aProperty == kNC_Date || + aProperty == kNC_FirstVisitDate) { + nsCOMPtr date = do_QueryInterface(aTarget); + if (date) { + PRInt64 n; + + rv = date->GetValue(&n); + if (NS_FAILED(rv)) return rv; + + nsCAutoString valueStr; + valueStr.AppendInt(n); + + value = (void *)ToNewCString(valueStr); + if (aProperty == kNC_Date) + col = kToken_LastVisitDateColumn; + else + col = kToken_FirstVisitDateColumn; + } + } + // PRInt32 properties + else if (aProperty == kNC_VisitCount) { + nsCOMPtr countLiteral = do_QueryInterface(aTarget); + if (countLiteral) { + PRInt32 intValue; + rv = countLiteral->GetValue(&intValue); + if (NS_FAILED(rv)) return rv; + + nsAutoString valueStr; valueStr.AppendInt(intValue); + value = ToNewUnicode(valueStr); + len = valueStr.Length() * sizeof(PRUnichar); + col = kToken_VisitCountColumn; + } + + } + // PRUnichar* properties + else if (aProperty == kNC_Name) { + nsCOMPtr name = do_QueryInterface(aTarget); + if (name) { + PRUnichar* p; + rv = name->GetValue(&p); + if (NS_FAILED(rv)) return rv; + + len = nsCRT::strlen(p) * sizeof(PRUnichar); + value = p; + + col = kToken_NameColumn; + } + } + + // char* properties + else if (aProperty == kNC_Hostname || + aProperty == kNC_Referrer) { + col = kToken_ReferrerColumn; + nsCOMPtr referrer = do_QueryInterface(aTarget); + if (referrer) { + char* p; + rv = referrer->GetValue(&p); + if (NS_FAILED(rv)) return rv; + + len = PL_strlen(p); + value = p; + + if (aProperty == kNC_Hostname) + col = kToken_HostnameColumn; + else if (aProperty == kNC_Referrer) + col = kToken_ReferrerColumn; + } + } + + if (col) { + // The URLEnumerator takes ownership of the bytes allocated in |value|. + URLEnumerator* result = new URLEnumerator(kToken_URLColumn, col, + kToken_HiddenColumn, + value, len); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + rv = result->Init(mEnv, mTable); + if (NS_FAILED(rv)) return rv; + + *aSources = result; + NS_ADDREF(*aSources); + return NS_OK; + } + } + + return NS_NewEmptyEnumerator(aSources); +} + +NS_IMETHODIMP +nsGlobalHistory::GetTarget(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + PRBool aTruthValue, + nsIRDFNode** aTarget) +{ + + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // Initialize return value. + *aTarget = nsnull; + + // Only "positive" assertions here. + if (! aTruthValue) + return NS_RDF_NO_VALUE; + + // XXX eventually use IsFindResource to simply return the first + // matching row? + if (aProperty == kNC_child && + (aSource == kNC_HistoryRoot || + aSource == kNC_HistoryByDateAndSite || + aSource == kNC_HistoryByDate || + IsFindResource(aSource))) { + + // If they're asking for all the children of the HistoryRoot, call + // through to GetTargets() and return the first one. + nsCOMPtr targets; + rv = GetTargets(aSource, aProperty, aTruthValue, getter_AddRefs(targets)); + if (NS_FAILED(rv)) return rv; + + PRBool hasMore; + rv = targets->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) return NS_RDF_NO_VALUE; + + nsCOMPtr isupports; + rv = targets->GetNext(getter_AddRefs(isupports)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(isupports, aTarget); + } + else if ((aProperty == kNC_Date) || + (aProperty == kNC_FirstVisitDate) || + (aProperty == kNC_VisitCount) || + (aProperty == kNC_AgeInDays) || + (aProperty == kNC_Name) || + (aProperty == kNC_NameSort) || + (aProperty == kNC_Hostname) || + (aProperty == kNC_Referrer) || + (aProperty == kNC_URL) || + (aProperty == kNC_DayFolderIndex)) { + + const char* uri; + rv = aSource->GetValueConst(&uri); + if (NS_FAILED(rv)) return rv; + + // url is self-referential, so we'll always just return itself + // however, don't return the URLs of find resources + if (aProperty == kNC_URL && !IsFindResource(aSource)) { + + nsCOMPtr uriLiteral; + rv = gRDFService->GetLiteral(NS_ConvertUTF8toUTF16(uri).get(), getter_AddRefs(uriLiteral)); + if (NS_FAILED(rv)) return(rv); + *aTarget = uriLiteral; + NS_ADDREF(*aTarget); + return NS_OK; + } + + // find URIs are special + if (IsFindResource(aSource)) { + if (aProperty == kNC_Name) + return GetFindUriName(uri, aTarget); + + if (aProperty == kNC_NameSort) { + // parse out the 'text' token + nsVoidArray tokenList; + FindUrlToTokenList(uri, tokenList); + + nsCOMPtr literal; + + for (PRInt32 i = 0; i < tokenList.Count(); ++i) { + tokenPair* token = static_cast(tokenList[i]); + + if (!strncmp(token->tokenName, "text", token->tokenNameLength)) { + rv = gRDFService->GetLiteral(NS_ConvertUTF8toUTF16(Substring(token->tokenValue, token->tokenValue + token->tokenValueLength)).get(), + getter_AddRefs(literal)); + // We don't break out of the loop here because there could be other text tokens in the string. + // The last one is the most specific so wait and see if we've got one... + } + } + + FreeTokenList(tokenList); + + if (literal && NS_SUCCEEDED(rv)) { + *aTarget = literal; + NS_ADDREF(*aTarget); + return NS_OK; + } + *aTarget = nsnull; + return rv; + } + } + + // ok, we got this far, so we have to retrieve something from + // the row in the database + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, uri, getter_AddRefs(row)); + if (NS_FAILED(rv)) return NS_RDF_NO_VALUE; + + mdb_err err; + // ...and then depending on the property they want, we'll pull the + // cell they want out of it. + if (aProperty == kNC_Date || + aProperty == kNC_FirstVisitDate) { + // Last visit date + PRTime i; + if (aProperty == kNC_Date) + rv = GetRowValue(row, kToken_LastVisitDateColumn, &i); + else + rv = GetRowValue(row, kToken_FirstVisitDateColumn, &i); + + if (NS_FAILED(rv)) return rv; + + nsCOMPtr date; + rv = gRDFService->GetDateLiteral(i, getter_AddRefs(date)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(date, aTarget); + } + else if (aProperty == kNC_VisitCount) { + mdbYarn yarn; + err = row->AliasCellYarn(mEnv, kToken_VisitCountColumn, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + PRInt32 visitCount = 0; + rv = GetRowValue(row, kToken_VisitCountColumn, &visitCount); + if (NS_FAILED(rv) || visitCount <1) + visitCount = 1; // assume we've visited at least once + + nsCOMPtr visitCountLiteral; + rv = gRDFService->GetIntLiteral(visitCount, + getter_AddRefs(visitCountLiteral)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(visitCountLiteral, aTarget); + } + else if (aProperty == kNC_AgeInDays) { + PRTime lastVisitDate; + rv = GetRowValue(row, kToken_LastVisitDateColumn, &lastVisitDate); + if (NS_FAILED(rv)) return rv; + + PRInt32 days = GetAgeInDays(NormalizeTime(GetNow()), lastVisitDate); + + nsCOMPtr ageLiteral; + rv = gRDFService->GetIntLiteral(days, getter_AddRefs(ageLiteral)); + if (NS_FAILED(rv)) return rv; + + *aTarget = ageLiteral; + NS_ADDREF(*aTarget); + return NS_OK; + } + else if (aProperty == kNC_Name || + aProperty == kNC_NameSort) { + // Site name (i.e., page title) + nsAutoString title; + rv = GetRowValue(row, kToken_NameColumn, title); + if (NS_FAILED(rv) || title.IsEmpty()) { + // yank out the filename from the url, use that + nsCOMPtr aUri; + rv = NS_NewURI(getter_AddRefs(aUri), uri); + if (NS_FAILED(rv)) return rv; + nsCOMPtr urlObj(do_QueryInterface(aUri)); + if (!urlObj) + return NS_ERROR_FAILURE; + + nsCAutoString filename; + rv = urlObj->GetFileName(filename); + if (NS_FAILED(rv) || filename.IsEmpty()) { + // ok fine. we'll use the file path. then we give up! + rv = urlObj->GetPath(filename); + if (strcmp(filename.get(), "/") == 0) { + // if the top of a site does not have a title + // (common for redirections) then return the hostname + rv = GetRowValue(row, kToken_HostnameColumn, filename); + } + } + + if (NS_FAILED(rv)) return rv; + + // assume the url is in UTF8 + title = NS_ConvertUTF8toUTF16(filename); + } + if (NS_FAILED(rv)) return rv; + + nsCOMPtr name; + rv = gRDFService->GetLiteral(title.get(), getter_AddRefs(name)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(name, aTarget); + } + else if (aProperty == kNC_Hostname || + aProperty == kNC_Referrer) { + + nsCAutoString str; + if (aProperty == kNC_Hostname) + rv = GetRowValue(row, kToken_HostnameColumn, str); + else if (aProperty == kNC_Referrer) + rv = GetRowValue(row, kToken_ReferrerColumn, str); + + if (NS_FAILED(rv)) return rv; + // Avoid trying to create a resource from an empty string, which + // will raise an exception + if (str.IsEmpty()) return NS_RDF_NO_VALUE; + + nsCOMPtr resource; + rv = gRDFService->GetResource(str, getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(resource, aTarget); + } + + else { + NS_NOTREACHED("huh, how'd I get here?"); + } + } + return NS_RDF_NO_VALUE; +} + +void +nsGlobalHistory::Sync() +{ + if (mDirty) + Flush(); + + mDirty = PR_FALSE; + mSyncTimer = nsnull; +} + +void +nsGlobalHistory::ExpireNow() +{ + mNowValid = PR_FALSE; + mExpireNowTimer = nsnull; +} + +// when we're dirty, we want to make sure we sync again soon, +// but make sure that we don't keep syncing if someone is surfing +// a lot, so cancel the existing timer if any is still waiting to fire +nsresult +nsGlobalHistory::SetDirty() +{ + nsresult rv; + + if (mSyncTimer) + mSyncTimer->Cancel(); + + if (!mSyncTimer) { + mSyncTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_FAILED(rv)) return rv; + } + + mDirty = PR_TRUE; + mSyncTimer->InitWithFuncCallback(fireSyncTimer, this, HISTORY_SYNC_TIMEOUT, + nsITimer::TYPE_ONE_SHOT); + + + return NS_OK; +} + +// hack to avoid calling PR_Now() too often, as is the case when +// we're asked the ageindays of many history entries in a row +PRTime +nsGlobalHistory::GetNow() +{ + if (!mNowValid) { // not dirty, mLastNow is crufty + mLastNow = PR_Now(); + mNowValid = PR_TRUE; + if (!mExpireNowTimer) + mExpireNowTimer = do_CreateInstance("@mozilla.org/timer;1"); + + if (mExpireNowTimer) + mExpireNowTimer->InitWithFuncCallback(expireNowTimer, this, HISTORY_EXPIRE_NOW_TIMEOUT, + nsITimer::TYPE_ONE_SHOT); + } + + return mLastNow; +} + +NS_IMETHODIMP +nsGlobalHistory::GetTargets(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + PRBool aTruthValue, + nsISimpleEnumerator** aTargets) +{ + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + if (!aTruthValue) + return NS_NewEmptyEnumerator(aTargets); + + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + // list all URLs off the root + if ((aSource == kNC_HistoryRoot) && + (aProperty == kNC_child)) { + URLEnumerator* result = new URLEnumerator(kToken_URLColumn, + kToken_HiddenColumn); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + rv = result->Init(mEnv, mTable); + if (NS_FAILED(rv)) return rv; + + *aTargets = result; + NS_ADDREF(*aTargets); + return NS_OK; + } + else if ((aSource == kNC_HistoryByDateAndSite) && + (aProperty == kNC_child)) { + + return GetRootDayQueries(aTargets, PR_TRUE); + } + else if ((aSource == kNC_HistoryByDate) && + (aProperty == kNC_child)) { + + return GetRootDayQueries(aTargets, PR_FALSE); + } + else if (aProperty == kNC_child && + IsFindResource(aSource)) { + return CreateFindEnumerator(aSource, aTargets); + } + + else if ((aProperty == kNC_Date) || + (aProperty == kNC_FirstVisitDate) || + (aProperty == kNC_VisitCount) || + (aProperty == kNC_AgeInDays) || + (aProperty == kNC_Name) || + (aProperty == kNC_Hostname) || + (aProperty == kNC_Referrer) || + (aProperty == kNC_DayFolderIndex)) { + nsresult rv; + + nsCOMPtr target; + rv = GetTarget(aSource, aProperty, aTruthValue, getter_AddRefs(target)); + if (NS_FAILED(rv)) return rv; + + if (rv == NS_OK) { + return NS_NewSingletonEnumerator(aTargets, target); + } + } + + // we've already answered the queries from the root, so we must be + // looking for real entries + + return NS_NewEmptyEnumerator(aTargets); +} + +NS_IMETHODIMP +nsGlobalHistory::Assert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + PRBool aTruthValue) +{ + // History cannot be modified + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +nsGlobalHistory::Unassert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + // translate into an appropriate removehistory call + nsresult rv; + if ((aSource == kNC_HistoryRoot || aSource == kNC_HistoryByDateAndSite || aSource == kNC_HistoryByDate + || IsFindResource(aSource)) && + aProperty == kNC_child) { + + nsCOMPtr resource = do_QueryInterface(aTarget, &rv); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + const char* targetUrl; + rv = resource->GetValueConst(&targetUrl); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + if (IsFindResource(resource)) { + // convert uri to a query + searchQuery query; + rv = FindUrlToSearchQuery(targetUrl, query); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + matchQuery_t matchQuery; + matchQuery.history = this; + matchQuery.query = &query; + rv = RemoveMatchingRows(matchQueryCallback, (void*)&matchQuery, PR_TRUE); + FreeSearchQuery(query); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + // if there are batches in progress, we don't want to notify + // observers that we're deleting items. the caller promises + // to handle whatever UI updating is necessary when we're finished. + if (!mBatchesInProgress) + NotifyUnassert(aSource, aProperty, aTarget); + + return NS_OK; + } + + // ignore any error + rv = RemovePageInternal(targetUrl); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + if (!mBatchesInProgress && IsFindResource(aSource)) { + // if there are batches in progress, we don't want to notify + // observers that we're deleting items. the caller promises + // to handle whatever UI updating is necessary when we're finished. + NotifyUnassert(aSource, aProperty, aTarget); + } + + return NS_OK; + } + + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +nsGlobalHistory::Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) +{ + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +nsGlobalHistory::Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +nsGlobalHistory::HasAssertion(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + PRBool aTruthValue, + PRBool* aHasAssertion) +{ + + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nsnull, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + // Only "positive" assertions here. + if (!aTruthValue) { + *aHasAssertion = PR_FALSE; + return NS_OK; + } + + nsresult rv; + + // answer if a specific row matches a find URI + // + // at some point, we should probably match groupby= findURIs with + // findURIs that match all their criteria + // + nsCOMPtr target = do_QueryInterface(aTarget); + if (target && + aProperty == kNC_child && + IsFindResource(aSource) && + !IsFindResource(target)) { + + const char *uri; + rv = target->GetValueConst(&uri); + if (NS_FAILED(rv)) return rv; + + searchQuery query; + FindUrlToSearchQuery(uri, query); + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, uri, getter_AddRefs(row)); + // not even in history. don't bother trying + if (NS_FAILED(rv) || HasCell(mEnv, row, kToken_HiddenColumn)) { + *aHasAssertion = PR_FALSE; + return NS_OK; + } + + *aHasAssertion = RowMatches(row, &query, PR_TRUE); + FreeSearchQuery(query); + return NS_OK; + } + + // Do |GetTargets()| and grovel through the results to see if we + // have the assertion. + // + // XXX *AHEM*, this could be implemented much more efficiently... + + nsCOMPtr targets; + rv = GetTargets(aSource, aProperty, aTruthValue, getter_AddRefs(targets)); + if (NS_FAILED(rv)) return rv; + + while (1) { + PRBool hasMore; + rv = targets->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) + break; + + nsCOMPtr isupports; + rv = targets->GetNext(getter_AddRefs(isupports)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr node = do_QueryInterface(isupports); + if (node.get() == aTarget) { + *aHasAssertion = PR_TRUE; + return NS_OK; + } + } + + *aHasAssertion = PR_FALSE; + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::AddObserver(nsIRDFObserver* aObserver) +{ + NS_PRECONDITION(aObserver != nsnull, "null ptr"); + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.AppendObject(aObserver); + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::RemoveObserver(nsIRDFObserver* aObserver) +{ + NS_PRECONDITION(aObserver != nsnull, "null ptr"); + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.RemoveObject(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, PRBool *result) +{ + NS_PRECONDITION(aNode != nsnull, "null ptr"); + if (! aNode) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr resource = do_QueryInterface(aNode); + if (resource && IsURLInHistory(resource)) { + *result = (aArc == kNC_child); + } + else { + *result = PR_FALSE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, PRBool *result) +{ + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + if ((aSource == kNC_HistoryRoot) || + (aSource == kNC_HistoryByDateAndSite) || + (aSource == kNC_HistoryByDate)) { + *result = (aArc == kNC_child); + } + else if (IsFindResource(aSource)) { + // we handle children of find urls + *result = (aArc == kNC_child || + aArc == kNC_Name || + aArc == kNC_NameSort || + aArc == kNC_DayFolderIndex); + } + else if (IsURLInHistory(aSource)) { + // If the URL is in the history, then it'll have all the + // appropriate attributes. + *result = (aArc == kNC_Date || + aArc == kNC_FirstVisitDate || + aArc == kNC_VisitCount || + aArc == kNC_Name || + aArc == kNC_Hostname || + aArc == kNC_Referrer); + } + else { + *result = PR_FALSE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::ArcLabelsIn(nsIRDFNode* aNode, + nsISimpleEnumerator** aLabels) +{ + NS_PRECONDITION(aNode != nsnull, "null ptr"); + if (! aNode) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr resource = do_QueryInterface(aNode); + if (resource && IsURLInHistory(resource)) { + return NS_NewSingletonEnumerator(aLabels, kNC_child); + } + else { + return NS_NewEmptyEnumerator(aLabels); + } +} + +NS_IMETHODIMP +nsGlobalHistory::ArcLabelsOut(nsIRDFResource* aSource, + nsISimpleEnumerator** aLabels) +{ + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + if ((aSource == kNC_HistoryRoot) || + (aSource == kNC_HistoryByDateAndSite) || + (aSource == kNC_HistoryByDate)) { + return NS_NewSingletonEnumerator(aLabels, kNC_child); + } + else if (IsURLInHistory(aSource)) { + // If the URL is in the history, then it'll have all the + // appropriate attributes. + nsCOMPtr array; + rv = NS_NewISupportsArray(getter_AddRefs(array)); + if (NS_FAILED(rv)) return rv; + + array->AppendElement(kNC_Date); + array->AppendElement(kNC_FirstVisitDate); + array->AppendElement(kNC_VisitCount); + array->AppendElement(kNC_Name); + array->AppendElement(kNC_Hostname); + array->AppendElement(kNC_Referrer); + + return NS_NewArrayEnumerator(aLabels, array); + } + else if (IsFindResource(aSource)) { + nsCOMPtr array; + rv = NS_NewISupportsArray(getter_AddRefs(array)); + if (NS_FAILED(rv)) return rv; + + array->AppendElement(kNC_child); + array->AppendElement(kNC_Name); + array->AppendElement(kNC_NameSort); + array->AppendElement(kNC_DayFolderIndex); + + return NS_NewArrayEnumerator(aLabels, array); + } + else { + return NS_NewEmptyEnumerator(aLabels); + } +} + +NS_IMETHODIMP +nsGlobalHistory::GetAllCmds(nsIRDFResource* aSource, + nsISimpleEnumerator/**/** aCommands) +{ + return NS_NewEmptyEnumerator(aCommands); +} + +NS_IMETHODIMP +nsGlobalHistory::IsCommandEnabled(nsISupportsArray/**/* aSources, + nsIRDFResource* aCommand, + nsISupportsArray/**/* aArguments, + PRBool* aResult) +{ + NS_NOTYETIMPLEMENTED("sorry"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGlobalHistory::DoCommand(nsISupportsArray/**/* aSources, + nsIRDFResource* aCommand, + nsISupportsArray/**/* aArguments) +{ + NS_NOTYETIMPLEMENTED("sorry"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGlobalHistory::GetAllResources(nsISimpleEnumerator** aResult) +{ + URLEnumerator* result = new URLEnumerator(kToken_URLColumn, + kToken_HiddenColumn); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + rv = result->Init(mEnv, mTable); + if (NS_FAILED(rv)) return rv; + + *aResult = result; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::BeginUpdateBatch() +{ + nsresult rv = NS_OK; + + ++mBatchesInProgress; + + PRUint32 i = mObservers.Count(); + while (i > 0) { + rv = mObservers[--i]->OnBeginUpdateBatch(this); + } + return rv; +} + +NS_IMETHODIMP +nsGlobalHistory::EndUpdateBatch() +{ + nsresult rv = NS_OK; + + --mBatchesInProgress; + + PRUint32 i = mObservers.Count(); + while (i > 0) { + rv = mObservers[--i]->OnEndUpdateBatch(this); + } + return rv; +} + + + +//////////////////////////////////////////////////////////////////////// +// nsIRDFRemoteDataSource + +NS_IMETHODIMP +nsGlobalHistory::GetLoaded(PRBool* _result) +{ + *_result = PR_TRUE; + return NS_OK; +} + + + +NS_IMETHODIMP +nsGlobalHistory::Init(const char* aURI) +{ + return(NS_OK); +} + + + +NS_IMETHODIMP +nsGlobalHistory::Refresh(PRBool aBlocking) +{ + return(NS_OK); +} + +NS_IMETHODIMP +nsGlobalHistory::Flush() +{ + return Commit(kLargeCommit); +} + +NS_IMETHODIMP +nsGlobalHistory::FlushTo(const char* aURI) +{ + // Do not ever implement this (security) + return NS_ERROR_NOT_IMPLEMENTED; +} + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// Miscellaneous implementation methods +// + +nsresult +nsGlobalHistory::Init() +{ + nsresult rv; + + // we'd like to get this pref when we need it, but at that point, + // we can't get the pref service. register a pref observer so we update + // if the pref changes + + if (!gPrefBranch) { + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = prefs->GetBranch(PREF_BRANCH_BASE, &gPrefBranch); + NS_ENSURE_SUCCESS(rv, rv); + } + + gPrefBranch->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS, &mExpireDays); + gPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ONLY_TYPED, &mAutocompleteOnlyTyped); + nsCOMPtr pbi = do_QueryInterface(gPrefBranch); + if (pbi) { + pbi->AddObserver(PREF_AUTOCOMPLETE_ONLY_TYPED, this, PR_FALSE); + pbi->AddObserver(PREF_BROWSER_HISTORY_EXPIRE_DAYS, this, PR_FALSE); + } + + if (gRefCnt++ == 0) { + rv = CallGetService(kRDFServiceCID, &gRDFService); + + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); + if (NS_FAILED(rv)) return rv; + + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Page"), &kNC_Page); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Date"), &kNC_Date); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "FirstVisitDate"), + &kNC_FirstVisitDate); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "VisitCount"), &kNC_VisitCount); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "AgeInDays"), &kNC_AgeInDays); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"), &kNC_Name); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name?sort=true"), &kNC_NameSort); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Hostname"), &kNC_Hostname); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Referrer"), &kNC_Referrer); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"), &kNC_child); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"), &kNC_URL); + gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "DayFolderIndex"), &kNC_DayFolderIndex); + gRDFService->GetResource(NS_LITERAL_CSTRING("NC:HistoryRoot"), &kNC_HistoryRoot); + gRDFService->GetResource(NS_LITERAL_CSTRING("NC:HistoryByDateAndSite"), &kNC_HistoryByDateAndSite); + gRDFService->GetResource(NS_LITERAL_CSTRING("NC:HistoryByDate"), &kNC_HistoryByDate); + } + + // register this as a named data source with the RDF service + rv = gRDFService->RegisterDataSource(this, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + rv = bundleService->CreateBundle("chrome://global/locale/history/history.properties", getter_AddRefs(mBundle)); + } + + // register to observe profile changes + nsCOMPtr observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ASSERTION(observerService, "failed to get observer service"); + if (observerService) { + observerService->AddObserver(this, "profile-before-change", PR_TRUE); + observerService->AddObserver(this, "profile-do-change", PR_TRUE); + observerService->AddObserver(this, "quit-application", PR_TRUE); + } + + return NS_OK; +} + + +nsresult +nsGlobalHistory::OpenDB() +{ + nsresult rv; + + if (mStore) return NS_OK; + + nsCOMPtr historyFile; + rv = NS_GetSpecialDirectory(NS_APP_HISTORY_50_FILE, getter_AddRefs(historyFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr factoryfactory = do_GetService(NS_MORK_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = factoryfactory->GetMdbFactory(&gMdbFactory); + NS_ENSURE_SUCCESS(rv, rv); + + mdb_err err; + + err = gMdbFactory->MakeEnv(nsnull, &mEnv); + mEnv->SetAutoClear(PR_TRUE); + NS_ASSERTION((err == 0), "unable to create mdb env"); + if (err != 0) return NS_ERROR_FAILURE; + + // MDB requires native file paths + + nsCAutoString filePath; + rv = historyFile->GetNativePath(filePath); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool exists = PR_TRUE; + + historyFile->Exists(&exists); + + if (!exists || NS_FAILED(rv = OpenExistingFile(gMdbFactory, filePath.get()))) { + + // we couldn't open the file, so it's either corrupt or doesn't exist. + // attempt to delete the file, but ignore the error + historyFile->Remove(PR_FALSE); + rv = OpenNewFile(gMdbFactory, filePath.get()); + } + + NS_ENSURE_SUCCESS(rv, rv); + + // get the initial filesize. Used in Commit() to determine type of commit. + rv = historyFile->GetFileSize(&mFileSizeOnDisk); + if (NS_FAILED(rv)) { + LL_I2L(mFileSizeOnDisk, 0); + } + + // See if we need to byte-swap. + InitByteOrder(PR_FALSE); + + return NS_OK; +} + +nsresult +nsGlobalHistory::OpenExistingFile(nsIMdbFactory *factory, const char *filePath) +{ + + mdb_err err; + nsresult rv; + mdb_bool canopen = 0; + mdbYarn outfmt = { nsnull, 0, 0, 0, 0, nsnull }; + + nsIMdbHeap* dbHeap = 0; + mdb_bool dbFrozen = mdbBool_kFalse; // not readonly, we want modifiable + nsCOMPtr oldFile; // ensures file is released + err = factory->OpenOldFile(mEnv, dbHeap, filePath, + dbFrozen, getter_AddRefs(oldFile)); + + // don't assert, the file might just not be there + if ((err !=0) || !oldFile) return NS_ERROR_FAILURE; + + err = factory->CanOpenFilePort(mEnv, oldFile, // the file to investigate + &canopen, &outfmt); + + // XXX possible that format out of date, in which case we should + // just re-write the file. + if ((err !=0) || !canopen) return NS_ERROR_FAILURE; + + nsIMdbThumb* thumb = nsnull; + mdbOpenPolicy policy = { { 0, 0 }, 0, 0 }; + + err = factory->OpenFileStore(mEnv, dbHeap, oldFile, &policy, &thumb); + if ((err !=0) || !thumb) return NS_ERROR_FAILURE; + + mdb_count total; + mdb_count current; + mdb_bool done; + mdb_bool broken; + + do { + err = thumb->DoMore(mEnv, &total, ¤t, &done, &broken); + } while ((err == 0) && !broken && !done); + + if ((err == 0) && done) { + err = factory->ThumbToOpenStore(mEnv, thumb, &mStore); + } + + NS_IF_RELEASE(thumb); + + if (err != 0) return NS_ERROR_FAILURE; + + rv = CreateTokens(); + NS_ENSURE_SUCCESS(rv, rv); + + mdbOid oid = { kToken_HistoryRowScope, 1 }; + err = mStore->GetTable(mEnv, &oid, &mTable); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + if (!mTable) { + NS_WARNING("Your history file is somehow corrupt.. deleting it."); + return NS_ERROR_FAILURE; + } + + err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow)); + if (err != 0) + NS_WARNING("Could not get meta row\n"); + + CheckHostnameEntries(); + + if (err != 0) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsGlobalHistory::OpenNewFile(nsIMdbFactory *factory, const char *filePath) +{ + nsresult rv; + mdb_err err; + + nsIMdbHeap* dbHeap = 0; + nsCOMPtr newFile; // ensures file is released + err = factory->CreateNewFile(mEnv, dbHeap, filePath, + getter_AddRefs(newFile)); + + if ((err != 0) || !newFile) return NS_ERROR_FAILURE; + + mdbOpenPolicy policy = { { 0, 0 }, 0, 0 }; + err = factory->CreateNewFileStore(mEnv, dbHeap, newFile, &policy, &mStore); + + if (err != 0) return NS_ERROR_FAILURE; + + rv = CreateTokens(); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the one and only table in the history db + err = mStore->NewTable(mEnv, kToken_HistoryRowScope, kToken_HistoryKind, PR_TRUE, nsnull, &mTable); + if (err != 0) return NS_ERROR_FAILURE; + if (!mTable) return NS_ERROR_FAILURE; + + // Create the meta row. + mdbOid oid = { kToken_HistoryRowScope, 1 }; + err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow)); + if (err != 0) + NS_WARNING("Could not get meta row\n"); + + // Force a commit now to get it written out. + nsCOMPtr thumb; + err = mStore->LargeCommit(mEnv, getter_AddRefs(thumb)); + if (err != 0) return NS_ERROR_FAILURE; + + mdb_count total; + mdb_count current; + mdb_bool done; + mdb_bool broken; + + do { + err = thumb->DoMore(mEnv, &total, ¤t, &done, &broken); + } while ((err == 0) && !broken && !done); + + if ((err != 0) || !done) return NS_ERROR_FAILURE; + + return NS_OK; +} + +// Set the history file byte order if necessary, and determine if +// we need to byte-swap Unicode values. +// If the force argument is true, the file byte order will be set +// to that of this machine. +nsresult +nsGlobalHistory::InitByteOrder(PRBool aForce) +{ +#ifdef IS_LITTLE_ENDIAN + NS_NAMED_LITERAL_CSTRING(machine_byte_order, "LE"); +#endif +#ifdef IS_BIG_ENDIAN + NS_NAMED_LITERAL_CSTRING(machine_byte_order, "BE"); +#endif + nsXPIDLCString file_byte_order; + nsresult rv = NS_OK; + + if (!aForce) + rv = GetByteOrder(getter_Copies(file_byte_order)); + if (aForce || NS_FAILED(rv) || + !(file_byte_order.Equals(NS_LITERAL_CSTRING("BE")) || + file_byte_order.Equals(NS_LITERAL_CSTRING("LE")))) { + // Byte order is not yet set, or needs to be reset; initialize it. + mReverseByteOrder = PR_FALSE; + rv = SaveByteOrder(machine_byte_order.get()); + if (NS_FAILED(rv)) + return rv; + } + else + mReverseByteOrder = !file_byte_order.Equals(machine_byte_order); + + return NS_OK; +} + +// break the uri down into a search query, and pass off to +// SearchEnumerator +nsresult +nsGlobalHistory::CreateFindEnumerator(nsIRDFResource *aSource, + nsISimpleEnumerator **aResult) +{ + nsresult rv; + // make sure this was a find query + if (!IsFindResource(aSource)) + return NS_ERROR_FAILURE; + + const char* uri; + rv = aSource->GetValueConst(&uri); + if (NS_FAILED(rv)) return rv; + + // convert uri to a query + searchQuery* query = new searchQuery; + if (!query) return NS_ERROR_OUT_OF_MEMORY; + FindUrlToSearchQuery(uri, *query); + + // the enumerator will take ownership of the query + SearchEnumerator *result = + new SearchEnumerator(query, kToken_HiddenColumn, this); + if (!result) return NS_ERROR_OUT_OF_MEMORY; + + rv = result->Init(mEnv, mTable); + if (NS_FAILED(rv)) return rv; + + // return the value + *aResult = result; + NS_ADDREF(*aResult); + + return NS_OK; +} + + +// for each row, we need to parse out the hostname from the url +// then store it in a column +nsresult +nsGlobalHistory::CheckHostnameEntries() +{ + nsresult rv = NS_OK; + + mdb_err err; + + nsCOMPtr cursor; + nsCOMPtr row; + + err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(cursor)); + if (err != 0) return NS_ERROR_FAILURE; + + int marker; + err = mTable->StartBatchChangeHint(mEnv, &marker); + NS_ASSERTION(err == 0, "unable to start batch"); + if (err != 0) return NS_ERROR_FAILURE; + + mdb_pos pos; + err = cursor->NextRow(mEnv, getter_AddRefs(row), &pos); + if (err != 0) return NS_ERROR_FAILURE; + + // comment out this code to rebuild the hostlist at startup +#if 1 + // bail early if the first row has a hostname + if (row) { + nsCAutoString hostname; + rv = GetRowValue(row, kToken_HostnameColumn, hostname); + if (NS_SUCCEEDED(rv) && !hostname.IsEmpty()) + return NS_OK; + } +#endif + + // cached variables used in the loop + nsCAutoString url; + nsXPIDLCString hostname; + + nsCOMPtr ioService = do_GetService(NS_IOSERVICE_CONTRACTID); + if (!ioService) return NS_ERROR_FAILURE; + + + while (row) { +#if 0 + rv = GetRowValue(row, kToken_URLColumn, url); + if (NS_FAILED(rv)) break; + + ioService->ExtractUrlPart(url, nsIIOService::url_Host, 0, 0, getter_Copies(hostname)); + + SetRowValue(row, kToken_HostnameColumn, hostname); + +#endif + + // to be turned on when we're confident in mork's ability + // to handle yarn forms properly +#if 0 + nsAutoString title; + rv = GetRowValue(row, kToken_NameColumn, title); + // reencode back into UTF8 + if (NS_SUCCEEDED(rv)) + SetRowValue(row, kToken_NameColumn, title.get()); +#endif + cursor->NextRow(mEnv, getter_AddRefs(row), &pos); + } + + // Finish the batch. + err = mTable->EndBatchChangeHint(mEnv, &marker); + NS_ASSERTION(err == 0, "error ending batch"); + + return rv; +} + +nsresult +nsGlobalHistory::CreateTokens() +{ + mdb_err err; + + NS_PRECONDITION(mStore != nsnull, "not initialized"); + if (! mStore) + return NS_ERROR_NOT_INITIALIZED; + + err = mStore->StringToToken(mEnv, "ns:history:db:row:scope:history:all", &kToken_HistoryRowScope); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "ns:history:db:table:kind:history", &kToken_HistoryKind); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "URL", &kToken_URLColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Referrer", &kToken_ReferrerColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "LastVisitDate", &kToken_LastVisitDateColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "FirstVisitDate", &kToken_FirstVisitDateColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "VisitCount", &kToken_VisitCountColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Name", &kToken_NameColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Hostname", &kToken_HostnameColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Hidden", &kToken_HiddenColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Typed", &kToken_TypedColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "GeckoFlags", &kToken_GeckoFlagsColumn); + if (err != 0) return NS_ERROR_FAILURE; + + // meta-data tokens + err = mStore->StringToToken(mEnv, "LastPageVisited", &kToken_LastPageVisited); + err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder); + + return NS_OK; +} + +nsresult nsGlobalHistory::Commit(eCommitType commitType) +{ + if (!mStore || !mTable) + return NS_OK; + + nsresult err = NS_OK; + nsCOMPtr thumb; + + if (commitType == kLargeCommit || commitType == kSessionCommit) + { + mdb_percent outActualWaste = 0; + mdb_bool outShould; + if (mStore) + { + // check how much space would be saved by doing a compress commit. + // If it's more than 30%, go for it. + // N.B. - I'm not sure this calls works in Mork for all cases. + err = mStore->ShouldCompress(mEnv, 30, &outActualWaste, &outShould); + if (NS_SUCCEEDED(err) && outShould) + { + commitType = kCompressCommit; + } + else + { + mdb_count count; + err = mTable->GetCount(mEnv, &count); + // Since Mork's shouldCompress doesn't work, we need to look + // at the file size and the number of rows, and make a stab + // at guessing if we've got a lot of deleted rows. The file + // size is the size when we opened the db, and we really want + // it to be the size after we've written out the file, + // but I think this is a good enough approximation. + if (count > 0) + { + PRInt64 numRows; + PRInt64 bytesPerRow; + PRInt64 desiredAvgRowSize; + + LL_UI2L(numRows, count); + LL_DIV(bytesPerRow, mFileSizeOnDisk, numRows); + LL_I2L(desiredAvgRowSize, 400); + if (LL_CMP(bytesPerRow, >, desiredAvgRowSize)) + commitType = kCompressCommit; + } + } + } + } + switch (commitType) + { + case kLargeCommit: + err = mStore->LargeCommit(mEnv, getter_AddRefs(thumb)); + break; + case kSessionCommit: + err = mStore->SessionCommit(mEnv, getter_AddRefs(thumb)); + break; + case kCompressCommit: + err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb)); + break; + } + if (err == 0) { + mdb_count total; + mdb_count current; + mdb_bool done; + mdb_bool broken; + + do { + err = thumb->DoMore(mEnv, &total, ¤t, &done, &broken); + } while ((err == 0) && !broken && !done); + } + if (err != 0) // mork doesn't return NS error codes. Yet. + return NS_ERROR_FAILURE; + else + return NS_OK; + +} +// if notify is true, we'll notify rdf of deleted rows. +// If we're shutting down history, then (maybe?) we don't +// need or want to notify rdf. +nsresult nsGlobalHistory::ExpireEntries(PRBool notify) +{ + PRTime expirationDate; + PRInt64 microSecondsPerSecond, secondsInDays, microSecondsInExpireDays; + + LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC); + LL_UI2L(secondsInDays, 60 * 60 * 24 * mExpireDays); + LL_MUL(microSecondsInExpireDays, secondsInDays, microSecondsPerSecond); + LL_SUB(expirationDate, GetNow(), microSecondsInExpireDays); + + matchExpiration_t expiration; + expiration.history = this; + expiration.expirationDate = &expirationDate; + + return RemoveMatchingRows(matchExpirationCallback, (void *)&expiration, notify); +} + +nsresult +nsGlobalHistory::CloseDB() +{ + mdb_err err; + + ExpireEntries(PR_FALSE /* don't notify */); + err = Commit(kSessionCommit); + + // order is important here - logically smallest objects first + mMetaRow = nsnull; + + if (mTable) + mTable->Release(); + + if (mStore) + mStore->Release(); + + if (mEnv) + mEnv->Release(); + + mTable = nsnull; mEnv = nsnull; mStore = nsnull; + + return NS_OK; +} + +nsresult +nsGlobalHistory::FindRow(mdb_column aCol, + const char *aValue, nsIMdbRow **aResult) +{ + if (! mStore) + return NS_ERROR_NOT_INITIALIZED; + + mdb_err err; + PRInt32 len = PL_strlen(aValue); + mdbYarn yarn = { (void*) aValue, len, len, 0, 0, nsnull }; + + mdbOid rowId; + nsCOMPtr row; + if (aResult) { + err = mStore->FindRow(mEnv, kToken_HistoryRowScope, + aCol, &yarn, &rowId, getter_AddRefs(row)); + + if (!row) return NS_ERROR_NOT_AVAILABLE; + } else { + err = mStore->FindRow(mEnv, kToken_HistoryRowScope, + aCol, &yarn, &rowId, nsnull); + } + + // make sure it's actually stored in the main table + mdb_bool hasRow; + mTable->HasOid(mEnv, &rowId, &hasRow); + + if (!hasRow) return NS_ERROR_NOT_AVAILABLE; + + if (aResult) { + *aResult = row; + (*aResult)->AddRef(); + } + + return NS_OK; +} + +PRBool +nsGlobalHistory::IsURLInHistory(nsIRDFResource* aResource) +{ + nsresult rv; + + const char* url; + rv = aResource->GetValueConst(&url); + if (NS_FAILED(rv)) return PR_FALSE; + + rv = FindRow(kToken_URLColumn, url, nsnull); + return (NS_SUCCEEDED(rv)) ? PR_TRUE : PR_FALSE; +} + + +nsresult +nsGlobalHistory::NotifyAssert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue) +{ + PRUint32 i = mObservers.Count(); + while (i > 0) { + mObservers[--i]->OnAssert(this, aSource, aProperty, aValue); + } + + return NS_OK; +} + + +nsresult +nsGlobalHistory::NotifyUnassert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue) +{ + PRUint32 i = mObservers.Count(); + while (i > 0) { + mObservers[--i]->OnUnassert(this, aSource, aProperty, aValue); + } + + return NS_OK; +} + + + +nsresult +nsGlobalHistory::NotifyChange(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldValue, + nsIRDFNode* aNewValue) +{ + PRUint32 i = mObservers.Count(); + while (i > 0) { + mObservers[--i]->OnChange(this, aSource, aProperty, aOldValue, aNewValue); + } + + return NS_OK; +} + +// +// this just generates a static list of find-style queries +// only returns queries that currently have matches in global history +// +nsresult +nsGlobalHistory::GetRootDayQueries(nsISimpleEnumerator **aResult, PRBool aBySite) +{ + nsresult rv; + nsCOMPtr dayArray; + NS_NewISupportsArray(getter_AddRefs(dayArray)); + + PRInt32 i; + nsCOMPtr finduri; + nsDependentCString + prefix(FIND_BY_AGEINDAYS_PREFIX "is" "&text="); + nsCAutoString uri; + nsCOMPtr findEnumerator; + PRBool hasMore = PR_FALSE; + for (i=0; i<7; i++) { + uri = prefix; + uri.AppendInt(i); + if (aBySite) + uri.Append("&groupby=Hostname"); + rv = gRDFService->GetResource(uri, getter_AddRefs(finduri)); + if (NS_FAILED(rv)) continue; + rv = CreateFindEnumerator(finduri, getter_AddRefs(findEnumerator)); + if (NS_FAILED(rv)) continue; + rv = findEnumerator->HasMoreElements(&hasMore); + if (NS_SUCCEEDED(rv) && hasMore) + dayArray->AppendElement(finduri); + } + + uri = FIND_BY_AGEINDAYS_PREFIX "isgreater" "&text="; + uri.AppendInt(i-1); + if (aBySite) + uri.Append("&groupby=Hostname"); + rv = gRDFService->GetResource(uri, getter_AddRefs(finduri)); + if (NS_SUCCEEDED(rv)) { + rv = CreateFindEnumerator(finduri, getter_AddRefs(findEnumerator)); + if (NS_SUCCEEDED(rv)) { + rv = findEnumerator->HasMoreElements(&hasMore); + if (NS_SUCCEEDED(rv) && hasMore) + dayArray->AppendElement(finduri); + } + } + + return NS_NewArrayEnumerator(aResult, dayArray); +} + +// +// convert the name/value pairs stored in a string into an array of +// these pairs +// find:a=b&c=d&e=f&g=h +// becomes an array containing +// {"a" = "b", "c" = "d", "e" = "f", "g" = "h" } +// +nsresult +nsGlobalHistory::FindUrlToTokenList(const char *aURL, nsVoidArray& aResult) +{ + if (PL_strncmp(aURL, "find:", 5) != 0) + return NS_ERROR_UNEXPECTED; + + const char *curpos = aURL + 5; + const char *tokenstart = curpos; + + // this is where we will store the current name and value + const char *tokenName = nsnull; + const char *tokenValue = nsnull; + PRUint32 tokenNameLength=0; + PRUint32 tokenValueLength=0; + + PRBool haveValue = PR_FALSE; // needed because some values are 0-length + while (PR_TRUE) { + while (*curpos && (*curpos != '&') && (*curpos != '=')) + curpos++; + + if (*curpos == '=') { // just found a token name + tokenName = tokenstart; + tokenNameLength = (curpos - tokenstart); + } + else if ((!*curpos || *curpos == '&') && + (tokenNameLength>0)) { // found a value, and we have a + // name + tokenValue = tokenstart; + tokenValueLength = (curpos - tokenstart); + haveValue = PR_TRUE; + } + + // once we have a name/value pair, store it away + // note we're looking at lengths, so that + // "find:&a=b" doesn't connect with a="" + if (tokenNameLength>0 && haveValue) { + + tokenPair *tokenStruct = new tokenPair(tokenName, tokenNameLength, + tokenValue, tokenValueLength); + if (tokenStruct) + aResult.AppendElement((void *)tokenStruct); + + // reset our state + tokenName = tokenValue = nsnull; + tokenNameLength = tokenValueLength = 0; + haveValue = PR_FALSE; + } + + // the test has to be here to catch empty values + if (!*curpos) break; + + curpos++; + tokenstart = curpos; + } + + return NS_OK; +} + +void +nsGlobalHistory::FreeTokenList(nsVoidArray& tokens) +{ + PRUint32 length = tokens.Count(); + PRUint32 i; + for (i=0; iGetValueConst(&value); + if (NS_FAILED(rv)) return PR_FALSE; + + return (PL_strncmp(value, "find:", 5)==0); +} + +// +// convert a list of name/value pairs into a search query with 0 or +// more terms and an optional groupby +// +// a term consists of the values of the 4 name/value pairs +// {datasource, match, method, text} +// groupby is stored as a column # +// +nsresult +nsGlobalHistory::TokenListToSearchQuery(const nsVoidArray& aTokens, + searchQuery& aResult) +{ + + PRInt32 i; + PRInt32 length = aTokens.Count(); + + aResult.groupBy = 0; + const char *datasource=nsnull, *property=nsnull, + *method=nsnull, *text=nsnull; + + PRUint32 datasourceLen=0, propertyLen=0, methodLen=0, textLen=0; + rowMatchCallback matchCallback=nsnull; // matching callback if needed + + for (i=0; itokenName, token->tokenName + token->tokenNameLength); + if (tokenName.EqualsLiteral("datasource")) { + datasource = token->tokenValue; + datasourceLen = token->tokenValueLength; + } + else if (tokenName.EqualsLiteral("match")) { + if (Substring(token->tokenValue, token->tokenValue+token->tokenValueLength).Equals("AgeInDays")) + matchCallback = matchAgeInDaysCallback; + + property = token->tokenValue; + propertyLen = token->tokenValueLength; + } + else if (tokenName.EqualsLiteral("method")) { + method = token->tokenValue; + methodLen = token->tokenValueLength; + } + else if (tokenName.EqualsLiteral("text")) { + text = token->tokenValue; + textLen = token->tokenValueLength; + } + + // really, we should be storing the group-by as a column number or + // rdf resource + else if (tokenName.EqualsLiteral("groupby")) { + mdb_err err; + err = mStore->QueryToken(mEnv, + nsCAutoString(token->tokenValue).get(), + &aResult.groupBy); + if (err != 0) + aResult.groupBy = 0; + } + + // once we complete a term, we move on to the next one + if (datasource && property && method && text) { + searchTerm *currentTerm = new searchTerm(datasource, datasourceLen, + property, propertyLen, + method, methodLen, + text, textLen); + currentTerm->match = matchCallback; + + // append the old one, then create a new one + aResult.terms.AppendElement((void *)currentTerm); + + // reset our state + matchCallback=nsnull; + currentTerm = nsnull; + datasource = property = method = text = 0; + } + } + + return NS_OK; +} + +nsresult +nsGlobalHistory::FindUrlToSearchQuery(const char *aUrl, searchQuery& aResult) +{ + + nsresult rv; + // convert uri to list of tokens + nsVoidArray tokenPairs; + rv = FindUrlToTokenList(aUrl, tokenPairs); + if (NS_FAILED(rv)) return rv; + + // now convert the tokens to a query + rv = TokenListToSearchQuery(tokenPairs, aResult); + + FreeTokenList(tokenPairs); + + return rv; +} + +// preemptively construct some common find-queries so that we show up +// asychronously when a search is open + +// we have to do the following assertions: +// (a=AgeInDays, h=hostname; g=groupby, -> = #child) +// 1) NC:HistoryRoot -> uri +// +// 2) NC:HistoryByDate -> a&g=h +// 3) a&g=h -> a&h +// 4) a&h -> uri +// +// 5) g=h -> h +// 6) h->uri +nsresult +nsGlobalHistory::NotifyFindAssertions(nsIRDFResource *aSource, + nsIMdbRow *aRow) +{ + // we'll construct a bunch of sample queries, and then do + // appropriate assertions + + // first pull out the appropriate values + PRTime lastVisited; + GetRowValue(aRow, kToken_LastVisitDateColumn, &lastVisited); + + PRInt32 ageInDays = GetAgeInDays(NormalizeTime(GetNow()), lastVisited); + nsCAutoString ageString; ageString.AppendInt(ageInDays); + + nsCAutoString hostname; + GetRowValue(aRow, kToken_HostnameColumn, hostname); + + // construct some terms that we'll use later + + // Hostname= + searchTerm hostterm("history", sizeof("history")-1, + "Hostname", sizeof("Hostname")-1, + "is", sizeof("is")-1, + hostname.get(), hostname.Length()); + + // AgeInDays= + searchTerm ageterm("history", sizeof("history") -1, + "AgeInDays", sizeof("AgeInDays")-1, + "is", sizeof("is")-1, + ageString.get(), ageString.Length()); + + searchQuery query; + nsCAutoString findUri; + nsCOMPtr childFindResource; + nsCOMPtr parentFindResource; + + // 2) NC:HistoryByDate -> AgeInDays=&groupby=Hostname + query.groupBy = kToken_HostnameColumn; + query.terms.AppendElement((void *)&ageterm); + + GetFindUriPrefix(query, PR_TRUE, findUri); + gRDFService->GetResource(findUri, getter_AddRefs(childFindResource)); + NotifyAssert(kNC_HistoryByDateAndSite, kNC_child, childFindResource); + parentFindResource = childFindResource; + + query.terms.Clear(); + + query.groupBy = 0; + query.terms.AppendElement((void *)&ageterm); + + GetFindUriPrefix(query, PR_TRUE, findUri); + gRDFService->GetResource(findUri, getter_AddRefs(childFindResource)); + NotifyAssert(kNC_HistoryByDate, kNC_child, childFindResource); + + query.terms.Clear(); + + + query.groupBy = 0; + query.terms.AppendElement((void *)&ageterm); + + GetFindUriPrefix(query, PR_TRUE, findUri); + gRDFService->GetResource(findUri, getter_AddRefs(childFindResource)); + NotifyAssert(childFindResource, kNC_child, aSource); + + query.terms.Clear(); + + // 3) AgeInDays=&groupby=Hostname -> + // AgeInDays=&Hostname= + + + query.groupBy = 0; // create AgeInDays=&Hostname= + query.terms.AppendElement((void *)&ageterm); + query.terms.AppendElement((void *)&hostterm); + + GetFindUriPrefix(query, PR_FALSE, findUri); + gRDFService->GetResource(findUri, getter_AddRefs(childFindResource)); + NotifyAssert(parentFindResource, kNC_child, childFindResource); + + query.terms.Clear(); + + // 4) AgeInDays=&Hostname= -> uri + parentFindResource = childFindResource; // AgeInDays=&hostname= + NotifyAssert(childFindResource, kNC_child, aSource); + + // 5) groupby=Hostname -> Hostname= + query.groupBy = kToken_HostnameColumn; // create groupby=Hostname + + GetFindUriPrefix(query, PR_TRUE, findUri); + gRDFService->GetResource(findUri, getter_AddRefs(parentFindResource)); + + query.groupBy = 0; // create Hostname= + query.terms.AppendElement((void *)&hostterm); + GetFindUriPrefix(query, PR_FALSE, findUri); + findUri.Append(hostname); // append + gRDFService->GetResource(findUri, getter_AddRefs(childFindResource)); + + NotifyAssert(parentFindResource, kNC_child, childFindResource); + + // 6) Hostname= -> uri + parentFindResource = childFindResource; // Hostname= + NotifyAssert(parentFindResource, kNC_child, aSource); + + return NS_OK; +} + + +// simpler than NotifyFindAssertions - basically just notifies +// unassertions from +// 1) NC:HistoryRoot -> uri +// 2) a&h -> uri +// 3) h -> uri + +nsresult +nsGlobalHistory::NotifyFindUnassertions(nsIRDFResource *aSource, + nsIMdbRow* aRow) +{ + // 1) NC:HistoryRoot + NotifyUnassert(kNC_HistoryRoot, kNC_child, aSource); + + // first get age in days + PRTime lastVisited; + GetRowValue(aRow, kToken_LastVisitDateColumn, &lastVisited); + PRInt32 ageInDays = GetAgeInDays(NormalizeTime(GetNow()), lastVisited); + nsCAutoString ageString; ageString.AppendInt(ageInDays); + + // now get hostname + nsCAutoString hostname; + GetRowValue(aRow, kToken_HostnameColumn, hostname); + + // construct some terms + // Hostname= + searchTerm hostterm("history", sizeof("history")-1, + "Hostname", sizeof("Hostname")-1, + "is", sizeof("is")-1, + hostname.get(), hostname.Length()); + + // AgeInDays= + searchTerm ageterm("history", sizeof("history") -1, + "AgeInDays", sizeof("AgeInDays")-1, + "is", sizeof("is")-1, + ageString.get(), ageString.Length()); + + searchQuery query; + query.groupBy = 0; + + nsCAutoString findUri; + nsCOMPtr findResource; + + // 2) AgeInDays=&Hostname= + query.terms.AppendElement((void *)&ageterm); + query.terms.AppendElement((void *)&hostterm); + GetFindUriPrefix(query, PR_FALSE, findUri); + + gRDFService->GetResource(findUri, getter_AddRefs(findResource)); + + NotifyUnassert(findResource, kNC_child, aSource); + + // 3) Hostname= + query.terms.Clear(); + + query.terms.AppendElement((void *)&hostterm); + GetFindUriPrefix(query, PR_FALSE, findUri); + + gRDFService->GetResource(findUri, getter_AddRefs(findResource)); + NotifyUnassert(findResource, kNC_child, aSource); + + query.terms.Clear(); + + return NS_OK; +} + +// +// get the user-visible "name" of a find resource +// we basically parse the string, and use the data stored in the last +// term to generate an appropriate string +// +nsresult +nsGlobalHistory::GetFindUriName(const char *aURL, nsIRDFNode **aResult) +{ + + nsresult rv; + + searchQuery query; + rv = FindUrlToSearchQuery(aURL, query); + + // can't exactly get a name if there's nothing to search for + if (query.terms.Count() < 1) + return NS_OK; + + // now build up a string from the query (using only the last term) + searchTerm *term = (searchTerm*)query.terms[query.terms.Count()-1]; + + // automatically build up string in the form + // findurl--[-] + // such as "finduri-AgeInDays-is" or "find-uri-AgeInDays-is-0" + nsAutoString stringName(NS_LITERAL_STRING("finduri-")); + + // property + stringName.Append(NS_ConvertASCIItoUTF16(term->property)); + stringName.Append(PRUnichar('-')); + + // and now the method, such as "is" or "isgreater" + stringName.Append(NS_ConvertASCIItoUTF16(term->method)); + + // try adding - to see if there's a match + // for example, to match + // finduri-LastVisitDate-is-0=Today + PRInt32 preTextLength = stringName.Length(); + stringName.Append(PRUnichar('-')); + stringName.Append(term->text); + stringName.Append(PRUnichar(0)); + + // try to find a localizable string + const PRUnichar *strings[] = { + term->text.get() + }; + nsXPIDLString value; + + // first with the search text + rv = mBundle->FormatStringFromName(stringName.get(), + strings, 1, getter_Copies(value)); + + // ok, try it without the -, to match + // finduri-LastVisitDate-is=%S days ago + if (NS_FAILED(rv)) { + stringName.Truncate(preTextLength); + rv = mBundle->FormatStringFromName(stringName.get(), + strings, 1, getter_Copies(value)); + } + + nsCOMPtr literal; + if (NS_SUCCEEDED(rv)) { + rv = gRDFService->GetLiteral(value, getter_AddRefs(literal)); + } else { + // ok, no such string, so just put the match text itself there + rv = gRDFService->GetLiteral(term->text.get(), + getter_AddRefs(literal)); + } + FreeSearchQuery(query); + + if (NS_FAILED(rv)) return rv; + + *aResult = literal; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::Observe(nsISupports *aSubject, + const char *aTopic, + const PRUnichar *aSomeData) +{ + nsresult rv; + // pref changing - update member vars + if (!nsCRT::strcmp(aTopic, "nsPref:changed")) { + NS_ENSURE_STATE(gPrefBranch); + + // expiration date + if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING(PREF_BROWSER_HISTORY_EXPIRE_DAYS).get())) { + gPrefBranch->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS, &mExpireDays); + } + else if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING(PREF_AUTOCOMPLETE_ONLY_TYPED).get())) { + gPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ONLY_TYPED, &mAutocompleteOnlyTyped); + } + } + else if (!nsCRT::strcmp(aTopic, "profile-before-change")) { + rv = CloseDB(); + if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING("shutdown-cleanse").get())) { + nsCOMPtr historyFile; + rv = NS_GetSpecialDirectory(NS_APP_HISTORY_50_FILE, getter_AddRefs(historyFile)); + if (NS_SUCCEEDED(rv)) + rv = historyFile->Remove(PR_FALSE); + } + } + else if (!nsCRT::strcmp(aTopic, "profile-do-change")) + rv = OpenDB(); + else if (!nsCRT::strcmp(aTopic, "quit-application")) + rv = Flush(); + + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// nsGlobalHistory::URLEnumerator +// +// Implementation + +nsGlobalHistory::URLEnumerator::~URLEnumerator() +{ + nsMemory::Free(mSelectValue); +} + + +PRBool +nsGlobalHistory::URLEnumerator::IsResult(nsIMdbRow* aRow) +{ + if (HasCell(mEnv, aRow, mHiddenColumn)) + return PR_FALSE; + + if (mSelectColumn) { + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, mURLColumn, &yarn); + if (err != 0) return PR_FALSE; + + // Do bitwise comparison + PRInt32 count = PRInt32(yarn.mYarn_Fill); + if (count != mSelectValueLen) + return PR_FALSE; + + const char* p = (const char*) yarn.mYarn_Buf; + const char* q = (const char*) mSelectValue; + + while (--count >= 0) { + if (*p++ != *q++) + return PR_FALSE; + } + } + + return PR_TRUE; +} + +nsresult +nsGlobalHistory::URLEnumerator::ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, mURLColumn, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + // Since the URLEnumerator always returns the value of the URL + // column, we create an RDF resource. + nsresult rv; + nsCOMPtr resource; + const char* startPtr = (const char*) yarn.mYarn_Buf; + rv = gRDFService->GetResource( + Substring(startPtr, startPtr+yarn.mYarn_Fill), + getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + *aResult = resource; + NS_ADDREF(*aResult); + return NS_OK; +} + +//---------------------------------------------------------------------- +// nsGlobalHistory::SearchEnumerator +// +// Implementation + +nsGlobalHistory::SearchEnumerator::~SearchEnumerator() +{ + nsGlobalHistory::FreeSearchQuery(*mQuery); + delete mQuery; +} + + +// convert the query in mQuery into a find URI +// if there is a groupby= in the query, then convert that +// into the start of another search term +// for example, in the following query with one term: +// +// term[0] = { history, AgeInDays, is, 0 } +// groupby = Hostname +// +// we generate the following uri: +// +// find:datasource=history&match=AgeInDays&method=is&text=0&datasource=history +// &match=Hostname&method=is&text= +// +// and then the caller will append some text after after the "text=" +// +void +nsGlobalHistory::GetFindUriPrefix(const searchQuery& aQuery, + const PRBool aDoGroupBy, + nsACString& aResult) +{ + mdb_err err; + + aResult.Assign("find:"); + PRUint32 length = aQuery.terms.Count(); + PRUint32 i; + + for (i=0; i" + if (aDoGroupBy) { + aResult.Append("&groupby="); + if (err == 0) + aResult.Append((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill); + } + + // put &datasource=history&match=&method=is&text= + else { + // if the query has a groupby= then we want to append that + // field as the last field to match.. caller has to be sure to + // append that! + aResult.Append("&datasource=history"); + + aResult.Append("&match="); + if (err == 0) + aResult.Append((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill); + // herep + aResult.Append("&method=is"); + aResult.Append("&text="); + } + +} + +// +// determines if the given row matches all terms +// +// if there is a "groupBy" column, then we have to remember that we've +// seen a row with the given value in that column, and then make sure +// all future rows with that value in that column DON'T match, no +// matter if they match the terms or not. +PRBool +nsGlobalHistory::SearchEnumerator::IsResult(nsIMdbRow *aRow) +{ + if (HasCell(mEnv, aRow, mHiddenColumn)) + return PR_FALSE; + + mdb_err err; + + mdbYarn groupColumnValue = { nsnull, 0, 0, 0, 0, nsnull}; + if (mQuery->groupBy!=0) { + + // if we have a 'groupby', then we use the hashtable to make sure + // we only match the FIRST row with the column value that we're + // grouping by + + err = aRow->AliasCellYarn(mEnv, mQuery->groupBy, &groupColumnValue); + if (err!=0) return PR_FALSE; + if (!groupColumnValue.mYarn_Buf) return PR_FALSE; + + const char* startPtr = (const char*)groupColumnValue.mYarn_Buf; + nsCStringKey key(Substring(startPtr, + startPtr + groupColumnValue.mYarn_Fill)); + + void *otherRow = mUniqueRows.Get(&key); + + // Hey! we've seen this row before, so ignore it + if (otherRow) return PR_FALSE; + } + + // now do the actual match + if (!mHistory->RowMatches(aRow, mQuery, PR_FALSE)) + return PR_FALSE; + + if (mQuery->groupBy != 0) { + // we got this far, so we must have matched. + // add ourselves to the hashtable so we don't match rows like this + // in the future + const char* startPtr = (const char*)groupColumnValue.mYarn_Buf; + nsCStringKey key(Substring(startPtr, + startPtr + groupColumnValue.mYarn_Fill)); + + // note - weak ref, don't worry about releasing + mUniqueRows.Put(&key, (void *)aRow); + } + + return PR_TRUE; +} + +// +// determines if the row matches the given terms, used above +// +PRBool +nsGlobalHistory::RowMatches(nsIMdbRow *aRow, + searchQuery *aQuery, + PRBool caseSensitive) +{ + PRUint32 length = aQuery->terms.Count(); + PRUint32 i; + + for (i=0; iterms[i]; + + if (!term->datasource.Equals("history")) + continue; // we only match against history queries + + // use callback if it exists + if (term->match) { + // queue up some values just in case callback needs it + // (how would we do this dynamically?) + matchSearchTerm_t matchSearchTerm = { mEnv, mStore, term , PR_FALSE}; + + if (!term->match(aRow, (void *)&matchSearchTerm)) + return PR_FALSE; + } else { + mdb_err err; + + mdb_column property_column; + nsCAutoString property_name(term->property); + property_name.Append(char(0)); + + err = mStore->QueryToken(mEnv, property_name.get(), &property_column); + if (err != 0) { + NS_WARNING("Unrecognized column!"); + continue; // assume we match??? + } + + // match the term directly against the column? + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, property_column, &yarn); + if (err != 0 || !yarn.mYarn_Buf) return PR_FALSE; + + nsAutoString rowVal; + + PRInt32 yarnLength = yarn.mYarn_Fill;; + if (property_column == kToken_NameColumn) { + // The name column (page title) is stored as UTF-16. + rowVal.Assign((const PRUnichar*)yarn.mYarn_Buf, yarnLength / 2); + } + else { + // Other columns are stored as UTF-8 and can be null. + if (yarn.mYarn_Buf) + rowVal = NS_ConvertUTF8toUTF16((const char*)yarn.mYarn_Buf, yarnLength); + } + + // set up some iterators + nsString::const_iterator start, end; + rowVal.BeginReading(start); + rowVal.EndReading(end); + + const nsXPIDLString& searchText = term->text; + + if (term->method.Equals("is")) { + if (caseSensitive) { + if (!searchText.Equals(rowVal, nsDefaultStringComparator())) + return PR_FALSE; + } + else { + if (!searchText.Equals(rowVal, nsCaseInsensitiveStringComparator())) + return PR_FALSE; + } + } + + else if (term->method.Equals("isnot")) { + if (caseSensitive) { + if (searchText.Equals(rowVal, nsDefaultStringComparator())) + return PR_FALSE; + } + else { + if (searchText.Equals(rowVal, nsCaseInsensitiveStringComparator())) + return PR_FALSE; + } + } + + else if (term->method.Equals("contains")) { + if (caseSensitive) { + if (!FindInReadable(searchText, start, end, nsDefaultStringComparator())) + return PR_FALSE; + } + else { + if (!FindInReadable(searchText, start, end, nsCaseInsensitiveStringComparator())) + return PR_FALSE; + } + } + + else if (term->method.Equals("doesntcontain")) { + if (caseSensitive) { + if (FindInReadable(searchText, start, end, nsDefaultStringComparator())) + return PR_FALSE; + } + else { + if (FindInReadable(searchText, start, end, nsCaseInsensitiveStringComparator())) + return PR_FALSE; + } + } + + else if (term->method.Equals("startswith")) { + // need to make sure that the found string is + // at the beginning of the string + nsAString::const_iterator real_start = start; + if (caseSensitive) { + if (!(FindInReadable(searchText, start, end, nsDefaultStringComparator()) && real_start == start)) + return PR_FALSE; + } + else { + if (!(FindInReadable(searchText, start, end, nsCaseInsensitiveStringComparator()) && + real_start == start)) + return PR_FALSE; + } + } + + else if (term->method.Equals("endswith")) { + // need to make sure that the found string ends + // at the end of the string + nsAString::const_iterator real_end = end; + if (caseSensitive) { + if (!(RFindInReadable(searchText, start, end, nsDefaultStringComparator()) && real_end == end)) + return PR_FALSE; + } + else { + if (!(RFindInReadable(searchText, start, end, nsCaseInsensitiveStringComparator()) && + real_end == end)) + return PR_FALSE; + } + } + + else { + NS_WARNING("Unrecognized search method in SearchEnumerator::RowMatches"); + // don't handle other match types like isgreater/etc yet, + // so assume the match failed and bail + return PR_FALSE; + } + + } + } + + // we've gone through each term and didn't bail, so they must have + // all matched! + return PR_TRUE; +} + +// +// return either the row, or another find resource. +// if we're doing grouping, then we don't want to return a real row, +// instead we want to expand the current query into a deeper query +// where we match up the groupby attribute. +// if we're not doing grouping, then we just return the URL for the +// current row +nsresult +nsGlobalHistory::SearchEnumerator::ConvertToISupports(nsIMdbRow* aRow, + nsISupports** aResult) + +{ + mdb_err err; + nsresult rv; + + nsCOMPtr resource; + if (mQuery->groupBy == 0) { + // no column to group by + // just create a resource based on the URL of the current row + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, mHistory->kToken_URLColumn, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + + const char* startPtr = (const char*)yarn.mYarn_Buf; + rv = gRDFService->GetResource( + Substring(startPtr, startPtr+yarn.mYarn_Fill), + getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + *aResult = resource; + NS_ADDREF(*aResult); + return NS_OK; + } + + // we have a group by, so now we recreate the find url, but add a + // query for the row asked for by groupby + mdbYarn groupByValue; + err = aRow->AliasCellYarn(mEnv, mQuery->groupBy, &groupByValue); + if (err != 0) return NS_ERROR_FAILURE; + + if (mFindUriPrefix.IsEmpty()) + mHistory->GetFindUriPrefix(*mQuery, PR_FALSE, mFindUriPrefix); + + nsCAutoString findUri(mFindUriPrefix); + + const char* startPtr = (const char *)groupByValue.mYarn_Buf; + findUri.Append(Substring(startPtr, startPtr+groupByValue.mYarn_Fill)); + findUri.Append('\0'); + + rv = gRDFService->GetResource(findUri, getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + *aResult = resource; + NS_ADDREF(*aResult); + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// nsIAutoCompleteSession implementation +// + +NS_IMETHODIMP +nsGlobalHistory::StartSearch(const nsAString &aSearchString, + const nsAString &aSearchParam, + nsIAutoCompleteResult *aPreviousResult, + nsIAutoCompleteObserver *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + NS_ENSURE_STATE(gPrefBranch); + + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + nsCOMPtr result; + if (aSearchString.IsEmpty()) { + AutoCompleteTypedSearch(getter_AddRefs(result)); + } else { + // if the search string is empty after it has had prefixes removed, then + // we need to ignore the previous result set + nsAutoString cut(aSearchString); + AutoCompleteCutPrefix(cut, nsnull); + if (cut.Length() == 0) + aPreviousResult = nsnull; + + // pass string through filter and then determine which prefixes to exclude + // when chopping prefixes off of history urls during comparison + nsString filtered = AutoCompletePrefilter(aSearchString); + AutocompleteExclude exclude; + AutoCompleteGetExcludeInfo(filtered, &exclude); + + // perform the actual search here + nsresult rv = AutoCompleteSearch(filtered, &exclude, + static_cast + (aPreviousResult), + getter_AddRefs(result)); + NS_ENSURE_SUCCESS(rv, rv); + } + + aListener->OnSearchResult(this, result); + + return NS_OK; +} + + +NS_IMETHODIMP +nsGlobalHistory::StopSearch() +{ + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// AutoComplete stuff +// + +nsresult +nsGlobalHistory::AutoCompleteTypedSearch(nsIAutoCompleteMdbResult2 **aResult) +{ + mdb_count count; + mdb_err err = mTable->GetCount(mEnv, &count); + + // Get a cursor to iterate through all rows in the database + nsCOMPtr rowCursor; + err = mTable->GetTableRowCursor(mEnv, count, getter_AddRefs(rowCursor)); + NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE); + + nsresult rv; + nsCOMPtr result = do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + result->Init(mEnv, mTable); + result->SetTokens(kToken_URLColumn, nsIAutoCompleteMdbResult2::kCharType, kToken_NameColumn, nsIAutoCompleteMdbResult2::kUnicharType); + result->SetReverseByteOrder(mReverseByteOrder); + + nsCOMPtr row; + mdb_pos pos; + do { + rowCursor->PrevRow(mEnv, getter_AddRefs(row), &pos); + if (!row) break; + + if (HasCell(mEnv, row, kToken_TypedColumn)) { + result->AddRow(row); + } + } while (row); + + // Determine the result of the search + PRUint32 matchCount; + rv = result->GetMatchCount(&matchCount); + if (matchCount > 0) { + result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS); + result->SetDefaultIndex(0); + } else { + result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH); + result->SetDefaultIndex(-1); + } + + *aResult = result; + NS_ADDREF(*aResult); + + return NS_OK; +} + +nsresult +nsGlobalHistory::AutoCompleteSearch(const nsAString &aSearchString, + AutocompleteExclude *aExclude, + nsIAutoCompleteMdbResult2 *aPrevResult, + nsIAutoCompleteMdbResult2 **aResult) +{ + // determine if we can skip searching the whole history and only search + // through the previous search results + PRBool searchPrevious = PR_FALSE; + if (aPrevResult) { + nsAutoString prevURLStr; + aPrevResult->GetSearchString(prevURLStr); + // if search string begins with the previous search string, it's a go + searchPrevious = Substring(aSearchString, 0, prevURLStr.Length()).Equals(prevURLStr); + } + + if (searchPrevious) { + // Search through the previous result + PRUint32 matchCount; + aPrevResult->GetMatchCount(&matchCount); + for (PRInt32 i = matchCount-1; i >= 0; --i) { + // Make a copy of the value because AutoCompleteCompare is destructive + nsAutoString url; + aPrevResult->GetValueAt(i, url); + + if (!AutoCompleteCompare(url, aSearchString, aExclude)) + aPrevResult->RemoveValueAt(i, PR_FALSE); + } + + NS_ADDREF(*aResult = aPrevResult); + } else { + // Search through the entire history + + // Create and initialize a new result object + nsresult rv = NS_OK; + nsCOMPtr result = do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + result->Init(mEnv, mTable); + result->SetTokens(kToken_URLColumn, nsIAutoCompleteMdbResult2::kCharType, kToken_NameColumn, nsIAutoCompleteMdbResult2::kUnicharType); + result->SetReverseByteOrder(mReverseByteOrder); + result->SetSearchString(aSearchString); + + // Get a cursor to iterate through all rows in the database + nsCOMPtr rowCursor; + mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor)); + NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE); + + // Store hits in an nsIArray initially + nsCOMArray resultArray; + + nsCOMPtr row; + mdb_pos pos; + do { + rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos); + if (!row) break; + + if (!HasCell(mEnv, row, kToken_TypedColumn)) + if (mAutocompleteOnlyTyped || HasCell(mEnv, row, kToken_HiddenColumn)) + continue; + + nsCAutoString url; + GetRowValue(row, kToken_URLColumn, url); + + NS_ConvertUTF8toUTF16 utf8Url(url); + if (AutoCompleteCompare(utf8Url, aSearchString, aExclude)) + resultArray.AppendObject(row); + } while (row); + + // Setup the structure we pass into the sort function, + // including a set of url prefixes to ignore. These prefixes + // must match with the logic in nsGlobalHistory::nsGlobalHistory(). + NS_NAMED_LITERAL_STRING(prefixHWStr, "http://www."); + NS_NAMED_LITERAL_STRING(prefixHStr, "http://"); + NS_NAMED_LITERAL_STRING(prefixHSWStr, "https://www."); + NS_NAMED_LITERAL_STRING(prefixHSStr, "https://"); + NS_NAMED_LITERAL_STRING(prefixFFStr, "ftp://ftp."); + NS_NAMED_LITERAL_STRING(prefixFStr, "ftp://"); + + // note: the number of prefixes stored in the closure below + // must match with the constant AUTOCOMPLETE_PREFIX_LIST_COUNT + AutoCompleteSortClosure closure; + closure.history = this; + closure.prefixCount = AUTOCOMPLETE_PREFIX_LIST_COUNT; + closure.prefixes[0] = &prefixHWStr; + closure.prefixes[1] = &prefixHStr; + closure.prefixes[2] = &prefixHSWStr; + closure.prefixes[3] = &prefixHSStr; + closure.prefixes[4] = &prefixFFStr; + closure.prefixes[5] = &prefixFStr; + + // sort it + resultArray.Sort(AutoCompleteSortComparison, static_cast(&closure)); + + // place the sorted array into the autocomplete results + PRUint32 count = resultArray.Count(); + PRUint32 i; + for (i = 0; i < count; ++i) { + result->AddRow(resultArray[i]); + } + + // Determine the result of the search + PRUint32 matchCount; + rv = result->GetMatchCount(&matchCount); + if (matchCount > 0) { + result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS); + result->SetDefaultIndex(0); + } else { + result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH); + result->SetDefaultIndex(-1); + } + + *aResult = result; + NS_ADDREF(*aResult); + } + + return NS_OK; +} + +// If aURL begins with a protocol or domain prefix from our lists, +// then mark their index in an AutocompleteExclude struct. +void +nsGlobalHistory::AutoCompleteGetExcludeInfo(const nsAString& aURL, AutocompleteExclude* aExclude) +{ + aExclude->schemePrefix = -1; + aExclude->hostnamePrefix = -1; + + PRInt32 index = 0; + PRInt32 i; + for (i = 0; i < mIgnoreSchemes.Count(); ++i) { + nsString* string = mIgnoreSchemes.StringAt(i); + if (Substring(aURL, 0, string->Length()).Equals(*string)) { + aExclude->schemePrefix = i; + index = string->Length(); + break; + } + } + + for (i = 0; i < mIgnoreHostnames.Count(); ++i) { + nsString* string = mIgnoreHostnames.StringAt(i); + if (Substring(aURL, index, string->Length()).Equals(*string)) { + aExclude->hostnamePrefix = i; + break; + } + } +} + +// Cut any protocol and domain prefixes from aURL, except for those which +// are specified in aExclude +void +nsGlobalHistory::AutoCompleteCutPrefix(nsAString& aURL, AutocompleteExclude* aExclude) +{ + // This comparison is case-sensitive. Therefore, it assumes that aUserURL is a + // potential URL whose host name is in all lower case. + PRInt32 idx = 0; + PRInt32 i; + for (i = 0; i < mIgnoreSchemes.Count(); ++i) { + if (aExclude && i == aExclude->schemePrefix) + continue; + nsString* string = mIgnoreSchemes.StringAt(i); + if (Substring(aURL, 0, string->Length()).Equals(*string)) { + idx = string->Length(); + break; + } + } + + if (idx > 0) + aURL.Cut(0, idx); + + idx = 0; + for (i = 0; i < mIgnoreHostnames.Count(); ++i) { + if (aExclude && i == aExclude->hostnamePrefix) + continue; + nsString* string = mIgnoreHostnames.StringAt(i); + if (Substring(aURL, 0, string->Length()).Equals(*string)) { + idx = string->Length(); + break; + } + } + + if (idx > 0) + aURL.Cut(0, idx); +} + +nsString +nsGlobalHistory::AutoCompletePrefilter(const nsAString& aSearchString) +{ + nsAutoString url(aSearchString); + + PRInt32 slash = url.FindChar('/', 0); + if (slash >= 0) { + // if user is typing a url but has already typed past the host, + // then convert the host to lowercase + nsAutoString host; + url.Left(host, slash); + ToLowerCase(host); + url.Assign(host + Substring(url, slash, url.Length()-slash)); + } else { + // otherwise, assume the user could still be typing the host, and + // convert everything to lowercase + ToLowerCase(url); + } + + return nsString(url); +} + +PRBool +nsGlobalHistory::AutoCompleteCompare(nsAString& aHistoryURL, + const nsAString& aUserURL, + AutocompleteExclude* aExclude) +{ + AutoCompleteCutPrefix(aHistoryURL, aExclude); + + return Substring(aHistoryURL, 0, aUserURL.Length()).Equals(aUserURL); +} + +int PR_CALLBACK +nsGlobalHistory::AutoCompleteSortComparison(nsIMdbRow *row1, nsIMdbRow *row2, + void *closureVoid) +{ + // + // NOTE: The design and reasoning behind the following autocomplete + // sort implementation is documented in bug 78270. + // + // cast our function parameters back into their real form + AutoCompleteSortClosure* closure = + static_cast(closureVoid); + + // get visit counts - we're ignoring all errors from GetRowValue(), + // and relying on default values + PRInt32 item1visits = 0, item2visits = 0; + closure->history->GetRowValue(row1, + closure->history->kToken_VisitCountColumn, + &item1visits); + closure->history->GetRowValue(row2, + closure->history->kToken_VisitCountColumn, + &item2visits); + + // get URLs + nsAutoString url1, url2; + closure->history->GetRowValue(row1, closure->history->kToken_URLColumn, url1); + closure->history->GetRowValue(row2, closure->history->kToken_URLColumn, url2); + + // Favour websites and webpaths more than webpages by boosting + // their visit counts. This assumes that URLs have been normalized, + // appending a trailing '/'. + // + // We use addition to boost the visit count rather than multiplication + // since we want URLs with large visit counts to remain pretty much + // in raw visit count order - we assume the user has visited these urls + // often for a reason and there shouldn't be a problem with putting them + // high in the autocomplete list regardless of whether they are sites/ + // paths or pages. However for URLs visited only a few times, sites + // & paths should be presented before pages since they are generally + // more likely to be visited again. + // + PRBool isPath1 = PR_FALSE, isPath2 = PR_FALSE; + if (!url1.IsEmpty()) + { + // url is a site/path if it has a trailing slash + isPath1 = (url1.Last() == PRUnichar('/')); + if (isPath1) + item1visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST; + } + if (!url2.IsEmpty()) + { + // url is a site/path if it has a trailing slash + isPath2 = (url2.Last() == PRUnichar('/')); + if (isPath2) + item2visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST; + } + + if (HasCell(closure->history->mEnv, row1, closure->history->kToken_TypedColumn)) + item1visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST; + if (HasCell(closure->history->mEnv, row2, closure->history->kToken_TypedColumn)) + item2visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST; + + // primary sort by visit count + if (item1visits != item2visits) + { + // return visit count comparison + return item2visits - item1visits; + } + else + { + // Favour websites and webpaths more than webpages + if (isPath1 && !isPath2) return -1; // url1 is a website/path, url2 isn't + if (!isPath1 && isPath2) return 1; // url1 isn't a website/path, url2 is + + // We have two websites/paths.. ignore "http[s]://[www.]" & "ftp://[ftp.]" + // prefixes. Find a starting position in the string, just past any of the + // above prefixes. Only check for the prefix once, in the far left of the + // string - it is assumed there is no whitespace. + PRInt32 postPrefix1 = 0, postPrefix2 = 0; + + size_t i; + // iterate through our prefixes looking for a match + for (i=0; iprefixCount; i++) + { + // Check if string is prefixed. Note: the parameters of the Find() + // method specify the url is searched at the 0th character and if there + // is no match the rest of the url is not searched. + if (url1.Find((*closure->prefixes[i]), 0, 1) == 0) + { + // found a match - record post prefix position + postPrefix1 = closure->prefixes[i]->Length(); + // bail out of the for loop + break; + } + } + + // iterate through our prefixes looking for a match + for (i=0; iprefixCount; i++) + { + // Check if string is prefixed. Note: the parameters of the Find() + // method specify the url is searched at the 0th character and if there + // is no match the rest of the url is not searched. + if (url2.Find((*closure->prefixes[i]), 0, 1) == 0) + { + // found a match - record post prefix position + postPrefix2 = closure->prefixes[i]->Length(); + // bail out of the for loop + break; + } + } + + // compare non-prefixed urls + PRInt32 ret = Compare( + Substring(url1, postPrefix1, url1.Length()), + Substring(url2, postPrefix2, url2.Length())); + if (ret != 0) return ret; + + // sort http://xyz.com before http://www.xyz.com + return postPrefix1 - postPrefix2; + } + return 0; +} + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGlobalHistory, Init) + +static const nsModuleComponentInfo components[] = +{ + { "Global History", + NS_GLOBALHISTORY_CID, + NS_GLOBALHISTORY2_CONTRACTID, + nsGlobalHistoryConstructor }, + + { "Global History", + NS_GLOBALHISTORY_CID, + NS_GLOBALHISTORY_DATASOURCE_CONTRACTID, + nsGlobalHistoryConstructor }, + + { "Global History", + NS_GLOBALHISTORY_CID, + NS_GLOBALHISTORY_AUTOCOMPLETE_CONTRACTID, + nsGlobalHistoryConstructor } +}; + +NS_IMPL_NSGETMODULE(nsToolkitHistory, components) diff --git a/mozilla/kmeleon/history/src/nsGlobalHistory.h b/mozilla/kmeleon/history/src/nsGlobalHistory.h new file mode 100644 index 00000000..49edad28 --- /dev/null +++ b/mozilla/kmeleon/history/src/nsGlobalHistory.h @@ -0,0 +1,455 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 Communicator client code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Waterson + * Blake Ross + * Joe Hewitt + * + * 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 ***** */ + +#ifndef nsglobalhistory__h____ +#define nsglobalhistory__h____ + +#include "nsIBrowserHistory.h" +#include "nsIGlobalHistory3.h" +#include "mdb.h" +#include "nsIObserver.h" +#include "nsIPrefBranch.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFRemoteDataSource.h" +#include "nsIRDFService.h" +#include "nsISupportsArray.h" +#include "nsIStringBundle.h" +#include "nsWeakReference.h" +#include "nsVoidArray.h" +#include "nsHashtable.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsITimer.h" +#include "nsIAutoCompleteSearch.h" +#include "nsIAutoCompleteResult.h" +#include "nsIAutoCompleteResultTypes.h" +#include "nsHashSets.h" +#include "nsCOMArray.h" +#include "nsCycleCollectionParticipant.h" + +//---------------------------------------------------------------------- +// +// nsMdbTableEnumerator +// +// An nsISimpleEnumerator implementation that returns the value of +// a column as an nsISupports. Allows for some simple selection. +// + +class nsMdbTableEnumerator : public nsISimpleEnumerator +{ +protected: + nsMdbTableEnumerator(); + virtual ~nsMdbTableEnumerator(); + + nsIMdbEnv* mEnv; + +private: + // subclasses should not tweak these + nsIMdbTable* mTable; + nsIMdbTableRowCursor* mCursor; + nsIMdbRow* mCurrent; + +public: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsISimpleEnumeratorMethods + NS_IMETHOD HasMoreElements(PRBool* _result); + NS_IMETHOD GetNext(nsISupports** _result); + + // Implementation methods + virtual nsresult Init(nsIMdbEnv* aEnv, nsIMdbTable* aTable); + +protected: + virtual PRBool IsResult(nsIMdbRow* aRow) = 0; + virtual nsresult ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult) = 0; +}; + +typedef PRBool (*rowMatchCallback)(nsIMdbRow *aRow, void *closure); + +struct matchHost_t; +struct searchQuery; +class searchTerm; + +// Number of prefixes used in the autocomplete sort comparison function +#define AUTOCOMPLETE_PREFIX_LIST_COUNT 6 +// Size of visit count boost to give to urls which are sites or paths +#define AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST 5 + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// This class is the browser's implementation of the +// nsIGlobalHistory interface. +// + + +// Used to describe what prefixes shouldn't be cut from +// history urls when doing an autocomplete url comparison. +struct AutocompleteExclude { + PRInt32 schemePrefix; + PRInt32 hostnamePrefix; +}; + +class nsGlobalHistory : nsSupportsWeakReference, + public nsIBrowserHistory, + public nsIObserver, + public nsIRDFDataSource, + public nsIRDFRemoteDataSource, + public nsIAutoCompleteSearch, + public nsIGlobalHistory3 +{ +public: + // nsISupports methods + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsGlobalHistory, + nsIBrowserHistory) + + NS_DECL_NSIGLOBALHISTORY2 + NS_DECL_NSIGLOBALHISTORY3 + NS_DECL_NSIBROWSERHISTORY + NS_DECL_NSIOBSERVER + NS_DECL_NSIRDFDATASOURCE + NS_DECL_NSIRDFREMOTEDATASOURCE + NS_DECL_NSIAUTOCOMPLETESEARCH + + NS_METHOD Init(); + + nsGlobalHistory(void); + virtual ~nsGlobalHistory(); + + // these must be public so that the callbacks can call them + PRBool MatchExpiration(nsIMdbRow *row, PRTime* expirationDate); + PRBool MatchHost(nsIMdbRow *row, matchHost_t *hostInfo); + PRBool RowMatches(nsIMdbRow* aRow, searchQuery *aQuery, PRBool caseSensitive); + +protected: + + // + // database junk + // + enum eCommitType + { + kLargeCommit = 0, + kSessionCommit = 1, + kCompressCommit = 2 + }; + + PRInt64 mFileSizeOnDisk; + nsresult OpenDB(); + nsresult OpenExistingFile(nsIMdbFactory *factory, const char *filePath); + nsresult OpenNewFile(nsIMdbFactory *factory, const char *filePath); + nsresult CreateTokens(); + nsresult CloseDB(); + nsresult CheckHostnameEntries(); + nsresult Commit(eCommitType commitType); + + // + // expiration/removal stuff + // + PRInt32 mExpireDays; + nsresult ExpireEntries(PRBool notify); + nsresult RemoveMatchingRows(rowMatchCallback aMatchFunc, + void *aClosure, PRBool notify); + + // + // search stuff - find URL stuff, etc + // + nsresult GetRootDayQueries(nsISimpleEnumerator **aResult, PRBool aBySite); + nsresult GetFindUriName(const char *aURL, nsIRDFNode **aResult); + nsresult CreateFindEnumerator(nsIRDFResource *aSource, + nsISimpleEnumerator **aResult); + + static nsresult FindUrlToTokenList(const char *aURL, nsVoidArray& aResult); + static void FreeTokenList(nsVoidArray& tokens); + static void FreeSearchQuery(searchQuery& aQuery); + static PRBool IsFindResource(nsIRDFResource *aResource); + void GetFindUriPrefix(const searchQuery& aQuery, + const PRBool aDoGroupBy, + nsACString& aResult); + + nsresult TokenListToSearchQuery(const nsVoidArray& tokens, + searchQuery& aResult); + nsresult FindUrlToSearchQuery(const char *aURL, searchQuery& aResult); + nsresult NotifyFindAssertions(nsIRDFResource *aSource, nsIMdbRow *aRow); + nsresult NotifyFindUnassertions(nsIRDFResource *aSource, nsIMdbRow *aRow); + + // + // autocomplete stuff + // + PRBool mAutocompleteOnlyTyped; + nsStringArray mIgnoreSchemes; + nsStringArray mIgnoreHostnames; + + nsresult AutoCompleteTypedSearch(nsIAutoCompleteMdbResult2 **aResult); + nsresult AutoCompleteSearch(const nsAString& aSearchString, + AutocompleteExclude* aExclude, + nsIAutoCompleteMdbResult2* aPrevResult, + nsIAutoCompleteMdbResult2** aResult); + void AutoCompleteCutPrefix(nsAString& aURL, AutocompleteExclude* aExclude); + void AutoCompleteGetExcludeInfo(const nsAString& aURL, AutocompleteExclude* aExclude); + nsString AutoCompletePrefilter(const nsAString& aSearchString); + PRBool AutoCompleteCompare(nsAString& aHistoryURL, + const nsAString& aUserURL, + AutocompleteExclude* aExclude); + PR_STATIC_CALLBACK(int) + AutoCompleteSortComparison(nsIMdbRow *row1, nsIMdbRow *row2, void *closureVoid); + + // AutoCompleteSortClosure - used to pass info into + // AutoCompleteSortComparison from the NS_QuickSort() function + struct AutoCompleteSortClosure + { + nsGlobalHistory* history; + size_t prefixCount; + const nsAFlatString* prefixes[AUTOCOMPLETE_PREFIX_LIST_COUNT]; + }; + + // caching of PR_Now() so we don't call it every time we do + // a history query + PRTime mLastNow; // cache the last PR_Now() + PRInt32 mBatchesInProgress; + PRBool mNowValid; // is mLastNow valid? + nsCOMPtr mExpireNowTimer; + + PRTime GetNow(); + void ExpireNow(); + + static void expireNowTimer(nsITimer *aTimer, void *aClosure) + {((nsGlobalHistory *)aClosure)->ExpireNow(); } + + // + // sync stuff to write the db to disk every so often + // + PRBool mDirty; // if we've changed history + nsCOMPtr mSyncTimer; + + void Sync(); + nsresult SetDirty(); + + static void fireSyncTimer(nsITimer *aTimer, void *aClosure) + {((nsGlobalHistory *)aClosure)->Sync(); } + + // + // RDF stuff + // + nsCOMArray mObservers; + + PRBool IsURLInHistory(nsIRDFResource* aResource); + + nsresult NotifyAssert(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aValue); + nsresult NotifyUnassert(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aValue); + nsresult NotifyChange(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aOldValue, nsIRDFNode* aNewValue); + + // + // row-oriented stuff + // + + // N.B., these are MDB interfaces, _not_ XPCOM interfaces. + nsIMdbEnv* mEnv; // OWNER + nsIMdbStore* mStore; // OWNER + nsIMdbTable* mTable; // OWNER + + nsCOMPtr mMetaRow; + + mdb_scope kToken_HistoryRowScope; + mdb_kind kToken_HistoryKind; + + mdb_column kToken_URLColumn; + mdb_column kToken_ReferrerColumn; + mdb_column kToken_LastVisitDateColumn; + mdb_column kToken_FirstVisitDateColumn; + mdb_column kToken_VisitCountColumn; + mdb_column kToken_NameColumn; + mdb_column kToken_HostnameColumn; + mdb_column kToken_HiddenColumn; + mdb_column kToken_TypedColumn; + mdb_column kToken_GeckoFlagsColumn; + + mdb_column kToken_ByteOrder; + // meta-data tokens + mdb_column kToken_LastPageVisited; + + // A set of the page URI specs that have been typed but not yet loaded + nsCStringHashSet mTypedHiddenURIs; + + // + // AddPage-oriented stuff + // + nsresult AddPageToDatabase(nsIURI* aURI, PRBool aRedirect, PRBool aTopLevel, + PRTime aLastVisitDate, nsIURI * aReferrer); + nsresult AddExistingPageToDatabase(nsIMdbRow *row, + PRTime aDate, + nsIURI *aReferrer, + PRTime *aOldDate, + PRInt32 *aOldCount); + nsresult AddNewPageToDatabase(nsIURI* aURI, + PRTime aDate, + PRBool aRedirect, + PRBool aTopLevel, + nsIURI *aReferrer, + nsIMdbRow **aResult); + + nsresult RemovePageInternal(const char *aSpec); + + // + // generic routines for setting/retrieving various datatypes + // + nsresult SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRTime& aValue); + nsresult SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRInt32 aValue); + nsresult SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const char *aValue); + nsresult SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRUnichar *aValue); + + nsresult GetRowValue(nsIMdbRow *aRow, mdb_column aCol, nsAString& aResult); + // byte order + // + nsresult SaveByteOrder(const char *aByteOrder); + nsresult GetByteOrder(char **_retval); + nsresult InitByteOrder(PRBool aForce); + void SwapBytes(const PRUnichar *source, PRUnichar *dest, PRInt32 aLen); + PRBool mReverseByteOrder; + + // + nsresult GetRowValue(nsIMdbRow *aRow, mdb_column aCol, nsACString& aResult); + nsresult GetRowValue(nsIMdbRow *aRow, mdb_column aCol, PRTime* aResult); + nsresult GetRowValue(nsIMdbRow *aRow, mdb_column aCol, PRInt32* aResult); + + // Look up a row in mStore and returns success if it is found or failure + // if it is not. |aResult| may be null if only testing for row existance. + nsresult FindRow(mdb_column aCol, const char *aURL, nsIMdbRow **aResult); + + // + // misc unrelated stuff + // + nsCOMPtr mBundle; + + // pseudo-constants. although the global history really is a + // singleton, we'll use this metaphor to be consistent. + static PRInt32 gRefCnt; + static nsIRDFService* gRDFService; + static nsIRDFResource* kNC_Page; // XXX do we need? + static nsIRDFResource* kNC_Date; + static nsIRDFResource* kNC_FirstVisitDate; + static nsIRDFResource* kNC_VisitCount; + static nsIRDFResource* kNC_AgeInDays; + static nsIRDFResource* kNC_Name; + static nsIRDFResource* kNC_NameSort; + static nsIRDFResource* kNC_Hostname; + static nsIRDFResource* kNC_Referrer; + static nsIRDFResource* kNC_child; + static nsIRDFResource* kNC_URL; // XXX do we need? + static nsIRDFResource* kNC_HistoryRoot; + static nsIRDFResource* kNC_HistoryByDate; + static nsIRDFResource* kNC_HistoryByDateAndSite; + static nsIRDFResource* kNC_DayFolderIndex; + + static nsIMdbFactory* gMdbFactory; + static nsIPrefBranch* gPrefBranch; + // + // custom enumerators + // + + // URLEnumerator - for searching for a specific set of rows which + // match a particular column + class URLEnumerator : public nsMdbTableEnumerator + { + protected: + mdb_column mURLColumn; + mdb_column mHiddenColumn; + mdb_column mSelectColumn; + void* mSelectValue; + PRInt32 mSelectValueLen; + + virtual ~URLEnumerator(); + + public: + URLEnumerator(mdb_column aURLColumn, + mdb_column aHiddenColumn, + mdb_column aSelectColumn = mdb_column(0), + void* aSelectValue = nsnull, + PRInt32 aSelectValueLen = 0) : + mURLColumn(aURLColumn), + mHiddenColumn(aHiddenColumn), + mSelectColumn(aSelectColumn), + mSelectValue(aSelectValue), + mSelectValueLen(aSelectValueLen) + {} + + protected: + virtual PRBool IsResult(nsIMdbRow* aRow); + virtual nsresult ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult); + }; + + // SearchEnumerator - for matching a set of rows based on a search query + class SearchEnumerator : public nsMdbTableEnumerator + { + public: + SearchEnumerator(searchQuery *aQuery, + mdb_column aHiddenColumn, + nsGlobalHistory *aHistory) : + mQuery(aQuery), + mHiddenColumn(aHiddenColumn), + mHistory(aHistory) + {} + + virtual ~SearchEnumerator(); + + protected: + searchQuery *mQuery; + mdb_column mHiddenColumn; + nsGlobalHistory *mHistory; + nsHashtable mUniqueRows; + + nsCString mFindUriPrefix; + + virtual PRBool IsResult(nsIMdbRow* aRow); + virtual nsresult ConvertToISupports(nsIMdbRow* aRow, + nsISupports** aResult); + + PRBool RowMatches(nsIMdbRow* aRow, searchQuery *aQuery); + }; + + + friend class URLEnumerator; + friend class SearchEnumerator; +}; + + +#endif // nsglobalhistory__h____ diff --git a/mozilla/kmeleon/history/src/nsHistoryLoadListener.h b/mozilla/kmeleon/history/src/nsHistoryLoadListener.h new file mode 100644 index 00000000..ab33636d --- /dev/null +++ b/mozilla/kmeleon/history/src/nsHistoryLoadListener.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** 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 Communicator client code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 "nsIWebProgressListener.h" +#include "nsIBrowserHistory.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsWeakReference.h" +#include "nsIGenericFactory.h" + +class nsHistoryLoadListener : public nsIWebProgressListener, + public nsSupportsWeakReference +{ + public: + nsHistoryLoadListener(nsIBrowserHistory *); + virtual ~nsHistoryLoadListener(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESSLISTENER + + protected: + nsCOMPtr mHistory; + +}; + diff --git a/mozilla/kmeleon/makefiles.sh b/mozilla/kmeleon/makefiles.sh new file mode 100644 index 00000000..fd034de3 --- /dev/null +++ b/mozilla/kmeleon/makefiles.sh @@ -0,0 +1,46 @@ +#! /bin/sh +# ***** 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 the the Mozilla build system +# +# The Initial Developer of the Original Code is +# Ben Turner +# +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +add_makefiles " +kmeleon/Makefile +kmeleon/wallet/Makefile +kmeleon/winstripe/Makefile +kmeleon/flashblock/Makefile +kmeleon/history/Makefile +kmeleon/typeaheadfind/Makefile +" diff --git a/mozilla/kmeleon/typeaheadfind/Makefile.in b/mozilla/kmeleon/typeaheadfind/Makefile.in new file mode 100644 index 00000000..aa86015b --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/Makefile.in @@ -0,0 +1,51 @@ +# ***** 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 +# Aaron Leventhal. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = suitetypeaheadfind +DIRS = public src + +ifndef MOZ_XUL_APP +DIRS += resources +endif + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/kmeleon/typeaheadfind/makefiles.sh b/mozilla/kmeleon/typeaheadfind/makefiles.sh new file mode 100644 index 00000000..69327396 --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/makefiles.sh @@ -0,0 +1,44 @@ +#! /bin/sh +# ***** 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 Build System +# +# The Initial Developer of the Original Code is +# Ben Turner +# +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +add_makefiles " + extensions/typeaheadfind/public/Makefile + extensions/typeaheadfind/resources/Makefile + extensions/typeaheadfind/src/Makefile + extensions/typeaheadfind/Makefile +" diff --git a/mozilla/kmeleon/typeaheadfind/public/Makefile.in b/mozilla/kmeleon/typeaheadfind/public/Makefile.in new file mode 100644 index 00000000..88159e0c --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/public/Makefile.in @@ -0,0 +1,50 @@ +# ***** 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 +# Aaron Leventhal. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE=suitetypeaheadfind +XPIDL_MODULE=suitetypeaheadfind +GRE_MODULE = 1 + +XPIDLSRCS = nsITypeAheadFind.idl + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/kmeleon/typeaheadfind/public/nsITypeAheadFind.idl b/mozilla/kmeleon/typeaheadfind/public/nsITypeAheadFind.idl new file mode 100644 index 00000000..11cffefc --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/public/nsITypeAheadFind.idl @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Original Author: Aaron Leventhal (aaronl@netscape.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * 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" +#include "domstubs.idl" +#include "nsISupportsPrimitives.idl" + +%{ C++ + #include "nsIDOMEvent.h" + + #define NS_TYPEAHEADFIND_CID \ + {0x46590685, 0xbc00, 0x4aac, {0xab, 0xed, 0x2c, 0x10, 0xa5, 0xb9, 0x45, 0xa4}} + + #define NS_TYPEAHEADFIND_CONTRACTID "@mozilla.org/suitetypeaheadfind;1" +%} + +interface nsIDOMEvent; + +[scriptable, uuid(AD1C62CC-72F4-4c5b-BE78-503854F9E0D8)] +interface nsISuiteTypeAheadFind : nsISupports +{ + /** Is type ahead find mode currently on? */ + readonly attribute boolean isActive; + + /** Manually start type ahead find mode */ + void startNewFind(in nsIDOMWindow aWindow, in boolean aLinksOnly); + + /** Manually cancel type ahead find mode */ + void cancelFind(); + + /** + * Will find as you type start automatically if the user + * types with the focus on page content other than a textfield or select? + * If autostart is off, the startNewFind() method can be used to enact + * type ahead find, as well as cmd_findTypeLinks or cmd_findTypeText. + */ + void setAutoStart(in nsIDOMWindow aWindow, in boolean aIsAutoStartOn); + boolean getAutoStart(in nsIDOMWindow aWindow); + + /** + * Find next recurrence if typeaheadfind was the last used find, + * as opposed to regular find. Returns false in nsISupportsPRBool if we + * don't handle the request. + */ + void findNext(in boolean aReverse, in nsISupportsInterfacePointer aCallerWindowSupports); + + /* + * Go back and remove one character from find string + * Returns true if backspace used + */ + boolean backOneChar(); +}; + diff --git a/mozilla/kmeleon/typeaheadfind/resources/Makefile.in b/mozilla/kmeleon/typeaheadfind/resources/Makefile.in new file mode 100644 index 00000000..ab74943a --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/resources/Makefile.in @@ -0,0 +1,44 @@ +# ***** 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 +# Aaron Leventhal. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/kmeleon/typeaheadfind/resources/content/notfound.wav b/mozilla/kmeleon/typeaheadfind/resources/content/notfound.wav new file mode 100644 index 00000000..c6fd5cb8 Binary files /dev/null and b/mozilla/kmeleon/typeaheadfind/resources/content/notfound.wav differ diff --git a/mozilla/kmeleon/typeaheadfind/resources/content/prefs/typeaheadfind.js b/mozilla/kmeleon/typeaheadfind/resources/content/prefs/typeaheadfind.js new file mode 100644 index 00000000..f7d92dbd --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/resources/content/prefs/typeaheadfind.js @@ -0,0 +1,41 @@ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Aaron Leventhal (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 ***** */ + +pref("accessibility.typeaheadfind", true); +pref("accessibility.typeaheadfind.linksonly", true); +pref("accessibility.typeaheadfind.startlinksonly", false); +pref("accessibility.typeaheadfind.timeout", 5000); diff --git a/mozilla/kmeleon/typeaheadfind/resources/jar.mn b/mozilla/kmeleon/typeaheadfind/resources/jar.mn new file mode 100644 index 00000000..4d084177 --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/resources/jar.mn @@ -0,0 +1,2 @@ +toolkit.jar: + content/global/notfound.wav (content/notfound.wav) diff --git a/mozilla/kmeleon/typeaheadfind/src/Makefile.in b/mozilla/kmeleon/typeaheadfind/src/Makefile.in new file mode 100644 index 00000000..2356e71d --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/src/Makefile.in @@ -0,0 +1,99 @@ +# ***** 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 +# Aaron Leventhal. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = suitetypeaheadfind +LIBRARY_NAME = suitetypeaheadfind +ifneq ($(OS_ARCH),WINNT) +SHORT_LIBNAME = typahead +endif +GRE_MODULE = 1 +MOZILLA_INTERNAL_API = 1 + +PACKAGE_FILE = typeaheadfind.pkg + +REQUIRES = appcomps \ + embedcomponents \ + content \ + docshell \ + dom \ + find \ + intl \ + gfx \ + thebes \ + layout \ + locale \ + necko \ + pref \ + string \ + unicharutil \ + uriloader \ + view \ + webshell \ + widget \ + webbrwsr \ + windowwatcher \ + xpcom \ + xuldoc \ + $(NULL) + +EXPORT_LIBRARY = 1 +IS_COMPONENT = 1 +MODULE_NAME = nsTypeAheadFind + +CPPSRCS = \ + nsTypeAheadFind.cpp \ + nsTypeAheadFindRegistration.cpp \ + $(NULL) + +EXTRA_DSO_LIBS = \ + gkgfx \ + $(NULL) + +EXTRA_DSO_LDOPTS += \ + $(LIBS_DIR) \ + $(MOZ_UNICHARUTIL_LIBS) \ + $(EXTRA_DSO_LIBS) \ + $(MOZ_COMPONENT_LIBS) + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFind.cpp b/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFind.cpp new file mode 100644 index 00000000..7c213948 --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFind.cpp @@ -0,0 +1,2993 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Original Author: Aaron Leventhal (aaronl@netscape.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * 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 "nsCOMPtr.h" +#include "nsMemory.h" +#include "nsIServiceManager.h" +#include "nsIGenericFactory.h" +#include "nsIWebBrowserChrome.h" +#include "nsCURILoader.h" +#include "nsNetUtil.h" +#include "nsIURL.h" +#include "nsIURI.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIEditorDocShell.h" +#include "nsISimpleEnumerator.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMNSUIEvent.h" +#include "nsIDOMNSEvent.h" +#include "nsIPrefBranch.h" +#include "nsIPrefBranch2.h" +#include "nsIPrefService.h" +#include "nsString.h" +#include "nsCRT.h" + +#include "nsIDOMNode.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsFrameTraversal.h" +#include "nsIDOMDocument.h" +#include "nsIDOMXULDocument.h" +#include "nsIImageDocument.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMNSHTMLDocument.h" +#include "nsIDOMHTMLElement.h" +#include "nsIEventStateManager.h" +#include "nsIViewManager.h" +#include "nsIScrollableView.h" +#include "nsIDocument.h" +#include "nsISelection.h" +#include "nsISelectionPrivate.h" +#include "nsISelectElement.h" +#include "nsILink.h" +#include "nsTextFragment.h" +#include "nsILookAndFeel.h" + +#include "nsIDOMKeyEvent.h" +#include "nsIDocShellTreeItem.h" +#include "nsIWebNavigation.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsContentCID.h" +#include "nsLayoutCID.h" +#include "nsWidgetsCID.h" +#include "nsIFormControl.h" +#include "nsINameSpaceManager.h" +#include "nsIWindowWatcher.h" +#include "nsIObserverService.h" + +#include "nsIPrivateTextEvent.h" +#include "nsIPrivateCompositionEvent.h" +#include "nsGUIEvent.h" +#include "nsPIDOMEventTarget.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOM3EventTarget.h" +#include "nsIDOMEventGroup.h" +#include "nsPresShellIterator.h" + +// Header for this class +#include "nsTypeAheadFind.h" + +//////////////////////////////////////////////////////////////////////// + + +NS_INTERFACE_MAP_BEGIN(nsTypeAheadFind) + NS_INTERFACE_MAP_ENTRY(nsISuiteTypeAheadFind) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsIScrollPositionListener) + NS_INTERFACE_MAP_ENTRY(nsISelectionListener) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMTextListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMCompositionListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMKeyListener) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsTypeAheadFind) +NS_IMPL_RELEASE(nsTypeAheadFind) + +static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID); +static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); +static NS_DEFINE_CID(kLookAndFeelCID, NS_LOOKANDFEEL_CID); + +#define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1" + +nsTypeAheadFind* nsTypeAheadFind::sInstance = nsnull; +PRInt32 nsTypeAheadFind::sAccelKey = -1; // magic value of -1 when unitialized + + +nsTypeAheadFind::nsTypeAheadFind(): + mIsFindAllowedInWindow(PR_FALSE), mAutoStartPref(PR_FALSE), + mLinksOnlyPref(PR_FALSE), mStartLinksOnlyPref(PR_FALSE), + mLinksOnly(PR_FALSE), mIsTypeAheadOn(PR_FALSE), mCaretBrowsingOn(PR_FALSE), + mLiteralTextSearchOnly(PR_FALSE), mDontTryExactMatch(PR_FALSE), + mAllTheSameChar(PR_TRUE), + mLinksOnlyManuallySet(PR_FALSE), mIsFindingText(PR_FALSE), + mIsMenuBarActive(PR_FALSE), mIsMenuPopupActive(PR_FALSE), + mIsFirstVisiblePreferred(PR_FALSE), mIsIMETypeAheadActive(PR_FALSE), + mIsBackspaceProtectOn(PR_FALSE), + mBadKeysSinceMatch(0), mLastBadChar(0), + mRepeatingMode(eRepeatingNone), mTimeoutLength(0), + mSoundInterface(nsnull), mIsSoundInitialized(PR_FALSE) +{ +#ifdef DEBUG + // There should only ever be one instance of us + static PRInt32 gInstanceCount; + ++gInstanceCount; + NS_ASSERTION(gInstanceCount == 1, + "There should be only 1 instance of nsTypeAheadFind!"); +#endif +} + + +nsTypeAheadFind::~nsTypeAheadFind() +{ + RemoveDocListeners(); + mTimer = nsnull; + + nsCOMPtr prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefInternal) { + prefInternal->RemoveObserver("accessibility.typeaheadfind", this); + prefInternal->RemoveObserver("accessibility.browsewithcaret", this); + } +} + +nsresult +nsTypeAheadFind::Init() +{ + nsresult rv = NS_NewISupportsArray(getter_AddRefs(mManualFindWindows)); + NS_ENSURE_SUCCESS(rv, rv); + + + nsCOMPtr prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID)); + mSearchRange = do_CreateInstance(kRangeCID); + mStartPointRange = do_CreateInstance(kRangeCID); + mEndPointRange = do_CreateInstance(kRangeCID); + mFind = do_CreateInstance(NS_FIND_CONTRACTID); + if (!prefInternal || !mSearchRange || !mStartPointRange || + !mEndPointRange || !mFind) { + return NS_ERROR_FAILURE; + } + + // ----------- Listen to prefs ------------------ + rv = prefInternal->AddObserver("accessibility.typeaheadfind", this, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + rv = prefInternal->AddObserver("accessibility.browsewithcaret", this, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + + // ----------- Get accel key -------------------- + rv = prefInternal->GetIntPref("ui.key.accelKey", &sAccelKey); + NS_ENSURE_SUCCESS(rv, rv); + + // ----------- Get initial preferences ---------- + PrefsReset(); + + // ----------- Set search options --------------- + mFind->SetCaseSensitive(PR_FALSE); + mFind->SetWordBreaker(nsnull); + + return rv; +} + +nsTypeAheadFind * +nsTypeAheadFind::GetInstance() +{ + if (!sInstance) { + sInstance = new nsTypeAheadFind(); + if (!sInstance) + return nsnull; + + NS_ADDREF(sInstance); // addref for sInstance global + + if (NS_FAILED(sInstance->Init())) { + NS_RELEASE(sInstance); + + return nsnull; + } + } + + NS_ADDREF(sInstance); // addref for the getter + + return sInstance; +} + + +void +nsTypeAheadFind::ReleaseInstance() +{ + NS_IF_RELEASE(sInstance); +} + + +void +nsTypeAheadFind::Shutdown() +{ + RemoveDocListeners(); + + // Application shutdown + mTimer = nsnull; + + nsCOMPtr windowWatcher = + do_GetService(NS_WINDOWWATCHER_CONTRACTID); + if (windowWatcher) { + windowWatcher->UnregisterNotification(this); + } + + // Clear strong refs. It's important to release these so that we don't hold + // on to objects that depends on other modules. + // E.g. gfxFonts in the nsThebesGfxModule, see bug 398084 and bug 414559. + mSoundInterface = nsnull; + mStartFindRange = nsnull; + mSearchRange = nsnull; + mStartPointRange = nsnull; + mEndPointRange = nsnull; + mFind = nsnull; + mFindService = nsnull; + mStringBundle = nsnull; + mTimer = nsnull; + mFocusController = nsnull; + mFocusedDocSelection = nsnull; + mFocusedDocSelCon = nsnull; + mFocusedWindow = nsnull; + mFocusedWeakShell = nsnull; + mManualFindWindows->Clear(); +} + + +// ------- Pref Callbacks (2) --------------- + +nsresult +nsTypeAheadFind::PrefsReset() +{ + nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(prefBranch, NS_ERROR_FAILURE); + + PRBool wasTypeAheadOn = mIsTypeAheadOn; + + prefBranch->GetBoolPref("accessibility.typeaheadfind", &mIsTypeAheadOn); + + if (mIsTypeAheadOn != wasTypeAheadOn) { + if (!mIsTypeAheadOn) { + CancelFind(); + } + else if (!mStringBundle) { + // Get ready to watch windows + nsresult rv; + nsCOMPtr windowWatcher = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + windowWatcher->RegisterNotification(this); + + // Initialize string bundle + nsCOMPtr stringBundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + + if (stringBundleService) + stringBundleService->CreateBundle(TYPEAHEADFIND_BUNDLE_URL, + getter_AddRefs(mStringBundle)); + + // Observe find again commands. We'll handle them if we were the last find + nsCOMPtr observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + observerService->AddObserver(this, "nsWebBrowserFind_FindAgain", + PR_TRUE); + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + PR_TRUE); + } + } + + PRBool oldAutoStartPref = mAutoStartPref; + prefBranch->GetBoolPref("accessibility.typeaheadfind.autostart", + &mAutoStartPref); + if (mAutoStartPref != oldAutoStartPref) { + ResetGlobalAutoStart(mAutoStartPref); + } + + prefBranch->GetBoolPref("accessibility.typeaheadfind.linksonly", + &mLinksOnlyPref); + + prefBranch->GetBoolPref("accessibility.typeaheadfind.startlinksonly", + &mStartLinksOnlyPref); + + PRBool isSoundEnabled = PR_TRUE; + prefBranch->GetBoolPref("accessibility.typeaheadfind.enablesound", + &isSoundEnabled); + nsXPIDLCString soundStr; + if (isSoundEnabled) { + prefBranch->GetCharPref("accessibility.typeaheadfind.soundURL", + getter_Copies(soundStr)); + } + mNotFoundSoundURL = soundStr; + + PRBool isTimeoutEnabled = PR_FALSE; + prefBranch->GetBoolPref("accessibility.typeaheadfind.enabletimeout", + &isTimeoutEnabled); + PRInt32 timeoutLength = 0; + if (isTimeoutEnabled) { + prefBranch->GetIntPref("accessibility.typeaheadfind.timeout", + &timeoutLength); + } + mTimeoutLength = timeoutLength; + + + prefBranch->GetBoolPref("accessibility.browsewithcaret", + &mCaretBrowsingOn); + + return NS_OK; +} + + +// ------- nsITimer Methods (1) --------------- + +NS_IMETHODIMP +nsTypeAheadFind::Notify(nsITimer *timer) +{ + CancelFind(); + return NS_OK; +} + +// ----------- nsIObserver Methods (1) ------------------- + +NS_IMETHODIMP +nsTypeAheadFind::Observe(nsISupports *aSubject, const char *aTopic, + const PRUnichar *aData) +{ + PRBool isOpening; + if (!nsCRT::strcmp(aTopic,"domwindowopened")) { + isOpening = PR_TRUE; + } + else if (!nsCRT::strcmp(aTopic,"domwindowclosed")) { + isOpening = PR_FALSE; + } + else if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Shutdown(); + return NS_OK; + } + else if (!nsCRT::strcmp(aTopic,"nsWebBrowserFind_FindAgain")) { + // A find next command wants to be executed. + // We might want to handle it. If we do, return true in didExecute. + nsCOMPtr callerWindowSupports(do_QueryInterface(aSubject)); + return FindNext(NS_LITERAL_STRING("up").Equals(aData), callerWindowSupports); + } + else if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + return PrefsReset(); + } + else { + return NS_OK; + } + + // -- Attach/Remove window listeners -- + nsCOMPtr topLevelWindow(do_QueryInterface(aSubject)); + NS_ENSURE_TRUE(topLevelWindow, NS_OK); + nsCOMPtr privateWindow = do_QueryInterface(aSubject); + nsIFocusController *focusController = + privateWindow->GetRootFocusController(); + NS_ENSURE_TRUE(focusController, NS_ERROR_FAILURE); + + if (isOpening) { + if (mAutoStartPref) { + AttachWindowListeners(topLevelWindow); + } + + // Attach nsTypeAheadController to window + // so it can handle / and ' shortcuts to start text and link search + if (privateWindow) { + nsCOMPtr controllers; + privateWindow->GetControllers(getter_AddRefs(controllers)); + NS_ENSURE_TRUE(controllers, NS_ERROR_FAILURE); + + nsCOMPtr controller = + new nsTypeAheadController(focusController); + NS_ENSURE_TRUE(controller, NS_ERROR_FAILURE); + + controllers->AppendController(controller); + } + + return NS_OK; + } + + nsCOMPtr activeWindowInternal; + focusController->GetFocusedWindow(getter_AddRefs(activeWindowInternal)); + nsCOMPtr activeWindow = do_QueryInterface(activeWindowInternal); + + RemoveWindowListeners(topLevelWindow); + + // -- Prevent leaks --- + // When a window closes, we have to remove it and all of its subwindows + // from mManualFindWindows so that we don't leak. + // Eek, lots of work for such a simple thing. + nsCOMPtr ifreq(do_QueryInterface(aSubject)); + NS_ENSURE_TRUE(ifreq, NS_OK); + + nsCOMPtr webNav(do_GetInterface(ifreq)); + nsCOMPtr docShell(do_QueryInterface(webNav)); + NS_ENSURE_TRUE(docShell, NS_OK); + + nsCOMPtr docShellEnumerator; + docShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(docShellEnumerator)); + + // Iterate through shells to get windows + PRBool hasMoreDocShells; + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) + && hasMoreDocShells) { + nsCOMPtr container; + docShellEnumerator->GetNext(getter_AddRefs(container)); + nsCOMPtr ifreq(do_QueryInterface(container)); + + if (ifreq) { + nsCOMPtr domWin(do_GetInterface(ifreq)); + nsCOMPtr windowSupports(do_QueryInterface(domWin)); + + if (windowSupports) { + PRInt32 index = mManualFindWindows->IndexOf(windowSupports); + + if (index >= 0) { + mManualFindWindows->RemoveElementAt(index); + } + } + + // Don't hold references to things that will keep objects alive + // longer than they would otherwise be. + if (domWin == mFocusedWindow) { + RemoveDocListeners(); + CancelFind(); + } + if (domWin == activeWindow) { + // If popup was still open as its parent window closes, don't stay in + // menu active state which prevents us from operating + mIsMenuBarActive = mIsMenuPopupActive = PR_FALSE; + } + } + } + + return NS_OK; +} + + +nsresult +nsTypeAheadFind::UseInWindow(nsIDOMWindow *aDOMWin) +{ + NS_ENSURE_ARG_POINTER(aDOMWin); + + // Set member variables and listeners up for new window and doc + + mFindNextBuffer.Truncate(); + CancelFind(); + + GetStartWindow(aDOMWin, getter_AddRefs(mFocusedWindow)); + + nsCOMPtr domDoc; + aDOMWin->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc(do_QueryInterface(domDoc)); + + if (!doc) { + return NS_OK; + } + + nsIPresShell *presShell = doc->GetPrimaryShell(); + + if (!presShell) { + return NS_OK; + } + + nsCOMPtr oldPresShell(GetPresShell()); + + if (!oldPresShell || oldPresShell != presShell) { + CancelFind(); + } else if (presShell == oldPresShell) { + // Same window, no need to reattach listeners + + return NS_OK; + } + + RemoveDocListeners(); + + mIsFindAllowedInWindow = PR_TRUE; + mFocusedWeakShell = do_GetWeakReference(presShell); + + // Add scroll position and selection listeners, so we can cancel + // current find when user navigates + GetSelection(presShell, getter_AddRefs(mFocusedDocSelCon), + getter_AddRefs(mFocusedDocSelection)); // cache for reuse + AttachDocListeners(presShell); + + return NS_OK; +} + + +// ------- nsIDOMEventListener Methods (1) --------------- + +NS_IMETHODIMP +nsTypeAheadFind::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString eventType; + aEvent->GetType(eventType); + + if (eventType.EqualsLiteral("DOMMenuBarActive")) { + mIsMenuBarActive = PR_TRUE; + } + else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { + mIsMenuBarActive = PR_FALSE; + } + else if (eventType.EqualsLiteral("popupshown")) { + mIsMenuPopupActive = PR_TRUE; + } + else if (eventType.EqualsLiteral("popuphidden")) { + mIsMenuPopupActive = PR_FALSE; + } + else if (eventType.EqualsLiteral("unload")) { + // When document is unloaded, check to see if it's the + // current typeahead doc. If it is, cancel find + // and reset member variables so we don't leak + nsCOMPtr event(do_QueryInterface(aEvent)); + NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); + + nsCOMPtr eventTarget; + event->GetOriginalTarget(getter_AddRefs(eventTarget)); + nsCOMPtr doc(do_QueryInterface(eventTarget)); + nsCOMPtr focusedShell(GetPresShell()); + if (!focusedShell || !doc) { + return NS_ERROR_FAILURE; + } + + PRBool cancelFind = PR_FALSE; + + nsPresShellIterator iter(doc); + nsCOMPtr shellToBeDestroyed; + while ((shellToBeDestroyed = iter.GetNextShell())) { + if (shellToBeDestroyed == focusedShell) { + cancelFind = PR_TRUE; + break; + } + } + + if (cancelFind) { + RemoveDocListeners(); + mSearchRange = do_CreateInstance(kRangeCID); + mStartPointRange = do_CreateInstance(kRangeCID); + mEndPointRange = do_CreateInstance(kRangeCID); + mFocusedWindow = nsnull; + CancelFind(); + } + } + + return NS_OK; +} + + +// ------- nsIDOMKeyListener Methods (3) --------------- + +NS_IMETHODIMP +nsTypeAheadFind::KeyDown(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::KeyUp(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::KeyPress(nsIDOMEvent* aEvent) +{ + if (!mIsTypeAheadOn || mIsMenuBarActive || mIsMenuPopupActive) { + return NS_OK; + } + + if (!mIsSoundInitialized && !mNotFoundSoundURL.IsEmpty()) { + // This makes sure system sound library is loaded so that + // there's no lag before the first sound is played + // by waiting for the first keystroke, we still get the startup time benefits. + mIsSoundInitialized = PR_TRUE; + mSoundInterface = do_CreateInstance("@mozilla.org/sound;1"); + if (mSoundInterface && !mNotFoundSoundURL.EqualsLiteral("beep")) { + mSoundInterface->Init(); + } + } + +#ifdef XP_WIN + // After each keystroke, ensure sound object is destroyed, to free up memory + // allocated for error sound, otherwise Windows' nsISound impl + // holds onto the last played sound, using up memory. + mSoundInterface = nsnull; +#endif + nsCOMPtr targetContent; + nsCOMPtr targetPresShell; + GetTargetIfTypeAheadOkay(aEvent, getter_AddRefs(targetContent), + getter_AddRefs(targetPresShell)); + if (!targetContent || !targetPresShell) + return NS_OK; + + PRUint32 keyCode(0), charCode; + PRBool isShift(PR_FALSE), isCtrl(PR_FALSE), isAlt(PR_FALSE), isMeta(PR_FALSE); + nsCOMPtr keyEvent(do_QueryInterface(aEvent)); + + // ---------- Analyze keystroke, exit early if possible -------------- + + if (!keyEvent || + NS_FAILED(keyEvent->GetKeyCode(&keyCode)) || + NS_FAILED(keyEvent->GetCharCode(&charCode)) || + NS_FAILED(keyEvent->GetShiftKey(&isShift)) || + NS_FAILED(keyEvent->GetCtrlKey(&isCtrl)) || + NS_FAILED(keyEvent->GetAltKey(&isAlt)) || + NS_FAILED(keyEvent->GetMetaKey(&isMeta))) { + return NS_ERROR_FAILURE; + } + + // ---------- Set Backspace Protection -------------------------- + // mIsBackspaceProtectOn should be PR_TRUE only if the last key + // was a backspace and this key is also a backspace. It keeps us + // from accidentally hitting backspace too many times in a row, going + // back in history when we really just wanted to clear the find string. + if (keyCode != nsIDOMKeyEvent::DOM_VK_BACK_SPACE) { + mIsBackspaceProtectOn = PR_FALSE; + } + + // ---------- Check the keystroke -------------------------------- + if ((isAlt && !isShift) || isCtrl || isMeta) { + // Ignore most modified keys, but alt+shift may be used for + // entering foreign chars. + + return NS_OK; + } + + // ------------- Escape pressed --------------------- + if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) { + // Escape accomplishes 2 things: + // 1. it is a way for the user to deselect with the keyboard + // 2. it is a way for the user to cancel incremental find with + // visual feedback + if (mLinksOnlyManuallySet || !mTypeAheadBuffer.IsEmpty()) { + // If Escape is normally used for a command, don't do it + aEvent->PreventDefault(); + CancelFind(); + } + if (mFocusedDocSelection) { + SetSelectionLook(targetPresShell, PR_FALSE); + mFocusedDocSelection->CollapseToStart(); + } + + return NS_OK; + } + + // ---------- PreventDefault check --------------- + // If a web page wants to use printable character keys, + // they have to use evt.preventDefault() after they get the key + nsCOMPtr uiEvent(do_QueryInterface(aEvent)); + PRBool preventDefault; + uiEvent->GetPreventDefault(&preventDefault); + if (preventDefault) { + return NS_OK; + } + + // ----------- Back space ------------------------- + if (keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) { + // The order of seeing keystrokes is: + // 1) Chrome, 2) Typeahead, 3) [platform]HTMLBindings.xml + // If chrome handles backspace, it needs to do this work + // Otherwise, we handle backspace here. + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + PRBool backspaceUsed; + BackOneChar(&backspaceUsed); + if (backspaceUsed) { + aEvent->PreventDefault(); // Prevent normal processing of this keystroke + } + + return NS_OK; + } + + // ----------- Other non-printable keys ----------- + // We ignore space only if it's the first character + // Function keys, etc. exit here + if (keyCode || charCode < ' ' || + (charCode == ' ' && mTypeAheadBuffer.IsEmpty())) { + return NS_OK; + } + + // Ignore first / or ' -- they are used to set links/text only + // Needs to come to us through htmlBindings.xml's keybinding + // then via nsTypeAheadController::DoCommand() + if (!mLinksOnlyManuallySet && (charCode == '\'' || charCode == '/') && + mTypeAheadBuffer.IsEmpty()) { + return NS_OK; + } + + // We're using this key, no one else should + aEvent->PreventDefault(); + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + return HandleChar(charCode); +} + + +NS_IMETHODIMP +nsTypeAheadFind::BackOneChar(PRBool *aIsBackspaceUsed) +{ + if (!mFocusedDocSelection) { + *aIsBackspaceUsed = PR_FALSE; + return NS_OK; + } + + // In normal type ahead find, remove a printable char from + // mTypeAheadBuffer, then search for buffer contents + // Or, in repeated char find, go backwards + + *aIsBackspaceUsed = PR_TRUE; + + // ---------- No chars in string ------------ + if (mTypeAheadBuffer.IsEmpty() || !mStartFindRange) { + if (!mFindNextBuffer.IsEmpty() && + (mRepeatingMode == eRepeatingChar || + mRepeatingMode == eRepeatingCharReverse)) { + // Backspace to find previous repeated char + mTypeAheadBuffer = mFindNextBuffer; + mFocusedDocSelection->GetRangeAt(0, getter_AddRefs(mStartFindRange)); + } + else { + // No find string to backspace in! + if (mIsBackspaceProtectOn) { + // This flag should be on only if the last key was a backspace. + // It keeps us from accidentally hitting backspace too many times and + // going back in history when we really just wanted to clear + // the find string. + nsCOMPtr soundInterface = + do_CreateInstance("@mozilla.org/sound;1"); + if (soundInterface) { + soundInterface->Beep(); // beep to warn + } + mIsBackspaceProtectOn = PR_FALSE; + } + else { + *aIsBackspaceUsed = PR_FALSE; + } + + return NS_OK; + } + } + + // ---------- Only 1 char in string ------------ + if (mTypeAheadBuffer.Length() == 1 && + mRepeatingMode != eRepeatingCharReverse) { + if (mStartFindRange) { + mIsFindingText = PR_TRUE; // Prevent selection listener side effects + mFocusedDocSelection->RemoveAllRanges(); + mFocusedDocSelection->AddRange(mStartFindRange); + } + + mFocusedDocSelection->CollapseToStart(); + mIsFindingText = PR_FALSE; + CancelFind(); + mIsBackspaceProtectOn = PR_TRUE; + + return NS_OK; + } + + // ---------- Multiple chars in string ---------- + PRBool findBackwards = PR_FALSE; + if (mRepeatingMode == eRepeatingChar || + mRepeatingMode == eRepeatingCharReverse) { + // Backspace in repeating char mode is like + mRepeatingMode = eRepeatingCharReverse; + findBackwards = PR_TRUE; + } + else if (!mLastBadChar) { + mTypeAheadBuffer.Truncate(mTypeAheadBuffer.Length() - 1); + } + + mLastBadChar = 0; + + if (mBadKeysSinceMatch > 1) { + --mBadKeysSinceMatch; + DisplayStatus(PR_FALSE, nsnull, PR_FALSE); // Display failure status + SaveFind(); + return NS_OK; + } + + mBadKeysSinceMatch = 0; + mDontTryExactMatch = PR_FALSE; + + // ---------- Get new find start ------------------ + nsIPresShell *presShell = nsnull; + if (!findBackwards) { + // For backspace, start from where first char was found + // unless in backspacing after repeating char mode + nsCOMPtr startNode; + mStartFindRange->GetStartContainer(getter_AddRefs(startNode)); + if (startNode) { + nsCOMPtr domDoc; + startNode->GetOwnerDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc(do_QueryInterface(domDoc)); + if (doc) { + presShell = doc->GetPrimaryShell(); + } + } + if (!presShell) { + *aIsBackspaceUsed = PR_FALSE; + return NS_ERROR_FAILURE; + } + // Set the selection to the where the first character was found + // so that find starts from there + mIsFindingText = PR_TRUE; // so selection won't call CancelFind() + GetSelection(presShell, getter_AddRefs(mFocusedDocSelCon), + getter_AddRefs(mFocusedDocSelection)); + nsCOMPtr startFindRange = do_CreateInstance(kRangeCID); + mStartFindRange->CloneRange(getter_AddRefs(startFindRange)); + mFocusedDocSelection->RemoveAllRanges(); + mFocusedDocSelection->AddRange(startFindRange); + mStartFindRange = startFindRange; + } + + // ----------- Perform the find ------------------ + mIsFindingText = PR_TRUE; // so selection won't call CancelFind() + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + if (NS_FAILED(FindItNow(presShell, findBackwards, mLinksOnly, PR_FALSE))) { + DisplayStatus(PR_FALSE, nsnull, PR_FALSE); // Display failure status + } + mIsFindingText = PR_FALSE; + + SaveFind(); + + return NS_OK; // Backspace handled +} + + +nsresult +nsTypeAheadFind::HandleChar(PRUnichar aChar) +{ + // Add a printable char to mTypeAheadBuffer, then search for buffer contents + + // ------------ Million keys protection ------------- + if (mBadKeysSinceMatch >= kMaxBadCharsBeforeCancel) { + // If they're just quickly mashing keys onto the keyboard, stop searching + // until typeahead find is canceled via timeout or another normal means + StartTimeout(); // Timeout from last bad key (this one) + DisplayStatus(PR_FALSE, nsnull, PR_TRUE); // Status message to say find stopped + return NS_ERROR_FAILURE; + } + + aChar = ToLowerCase(aChar); + PRInt32 bufferLength = mTypeAheadBuffer.Length(); + + mIsFirstVisiblePreferred = PR_FALSE; + + // --------- No new chars after find again ---------- + if (mRepeatingMode == eRepeatingForward || + mRepeatingMode == eRepeatingReverse) { + // Once Accel+[shift]+G or [shift]+F3 has been used once, + // new typing will start a new find + CancelFind(); + bufferLength = 0; + mRepeatingMode = eRepeatingNone; + } + // --------- New char in repeated char mode --------- + else if ((mRepeatingMode == eRepeatingChar || + mRepeatingMode == eRepeatingCharReverse) && + bufferLength > 1 && aChar != mTypeAheadBuffer.First()) { + // If they repeat the same character and then change, such as aaaab + // start over with new char as a repeated char find + mTypeAheadBuffer = aChar; + } + // ------- Set repeating mode --------- + else if (bufferLength > 0) { + if (mTypeAheadBuffer.First() != aChar) { + mRepeatingMode = eRepeatingNone; + mAllTheSameChar = PR_FALSE; + } + } + + mTypeAheadBuffer += aChar; // Add the char! + + // --------- Initialize find if 1st char ---------- + if (bufferLength == 0) { + if (!mLinksOnlyManuallySet) { + // Reset links only to default, if not manually set + // by the user via ' or / keypress at beginning + mLinksOnly = mLinksOnlyPref; + } + + mRepeatingMode = eRepeatingNone; + + // If you can see the selection (not collapsed or thru caret browsing), + // or if already focused on a page element, start there. + // Otherwise we're going to start at the first visible element + NS_ENSURE_TRUE(mFocusedDocSelection, NS_ERROR_FAILURE); + PRBool isSelectionCollapsed; + mFocusedDocSelection->GetIsCollapsed(&isSelectionCollapsed); + + // If true, we will scan from top left of visible area + // If false, we will scan from start of selection + mIsFirstVisiblePreferred = !mCaretBrowsingOn && isSelectionCollapsed; + if (mIsFirstVisiblePreferred) { + // Get focused content from esm. If it's null, the document is focused. + // If not, make sure the selection is in sync with the focus, so we can + // start our search from there. + nsCOMPtr focusedContent; + nsCOMPtr presShell(GetPresShell()); + NS_ENSURE_TRUE(presShell, NS_OK); + nsPresContext *presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_OK); + + nsIEventStateManager *esm = presContext->EventStateManager(); + esm->GetFocusedContent(getter_AddRefs(focusedContent)); + if (focusedContent) { + mIsFindingText = PR_TRUE; // prevent selection listener from calling CancelFind() + esm->MoveCaretToFocus(); + mIsFindingText = PR_FALSE; + mIsFirstVisiblePreferred = PR_FALSE; + } + else { + nsCOMPtr container = presContext->GetContainer(); + nsCOMPtr docShellTreeItem = + do_QueryInterface(container); + nsCOMPtr parentTreeItem; + docShellTreeItem->GetSameTypeParent(getter_AddRefs(parentTreeItem)); + if (parentTreeItem) { + mIsFirstVisiblePreferred = PR_FALSE; // focused on a frame or iframe + } + } + } + } + + // ----------- Find the text! --------------------- + mIsFindingText = PR_TRUE; // prevent selection listener from calling CancelFind() + + nsresult rv = NS_ERROR_FAILURE; + + if (mBadKeysSinceMatch <= 1) { // Don't even try if the last key was already bad + if (!mDontTryExactMatch) { + // Regular find, not repeated char find + + // Prefer to find exact match + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = FindItNow(nsnull, PR_FALSE, mLinksOnly, mIsFirstVisiblePreferred); + } + +#ifndef NO_LINK_CYCLE_ON_SAME_CHAR + if (NS_FAILED(rv) && !mLiteralTextSearchOnly && mAllTheSameChar && + mTypeAheadBuffer.Length() > 1) { + mRepeatingMode = eRepeatingChar; + mDontTryExactMatch = PR_TRUE; // Repeated character find mode + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = FindItNow(nsnull, PR_TRUE, PR_TRUE, mIsFirstVisiblePreferred); + } +#endif + } + + // ---------Handle success or failure --------------- + mIsFindingText = PR_FALSE; + if (NS_SUCCEEDED(rv)) { + mLastBadChar = 0; + if (mTypeAheadBuffer.Length() == 1) { + // If first letter, store where the first find succeeded + // (mStartFindRange) + + mStartFindRange = nsnull; + nsCOMPtr startFindRange; + mFocusedDocSelection->GetRangeAt(0, getter_AddRefs(startFindRange)); + + if (startFindRange) { + startFindRange->CloneRange(getter_AddRefs(mStartFindRange)); + } + } + } + else { + if (aChar == '/' || aChar == '\'') { + // Didn't find / or ' -- use that key to start a new text or link find + return StartNewFind(mFocusedWindow, aChar == '\''); + } + PRUint32 length = mTypeAheadBuffer.Length(); + if (mLastBadChar && length >= 1) { + // We have to do this to put the exact typed string in the status + // Otherwise, it will be missing mLastBadChar, which had been removed + // so that the user could avoid pressing backspace + nsAutoString lastTwoCharsTyped(mLastBadChar); + lastTwoCharsTyped += mTypeAheadBuffer.CharAt(length - 1); + mTypeAheadBuffer.Truncate(length - 1); + mTypeAheadBuffer += lastTwoCharsTyped; + ++length; + } + DisplayStatus(PR_FALSE, nsnull, PR_FALSE); // Display failure status + mRepeatingMode = eRepeatingNone; + + ++mBadKeysSinceMatch; + + // Error sound (don't fire when backspace is pressed, they're + // trying to correct the mistake!) + PlayNotFoundSound(); + + // Remove bad character from buffer, so we can continue typing from + // last matched character + if (length >= 1) { + mLastBadChar = mTypeAheadBuffer.CharAt(length - 1); + mTypeAheadBuffer.Truncate(length - 1); + } + } + + SaveFind(); + + return NS_OK; +} + + +void +nsTypeAheadFind::SaveFind() +{ + // Store find string for find-next + mFindNextBuffer = mTypeAheadBuffer; + if (mLastBadChar) { + mFindNextBuffer.Append(mLastBadChar); + } + + nsCOMPtr webBrowserFind; + GetWebBrowserFind(mFocusedWindow, getter_AddRefs(webBrowserFind)); + if (webBrowserFind) { + webBrowserFind->SetSearchString(PromiseFlatString(mTypeAheadBuffer).get()); + } + + if (!mFindService) { + mFindService = do_GetService("@mozilla.org/find/find_service;1"); + } + if (mFindService) { + mFindService->SetSearchString(mFindNextBuffer); + } + + // --- If accessibility.typeaheadfind.timeout is set, + // cancel find after specified # milliseconds --- + StartTimeout(); +} + + +void +nsTypeAheadFind::PlayNotFoundSound() +{ + if (mNotFoundSoundURL.IsEmpty()) // no sound + return; + if (!mSoundInterface) { + mSoundInterface = do_CreateInstance("@mozilla.org/sound;1"); + } + if (mSoundInterface) { + mIsSoundInitialized = PR_TRUE; + + if (mNotFoundSoundURL.Equals("beep")) { + mSoundInterface->Beep(); + return; + } + + nsCOMPtr soundURI; + if (mNotFoundSoundURL.Equals("default")) + NS_NewURI(getter_AddRefs(soundURI), + NS_LITERAL_CSTRING(TYPEAHEADFIND_NOTFOUND_WAV_URL)); + else + NS_NewURI(getter_AddRefs(soundURI), mNotFoundSoundURL); + nsCOMPtr soundURL(do_QueryInterface(soundURI)); + if (soundURL) { + mSoundInterface->Play(soundURL); + } + } +} + + +NS_IMETHODIMP +nsTypeAheadFind::HandleText(nsIDOMEvent* aTextEvent) +{ + // This is called multiple times in the middle of an + // IME composition + + if (!mIsIMETypeAheadActive) { + return NS_OK; + } + + // ------- Check if Type Ahead can occur here ------------- + // (and if it can, get the target content and document) + nsCOMPtr targetContent; + nsCOMPtr targetPresShell; + GetTargetIfTypeAheadOkay(aTextEvent, getter_AddRefs(targetContent), + getter_AddRefs(targetPresShell)); + if (!targetContent || !targetPresShell) { + mIsIMETypeAheadActive = PR_FALSE; + return NS_OK; + } + + nsCOMPtr textEvent(do_QueryInterface(aTextEvent)); + if (!textEvent) + return NS_OK; + + textEvent->GetText(mIMEString); + + // show the candidate char/word in the status bar + DisplayStatus(PR_FALSE, nsnull, PR_FALSE, mIMEString.get()); + + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::HandleStartComposition(nsIDOMEvent* aCompositionEvent) +{ + // This is called once at the start of an IME composition + + mIsIMETypeAheadActive = PR_TRUE; + + if (!mIsTypeAheadOn || mIsMenuBarActive || mIsMenuPopupActive) { + mIsIMETypeAheadActive = PR_FALSE; + return NS_OK; + } + + // Pause the cancellation timer until IME is finished + // HandleChar() will start it again + if (mTimer) { + mTimer->Cancel(); + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::HandleEndComposition(nsIDOMEvent* aCompositionEvent) +{ + // This is called once at the end of an IME composition + + if (!mIsIMETypeAheadActive) { + return PR_FALSE; + } + + // -------- Find the composed chars one at a time --------- + nsReadingIterator iter; + nsReadingIterator iterEnd; + + mIMEString.BeginReading(iter); + mIMEString.EndReading(iterEnd); + + // Handle the characters one at a time + while (iter != iterEnd) { + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + if (NS_FAILED(HandleChar(*iter))) { + // Character not found, exit loop early + break; + } + ++iter; + } + + mIMEString.Truncate(); // To be safe, so that find won't happen twice + + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::HandleQueryComposition(nsIDOMEvent* aCompositionEvent) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::HandleQueryReconversion(nsIDOMEvent* aCompositionEvent) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::HandleQueryCaretRect(nsIDOMEvent* aCompositionEvent) +{ + return NS_OK; +} + + +nsresult +nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, + PRBool aIsRepeatingSameChar, PRBool aIsLinksOnly, + PRBool aIsFirstVisiblePreferred) +{ + nsCOMPtr presShell(aPresShell); + nsCOMPtr startingPresShell = GetPresShell(); + + if (!presShell) { + presShell = startingPresShell; // this is the current document + + if (!presShell) { + return NS_ERROR_FAILURE; + } + } + + nsCOMPtr presContext = presShell->GetPresContext(); + if (!presContext) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr startingContainer = presContext->GetContainer(); + nsCOMPtr treeItem(do_QueryInterface(startingContainer)); + NS_ASSERTION(treeItem, "Bug 175321 Crashes with Type Ahead Find [@ nsTypeAheadFind::FindItNow]"); + if (!treeItem) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr rootContentTreeItem; + nsCOMPtr currentDocShell; + nsCOMPtr startingDocShell(do_QueryInterface(startingContainer)); + + treeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem)); + nsCOMPtr rootContentDocShell = + do_QueryInterface(rootContentTreeItem); + + if (!rootContentDocShell) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr docShellEnumerator; + rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(docShellEnumerator)); + + // Default: can start at the current document + nsCOMPtr currentContainer = startingContainer = + do_QueryInterface(rootContentDocShell); + + // Iterate up to current shell, if there's more than 1 that we're + // dealing with + PRBool hasMoreDocShells; + + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) + && hasMoreDocShells) { + docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); + currentDocShell = do_QueryInterface(currentContainer); + if (!currentDocShell || currentDocShell == startingDocShell || + aIsFirstVisiblePreferred) { + break; + } + } + + // ------------ Get ranges ready ---------------- + nsCOMPtr returnRange; + if (NS_FAILED(GetSearchContainers(currentContainer, aIsRepeatingSameChar, + aIsFirstVisiblePreferred, + !aIsFirstVisiblePreferred || mStartFindRange, + getter_AddRefs(presShell), + getter_AddRefs(presContext)))) { + return NS_ERROR_FAILURE; + } + + PRInt16 rangeCompareResult = 0; + mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, + mSearchRange, &rangeCompareResult); + // No need to wrap find in doc if starting at beginning + PRBool hasWrapped = (rangeCompareResult <= 0); + + nsAutoString findBuffer; + if (aIsRepeatingSameChar) { + findBuffer = mTypeAheadBuffer.First(); + } else { + findBuffer = PromiseFlatString(mTypeAheadBuffer); + } + + if (findBuffer.IsEmpty()) + return NS_ERROR_FAILURE; + + mFind->SetFindBackwards(mRepeatingMode == eRepeatingCharReverse || + mRepeatingMode == eRepeatingReverse); + + while (PR_TRUE) { // ----- Outer while loop: go through all docs ----- + while (PR_TRUE) { // === Inner while loop: go through a single doc === + mFind->Find(findBuffer.get(), mSearchRange, mStartPointRange, + mEndPointRange, getter_AddRefs(returnRange)); + if (!returnRange) { + break; // Nothing found in this doc, go to outer loop (try next doc) + } + + // ------- Test resulting found range for success conditions ------ + PRBool isInsideLink = PR_FALSE, isStartingLink = PR_FALSE; + + if (aIsLinksOnly) { + // Don't check if inside link when searching all text + + RangeStartsInsideLink(returnRange, presShell, &isInsideLink, + &isStartingLink); + } + + PRBool usesIndependentSelection; + if (!IsRangeVisible(presShell, presContext, returnRange, + aIsFirstVisiblePreferred, PR_FALSE, + getter_AddRefs(mStartPointRange), + &usesIndependentSelection) || + (aIsLinksOnly && !isInsideLink) || + (mStartLinksOnlyPref && aIsLinksOnly && !isStartingLink)) { + // ------ Failure ------ + // Start find again from here + returnRange->CloneRange(getter_AddRefs(mStartPointRange)); + + // Collapse to end + mStartPointRange->Collapse(mRepeatingMode == eRepeatingReverse || + mRepeatingMode == eRepeatingCharReverse); + + continue; + } + + // ------ Success! ------- + // Make sure new document is selected + if (presShell != startingPresShell) { + // We are in a new document (because of frames/iframes) + mFocusedDocSelection->CollapseToStart(); // Hide old doc's selection + SetSelectionLook(startingPresShell, PR_FALSE); + + nsIDocument *doc = presShell->GetDocument(); + if (!doc) { + return NS_ERROR_FAILURE; + } + mFocusedWeakShell = do_GetWeakReference(presShell); + + // Get selection controller and selection for new frame/iframe + GetSelection(presShell, getter_AddRefs(mFocusedDocSelCon), + getter_AddRefs(mFocusedDocSelection)); + } + + if (!mFocusedDocSelection || !mFocusedDocSelCon) { + // Apparently these can go away even though presshell/prescontext exist + return NS_ERROR_FAILURE; + } + + // Select the found text and focus it + mFocusedDocSelection->RemoveAllRanges(); + mFocusedDocSelection->AddRange(returnRange); + // After ScrollSelectionIntoView(), the pending notifications might be + // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + mFocusedDocSelCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_FOCUS_REGION, + PR_TRUE); + SetSelectionLook(presShell, PR_TRUE); + + nsIEventStateManager *esm = presContext->EventStateManager(); + + PRBool isSelectionWithFocus; + esm->SetContentState(nsnull, NS_EVENT_STATE_FOCUS); // Start off focusing doc + esm->MoveFocusToCaret(PR_TRUE, &isSelectionWithFocus); + + nsCOMPtr focusedContent; + esm->GetFocusedContent(getter_AddRefs(focusedContent)); + + DisplayStatus(PR_TRUE, focusedContent, PR_FALSE); + + mBadKeysSinceMatch = 0; + + return NS_OK; + } + + // ======= end-inner-while (go through a single document) ========== + + // ---------- Nothing found yet, try next document ------------- + PRBool hasTriedFirstDoc = PR_FALSE; + do { + // ==== Second inner loop - get another while ==== + if (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) + && hasMoreDocShells) { + docShellEnumerator->GetNext(getter_AddRefs(currentContainer)); + NS_ASSERTION(currentContainer, "HasMoreElements lied to us!"); + currentDocShell = do_QueryInterface(currentContainer); + + if (currentDocShell) { + break; + } + } + else if (hasTriedFirstDoc) { // Avoid potential infinite loop + return NS_ERROR_FAILURE; // No content doc shells + } + + // Reached last doc shell, loop around back to first doc shell + rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(docShellEnumerator)); + hasTriedFirstDoc = PR_TRUE; + } while (docShellEnumerator); // ==== end second inner while === + + PRBool continueLoop = PR_FALSE; + if (currentDocShell != startingDocShell) { + continueLoop = PR_TRUE; // Try next document + } + else if (!hasWrapped || aIsFirstVisiblePreferred) { + // Finished searching through docshells: + // If aFirstVisiblePreferred == PR_TRUE, we may need to go through all + // docshells twice -once to look for visible matches, the second time + // for any match + aIsFirstVisiblePreferred = PR_FALSE; + hasWrapped = PR_TRUE; + continueLoop = PR_TRUE; // Go through all docs again + } + + if (continueLoop) { + if (NS_FAILED(GetSearchContainers(currentContainer, + aIsRepeatingSameChar, + aIsFirstVisiblePreferred, PR_FALSE, + getter_AddRefs(presShell), + getter_AddRefs(presContext)))) { + return NS_ERROR_FAILURE; + } + + if (mRepeatingMode == eRepeatingCharReverse || + mRepeatingMode == eRepeatingReverse) { + // Reverse mode: + // swap start and end points, so that we start + // at end of document and go to beginning + nsCOMPtr tempRange; + mStartPointRange->CloneRange(getter_AddRefs(tempRange)); + mStartPointRange = mEndPointRange; + mEndPointRange = tempRange; + } + + continue; + } + + // ------------- Failed -------------- + break; + } // end-outer-while: go through all docs + + return NS_ERROR_FAILURE; +} + + +nsresult +nsTypeAheadFind::GetSearchContainers(nsISupports *aContainer, + PRBool aIsRepeatingSameChar, + PRBool aIsFirstVisiblePreferred, + PRBool aCanUseDocSelection, + nsIPresShell **aPresShell, + nsPresContext **aPresContext) +{ + NS_ENSURE_ARG_POINTER(aContainer); + NS_ENSURE_ARG_POINTER(aPresShell); + NS_ENSURE_ARG_POINTER(aPresContext); + + *aPresShell = nsnull; + *aPresContext = nsnull; + + nsCOMPtr docShell(do_QueryInterface(aContainer)); + if (!docShell) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr presContext; + nsCOMPtr presShell; + + docShell->GetPresShell(getter_AddRefs(presShell)); + docShell->GetPresContext(getter_AddRefs(presContext)); + + if (!presShell || !presContext) { + return NS_ERROR_FAILURE; + } + + nsIDocument *doc = presShell->GetDocument(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr rootContent; + nsCOMPtr htmlDoc(do_QueryInterface(doc)); + if (htmlDoc) { + nsCOMPtr bodyEl; + htmlDoc->GetBody(getter_AddRefs(bodyEl)); + rootContent = do_QueryInterface(bodyEl); + } + if (!rootContent) { + rootContent = doc->GetRootContent(); + } + nsCOMPtr rootNode(do_QueryInterface(rootContent)); + + if (!rootNode) { + return NS_ERROR_FAILURE; + } + + PRUint32 childCount = rootContent->GetChildCount(); + + mSearchRange->SelectNodeContents(rootNode); + + mEndPointRange->SetEnd(rootNode, childCount); + mEndPointRange->Collapse(PR_FALSE); // collapse to end + + // Consider current selection as null if + // it's not in the currently focused document + nsCOMPtr currentSelectionRange; + nsCOMPtr selectionPresShell = GetPresShell(); + + if (aCanUseDocSelection && selectionPresShell == presShell && mFocusedDocSelection) { + mFocusedDocSelection->GetRangeAt(0, getter_AddRefs(currentSelectionRange)); + } + + if (!currentSelectionRange) { + // Ensure visible range, move forward if necessary + // This uses ignores the return value, but usese the side effect of + // IsRangeVisible. It returns the first visible range after searchRange + IsRangeVisible(presShell, presContext, mSearchRange, + aIsFirstVisiblePreferred, PR_TRUE, + getter_AddRefs(mStartPointRange), nsnull); + } + else { + PRInt32 startOffset; + nsCOMPtr startNode; + if ((aIsRepeatingSameChar && mRepeatingMode != eRepeatingCharReverse) || + mRepeatingMode == eRepeatingForward) { + currentSelectionRange->GetEndContainer(getter_AddRefs(startNode)); + currentSelectionRange->GetEndOffset(&startOffset); + } + else { + currentSelectionRange->GetStartContainer(getter_AddRefs(startNode)); + currentSelectionRange->GetStartOffset(&startOffset); + } + if (!startNode) { + startNode = rootNode; + } + + // We need to set the start point this way, other methods haven't worked + mStartPointRange->SelectNode(startNode); + mStartPointRange->SetStart(startNode, startOffset); + } + + mStartPointRange->Collapse(PR_TRUE); // collapse to start + + *aPresShell = presShell; + NS_ADDREF(*aPresShell); + + *aPresContext = presContext; + NS_ADDREF(*aPresContext); + + return NS_OK; +} + + +void +nsTypeAheadFind::RangeStartsInsideLink(nsIDOMRange *aRange, + nsIPresShell *aPresShell, + PRBool *aIsInsideLink, + PRBool *aIsStartingLink) +{ + *aIsInsideLink = PR_FALSE; + *aIsStartingLink = PR_TRUE; + + // ------- Get nsIContent to test ------- + nsCOMPtr startNode; + nsCOMPtr startContent, origContent; + aRange->GetStartContainer(getter_AddRefs(startNode)); + PRInt32 startOffset; + aRange->GetStartOffset(&startOffset); + + startContent = do_QueryInterface(startNode); + if (!startContent) { + NS_NOTREACHED("startContent should never be null"); + return; + } + origContent = startContent; + + if (startContent->IsNodeOfType(nsINode::eELEMENT)) { + nsIContent *childContent = startContent->GetChildAt(startOffset); + if (childContent) { + startContent = childContent; + } + } + else if (startOffset > 0) { + const nsTextFragment *textFrag = startContent->GetText(); + if (textFrag) { + // look for non whitespace character before start offset + for (PRInt32 index = 0; index < startOffset; index++) { + if (!XP_IS_SPACE(textFrag->CharAt(index))) { + *aIsStartingLink = PR_FALSE; // not at start of a node + + break; + } + } + } + } + + // ------- Check to see if inside link --------- + + // We now have the correct start node for the range + // Search for links, starting with startNode, and going up parent chain + + nsCOMPtr tag, hrefAtom(do_GetAtom("href")); + nsCOMPtr typeAtom(do_GetAtom("type")); + + while (PR_TRUE) { + // Keep testing while textContent is equal to something, + // eventually we'll run out of ancestors + + if (startContent->IsNodeOfType(nsINode::eHTML)) { + nsCOMPtr link(do_QueryInterface(startContent)); + if (link) { + // Check to see if inside HTML link + *aIsInsideLink = startContent->HasAttr(kNameSpaceID_None, hrefAtom); + return; + } + } + else { + // Any xml element can be an xlink + *aIsInsideLink = startContent->HasAttr(kNameSpaceID_XLink, hrefAtom); + if (*aIsInsideLink) { + if (!startContent->AttrValueIs(kNameSpaceID_XLink, typeAtom, + NS_LITERAL_STRING("simple"), + eCaseMatters)) { + *aIsInsideLink = PR_FALSE; // Xlink must be type="simple" + } + + return; + } + } + + // Get the parent + nsCOMPtr parent = startContent->GetParent(); + if (parent) { + nsIContent *parentsFirstChild = parent->GetChildAt(0); + // We don't want to look at a whitespace-only first child + if (parentsFirstChild && parentsFirstChild->TextIsOnlyWhitespace()) + parentsFirstChild = parent->GetChildAt(1); + + if (parentsFirstChild != startContent) { + // startContent wasn't a first child, so we conclude that + // if this is inside a link, it's not at the beginning of it + *aIsStartingLink = PR_FALSE; + } + + startContent = parent; + } + else + break; + } + + *aIsStartingLink = PR_FALSE; +} + + +NS_IMETHODIMP +nsTypeAheadFind::ScrollPositionWillChange(nsIScrollableView *aView, + nscoord aX, nscoord aY) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::ScrollPositionDidChange(nsIScrollableView *aScrollableView, + nscoord aX, nscoord aY) +{ + if (!mIsFindingText) + CancelFind(); + + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::NotifySelectionChanged(nsIDOMDocument *aDoc, + nsISelection *aSel, PRInt16 aReason) +{ + if (!mIsFindingText) { + if (mRepeatingMode != eRepeatingNone) { + // Selection had changed color for Type Ahead Find's version of Accel+G + // We change it back when the selection changes from someone else + nsCOMPtr presShell(GetPresShell()); + SetSelectionLook(presShell, PR_FALSE); + } + CancelFind(); + } + + return NS_OK; +} + + +// ---------------- nsISuiteTypeAheadFind -------------------- + +NS_IMETHODIMP +nsTypeAheadFind::FindNext(PRBool aFindBackwards, nsISupportsInterfacePointer *aCallerWindowSupports) +{ + NS_ENSURE_TRUE(aCallerWindowSupports, NS_ERROR_FAILURE); + + // aCallerWindowSupports holds an nsISupports to the window the + // find next command was fired in + // We clear out the window pointer when handling the find command ourselves. + + if (!mIsFindAllowedInWindow || mFindNextBuffer.IsEmpty() || !mFocusedWindow) { + return NS_OK; + } + + // Compare the top level content pres shell of typeaheadfind + // with the top level content pres shell window where find next is happening + // If they're different, exit so that webbrowswerfind can handle FindNext() + + nsCOMPtr typeAheadPresShell(GetPresShell()); + NS_ENSURE_TRUE(typeAheadPresShell, NS_OK); + + nsPresContext *presContext = typeAheadPresShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_OK); + + nsCOMPtr container = presContext->GetContainer(); + nsCOMPtr treeItem(do_QueryInterface(container)); + NS_ENSURE_TRUE(treeItem, NS_OK); + + // Reget typeAheadPresShell to make sure we're + // comparing with the top content presshell + GetTopContentPresShell(treeItem, getter_AddRefs(typeAheadPresShell)); + NS_ENSURE_TRUE(typeAheadPresShell, NS_OK); + + nsCOMPtr callerWindowSupports; + aCallerWindowSupports->GetData(getter_AddRefs(callerWindowSupports)); + nsCOMPtr ifreq(do_QueryInterface(callerWindowSupports)); + NS_ENSURE_TRUE(ifreq, NS_ERROR_FAILURE); + + nsCOMPtr webNav(do_GetInterface(ifreq)); + treeItem = do_QueryInterface(webNav); + NS_ENSURE_TRUE(treeItem, NS_OK); + + nsCOMPtr callerPresShell; + GetTopContentPresShell(treeItem, getter_AddRefs(callerPresShell)); + NS_ENSURE_TRUE(callerPresShell, NS_OK); + + if (callerPresShell != typeAheadPresShell) { + // This means typeaheadfind is active in a different window or doc + // So it's not appropriate to find next for the current window + mFindNextBuffer.Truncate(); + return NS_OK; + } + + nsCOMPtr callerWin(do_QueryInterface(callerWindowSupports)); + NS_ENSURE_TRUE(callerWin, NS_OK); + + nsCOMPtr webBrowserFind; + GetWebBrowserFind(callerWin, getter_AddRefs(webBrowserFind)); + NS_ENSURE_TRUE(webBrowserFind, NS_ERROR_FAILURE); + + nsXPIDLString webBrowserFindString; + if (webBrowserFind) { + webBrowserFind->GetSearchString(getter_Copies(webBrowserFindString)); + if (!webBrowserFindString.Equals(mFindNextBuffer)) { + // If they're not equal, then the find dialog was used last, + // not typeaheadfind. Typeaheadfind applies to the last find, + // so we should let nsIWebBrowserFind::FindNext() do it. + mFindNextBuffer.Truncate(); + return NS_OK; + } + + } + + /* ------------------------------------------------------- + * Typeaheadfind is active in the currently focused window, + * so do the find next operation now + */ + + // Clear out window data, to indicate we handled the findnext + aCallerWindowSupports->SetData(nsnull); + + if (mBadKeysSinceMatch > 0) { + // We know it will fail, so just return + return NS_OK; + } + + mTypeAheadBuffer = mFindNextBuffer; + PRBool repeatingSameChar = PR_FALSE; + + if (mRepeatingMode == eRepeatingChar || + mRepeatingMode == eRepeatingCharReverse) { + mRepeatingMode = aFindBackwards? eRepeatingCharReverse: eRepeatingChar; + repeatingSameChar = PR_TRUE; + } + else { + mRepeatingMode = aFindBackwards? eRepeatingReverse: eRepeatingForward; + } + mLiteralTextSearchOnly = PR_TRUE; + + mIsFindingText = PR_TRUE; // prevent our listeners from calling CancelFind() + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + if (NS_FAILED(FindItNow(nsnull, repeatingSameChar, mLinksOnly, PR_FALSE))) { + DisplayStatus(PR_FALSE, nsnull, PR_FALSE); // Display failure status + mRepeatingMode = eRepeatingNone; + } + + mTypeAheadBuffer.Truncate(); // Find buffer is now in mFindNextBuffer + StartTimeout(); + mIsFindingText = PR_FALSE; + + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::GetIsActive(PRBool *aIsActive) +{ + *aIsActive = mLinksOnlyManuallySet || !mTypeAheadBuffer.IsEmpty(); + + return NS_OK; +} + + +/* + * Start new type ahead find manually + */ + +NS_IMETHODIMP +nsTypeAheadFind::StartNewFind(nsIDOMWindow *aWindow, PRBool aLinksOnly) +{ + if (!mFind || !mIsTypeAheadOn || !aWindow) + return NS_ERROR_FAILURE; // Type Ahead Find not correctly initialized + + // This routine will set up the doc listeners + // Do it first, it does a CancelFind() + UseInWindow(aWindow); + + mLinksOnly = aLinksOnly; + mLinksOnlyManuallySet = PR_TRUE; + mRepeatingMode = eRepeatingNone; + + PRBool isAutoStartWin; + GetAutoStart(mFocusedWindow, &isAutoStartWin); + if (!isAutoStartWin) { + AttachWindowListeners(mFocusedWindow); + } + + if (mFocusedDocSelection) { + mIsFindingText = PR_TRUE; // Turn off side effects from selection listener + mFocusedDocSelection->CollapseToStart(); + mIsFindingText = PR_FALSE; + nsCOMPtr presShell(GetPresShell()); + SetSelectionLook(presShell, PR_TRUE); + } + DisplayStatus(PR_TRUE, nsnull, PR_FALSE); + StartTimeout(); + + return NS_OK; +} + +void +nsTypeAheadFind::ResetGlobalAutoStart(PRBool aAutoStart) +{ + // Enumerate through the current top level windows + // and either attach or remove window listeners + + CancelFind(); + + nsCOMPtr windowWatcher = + do_GetService(NS_WINDOWWATCHER_CONTRACTID); + if (!windowWatcher) { + return; + } + + nsCOMPtr enumerator; + windowWatcher->GetWindowEnumerator(getter_AddRefs(enumerator)); + if (!enumerator) { + return; + } + + PRBool hasMoreWindows; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreWindows)) + && hasMoreWindows) { + nsCOMPtr supports; + enumerator->GetNext(getter_AddRefs(supports)); + nsCOMPtr domWin(do_QueryInterface(supports)); + if (domWin) { + if (aAutoStart) { + AttachWindowListeners(domWin); + } + else { + RemoveWindowListeners(domWin); + } + } + } +} + + +NS_IMETHODIMP +nsTypeAheadFind::SetAutoStart(nsIDOMWindow *aDOMWin, PRBool aAutoStartOn) +{ + if (!aDOMWin) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr windowSupports(do_QueryInterface(aDOMWin)); + PRInt32 index = mManualFindWindows->IndexOf(windowSupports); + + if (aAutoStartOn) { + if (index >= 0) { + // Remove from list of windows requiring manual find + mManualFindWindows->RemoveElementAt(index); + } + } + else { // Should be in list of windows requiring manual find + if (aDOMWin == mFocusedWindow) { + CancelFind(); + } + + if (index < 0) { // Should be in list of windows requiring manual find + mManualFindWindows->InsertElementAt(windowSupports, 0); + } + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::GetAutoStart(nsIDOMWindow *aDOMWin, PRBool *aIsAutoStartOn) +{ + *aIsAutoStartOn = PR_FALSE; + + if (!mAutoStartPref || !aDOMWin) + return NS_OK; + + nsCOMPtr ifreq(do_QueryInterface(aDOMWin)); + NS_ENSURE_TRUE(ifreq, NS_OK); + + nsCOMPtr webNav(do_GetInterface(ifreq)); + nsCOMPtr treeItem(do_QueryInterface(webNav)); + nsCOMPtr editorDocShell(do_QueryInterface(treeItem)); + if (editorDocShell) { + PRBool isEditable; + editorDocShell->GetEditable(&isEditable); + + if (isEditable) { + return NS_OK; + } + } + + nsCOMPtr domDoc; + aDOMWin->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc(do_QueryInterface(domDoc)); + NS_ENSURE_TRUE(doc, NS_OK); + + nsCOMPtr xulDoc(do_QueryInterface(doc)); + nsCOMPtr imageDoc(do_QueryInterface(doc)); + if (xulDoc || imageDoc) { + return NS_OK; // Avoid any xul docs, whether in chrome or content + } + + if (mLinksOnlyPref) { + nsAutoString contentType; + doc->GetContentType(contentType); + if (contentType.EqualsLiteral("text/plain")) { + return NS_OK; // No auto link search in plain text pages + } + } + + nsIDocument *parentDoc = doc->GetParentDocument(); + if (parentDoc) { + // get content for + nsCOMPtr browserElement = + do_QueryInterface(parentDoc->FindContentForSubDocument(doc)); + + if (browserElement) { + nsAutoString tagName, autoFind, test; + browserElement->GetLocalName(tagName); + browserElement->GetAttribute(NS_LITERAL_STRING("type"), test); + browserElement->GetAttribute(NS_LITERAL_STRING("autofind"), autoFind); + if (tagName.EqualsLiteral("editor") || + autoFind.EqualsLiteral("false")) { + return NS_OK; + } + } + } + + // Is this window stored in manual find windows list? + nsCOMPtr windowSupports(do_QueryInterface(aDOMWin)); + *aIsAutoStartOn = mManualFindWindows->IndexOf(windowSupports) < 0; + + return NS_OK; +} + + +NS_IMETHODIMP +nsTypeAheadFind::CancelFind() +{ + // Stop current find if: + // 1. Escape pressed + // 2. Selection is moved/changed + // 3. User clicks in window (if it changes the selection) + // 4. Window scrolls + // 5. User tabs (this can move the selection) + // 6. Timer expires + + if (mLinksOnlyManuallySet == PR_FALSE && mTypeAheadBuffer.IsEmpty()) { + // Nothing to cancel + return NS_OK; + } + + if (mIsTypeAheadOn || mRepeatingMode != eRepeatingNone) { + mTypeAheadBuffer.Truncate(); + DisplayStatus(PR_FALSE, nsnull, PR_TRUE); // Clear status + nsCOMPtr presShell(GetPresShell()); + SetSelectionLook(presShell, PR_FALSE); + } + + // This is set to true if the user types / (all text) or ' (links only) first + mLinksOnlyManuallySet = PR_FALSE; + + // These will be initialized to their true values after + // the first character is typed + mLiteralTextSearchOnly = PR_FALSE; + mDontTryExactMatch = PR_FALSE; + mStartFindRange = nsnull; + mBadKeysSinceMatch = 0; + mIsBackspaceProtectOn = PR_FALSE; + mLastBadChar = 0; + mAllTheSameChar = PR_TRUE; // Until at least 2 different chars are typed + + if (mTimer) { + mTimer->Cancel(); + mTimer = nsnull; + } + + PRBool isAutoStartWin; + GetAutoStart(mFocusedWindow, &isAutoStartWin); + if (!isAutoStartWin) { + RemoveDocListeners(); + RemoveWindowListeners(mFocusedWindow); + mIsFindAllowedInWindow = PR_FALSE; + mFocusedWindow = nsnull; + } + + return NS_OK; +} + + +// ------- Helper Methods --------------- + +void +nsTypeAheadFind::GetTopContentPresShell(nsIDocShellTreeItem *aDocShellTreeItem, + nsIPresShell **aPresShell) +{ + *aPresShell = nsnull; + + nsCOMPtr topContentTreeItem; + aDocShellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(topContentTreeItem)); + nsCOMPtr topContentDocShell(do_QueryInterface(topContentTreeItem)); + + if (!topContentDocShell) + return; + + topContentDocShell->GetPresShell(aPresShell); +} + +void +nsTypeAheadFind::GetStartWindow(nsIDOMWindow *aWindow, nsIDOMWindow **aStartWindow) +{ + // Return the root ancestor content window of aWindow + + *aStartWindow = nsnull; + nsCOMPtr ifreq(do_QueryInterface(aWindow)); + NS_ASSERTION(ifreq, "Can't get interface requestor"); + if (!ifreq) + return; + + nsCOMPtr webNav(do_GetInterface(ifreq)); + nsCOMPtr treeItem(do_QueryInterface(webNav)); + NS_ASSERTION(ifreq, "Can't get doc shell tree item"); + if (!treeItem) + return; + + PRInt32 docShellType; + treeItem->GetItemType(&docShellType); + if (docShellType == nsIDocShellTreeItem::typeContent) { + nsCOMPtr rootContentTreeItem; + treeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem)); + nsCOMPtr domWin(do_GetInterface(rootContentTreeItem)); + *aStartWindow = domWin; + } + else { + *aStartWindow = aWindow; + } + + NS_IF_ADDREF(*aStartWindow); +} + +nsresult +nsTypeAheadFind::GetWebBrowserFind(nsIDOMWindow *aWin, + nsIWebBrowserFind **aWebBrowserFind) +{ + NS_ENSURE_ARG_POINTER(aWin); + NS_ENSURE_ARG_POINTER(aWebBrowserFind); + + *aWebBrowserFind = nsnull; + + nsCOMPtr ifreq(do_QueryInterface(aWin)); + NS_ENSURE_TRUE(ifreq, NS_ERROR_FAILURE); + + nsCOMPtr webNav(do_GetInterface(ifreq)); + nsCOMPtr docShell(do_QueryInterface(webNav)); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + nsCOMPtr webBrowserFind(do_GetInterface(docShell)); + NS_ENSURE_TRUE(webBrowserFind, NS_ERROR_FAILURE); + + NS_ADDREF(*aWebBrowserFind = webBrowserFind); + + return NS_OK; +} + + +void +nsTypeAheadFind::StartTimeout() +{ + if (mTimeoutLength) { + if (!mTimer) { + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mTimer) { + mTimer->InitWithCallback(this, mTimeoutLength, nsITimer::TYPE_ONE_SHOT); + } + } + else { + mTimer->SetDelay(mTimeoutLength); + } + } +} + +void +nsTypeAheadFind::SetSelectionLook(nsIPresShell *aPresShell, + PRBool aChangeColor) +{ + if (!aPresShell || !mFocusedDocSelCon) + return; + + // Paint selection bright (typeaheadfind on) or normal + // (typeaheadfind off) + + if (aChangeColor) { + mFocusedDocSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ATTENTION); + } else { + mFocusedDocSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); + } + + mFocusedDocSelCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL); +} + + +void +nsTypeAheadFind::RemoveDocListeners() +{ + nsCOMPtr presShell(GetPresShell()); + nsIViewManager* vm = nsnull; + + if (presShell) { + vm = presShell->GetViewManager(); + } + + nsIScrollableView* scrollableView = nsnull; + if (vm) { + vm->GetRootScrollableView(&scrollableView); + } + + if (scrollableView) { + scrollableView->RemoveScrollPositionListener(this); + } + + mFocusedWeakShell = nsnull; + + // Remove selection listener + nsCOMPtr selPrivate = + do_QueryInterface(mFocusedDocSelection); + + if (selPrivate) { + selPrivate->RemoveSelectionListener(this); // remove us if we're a listener + } + + mFocusedDocSelection = nsnull; + mFocusedDocSelCon = nsnull; // Selection controller owns pres shell! +} + + +void +nsTypeAheadFind::AttachDocListeners(nsIPresShell *aPresShell) +{ + if (!aPresShell) { + return; + } + + nsIViewManager* vm = aPresShell->GetViewManager(); + if (!vm) { + return; + } + + nsIScrollableView* scrollableView = nsnull; + vm->GetRootScrollableView(&scrollableView); + if (!scrollableView) { + return; + } + + scrollableView->AddScrollPositionListener(this); + + // Attach selection listener + nsCOMPtr selPrivate = + do_QueryInterface(mFocusedDocSelection); + + if (selPrivate) { + selPrivate->AddSelectionListener(this); + } +} + + +void +nsTypeAheadFind::RemoveWindowListeners(nsIDOMWindow *aDOMWin) +{ + nsCOMPtr chromeEventHandler; + GetChromeEventHandler(aDOMWin, getter_AddRefs(chromeEventHandler)); + if (!chromeEventHandler) { + return; + } + + // Use capturing, otherwise the normal find next will get activated when ours should + nsCOMPtr piTarget(do_QueryInterface(chromeEventHandler)); + nsCOMPtr systemGroup; + piTarget->GetSystemEventGroup(getter_AddRefs(systemGroup)); + nsCOMPtr target(do_QueryInterface(piTarget)); + + target->RemoveGroupedEventListener(NS_LITERAL_STRING("keypress"), + static_cast(this), + PR_FALSE, systemGroup); + + if (aDOMWin == mFocusedWindow) { + mFocusedWindow = nsnull; + } + + // Remove menu listeners + nsIDOMEventListener *genericEventListener = + static_cast(static_cast(this)); + + chromeEventHandler->RemoveEventListener(NS_LITERAL_STRING("popupshown"), + genericEventListener, + PR_TRUE); + + chromeEventHandler->RemoveEventListener(NS_LITERAL_STRING("popuphidden"), + genericEventListener, + PR_TRUE); + + chromeEventHandler->RemoveEventListener(NS_LITERAL_STRING("DOMMenuBarActive"), + genericEventListener, + PR_TRUE); + + chromeEventHandler->RemoveEventListener(NS_LITERAL_STRING("DOMMenuBarInactive"), + genericEventListener, + PR_TRUE); + + chromeEventHandler->RemoveEventListener(NS_LITERAL_STRING("unload"), + genericEventListener, + PR_TRUE); + + // Remove DOM Text listener for IME text events + piTarget->RemoveEventListenerByIID(static_cast(this), + NS_GET_IID(nsIDOMTextListener)); + piTarget->RemoveEventListenerByIID(static_cast(this), + NS_GET_IID(nsIDOMCompositionListener)); +} + + +void +nsTypeAheadFind::AttachWindowListeners(nsIDOMWindow *aDOMWin) +{ + nsCOMPtr chromeEventHandler; + GetChromeEventHandler(aDOMWin, getter_AddRefs(chromeEventHandler)); + if (!chromeEventHandler) { + return; + } + + // Use capturing, otherwise the normal find next will get activated when ours should + nsCOMPtr piTarget(do_QueryInterface(chromeEventHandler)); + nsCOMPtr systemGroup; + piTarget->GetSystemEventGroup(getter_AddRefs(systemGroup)); + nsCOMPtr target(do_QueryInterface(piTarget)); + + target->AddGroupedEventListener(NS_LITERAL_STRING("keypress"), + static_cast(this), + PR_FALSE, systemGroup); + + // Attach menu listeners, this will help us ignore keystrokes meant for menus + nsIDOMEventListener *genericEventListener = + static_cast(static_cast(this)); + + chromeEventHandler->AddEventListener(NS_LITERAL_STRING("popupshown"), + genericEventListener, + PR_TRUE); + + chromeEventHandler->AddEventListener(NS_LITERAL_STRING("popuphidden"), + genericEventListener, + PR_TRUE); + + chromeEventHandler->AddEventListener(NS_LITERAL_STRING("DOMMenuBarActive"), + genericEventListener, + PR_TRUE); + + chromeEventHandler->AddEventListener(NS_LITERAL_STRING("DOMMenuBarInactive"), + genericEventListener, + PR_TRUE); + + chromeEventHandler->AddEventListener(NS_LITERAL_STRING("unload"), + genericEventListener, + PR_TRUE); + + // Add DOM Text listener for IME text events + piTarget->AddEventListenerByIID(static_cast(this), + NS_GET_IID(nsIDOMTextListener)); + piTarget->AddEventListenerByIID(static_cast(this), + NS_GET_IID(nsIDOMCompositionListener)); +} + + +void +nsTypeAheadFind::GetChromeEventHandler(nsIDOMWindow *aDOMWin, + nsIDOMEventTarget **aChromeTarget) +{ + nsCOMPtr privateDOMWindow(do_QueryInterface(aDOMWin)); + nsPIDOMEventTarget* chromeEventHandler = nsnull; + if (privateDOMWindow) { + chromeEventHandler = privateDOMWindow->GetChromeEventHandler(); + } + + nsCOMPtr target(do_QueryInterface(chromeEventHandler)); + + *aChromeTarget = target; + NS_IF_ADDREF(*aChromeTarget); +} + +PRBool +nsTypeAheadFind::IsTargetContentOkay(nsIContent *aContent) +{ + if (!aContent) { + return PR_FALSE; + } + + if (aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) { + nsCOMPtr formControl(do_QueryInterface(aContent)); + PRInt32 controlType = formControl->GetType(); + if (controlType == NS_FORM_SELECT || + controlType == NS_FORM_TEXTAREA || + controlType == NS_FORM_INPUT_TEXT || + controlType == NS_FORM_INPUT_PASSWORD || + controlType == NS_FORM_INPUT_FILE) { + // Don't steal keys from these form controls + // - selects have their own incremental find for options + // - text fields need to allow typing + return PR_FALSE; + } + } + else if (aContent->IsNodeOfType(nsINode::eHTML)) { + // Test for isindex, a deprecated kind of text field. We're using a string + // compare because is not considered a form control, so it does + // not support nsIFormControl or eHTML_FORM_CONTROL, and it's not worth + // having a table of atoms just for it. Instead, we're paying for 1 extra + // string compare per keystroke, which isn't too bad. + const char *tagStr; + aContent->Tag()->GetUTF8String(&tagStr); + if (strcmp(tagStr, "isindex") == 0) { + return PR_FALSE; + } + } + + return PR_TRUE; +} + + +nsresult +nsTypeAheadFind::GetTargetIfTypeAheadOkay(nsIDOMEvent *aEvent, + nsIContent **aTargetContent, + nsIPresShell **aTargetPresShell) +{ + NS_ENSURE_ARG_POINTER(aEvent); + NS_ENSURE_ARG_POINTER(aTargetContent); + NS_ENSURE_ARG_POINTER(aTargetPresShell); + + *aTargetContent = nsnull; + *aTargetPresShell = nsnull; + + nsCOMPtr nsEvent(do_QueryInterface(aEvent)); + if (!nsEvent) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr domEventTarget; + nsEvent->GetOriginalTarget(getter_AddRefs(domEventTarget)); + + nsCOMPtr targetContent(do_QueryInterface(domEventTarget)); + + // ---- Exit early if in form controls that can be typed in --------- + + if (!IsTargetContentOkay(targetContent)) { + if (!mTypeAheadBuffer.IsEmpty()) { + CancelFind(); + } + return NS_OK; + } + + NS_ADDREF(*aTargetContent = targetContent); + + // ---------- Is the keystroke in a new window? ------------------- + + nsCOMPtr doc = targetContent->GetDocument(); + if (!doc) { + return NS_OK; + } + + nsIDOMWindow *domWin = doc->GetWindow(); + nsCOMPtr topContentWin; + GetStartWindow(domWin, getter_AddRefs(topContentWin)); + + // ---------- Get presshell ----------- + + nsIPresShell *presShell = doc->GetPrimaryShell(); + if (!presShell) { + return NS_OK; + } + + nsCOMPtr lastShell(GetPresShell()); + + if (lastShell != presShell || topContentWin != mFocusedWindow) { + GetAutoStart(topContentWin, &mIsFindAllowedInWindow); + if (mIsFindAllowedInWindow) { + UseInWindow(topContentWin); + } + else { + CancelFind(); + mFocusedWindow = nsnull; + } + } + if (!mIsFindAllowedInWindow) { + return NS_OK; + } + + if (presShell->GetPresContext()->Type() == nsPresContext::eContext_PrintPreview) { + // Typeaheadfind is not designed to work in print preview. + // You can't navigate through the links there. + if (lastShell != presShell) { + mFocusedWeakShell = do_GetWeakReference(presShell); + CancelFind(); + DisplayStatus(PR_FALSE, nsnull, PR_TRUE, EmptyString().get()); // Clear status + } + return NS_OK; + } + + NS_ADDREF(*aTargetPresShell = presShell); + return NS_OK; +} + + +void +nsTypeAheadFind::GetSelection(nsIPresShell *aPresShell, + nsISelectionController **aSelCon, + nsISelection **aDOMSel) +{ + // if aCurrentNode is nsnull, get selection for document + *aDOMSel = nsnull; + + nsPresContext *presContext = aPresShell->GetPresContext(); + + nsIFrame *frame = aPresShell->GetRootFrame(); + + if (presContext && frame) { + frame->GetSelectionController(presContext, aSelCon); + if (*aSelCon) { + (*aSelCon)->GetSelection(nsISelectionController::SELECTION_NORMAL, + aDOMSel); + } + } +} + + +PRBool +nsTypeAheadFind::IsRangeVisible(nsIPresShell *aPresShell, + nsPresContext *aPresContext, + nsIDOMRange *aRange, PRBool aMustBeInViewPort, + PRBool aGetTopVisibleLeaf, + nsIDOMRange **aFirstVisibleRange, + PRBool *aUsesIndependentSelection) +{ + NS_ASSERTION(aPresShell && aPresContext && aRange && aFirstVisibleRange, + "params are invalid"); + + // We need to know if the range start is visible. + // Otherwise, return a the first visible range start + // in aFirstVisibleRange + + aRange->CloneRange(aFirstVisibleRange); + nsCOMPtr node; + aRange->GetStartContainer(getter_AddRefs(node)); + + nsCOMPtr content(do_QueryInterface(node)); + if (!content) + return PR_FALSE; + + nsIFrame *frame = aPresShell->GetPrimaryFrameFor(content); + if (!frame) + return PR_FALSE; // No frame! Not visible then. + + if (!frame->GetStyleVisibility()->IsVisible()) + return PR_FALSE; + + // Detect if we are _inside_ a text control, or something else with its own + // selection controller. + if (aUsesIndependentSelection) { + *aUsesIndependentSelection = + (frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION); + } + + // ---- We have a frame ---- + if (!aMustBeInViewPort) + return PR_TRUE; // Don't need it to be on screen, just in rendering tree + + // Get the next in flow frame that contains the range start + PRInt32 startRangeOffset, startFrameOffset, endFrameOffset; + aRange->GetStartOffset(&startRangeOffset); + while (PR_TRUE) { + frame->GetOffsets(startFrameOffset, endFrameOffset); + if (startRangeOffset < endFrameOffset) + break; + + nsIFrame *nextContinuationFrame = frame->GetNextContinuation(); + if (nextContinuationFrame) + frame = nextContinuationFrame; + else + break; + } + + // Set up the variables we need, return true if we can't get at them all + const PRUint16 kMinPixels = 12; + PRUint16 minPixels = nsPresContext::CSSPixelsToAppUnits(kMinPixels); + + nsIViewManager* viewManager = aPresShell->GetViewManager(); + if (!viewManager) + return PR_TRUE; + + // Get the bounds of the current frame, relative to the current view. + // We don't use the more accurate AccGetBounds, because that is + // more expensive and the STATE_OFFSCREEN flag that this is used + // for only needs to be a rough indicator + nsIView *containingView = nsnull; + nsPoint frameOffset; + nsRectVisibility rectVisibility = nsRectVisibility_kAboveViewport; + + if (!aGetTopVisibleLeaf) { + nsRect relFrameRect = frame->GetRect(); + frame->GetOffsetFromView(frameOffset, &containingView); + if (!containingView) + return PR_FALSE; // no view -- not visible + + relFrameRect.x = frameOffset.x; + relFrameRect.y = frameOffset.y; + + viewManager->GetRectVisibility(containingView, relFrameRect, + minPixels, &rectVisibility); + + if (rectVisibility != nsRectVisibility_kAboveViewport && + rectVisibility != nsRectVisibility_kZeroAreaRect) { + return PR_TRUE; + } + } + + // We know that the target range isn't usable because it's not in the + // view port. Move range forward to first visible point, + // this speeds us up a lot in long documents + nsCOMPtr frameTraversal; + nsCOMPtr trav(do_CreateInstance(kFrameTraversalCID)); + if (trav) + trav->NewFrameTraversal(getter_AddRefs(frameTraversal), + aPresContext, frame, + eLeaf, + PR_FALSE, // aVisual + PR_FALSE, // aLockInScrollView + PR_FALSE // aFollowOOFs + ); + + if (!frameTraversal) + return PR_FALSE; + + while (rectVisibility == nsRectVisibility_kAboveViewport || rectVisibility == nsRectVisibility_kZeroAreaRect) { + frameTraversal->Next(); + frame = frameTraversal->CurrentItem(); + if (!frame) + return PR_FALSE; + + nsRect relFrameRect = frame->GetRect(); + frame->GetOffsetFromView(frameOffset, &containingView); + if (containingView) { + relFrameRect.x = frameOffset.x; + relFrameRect.y = frameOffset.y; + viewManager->GetRectVisibility(containingView, relFrameRect, + minPixels, &rectVisibility); + } + } + + if (frame) { + nsCOMPtr firstVisibleNode = do_QueryInterface(frame->GetContent()); + + if (firstVisibleNode) { + (*aFirstVisibleRange)->SelectNode(firstVisibleNode); + frame->GetOffsets(startFrameOffset, endFrameOffset); + (*aFirstVisibleRange)->SetStart(firstVisibleNode, startFrameOffset); + (*aFirstVisibleRange)->Collapse(PR_TRUE); // Collapse to start + } + } + + return PR_FALSE; +} + + +nsresult +nsTypeAheadFind::GetTranslatedString(const nsAString& aKey, + nsAString& aStringOut) +{ + nsXPIDLString xsValue; + + if (!mStringBundle || + NS_FAILED(mStringBundle->GetStringFromName(PromiseFlatString(aKey).get(), + getter_Copies(xsValue)))) { + return NS_ERROR_FAILURE; + } + + aStringOut.Assign(xsValue); + + return NS_OK; +} + + +void +nsTypeAheadFind::DisplayStatus(PRBool aSuccess, nsIContent *aFocusedContent, + PRBool aClearStatus, const PRUnichar *aText) +{ + // pres shell -> pres context -> container -> tree item -> + // tree owner -> browser chrome + + nsCOMPtr presShell(GetPresShell()); + if (!presShell) { + return; + } + + nsPresContext *presContext = presShell->GetPresContext(); + if (!presContext) { + return; + } + + nsCOMPtr pcContainer = presContext->GetContainer(); + nsCOMPtr treeItem(do_QueryInterface(pcContainer)); + if (!treeItem) { + return; + } + + nsCOMPtr treeOwner; + treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); + if (!treeOwner) { + return; + } + + nsCOMPtr browserChrome(do_GetInterface(treeOwner)); + if (!browserChrome) { + return; + } + + nsAutoString statusString; + if (aText) + statusString = aText; + else { + if (aClearStatus) { + GetTranslatedString(NS_LITERAL_STRING("stopfind"), statusString); + } else if (aSuccess && mTypeAheadBuffer.IsEmpty()) { + // When find has been started manually + // but no characters have been typed yet + nsAutoString key; + + if (mLinksOnly) { + key.AssignLiteral("startlinkfind"); + } else { + key.AssignLiteral("starttextfind"); + } + GetTranslatedString(key, statusString); + } else { + nsAutoString key; + + if (mLinksOnly) { + key.AssignLiteral("link"); + } else { + key.AssignLiteral("text"); + } + + if (!aSuccess) { + key.AppendLiteral("not"); + } + + key.AppendLiteral("found"); + + if (NS_SUCCEEDED(GetTranslatedString(key, statusString))) { + if (mRepeatingMode == eRepeatingChar || + mRepeatingMode == eRepeatingCharReverse) { + statusString += mTypeAheadBuffer.First(); + } + else { + statusString += mTypeAheadBuffer; + } + + nsAutoString closeQuoteString, urlString; + GetTranslatedString(NS_LITERAL_STRING("closequote"), closeQuoteString); + statusString += closeQuoteString; + + if (mRepeatingMode != eRepeatingNone) { + if (mRepeatingMode == eRepeatingChar) { + key.AssignLiteral("repeated"); + } + else if (mRepeatingMode == eRepeatingForward) { + key.AssignLiteral("nextmatch"); + } + else { + key.AssignLiteral("prevmatch"); + } + nsAutoString repeatedModeString; + GetTranslatedString(key, repeatedModeString); + statusString += NS_LITERAL_STRING(" ") + repeatedModeString; + } + + nsCOMPtr focusedNode(do_QueryInterface(aFocusedContent)); + if (focusedNode) { + presShell->GetLinkLocation(focusedNode, urlString); + } + + if (!urlString.IsEmpty()) { // Add URL in parenthesis + nsAutoString openParenString, closeParenString; + GetTranslatedString(NS_LITERAL_STRING("openparen"), openParenString); + GetTranslatedString(NS_LITERAL_STRING("closeparen"), closeParenString); + statusString += NS_LITERAL_STRING(" ") + openParenString + + urlString + closeParenString; + } + } + } + } + + browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, + PromiseFlatString(statusString).get()); +} + + +// ------- nsTypeAheadController --------------- + +const char * const sLinkFindString = "cmd_findTypeLinks"; +const char * const sTextFindString = "cmd_findTypeText"; + +NS_IMPL_ISUPPORTS1(nsTypeAheadController, nsIController) + +nsTypeAheadController::nsTypeAheadController(nsIFocusController *aFocusController): + mFocusController(aFocusController) +{ +} + +nsTypeAheadController::~nsTypeAheadController() +{ +} + +NS_IMETHODIMP +nsTypeAheadController::IsCommandEnabled(const char *aCommand, PRBool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = PR_FALSE; + + NS_ENSURE_TRUE(mFocusController, NS_ERROR_FAILURE); + + nsCOMPtr focusedElement; + mFocusController->GetFocusedElement(getter_AddRefs(focusedElement)); + + nsCOMPtr focusedContent(do_QueryInterface(focusedElement)); + + // Make sure we're not focused on a text field, listbox + // or other form control that needs typeahead keystrokes + if (focusedContent) { + *aResult = nsTypeAheadFind::IsTargetContentOkay(focusedContent); + return NS_OK; + } + + // We're focused on a document + nsCOMPtr winInternal; + mFocusController->GetFocusedWindow(getter_AddRefs(winInternal)); + nsCOMPtr domWin(do_QueryInterface(winInternal)); + if (!domWin) { + return NS_OK; + } + + *aResult = PR_TRUE; + + // Make sure we're not in Midas editing mode + nsCOMPtr domDoc; + domWin->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr htmlDoc(do_QueryInterface(domDoc)); + if (htmlDoc) { + nsAutoString designMode; + htmlDoc->GetDesignMode(designMode); + if (designMode.EqualsLiteral("on")) { + *aResult = PR_FALSE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadController::SupportsCommand(const char *aCommand, PRBool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = PR_FALSE; + + if (!nsCRT::strcmp(sLinkFindString, aCommand) || + !nsCRT::strcmp(sTextFindString, aCommand)) { + *aResult = PR_TRUE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTypeAheadController::DoCommand(const char *aCommand) +{ + PRBool isLinkSearch = PR_FALSE; + + if (nsCRT::strcmp(sLinkFindString, aCommand) == 0) { + isLinkSearch = PR_TRUE; + } + else if (nsCRT::strcmp(sTextFindString, aCommand) != 0) { + return NS_OK; + } + + NS_ENSURE_TRUE(mFocusController, NS_ERROR_FAILURE); + + nsCOMPtr domWinInternal; + mFocusController->GetFocusedWindow(getter_AddRefs(domWinInternal)); + + nsCOMPtr startContentWin; + EnsureContentWindow(domWinInternal, getter_AddRefs(startContentWin)); + NS_ENSURE_TRUE(startContentWin, NS_ERROR_FAILURE); + + nsCOMPtr typeAhead = + do_GetService(NS_TYPEAHEADFIND_CONTRACTID); + NS_ENSURE_TRUE(typeAhead, NS_ERROR_FAILURE); + + return typeAhead->StartNewFind(startContentWin, isLinkSearch); +} + +/* void onEvent (in string eventName); */ +NS_IMETHODIMP +nsTypeAheadController::OnEvent(const char *eventName) +{ + return NS_OK; +} + +nsresult +nsTypeAheadController::EnsureContentWindow(nsIDOMWindowInternal *aFocusedWin, + nsIDOMWindow **aStartContentWin) +{ + NS_ENSURE_ARG_POINTER(aFocusedWin); + NS_ENSURE_ARG_POINTER(aStartContentWin); + + *aStartContentWin = nsnull; + nsCOMPtr ifreq(do_QueryInterface(aFocusedWin)); + NS_ENSURE_TRUE(ifreq, NS_OK); + + nsCOMPtr webNav(do_GetInterface(ifreq)); + nsCOMPtr treeItem(do_QueryInterface(webNav)); + NS_ENSURE_TRUE(treeItem, NS_OK); + PRInt32 treeItemType; + treeItem->GetItemType(&treeItemType); + + nsCOMPtr startContentWin; + if (treeItemType == nsIDocShellTreeItem::typeContent) { + startContentWin = do_QueryInterface(aFocusedWin); + } + else { + // Use enumerator tet first content docshell + nsCOMPtr docShell(do_QueryInterface(webNav)); + NS_ENSURE_TRUE(docShell, NS_OK); + + nsCOMPtr docShellEnumerator; + docShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(docShellEnumerator)); + + PRBool hasMoreDocShells; + if (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) + && hasMoreDocShells) { + + // There is a content docshell child, let's use it (focus it and return it) + nsCOMPtr container; + docShellEnumerator->GetNext(getter_AddRefs(container)); + nsCOMPtr ifreq(do_QueryInterface(container)); + + if (ifreq) { + startContentWin = do_GetInterface(ifreq); + NS_ENSURE_TRUE(startContentWin, NS_ERROR_FAILURE); + + // Set new focus in root content of new window + // This only happens if we weren't already in content + // Using nsIContent to focus makes sure the + // previous window's focused content gets blurred properly + nsCOMPtr domDoc; + startContentWin->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc(do_QueryInterface(domDoc)); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsCOMPtr docShell(do_QueryInterface(container)); + nsCOMPtr presContext; + docShell->GetPresContext(getter_AddRefs(presContext)); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + nsIContent *rootContent = doc->GetRootContent(); + NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE); + rootContent->SetFocus(presContext); + } + } + } + + *aStartContentWin = startContentWin; + NS_IF_ADDREF(*aStartContentWin); + return NS_OK; +} + +already_AddRefed +nsTypeAheadFind::GetPresShell() +{ + if (!mFocusedWeakShell) + return nsnull; + + nsIPresShell *shell = nsnull; + CallQueryReferent(mFocusedWeakShell.get(), &shell); + if (shell) { + nsPresContext *pc = shell->GetPresContext(); + if (!pc || !nsCOMPtr(pc->GetContainer())) { + NS_RELEASE(shell); + } + } + + return shell; +} diff --git a/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFind.h b/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFind.h new file mode 100644 index 00000000..278188b4 --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFind.h @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Original Author: Aaron Leventhal + * + * 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 "nsIDOMEventListener.h" +#include "nsIDOMKeyListener.h" +#include "nsIDOMTextListener.h" +#include "nsIDOMCompositionListener.h" +#include "nsIScrollPositionListener.h" +#include "nsISelectionListener.h" +#include "nsISelectionController.h" +#include "nsIController.h" +#include "nsIControllers.h" +#include "nsIFocusController.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsUnicharUtils.h" +#include "nsIFindService.h" +#include "nsIFind.h" +#include "nsIWebBrowserFind.h" +#include "nsWeakReference.h" +#include "nsIAppStartupNotifier.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsISelection.h" +#include "nsIDOMRange.h" +#include "nsIDOMWindow.h" +#include "nsIDocShellTreeItem.h" +#include "nsITypeAheadFind.h" +#include "nsIStringBundle.h" +#include "nsISupportsArray.h" +#include "nsISound.h" + +#define TYPEAHEADFIND_BUNDLE_URL \ + "chrome://communicator/locale/typeaheadfind.properties" +#define TYPEAHEADFIND_NOTFOUND_WAV_URL \ + "chrome://global/content/notfound.wav" + +enum { + eRepeatingNone, + eRepeatingChar, + eRepeatingCharReverse, + eRepeatingForward, + eRepeatingReverse +}; + +const int kMaxBadCharsBeforeCancel = 3; + +class nsTypeAheadFind : public nsISuiteTypeAheadFind, + public nsIDOMKeyListener, + public nsIDOMTextListener, + public nsIDOMCompositionListener, + public nsIObserver, + public nsIScrollPositionListener, + public nsISelectionListener, + public nsITimerCallback, + public nsSupportsWeakReference +{ +public: + nsTypeAheadFind(); + virtual ~nsTypeAheadFind(); + + NS_DEFINE_STATIC_CID_ACCESSOR(NS_TYPEAHEADFIND_CID) + + NS_DECL_ISUPPORTS + NS_DECL_NSISUITETYPEAHEADFIND + NS_DECL_NSIOBSERVER + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_NSISELECTIONLISTENER + + // ----- nsIDOMKeyListener ---------------------------- + NS_IMETHOD KeyDown(nsIDOMEvent* aKeyEvent); + NS_IMETHOD KeyUp(nsIDOMEvent* aKeyEvent); + NS_IMETHOD KeyPress(nsIDOMEvent* aKeyEvent); + + // ----- nsIDOMTextListener ---------------------------- + NS_IMETHOD HandleText(nsIDOMEvent* aTextEvent); + + // ----- nsIDOMCompositionListener ---------------------------- + NS_IMETHOD HandleStartComposition(nsIDOMEvent* aCompositionEvent); + NS_IMETHOD HandleEndComposition(nsIDOMEvent* aCompositionEvent); + NS_IMETHOD HandleQueryComposition(nsIDOMEvent* aCompositionEvent); + NS_IMETHOD HandleQueryReconversion(nsIDOMEvent* aCompositionEvent); + NS_IMETHOD HandleQueryCaretRect(nsIDOMEvent* aCompositionEvent); + + // ----- nsIScrollPositionListener -------------------- + NS_IMETHOD ScrollPositionWillChange(nsIScrollableView *aView, + nscoord aX, nscoord aY); + virtual void ViewPositionDidChange(nsIScrollableView *aView) {} + NS_IMETHOD ScrollPositionDidChange(nsIScrollableView *aView, + nscoord aX, nscoord aY); + + // ----- nsITimerCallback ----------------------------- + NS_DECL_NSITIMERCALLBACK + + static nsTypeAheadFind *GetInstance(); + static void ReleaseInstance(void); + static PRBool IsTargetContentOkay(nsIContent *aContent); + +protected: + nsresult PrefsReset(); + + // Helper methods + nsresult HandleChar(PRUnichar aChar); + PRBool HandleBackspace(); + void SaveFind(); + void PlayNotFoundSound(); + void GetTopContentPresShell(nsIDocShellTreeItem *aTreeItem, + nsIPresShell **aPresShell); + void GetStartWindow(nsIDOMWindow *aWindow, nsIDOMWindow **aStartWindow); + nsresult GetWebBrowserFind(nsIDOMWindow *aDOMWin, + nsIWebBrowserFind **aWebBrowserFind); + void StartTimeout(); + nsresult Init(); + void Shutdown(); + nsresult UseInWindow(nsIDOMWindow *aDomWin); + void SetSelectionLook(nsIPresShell *aPresShell, PRBool aChangeColor); + void ResetGlobalAutoStart(PRBool aAutoStart); + void AttachDocListeners(nsIPresShell *aPresShell); + void RemoveDocListeners(); + void AttachWindowListeners(nsIDOMWindow *aDOMWin); + void RemoveWindowListeners(nsIDOMWindow *aDOMWin); + void GetChromeEventHandler(nsIDOMWindow *aDOMWin, + nsIDOMEventTarget **aChromeTarget); + + void RangeStartsInsideLink(nsIDOMRange *aRange, nsIPresShell *aPresShell, + PRBool *aIsInsideLink, PRBool *aIsStartingLink); + + nsresult GetTargetIfTypeAheadOkay(nsIDOMEvent *aEvent, + nsIContent **aTargetContent, + nsIPresShell **aTargetPresShell); + + // Get selection and selection controller for current pres shell + void GetSelection(nsIPresShell *aPresShell, nsISelectionController **aSelCon, + nsISelection **aDomSel); + PRBool IsRangeVisible(nsIPresShell *aPresShell, nsPresContext *aPresContext, + nsIDOMRange *aRange, PRBool aMustBeVisible, + PRBool aGetTopVisibleLeaf, nsIDOMRange **aNewRange, + PRBool *aUsesIndependentSelection); + nsresult FindItNow(nsIPresShell *aPresShell, PRBool aIsRepeatingSameChar, + PRBool aIsLinksOnly, PRBool aIsFirstVisiblePreferred); + nsresult GetSearchContainers(nsISupports *aContainer, + PRBool aIsRepeatingSameChar, + PRBool aIsFirstVisiblePreferred, + PRBool aCanUseDocSelection, + nsIPresShell **aPresShell, + nsPresContext **aPresContext); + void DisplayStatus(PRBool aSuccess, nsIContent *aFocusedContent, + PRBool aClearStatus, const PRUnichar *aText = nsnull); + nsresult GetTranslatedString(const nsAString& aKey, nsAString& aStringOut); + + // Get the pres shell from mFocusedWeakShell and return it only if it is + // still attached to the DOM window. + NS_HIDDEN_(already_AddRefed) GetPresShell(); + + // Used by GetInstance and ReleaseInstance + static nsTypeAheadFind *sInstance; + + // Current find state + nsString mTypeAheadBuffer; + nsString mFindNextBuffer; + nsString mIMEString; + + nsCString mNotFoundSoundURL; + + // PRBool's are used instead of PRPackedBool's where the address of the + // boolean variable is getting passed into a method. For example: + // GetBoolPref("accessibility.typeaheadfind.linksonly", &mLinksOnlyPref); + PRBool mIsFindAllowedInWindow; + PRBool mAutoStartPref; + PRBool mLinksOnlyPref; + PRBool mStartLinksOnlyPref; + PRPackedBool mLinksOnly; + PRBool mIsTypeAheadOn; + PRBool mCaretBrowsingOn; + PRPackedBool mLiteralTextSearchOnly; + PRPackedBool mDontTryExactMatch; + // mAllTheSame Char starts out PR_TRUE, becomes false when + // at least 2 different chars typed + PRPackedBool mAllTheSameChar; + // mLinksOnlyManuallySet = PR_TRUE when the user has already + // typed / or '. This allows the next / or ' to get searched for. + PRPackedBool mLinksOnlyManuallySet; + // mIsFindingText = PR_TRUE when we need to prevent listener callbacks + // from resetting us during typeahead find processing + PRPackedBool mIsFindingText; + PRPackedBool mIsMenuBarActive; + PRPackedBool mIsMenuPopupActive; + PRPackedBool mIsFirstVisiblePreferred; + PRPackedBool mIsIMETypeAheadActive; + PRPackedBool mIsBackspaceProtectOn; // from accidentally going back in history + PRInt32 mBadKeysSinceMatch; + PRUnichar mLastBadChar; // if taf automatically overwrites an unfound character + PRInt32 mRepeatingMode; + PRInt32 mTimeoutLength; // time in ms before find is automatically cancelled + + // Sound is played asynchronously on some platforms. + // If we destroy mSoundInterface before sound has played, it won't play + nsCOMPtr mSoundInterface; + PRBool mIsSoundInitialized; + + static PRInt32 sAccelKey; // magic value of -1 indicates unitialized state + + // where selection was when user started the find + nsCOMPtr mStartFindRange; + nsCOMPtr mSearchRange; + nsCOMPtr mStartPointRange; + nsCOMPtr mEndPointRange; + + // Cached useful interfaces + nsCOMPtr mFind; + nsCOMPtr mFindService; + nsCOMPtr mStringBundle; + nsCOMPtr mTimer; + nsCOMPtr mFocusController; + + // The focused content window that we're listening to and it's cached objects + nsCOMPtr mFocusedDocSelection; + nsCOMPtr mFocusedDocSelCon; + nsCOMPtr mFocusedWindow; + nsCOMPtr mFocusedWeakShell; + + // Windows where typeaheadfind doesn't auto start as the user types + nsCOMPtr mManualFindWindows; + + // + // NOTE: if you add strong ref members here please make sure to null + // them out in Shutdown() to prevent crashes like bug 414559. + // +}; + + +class nsTypeAheadController : public nsIController +{ +public: + nsTypeAheadController(nsIFocusController *aFocusController); + virtual ~nsTypeAheadController(); + NS_DECL_ISUPPORTS + NS_DECL_NSICONTROLLER + +private: + nsCOMPtr mFocusController; + nsresult EnsureContentWindow(nsIDOMWindowInternal *aFocusedWin, + nsIDOMWindow **aStartContentWin); +}; diff --git a/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFindRegistration.cpp b/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFindRegistration.cpp new file mode 100644 index 00000000..6593998f --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/src/nsTypeAheadFindRegistration.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Original Author: Aaron Leventhal (aaronl@netscape.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * 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 "nsIGenericFactory.h" +#include "nsTypeAheadFind.h" +#include "nsIServiceManager.h" +#include "prprf.h" +#include "nsCRT.h" +#include "nsICategoryManager.h" + +//////////////////////////////////////////////////////////////////////// +// Define a table of CIDs implemented by this module along with other +// information like the function to create an instance, contractid, and +// class name. +// +// The Registration and Unregistration proc are optional in the structure. +// + + +// This function is called at component registration time +static NS_METHOD +nsTypeAheadFindRegistrationProc(nsIComponentManager *aCompMgr, nsIFile *aPath, + const char *registryLocation, + const char *componentType, + const nsModuleComponentInfo *info) +{ + // This function performs the extra step of installing us as + // an application component. This makes sure that we're + // initialized on application startup. + + // Register nsTypeAheadFind to be instantiated on startup. + // XXX This is needed on linux, but for some reason not needed on win32. + nsresult rv; + nsCOMPtr categoryManager = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + rv = categoryManager->AddCategoryEntry(APPSTARTUP_CATEGORY, + "Type Ahead Find", + "service," + NS_TYPEAHEADFIND_CONTRACTID, + PR_TRUE, PR_TRUE, nsnull); + } + + return rv; +} + + +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsTypeAheadFind, + nsTypeAheadFind::GetInstance) + +static void PR_CALLBACK +TypeAheadFindModuleDtor(nsIModule* self) +{ + nsTypeAheadFind::ReleaseInstance(); +} + +static const nsModuleComponentInfo components[] = +{ + { "TypeAheadFind Component", NS_TYPEAHEADFIND_CID, + NS_TYPEAHEADFIND_CONTRACTID, nsTypeAheadFindConstructor, + nsTypeAheadFindRegistrationProc, nsnull // Unregistration proc + } +}; + +NS_IMPL_NSGETMODULE_WITH_DTOR(nsTypeAheadFind, components, + TypeAheadFindModuleDtor) diff --git a/mozilla/kmeleon/typeaheadfind/src/typeaheadfind.pkg b/mozilla/kmeleon/typeaheadfind/src/typeaheadfind.pkg new file mode 100644 index 00000000..4b93174f --- /dev/null +++ b/mozilla/kmeleon/typeaheadfind/src/typeaheadfind.pkg @@ -0,0 +1,9 @@ +# why not ship this with GRE/embedding, if we're building it? + +[xpfe-browser browser] +#if SHARED_LIBRARY +dist/bin/components/@SHARED_LIBRARY@ +#else +!staticcomp @LIBRARY@ @MODULE_NAME@ +#endif +!xpt dist/bin/components/typeaheadfind.xpt diff --git a/mozilla/kmeleon/wallet/Makefile.in b/mozilla/kmeleon/wallet/Makefile.in new file mode 100644 index 00000000..fa70652d --- /dev/null +++ b/mozilla/kmeleon/wallet/Makefile.in @@ -0,0 +1,48 @@ +# +# ***** 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 +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +DIRS = public src + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/kmeleon/wallet/build/Makefile.in b/mozilla/kmeleon/wallet/build/Makefile.in new file mode 100644 index 00000000..3973726f --- /dev/null +++ b/mozilla/kmeleon/wallet/build/Makefile.in @@ -0,0 +1,82 @@ +# +# ***** 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 +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = walletviewers +LIBRARY_NAME = walletviewers +EXPORT_LIBRARY = 1 +SHORT_LIBNAME = wlltvwrs +IS_COMPONENT = 1 +MODULE_NAME = nsWalletViewerModule +MOZILLA_INTERNAL_API = 1 + +PACKAGE_FILE = walletviewer.pkg + +REQUIRES = xpcom \ + $(NULL) + +CPPSRCS = nsWalletViewerFactory.cpp + +LOCAL_INCLUDES = \ + -I$(srcdir)/../editor \ + -I$(srcdir)/../signonviewer \ + -I$(srcdir)/../walletpreview \ + $(NULL) + +SHARED_LIBRARY_LIBS = \ + ../signonviewer/$(LIB_PREFIX)signonviewer_s.$(LIB_SUFFIX) \ + ../walletpreview/$(LIB_PREFIX)walletpreview_s.$(LIB_SUFFIX) \ + ../editor/$(LIB_PREFIX)walleteditor_s.$(LIB_SUFFIX) \ + $(NULL) + +EXTRA_DSO_LDOPTS = \ + $(MOZ_COMPONENT_LIBS) \ + $(NULL) + +ifeq ($(OS_ARCH),WINNT) +OS_LIBS += $(call EXPAND_LIBNAME,shell32) +endif + +include $(topsrcdir)/config/rules.mk + + diff --git a/mozilla/kmeleon/wallet/build/nsWalletViewerFactory.cpp b/mozilla/kmeleon/wallet/build/nsWalletViewerFactory.cpp new file mode 100644 index 00000000..8012bcbf --- /dev/null +++ b/mozilla/kmeleon/wallet/build/nsWalletViewerFactory.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 "nsCOMPtr.h" +#include "nsIModule.h" +#include "nsIGenericFactory.h" + +#include "nsWalletPreview.h" +#include "nsSignonViewer.h" +#include "nsWalletEditor.h" + +NS_GENERIC_FACTORY_CONSTRUCTOR(WalletPreviewImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(SignonViewerImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(WalletEditorImpl) + +// The list of components we register +static const nsModuleComponentInfo components[] = { + { "WalletPreview World Component", NS_WALLETPREVIEW_CID, + "@mozilla.org/walletpreview/walletpreview-world;1", WalletPreviewImplConstructor }, + { "SignonViewer World Component", NS_SIGNONVIEWER_CID, + "@mozilla.org/signonviewer/signonviewer-world;1", SignonViewerImplConstructor }, + { "WalletEditor World Component", NS_WALLETEDITOR_CID, + "@mozilla.org/walleteditor/walleteditor-world;1", WalletEditorImplConstructor }, +}; + +NS_IMPL_NSGETMODULE(nsWalletViewerModule, components) + diff --git a/mozilla/kmeleon/wallet/build/walletviewer.pkg b/mozilla/kmeleon/wallet/build/walletviewer.pkg new file mode 100644 index 00000000..34921b2f --- /dev/null +++ b/mozilla/kmeleon/wallet/build/walletviewer.pkg @@ -0,0 +1,9 @@ +[walletviewers] +#if SHARED_LIBRARY +dist/bin/components/@SHARED_LIBRARY@ +#else +!staticcomp @LIBRARY@ @MODULE_NAME@ +#endif +!xpt dist/bin/components/signonviewer.xpt +!xpt dist/bin/components/walleteditor.xpt +!xpt dist/bin/components/walletpreview.xpt diff --git a/mozilla/kmeleon/wallet/editor/Makefile.in b/mozilla/kmeleon/wallet/editor/Makefile.in new file mode 100644 index 00000000..025f2458 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/Makefile.in @@ -0,0 +1,64 @@ +# +# ***** 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 +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = walletviewers +LIBRARY_NAME = walleteditor_s +XPIDL_MODULE = walleteditor +MOZILLA_INTERNAL_API = 1 + +REQUIRES = xpcom \ + string \ + wallet \ + dom \ + docshell \ + $(NULL) + +CPPSRCS = nsWalletEditor.cpp + +XPIDLSRCS = nsIWalletEditor.idl + +# we don't want the shared lib, but we want to force the creation of a static lib +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/kmeleon/wallet/editor/nsIWalletEditor.idl b/mozilla/kmeleon/wallet/editor/nsIWalletEditor.idl new file mode 100644 index 00000000..b8711406 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/nsIWalletEditor.idl @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 ***** */ + +/* + + A sample of XPConnect. This file contains a WalletEditor interface. + + */ + +#include "nsISupports.idl" + +interface nsIDOMWindowInternal; + + +[scriptable, uuid(0A904580-1C88-11d3-ABA9-0080C787AD96)] +interface nsIWalletEditor : nsISupports +{ + void SetValue(in wstring aValue, in nsIDOMWindowInternal win); + wstring GetValue(); +}; + + +%{ C++ +// {F86A2E60-1C6A-11d3-ABA9-0080C787AD96} +#define NS_WALLETEDITOR_CID \ +{ 0xf86a2e60, 0x1c6a, 0x11d3, { 0xab, 0xa9, 0x0, 0x80, 0xc7, 0x87, 0xad, 0x96 } } +%} diff --git a/mozilla/kmeleon/wallet/editor/nsWalletEditor.cpp b/mozilla/kmeleon/wallet/editor/nsWalletEditor.cpp new file mode 100644 index 00000000..6bf862a4 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/nsWalletEditor.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 "nscore.h" +#include "nsIMemory.h" +#include "plstr.h" +#include +#include "nsIWalletService.h" +#include "nsIServiceManager.h" +#include "nsIDOMWindowInternal.h" +#include "nsCOMPtr.h" +#include "nsReadableUtils.h" +#include "nsIDocShell.h" +#include "nsWalletEditor.h" +#include "nsString.h" + +static NS_DEFINE_IID(kWalletServiceCID, NS_WALLETSERVICE_CID); + +//////////////////////////////////////////////////////////////////////// + +WalletEditorImpl::WalletEditorImpl() +{ +} + +WalletEditorImpl::~WalletEditorImpl() +{ +} + +NS_IMPL_ISUPPORTS1(WalletEditorImpl, nsIWalletEditor) + +NS_IMETHODIMP +WalletEditorImpl::GetValue(PRUnichar** aValue) +{ + NS_PRECONDITION(aValue != nsnull, "null ptr"); + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + nsresult res; + nsCOMPtr walletservice = + do_GetService(kWalletServiceCID, &res); + if (NS_FAILED(res)) return res; + nsAutoString walletList; + res = walletservice->WALLET_PreEdit(walletList); + if (NS_SUCCEEDED(res)) { + *aValue = ToNewUnicode(walletList); + } + return res; +} + +NS_IMETHODIMP +WalletEditorImpl::SetValue(const PRUnichar* aValue, nsIDOMWindowInternal* win) +{ + /* process the value */ + NS_PRECONDITION(aValue != nsnull, "null ptr"); + if (! aValue) { + return NS_ERROR_NULL_POINTER; + } + nsresult res; + nsCOMPtr walletservice = + do_GetService(kWalletServiceCID, &res); + if (NS_FAILED(res)) return res; + nsAutoString walletList( aValue ); + res = walletservice->WALLET_PostEdit(walletList); + return res; +} diff --git a/mozilla/kmeleon/wallet/editor/nsWalletEditor.h b/mozilla/kmeleon/wallet/editor/nsWalletEditor.h new file mode 100644 index 00000000..8c87f941 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/nsWalletEditor.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 "nsIWalletEditor.h" + +class WalletEditorImpl : public nsIWalletEditor +{ +public: + WalletEditorImpl(); + virtual ~WalletEditorImpl(); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIWalletEditor interface + NS_DECL_NSIWALLETEDITOR +}; diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletAddress.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletAddress.xul new file mode 100644 index 00000000..79845917 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletAddress.xul @@ -0,0 +1,185 @@ + + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletBilling.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletBilling.xul new file mode 100644 index 00000000..f09cde95 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletBilling.xul @@ -0,0 +1,57 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletConcatenated.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletConcatenated.xul new file mode 100644 index 00000000..6b969875 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletConcatenated.xul @@ -0,0 +1,525 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletCredit.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletCredit.xul new file mode 100644 index 00000000..d737a422 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletCredit.xul @@ -0,0 +1,127 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + &creditExpires.label; + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletEmploy.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletEmploy.xul new file mode 100644 index 00000000..76c9376f --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletEmploy.xul @@ -0,0 +1,105 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletMisc.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletMisc.xul new file mode 100644 index 00000000..5f05618c --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletMisc.xul @@ -0,0 +1,191 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletName.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletName.xul new file mode 100644 index 00000000..0b22489b --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletName.xul @@ -0,0 +1,129 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletOther.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletOther.xul new file mode 100644 index 00000000..f09cde95 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletOther.xul @@ -0,0 +1,57 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletPhone.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletPhone.xul new file mode 100644 index 00000000..6803d4b5 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletPhone.xul @@ -0,0 +1,229 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletPrimary.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletPrimary.xul new file mode 100644 index 00000000..f09cde95 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletPrimary.xul @@ -0,0 +1,57 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletShipping.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletShipping.xul new file mode 100644 index 00000000..f09cde95 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletShipping.xul @@ -0,0 +1,57 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletTree.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletTree.xul new file mode 100644 index 00000000..b5db98b0 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletTree.xul @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletUrlspecific.xul b/mozilla/kmeleon/wallet/editor/resources/content/WalletUrlspecific.xul new file mode 100644 index 00000000..a37900ef --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletUrlspecific.xul @@ -0,0 +1,112 @@ + + + + + + + +%brandDTD; + +%walletviewerDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/content/WalletViewer.js b/mozilla/kmeleon/wallet/editor/resources/content/WalletViewer.js new file mode 100644 index 00000000..4c058210 --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/content/WalletViewer.js @@ -0,0 +1,479 @@ +/* -*- Mode: Java; tab-width: 4; c-basic-offset: 2; -*- + * + * ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 ***** */ + +function initPanel() { + var tag = document.getElementById("panelFrame").getAttribute("tag"); + if (hWalletViewer) { + hWalletViewer.onpageload(tag) + } else { + window.queuedTag = tag; + window.queuedTagPending = true; + } +} + +var hWalletViewer = null; +var walletViewerInterface = null; +var walletServiceInterface = null; +var bundle = null; // string bundle +var JS_STRINGS_FILE = "chrome://communicator/locale/wallet/WalletEditor.properties"; +var schemaToValue = []; +var BREAK = "|"; + +function onLoad() +{ + hWalletViewer = new nsWalletViewer('panelFrame'); + + if (!hWalletViewer) + throw "failed to create walletviewer"; + else + hWalletViewer.init(); +} + +function nsWalletViewer(frame_id) +{ + if (!frame_id) { + throw "Error: frame_id not supplied!"; + } + this.contentFrame = frame_id + this.cancelHandlers = []; + this.okHandlers = []; + + // set up window + this.onload(); +} + +nsWalletViewer.prototype = + { + onload: + function() { + walletViewerInterface = Components.classes["@mozilla.org/walleteditor/walleteditor-world;1"].createInstance(); + walletViewerInterface = walletViewerInterface.QueryInterface(Components.interfaces.nsIWalletEditor); + + walletServiceInterface = Components.classes['@mozilla.org/wallet/wallet-service;1']; + walletServiceInterface = walletServiceInterface.getService(); + walletServiceInterface = walletServiceInterface.QueryInterface(Components.interfaces.nsIWalletService); + + bundle = srGetStrBundle(JS_STRINGS_FILE); /* initialize string bundle */ + + if (!EncryptionTest()) { + dump("*** user failed to unlock the database\n"); + return; + } + if (!FetchInput()) { + dump("*** user failed to unlock the database\n"); + return; + } + + // allow l10n to hide certain panels + var pref = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + var panel; + try { + if (pref.getBoolPref("wallet.namePanel.hide")) { + panel = document.getElementById("pnameID"); + panel.setAttribute("hidden", "true"); + panel = document.getElementById("snameID"); + panel.setAttribute("hidden", "true"); + panel = document.getElementById("bnameID"); + panel.setAttribute("hidden", "true"); + } + if (pref.getBoolPref("wallet.addressPanel.hide")) { + panel = document.getElementById("paddressID"); + panel.setAttribute("hidden", "true"); + panel = document.getElementById("saddressID"); + panel.setAttribute("hidden", "true"); + panel = document.getElementById("baddressID"); + panel.setAttribute("hidden", "true"); + } + if (pref.getBoolPref("wallet.phonePanel.hide")) { + panel = document.getElementById("pphoneID"); + panel.setAttribute("hidden", "true"); + panel = document.getElementById("sphoneID"); + panel.setAttribute("hidden", "true"); + panel = document.getElementById("bphoneID"); + panel.setAttribute("hidden", "true"); + } + if (pref.getBoolPref("wallet.creditPanel.hide")) { + panel = document.getElementById("pcreditID"); + panel.setAttribute("hidden", "true"); + } + if (pref.getBoolPref("wallet.employPanel.hide")) { + panel = document.getElementById("pemployID"); + panel.setAttribute("hidden", "true"); + } + if (pref.getBoolPref("wallet.miscPanel.hide")) { + panel = document.getElementById("pmiscID"); + panel.setAttribute("hidden", "true"); + } + } catch(e) { + // error -- stop hiding if prefs are missing + } + }, + + init: + function() { + if (window.queuedTagPending) { + this.onpageload(window.queuedTag); + } + this.closeBranches("pnameID"); + }, + + onAccept: + function() { + for(var i = 0; i < hWalletViewer.okHandlers.length; i++) { + hWalletViewer.okHandlers[i](); + } + + var tag = document.getElementById(hWalletViewer.contentFrame).getAttribute("tag"); + hWalletViewer.savePageData(tag); + hWalletViewer.saveAllData(); + + return true; + }, + + onCancel: + function() { + for(var i = 0; i < hWalletViewer.cancelHandlers.length; i++) { + hWalletViewer.cancelHandlers[i](); + } + + return true; + }, + + registerOKCallbackFunc: + function(aFunctionReference) { + this.okHandlers[this.okHandlers.length] = aFunctionReference; + }, + + registerCancelCallbackFunc: + function(aFunctionReference) { + this.cancelHandlers[this.cancelHandlers.length] = aFunctionReference; + }, + + saveAllData: + function() { + ReturnOutput(); + }, + + savePageData: + function(tag) { + /* collect the list of menuItem labels */ + var elementIDs; + var contentFrame = window.frames[this.contentFrame]; + if ("_elementIDs" in contentFrame) { // make sure page finished loading + elementIDs = contentFrame._elementIDs; + } + for(var i = 0; i < elementIDs.length; i++) { + var values = ""; + var menuList = contentFrame.document.getElementById(elementIDs[i]); + if (menuList.parentNode.getAttribute("hidden") == "true") { + continue; /* needed for concatenations only */ + } + Append(menuList); /* in case current editing has not been stored away */ + var menuPopup = menuList.firstChild; + + /* visit each menuItem */ + for (var menuItem = menuPopup.firstChild; + menuItem != menuPopup.lastChild; /* skip empty item at end of list */ + menuItem = menuItem.nextSibling) { + values += (menuItem.getAttribute("label") + BREAK); + } + schemaToValue[tag+elementIDs[i]] = values; + } + }, + + switchPage: + function() { + var PanelTree = document.getElementById("panelTree"); + if (PanelTree.view.selection.count == 0) return; + var selectedItem = PanelTree.contentView.getItemAtIndex(PanelTree.currentIndex); + + var oldURL = document.getElementById(this.contentFrame).getAttribute("src"); + var oldTag = document.getElementById(this.contentFrame).getAttribute("tag"); + + this.savePageData(oldTag); // save data from the current page. + + var newURL = selectedItem.firstChild.firstChild.getAttribute("url"); + var newTag = selectedItem.firstChild.firstChild.getAttribute("tag"); + if (newURL != oldURL || newTag != oldTag) { + document.getElementById(this.contentFrame).setAttribute("src", newURL); + document.getElementById(this.contentFrame).setAttribute("tag", newTag); + } + }, + + onpageload: + function(aPageTag) { + if ('Startup' in window.frames[ this.contentFrame ]) { + window.frames[ this.contentFrame ].Startup(schemaToValue); + } + + /* restore the list of menuItem labels */ + var elementIDs = window.frames[this.contentFrame]._elementIDs; + for(var i = 0; i < elementIDs.length; i++) { + var menuList = window.frames[this.contentFrame].document.getElementById(elementIDs[i]); + if (!menuList) { + dump("*** FIX ME: '_elementIDs' in '" + aPageTag + + "' contains a reference to a non-existent element ID '" + + elementIDs[i] + "'.\n"); + return; + } + var menuPopup = menuList.firstChild; + if ((aPageTag+elementIDs[i]) in schemaToValue) { + + /* following unhiding is needed for concatenations only */ + var row = menuList.parentNode; + var rows = row.parentNode; + var grid = rows.parentNode; + var groupBox = grid.parentNode; + groupBox.setAttribute("hidden", "false"); + row.setAttribute("hidden", "false"); + + var strings = schemaToValue[aPageTag+elementIDs[i]].split(BREAK); + for (var j = 0; j 1) { + thisMenuPopup.removeChild(thisMenuItem); + thisMenuPopup.insertBefore(thisMenuItem, thisMenuPopup.firstChild); + } + + /* determine if it's time to add menuItem */ + if (len) { + /* previously there were some characters and there still are so it's not time to add */ + return; + } + + /* add menu item */ + var menuItem = thisMenuPopup.ownerDocument.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem"); + if (!menuItem) { + return; + } + menuItem.setAttribute("label", ""); + menuItem.setAttribute("len", "0"); + thisMenuPopup.appendChild(menuItem); + + return; + } + + /* return the wallet output data */ + function ReturnOutput() { + var schema; + var output = "OK" + BREAK; + var value; + for (schema in schemaToValue) { + if (schemaToValue[schema] != "") { + value = schemaToValue[schema].split(BREAK); + for (var i=0; i 1) { + menuPopup.removeChild(menuPopup.firstChild); + } + menuList.removeAttribute("label"); + menuList.selectedItem = menuPopup.firstChild; + } + } + + /* get the wallet input data */ + function FetchInput() { + /* get wallet data into a list */ + var list = walletViewerInterface.GetValue(); + + /* format of this list is as follows: + * + * BREAK-CHARACTER + * schema-name BREAK-CHARACTER + * value BREAK-CHARACTER + * synonymous-value-1 BREAK-CHARACTER + * ... + * synonymous-value-n BREAK-CHARACTER + * + * and repeat above pattern for each schema name. Note that if there are more than + * one distinct values for a particular schema name, the above pattern is repeated + * for each such distinct value + */ + + /* check for database being unlocked */ + if (list.length == 0) { + /* user supplied invalid database key */ + window.close(); // ????? + return false; + } + + /* parse the list into the schemas and their corresponding values */ + BREAK = list[0]; + var strings = list.split(BREAK); + var stringsLength = strings.length; + var schema, value; + for (var i=1; i + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletEditor.dtd b/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletEditor.dtd new file mode 100644 index 00000000..3acce79f --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletEditor.dtd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletEditor.properties b/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletEditor.properties new file mode 100644 index 00000000..4e98981a --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletEditor.properties @@ -0,0 +1,46 @@ +# ***** 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 +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +AddingTitle=Adding +EnterNewSchema=Enter a new field name +# String is of the form: EnterNewEntry %value% EnterNewEntry1 +EnterNewEntry=Enter a new value for +EnterNewEntry1= +# String is of the form: EnterNewSynonym %value% EnterNewSynonym1 +EnterNewSynonym=Enter another value that means the same as +EnterNewSynonym1= +EncryptionFailure=Encryption Failure +UnableToUnlockDatabase=Unable to unlock database diff --git a/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletViewer.dtd b/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletViewer.dtd new file mode 100644 index 00000000..5066cb5b --- /dev/null +++ b/mozilla/kmeleon/wallet/editor/resources/locale/en-US/WalletViewer.dtd @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mozilla/kmeleon/wallet/jar.mn b/mozilla/kmeleon/wallet/jar.mn new file mode 100644 index 00000000..318265bb --- /dev/null +++ b/mozilla/kmeleon/wallet/jar.mn @@ -0,0 +1,53 @@ +comm.jar: +#ifdef MOZ_XUL_APP +% content wallet %content/wallet/ xpcnativewrappers=yes +% overlay chrome://communicator/content/pref/preftree.xul chrome://wallet/content/walletPrefsOverlay.xul +% overlay chrome://communicator/content/tasksOverlay.xul chrome://wallet/content/walletTasksOverlay.xul +#else +* content/wallet/contents.rdf (resources/content/contents.rdf) +#endif + content/wallet/walletNavigatorOverlay.xul (resources/content/walletNavigatorOverlay.xul) + content/wallet/walletTasksOverlay.xul (resources/content/walletTasksOverlay.xul) + content/wallet/walletContextOverlay.xul (resources/content/walletContextOverlay.xul) + content/wallet/walletPrefsOverlay.xul (resources/content/walletPrefsOverlay.xul) + content/wallet/pref-wallet.xul (resources/content/pref-wallet.xul) + content/wallet/pref-passwords.xul (resources/content/pref-passwords.xul) + content/wallet/walletOverlay.js (resources/content/walletOverlay.js) + content/communicator/wallet/SignonViewer.js (signonviewer/resources/content/SignonViewer.js) + content/communicator/wallet/SignonViewer.xul (signonviewer/resources/content/SignonViewer.xul) + content/communicator/wallet/WalletPreview.js (walletpreview/resources/content/WalletPreview.js) + content/communicator/wallet/WalletPreview.xul (walletpreview/resources/content/WalletPreview.xul) + content/communicator/wallet/WalletTree.xul (editor/resources/content/WalletTree.xul) + content/communicator/wallet/WalletViewer.xul (editor/resources/content/WalletViewer.xul) + content/communicator/wallet/WalletViewer.js (editor/resources/content/WalletViewer.js) + +en-US.jar: +#ifdef MOZ_XUL_APP +% locale wallet en-US %locale/en-US/wallet/ +#else +* locale/en-US/wallet/contents.rdf (resources/locale/en-US/contents.rdf) +#endif + locale/en-US/wallet/walletNavigatorOverlay.dtd (resources/locale/en-US/walletNavigatorOverlay.dtd) + locale/en-US/wallet/walletTasksOverlay.dtd (resources/locale/en-US/walletTasksOverlay.dtd) + locale/en-US/wallet/walletPrefsOverlay.dtd (resources/locale/en-US/walletPrefsOverlay.dtd) + locale/en-US/wallet/pref-wallet.dtd (resources/locale/en-US/pref-wallet.dtd) + locale/en-US/wallet/pref-passwords.dtd (resources/locale/en-US/pref-passwords.dtd) + locale/en-US/communicator/wallet/WalletEditor.properties (editor/resources/locale/en-US/WalletEditor.properties) + locale/en-US/communicator/wallet/WalletEditor.dtd (editor/resources/locale/en-US/WalletEditor.dtd) + locale/en-US/communicator/wallet/SignonViewer.properties (signonviewer/resources/locale/en-US/SignonViewer.properties) + locale/en-US/communicator/wallet/SignonViewer.dtd (signonviewer/resources/locale/en-US/SignonViewer.dtd) + locale/en-US/communicator/wallet/WalletPreview.dtd (walletpreview/resources/locale/en-US/WalletPreview.dtd) + locale/en-US/communicator/wallet/wallet.properties (src/resources/locale/en-US/wallet.properties) + locale/en-US/communicator/wallet/WalletViewer.dtd (editor/resources/locale/en-US/WalletViewer.dtd) + locale/en-US/communicator-region/wallet/WalletAddress.xul (editor/resources/content/WalletAddress.xul) + locale/en-US/communicator-region/wallet/WalletBilling.xul (editor/resources/content/WalletBilling.xul) + locale/en-US/communicator-region/wallet/WalletConcatenated.xul (editor/resources/content/WalletConcatenated.xul) + locale/en-US/communicator-region/wallet/WalletCredit.xul (editor/resources/content/WalletCredit.xul) + locale/en-US/communicator-region/wallet/WalletEmploy.xul (editor/resources/content/WalletEmploy.xul) + locale/en-US/communicator-region/wallet/WalletMisc.xul (editor/resources/content/WalletMisc.xul) + locale/en-US/communicator-region/wallet/WalletName.xul (editor/resources/content/WalletName.xul) + locale/en-US/communicator-region/wallet/WalletOther.xul (editor/resources/content/WalletOther.xul) + locale/en-US/communicator-region/wallet/WalletPhone.xul (editor/resources/content/WalletPhone.xul) + locale/en-US/communicator-region/wallet/WalletPrimary.xul (editor/resources/content/WalletPrimary.xul) + locale/en-US/communicator-region/wallet/WalletShipping.xul (editor/resources/content/WalletShipping.xul) + locale/en-US/communicator-region/wallet/WalletUrlspecific.xul (editor/resources/content/WalletUrlspecific.xul) diff --git a/mozilla/kmeleon/wallet/makefiles.sh b/mozilla/kmeleon/wallet/makefiles.sh new file mode 100644 index 00000000..06d2e7db --- /dev/null +++ b/mozilla/kmeleon/wallet/makefiles.sh @@ -0,0 +1,47 @@ +#! /bin/sh +# ***** 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 Build System +# +# The Initial Developer of the Original Code is +# Ben Turner +# +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +add_makefiles " + extensions/wallet/Makefile + extensions/wallet/public/Makefile + extensions/wallet/src/Makefile + extensions/wallet/editor/Makefile + extensions/wallet/signonviewer/Makefile + extensions/wallet/walletpreview/Makefile + extensions/wallet/build/Makefile +" diff --git a/mozilla/kmeleon/wallet/public/Makefile.in b/mozilla/kmeleon/wallet/public/Makefile.in new file mode 100644 index 00000000..13e75cfb --- /dev/null +++ b/mozilla/kmeleon/wallet/public/Makefile.in @@ -0,0 +1,61 @@ +# +# ***** 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 +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = wallet +GRE_MODULE = 1 + +SDK_XPIDLSRCS = \ + nsIPassword.idl \ + $(NULL) + +XPIDLSRCS = \ + nsIWalletService.idl \ + $(NULL) + +EXPORTS = \ + nsCPassword.h \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/kmeleon/wallet/public/nsCPassword.h b/mozilla/kmeleon/wallet/public/nsCPassword.h new file mode 100644 index 00000000..6c5420a8 --- /dev/null +++ b/mozilla/kmeleon/wallet/public/nsCPassword.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 ***** */ + +/* + * This is a "service" and not just an ordinary component. A consumer must talk + * to the service manager, not the component manager. + */ + +#ifndef NSCPASSWORD_H +#define NSCPASSWORD_H + +#include "nsIPassword.h" +#define NS_PASSWORD_CONTRACTID "@mozilla.org/password;1" + +#endif diff --git a/mozilla/kmeleon/wallet/public/nsIPassword.idl b/mozilla/kmeleon/wallet/public/nsIPassword.idl new file mode 100644 index 00000000..d7de54a3 --- /dev/null +++ b/mozilla/kmeleon/wallet/public/nsIPassword.idl @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** 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 + * Netscape Communications, Inc. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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" + +[scriptable, uuid(CF39C2B0-1E4B-11d5-A549-0010A401EB10)] + +/** + * An optional interface for clients wishing to access a + * password object + * + * @status FROZEN + */ + +interface nsIPassword : nsISupports { + + /** + * the name of the host corresponding to the login being saved + * + * The form of the host depends on how the nsIPassword object was created + * + * - if it was created as a result of submitting a form to a site, then the + * host is the url of the site, as obtained from a call to GetSpec + * + * - if it was created as a result of another app (e.g., mailnews) calling a + * prompt routine such at PromptUsernameAndPassword, then the host is whatever + * arbitrary string the app decided to pass in. + * + * Whatever form it is in, it will be used by the password manager to uniquely + * identify the login realm, so that "newsserver:119" is not the same thing as + * "newsserver". + */ + readonly attribute AUTF8String host; + + /** + * the user name portion of the login + */ + readonly attribute AString user; + + /** + * the password portion of the login + */ + readonly attribute AString password; +}; diff --git a/mozilla/kmeleon/wallet/public/nsIWalletService.idl b/mozilla/kmeleon/wallet/public/nsIWalletService.idl new file mode 100644 index 00000000..cee73531 --- /dev/null +++ b/mozilla/kmeleon/wallet/public/nsIWalletService.idl @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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" + +%{ C++ +// {738CFD52-ABCF-11d2-AB4B-0080C787AD96} +#define NS_WALLETSERVICE_CID \ +{ 0x738cfd52, 0xabcf, 0x11d2, { 0xab, 0x4b, 0x0, 0x80, 0xc7, 0x87, 0xad, 0x96 } } + +#define NS_WALLETSERVICE_CONTRACTID "@mozilla.org/wallet/wallet-service;1" +#define NS_WALLETSERVICE_CLASSNAME "Auto Form Fill and Wallet" +%} + +interface nsIDOMNode; +interface nsIDOMWindowInternal; +interface nsIPrompt; + +/** + * The nsIWalletService interface provides an API to the wallet service. + * This is a preliminary interface which will change over time! + * + */ +[scriptable, uuid(FEDBF066-9C29-4307-86B1-2C0F69717FC5)] +interface nsIWalletService : nsISupports { + void WALLET_PreEdit(out AString walletList); + void WALLET_PostEdit(in AString walletList); + boolean WALLET_ChangePassword(); + void WALLET_DeleteAll(); + unsigned long WALLET_RequestToCapture(in nsIDOMWindowInternal win); + boolean WALLET_Prefill(in boolean quick, in nsIDOMWindowInternal win); + wstring WALLET_PrefillOneElement + (in nsIDOMWindowInternal win, in nsIDOMNode elementNode); + void WALLET_PrefillReturn(in AString results); + boolean WALLET_ExpirePassword(); + void WALLET_InitReencryptCallback(in nsIDOMWindowInternal win); + + boolean haveData(in nsIPrompt dialog, in string key, in wstring userName); + + void WALLET_GetNopreviewListForViewer(out AString aNopreviewList); + void WALLET_GetNocaptureListForViewer(out AString aNocaptureList); + void WALLET_GetPrefillListForViewer(out AString aPrefillList); + void SI_SignonViewerReturn(in AString results); + + string WALLET_Encrypt(in wstring text); + wstring WALLET_Decrypt(in string crypt); +}; + + +%{C++ + +#define NS_SINGLESIGNONPROMPT_CID \ +{ /* 64997e60-17fe-11d4-8cee-0060b0fc14a3 */ \ + 0x64997e60, \ + 0x17fe, \ + 0x11d4, \ + {0x8c, 0xee, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \ +} + +#define NS_SINGLESIGNONPROMPT_CLASSNAME "Single Sign-On Prompt" +#define NS_SINGLESIGNONPROMPT_CONTRACTID "@mozilla.org/wallet/single-sign-on-prompt;1" + +%} diff --git a/mozilla/kmeleon/wallet/resources/content/contents.rdf b/mozilla/kmeleon/wallet/resources/content/contents.rdf new file mode 100644 index 00000000..6c737034 --- /dev/null +++ b/mozilla/kmeleon/wallet/resources/content/contents.rdf @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + chrome://wallet/content/walletNavigatorOverlay.xul + + + + chrome://wallet/content/walletTasksOverlay.xul + + + + chrome://wallet/content/walletPrefsOverlay.xul + + + diff --git a/mozilla/kmeleon/wallet/resources/content/pref-passwords.xul b/mozilla/kmeleon/wallet/resources/content/pref-passwords.xul new file mode 100644 index 00000000..1db51154 --- /dev/null +++ b/mozilla/kmeleon/wallet/resources/content/pref-passwords.xul @@ -0,0 +1,109 @@ + + + + + + +%brandDTD; + +%prefWalletDTD; +]> + + + + + + + + + &signonDescription.label; + + + + +