diff --git a/mobile/.hgignore b/mobile/.hgignore new file mode 100644 index 000000000000..e458c8be968e --- /dev/null +++ b/mobile/.hgignore @@ -0,0 +1,8 @@ +# .hgignore - List of filenames hg should ignore + +# Filenames that should be ignored wherever they appear +~$ +\.pyc$ +\.swp$ +(^|/)TAGS$ +\.DS_Store$ diff --git a/mobile/.hgtags b/mobile/.hgtags new file mode 100644 index 000000000000..998bd76f1127 --- /dev/null +++ b/mobile/.hgtags @@ -0,0 +1,8 @@ +14bbac84b71cbb9d603ed571538c9c41b023d94e FENNEC_M4 +7dbca6d7d5cf0a052c658b6ba3e89068b0772734 FENNEC_M7 +eb17df1df284c8aa882ae626d6f8b298adc55c6b FENNEC_M8 +b394a7122c1018dd3d49d9049bcbbcb5b9ab17e2 FENNEC_A1 +5f1388238359c2019572316cf75c3a404d2d4e70 FENNEC_A2 +84195fc5e34609b49862f95799e21b6f790ec9cc FENNEC_M11 +9aa835472fed9cb58c809c2f3182e46bf5ce25bd FENNEC_B1 +6da60192232841d4d6a090a577585c70d790dac8 FENNEC_B5 diff --git a/mobile/LICENSE b/mobile/LICENSE new file mode 100644 index 000000000000..875cfbeb9a72 --- /dev/null +++ b/mobile/LICENSE @@ -0,0 +1,1333 @@ +This code may be used and redistributed under your choice of: + + * Mozilla Public License, version 1.1 or later + * GNU General Public License, version 2.0 or later + * GNU Lesser General Public License, version 2.1 or later + +The terms of these licenses are included here for reference: + + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code 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 Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``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 Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + + + + + + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + + + + + + + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/mobile/Makefile.in b/mobile/Makefile.in new file mode 100644 index 000000000000..056d49998877 --- /dev/null +++ b/mobile/Makefile.in @@ -0,0 +1,62 @@ +# ***** 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. +# +# The Initial Developer of the Original Code is +# the Mozilla Foundation . +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Finkle +# Joel Maher +# +# 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 = chrome locales components modules themes app + +ifndef LIBXUL_SDK +PARALLEL_DIRS += $(DEPTH)/xulrunner/tools/redit +endif + +ifdef WINCE +DIRS += installer/wince +endif + +include $(topsrcdir)/config/rules.mk +include $(topsrcdir)/testing/testsuite-targets.mk + +package-mobile-tests: + $(MAKE) stage-mochitest DIST_BIN=$(DEPTH)/$(DIST)/bin/xulrunner + $(NSINSTALL) -D $(DIST)/$(PKG_PATH) + @(cd $(PKG_STAGE) && tar $(TAR_CREATE_FLAGS) - *) | bzip2 -f > $(DIST)/$(PKG_PATH)$(TEST_PACKAGE) diff --git a/mobile/app/Makefile.in b/mobile/app/Makefile.in new file mode 100644 index 000000000000..7fd88888ffe8 --- /dev/null +++ b/mobile/app/Makefile.in @@ -0,0 +1,219 @@ +# ***** 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. +# +# The Initial Developer of the Original Code is +# the Mozilla Foundation . +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Finkle +# +# 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 = profile/extensions + +PREF_JS_EXPORTS = $(srcdir)/mobile.js +DIST_FILES = application.ini + +ifndef LIBXUL_SDK +PROGRAM=$(MOZ_APP_NAME)$(BIN_SUFFIX) + +CPPSRCS = nsBrowserApp.cpp + +LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre +LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/base + +LIBS += $(JEMALLOC_LIBS) + +ifeq (Linux_1, $(OS_ARCH)_$(GNU_LD)) +OS_LDFLAGS += -Wl,-rpath='$$ORIGIN' +NSDISTMODE = copy +endif + +ifdef MOZ_ENABLE_LIBXUL +APP_XPCOM_LIBS = $(XPCOM_GLUE_LDOPTS) +else +MOZILLA_INTERNAL_API = 1 +APP_XPCOM_LIBS = $(XPCOM_LIBS) +endif + +LIBS += $(APP_XPCOM_LIBS) \ + $(NSPR_LIBS) \ + $(NULL) + +ifdef BUILD_STATIC_LIBS +LIBS += $(DEPTH)/toolkit/xre/$(LIB_PREFIX)xulapp_s.$(LIB_SUFFIX) +else +ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa) +LIBS += $(DIST)/bin/XUL +else +EXTRA_DSO_LIBS += xul +LIBS += $(EXTRA_DSO_LIBS) +endif +ifeq ($(OS_ARCH),WINNT) +OS_LIBS += $(call EXPAND_LIBNAME,version) +endif +endif # BUILD_STATIC_LIBS + +ifdef _MSC_VER +# Always enter a Windows program through wmain, whether or not we're +# a console application. +ifdef WINCE +WIN32_EXE_LDFLAGS += -ENTRY:mainWCRTStartup +else +WIN32_EXE_LDFLAGS += -ENTRY:wmainCRTStartup +endif +endif +endif #LIBXUL_SDK + +include $(topsrcdir)/config/rules.mk + +GRE_MILESTONE = $(shell $(PYTHON) $(topsrcdir)/config/printconfigsetting.py $(LIBXUL_DIST)/bin/platform.ini Build Milestone) +GRE_BUILDID = $(shell $(PYTHON) $(topsrcdir)/config/printconfigsetting.py $(LIBXUL_DIST)/bin/platform.ini Build BuildID) +APP_BUILDID = $(shell $(PYTHON) $(topsrcdir)/toolkit/xre/make-platformini.py --print-buildid) +APP_ICON = mobile +APP_SPLASH = splash + +DEFINES += -DGRE_MILESTONE=$(GRE_MILESTONE) \ + -DGRE_BUILDID=$(GRE_BUILDID) \ + -DAPP_BUILDID=$(APP_BUILDID) \ + -DAPP_NAME=$(MOZ_APP_NAME) \ + -DAPP_VERSION=$(MOZ_APP_VERSION) \ + -DMOZ_UPDATER=$(MOZ_UPDATER) \ + $(NULL) + +ifdef MOZILLA_OFFICIAL +DEFINES += -DMOZILLA_OFFICIAL +endif + +SOURCE_STAMP := $(shell cd $(srcdir)/.. && hg identify 2>/dev/null | cut -f1 -d' ') +ifdef SOURCE_STAMP +DEFINES += -DMOZ_SOURCE_STAMP="$(SOURCE_STAMP)" +endif + +# strip a trailing slash from the repo URL because it's not always present, +# and we want to construct a working URL in buildconfig.html +# make+shell+sed = awful +_dollar=$$ +SOURCE_REPO := $(shell cd $(srcdir)/.. && hg showconfig paths.default 2>/dev/null | head -n1 | sed -e "s/^ssh:/http:/" -e "s/\/$(_dollar)//" ) +# extra sanity check for old versions of hg +# that don't support showconfig +ifeq (http,$(patsubst http%,http,$(SOURCE_REPO))) +DEFINES += -DMOZ_SOURCE_REPO="$(SOURCE_REPO)" +endif + +ifdef WINCE +DEFINES += -DWINCE=1 +endif + +ifeq ($(OS_ARCH),WINCE) +REDIT_PATH = $(LIBXUL_DIST)/host/bin +endif +ifeq ($(OS_ARCH),WINNT) +REDIT_PATH = $(LIBXUL_DIST)/bin +endif + +APP_BINARY = $(MOZ_APP_NAME)$(BIN_SUFFIX) +APP_BINARY_FASTSTART = $(MOZ_APP_NAME)faststart$(BIN_SUFFIX) + +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) + +APP_NAME = $(MOZ_APP_DISPLAYNAME) +APP_VERSION = $(MOZ_APP_VERSION) + +ifdef MOZ_DEBUG +APP_NAME := $(APP_NAME)Debug +endif + +AB_CD = $(MOZ_UI_LOCALE) + +AB := $(firstword $(subst -, ,$(AB_CD))) + +clean clobber repackage:: + rm -rf $(DIST)/$(APP_NAME).app + +ifdef LIBXUL_SDK +APPFILES = Resources +else +APPFILES = MacOS +endif + +libs repackage:: application.ini + mkdir -p $(DIST)/$(APP_NAME).app/Contents/MacOS + rsync -a --exclude CVS --exclude "*.in" $(srcdir)/macbuild/Contents $(DIST)/$(APP_NAME).app --exclude English.lproj + mkdir -p $(DIST)/$(APP_NAME).app/Contents/Resources/$(AB).lproj + rsync -a --exclude CVS --exclude "*.in" $(srcdir)/macbuild/Contents/Resources/English.lproj/ $(DIST)/$(APP_NAME).app/Contents/Resources/$(AB).lproj + sed -e "s/%APP_VERSION%/$(APP_VERSION)/" -e "s/%APP_NAME%/$(APP_NAME)/" -e "s/%APP_BINARY%/$(APP_BINARY)/" $(srcdir)/macbuild/Contents/Info.plist.in > $(DIST)/$(APP_NAME).app/Contents/Info.plist + sed -e "s/%APP_VERSION%/$(APP_VERSION)/" -e "s/%APP_NAME%/$(APP_NAME)/" $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | iconv -f UTF-8 -t UTF-16 > $(DIST)/$(APP_NAME).app/Contents/Resources/$(AB).lproj/InfoPlist.strings + rsync -a $(DIST)/bin/ $(DIST)/$(APP_NAME).app/Contents/$(APPFILES) + $(RM) $(DIST)/$(APP_NAME).app/Contents/$(APPFILES)/mangle $(DIST)/$(APP_NAME).app/Contents/$(APPFILES)/shlibsign +ifdef LIBXUL_SDK + cp $(LIBXUL_DIST)/bin/xulrunner$(BIN_SUFFIX) $(DIST)/$(APP_NAME).app/Contents/MacOS/$(APP_BINARY) + rsync -a --exclude nsinstall --copy-unsafe-links $(LIBXUL_DIST)/XUL.framework $(DIST)/$(APP_NAME).app/Contents/Frameworks +else + rm -f $(DIST)/$(APP_NAME).app/Contents/MacOS/$(PROGRAM) + rsync -aL $(PROGRAM) $(DIST)/$(APP_NAME).app/Contents/MacOS +endif + printf "APPLMOZB" > $(DIST)/$(APP_NAME).app/Contents/PkgInfo + +else # MOZ_WIDGET_TOOLKIT != cocoa + +libs:: +ifdef LIBXUL_SDK + cp $(LIBXUL_DIST)/bin/xulrunner-stub$(BIN_SUFFIX) $(DIST)/bin/$(APP_BINARY) +endif +ifdef MOZ_FASTSTART +ifdef _MSC_VER +ifdef WINCE + cp $(LIBXUL_DIST)/bin/faststartstub$(BIN_SUFFIX) $(DIST)/bin/$(APP_BINARY_FASTSTART) +endif +endif +endif +ifndef SKIP_COPY_XULRUNNER +ifdef LIBXUL_SDK + $(NSINSTALL) -D $(DIST)/bin/xulrunner + (cd $(LIBXUL_SDK)/bin && tar $(TAR_CREATE_FLAGS) - .) | (cd $(DIST)/bin/xulrunner && tar -xf -) +endif +endif # SKIP_COPY_XULRUNNER + + $(NSINSTALL) -D $(DIST)/bin/chrome/icons/default + +ifneq (,$(filter WINNT WINCE,$(OS_ARCH))) + cp $(srcdir)/$(APP_ICON).ico $(DIST)/bin/chrome/icons/default/$(APP_ICON).ico + cp $(srcdir)/$(APP_SPLASH).bmp $(DIST)/bin/$(APP_SPLASH).bmp + $(REDIT_PATH)/redit$(HOST_BIN_SUFFIX) $(DIST)/bin/$(APP_BINARY) $(srcdir)/$(APP_ICON).ico +endif + +endif diff --git a/mobile/app/android/drawable-hdpi/alertaddons.png b/mobile/app/android/drawable-hdpi/alertaddons.png new file mode 100644 index 000000000000..06a33376ae61 Binary files /dev/null and b/mobile/app/android/drawable-hdpi/alertaddons.png differ diff --git a/mobile/app/android/drawable-hdpi/alertdownloads.png b/mobile/app/android/drawable-hdpi/alertdownloads.png new file mode 100644 index 000000000000..5b3daa545178 Binary files /dev/null and b/mobile/app/android/drawable-hdpi/alertdownloads.png differ diff --git a/mobile/app/android/drawable/alertaddons.png b/mobile/app/android/drawable/alertaddons.png new file mode 100644 index 000000000000..9d0a35c192cd Binary files /dev/null and b/mobile/app/android/drawable/alertaddons.png differ diff --git a/mobile/app/android/drawable/alertdownloads.png b/mobile/app/android/drawable/alertdownloads.png new file mode 100644 index 000000000000..a604e27e7908 Binary files /dev/null and b/mobile/app/android/drawable/alertdownloads.png differ diff --git a/mobile/app/application.ini b/mobile/app/application.ini new file mode 100644 index 000000000000..4967f0a124e4 --- /dev/null +++ b/mobile/app/application.ini @@ -0,0 +1,26 @@ +#filter substitution +[App] +Vendor=Mozilla +Name=Fennec +Version=@APP_VERSION@ +BuildID=@APP_BUILDID@ +#ifdef MOZ_SOURCE_REPO +SourceRepository=@MOZ_SOURCE_REPO@ +#endif +#ifdef MOZ_SOURCE_STAMP +SourceStamp=@MOZ_SOURCE_STAMP@ +#endif +ID={a23983c0-fd0e-11dc-95ff-0800200c9a66} + +[Gecko] +MinVersion=1.9.2b5pre +MaxVersion=@GRE_MILESTONE@ + +[XRE] +EnableExtensionManager=1 + +[Crash Reporter] +#if MOZILLA_OFFICIAL +Enabled=1 +#endif +ServerURL=https://crash-reports.mozilla.com/submit diff --git a/mobile/app/macbuild/.DS_Store b/mobile/app/macbuild/.DS_Store new file mode 100644 index 000000000000..2766f49d5d03 Binary files /dev/null and b/mobile/app/macbuild/.DS_Store differ diff --git a/mobile/app/macbuild/CVS/Entries b/mobile/app/macbuild/CVS/Entries new file mode 100644 index 000000000000..17976e103322 --- /dev/null +++ b/mobile/app/macbuild/CVS/Entries @@ -0,0 +1,7 @@ +D/Contents//// +/background.png/1.1/Sun Dec 2 23:08:12 2007/-kb/ +/disk.icns/1.1/Sun Dec 2 23:08:12 2007/-kb/ +/document.icns/1.1/Sun Dec 2 23:08:12 2007/-kb/ +/dsstore/1.1/Sun Dec 2 23:08:12 2007/-kb/ +/firefox.icns/1.3/Sun Dec 2 23:08:12 2007/-kb/ +/license.r/1.4/Mon May 5 00:40:25 2008// diff --git a/mobile/app/macbuild/CVS/Repository b/mobile/app/macbuild/CVS/Repository new file mode 100644 index 000000000000..bc49d595ea7b --- /dev/null +++ b/mobile/app/macbuild/CVS/Repository @@ -0,0 +1 @@ +mozilla/browser/app/macbuild diff --git a/mobile/app/macbuild/CVS/Root b/mobile/app/macbuild/CVS/Root new file mode 100644 index 000000000000..cdb6f4a0739a --- /dev/null +++ b/mobile/app/macbuild/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot diff --git a/mobile/app/macbuild/Contents/CVS/Entries b/mobile/app/macbuild/Contents/CVS/Entries new file mode 100644 index 000000000000..2252d217a2c7 --- /dev/null +++ b/mobile/app/macbuild/Contents/CVS/Entries @@ -0,0 +1,2 @@ +D/Resources//// +/Info.plist.in/1.18/Tue Feb 19 05:11:34 2008// diff --git a/mobile/app/macbuild/Contents/CVS/Repository b/mobile/app/macbuild/Contents/CVS/Repository new file mode 100644 index 000000000000..412caa952bd0 --- /dev/null +++ b/mobile/app/macbuild/Contents/CVS/Repository @@ -0,0 +1 @@ +mozilla/browser/app/macbuild/Contents diff --git a/mobile/app/macbuild/Contents/CVS/Root b/mobile/app/macbuild/Contents/CVS/Root new file mode 100644 index 000000000000..cdb6f4a0739a --- /dev/null +++ b/mobile/app/macbuild/Contents/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot diff --git a/mobile/app/macbuild/Contents/Info.plist.in b/mobile/app/macbuild/Contents/Info.plist.in new file mode 100644 index 000000000000..ff4091140cd6 --- /dev/null +++ b/mobile/app/macbuild/Contents/Info.plist.in @@ -0,0 +1,138 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + html + htm + shtml + xht + xhtml + + CFBundleTypeIconFile + document.icns + CFBundleTypeName + HTML Document + CFBundleTypeOSTypes + + HTML + + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + text + txt + js + log + css + xul + rdf + + CFBundleTypeIconFile + document.icns + CFBundleTypeName + Text Document + CFBundleTypeOSTypes + + TEXT + utxt + + CFBundleTypeRole + Viewer + + + CFBundleTypeExtensions + + jpeg + jpg + png + gif + + CFBundleTypeIconFile + fileBookmark.icns + CFBundleTypeName + document.icns + CFBundleTypeOSTypes + + GIFf + JPEG + PNGf + + CFBundleTypeRole + Viewer + + + CFBundleExecutable + fennec + CFBundleGetInfoString + %APP_NAME% %APP_VERSION% + CFBundleIconFile + fennec + CFBundleIdentifier + org.mozilla.fennec + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + %APP_NAME% + CFBundlePackageType + APPL + CFBundleShortVersionString + %APP_VERSION% + CFBundleSignature + MOZB + CFBundleURLTypes + + + CFBundleURLIconFile + document.icns + CFBundleURLName + http URL + CFBundleURLSchemes + + http + + + + CFBundleURLIconFile + document.icns + CFBundleURLName + https URL + CFBundleURLSchemes + + https + + + + CFBundleURLName + ftp URL + CFBundleURLSchemes + + ftp + + + + CFBundleURLName + file URL + CFBundleURLSchemes + + file + + + + CFBundleVersion + %APP_VERSION% + NSAppleScriptEnabled + + CGDisableCoalescedUpdates + + + diff --git a/mobile/app/macbuild/Contents/Resources/CVS/Entries b/mobile/app/macbuild/Contents/Resources/CVS/Entries new file mode 100644 index 000000000000..e518e95d7d43 --- /dev/null +++ b/mobile/app/macbuild/Contents/Resources/CVS/Entries @@ -0,0 +1 @@ +D/English.lproj//// diff --git a/mobile/app/macbuild/Contents/Resources/CVS/Repository b/mobile/app/macbuild/Contents/Resources/CVS/Repository new file mode 100644 index 000000000000..88a87a46f209 --- /dev/null +++ b/mobile/app/macbuild/Contents/Resources/CVS/Repository @@ -0,0 +1 @@ +mozilla/browser/app/macbuild/Contents/Resources diff --git a/mobile/app/macbuild/Contents/Resources/CVS/Root b/mobile/app/macbuild/Contents/Resources/CVS/Root new file mode 100644 index 000000000000..cdb6f4a0739a --- /dev/null +++ b/mobile/app/macbuild/Contents/Resources/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot diff --git a/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Entries b/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Entries new file mode 100644 index 000000000000..e702a5d0dc78 --- /dev/null +++ b/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Entries @@ -0,0 +1,2 @@ +/InfoPlist.strings.in/1.4/Wed Jan 2 19:06:47 2008// +D diff --git a/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Repository b/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Repository new file mode 100644 index 000000000000..d3c5cc961c3f --- /dev/null +++ b/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Repository @@ -0,0 +1 @@ +mozilla/browser/app/macbuild/Contents/Resources/English.lproj diff --git a/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Root b/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Root new file mode 100644 index 000000000000..cdb6f4a0739a --- /dev/null +++ b/mobile/app/macbuild/Contents/Resources/English.lproj/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot diff --git a/mobile/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/mobile/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in new file mode 100644 index 000000000000..39e694876821 --- /dev/null +++ b/mobile/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in @@ -0,0 +1 @@ +CFBundleName = "%APP_NAME%"; diff --git a/mobile/app/macbuild/background.png b/mobile/app/macbuild/background.png new file mode 100644 index 000000000000..e52f31d05101 Binary files /dev/null and b/mobile/app/macbuild/background.png differ diff --git a/mobile/app/macbuild/disk.icns b/mobile/app/macbuild/disk.icns new file mode 100644 index 000000000000..e97e49058522 Binary files /dev/null and b/mobile/app/macbuild/disk.icns differ diff --git a/mobile/app/macbuild/document.icns b/mobile/app/macbuild/document.icns new file mode 100644 index 000000000000..f5af7a70fc77 Binary files /dev/null and b/mobile/app/macbuild/document.icns differ diff --git a/mobile/app/macbuild/dsstore b/mobile/app/macbuild/dsstore new file mode 100755 index 000000000000..00b8f9a30536 Binary files /dev/null and b/mobile/app/macbuild/dsstore differ diff --git a/mobile/app/macbuild/fennec.icns b/mobile/app/macbuild/fennec.icns new file mode 100644 index 000000000000..17263f5b2dd0 Binary files /dev/null and b/mobile/app/macbuild/fennec.icns differ diff --git a/mobile/app/macbuild/license.r b/mobile/app/macbuild/license.r new file mode 100644 index 000000000000..52f03518a953 --- /dev/null +++ b/mobile/app/macbuild/license.r @@ -0,0 +1,117 @@ +// See /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/Script.h for language IDs. +data 'LPic' (5000) { + // Default language ID, 0 = English + $"0000" + // Number of entries in list + $"0001" + + // Entry 1 + // Language ID, 0 = English + $"0000" + // Resource ID, 0 = STR#/TEXT/styl 5000 + $"0000" + // Multibyte language, 0 = no + $"0000" +}; + +resource 'STR#' (5000, "English") { + { + // Language (unused?) = English + "English", + // Accept (Agree) + "Accept", + // Decline (Disagree) + "Decline", + // Print, ellipsis is 0xC9 + "PrintÉ", + // Save As, ellipsis is 0xC9 + "Save AsÉ", + // Descriptive text, curly quotes are 0xD2 and 0xD3 + "You are about to install\n" + "Minefield.\n" + "\n" + "Please read the license agreement. If you agree to its terms and accept, click ÒAcceptÓ to access the software. Otherwise, click ÒDeclineÓ to cancel." + }; +}; + +// Beware of 1024(?) byte (character?) line length limitation. Split up long +// lines. +// If straight quotes are used ("), remember to escape them (\"). +// Newline is \n, to leave a blank line, use two of them. +// 0xD2 and 0xD3 are curly double-quotes ("), 0xD4 and 0xD5 are curly +// single quotes ('), 0xD5 is also the apostrophe. +data 'TEXT' (5000, "English") { + "MINEFIELD END-USER SOFTWARE LICENSE AGREEMENT\n" + "Version 3.0, May 2008\n" + "\n" + "A SOURCE CODE VERSION OF CERTAIN MINEFIELD BROWSER FUNCTIONALITY THAT YOU MAY USE, MODIFY AND DISTRIBUTE IS AVAILABLE TO YOU FREE-OF-CHARGE FROM WWW.MOZILLA.ORG UNDER THE MOZILLA PUBLIC LICENSE and other open source software licenses.\n" + "\n" + "The accompanying executable code version of Minefield and related documentation (the ÒProductÓ) is made available to you under the terms of this MINEFIELD END-USER SOFTWARE LICENSE AGREEMENT (THE ÒAGREEMENTÓ). BY CLICKING THE ÒACCEPTÓ BUTTON, OR BY INSTALLING OR USING THE MINEFIELD BROWSER, YOU ARE CONSENTING TO BE BOUND BY THE AGREEMENT. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT, DO NOT CLICK THE ÒACCEPTÓ BUTTON, AND DO NOT INSTALL OR USE ANY PART OF THE MINEFIELD BROWSER.\n" + "\n" + "DURING THE MINEFIELD INSTALLATION PROCESS, AND AT LATER TIMES, YOU MAY BE GIVEN THE OPTION OF INSTALLING ADDITIONAL COMPONENTS FROM THIRD-PARTY SOFTWARE PROVIDERS. THE INSTALLATION AND USE OF THOSE THIRD-PARTY COMPONENTS MAY BE GOVERNED BY ADDITIONAL LICENSE AGREEMENTS.\n" + "\n" + "1. LICENSE GRANT. The Mozilla Corporation grants you a non-exclusive license to use the executable code version of the Product. This Agreement will also govern any software upgrades provided by Mozilla that replace and/or supplement the original Product, unless such upgrades are accompanied by a separate license, in which case the terms of that license will govern.\n" + "\n" + "2. TERMINATION. If you breach this Agreement your right to use the Product will terminate immediately and without notice, but all provisions of this Agreement except the License Grant (Paragraph 1) will survive termination and continue in effect. Upon termination, you must destroy all copies of the Product.\n" + "\n" + "3. PROPRIETARY RIGHTS. Portions of the Product are available in source code form under the terms of the Mozilla Public License and other open source licenses (collectively, ÒOpen Source LicensesÓ) at http://www.mozilla.org/MPL. Nothing in this Agreement will be construed to limit any rights granted under the Open Source Licenses. Subject to the foregoing, Mozilla, for itself and on behalf of its licensors, hereby reserves all intellectual property rights in the Product, except for the rights expressly granted in this Agreement. You may not remove or alter any trademark, logo, copyright or other proprietary notice in or on the Product. This license does not grant you any right to use the trademarks, service marks or logos of Mozilla or its licensors.\n" + "\n" + "4. PRIVACY POLICY. You agree to the Mozilla Firefox Privacy Policy, made available online at http://www.mozilla.com/legal/privacy/, as that policy may be changed from time to time. When Mozilla changes the policy in a material way a notice will be posted on the website at www.mozilla.com and when any change is made in the privacy policy, the updated policy will be posted at the above link. It is your responsibility to ensure that you understand the terms of the privacy policy, so you should periodically check the current version of the policy for changes.\n" + "\n" + "5. WEBSITE INFORMATION SERVICES. Mozilla and its contributors, licensors and partners work to provide the most accurate and up-to-date phishing and malware information. However, they cannot guarantee that this information is comprehensive and error-free: some risky sites may not be identified, and some safe sites may be identified in error.\n" + "\n" + "6. DISCLAIMER OF WARRANTY. THE PRODUCT IS PROVIDED ÒAS ISÓ WITH ALL FAULTS. TO THE EXTENT PERMITTED BY LAW, MOZILLA AND MOZILLAÕS DISTRIBUTORS, AND LICENSORS HEREBY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES THAT THE PRODUCT IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE AND NON-INFRINGING. YOU BEAR THE ENTIRE RISK AS TO SELECTING THE PRODUCT FOR YOUR PURPOSES AND AS TO THE QUALITY AND PERFORMANCE OF THE PRODUCT. THIS LIMITATION WILL APPLY NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF IMPLIED WARRANTIES, SO THIS DISCLAIMER MAY NOT APPLY TO YOU.\n" + "\n" + "7. LIMITATION OF LIABILITY. EXCEPT AS REQUIRED BY LAW, MOZILLA AND ITS DISTRIBUTORS, DIRECTORS, LICENSORS, CONTRIBUTORS AND AGENTS (COLLECTIVELY, THE ÒMOZILLA GROUPÓ) WILL NOT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES ARISING OUT OF OR IN ANY WAY RELATING TO THIS AGREEMENT OR THE USE OF OR INABILITY TO USE THE PRODUCT, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, LOST PROFITS, LOSS OF DATA, AND COMPUTER FAILURE OR MALFUNCTION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF THE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH SUCH CLAIM IS BASED. THE MOZILLA GROUPÕS COLLECTIVE LIABILITY UNDER THIS AGREEMENT WILL NOT EXCEED THE GREATER OF $500 (FIVE HUNDRED DOLLARS) AND THE FEES PAID BY YOU UNDER THE LICENSE (IF ANY). SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL, CONSEQUENTIAL OR SPECIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.\n" + "\n" + "8. EXPORT CONTROLS. This license is subject to all applicable export restrictions. You must comply with all export and import laws and restrictions and regulations of any United States or foreign agency or authority relating to the Product and its use.\n" + "\n" + "9. U.S. GOVERNMENT END-USERS. This Product is a Òcommercial item,Ó as that term is defined in 48 C.F.R. 2.101, consisting of Òcommercial computer softwareÓ and Òcommercial computer software documentation,Ó as such terms are used in 48 C.F.R. 12.212 (Sept. 1995) and 48 C.F.R. 227.7202 (June 1995). Consistent with 48 C.F.R. 12.212, 48 C.F.R. 27.405(b)(2) (June 1998) and 48 C.F.R. 227.7202, all U.S. Government End Users acquire the Product with only those rights as set forth therein.\n" + "\n" + "10. MISCELLANEOUS. (a) This Agreement constitutes the entire agreement between Mozilla and you concerning the subject matter hereof, and it may only be modified by a written amendment signed by an authorized executive of Mozilla. (b) Except to the extent applicable law, if any, provides otherwise, this Agreement will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions. (c) This Agreement will not be governed by the United Nations Convention on Contracts for the International Sale of Goods. " + "(d) If any part of this Agreement is held invalid or unenforceable, that part will be construed to reflect the partiesÕ original intent, and the remaining portions will remain in full force and effect. (e) A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof. (f) Except as required by law, the controlling language of this Agreement is English. " + "(g) You may assign your rights under this Agreement to any party that consents to, and agrees to be bound by, its terms; the Mozilla Corporation may assign its rights under this Agreement without condition. (h) This Agreement will be binding upon and inure to the benefit of the parties, their successors and permitted assigns." +}; + +data 'styl' (5000, "English") { + // Number of styles following = 2 + $"0002" + + // Style 1. This is used to display the header lines in bold text. + // Start character = 0 + $"0000 0000" + // Height = 16 + $"0010" + // Ascent = 12 + $"000C" + // Font family = 1024 (Lucida Grande) + $"0400" + // Style bitfield, 0x1=bold 0x2=italic 0x4=underline 0x8=outline + // 0x10=shadow 0x20=condensed 0x40=extended + $"01" + // Style, unused? + $"02" + // Size = 12 point + $"000C" + // Color, RGB + $"0000 0000 0000" + + // Style 2. This is used to display the body. + // Start character = 68 + $"0000 0044" + // Height = 16 + $"0010" + // Ascent = 12 + $"000C" + // Font family = 1024 (Lucida Grande) + $"0400" + // Style bitfield, 0x1=bold 0x2=italic 0x4=underline 0x8=outline + // 0x10=shadow 0x20=condensed 0x40=extended + $"00" + // Style, unused? + $"02" + // Size = 12 point + $"000C" + // Color, RGB + $"0000 0000 0000" +}; diff --git a/mobile/app/mobile.ico b/mobile/app/mobile.ico new file mode 100644 index 000000000000..38312abac428 Binary files /dev/null and b/mobile/app/mobile.ico differ diff --git a/mobile/app/mobile.js b/mobile/app/mobile.js new file mode 100644 index 000000000000..0776824e5514 --- /dev/null +++ b/mobile/app/mobile.js @@ -0,0 +1,596 @@ +/* ***** 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 Mobile Browser. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Matt Brubeck + * + * 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 ***** */ + +#filter substitution + +// For browser.xml binding +// +// cacheRatio* is a ratio that determines the amount of pixels to cache. The +// ratio is multiplied by the viewport width or height to get the displayport's +// width or height, respectively. +// +// (divide integer value by 1000 to get the ratio) +// +// For instance: cachePercentageWidth is 1500 +// viewport height is 500 +// => display port height will be 500 * 1.5 = 750 +// +pref("toolkit.browser.cacheRatioWidth", 2000); +pref("toolkit.browser.cacheRatioHeight", 3000); + +// How long before a content view (a handle to a remote scrollable object) +// expires. +pref("toolkit.browser.contentViewExpire", 3000); + +pref("toolkit.defaultChromeURI", "chrome://browser/content/browser.xul"); +pref("general.useragent.compatMode.firefox", true); +pref("browser.chromeURL", "chrome://browser/content/"); + +pref("browser.tabs.warnOnClose", true); +#ifdef MOZ_IPC +pref("browser.tabs.remote", true); +#else +pref("browser.tabs.remote", false); +#endif + +pref("toolkit.screen.lock", false); + +// From libpref/src/init/all.js, extended to allow a slightly wider zoom range. +pref("zoom.minPercent", 20); +pref("zoom.maxPercent", 400); +pref("toolkit.zoomManager.zoomValues", ".2,.3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2,2.4,3,4"); + +// Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density. +pref("browser.viewport.scaleRatio", -1); + +/* use custom widget for html:select */ +pref("ui.use_native_popup_windows", true); + +/* allow scrollbars to float above chrome ui */ +pref("ui.scrollbarsCanOverlapContent", 1); + +/* use long press to display a context menu */ +pref("ui.click_hold_context_menus", true); + +/* cache prefs */ +pref("browser.cache.disk.enable", false); +pref("browser.cache.disk.capacity", 0); // kilobytes +pref("browser.cache.disk.smart_size.enabled", false); +pref("browser.cache.disk.smart_size.first_run", false); + +pref("browser.cache.memory.enable", true); +pref("browser.cache.memory.capacity", 1024); // kilobytes + +/* image cache prefs */ +pref("image.cache.size", 1048576); // bytes + +/* offline cache prefs */ +pref("browser.offline-apps.notify", true); +pref("browser.cache.offline.enable", true); +pref("browser.cache.offline.capacity", 5120); // kilobytes +pref("offline-apps.quota.max", 2048); // kilobytes +pref("offline-apps.quota.warn", 1024); // kilobytes + +/* protocol warning prefs */ +pref("network.protocol-handler.warn-external.tel", false); +pref("network.protocol-handler.warn-external.mailto", false); +pref("network.protocol-handler.warn-external.vnd.youtube", false); + +/* http prefs */ +pref("network.http.pipelining", true); +pref("network.http.pipelining.ssl", true); +pref("network.http.proxy.pipelining", true); +pref("network.http.pipelining.maxrequests" , 6); +pref("network.http.keep-alive.timeout", 600); +pref("network.http.max-connections", 6); +pref("network.http.max-connections-per-server", 4); +pref("network.http.max-persistent-connections-per-server", 4); +pref("network.http.max-persistent-connections-per-proxy", 4); +#ifdef MOZ_PLATFORM_MAEMO +pref("network.autodial-helper.enabled", true); +#endif + +// See bug 545869 for details on why these are set the way they are +pref("network.buffer.cache.count", 24); +pref("network.buffer.cache.size", 16384); + +/* history max results display */ +pref("browser.display.history.maxresults", 100); + +/* How many times should have passed before the remote tabs list is refreshed */ +pref("browser.display.remotetabs.timeout", 10); + +/* session history */ +pref("browser.sessionhistory.max_total_viewers", 1); +pref("browser.sessionhistory.max_entries", 50); +pref("browser.sessionhistory.optimize_eviction", true); + +/* session store */ +pref("browser.sessionstore.resume_session_once", false); +pref("browser.sessionstore.resume_from_crash", true); +pref("browser.sessionstore.resume_from_crash_timeout", 60); // minutes +pref("browser.sessionstore.interval", 10000); // milliseconds +pref("browser.sessionstore.max_tabs_undo", 5); + +/* these should help performance */ +pref("mozilla.widget.force-24bpp", true); +pref("mozilla.widget.use-buffer-pixmap", true); +pref("mozilla.widget.disable-native-theme", true); + +/* download manager (don't show the window or alert) */ +pref("browser.download.useDownloadDir", true); +pref("browser.download.folderList", 1); // Default to ~/Downloads +pref("browser.download.manager.showAlertOnComplete", false); +pref("browser.download.manager.showAlertInterval", 2000); +pref("browser.download.manager.retention", 2); +pref("browser.download.manager.showWhenStarting", false); +pref("browser.download.manager.closeWhenDone", true); +pref("browser.download.manager.openDelay", 0); +pref("browser.download.manager.focusWhenStarting", false); +pref("browser.download.manager.flashCount", 2); +pref("browser.download.manager.displayedHistoryDays", 7); + +/* download alerts (disabled above) */ +pref("alerts.slideIncrement", 1); +pref("alerts.slideIncrementTime", 10); +pref("alerts.totalOpenTime", 6000); +pref("alerts.height", 50); + +/* password manager */ +pref("signon.rememberSignons", true); +pref("signon.expireMasterPassword", false); +pref("signon.SignonFileName", "signons.txt"); + +/* form helper */ +pref("formhelper.enabled", true); +pref("formhelper.autozoom", true); +pref("formhelper.autozoom.caret", true); +pref("formhelper.restore", false); + +/* find helper */ +pref("findhelper.autozoom", true); + +/* autocomplete */ +pref("browser.formfill.enable", true); + +/* microsummaries */ +pref("browser.microsummary.enabled", false); +pref("browser.microsummary.updateGenerators", false); + +/* spellcheck */ +pref("layout.spellcheckDefault", 0); + +/* extension manager and xpinstall */ +pref("xpinstall.whitelist.add", "addons.mozilla.org"); + +pref("extensions.autoupdate.enabled", true); +pref("extensions.autoupdate.interval", 86400); +pref("extensions.update.enabled", false); +pref("extensions.update.interval", 86400); +pref("extensions.dss.enabled", false); +pref("extensions.dss.switchPending", false); +pref("extensions.ignoreMTimeChanges", false); +pref("extensions.logging.enabled", false); +pref("extensions.hideInstallButton", true); +pref("extensions.showMismatchUI", false); +pref("extensions.hideUpdateButton", false); + +pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%¤tAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%"); + +/* preferences for the Get Add-ons pane */ +pref("extensions.getAddons.cache.enabled", true); +pref("extensions.getAddons.maxResults", 15); +pref("extensions.getAddons.recommended.browseURL", "https://addons.mozilla.org/%LOCALE%/mobile/recommended/"); +pref("extensions.getAddons.recommended.url", "https://services.addons.mozilla.org/%LOCALE%/mobile/api/%API_VERSION%/list/featured/all/%MAX_RESULTS%/%OS%/%VERSION%"); +pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/mobile/search?q=%TERMS%"); +pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/mobile/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%"); +pref("extensions.getAddons.browseAddons", "https://addons.mozilla.org/%LOCALE%/mobile/"); +pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/mobile/api/%API_VERSION%/search/guid:%IDS%?src=mobile&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%"); + +/* blocklist preferences */ +pref("extensions.blocklist.enabled", true); +pref("extensions.blocklist.interval", 86400); +pref("extensions.blocklist.url", "https://addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PING_COUNT%/%TOTAL_PING_COUNT%/%DAYS_SINCE_LAST_PING%/"); +pref("extensions.blocklist.detailsURL", "https://www.mozilla.com/%LOCALE%/blocklist/"); + +/* block popups by default, and notify the user about blocked popups */ +pref("dom.disable_open_during_load", true); +pref("privacy.popups.showBrowserMessage", true); + +pref("keyword.enabled", true); +pref("keyword.URL", "http://www.google.com/m?ie=UTF-8&oe=UTF-8&sourceid=navclient&gfns=1&q="); + +pref("accessibility.typeaheadfind", false); +pref("accessibility.typeaheadfind.timeout", 5000); +pref("accessibility.typeaheadfind.flashBar", 1); +pref("accessibility.typeaheadfind.linksonly", false); +pref("accessibility.typeaheadfind.casesensitive", 0); +// zoom key(F7) conflicts with caret browsing on maemo +pref("accessibility.browsewithcaret_shortcut.enabled", false); + +// Whether or not we show a dialog box informing the user that the update was +// successfully applied. +pref("app.update.showInstalledUI", false); + +// Whether the character encoding menu is under the main Firefox button. This +// preference is a string so that localizers can alter it. +pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties"); +pref("intl.charsetmenu.browser.static", "chrome://browser/locale/browser.properties"); + +// pointer to the default engine name +pref("browser.search.defaultenginename", "chrome://browser/locale/region.properties"); +// SSL error page behaviour +pref("browser.ssl_override_behavior", 2); +pref("browser.xul.error_pages.expert_bad_cert", false); + +// disable logging for the search service by default +pref("browser.search.log", false); + +// ordering of search engines in the engine list. +pref("browser.search.order.1", "chrome://browser/locale/region.properties"); +pref("browser.search.order.2", "chrome://browser/locale/region.properties"); + +// disable updating +pref("browser.search.update", false); +pref("browser.search.update.log", false); +pref("browser.search.updateinterval", 6); + +// enable search suggestions by default +pref("browser.search.suggest.enabled", true); + +// Tell the search service to load search plugins from the locale JAR +pref("browser.search.loadFromJars", true); +pref("browser.search.jarURIs", "chrome://browser/locale/searchplugins/"); + +// tell the search service that we don't really expose the "current engine" +pref("browser.search.noCurrentEngine", true); + +// enable xul error pages +pref("browser.xul.error_pages.enabled", true); + +// Specify emptyRestriction = 0 so that bookmarks appear in the list by default +pref("browser.urlbar.default.behavior", 0); +pref("browser.urlbar.default.behavior.emptyRestriction", 0); + +// Let the faviconservice know that we display favicons as 32x32px so that it +// uses the right size when optimizing favicons +pref("places.favicons.optimizeToDimension", 32); + +// various and sundry awesomebar prefs (should remove/re-evaluate +// these once bug 447900 is fixed) +pref("browser.urlbar.clickSelectsAll", true); +pref("browser.urlbar.doubleClickSelectsAll", true); +pref("browser.urlbar.autoFill", false); +pref("browser.urlbar.matchOnlyTyped", false); +pref("browser.urlbar.matchBehavior", 1); +pref("browser.urlbar.filter.javascript", true); +pref("browser.urlbar.maxRichResults", 24); // increased so we see more results when portrait +pref("browser.urlbar.search.chunkSize", 1000); +pref("browser.urlbar.search.timeout", 100); +pref("browser.urlbar.restrict.history", "^"); +pref("browser.urlbar.restrict.bookmark", "*"); +pref("browser.urlbar.restrict.tag", "+"); +pref("browser.urlbar.match.title", "#"); +pref("browser.urlbar.match.url", "@"); +pref("browser.urlbar.autocomplete.search_threshold", 5); +pref("browser.history.grouping", "day"); +pref("browser.history.showSessions", false); +pref("browser.sessionhistory.max_entries", 50); +pref("browser.history_expire_days", 180); +pref("browser.history_expire_days_min", 90); +pref("browser.history_expire_sites", 40000); +pref("browser.places.migratePostDataAnnotations", true); +pref("browser.places.updateRecentTagsUri", true); +pref("places.frecency.numVisits", 10); +pref("places.frecency.numCalcOnIdle", 50); +pref("places.frecency.numCalcOnMigrate", 50); +pref("places.frecency.updateIdleTime", 60000); +pref("places.frecency.firstBucketCutoff", 4); +pref("places.frecency.secondBucketCutoff", 14); +pref("places.frecency.thirdBucketCutoff", 31); +pref("places.frecency.fourthBucketCutoff", 90); +pref("places.frecency.firstBucketWeight", 100); +pref("places.frecency.secondBucketWeight", 70); +pref("places.frecency.thirdBucketWeight", 50); +pref("places.frecency.fourthBucketWeight", 30); +pref("places.frecency.defaultBucketWeight", 10); +pref("places.frecency.embedVisitBonus", 0); +pref("places.frecency.linkVisitBonus", 100); +pref("places.frecency.typedVisitBonus", 2000); +pref("places.frecency.bookmarkVisitBonus", 150); +pref("places.frecency.downloadVisitBonus", 0); +pref("places.frecency.permRedirectVisitBonus", 0); +pref("places.frecency.tempRedirectVisitBonus", 0); +pref("places.frecency.defaultVisitBonus", 0); +pref("places.frecency.unvisitedBookmarkBonus", 140); +pref("places.frecency.unvisitedTypedBonus", 200); + +// disable color management +pref("gfx.color_management.mode", 0); + +// don't allow JS to move and resize existing windows +pref("dom.disable_window_move_resize", true); + +// prevent click image resizing for nsImageDocument +pref("browser.enable_click_image_resizing", false); + +// open in tab preferences +// 0=default window, 1=current window/tab, 2=new window, 3=new tab in most window +pref("browser.link.open_external", 3); +pref("browser.link.open_newwindow", 3); +// 0=force all new windows to tabs, 1=don't force, 2=only force those with no features set +pref("browser.link.open_newwindow.restriction", 0); + +// controls which bits of private data to clear. by default we clear them all. +pref("privacy.item.cache", true); +pref("privacy.item.cookies", true); +pref("privacy.item.offlineApps", true); +pref("privacy.item.history", true); +pref("privacy.item.formdata", true); +pref("privacy.item.downloads", true); +pref("privacy.item.passwords", true); +pref("privacy.item.sessions", true); +pref("privacy.item.geolocation", true); +pref("privacy.item.siteSettings", true); +pref("privacy.item.syncAccount", true); + +#ifdef MOZ_PLATFORM_MAEMO +pref("plugins.force.wmode", "opaque"); +#endif + +// URL to the Learn More link XXX this is the firefox one. Bug 495578 fixes this. +pref("browser.geolocation.warning.infoURL", "http://www.mozilla.com/%LOCALE%/firefox/geolocation/"); + +// base url for the wifi geolocation network provider +pref("geo.wifi.uri", "https://www.google.com/loc/json"); + +// enable geo +pref("geo.enabled", true); + +// content sink control -- controls responsiveness during page load +// see https://bugzilla.mozilla.org/show_bug.cgi?id=481566#c9 +pref("content.sink.enable_perf_mode", 2); // 0 - switch, 1 - interactive, 2 - perf +pref("content.sink.pending_event_mode", 0); +pref("content.sink.perf_deflect_count", 1000000); +pref("content.sink.perf_parse_time", 50000000); + +pref("javascript.options.mem.gc_frequency", 300); +pref("javascript.options.mem.high_water_mark", 32); +pref("javascript.options.methodjit.chrome", false); + +pref("dom.max_chrome_script_run_time", 0); // disable slow script dialog for chrome +pref("dom.max_script_run_time", 20); + +// JS error console +pref("devtools.errorconsole.enabled", false); + +// kinetic tweakables +pref("browser.ui.kinetic.updateInterval", 30); +pref("browser.ui.kinetic.decelerationRate", 20); +pref("browser.ui.kinetic.speedSensitivity", 80); +pref("browser.ui.kinetic.swipeLength", 160); + +// zooming +pref("browser.ui.zoom.pageFitGranularity", 9); // don't zoom to fit by less than 1/9 (11%) +pref("browser.ui.zoom.animationDuration", 200); // ms duration of double-tap zoom animation +pref("browser.ui.zoom.reflow", false); // Change text wrapping on double-tap +pref("browser.ui.zoom.reflow.fontSize", 720); + +// pinch gesture +pref("browser.ui.pinch.maxGrowth", 150); // max pinch distance growth +pref("browser.ui.pinch.maxShrink", 200); // max pinch distance shrinkage +pref("browser.ui.pinch.scalingFactor", 500); // scaling factor for above pinch limits + +// Touch radius (area around the touch location to look for target elements), +// in 1/240-inch pixels: +pref("browser.ui.touch.left", 8); +pref("browser.ui.touch.right", 8); +pref("browser.ui.touch.top", 12); +pref("browser.ui.touch.bottom", 4); +pref("browser.ui.touch.weight.visited", 120); // percentage + +// plugins +#if MOZ_PLATFORM_MAEMO == 6 +pref("plugin.disable", false); +#else +pref("plugin.disable", true); +#endif +pref("dom.ipc.plugins.enabled", true); + +// process priority +// higher values give content process less CPU time +#if MOZ_PLATFORM_MAEMO +pref("dom.ipc.content.nice", 10); +#else +pref("dom.ipc.content.nice", 1); +#endif + +// product URLs +// The breakpad report server to link to in about:crashes +pref("breakpad.reportURL", "http://crash-stats.mozilla.com/report/index/"); +pref("app.releaseNotesURL", "http://www.mozilla.com/%LOCALE%/mobile/%VERSION%/releasenotes/"); +pref("app.sync.tutorialURL", "https://support.mozilla.com/kb/sync-firefox-between-desktop-and-mobile"); +pref("app.support.baseURL", "http://support.mozilla.com/mobile"); +pref("app.feedbackURL", "http://input.mozilla.com/feedback/"); +pref("app.privacyURL", "http://www.mozilla.com/%LOCALE%/m/privacy.html"); +pref("app.creditsURL", "http://www.mozilla.org/credits/"); +#if MOZ_UPDATE_CHANNEL == beta +pref("app.featuresURL", "http://www.mozilla.com/%LOCALE%/mobile/beta/features/"); +pref("app.faqURL", "http://www.mozilla.com/%LOCALE%/mobile/beta/faq/"); +#else +pref("app.featuresURL", "http://www.mozilla.com/%LOCALE%/mobile/features/"); +pref("app.faqURL", "http://www.mozilla.com/%LOCALE%/mobile/faq/"); +#endif + +pref("app.promo.spark.baseURL", "http://spark.mozilla.org"); +#ifdef MOZ_OFFICIAL_BRANDING +pref("app.promo.spark.endDate", "2011-05-01"); +#else +pref("app.promo.spark.endDate", "2011-01-01"); +#endif + +// Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror) +pref("security.alternate_certificate_error_page", "certerror"); + +pref("security.warn_viewing_mixed", false); // Warning is disabled. See Bug 616712. + +// Override some named colors to avoid inverse OS themes +pref("ui.-moz-dialog", "#efebe7"); +pref("ui.-moz-dialogtext", "#101010"); +pref("ui.-moz-field", "#fff"); +pref("ui.-moz-fieldtext", "#1a1a1a"); +pref("ui.-moz-buttonhoverface", "#f3f0ed"); +pref("ui.-moz-buttonhovertext", "#101010"); +pref("ui.-moz-combobox", "#fff"); +pref("ui.-moz-comboboxtext", "#101010"); +pref("ui.buttonface", "#ece7e2"); +pref("ui.buttonhighlight", "#fff"); +pref("ui.buttonshadow", "#aea194"); +pref("ui.buttontext", "#101010"); +pref("ui.captiontext", "#101010"); +pref("ui.graytext", "#b1a598"); +pref("ui.highlight", "#fad184"); +pref("ui.highlighttext", "#1a1a1a"); +pref("ui.infobackground", "#f5f5b5"); +pref("ui.infotext", "#000"); +pref("ui.menu", "#f7f5f3"); +pref("ui.menutext", "#101010"); +pref("ui.threeddarkshadow", "#000"); +pref("ui.threedface", "#ece7e2"); +pref("ui.threedhighlight", "#fff"); +pref("ui.threedlightshadow", "#ece7e2"); +pref("ui.threedshadow", "#aea194"); +pref("ui.window", "#efebe7"); +pref("ui.windowtext", "#101010"); +pref("ui.windowframe", "#efebe7"); + +#ifdef MOZ_OFFICIAL_BRANDING +pref("browser.search.param.yahoo-fr", "moz35"); +pref("browser.search.param.yahoo-fr-cjkt", "moz35"); +pref("browser.search.param.yahoo-fr-ja", "mozff"); +#endif + +/* app update prefs */ +pref("app.update.timer", 60000); // milliseconds (1 min) + +#ifdef MOZ_UPDATER +pref("app.update.enabled", true); +pref("app.update.timerFirstInterval", 20000); // milliseconds +pref("app.update.auto", false); +pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@"); +pref("app.update.mode", 1); +pref("app.update.silent", false); +pref("app.update.url", "https://aus2.mozilla.org/update/4/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PLATFORM_VERSION%/update.xml"); +pref("app.update.nagTimer.restart", 86400); +pref("app.update.promptWaitTime", 43200); +pref("app.update.idletime", 60); +pref("app.update.showInstalledUI", false); +pref("app.update.incompatible.mode", 0); +pref("app.update.download.backgroundInterval", 0); + +#ifdef MOZ_OFFICIAL_BRANDING +pref("app.update.interval", 86400); +pref("app.update.url.manual", "http://www.mozilla.com/%LOCALE%/m/"); +pref("app.update.url.details", "http://www.mozilla.com/%LOCALE%/mobile/releases/"); +#else +pref("app.update.interval", 28800); +pref("app.update.url.manual", "http://www.mozilla.com/%LOCALE%/mobile/"); +pref("app.update.url.details", "http://www.mozilla.com/%LOCALE%/mobile/"); +#endif +#endif + +// replace newlines with spaces on paste into single-line text boxes +pref("editor.singleLine.pasteNewlines", 2); + +#ifdef MOZ_PLATFORM_MAEMO +// update fonts for better readability +pref("font.default.x-baltic", "SwissA"); +pref("font.default.x-central-euro", "SwissA"); +pref("font.default.x-cyrillic", "SwissA"); +pref("font.default.x-unicode", "SwissA"); +pref("font.default.x-user-def", "SwissA"); +pref("font.default.x-western", "SwissA"); +#endif + +#ifdef MOZ_SERVICES_SYNC +pref("browser.sync.enabled", true); + +// sync service +pref("services.sync.client.type", "mobile"); +pref("services.sync.registerEngines", "Tab,Bookmarks,Form,History,Password,Prefs"); +pref("services.sync.autoconnectDelay", 5); + +// prefs to sync by default +pref("services.sync.prefs.sync.browser.startup.homepage.title", true); +pref("services.sync.prefs.sync.browser.startup.homepage", true); +pref("services.sync.prefs.sync.browser.tabs.warnOnClose", true); +pref("services.sync.prefs.sync.browser.ui.zoom.reflow", true); +pref("services.sync.prefs.sync.devtools.errorconsole.enabled", true); +pref("services.sync.prefs.sync.javascript.enabled", true); +pref("services.sync.prefs.sync.lightweightThemes.isThemeSelected", true); +pref("services.sync.prefs.sync.lightweightThemes.usedThemes", true); +pref("services.sync.prefs.sync.network.cookie.cookieBehavior", true); +pref("services.sync.prefs.sync.permissions.default.image", true); +pref("services.sync.prefs.sync.signon.rememberSignons", true); +#endif + +// threshold where a tap becomes a drag, in 1/240" reference pixels +// The names of the preferences are to be in sync with nsEventStateManager.cpp +pref("ui.dragThresholdX", 25); +pref("ui.dragThresholdY", 25); + +#if MOZ_PLATFORM_MAEMO == 6 +pref("layers.acceleration.disabled", false); +#else +pref("layers.acceleration.disabled", true); +#endif + +pref("notification.feature.enabled", true); + +// prevent tooltips from showing up +pref("browser.chrome.toolbar_tips", false); +pref("indexedDB.feature.enabled", false); + +// prevent video elements from preloading too much data +pref("media.preload.default", 1); // default to preload none +pref("media.preload.auto", 2); // preload metadata if preload=auto + +// optimize images memory usage +pref("image.mem.decodeondraw", true); +pref("content.image.allow_locking", false); +pref("image.mem.min_discard_timeout_ms", 20000); + diff --git a/mobile/app/nsBrowserApp.cpp b/mobile/app/nsBrowserApp.cpp new file mode 100644 index 000000000000..85c033a07cef --- /dev/null +++ b/mobile/app/nsBrowserApp.cpp @@ -0,0 +1,160 @@ +/* -*- 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) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian Ryner + * + * 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 "nsXULAppAPI.h" +#ifdef XP_WIN +#include +#include +#endif + +#include +#include + +#include "plstr.h" +#include "prprf.h" +#include "prenv.h" + +#include "nsCOMPtr.h" +#include "nsILocalFile.h" +#include "nsStringGlue.h" + +#ifdef XP_WIN +// we want to use the DLL blocklist if possible +#define XRE_WANT_DLL_BLOCKLIST +// we want a wmain entry point +#include "nsWindowsWMain.cpp" +#endif + +static void Output(const char *fmt, ... ) +{ + va_list ap; + va_start(ap, fmt); + +#if defined(XP_WIN) && !MOZ_WINCONSOLE + PRUnichar msg[2048]; + _vsnwprintf(msg, sizeof(msg)/sizeof(msg[0]), NS_ConvertUTF8toUTF16(fmt).get(), ap); + MessageBoxW(NULL, msg, L"XULRunner", MB_OK | MB_ICONERROR); +#else + vfprintf(stderr, fmt, ap); +#endif + + va_end(ap); +} + +/** + * Return true if |arg| matches the given argument name. + */ +static PRBool IsArg(const char* arg, const char* s) +{ + if (*arg == '-') + { + if (*++arg == '-') + ++arg; + return !PL_strcasecmp(arg, s); + } + +#if defined(XP_WIN) || defined(XP_OS2) + if (*arg == '/') + return !PL_strcasecmp(++arg, s); +#endif + + return PR_FALSE; +} + +class ScopedLogging +{ +public: + ScopedLogging() { NS_LogInit(); } + ~ScopedLogging() { NS_LogTerm(); } +}; + +int main(int argc, char* argv[]) +{ + ScopedLogging log; + + nsCOMPtr appini; + nsresult rv = XRE_GetBinaryPath(argv[0], getter_AddRefs(appini)); + if (NS_FAILED(rv)) { + Output("Couldn't calculate the application directory."); + return 255; + } + appini->SetNativeLeafName(NS_LITERAL_CSTRING("application.ini")); + + // Allow firefox.exe to launch XULRunner apps via -app + // Note that -app must be the *first* argument. + char *appEnv = nsnull; + const char *appDataFile = PR_GetEnv("XUL_APP_FILE"); + if (appDataFile && *appDataFile) { + rv = XRE_GetFileFromPath(appDataFile, getter_AddRefs(appini)); + if (NS_FAILED(rv)) { + Output("Invalid path found: '%s'", appDataFile); + return 255; + } + } + else if (argc > 1 && IsArg(argv[1], "app")) { + if (argc == 2) { + Output("Incorrect number of arguments passed to -app"); + return 255; + } + + rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(appini)); + if (NS_FAILED(rv)) { + Output("application.ini path not recognized: '%s'", argv[2]); + return 255; + } + + appEnv = PR_smprintf("XUL_APP_FILE=%s", argv[2]); + PR_SetEnv(appEnv); + argv[2] = argv[0]; + argv += 2; + argc -= 2; + } + + nsXREAppData *appData; + rv = XRE_CreateAppData(appini, &appData); + if (NS_FAILED(rv)) { + Output("Couldn't read application.ini"); + return 255; + } + + int result = XRE_main(argc, argv, appData); + XRE_FreeAppData(appData); + if (appEnv) + PR_smprintf_free(appEnv); + return result; +} diff --git a/mobile/app/profile/extensions/Makefile.in b/mobile/app/profile/extensions/Makefile.in new file mode 100644 index 000000000000..d3ebb9707268 --- /dev/null +++ b/mobile/app/profile/extensions/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. +# +# The Initial Developer of the Original Code is +# the Mozilla Foundation . +# Portions created by the Initial Developer are Copyright (C) 2010 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Finkle +# +# 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 + +ifeq (beta,$(MOZ_UPDATE_CHANNEL)) +EXTENSIONS = \ + feedback@mobile.mozilla.org \ + $(NULL) + +ABS_DIST = $(call core_abspath,$(DIST)) + +define _PACKAGE_EXTENSIONS +rm -f $(ABS_DIST)/bin/extensions/$(dir).xpi +mkdir -p $(ABS_DIST)/bin/extensions +cd $(srcdir)/$(dir)/; $(ZIP) -r9 $(ABS_DIST)/bin/extensions/$(dir).xpi * + +endef # do not remove the blank line! + +libs:: + $(foreach dir,$(EXTENSIONS),$(_PACKAGE_EXTENSIONS)) + +endif diff --git a/mobile/app/profile/extensions/feedback@mobile.mozilla.org/chrome.manifest b/mobile/app/profile/extensions/feedback@mobile.mozilla.org/chrome.manifest new file mode 100644 index 000000000000..49bb5554d98f --- /dev/null +++ b/mobile/app/profile/extensions/feedback@mobile.mozilla.org/chrome.manifest @@ -0,0 +1,5 @@ +content feedback content/ +skin feedback classic/1.0 skin/ +locale feedback en-US locale/en-US/ + +overlay chrome://browser/content/browser.xul chrome://feedback/content/overlay.xul diff --git a/mobile/app/profile/extensions/feedback@mobile.mozilla.org/content/content.js b/mobile/app/profile/extensions/feedback@mobile.mozilla.org/content/content.js new file mode 100644 index 000000000000..e9ac8f6c1b17 --- /dev/null +++ b/mobile/app/profile/extensions/feedback@mobile.mozilla.org/content/content.js @@ -0,0 +1,33 @@ + +function populateFeedback(aMessage) { + let json = aMessage.json; + + let referrer = json.referrer; + let URLElem = content.document.getElementById("id_url"); + if (URLElem) + URLElem.value = referrer; + + let URLElems = content.document.getElementsByClassName("url"); + for (let index=0; index + * + * 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 ***** */ + +var Feedback = { + _prefs: [], + _device: "", + _manufacturer: "", + + init: function(aEvent) { + // Delay the widget initialization during startup. + window.addEventListener("UIReadyDelayed", function(aEvent) { + let appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); + document.getElementById("feedback-about").setAttribute("desc", appInfo.version); + + // A simple frame script to fill in the referrer page and device info + messageManager.loadFrameScript("chrome://feedback/content/content.js", true); + + window.removeEventListener(aEvent.type, arguments.callee, false); + document.getElementById("feedback-container").hidden = false; + + let feedbackPrefs = document.getElementById("feedback-tools").childNodes; + for (let i = 0; i < feedbackPrefs.length; i++) { + let pref = feedbackPrefs[i].getAttribute("pref"); + if (!pref) + continue; + + let value = Services.prefs.getPrefType(pref) == Ci.nsIPrefBranch.PREF_INVALID ? false : Services.prefs.getBoolPref(pref); + Feedback._prefs.push({ "name": pref, "value": value }); + } + + let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); + Feedback._device = sysInfo.get("device"); + Feedback._manufacturer = sysInfo.get("manufacturer"); + }, false); + }, + + openFeedback: function(aName) { + let pref = "extensions.feedback.url." + aName; + let url = Services.prefs.getPrefType(pref) == Ci.nsIPrefBranch.PREF_INVALID ? "" : Services.prefs.getCharPref(pref); + if (!url) + return; + + let currentURL = Browser.selectedBrowser.currentURI.spec; + let newTab = BrowserUI.newTab(url, Browser.selectedTab); + + // Tell the feedback page to fill in the referrer URL + newTab.browser.messageManager.addMessageListener("DOMContentLoaded", function() { + newTab.browser.messageManager.removeMessageListener("DOMContentLoaded", arguments.callee, true); + newTab.browser.messageManager.sendAsyncMessage("Feedback:InitPage", { referrer: currentURL, device: Feedback._device, manufacturer: Feedback._manufacturer }); + }); + }, + + openReadme: function() { + let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); + let url = formatter.formatURLPref("app.releaseNotesURL"); + BrowserUI.newTab(url, Browser.selectedTab); + }, + + updateRestart: function updateRestart() { + let msg = document.getElementById("feedback-messages"); + if (msg) { + let value = "restart-app"; + let notification = msg.getNotificationWithValue(value); + if (notification) { + // Check if the prefs are back to the initial state dismiss the restart + // notification because if does not make sense anymore + for each (let pref in this._prefs) { + let value = Services.prefs.getPrefType(pref.name) == Ci.nsIPrefBranch.PREF_INVALID ? false : Services.prefs.getBoolPref(pref.name); + if (value != pref.value) + return; + } + + notification.close(); + return; + } + + let restartCallback = function(aNotification, aDescription) { + // Notify all windows that an application quit has been requested + let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + + // If nothing aborted, quit the app + if (cancelQuit.data == false) { + let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); + appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); + } + }; + + let strings = Strings.browser; + + let buttons = [ { + label: strings.GetStringFromName("notificationRestart.button"), + accessKey: "", + callback: restartCallback + } ]; + + let message = strings.GetStringFromName("notificationRestart.normal"); + msg.appendNotification(message, value, "", msg.PRIORITY_WARNING_LOW, buttons); + } + } +}; + +window.addEventListener("load", Feedback.init, false); diff --git a/mobile/app/profile/extensions/feedback@mobile.mozilla.org/content/overlay.xul b/mobile/app/profile/extensions/feedback@mobile.mozilla.org/content/overlay.xul new file mode 100644 index 000000000000..1fc0a5e4c4bc --- /dev/null +++ b/mobile/app/profile/extensions/feedback@mobile.mozilla.org/content/overlay.xul @@ -0,0 +1,78 @@ + + + + + + + +%feedbackDTD; +]> + + + + + + diff --git a/mobile/chrome/content/aboutCertError.css b/mobile/chrome/content/aboutCertError.css new file mode 100644 index 000000000000..edfcc0eee8c6 --- /dev/null +++ b/mobile/chrome/content/aboutCertError.css @@ -0,0 +1,59 @@ +/* ***** 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 + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * William Price + * Steven Garrity + * Henrik Skupin + * Johnathan Nightingale + * + * 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 ***** */ + +/* Logical CSS rules belong here, but presentation & theming rules + should live in the CSS of the appropriate theme */ + +#technicalContentText { + overflow: auto; + white-space: pre-wrap; +} + +#technicalContent > h2, #expertContent > h2 { + cursor: pointer; + padding-left: 20px; + position: relative; + left: -20px; +} + +div[collapsed] > p, +div[collapsed] > div { + display: none; +} diff --git a/mobile/chrome/content/aboutCertError.xhtml b/mobile/chrome/content/aboutCertError.xhtml new file mode 100644 index 000000000000..3d9a4b064e1a --- /dev/null +++ b/mobile/chrome/content/aboutCertError.xhtml @@ -0,0 +1,280 @@ + + + + %htmlDTD; + + %globalDTD; + + %certerrorDTD; +]> + + + + + &certerror.pagetitle; + + + + + + + + + + + +
+ + +
+

&certerror.longpagetitle;

+
+ + +
+
+

&certerror.introPara1;

+

&certerror.introPara2;

+
+ +
+

&certerror.whatShouldIDo.heading;

+
+

&certerror.whatShouldIDo.content;

+ +
+
+ + +
+

&certerror.technical.heading;

+

+

+ +
+

&certerror.expert.heading;

+
+

&certerror.expert.content;

+

&certerror.expert.contentPara2;

+ + +
+
+
+
+ + + + + + diff --git a/mobile/chrome/content/aboutHome.xhtml b/mobile/chrome/content/aboutHome.xhtml new file mode 100644 index 000000000000..31f823f783d3 --- /dev/null +++ b/mobile/chrome/content/aboutHome.xhtml @@ -0,0 +1,381 @@ + + + +%brandDTD; + +%globalDTD; + +%preferenceDTD; + +%aboutDTD; +]> + + + + + + &homepage.default; + + + + + + +
+ + +
+

&aboutHome.recentTabs;

+
+ +
+
+ +
+
+ +
&aboutHome.remoteTabs;
+
+
+ +
+

&aboutHome.recommendedAddons2;

+
+ +
+
+ + + +
&aboutHome.promoLabel;&aboutHome.promoButton;
+
+ + +
+ &aboutHome.openAllTabs; + &aboutHome.noTabs; + &aboutHome.noAddons; +
+ + + + diff --git a/mobile/chrome/content/aboutRights.xhtml b/mobile/chrome/content/aboutRights.xhtml new file mode 100644 index 000000000000..a4650d5dbfd9 --- /dev/null +++ b/mobile/chrome/content/aboutRights.xhtml @@ -0,0 +1,128 @@ + + + %htmlDTD; + + %brandDTD; + + %aboutRightsDTD; +]> +# ***** 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 +# Gervase Markham. +# Portions created by the Initial Developer are Copyright (C) 2008 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Justin Dolske +# +# 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 ***** + + + + + &rights.pagetitle; + + + + + +

&rights.intro-header;

+ +

&rights.intro;

+ +
    +
  • &rights.intro-point1a;&rights.intro-point1b;&rights.intro-point1c;
  • +# Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded. +# Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace) +# Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) +
  • &rights.intro-point2-a;&rights.intro-point2-b;&rights.intro-point2-c;
  • +
  • &rights.intro-point2.5;
  • +
  • &rights2.intro-point3a;&rights2.intro-point3b;&rights.intro-point3c;
  • +
  • &rights2.intro-point4a;&rights.intro-point4b;&rights.intro-point4c;
  • +
+ + + + + + + diff --git a/mobile/chrome/content/bindings.xml b/mobile/chrome/content/bindings.xml new file mode 100644 index 000000000000..5f8eaaeff70f --- /dev/null +++ b/mobile/chrome/content/bindings.xml @@ -0,0 +1,1774 @@ + + + +%browserDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this.boxObject.QueryInterface(Ci.nsIScrollBoxObject); + + + + [] + -1 + + + + + + + + -1 + + + this._matchCount - 1) + return val; + + if (this._selectedItem) + this._styleItem(this._selectedItem, false); + + // highlight the selected item + let item = this._items.childNodes.item(val); + if (item) { + this._selectedItem = item; + this._styleItem(this._selectedItem, true); + this._scrollBoxObject.ensureElementIsVisible(this._selectedItem); + } + + return this._selectedIndex = val; + ]]> + + + false + + + + + + + + + + + + + + + + + + + + + + ("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") + + + matchCount - 1) { + // Just clear out the old item's value. CSS takes care of hiding + // everything else based on value="" (star, tags, etc.) + item.setAttribute("value", ""); + item._empty = true; + + continue; + } + + // Assign the values + let type = controller.getStyleAt(i); + let title = controller.getCommentAt(i); + let tags = ''; + + if (type == "tag") { + try { + [, title, tags] = title.match(/^(.+) \u2013 (.+)$/); + } catch (e) {} + } + item.setAttribute("tags", tags); + + let url = controller.getValueAt(i); + item.setAttribute("value", title || url); + + // remove the badge only if the url has changed + if (item._empty || item.getAttribute("url") != url) { + item.setAttribute("url", url); + item.setAttribute("subtitle", url); + item.removeAttribute("badge"); + item.removeAttribute("remote"); + item.removeAttribute("search"); + } + + let isBookmark = ((type == "bookmark") || (type == "tag")); + item.setAttribute("favorite", isBookmark); + item.setAttribute("src", controller.getImageAt(i)); + + if (type=="search") { + item.setAttribute("search", true); + item.setAttribute("subtitle", searchSubtitle); + } + + item._empty = false; + } + + // Show the "no results" or "all bookmarks" entries as needed + this._updateNoResultsItem(matchCount); + + // Make sure the list is scrolled to the top + this.scrollToTop(); + this._invalidateBadges(); + ]]> + + + + + &noResults.label; + + + + + + lastIndex) + newIndex = 0; + else if (newIndex < 0) + newIndex = lastIndex; + + this.selectedIndex = newIndex; + ]]> + + + + + + + + + + + + + + + + + + + + + + + + document.getAnonymousElementByAttribute(this, + "anonid", "autocomplete-items"); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + null + false + + + document.getAnonymousElementByAttribute(this, "anonid", "name"); + + + document.getAnonymousElementByAttribute(this, "anonid", "uri"); + + + document.getAnonymousElementByAttribute(this, "anonid", "tags"); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 || currentTags.length > 0) { + let tagsToRemove = []; + let tagsToAdd = []; + for (let i = 0; i < currentTags.length; i++) { + if (tags.indexOf(currentTags[i]) == -1) + tagsToRemove.push(currentTags[i]); + } + for (let i = 0; i < tags.length; i++) { + if (currentTags.indexOf(tags[i]) == -1) + tagsToAdd.push(tags[i]); + } + + if (tagsToAdd.length > 0) + PlacesUtils.tagging.tagURI(this.uri, tagsToAdd); + if (tagsToRemove.length > 0) + PlacesUtils.tagging.untagURI(this.uri, tagsToRemove); + } + this.setAttribute("tags", this.tags); + + // If the URI was updated change it in the bookmark, but don't + // allow a blank URI. Revert to previous URI if blank. + let spec = this.spec; + if (spec && this.uri.spec != spec) { + try { + let oldURI = this._uri; + this._uri = Services.io.newURI(spec, null, null); + PlacesUtils.bookmarks.changeBookmarkURI(this.itemId, this.uri); + this.setAttribute("uri", this.spec); + + // move tags from old URI to new URI + let tags = this.tagsAsArray; + if (tags.length != 0) { + // only untag the old URI if this is the only bookmark + if (PlacesUtils.getBookmarksForURI(oldURI, {}).length == 0) + PlacesUtils.tagging.untagURI(oldURI, tags); + + PlacesUtils.tagging.tagURI(this._uri, tags); + } + } + catch (e) { } + } + if (spec != this.uri.spec) + this.spec = this.uri.spec; + } + + // Update the name and use the URI if name is blank + this.name = this.name || this.spec; + this.setAttribute("title", this.name); + PlacesUtils.bookmarks.setItemTitle(this.itemId, this.name); + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -1000 + + + + + + + + false + + document.getAnonymousElementByAttribute(this, "anonid", "parent-items"); + + + document.getAnonymousElementByAttribute(this, "anonid", "child-items"); + + + this._children.scrollBoxObject + + + + + null + + + + + + + + + + + null + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + document.getAnonymousElementByAttribute(this, "anonid", "child-items"); + + + this._children.scrollBoxObject + + + 0 && msDelta < msPerDay && lastTitle != titleYesterday) { + lastTitle = titleYesterday; + items.push({ title: lastTitle }); + } else if (msDelta > msPerDay && msDelta < msPerWeek && lastTitle != titleLastWeek) { + lastTitle = titleLastWeek; + items.push({ title: lastTitle }); + } else if (msDelta > msPerWeek && lastTitle != titleOlder) { + lastTitle = titleOlder; + items.push({ title: lastTitle }); + } + + items.push(node); + } + + rootNode.containerOpen = false; + return items; + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + document.getAnonymousElementByAttribute(this, "anonid", "child-items"); + + + this._children.scrollBoxObject + + + = Services.prefs.getIntPref("browser.display.remotetabs.timeout")) { + // Force a sync only for the tabs engine + engine.lastModified = null; + engine.sync(); + Weave.Svc.Prefs.set("lastTabFetch", now); + }; + + // Generate the list of tabs + let tabs = []; + for (let [guid, client] in Iterator(engine.getAllClients())) { + if (!client.tabs.length) + continue; + + tabs.push({ name: client.clientName }); + + client.tabs.forEach(function({title, urlHistory, icon}) { + let pageURL = urlHistory[0]; + + tabs.push({ + title: title || pageURL, + uri: pageURL, + icon: Weave.Utils.getIcon(icon, "chrome://browser/skin/images/tab.png") + }); + }); + }; + + return tabs; + ]]> + + + + + + + + + + + + + + + 0.8) + this._insertItems(); + ]]> + + + + + + + this.scrollBoxObject.height; + [] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + document.getAnonymousElementByAttribute(this, "anonid", "previous-button"); + + + + document.getAnonymousElementByAttribute(this, "anonid", "next-button"); + + + + + + + + + null + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 || selectionEnd < aTextbox.textLength) + json.types.push("select-all"); + + let clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard); + let flavors = ["text/unicode"]; + let hasData = clipboard.hasDataMatchingFlavors(flavors, flavors.length, Ci.nsIClipboard.kGlobalClipboard); + + if (hasData && (!aTextbox.readOnly || aIgnoreReadOnly)) + json.types.push("paste"); + + ContextHelper.showPopup({ target: aTextbox, json: json }); + ]]> + + + + diff --git a/mobile/chrome/content/bindings/arrowbox.xml b/mobile/chrome/content/bindings/arrowbox.xml new file mode 100644 index 000000000000..ec7b96ea3a7a --- /dev/null +++ b/mobile/chrome/content/bindings/arrowbox.xml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + + + + window.innerHeight) { + content.style.overflow = "hidden"; + this.style.minHeight = (window.innerHeight - parseInt(this.top) - kBottomMargin) + "px"; + } + + let anchorRect = aAnchorNode.getBoundingClientRect(); + let popupRect = this.getBoundingClientRect(); + let offset = this.offset; + + let horizPos = (Math.round(popupRect.right) <= Math.round(anchorRect.left + offset)) ? -1 : + (Math.round(popupRect.left) >= Math.round(anchorRect.right - offset)) ? 1 : 0; + let vertPos = (Math.round(popupRect.bottom) <= Math.round(anchorRect.top + offset)) ? -1 : + (Math.round(popupRect.top) >= Math.round(anchorRect.bottom - offset)) ? 1 : 0; + + let HALF_ARROW_WIDTH = 16; + + let anchorClass = ""; + let hideArrow = false; + if (horizPos == 0) { + container.orient = "vertical"; + arrowbox.orient = ""; + if (vertPos == 0) { + hideArrow = true; + } else { + arrowbox.style.marginLeft = ((anchorRect.left - popupRect.left) + (anchorRect.width / 2) - HALF_ARROW_WIDTH) + "px"; + if (vertPos == 1) { + container.dir = "ltr"; + anchorClass = "top"; + } else if (vertPos == -1) { + container.dir = "reverse"; + anchorClass = "bottom"; + } + } + } else if (vertPos == 0) { + container.orient = ""; + arrowbox.orient = "vertical"; + arrowbox.style.marginTop = ((anchorRect.top - popupRect.top) + (anchorRect.height / 2) - HALF_ARROW_WIDTH) + "px"; + if (horizPos == 1) { + container.dir = "ltr"; + anchorClass = "left"; + } else if (horizPos == -1) { + container.dir = "reverse"; + anchorClass = "right"; + } + } else { + hideArrow = true; + } + arrow.hidden = hideArrow; + arrow.setAttribute("side", anchorClass); + ]]> + + + + + + + diff --git a/mobile/chrome/content/bindings/browser.js b/mobile/chrome/content/bindings/browser.js new file mode 100644 index 000000000000..c1bc7a110d32 --- /dev/null +++ b/mobile/chrome/content/bindings/browser.js @@ -0,0 +1,455 @@ +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- +let Cc = Components.classes; +let Ci = Components.interfaces; + +dump("!! remote browser loaded\n") + +let WebProgressListener = { + _lastLocation: null, + + init: function() { + let flags = Ci.nsIWebProgress.NOTIFY_LOCATION | + Ci.nsIWebProgress.NOTIFY_SECURITY | + Ci.nsIWebProgress.NOTIFY_STATE_NETWORK | Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT; + + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress); + webProgress.addProgressListener(this, flags); + }, + + onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + if (content != aWebProgress.DOMWindow) + return; + + let json = { + contentWindowId: content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, + stateFlags: aStateFlags, + status: aStatus + }; + + sendAsyncMessage("Content:StateChange", json); + }, + + onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) { + }, + + onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI) { + if (content != aWebProgress.DOMWindow) + return; + + let spec = aLocationURI ? aLocationURI.spec : ""; + let location = spec.split("#")[0]; + + let charset = content.document.characterSet; + + let json = { + contentWindowId: content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, + documentURI: aWebProgress.DOMWindow.document.documentURIObject.spec, + location: spec, + canGoBack: docShell.canGoBack, + canGoForward: docShell.canGoForward, + charset: charset.toString() + }; + + sendAsyncMessage("Content:LocationChange", json); + + // Keep track of hash changes + this.hashChanged = (location == this._lastLocation); + this._lastLocation = location; + + // When a new page is loaded fire a message for the first paint + addEventListener("MozAfterPaint", function(aEvent) { + removeEventListener("MozAfterPaint", arguments.callee, true); + + let scrollOffset = ContentScroll.getScrollOffset(content); + sendAsyncMessage("Browser:FirstPaint", scrollOffset); + }, true); + }, + + onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { + }, + + onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) { + if (content != aWebProgress.DOMWindow) + return; + + let serialization = SecurityUI.getSSLStatusAsString(); + + let json = { + contentWindowId: content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, + SSLStatusAsString: serialization, + state: aState + }; + + sendAsyncMessage("Content:SecurityChange", json); + }, + + QueryInterface: function QueryInterface(aIID) { + if (aIID.equals(Ci.nsIWebProgressListener) || + aIID.equals(Ci.nsISupportsWeakReference) || + aIID.equals(Ci.nsISupports)) { + return this; + } + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +}; + +WebProgressListener.init(); + + +let SecurityUI = { + getSSLStatusAsString: function() { + let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; + + if (status) { + let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Ci.nsISerializationHelper); + + status.QueryInterface(Ci.nsISerializable); + return serhelper.serializeToString(status); + } + + return null; + } +}; + +let WebNavigation = { + _webNavigation: docShell.QueryInterface(Ci.nsIWebNavigation), + + init: function() { + addMessageListener("WebNavigation:GoBack", this); + addMessageListener("WebNavigation:GoForward", this); + addMessageListener("WebNavigation:GotoIndex", this); + addMessageListener("WebNavigation:LoadURI", this); + addMessageListener("WebNavigation:Reload", this); + addMessageListener("WebNavigation:Stop", this); + }, + + receiveMessage: function(message) { + switch (message.name) { + case "WebNavigation:GoBack": + this.goBack(message); + break; + case "WebNavigation:GoForward": + this.goForward(message); + break; + case "WebNavigation:GotoIndex": + this.gotoIndex(message); + break; + case "WebNavigation:LoadURI": + this.loadURI(message); + break; + case "WebNavigation:Reload": + this.reload(message); + break; + case "WebNavigation:Stop": + this.stop(message); + break; + } + }, + + goBack: function() { + this._webNavigation.goBack(); + }, + + goForward: function() { + this._webNavigation.goForward(); + }, + + gotoIndex: function(message) { + this._webNavigation.gotoIndex(message.index); + }, + + loadURI: function(message) { + let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE; + this._webNavigation.loadURI(message.json.uri, flags, null, null, null); + }, + + reload: function(message) { + let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE; + this._webNavigation.reload(flags); + }, + + stop: function(message) { + let flags = message.json.flags || this._webNavigation.STOP_ALL; + this._webNavigation.stop(flags); + } +}; + +WebNavigation.init(); + + +let DOMEvents = { + init: function() { + addEventListener("DOMContentLoaded", this, false); + addEventListener("DOMTitleChanged", this, false); + addEventListener("DOMLinkAdded", this, false); + addEventListener("DOMWillOpenModalDialog", this, false); + addEventListener("DOMModalDialogClosed", this, true); + addEventListener("DOMWindowClose", this, false); + addEventListener("DOMPopupBlocked", this, false); + addEventListener("pageshow", this, false); + addEventListener("pagehide", this, false); + }, + + handleEvent: function(aEvent) { + let document = content.document; + switch (aEvent.type) { + case "DOMContentLoaded": + if (document.documentURIObject.spec == "about:blank") + return; + + sendAsyncMessage("DOMContentLoaded", { }); + break; + + case "pageshow": + case "pagehide": { + if (aEvent.target.defaultView != content) + break; + + let util = aEvent.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + let json = { + contentWindowWidth: content.innerWidth, + contentWindowHeight: content.innerHeight, + windowId: util.outerWindowID, + persisted: aEvent.persisted + }; + + // Clear onload focus to prevent the VKB to be shown unexpectingly + // but only if the location has really changed and not only the + // fragment identifier + let contentWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; + if (!WebProgressListener.hashChanged && contentWindowID == util.currentInnerWindowID) { + let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + focusManager.clearFocus(content); + } + + sendAsyncMessage(aEvent.type, json); + break; + } + + case "DOMPopupBlocked": { + let util = aEvent.requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let json = { + windowId: util.outerWindowID, + popupWindowURI: { + spec: aEvent.popupWindowURI.spec, + charset: aEvent.popupWindowURI.originCharset + }, + popupWindowFeatures: aEvent.popupWindowFeatures, + popupWindowName: aEvent.popupWindowName + }; + + sendAsyncMessage("DOMPopupBlocked", json); + break; + } + + case "DOMTitleChanged": + sendAsyncMessage("DOMTitleChanged", { title: document.title }); + break; + + case "DOMLinkAdded": + let target = aEvent.originalTarget; + if (!target.href || target.disabled) + return; + + let json = { + windowId: target.ownerDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, + href: target.href, + charset: document.characterSet, + title: target.title, + rel: target.rel, + type: target.type + }; + + sendAsyncMessage("DOMLinkAdded", json); + break; + + case "DOMWillOpenModalDialog": + case "DOMModalDialogClosed": + case "DOMWindowClose": + let retvals = sendSyncMessage(aEvent.type, { }); + for (let i in retvals) { + if (retvals[i].preventDefault) { + aEvent.preventDefault(); + break; + } + } + break; + } + } +}; + +DOMEvents.init(); + +let ContentScroll = { + _scrollOffset: { x: 0, y: 0 }, + + init: function() { + addMessageListener("Content:SetCacheViewport", this); + addMessageListener("Content:SetWindowSize", this); + + addEventListener("scroll", this, false); + addEventListener("pagehide", this, false); + addEventListener("MozScrolledAreaChanged", this, false); + }, + + getScrollOffset: function(aWindow) { + let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + let scrollX = {}, scrollY = {}; + cwu.getScrollXY(false, scrollX, scrollY); + return { x: scrollX.value, y: scrollY.value }; + }, + + getScrollOffsetForElement: function(aElement) { + if (aElement.parentNode == aElement.ownerDocument) + return this.getScrollOffset(aElement.ownerDocument.defaultView); + return { x: aElement.scrollLeft, y: aElement.scrollTop }; + }, + + setScrollOffsetForElement: function(aElement, aLeft, aTop) { + if (aElement.parentNode == aElement.ownerDocument) { + aElement.ownerDocument.defaultView.scrollTo(aLeft, aTop); + } else { + aElement.scrollLeft = aLeft; + aElement.scrollTop = aTop; + } + }, + + receiveMessage: function(aMessage) { + let json = aMessage.json; + switch (aMessage.name) { + case "Content:SetCacheViewport": { + // Set resolution for root view + let rootCwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + if (json.id == 1) + rootCwu.setResolution(json.scale, json.scale); + + let displayport = new Rect(json.x, json.y, json.w, json.h); + if (displayport.isEmpty()) + break; + + // Map ID to element + let element = rootCwu.findElementWithViewId(json.id); + if (!element) + break; + + // Set the scroll offset for this element if specified + if (json.scrollX >= 0 && json.scrollY >= 0) { + this.setScrollOffsetForElement(element, json.scrollX, json.scrollY) + if (json.id == 1) + this._scrollOffset = this.getScrollOffset(content); + } + + // Set displayport. We want to set this after setting the scroll offset, because + // it is calculated based on the scroll offset. + let scrollOffset = this.getScrollOffsetForElement(element); + let x = displayport.x - scrollOffset.x; + let y = displayport.y - scrollOffset.y; + + if (json.id == 1) { + x = Math.round(x * json.scale) / json.scale; + y = Math.round(y * json.scale) / json.scale; + } + + let win = element.ownerDocument.defaultView; + let winCwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + winCwu.setDisplayPortForElement(x, y, displayport.width, displayport.height, element); + + // XXX If we scrolled during this displayport update, then it is the + // end of a pan. Due to bug 637852, there may be seaming issues + // with the visible content, so we need to redraw. + if (json.id == 1 && json.scrollX >= 0 && json.scrollY >= 0) + win.setTimeout( + function() { + winCwu.redraw(); + }, 0); + + break; + } + + case "Content:SetWindowSize": { + let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + cwu.setCSSViewport(json.width, json.height); + break; + } + } + }, + + handleEvent: function(aEvent) { + switch (aEvent.type) { + case "pagehide": + this._scrollOffset = { x: 0, y: 0 }; + break; + + case "scroll": { + let doc = aEvent.target; + if (doc != content.document) + break; + + this.sendScroll(); + break; + } + + case "MozScrolledAreaChanged": { + let doc = aEvent.originalTarget; + if (content != doc.defaultView) // We are only interested in root scroll pane changes + return; + + // Adjust width and height from the incoming event properties so that we + // ignore changes to width and height contributed by growth in page + // quadrants other than x > 0 && y > 0. + let scrollOffset = this.getScrollOffset(content); + let x = aEvent.x + scrollOffset.x; + let y = aEvent.y + scrollOffset.y; + let width = aEvent.width + (x < 0 ? x : 0); + let height = aEvent.height + (y < 0 ? y : 0); + + sendAsyncMessage("MozScrolledAreaChanged", { + width: width, + height: height + }); + + break; + } + } + }, + + sendScroll: function sendScroll() { + let scrollOffset = this.getScrollOffset(content); + if (this._scrollOffset.x == scrollOffset.x && this._scrollOffset.y == scrollOffset.y) + return; + + this._scrollOffset = scrollOffset; + sendAsyncMessage("scroll", scrollOffset); + } +}; + +ContentScroll.init(); + +let ContentActive = { + init: function() { + addMessageListener("Content:Activate", this); + addMessageListener("Content:Deactivate", this); + }, + + receiveMessage: function(aMessage) { + let json = aMessage.json; + switch (aMessage.name) { + case "Content:Deactivate": + docShell.isActive = false; + let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + cwu.setDisplayPortForElement(0,0,0,0,content.document.documentElement); + break; + + case "Content:Activate": + docShell.isActive = true; + break; + } + } +}; + +ContentActive.init(); diff --git a/mobile/chrome/content/bindings/browser.xml b/mobile/chrome/content/bindings/browser.xml new file mode 100644 index 000000000000..a88db16c735b --- /dev/null +++ b/mobile/chrome/content/bindings/browser.xml @@ -0,0 +1,1175 @@ + + + + + + %findBarDTD; +]> + + + + + + null + + + + + + [] + + + null + + + null + + + + null + + + Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + null + + + + + + + 0 + 0 + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + binding. + this.removeEventListener("pageshow", this.onPageShow, true); + this.removeEventListener("pagehide", this.onPageHide, true); + this.removeEventListener("DOMPopupBlocked", this.onPopupBlocked, true); + ]]> + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + ({}) + + + + + = this.kDieTime) + this._die(); + else + // This doesn't need to be exact, just be sure to clean up at some point. + this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime); + }, + + /** Cleanup after ourselves. */ + _die: function() { + let timeout = this._timeout; + if (timeout) { + clearTimeout(timeout); + this._timeout = null; + } + + if (this._contentView && this._pixelsPannedSinceRefresh > 0) { + this._updateCacheViewport(); + } + + // We expect contentViews to contain our ID. If not, something bad + // happened. + delete this.self._contentViews[this._id]; + }, + + /** + * Given the cache size and the viewport size, this determines where the cache + * should start relative to the scroll position. This adjusts the position based + * on which direction the user is panning, so that we use our cache as + * effectively as possible. + * + * @param aDirection Negative means user is panning to the left or above + * Zero means user did not pan + * Positive means user is panning to the right or below + * @param aViewportSize The width or height of the viewport + * @param aCacheSize The width or height of the displayport + */ + _getRelativeCacheStart: function(aDirection, aViewportSize, aCacheSize) { + // Remember that this is relative to the viewport scroll position. + // Let's assume we are thinking about the y-axis. + // The extreme cases: + // |0| would mean that there is no content available above + // |aViewportSize - aCacheSize| would mean no content available below + // + // Taking the average of the extremes puts equal amounts of cache on the + // top and bottom of the viewport. If we think of this like a weighted + // average, .5 is the sweet spot where equals amounts of content are + // above and below the visible area. + // + // This weight is therefore how much of the cache is above (or to the + // left) the visible area. + let cachedAbove = .5; + + // If panning down, leave only 25% of the non-visible cache above. + if (aDirection > 0) + cachedAbove = .25; + + // If panning up, Leave 75% of the non-visible cache above. + if (aDirection < 0) + cachedAbove = .75; + + return (aViewportSize - aCacheSize) * cachedAbove; + }, + + /** Determine size of the pixel cache. */ + _getCacheSize: function(viewportSize) { + let self = this.self; + let contentView = this._contentView; + + let cacheWidth = self._cacheRatioWidth * viewportSize.width; + let cacheHeight = self._cacheRatioHeight * viewportSize.height; + let contentSize = this._getContentSize(); + let contentWidth = contentSize.width; + let contentHeight = contentSize.height; + + // There are common cases, such as long skinny pages, where our cache size is + // bigger than our content size. In those cases, we take that sliver of leftover + // space and apply it to the other dimension. + if (contentWidth < cacheWidth) { + cacheHeight += (cacheWidth - contentWidth) * cacheHeight / cacheWidth; + cacheWidth = contentWidth; + } else if (contentHeight < cacheHeight) { + cacheWidth += (cacheHeight - contentHeight) * cacheWidth / cacheHeight; + cacheHeight = contentHeight; + } + + return { width: cacheWidth, height: cacheHeight }; + }, + + _sendDisplayportUpdate: function(scrollX, scrollY) { + let self = this.self; + if (!self.active) + return; + + let contentView = this._contentView; + let viewportSize = this._getViewportSize(); + let cacheSize = this._getCacheSize(viewportSize); + let cacheX = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.x, viewportSize.width, cacheSize.width) + contentView.scrollX; + let cacheY = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.y, viewportSize.height, cacheSize.height) + contentView.scrollY; + let contentSize = this._getContentSize(); + + // Use our pixels efficiently and don't try to cache things outside of content + // boundaries. + let bounds = new Rect(0, 0, contentSize.width, contentSize.height); + let displayport = new Rect(cacheX, cacheY, cacheSize.width, cacheSize.height); + displayport.translateInside(bounds); + + let rootScale = self.scale; + self.messageManager.sendAsyncMessage("Content:SetCacheViewport", { + scrollX: Math.round(scrollX) / rootScale, + scrollY: Math.round(scrollY) / rootScale, + x: Math.round(displayport.x) / rootScale, + y: Math.round(displayport.y) / rootScale, + w: Math.round(displayport.width) / rootScale, + h: Math.round(displayport.height) / rootScale, + scale: rootScale, + id: contentView.id + }); + + this._pixelsPannedSinceRefresh.x = 0; + this._pixelsPannedSinceRefresh.y = 0; + }, + + _updateCSSViewport: function() { + let contentView = this._contentView; + this._sendDisplayportUpdate(contentView.scrollX, + contentView.scrollY); + }, + + /** + * The cache viewport is what parts of content is cached in the parent process for + * fast scrolling. This syncs that up with the current projection viewport. + */ + _updateCacheViewport: function() { + // Do not update scroll values for content. + if (this.isRoot()) + this._sendDisplayportUpdate(-1, -1); + else { + let contentView = this._contentView; + this._sendDisplayportUpdate(contentView.scrollX, + contentView.scrollY); + } + }, + + _getContentSize: function() { + let self = this.self; + if (this.isRoot()) { + // XXX Bug 626792 means contentWidth and contentHeight aren't always + // updated immediately. This makes the displayport go haywire so + // use contentDocument properties. + return { width: self._contentDocumentWidth * this._scale, + height: self._contentDocumentHeight * this._scale }; + } else { + return { width: this._contentView.contentWidth, + height: this._contentView.contentHeight }; + } + }, + + _getViewportSize: function() { + let self = this.self; + if (this.isRoot()) { + let bcr = self.getBoundingClientRect(); + return { width: bcr.width, height: bcr.height }; + } else { + return { width: this._contentView.viewportWidth, height: this._contentView.viewportHeight }; + } + }, + + init: function(contentView) { + let self = this.self; + + this._contentView = contentView; + this._id = contentView.id; + this._scale = 1; + self._contentViews[this._id] = this; + + if (!this.isRoot()) { + // Non-root content views are short lived. + this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime); + // This iframe may not have a display port yet, so build up a cache + // immediately. + this._updateCacheViewport(); + } + }, + + isRoot: function() { + return this.self._contentViewManager.rootContentView == this._contentView; + }, + + scrollBy: function(x, y) { + // Bounding content rectangle is in device pixels + let contentView = this._contentView; + let viewportSize = this._getViewportSize(); + let contentSize = this._getContentSize(); + // Calculate document dimensions in device pixels + let scrollRangeX = contentSize.width - viewportSize.width; + let scrollRangeY = contentSize.height - viewportSize.height; + + x = Math.floor(Math.max(0, Math.min(scrollRangeX, contentView.scrollX + x))) - contentView.scrollX; + y = Math.floor(Math.max(0, Math.min(scrollRangeY, contentView.scrollY + y))) - contentView.scrollY; + + if (x == 0 && y == 0) + return; + + contentView.scrollBy(x, y); + + this._lastPanTime = Date.now(); + + this._pixelsPannedSinceRefresh.x += x; + this._pixelsPannedSinceRefresh.y += y; + if (Math.abs(this._pixelsPannedSinceRefresh.x) > 20 || + Math.abs(this._pixelsPannedSinceRefresh.y) > 20) + this._updateCacheViewport(); + }, + + scrollTo: function(x, y) { + let contentView = this._contentView; + this.scrollBy(x - contentView.scrollX, y - contentView.scrollY); + }, + + _setScale: function _setScale(scale) { + this._scale = scale; + this._contentView.setScale(scale, scale); + }, + + getPosition: function() { + let contentView = this._contentView; + return { x: contentView.scrollX, y: contentView.scrollY }; + } + }) + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/content/bindings/checkbox.xml b/mobile/chrome/content/bindings/checkbox.xml new file mode 100644 index 000000000000..b19e236e301f --- /dev/null +++ b/mobile/chrome/content/bindings/checkbox.xml @@ -0,0 +1,107 @@ + + + + + + %checkboxDTD; +]> + + + + + + + + + + + + + + + document.getAnonymousElementByAttribute(this, "anonid", "group"); + + + + document.getAnonymousElementByAttribute(this, "anonid", "on"); + + + + document.getAnonymousElementByAttribute(this, "anonid", "off"); + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/content/bindings/console.xml b/mobile/chrome/content/bindings/console.xml new file mode 100644 index 000000000000..f13335f16d18 --- /dev/null +++ b/mobile/chrome/content/bindings/console.xml @@ -0,0 +1,57 @@ + + + +%browserDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/content/bindings/dialog.xml b/mobile/chrome/content/bindings/dialog.xml new file mode 100644 index 000000000000..8843a3a5db2c --- /dev/null +++ b/mobile/chrome/content/bindings/dialog.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/content/bindings/downloads.xml b/mobile/chrome/content/bindings/downloads.xml new file mode 100644 index 000000000000..a6e8c47275bb --- /dev/null +++ b/mobile/chrome/content/bindings/downloads.xml @@ -0,0 +1,194 @@ + + + +%browserDTD; +]> + + + + + + Components.interfaces.nsIDownloadManager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/content/bindings/extensions.xml b/mobile/chrome/content/bindings/extensions.xml new file mode 100644 index 000000000000..29db8572a6b5 --- /dev/null +++ b/mobile/chrome/content/bindings/extensions.xml @@ -0,0 +1,285 @@ + + + +%browserDTD; + +%brandDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0) && !("LightWeightThemeWebInstaller" in window)) { + document.getAnonymousElementByAttribute(this, "anonid", "enable-button").setAttribute("disabled", "true"); + document.getAnonymousElementByAttribute(this, "anonid", "enable-button").nextElementSibling.setAttribute("disabled", "true"); + document.getAnonymousElementByAttribute(this, "anonid", "uninstall-button").setAttribute("disabled", "true"); + } + ]]> + + + + + + + + + + + + + + + + + for now + let prefs = xhr.responseXML.querySelectorAll(":root > setting"); + for (let i = 0; i < prefs.length; i++) + box.appendChild(prefs.item(i)); + + // Send an event so add-ons can prepopulate any non-preference based + // settings + let event = document.createEvent("Events"); + event.initEvent("AddonOptionsLoad", true, false); + this.dispatchEvent(event); + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + document.getAnonymousElementByAttribute(this, "anonid", "show-page"); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/content/bindings/pageaction.xml b/mobile/chrome/content/bindings/pageaction.xml new file mode 100644 index 000000000000..7f68f74f35a4 --- /dev/null +++ b/mobile/chrome/content/bindings/pageaction.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/content/bindings/setting.xml b/mobile/chrome/content/bindings/setting.xml new file mode 100644 index 000000000000..202db85c6d3d --- /dev/null +++ b/mobile/chrome/content/bindings/setting.xml @@ -0,0 +1,361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + document.getAnonymousElementByAttribute(this, "anonid", "input"); + + + this.parentNode.localName == "settings" ? this.parentNode : null; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/content/browser-scripts.js b/mobile/chrome/content/browser-scripts.js new file mode 100644 index 000000000000..1ac8b77158f9 --- /dev/null +++ b/mobile/chrome/content/browser-scripts.js @@ -0,0 +1,151 @@ +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- +/* ***** 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 Mobile Browser. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle + * + * 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 ***** */ + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +/** + * Delay load some JS modules + */ +XPCOMUtils.defineLazyGetter(this, "PluralForm", function() { + Cu.import("resource://gre/modules/PluralForm.jsm"); + return PluralForm; +}); + +XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() { + Cu.import("resource://gre/modules/PlacesUtils.jsm"); + return PlacesUtils; +}); + +/* window.Rect is used by http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-Rect + * so it is not possible to set a lazy getter for Geometry.jsm + */ +Cu.import("resource://gre/modules/Geometry.jsm"); + +/** + * Delay load some objects from a common script. We only load the script once, + * into a namespace, then access each object from the namespace. + */ +XPCOMUtils.defineLazyGetter(this, "CommonUI", function() { + let CommonUI = {}; + Services.scriptloader.loadSubScript("chrome://browser/content/common-ui.js", CommonUI); + return CommonUI; +}); + +[ + ["FullScreenVideo"], + ["BadgeHandlers"], + ["ContextHelper"], + ["FormHelperUI"], + ["FindHelperUI"], + ["NewTabPopup"], + ["PageActions"], + ["BrowserSearch"], + ["CharsetMenu"] +].forEach(function (aObject) { + XPCOMUtils.defineLazyGetter(window, aObject, function() { + return CommonUI[aObject]; + }); +}); + +/** + * Delay load some browser scripts + */ +[ + ["AlertsHelper", "chrome://browser/content/AlertsHelper.js"], + ["AnimatedZoom", "chrome://browser/content/AnimatedZoom.js"], + ["AppMenu", "chrome://browser/content/AppMenu.js"], + ["AwesomePanel", "chrome://browser/content/AwesomePanel.js"], + ["BookmarkHelper", "chrome://browser/content/BookmarkHelper.js"], + ["BookmarkPopup", "chrome://browser/content/BookmarkPopup.js"], + ["CommandUpdater", "chrome://browser/content/commandUtil.js"], + ["ContextCommands", "chrome://browser/content/ContextCommands.js"], + ["ConsoleView", "chrome://browser/content/console.js"], + ["DownloadsView", "chrome://browser/content/downloads.js"], + ["ExtensionsView", "chrome://browser/content/extensions.js"], + ["MenuListHelperUI", "chrome://browser/content/MenuListHelperUI.js"], + ["OfflineApps", "chrome://browser/content/OfflineApps.js"], + ["PreferencesView", "chrome://browser/content/preferences.js"], + ["Sanitizer", "chrome://browser/content/sanitize.js"], + ["SelectHelperUI", "chrome://browser/content/SelectHelperUI.js"], + ["SharingUI", "chrome://browser/content/SharingUI.js"], +#ifdef MOZ_SERVICES_SYNC + ["WeaveGlue", "chrome://browser/content/sync.js"], +#endif + ["SSLExceptions", "chrome://browser/content/exceptions.js"] +].forEach(function (aScript) { + let [name, script] = aScript; + XPCOMUtils.defineLazyGetter(window, name, function() { + let sandbox = {}; + Services.scriptloader.loadSubScript(script, sandbox); + return sandbox[name]; + }); +}); + +#ifdef MOZ_SERVICES_SYNC +XPCOMUtils.defineLazyGetter(this, "Weave", function() { + Components.utils.import("resource://services-sync/main.js"); + return Weave; +}); +#endif + +/** + * Delay load some global scripts using a custom namespace + */ +XPCOMUtils.defineLazyGetter(this, "GlobalOverlay", function() { + let GlobalOverlay = {}; + Services.scriptloader.loadSubScript("chrome://global/content/globalOverlay.js", GlobalOverlay); + return GlobalOverlay; +}); + +XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() { + let ContentAreaUtils = {}; + Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils); + return ContentAreaUtils; +}); + +XPCOMUtils.defineLazyGetter(this, "ZoomManager", function() { + let sandbox = {}; + Services.scriptloader.loadSubScript("chrome://global/content/viewZoomOverlay.js", sandbox); + return sandbox.ZoomManager; +}); + +XPCOMUtils.defineLazyServiceGetter(window, "gHistSvc", "@mozilla.org/browser/nav-history-service;1", "nsINavHistoryService", "nsIBrowserHistory"); +XPCOMUtils.defineLazyServiceGetter(window, "gURIFixup", "@mozilla.org/docshell/urifixup;1", "nsIURIFixup"); +XPCOMUtils.defineLazyServiceGetter(window, "gFaviconService", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService"); +XPCOMUtils.defineLazyServiceGetter(window, "gFocusManager", "@mozilla.org/focus-manager;1", "nsIFocusManager"); diff --git a/mobile/chrome/content/browser-ui.js b/mobile/chrome/content/browser-ui.js new file mode 100644 index 000000000000..10d3d971b800 --- /dev/null +++ b/mobile/chrome/content/browser-ui.js @@ -0,0 +1,1267 @@ +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- +/* ***** 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 Mobile Browser. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle + * Matt Brubeck + * + * 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 ***** */ + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +[ + ["AllPagesList", "popup_autocomplete", "cmd_openLocation"], + ["HistoryList", "history-items", "cmd_history"], + ["BookmarkList", "bookmarks-items", "cmd_bookmarks"], +#ifdef MOZ_SERVICES_SYNC + ["RemoteTabsList", "remotetabs-items", "cmd_remoteTabs"] +#endif +].forEach(function(aPanel) { + let [name, id, command] = aPanel; + XPCOMUtils.defineLazyGetter(window, name, function() { + return new AwesomePanel(id, command); + }); +}); + +/** + * Cache of commonly used elements. + */ +let Elements = {}; + +[ + ["contentShowing", "bcast_contentShowing"], + ["urlbarState", "bcast_urlbarState"], + ["stack", "stack"], + ["tabs", "tabs-container"], + ["controls", "browser-controls"], + ["panelUI", "panel-container"], + ["toolbarContainer", "toolbar-container"], + ["browsers", "browsers"], + ["contentViewport", "content-viewport"], + ["contentNavigator", "content-navigator"] +].forEach(function (aElementGlobal) { + let [name, id] = aElementGlobal; + XPCOMUtils.defineLazyGetter(Elements, name, function() { + return document.getElementById(id); + }); +}); + +/** + * Cache of commonly used string bundles. + */ +var Strings = {}; +[ + ["browser", "chrome://browser/locale/browser.properties"], + ["brand", "chrome://branding/locale/brand.properties"] +].forEach(function (aStringBundle) { + let [name, bundle] = aStringBundle; + XPCOMUtils.defineLazyGetter(Strings, name, function() { + return Services.strings.createBundle(bundle); + }); +}); + +const TOOLBARSTATE_LOADING = 1; +const TOOLBARSTATE_LOADED = 2; + +var BrowserUI = { + _edit: null, + _title: null, + _throbber: null, + _favicon: null, + _dialogs: [], + + _domWillOpenModalDialog: function(aBrowser) { + // We're about to open a modal dialog, make sure the opening + // tab is brought to the front. + + for (let i = 0; i < Browser.tabs.length; i++) { + if (Browser._tabs[i].browser == aBrowser) { + Browser.selectedTab = Browser.tabs[i]; + break; + } + } + }, + + _titleChanged: function(aBrowser) { + let browser = Browser.selectedBrowser; + if (browser && aBrowser != browser) + return; + + let url = this.getDisplayURI(browser); + let caption = browser.contentTitle || url; + + if (browser.contentTitle == "" && !Util.isURLEmpty(browser.userTypedValue)) + caption = browser.userTypedValue; + else if (Util.isURLEmpty(url)) + caption = ""; + + if (caption) { + this._title.value = caption; + this._title.classList.remove("placeholder"); + } else { + this._title.value = this._title.getAttribute("placeholder"); + this._title.classList.add("placeholder"); + } + }, + + /* + * Dispatched by window.close() to allow us to turn window closes into tabs + * closes. + */ + _domWindowClose: function(aBrowser) { + // Find the relevant tab, and close it. + let browsers = Browser.browsers; + for (let i = 0; i < browsers.length; i++) { + if (browsers[i] == aBrowser) { + Browser.closeTab(Browser.getTabAtIndex(i)); + return { preventDefault: true }; + } + } + }, + + _updateButtons: function(aBrowser) { + let back = document.getElementById("cmd_back"); + let forward = document.getElementById("cmd_forward"); + + back.setAttribute("disabled", !aBrowser.canGoBack); + forward.setAttribute("disabled", !aBrowser.canGoForward); + }, + + _updateToolbar: function _updateToolbar() { + let mode = Elements.urlbarState.getAttribute("mode"); + if (mode == "edit" && this.activePanel) + return; + + if (Browser.selectedTab.isLoading() && mode != "loading") + Elements.urlbarState.setAttribute("mode", "loading"); + else if (mode != "view") + Elements.urlbarState.setAttribute("mode", "view"); + }, + + _tabSelect: function(aEvent) { + let browser = Browser.selectedBrowser; + this._titleChanged(browser); + this._updateToolbar(); + this._updateButtons(browser); + this._updateIcon(browser.mIconURL); + this.updateStar(); + }, + + _toolbarLocked: 0, + + isToolbarLocked: function isToolbarLocked() { + return this._toolbarLocked; + }, + + lockToolbar: function lockToolbar() { + this._toolbarLocked++; + document.getElementById("toolbar-moveable-container").top = "0"; + if (this._toolbarLocked == 1) + Browser.forceChromeReflow(); + }, + + unlockToolbar: function unlockToolbar() { + if (!this._toolbarLocked) + return; + + this._toolbarLocked--; + if (!this._toolbarLocked) + document.getElementById("toolbar-moveable-container").top = ""; + }, + + _setURL: function _setURL(aURL) { + if (this.activePanel) + this._edit.defaultValue = aURL; + else + this._edit.value = aURL; + }, + + _editURI: function _editURI(aEdit) { + Elements.urlbarState.setAttribute("mode", "edit"); + this._edit.defaultValue = this._edit.value; + }, + + _showURI: function _showURI() { + // Replace the web page title by the url of the page + let urlString = this.getDisplayURI(Browser.selectedBrowser); + if (Util.isURLEmpty(urlString)) + urlString = ""; + + this._edit.value = urlString; + }, + + updateAwesomeHeader: function updateAwesomeHeader(aString) { + document.getElementById("awesome-header").hidden = (aString != ""); + + // During an awesome search we always show the popup_autocomplete/AllPagesList + // panel since this looks in every places and the rationale behind typing + // is to find something, whereever it is. + if (this.activePanel != AllPagesList) { + let inputField = this._edit; + let oldClickSelectsAll = inputField.clickSelectsAll; + inputField.clickSelectsAll = false; + + this.activePanel = AllPagesList; + + // changing the searchString property call updateAwesomeHeader again + inputField.controller.searchString = aString; + inputField.readOnly = false; + inputField.clickSelectsAll = oldClickSelectsAll; + return; + } + + let event = document.createEvent("Events"); + event.initEvent("onsearchbegin", true, true); + this._edit.dispatchEvent(event); + }, + + _closeOrQuit: function _closeOrQuit() { + // Close active dialog, if we have one. If not then close the application. + if (this.activePanel) { + this.activePanel = null; + } else if (this.activeDialog) { + this.activeDialog.close(); + } else { + // Check to see if we should really close the window + if (Browser.closing()) + window.close(); + } + }, + + _activePanel: null, + get activePanel() { + return this._activePanel; + }, + + set activePanel(aPanel) { + if (this._activePanel == aPanel) + return; + + let awesomePanel = document.getElementById("awesome-panels"); + let awesomeHeader = document.getElementById("awesome-header"); + + let willShowPanel = (!this._activePanel && aPanel); + if (willShowPanel) { + this.pushDialog(aPanel); + this._edit.attachController(); + this._editURI(); + awesomePanel.hidden = awesomeHeader.hidden = false; + }; + + if (aPanel) { + aPanel.open(); + if (this._edit.value == "") + this._showURI(); + } + + let willHidePanel = (this._activePanel && !aPanel); + if (willHidePanel) { + awesomePanel.hidden = true; + awesomeHeader.hidden = false; + this._edit.reset(); + this._edit.detachController(); + this.popDialog(); + } + + if (this._activePanel) + this._activePanel.close(); + + // The readOnly state of the field enabled/disabled the VKB + let isReadOnly = !(aPanel == AllPagesList && Util.isPortrait() && (willShowPanel || !this._edit.readOnly)); + this._edit.readOnly = isReadOnly; + if (isReadOnly) + this._edit.blur(); + + this._activePanel = aPanel; + if (willHidePanel || willShowPanel) { + let event = document.createEvent("UIEvents"); + event.initUIEvent("NavigationPanel" + (willHidePanel ? "Hidden" : "Shown"), true, true, window, false); + window.dispatchEvent(event); + } + }, + + get activeDialog() { + // Return the topmost dialog + if (this._dialogs.length) + return this._dialogs[this._dialogs.length - 1]; + return null; + }, + + pushDialog: function pushDialog(aDialog) { + // If we have a dialog push it on the stack and set the attr for CSS + if (aDialog) { + this.lockToolbar(); + this._dialogs.push(aDialog); + document.getElementById("toolbar-main").setAttribute("dialog", "true"); + Elements.contentShowing.setAttribute("disabled", "true"); + } + }, + + popDialog: function popDialog() { + if (this._dialogs.length) { + this._dialogs.pop(); + this.unlockToolbar(); + } + + // If no more dialogs are being displayed, remove the attr for CSS + if (!this._dialogs.length) { + document.getElementById("toolbar-main").removeAttribute("dialog"); + Elements.contentShowing.removeAttribute("disabled"); + } + }, + + pushPopup: function pushPopup(aPanel, aElements) { + this._hidePopup(); + this._popup = { "panel": aPanel, + "elements": (aElements instanceof Array) ? aElements : [aElements] }; + this._dispatchPopupChanged(true); + }, + + popPopup: function popPopup(aPanel) { + if (!this._popup || aPanel != this._popup.panel) + return; + this._popup = null; + this._dispatchPopupChanged(false); + }, + + _dispatchPopupChanged: function _dispatchPopupChanged(aVisible) { + let stack = document.getElementById("stack"); + let event = document.createEvent("UIEvents"); + event.initUIEvent("PopupChanged", true, true, window, aVisible); + event.popup = this._popup; + stack.dispatchEvent(event); + }, + + _hidePopup: function _hidePopup() { + if (!this._popup) + return; + let panel = this._popup.panel; + if (panel.hide) + panel.hide(); + }, + + _isEventInsidePopup: function _isEventInsidePopup(aEvent) { + if (!this._popup) + return false; + let elements = this._popup.elements; + let targetNode = aEvent ? aEvent.target : null; + while (targetNode && elements.indexOf(targetNode) == -1) + targetNode = targetNode.parentNode; + return targetNode ? true : false; + }, + + switchPane: function switchPane(aPanelId) { + this.blurFocusedElement(); + + let panels = document.getElementById("panel-items") + let panel = aPanelId ? document.getElementById(aPanelId) : panels.selectedPanel; + let oldPanel = panels.selectedPanel; + + if (oldPanel != panel) { + panels.selectedPanel = panel; + let button = document.getElementsByAttribute("linkedpanel", aPanelId)[0]; + if (button) + button.checked = true; + + let event = document.createEvent("Events"); + event.initEvent("ToolPanelHidden", true, true); + oldPanel.dispatchEvent(event); + } + + let event = document.createEvent("Events"); + event.initEvent("ToolPanelShown", true, true); + panel.dispatchEvent(event); + }, + + get toolbarH() { + if (!this._toolbarH) { + let toolbar = document.getElementById("toolbar-main"); + this._toolbarH = toolbar.boxObject.height; + } + return this._toolbarH; + }, + + get sidebarW() { + delete this._sidebarW; + return this._sidebarW = Elements.controls.getBoundingClientRect().width; + }, + + get starButton() { + delete this.starButton; + return this.starButton = document.getElementById("tool-star"); + }, + + sizeControls: function(windowW, windowH) { + // tabs + document.getElementById("tabs").resize(); + + // awesomebar and related panels + let popup = document.getElementById("awesome-panels"); + popup.top = this.toolbarH; + popup.height = windowH - this.toolbarH; + popup.width = windowW; + + // content navigator helper + document.getElementById("content-navigator").contentHasChanged(); + }, + + init: function() { + this._edit = document.getElementById("urlbar-edit"); + this._title = document.getElementById("urlbar-title"); + this._throbber = document.getElementById("urlbar-throbber"); + this._favicon = document.getElementById("urlbar-favicon"); + this._favicon.addEventListener("error", this, false); + + this._edit.addEventListener("click", this, false); + this._edit.addEventListener("mousedown", this, false); + + window.addEventListener("NavigationPanelShown", this, false); + window.addEventListener("NavigationPanelHidden", this, false); + + Elements.browsers.addEventListener("PanFinished", this, true); +#if MOZ_PLATFORM_MAEMO == 6 + Elements.browsers.addEventListener("SizeChanged", this, true); +#endif + + // listen content messages + messageManager.addMessageListener("DOMLinkAdded", this); + messageManager.addMessageListener("DOMTitleChanged", this); + messageManager.addMessageListener("DOMWillOpenModalDialog", this); + messageManager.addMessageListener("DOMWindowClose", this); + + messageManager.addMessageListener("Browser:OpenURI", this); + messageManager.addMessageListener("Browser:SaveAs:Return", this); + + // listening mousedown for automatically dismiss some popups (e.g. larry) + window.addEventListener("mousedown", this, true); + + // listening escape to dismiss dialog on VK_ESCAPE + window.addEventListener("keypress", this, true); + + // listening AppCommand to handle special keys + window.addEventListener("AppCommand", this, true); + + // We can delay some initialization until after startup. We wait until + // the first page is shown, then dispatch a UIReadyDelayed event. + messageManager.addMessageListener("pageshow", function() { + if (getBrowser().currentURI.spec == "about:blank") + return; + + messageManager.removeMessageListener("pageshow", arguments.callee, true); + + setTimeout(function() { + let event = document.createEvent("Events"); + event.initEvent("UIReadyDelayed", true, false); + window.dispatchEvent(event); + }, 0); + }); + + // Delay the panel UI and Sync initialization. + window.addEventListener("UIReadyDelayed", function(aEvent) { + window.removeEventListener(aEvent.type, arguments.callee, false); + + // We unhide the panelUI so the XBL and settings can initialize + Elements.panelUI.hidden = false; + + // Login Manager and Form History initialization + Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); + Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2); + + // Listen tabs event + Elements.tabs.addEventListener("TabSelect", BrowserUI, true); + Elements.tabs.addEventListener("TabOpen", BrowserUI, true); + Elements.tabs.addEventListener("TabRemove", BrowserUI, true); + + // Init the tool panel views + ExtensionsView.init(); + DownloadsView.init(); + ConsoleView.init(); + +#ifdef MOZ_IPC + // Pre-start the content process + Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) + .ensureContentProcess(); +#endif + +#ifdef MOZ_SERVICES_SYNC + // Init the sync system + WeaveGlue.init(); +#endif + + Services.obs.addObserver(BrowserSearch, "browser-search-engine-modified", false); + messageManager.addMessageListener("Browser:MozApplicationManifest", OfflineApps); + + // Init helpers + BadgeHandlers.register(BrowserUI._edit.popup); + FormHelperUI.init(); + FindHelperUI.init(); + PageActions.init(); + FullScreenVideo.init(); + NewTabPopup.init(); + CharsetMenu.init(); + + // If some add-ons were disabled during during an application update, alert user + if (Services.prefs.prefHasUserValue("extensions.disabledAddons")) { + let addons = Services.prefs.getCharPref("extensions.disabledAddons").split(","); + if (addons.length > 0) { + let disabledStrings = Strings.browser.GetStringFromName("alertAddonsDisabled"); + let label = PluralForm.get(addons.length, disabledStrings).replace("#1", addons.length); + let image = "chrome://browser/skin/images/alert-addons-30.png"; + + let alerts = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService); + alerts.showAlertNotification(image, Strings.browser.GetStringFromName("alertAddons"), label, false, "", null); + } + Services.prefs.clearUserPref("extensions.disabledAddons"); + } + +#ifdef MOZ_UPDATER + // Check for updates in progress + let updatePrompt = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt); + updatePrompt.checkForUpdates(); +#endif + }, false); + + let panels = document.getElementById("panel-items"); + let panelViews = { // Use strings to avoid lazy-loading objects too soon. + "prefs-container": "PreferencesView", + "downloads-container": "DownloadsView", + "addons-container": "ExtensionsView", + "console-container": "ConsoleView" + }; + + // Some initialization can be delayed until a panel is selected. + panels.addEventListener("ToolPanelShown", function(aEvent) { + let viewName = panelViews[panels.selectedPanel.id]; + if (viewName) + window[viewName].delayedInit(); + }, true); + +#ifndef MOZ_OFFICIAL_BRANDING + setTimeout(function() { + let startup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup).getStartupInfo(); + for (let name in startup) { + if (name != "process") + Services.console.logStringMessage("[timing] " + name + ": " + (startup[name] - startup.process) + "ms"); + } + }, 3000); +#endif + }, + + uninit: function() { + Services.obs.removeObserver(BrowserSearch, "browser-search-engine-modified"); + messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps); + ExtensionsView.uninit(); + ConsoleView.uninit(); + }, + + update: function(aState) { + let browser = Browser.selectedBrowser; + + switch (aState) { + case TOOLBARSTATE_LOADED: + this._updateToolbar(); + + this._updateIcon(browser.mIconURL); + this.unlockToolbar(); + break; + + case TOOLBARSTATE_LOADING: + this._updateToolbar(); + + browser.mIconURL = ""; + this._updateIcon(); + this.lockToolbar(); + break; + } + }, + + _updateIcon: function(aIconSrc) { + this._favicon.src = aIconSrc || ""; + if (Browser.selectedTab.isLoading()) { + this._throbber.hidden = false; + this._throbber.setAttribute("loading", "true"); + this._favicon.hidden = true; + } + else { + this._favicon.hidden = false; + this._throbber.hidden = true; + this._throbber.removeAttribute("loading"); + } + }, + + getDisplayURI: function(browser) { + let uri = browser.currentURI; + try { + uri = gURIFixup.createExposableURI(uri); + } catch (ex) {} + + return uri.spec; + }, + + /* Set the location to the current content */ + updateURI: function(aOptions) { + aOptions = aOptions || {}; + + let browser = Browser.selectedBrowser; + let urlString = this.getDisplayURI(browser); + if (Util.isURLEmpty(urlString)) + urlString = ""; + + this._setURL(urlString); + + if ("captionOnly" in aOptions && aOptions.captionOnly) + return; + + // Update the navigation buttons + this._updateButtons(browser); + + // Check for a bookmarked page + this.updateStar(); + }, + + goToURI: function(aURI) { + aURI = aURI || this._edit.value; + if (!aURI) + return; + + // Make sure we're online before attempting to load + Util.forceOnline(); + + // Give the new page lots of room + Browser.hideSidebars(); + this.closeAutoComplete(); + + this._edit.value = aURI; + + let postData = {}; + aURI = Browser.getShortcutOrURI(aURI, postData); + Browser.loadURI(aURI, { flags: Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP, postData: postData }); + + // Delay doing the fixup so the raw URI is passed to loadURIWithFlags + // and the proper third-party fixup can be done + let fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + let uri = gURIFixup.createFixupURI(aURI, fixupFlags); + gHistSvc.markPageAsTyped(uri); + + this._titleChanged(Browser.selectedBrowser); + }, + + showAutoComplete: function showAutoComplete() { + if (this.isAutoCompleteOpen()) + return; + + this.hidePanel(); + this._hidePopup(); + this.activePanel = AllPagesList; + }, + + closeAutoComplete: function closeAutoComplete() { + if (this.isAutoCompleteOpen()) + this._edit.popup.closePopup(); + + this.activePanel = null; + }, + + isAutoCompleteOpen: function isAutoCompleteOpen() { + return this.activePanel == AllPagesList; + }, + + doOpenSearch: function doOpenSearch(aName) { + // save the current value of the urlbar + let searchValue = this._edit.value; + + // Give the new page lots of room + Browser.hideSidebars(); + this.closeAutoComplete(); + + // Make sure we're online before attempting to load + Util.forceOnline(); + + let engine = Services.search.getEngineByName(aName); + let submission = engine.getSubmission(searchValue, null); + Browser.selectedBrowser.userTypedValue = submission.uri.spec; + Browser.loadURI(submission.uri.spec, { postData: submission.postData }); + + this._titleChanged(Browser.selectedBrowser); + }, + + updateUIFocus: function _updateUIFocus() { + if (Elements.contentShowing.getAttribute("disabled") == "true") + Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:Blur", { }); + }, + + updateStar: function() { + let uri = getBrowser().currentURI; + if (uri.spec == "about:blank") { + this.starButton.removeAttribute("starred"); + return; + } + + PlacesUtils.asyncGetBookmarkIds(uri, function (aItemIds) { + if (aItemIds.length) + this.starButton.setAttribute("starred", "true"); + else + this.starButton.removeAttribute("starred"); + }, this); + }, + + newTab: function newTab(aURI, aOwner) { + aURI = aURI || "about:blank"; + let tab = Browser.addTab(aURI, true, aOwner); + + this.hidePanel(); + + if (aURI == "about:blank") { + // Display awesomebar UI + this.showAutoComplete(); + } + else { + // Give the new page lots of room + Browser.hideSidebars(); + this.closeAutoComplete(); + } + + return tab; + }, + + newOrSelectTab: function newOrSelectTab(aURI, aOwner) { + let tabs = Browser.tabs; + for (let i = 0; i < tabs.length; i++) { + if (tabs[i].browser.currentURI.spec == aURI) { + Browser.selectedTab = tabs[i]; + return; + } + } + this.newTab(aURI, aOwner); + }, + + closeTab: function closeTab(aTab) { + // If no tab is passed in, assume the current tab + Browser.closeTab(aTab || Browser.selectedTab); + }, + + selectTab: function selectTab(aTab) { + this.activePanel = null; + Browser.selectedTab = aTab; + }, + + undoCloseTab: function undoCloseTab(aIndex) { + let tab = null; + let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); + if (ss.getClosedTabCount(window) > (aIndex || 0)) { + let chromeTab = ss.undoCloseTab(window, aIndex || 0); + tab = Browser.getTabFromChrome(chromeTab); + } + return tab; + }, + + isTabsVisible: function isTabsVisible() { + // The _1, _2 and _3 are to make the js2 emacs mode happy + let [leftvis,_1,_2,_3] = Browser.computeSidebarVisibility(); + return (leftvis > 0.002); + }, + + showPanel: function showPanel(aPanelId) { + if (this.activePanel) + this.activePanel = null; // Hide the awesomescreen. + + Elements.panelUI.left = 0; + Elements.panelUI.hidden = false; + Elements.contentShowing.setAttribute("disabled", "true"); + + this.switchPane(aPanelId); + }, + + hidePanel: function hidePanel() { + if (!this.isPanelVisible()) + return; + Elements.panelUI.hidden = true; + Elements.contentShowing.removeAttribute("disabled"); + this.blurFocusedElement(); + + let panels = document.getElementById("panel-items") + let event = document.createEvent("Events"); + event.initEvent("ToolPanelHidden", true, true); + panels.selectedPanel.dispatchEvent(event); + }, + + isPanelVisible: function isPanelVisible() { + return (!Elements.panelUI.hidden && Elements.panelUI.left == 0); + }, + + blurFocusedElement: function blurFocusedElement() { + let focusedElement = document.commandDispatcher.focusedElement; + if (focusedElement) + focusedElement.blur(); + }, + + switchTask: function switchTask() { + try { + let phone = Cc["@mozilla.org/phone/support;1"].createInstance(Ci.nsIPhoneSupport); + phone.switchTask(); + } catch(e) { } + }, + + handleEscape: function (aEvent) { + aEvent.stopPropagation(); + + // Check open popups + if (this._popup) { + this._hidePopup(); + return; + } + + // Check active panel + if (this.activePanel) { + this.activePanel = null; + return; + } + + // Check open dialogs + let dialog = this.activeDialog; + if (dialog) { + dialog.close(); + return; + } + + // Check open modal elements + let modalElementsLength = document.getElementsByClassName("modal-block").length; + if (modalElementsLength > 0) + return; + + // Check open panel + if (this.isPanelVisible()) { + this.hidePanel(); + return; + } + + // Check content helper + let contentHelper = document.getElementById("content-navigator"); + if (contentHelper.isActive) { + contentHelper.model.hide(); + return; + } + + // Only if there are no dialogs, popups, or panels open + let tab = Browser.selectedTab; + let browser = tab.browser; + + if (browser.canGoBack) { + browser.goBack(); + } else if (tab.owner) { + this.closeTab(tab); + } +#ifdef ANDROID + else { + window.QueryInterface(Ci.nsIDOMChromeWindow).minimize(); + if (tab.closeOnExit) + this.closeTab(tab); + } +#endif + }, + + handleEvent: function handleEvent(aEvent) { + switch (aEvent.type) { + // Browser events + case "TabSelect": + this._tabSelect(aEvent); + break; + case "TabOpen": + case "TabRemove": + { + // Workaround to hide the tabstrip if it is partially visible + // See bug 524469 and bug 626660 + let [tabsVisibility,,,] = Browser.computeSidebarVisibility(); + if (tabsVisibility > 0.0 && tabsVisibility < 1.0) + Browser.hideSidebars(); + + break; + } + case "PanFinished": + let tabs = document.getElementById("tabs"); + let [tabsVisibility,,oldLeftWidth, oldRightWidth] = Browser.computeSidebarVisibility(); + if (tabsVisibility == 0.0 && tabs.hasClosedTab) { + let { x: x1, y: y1 } = Browser.getScrollboxPosition(Browser.controlsScrollboxScroller); + tabs.removeClosedTab(); + + let [,, leftWidth, rightWidth] = Browser.computeSidebarVisibility(); + let delta = (oldLeftWidth - leftWidth) || (oldRightWidth - rightWidth); + x1 += (x1 == leftWidth) ? delta : -delta; + Browser.controlsScrollboxScroller.scrollTo(x1, 0); + Browser.tryFloatToolbar(0, 0); + } + break; + case "SizeChanged": + this.sizeControls(ViewableAreaObserver.width, ViewableAreaObserver.height); + break; + // Window events + case "keypress": + if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) + this.handleEscape(aEvent); + break; + case "AppCommand": + aEvent.stopPropagation(); + switch (aEvent.command) { + case "Menu": + this.doCommand("cmd_menu"); + break; + case "Search": + if (!this.activePanel) + AllPagesList.doCommand(); + else + this.doCommand("cmd_opensearch"); + break; + default: + break; + } + break; + // URL textbox events + case "click": + if (this._edit.readOnly) + this._edit.readOnly = false; + break; + case "mousedown": + if (!this._isEventInsidePopup(aEvent)) + this._hidePopup(); + break; + // Favicon events + case "error": + this._favicon.src = ""; + break; + // Awesome popup event + case "NavigationPanelShown": + this._edit.collapsed = false; + this._title.collapsed = true; + + if (!this._edit.readOnly) + this._edit.focus(); + + // Disabled the search button if no search engines are available + let button = document.getElementById("urlbar-icons"); + if (BrowserSearch.engines.length) + button.removeAttribute("disabled"); + else + button.setAttribute("disabled", "true"); + + break; + case "NavigationPanelHidden": { + this._edit.collapsed = true; + this._title.collapsed = false; + this._updateToolbar(); + + let button = document.getElementById("urlbar-icons"); + button.removeAttribute("open"); + button.removeAttribute("disabled"); + break; + } + } + }, + + receiveMessage: function receiveMessage(aMessage) { + let browser = aMessage.target; + let json = aMessage.json; + switch (aMessage.name) { + case "DOMTitleChanged": + this._titleChanged(browser); + break; + case "DOMWillOpenModalDialog": + return this._domWillOpenModalDialog(browser); + break; + case "DOMWindowClose": + return this._domWindowClose(browser); + break; + case "DOMLinkAdded": + if (Browser.selectedBrowser == browser) + this._updateIcon(Browser.selectedBrowser.mIconURL); + break; + case "Browser:SaveAs:Return": + if (json.type != Ci.nsIPrintSettings.kOutputFormatPDF) + return; + + let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); + let db = dm.DBConnection; + let stmt = db.createStatement("UPDATE moz_downloads SET endTime = :endTime, state = :state WHERE id = :id"); + stmt.params.endTime = Date.now() * 1000; + stmt.params.state = Ci.nsIDownloadManager.DOWNLOAD_FINISHED; + stmt.params.id = json.id; + stmt.execute(); + stmt.finalize(); + + let download = dm.getDownload(json.id); +#ifdef ANDROID + // since our content process doesn't have write permissions to the + // downloads dir, we save it to the tmp dir and then move it here + let dlFile = download.targetFile; + if (!dlFile.exists()) + dlFile.create(file.NORMAL_FILE_TYPE, 0666); + let tmpDir = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile); + let tmpFile = tmpDir.clone(); + tmpFile.append(dlFile.leafName); + + // we sometimes race with the content process, so make sure its finished + // creating/writing the file + while (!tmpFile.exists()); + tmpFile.moveTo(dlFile.parent, dlFile.leafName); +#endif + try { + DownloadsView.downloadCompleted(download); + let element = DownloadsView.getElementForDownload(json.id); + element.setAttribute("state", Ci.nsIDownloadManager.DOWNLOAD_FINISHED); + element.setAttribute("endTime", Date.now()); + element.setAttribute("referrer", json.referrer); + DownloadsView._updateTime(element); + DownloadsView._updateStatus(element); + } + catch(e) {} + Services.obs.notifyObservers(download, "dl-done", null); + break; + + case "Browser:OpenURI": + let referrerURI = null; + if (json.referrer) + referrerURI = Services.io.newURI(json.referrer, null, null); + Browser.addTab(json.uri, json.bringFront, Browser.selectedTab, { referrerURI: referrerURI }); + break; + } + }, + + supportsCommand : function(cmd) { + var isSupported = false; + switch (cmd) { + case "cmd_back": + case "cmd_forward": + case "cmd_reload": + case "cmd_forceReload": + case "cmd_stop": + case "cmd_go": + case "cmd_openLocation": + case "cmd_star": + case "cmd_opensearch": + case "cmd_bookmarks": + case "cmd_history": + case "cmd_remoteTabs": + case "cmd_quit": + case "cmd_close": + case "cmd_menu": + case "cmd_newTab": + case "cmd_closeTab": + case "cmd_undoCloseTab": + case "cmd_actions": + case "cmd_panel": + case "cmd_sanitize": + case "cmd_zoomin": + case "cmd_zoomout": + case "cmd_volumeLeft": + case "cmd_volumeRight": + case "cmd_lockscreen": + isSupported = true; + break; + default: + isSupported = false; + break; + } + return isSupported; + }, + + isCommandEnabled : function(cmd) { + let elem = document.getElementById(cmd); + if (elem && (elem.getAttribute("disabled") == "true")) + return false; + return true; + }, + + doCommand : function(cmd) { + if (!this.isCommandEnabled(cmd)) + return; + let browser = getBrowser(); + switch (cmd) { + case "cmd_back": + browser.goBack(); + break; + case "cmd_forward": + browser.goForward(); + break; + case "cmd_reload": + browser.reload(); + break; + case "cmd_forceReload": + { + // Simulate a new page + browser.lastLocation = null; + + const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | + Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; + browser.reloadWithFlags(reloadFlags); + break; + } + case "cmd_stop": + browser.stop(); + break; + case "cmd_go": + this.goToURI(); + break; + case "cmd_openLocation": + this.showAutoComplete(); + break; + case "cmd_star": + { + BookmarkPopup.toggle(); + if (!this.starButton.hasAttribute("starred")) + this.starButton.setAttribute("starred", "true"); + + let bookmarkURI = browser.currentURI; + PlacesUtils.asyncGetBookmarkIds(bookmarkURI, function (aItemIds) { + if (!aItemIds.length) { + let bookmarkTitle = browser.contentTitle || bookmarkURI.spec; + try { + let bookmarkService = PlacesUtils.bookmarks; + let bookmarkId = bookmarkService.insertBookmark(BookmarkList.panel.mobileRoot, bookmarkURI, + bookmarkService.DEFAULT_INDEX, + bookmarkTitle); + } catch (e) { + // Insert failed; reset the star state. + this.updateStar(); + } + + // XXX Used for browser-chrome tests + let event = document.createEvent("Events"); + event.initEvent("BookmarkCreated", true, false); + window.dispatchEvent(event); + } + }, this); + break; + } + case "cmd_opensearch": + this.blurFocusedElement(); + BrowserSearch.toggle(); + break; + case "cmd_bookmarks": + this.activePanel = BookmarkList; + break; + case "cmd_history": + this.activePanel = HistoryList; + break; + case "cmd_remoteTabs": + // remove the checked state set by the click it will be reset by setting + // checked on the command element if we decide to show this panel (see AwesomePanel.js) + document.getElementById("remotetabs-button").removeAttribute("checked"); + + if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED) { + this.activePanel = null; + + WeaveGlue.open(); + } else if (!Weave.Service.isLoggedIn) { + this.activePanel = null; + + BrowserUI.showPanel("prefs-container"); + let prefsBox = document.getElementById("prefs-list"); + let syncArea = document.getElementById("prefs-sync"); + if (prefsBox && syncArea) { + let prefsBoxY = prefsBox.firstChild.boxObject.screenY; + let syncAreaY = syncArea.boxObject.screenY; + setTimeout(function() { + prefsBox.scrollBoxObject.scrollTo(0, syncAreaY - prefsBoxY); + }, 0); + } + } else { + this.activePanel = RemoteTabsList; + } + break; + case "cmd_quit": + GlobalOverlay.goQuitApplication(); + break; + case "cmd_close": + this._closeOrQuit(); + break; + case "cmd_menu": + AppMenu.toggle(); + break; + case "cmd_newTab": + this.newTab(); + break; + case "cmd_closeTab": + this.closeTab(); + break; + case "cmd_undoCloseTab": + this.undoCloseTab(); + break; + case "cmd_sanitize": + { + let title = Strings.browser.GetStringFromName("clearPrivateData.title"); + let message = Strings.browser.GetStringFromName("clearPrivateData.message"); + let clear = Services.prompt.confirm(window, title, message); + if (clear) { + // disable the button temporarily to indicate something happened + let button = document.getElementById("prefs-clear-data"); + button.disabled = true; + setTimeout(function() { button.disabled = false; }, 5000); + + Sanitizer.sanitize(); + } + break; + } + case "cmd_panel": + if (this.isPanelVisible()) + this.hidePanel(); + else + this.showPanel(); + break; + case "cmd_zoomin": + Browser.zoom(-1); + break; + case "cmd_zoomout": + Browser.zoom(1); + break; + case "cmd_volumeLeft": + // Zoom in (portrait) or out (landscape) + Browser.zoom(Util.isPortrait() ? -1 : 1); + break; + case "cmd_volumeRight": + // Zoom out (portrait) or in (landscape) + Browser.zoom(Util.isPortrait() ? 1 : -1); + break; + case "cmd_lockscreen": + { + let locked = Services.prefs.getBoolPref("toolkit.screen.lock"); + Services.prefs.setBoolPref("toolkit.screen.lock", !locked); + + let strings = Strings.browser; + let alerts = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService); + alerts.showAlertNotification(null, strings.GetStringFromName("alertLockScreen"), + strings.GetStringFromName("alertLockScreen." + (!locked ? "locked" : "unlocked")), false, "", null); + break; + } + } + } +}; diff --git a/mobile/chrome/content/browser.css b/mobile/chrome/content/browser.css new file mode 100644 index 000000000000..7ae2140cd35b --- /dev/null +++ b/mobile/chrome/content/browser.css @@ -0,0 +1,265 @@ +browser[remote="false"] { + -moz-binding: url("chrome://browser/content/bindings/browser.xml#local-browser"); +} + +browser[remote="true"] { + -moz-binding: url("chrome://browser/content/bindings/browser.xml#remote-browser"); +} + +#urlbar-edit { + -moz-binding: url("chrome://browser/content/bindings.xml#autocomplete-aligned"); +} + +#content-navigator { + -moz-binding: url("chrome://browser/content/bindings.xml#content-navigator"); +} + +#tabs { + -moz-binding: url("chrome://browser/content/tabs.xml#tablist"); +} + +documenttab { + -moz-binding: url("chrome://browser/content/tabs.xml#documenttab"); +} + +settings { + -moz-binding: url("chrome://browser/content/bindings/setting.xml#settings"); +} + +setting[type="bool"] { + -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-bool"); +} + +setting[type="bool"][localized="true"] { + -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-localized-bool"); +} + +setting[type="boolint"] { + -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-boolint"); +} + +setting[type="integer"] { + -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-integer"); +} + +setting[type="control"] { + -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-control"); +} + +setting[type="string"] { + -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-string"); +} + +#browsers > notificationbox { + -moz-binding: url("chrome://browser/content/notification.xml#stacked-notificationbox"); + overflow: -moz-hidden-unscrollable; +} + +notification { + -moz-binding: url("chrome://browser/content/notification.xml#notification"); +} + +notification[type="geo"] { + -moz-binding: url("chrome://browser/content/notification.xml#geo-notification"); +} + +#popup_autocomplete { + -moz-binding: url("chrome://browser/content/bindings.xml#popup_autocomplete"); +} + +autocompleteresult { + -moz-binding: url("chrome://browser/content/bindings.xml#popup_autocomplete_result"); +} + +/* visibility: hidden rather than display:none the first child, since it needs a + height for the 'No results' row to be properly positioned */ +autocompleteresult[value=""]:first-child { + visibility: hidden; +} + +autocompleteresult[value=""]:not(:first-child) { + display: none; +} + +placeitem { + -moz-binding: url("chrome://browser/content/bindings.xml#place-item"); + background-color: transparent; +} + +placeitem[type="folder"] { + -moz-binding: url("chrome://browser/content/bindings.xml#place-folder"); +} + +placelist { + -moz-binding: url("chrome://browser/content/bindings.xml#place-list"); +} + +historylist { + -moz-binding: url("chrome://browser/content/bindings.xml#history-list"); +} + +remotetabslist { + -moz-binding: url("chrome://browser/content/bindings.xml#remotetabs-list"); +} + +placelabel { + -moz-binding: url("chrome://browser/content/bindings.xml#place-label"); +} + +radio { + -moz-binding: url("chrome://global/content/bindings/radio.xml#radio"); +} + +checkbox { + -moz-binding: url("chrome://browser/content/bindings/checkbox.xml#checkbox-radio"); +} + +menulist { + -moz-binding: url("chrome://browser/content/bindings.xml#menulist"); +} + +.chrome-select-option { + -moz-binding: url("chrome://browser/content/bindings.xml#chrome-select-option"); +} + +/* richlist defaults ------------------------------------------------------- */ +richlistbox[batch] { + -moz-binding: url("chrome://browser/content/bindings.xml#richlistbox-batch"); +} + +richlistitem { + -moz-binding: url("chrome://browser/content/bindings.xml#richlistitem"); +} + +richlistitem[typeName="local"] { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-local"); +} + +richlistitem[typeName="searchplugin"] { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-searchplugin"); +} + +richlistitem[typeName="search"] { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-search"); +} + +richlistitem[typeName="search"] hbox.addon-type-or-rating { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-search-recommended"); +} + +richlistitem[typeName="search"] hbox.addon-type-or-rating[rating] { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-search-rating"); +} + +richlistitem[typeName="search"] hbox.addon-type-or-rating[rating="-1"] { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-search-no-rating"); +} + +richlistitem[typeName="message"] { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-message"); +} + +richlistitem[typeName="showmore"] { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-search-showmore"); +} + +richlistitem[typeName="banner"] { + -moz-binding: url("chrome://browser/content/bindings/extensions.xml#extension-search-banner"); +} + +richlistitem[typeName="download"][state="-1"] { + -moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-not-started"); +} + +richlistitem[typeName="download"] { + -moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-downloading"); +} + +richlistitem[typeName="download"][state="1"] { + -moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-done"); +} + +richlistitem[typeName="download"][state="2"], +richlistitem[typeName="download"][state="3"] { + -moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-retry"); +} + +richlistitem[typeName="download"][state="4"] { + -moz-binding: url("chrome://browser/content/bindings/downloads.xml#download-paused"); +} + +/* addons states ----------------------------------------------------------- */ +.hide-on-enable, +.show-on-error, +.show-on-uninstall, +.show-on-install, +.show-on-restart, +richlistitem[isDisabled="true"] .hide-on-disable { + display: none; +} + +richlistitem[error] .show-on-error, +richlistitem[opType="needs-restart"] .show-on-restart, +richlistitem[opType="needs-uninstall"] .show-on-uninstall, +richlistitem[opType="needs-install"] .show-on-install, +richlistitem[opType="needs-enable"] .show-on-enable, +richlistitem[opType="needs-disable"] .show-on-disable, +richlistitem[isDisabled="true"] .show-on-disable { + display: -moz-box; +} + +richlistitem[opType="needs-restart"] .hide-on-restart, +richlistitem[opType="needs-uninstall"] .hide-on-uninstall, +richlistitem[isDisabled="true"][opType="needs-uninstall"], +richlistitem[opType="needs-install"] .hide-on-install, +richlistitem[opType="needs-enable"] .hide-on-enable, +richlistitem[opType="needs-disable"] .hide-on-disable { + display: none; +} + +richlistitem[type="error"], +richlistitem[type="warning"] { + -moz-binding: url("chrome://browser/content/bindings/console.xml#error"); +} + +richlistitem[type="message"]{ + -moz-binding: url("chrome://browser/content/bindings/console.xml#message"); +} + +dialog { + -moz-binding: url("chrome://browser/content/bindings/dialog.xml#dialog"); +} + +pageaction { + -moz-binding: url("chrome://browser/content/bindings/pageaction.xml#pageaction"); +} + +arrowbox { + -moz-binding: url("chrome://browser/content/bindings/arrowbox.xml#arrowbox"); +} + +/* Disable context menus in textboxes */ +.textbox-input-box, +.textbox-input-box[spellcheck="true"] { + -moz-binding: url("chrome://browser/content/bindings.xml#input-box"); +} + +textbox { + -moz-binding: url("chrome://browser/content/bindings.xml#textbox"); +} + +textbox[multiline="true"] { + -moz-binding: url("chrome://browser/content/bindings.xml#textarea"); +} + +textbox[type="timed"] { + -moz-binding: url("chrome://browser/content/bindings.xml#timed-textbox"); +} + +textbox[type="search"] { + -moz-binding: url("chrome://browser/content/bindings.xml#search-textbox"); +} + +textbox[type="number"] { + -moz-binding: url("chrome://browser/content/bindings.xml#numberbox"); +} diff --git a/mobile/chrome/content/browser.js b/mobile/chrome/content/browser.js new file mode 100644 index 000000000000..70e8078acf60 --- /dev/null +++ b/mobile/chrome/content/browser.js @@ -0,0 +1,2878 @@ +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- +/* + * ***** 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 Mobile Browser. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brad Lassey + * Mark Finkle + * Aleks Totic + * Johnathan Nightingale + * Stuart Parmenter + * Taras Glek + * Roy Frostig + * Ben Combee + * Matt Brubeck + * Benjamin Stover + * Miika Jarvinen + * Jaakko Kiviluoto + * + * 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 ***** */ + +let Cc = Components.classes; +let Ci = Components.interfaces; +let Cu = Components.utils; +let Cr = Components.results; + +function getBrowser() { + return Browser.selectedBrowser; +} + +const kBrowserFormZoomLevelMin = 0.8; +const kBrowserFormZoomLevelMax = 2.0; +const kBrowserViewZoomLevelPrecision = 10000; + +const kDefaultBrowserWidth = 800; +const kFallbackBrowserWidth = 980; +const kDefaultMetadata = { autoSize: false, allowZoom: true, autoScale: true }; + +// Override sizeToContent in the main window. It breaks things (bug 565887) +window.sizeToContent = function() { + Cu.reportError("window.sizeToContent is not allowed in this window"); +} + +#ifdef MOZ_CRASH_REPORTER +XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter", + "@mozilla.org/xre/app-info;1", "nsICrashReporter"); +#endif + +function onDebugKeyPress(aEvent) { + if (!aEvent.ctrlKey) + return; + + function doSwipe(aDirection) { + let evt = document.createEvent("SimpleGestureEvent"); + evt.initSimpleGestureEvent("MozSwipeGesture", true, true, window, null, + 0, 0, 0, 0, false, false, false, false, 0, null, + aDirection, 0); + Browser.selectedTab.inputHandler.dispatchEvent(evt); + } + + let nsIDOMKeyEvent = Ci.nsIDOMKeyEvent; + switch (aEvent.charCode) { + case nsIDOMKeyEvent.DOM_VK_A: // Swipe Left + doSwipe(Ci.nsIDOMSimpleGestureEvent.DIRECTION_LEFT); + break; + case nsIDOMKeyEvent.DOM_VK_D: // Swipe Right + doSwipe(Ci.nsIDOMSimpleGestureEvent.DIRECTION_RIGHT); + break; + case nsIDOMKeyEvent.DOM_VK_F: // Forge GC + MemoryObserver.observe(); + dump("Forced a GC\n"); + break; + case nsIDOMKeyEvent.DOM_VK_M: // Android Menu + CommandUpdater.doCommand("cmd_menu"); + break; +#ifndef MOZ_PLATFORM_MAEMO + case nsIDOMKeyEvent.DOM_VK_P: // Fake pinch zoom + function dispatchMagnifyEvent(aName, aDelta) { + let evt = document.createEvent("SimpleGestureEvent"); + evt.initSimpleGestureEvent("MozMagnifyGesture" + aName, true, true, window, null, + 0, 0, 0, 0, false, false, false, false, 0, null, 0, aDelta); + Browser.selectedTab.inputHandler.dispatchEvent(evt); + } + dispatchMagnifyEvent("Start", 0); + + let frame = 0; + let timer = new Util.Timeout(); + timer.interval(100, function() { + dispatchMagnifyEvent("Update", 20); + if (++frame > 10) { + timer.clear(); + dispatchMagnifyEvent("", frame*20); + } + }); + break; + case nsIDOMKeyEvent.DOM_VK_Q: // Toggle Orientation + if (Util.isPortrait()) + window.top.resizeTo(800,480); + else + window.top.resizeTo(480,800); + break; +#endif + case nsIDOMKeyEvent.DOM_VK_S: // Swipe down + doSwipe(Ci.nsIDOMSimpleGestureEvent.DIRECTION_DOWN); + break; + case nsIDOMKeyEvent.DOM_VK_W: // Swipe up + doSwipe(Ci.nsIDOMSimpleGestureEvent.DIRECTION_UP); + break; + default: + break; + } +} + +var Browser = { + _tabs: [], + _selectedTab: null, + windowUtils: window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils), + controlsScrollbox: null, + controlsScrollboxScroller: null, + controlsPosition: null, + pageScrollbox: null, + pageScrollboxScroller: null, + styles: {}, + + startup: function startup() { + var self = this; + + try { + messageManager.loadFrameScript("chrome://browser/content/Util.js", true); + messageManager.loadFrameScript("chrome://browser/content/forms.js", true); + messageManager.loadFrameScript("chrome://browser/content/content.js", true); + } catch (e) { + // XXX whatever is calling startup needs to dump errors! + dump("###########" + e + "\n"); + } + + // XXX change + + /* handles dispatching clicks on browser into clicks in content or zooms */ + Elements.browsers.customDragger = new Browser.MainDragger(); + + /* handles web progress management for open browsers */ + Elements.browsers.webProgress = new Browser.WebProgress(); + + this.keySender = new ContentCustomKeySender(Elements.browsers); + let mouseModule = new MouseModule(); + let gestureModule = new GestureModule(Elements.browsers); + let scrollWheelModule = new ScrollwheelModule(Elements.browsers); + + ContentTouchHandler.init(); + + // Warning, total hack ahead. All of the real-browser related scrolling code + // lies in a pretend scrollbox here. Let's not land this as-is. Maybe it's time + // to redo all the dragging code. + this.contentScrollbox = Elements.browsers; + this.contentScrollboxScroller = { + scrollBy: function(aDx, aDy) { + let view = getBrowser().getRootView(); + view.scrollBy(aDx, aDy); + }, + + scrollTo: function(aX, aY) { + let view = getBrowser().getRootView(); + view.scrollTo(aX, aY); + }, + + getPosition: function(aScrollX, aScrollY) { + let view = getBrowser().getRootView(); + let scroll = view.getPosition(); + aScrollX.value = scroll.x; + aScrollY.value = scroll.y; + } + }; + + /* horizontally scrolling box that holds the sidebars as well as the contentScrollbox */ + let controlsScrollbox = this.controlsScrollbox = document.getElementById("controls-scrollbox"); + this.controlsScrollboxScroller = controlsScrollbox.boxObject.QueryInterface(Ci.nsIScrollBoxObject); + controlsScrollbox.customDragger = { + isDraggable: function isDraggable(target, content) { return {}; }, + dragStart: function dragStart(cx, cy, target, scroller) {}, + dragStop: function dragStop(dx, dy, scroller) { return false; }, + dragMove: function dragMove(dx, dy, scroller) { return false; } + }; + + /* vertically scrolling box that contains the url bar, notifications, and content */ + let pageScrollbox = this.pageScrollbox = document.getElementById("page-scrollbox"); + this.pageScrollboxScroller = pageScrollbox.boxObject.QueryInterface(Ci.nsIScrollBoxObject); + pageScrollbox.customDragger = controlsScrollbox.customDragger; + + let stylesheet = document.styleSheets[0]; + for each (let style in ["window-width", "window-height", "viewable-height", "viewable-width", "toolbar-height"]) { + let index = stylesheet.insertRule("." + style + " {}", stylesheet.cssRules.length); + this.styles[style] = stylesheet.cssRules[index].style; + } + + // Saved the scrolls values before the resizing of the window, to restore + // the scrollbox position once the resize has finished. + // The last parameter of addEventListener is true to be sure we performed + // the computation before something else could happened (bug 622121) + window.addEventListener("MozBeforeResize", function(aEvent) { + if (aEvent.target != window) + return; + + let { x: x1, y: y1 } = Browser.getScrollboxPosition(Browser.controlsScrollboxScroller); + let { x: x2, y: y2 } = Browser.getScrollboxPosition(Browser.pageScrollboxScroller); + let [,, leftWidth, rightWidth] = Browser.computeSidebarVisibility(); + + let shouldHideSidebars = Browser.controlsPosition ? Browser.controlsPosition.hideSidebars : true; + Browser.controlsPosition = { x: x1, y: y2, hideSidebars: shouldHideSidebars, + leftSidebar: leftWidth, rightSidebar: rightWidth }; + }, true); + + function resizeHandler(e) { + if (e.target != window) + return; + + let w = window.innerWidth; + let h = window.innerHeight; + + // Don't bother doing unuseful work during intermediate resized during + // startup if the goal is to be fullscreen + let fullscreen = (document.documentElement.getAttribute("sizemode") == "fullscreen"); + if (fullscreen && w != screen.width) + return; + + let toolbarHeight = Math.round(document.getElementById("toolbar-main").getBoundingClientRect().height); + + Browser.styles["window-width"].width = w + "px"; + Browser.styles["window-height"].height = h + "px"; + Browser.styles["toolbar-height"].height = toolbarHeight + "px"; + + // Tell the UI to resize the browser controls + BrowserUI.sizeControls(w, h); + ViewableAreaObserver.update(); + + // Restore the previous scroll position + let restorePosition = Browser.controlsPosition; + if (restorePosition.hideSidebars) { + restorePosition.hideSidebars = false; + Browser.hideSidebars(); + } else { + // Handle Width transformation of the tabs sidebar + if (restorePosition.x) { + let [,, leftWidth, rightWidth] = Browser.computeSidebarVisibility(); + let delta = ((restorePosition.leftSidebar - leftWidth) || (restorePosition.rightSidebar - rightWidth)); + restorePosition.x += (restorePosition.x == leftWidth) ? delta : -delta; + } + + Browser.controlsScrollboxScroller.scrollTo(restorePosition.x, 0); + Browser.pageScrollboxScroller.scrollTo(0, restorePosition.y); + Browser.tryFloatToolbar(0, 0); + } + + // We want to keep the current focused element into view if possible + let currentElement = document.activeElement; + let [scrollbox, scrollInterface] = ScrollUtils.getScrollboxFromElement(currentElement); + if (scrollbox && scrollInterface && currentElement && currentElement != scrollbox) { + // retrieve the direct child of the scrollbox + while (currentElement.parentNode != scrollbox) + currentElement = currentElement.parentNode; + + setTimeout(function() { scrollInterface.ensureElementIsVisible(currentElement) }, 0); + } + } + window.addEventListener("resize", resizeHandler, false); + + function fullscreenHandler() { + if (!window.fullScreen) + document.getElementById("toolbar-main").setAttribute("fullscreen", "true"); + else + document.getElementById("toolbar-main").removeAttribute("fullscreen"); + } + window.addEventListener("fullscreen", fullscreenHandler, false); + + BrowserUI.init(); + + window.controllers.appendController(this); + window.controllers.appendController(BrowserUI); + + var os = Services.obs; + os.addObserver(XPInstallObserver, "addon-install-blocked", false); + os.addObserver(XPInstallObserver, "addon-install-started", false); + os.addObserver(SessionHistoryObserver, "browser:purge-session-history", false); + os.addObserver(ContentCrashObserver, "ipc:content-shutdown", false); + os.addObserver(MemoryObserver, "memory-pressure", false); + + // Listens for change in the viewable area +#if MOZ_PLATFORM_MAEMO == 6 + os.addObserver(ViewableAreaObserver, "softkb-change", false); +#endif + + window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess(); + + Elements.browsers.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false); + + // Make sure we're online before attempting to load + Util.forceOnline(); + + // If this is an intial window launch the commandline handler passes us the default + // page as an argument. commandURL _should_ never be empty, but we protect against it + // below. However, we delay trying to get the fallback homepage until we really need it. + let commandURL = null; + if (window.arguments && window.arguments[0]) + commandURL = window.arguments[0]; + + // Should we restore the previous session (crash or some other event) + let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); + if (ss.shouldRestore()) { + ss.restoreLastSession(); + + // Also open any commandline URLs, except the homepage + if (commandURL && commandURL != this.getHomePage()) + this.addTab(commandURL, true); + } else { + this.addTab(commandURL || this.getHomePage(), true); + } + + messageManager.addMessageListener("Browser:ViewportMetadata", this); + messageManager.addMessageListener("Browser:FormSubmit", this); + messageManager.addMessageListener("Browser:KeyPress", this); + messageManager.addMessageListener("Browser:ZoomToPoint:Return", this); + messageManager.addMessageListener("Browser:CanUnload:Return", this); + messageManager.addMessageListener("scroll", this); + messageManager.addMessageListener("Browser:CertException", this); + + // broadcast a UIReady message so add-ons know we are finished with startup + let event = document.createEvent("Events"); + event.initEvent("UIReady", true, false); + window.dispatchEvent(event); + }, + + _waitingToClose: false, + closing: function closing() { + // If we are already waiting for the close prompt, don't show another + if (this._waitingToClose) + return false; + + // Prompt if we have multiple tabs before closing window + let numTabs = this._tabs.length; + if (numTabs > 1) { + let shouldPrompt = Services.prefs.getBoolPref("browser.tabs.warnOnClose"); + if (shouldPrompt) { + let prompt = Services.prompt; + + // Default to true: if it were false, we wouldn't get this far + let warnOnClose = { value: true }; + + let messageBase = Strings.browser.GetStringFromName("tabs.closeWarning"); + let message = PluralForm.get(numTabs, messageBase).replace("#1", numTabs); + + let title = Strings.browser.GetStringFromName("tabs.closeWarningTitle"); + let closeText = Strings.browser.GetStringFromName("tabs.closeButton"); + let checkText = Strings.browser.GetStringFromName("tabs.closeWarningPromptMe"); + let buttons = (prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_0) + + (prompt.BUTTON_TITLE_CANCEL * prompt.BUTTON_POS_1); + + this._waitingToClose = true; + let pressed = prompt.confirmEx(window, title, message, buttons, closeText, null, null, checkText, warnOnClose); + this._waitingToClose = false; + + // Don't set the pref unless they press OK and it's false + let reallyClose = (pressed == 0); + if (reallyClose && !warnOnClose.value) + Services.prefs.setBoolPref("browser.tabs.warnOnClose", false); + + // If we don't want to close, return now. If we are closing, continue with other housekeeping. + if (!reallyClose) + return false; + } + } + + // Figure out if there's at least one other browser window around. + let lastBrowser = true; + let e = Services.wm.getEnumerator("navigator:browser"); + while (e.hasMoreElements() && lastBrowser) { + let win = e.getNext(); + if (win != window) + lastBrowser = false; + } + if (!lastBrowser) + return true; + + // Let everyone know we are closing the last browser window + let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + Services.obs.notifyObservers(closingCanceled, "browser-lastwindow-close-requested", null); + if (closingCanceled.data) + return false; + + Services.obs.notifyObservers(null, "browser-lastwindow-close-granted", null); + return true; + }, + + shutdown: function shutdown() { + BrowserUI.uninit(); + + messageManager.removeMessageListener("Browser:ViewportMetadata", this); + messageManager.removeMessageListener("Browser:FormSubmit", this); + messageManager.removeMessageListener("Browser:KeyPress", this); + messageManager.removeMessageListener("Browser:ZoomToPoint:Return", this); + messageManager.removeMessageListener("scroll", this); + messageManager.removeMessageListener("Browser:CertException", this); + + var os = Services.obs; + os.removeObserver(XPInstallObserver, "addon-install-blocked"); + os.removeObserver(XPInstallObserver, "addon-install-started"); + os.removeObserver(SessionHistoryObserver, "browser:purge-session-history"); + os.removeObserver(ContentCrashObserver, "ipc:content-shutdown"); + os.removeObserver(MemoryObserver, "memory-pressure"); + + window.controllers.removeController(this); + window.controllers.removeController(BrowserUI); + }, + + getHomePage: function getHomePage(aOptions) { + aOptions = aOptions || { useDefault: false }; + + let url = "about:home"; + try { + let prefs = aOptions.useDefault ? Services.prefs.getDefaultBranch(null) : Services.prefs; + url = prefs.getComplexValue("browser.startup.homepage", Ci.nsIPrefLocalizedString).data; + } + catch(e) { } + + return url; + }, + + get browsers() { + return this._tabs.map(function(tab) { return tab.browser; }); + }, + + scrollContentToTop: function scrollContentToTop(aOptions) { + let x = {}, y = {}; + this.contentScrollboxScroller.getPosition(x, y); + if (aOptions) + x.value = ("x" in aOptions ? aOptions.x : x.value); + + this.contentScrollboxScroller.scrollTo(x.value, 0); + this.pageScrollboxScroller.scrollTo(0, 0); + }, + + // cmd_scrollBottom does not work in Fennec (Bug 590535). + scrollContentToBottom: function scrollContentToBottom(aOptions) { + let x = {}, y = {}; + this.contentScrollboxScroller.getPosition(x, y); + if (aOptions) + x.value = ("x" in aOptions ? aOptions.x : x.value); + + this.contentScrollboxScroller.scrollTo(x.value, Number.MAX_VALUE); + this.pageScrollboxScroller.scrollTo(0, Number.MAX_VALUE); + this.hideTitlebar(); + }, + + hideSidebars: function scrollSidebarsOffscreen() { + let rect = Elements.browsers.getBoundingClientRect(); + this.controlsScrollboxScroller.scrollBy(Math.round(rect.left), 0); + this.tryUnfloatToolbar(); + }, + + hideTitlebar: function hideTitlebar() { + let rect = Elements.browsers.getBoundingClientRect(); + this.pageScrollboxScroller.scrollBy(0, Math.round(rect.top)); + this.tryUnfloatToolbar(); + }, + + /** + * Load a URI in the current tab, or a new tab if necessary. + * @param aURI String + * @param aParams Object with optional properties that will be passed to loadURIWithFlags: + * flags, referrerURI, charset, postData. + */ + loadURI: function loadURI(aURI, aParams) { + let browser = this.selectedBrowser; + + // We need to keep about: pages opening in new "local" tabs. We also want to spawn + // new "remote" tabs if opening web pages from a "local" about: page. + let currentURI = browser.currentURI.spec; + let useLocal = Util.isLocalScheme(aURI); + let hasLocal = Util.isLocalScheme(currentURI); + + if (hasLocal != useLocal) { + let oldTab = this.selectedTab; + + // Add new tab before closing the old one, in case there is only one. + Browser.addTab(aURI, true, oldTab, aParams); + if (/^about:(blank|empty)$/.test(currentURI) && !browser.canGoBack && !browser.canGoForward) { + oldTab.chromeTab.ignoreUndo = true; + this.closeTab(oldTab, { forceClose: true }); + oldTab = null; + } + } + else { + let params = aParams || {}; + let flags = params.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE; + browser.loadURIWithFlags(aURI, flags, params.referrerURI, params.charset, params.postData); + } + }, + + /** + * Determine if the given URL is a shortcut/keyword and, if so, expand it + * @param aURL String + * @param aPostDataRef Out param contains any required post data for a search + * @returns the expanded shortcut, or the original URL if not a shortcut + */ + getShortcutOrURI: function getShortcutOrURI(aURL, aPostDataRef) { + let shortcutURL = null; + let keyword = aURL; + let param = ""; + + let offset = aURL.indexOf(" "); + if (offset > 0) { + keyword = aURL.substr(0, offset); + param = aURL.substr(offset + 1); + } + + if (!aPostDataRef) + aPostDataRef = {}; + + let engine = Services.search.getEngineByAlias(keyword); + if (engine) { + let submission = engine.getSubmission(param); + aPostDataRef.value = submission.postData; + return submission.uri.spec; + } + + try { + [shortcutURL, aPostDataRef.value] = PlacesUtils.getURLAndPostDataForKeyword(keyword); + } catch (e) {} + + if (!shortcutURL) + return aURL; + + let postData = ""; + if (aPostDataRef.value) + postData = unescape(aPostDataRef.value); + + if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) { + let charset = ""; + const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; + let matches = shortcutURL.match(re); + if (matches) + [, shortcutURL, charset] = matches; + else { + // Try to get the saved character-set. + try { + // makeURI throws if URI is invalid. + // Will return an empty string if character-set is not found. + charset = PlacesUtils.history.getCharsetForURI(Util.makeURI(shortcutURL)); + } catch (e) { dump("--- error " + e + "\n"); } + } + + let encodedParam = ""; + if (charset) + encodedParam = escape(convertFromUnicode(charset, param)); + else // Default charset is UTF-8 + encodedParam = encodeURIComponent(param); + + shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); + + if (/%s/i.test(postData)) // POST keyword + aPostDataRef.value = getPostDataStream(postData, param, encodedParam, "application/x-www-form-urlencoded"); + } else if (param) { + // This keyword doesn't take a parameter, but one was provided. Just return + // the original URL. + aPostDataRef.value = null; + + return aURL; + } + + return shortcutURL; + }, + + /** + * Return the currently active object + */ + get selectedBrowser() { + return this._selectedTab.browser; + }, + + get tabs() { + return this._tabs; + }, + + getTabForBrowser: function getTabForBrowser(aBrowser) { + let tabs = this._tabs; + for (let i = 0; i < tabs.length; i++) { + if (tabs[i].browser == aBrowser) + return tabs[i]; + } + return null; + }, + + getBrowserForWindowId: function getBrowserForWindowId(aWindowId) { + for (let i = 0; i < this.browsers.length; i++) { + if (this.browsers[i].contentWindowId == aWindowId) + return this.browsers[i]; + } + return null; + }, + + getTabAtIndex: function getTabAtIndex(index) { + if (index > this._tabs.length || index < 0) + return null; + return this._tabs[index]; + }, + + getTabFromChrome: function getTabFromChrome(chromeTab) { + for (var t = 0; t < this._tabs.length; t++) { + if (this._tabs[t].chromeTab == chromeTab) + return this._tabs[t]; + } + return null; + }, + + addTab: function browser_addTab(aURI, aBringFront, aOwner, aParams) { + let params = aParams || {}; + let newTab = new Tab(aURI, params); + newTab.owner = aOwner || null; + this._tabs.push(newTab); + + if (aBringFront) + this.selectedTab = newTab; + + let getAttention = ("getAttention" in params ? params.getAttention : !aBringFront); + let event = document.createEvent("UIEvents"); + event.initUIEvent("TabOpen", true, false, window, getAttention); + newTab.chromeTab.dispatchEvent(event); + newTab.browser.messageManager.sendAsyncMessage("Browser:TabOpen"); + + return newTab; + }, + + closeTab: function closeTab(aTab, aOptions) { + let tab = aTab instanceof XULElement ? this.getTabFromChrome(aTab) : aTab; + if (!tab || !this._getNextTab(tab)) + return; + + if (aOptions && "forceClose" in aOptions && aOptions.forceClose) { + this._doCloseTab(aTab); + return; + } + + tab.browser.messageManager.sendAsyncMessage("Browser:CanUnload", {}); + }, + + _doCloseTab: function _docloseTab(aTab) { + let nextTab = this._getNextTab(aTab); + if (!nextTab) + return; + + // Make sure we leave the toolbar in an unlocked state + if (aTab == this._selectedTab && aTab.isLoading()) + BrowserUI.unlockToolbar(); + + // Tabs owned by the closed tab are now orphaned. + this._tabs.forEach(function(item, index, array) { + if (item.owner == aTab) + item.owner = null; + }); + + let event = document.createEvent("Events"); + event.initEvent("TabClose", true, false); + aTab.chromeTab.dispatchEvent(event); + aTab.browser.messageManager.sendAsyncMessage("Browser:TabClose"); + + let container = aTab.chromeTab.parentNode; + aTab.destroy(); + this._tabs.splice(this._tabs.indexOf(aTab), 1); + + this.selectedTab = nextTab; + + event = document.createEvent("Events"); + event.initEvent("TabRemove", true, false); + container.dispatchEvent(event); + }, + + _getNextTab: function _getNextTab(aTab) { + let tabIndex = this._tabs.indexOf(aTab); + if (tabIndex == -1) + return null; + + let nextTab = this._selectedTab; + if (nextTab == aTab) { + nextTab = aTab.owner || this.getTabAtIndex(tabIndex + 1) || this.getTabAtIndex(tabIndex - 1); + if (!nextTab) + return null; + } + + return nextTab; + }, + + get selectedTab() { + return this._selectedTab; + }, + + set selectedTab(tab) { + if (tab instanceof XULElement) + tab = this.getTabFromChrome(tab); + + if (!tab) + return; + + if (this._selectedTab == tab) { + // Deck does not update its selectedIndex when children + // are removed. See bug 602708 + Elements.browsers.selectedPanel = tab.notification; + return; + } + + if (this._selectedTab) { + this._selectedTab.pageScrollOffset = this.getScrollboxPosition(this.pageScrollboxScroller); + + // Make sure we leave the toolbar in an unlocked state + if (this._selectedTab.isLoading()) + BrowserUI.unlockToolbar(); + } + + let isFirstTab = this._selectedTab == null; + let lastTab = this._selectedTab; + let oldBrowser = lastTab ? lastTab._browser : null; + let browser = tab.browser; + + this._selectedTab = tab; + + // Lock the toolbar if the new tab is still loading + if (this._selectedTab.isLoading()) + BrowserUI.lockToolbar(); + + if (lastTab) + lastTab.active = false; + + if (tab) + tab.active = true; + + if (!isFirstTab) { + // Update all of our UI to reflect the new tab's location + BrowserUI.updateURI(); + getIdentityHandler().checkIdentity(); + + let event = document.createEvent("Events"); + event.initEvent("TabSelect", true, false); + event.lastTab = lastTab; + tab.chromeTab.dispatchEvent(event); + } + + tab.lastSelected = Date.now(); + + if (tab.pageScrollOffset) { + let pageScroll = tab.pageScrollOffset; + Browser.pageScrollboxScroller.scrollTo(pageScroll.x, pageScroll.y); + } + }, + + supportsCommand: function(cmd) { + var isSupported = false; + switch (cmd) { + case "cmd_fullscreen": + isSupported = true; + break; + default: + isSupported = false; + break; + } + return isSupported; + }, + + isCommandEnabled: function(cmd) { + return true; + }, + + doCommand: function(cmd) { + switch (cmd) { + case "cmd_fullscreen": + window.fullScreen = !window.fullScreen; + break; + } + }, + + getNotificationBox: function getNotificationBox(aBrowser) { + let browser = aBrowser || this.selectedBrowser; + return browser.parentNode; + }, + + /** + * Handle cert exception event bubbling up from content. + */ + _handleCertException: function _handleCertException(aMessage) { + let json = aMessage.json; + if (json.action == "leave") { + // Get the start page from the *default* pref branch, not the user's + let url = Browser.getHomePage({ useDefault: true }); + this.loadURI(url); + } else { + // Handle setting an cert exception and reloading the page + try { + // Add a new SSL exception for this URL + let uri = Services.io.newURI(json.url, null, null); + let sslExceptions = new SSLExceptions(); + + if (json.action == "permanent") + sslExceptions.addPermanentException(uri); + else + sslExceptions.addTemporaryException(uri); + } catch (e) { + dump("EXCEPTION handle content command: " + e + "\n" ); + } + + // Automatically reload after the exception was added + aMessage.target.reload(); + } + }, + + /** + * Compute the sidebar percentage visibility. + * + * @param [optional] dx + * @param [optional] dy an offset distance at which to perform the visibility + * computation + */ + computeSidebarVisibility: function computeSidebarVisibility(dx, dy) { + function visibility(aSidebarRect, aVisibleRect) { + let width = aSidebarRect.width; + aSidebarRect.restrictTo(aVisibleRect); + return aSidebarRect.width / width; + } + + if (!dx) dx = 0; + if (!dy) dy = 0; + + let [leftSidebar, rightSidebar] = [Elements.tabs.getBoundingClientRect(), Elements.controls.getBoundingClientRect()]; + if (leftSidebar.left > rightSidebar.left) + [rightSidebar, leftSidebar] = [leftSidebar, rightSidebar]; // switch in RTL case + + let visibleRect = new Rect(0, 0, window.innerWidth, 1); + let leftRect = new Rect(Math.round(leftSidebar.left) - Math.round(dx), 0, Math.round(leftSidebar.width), 1); + let rightRect = new Rect(Math.round(rightSidebar.left) - Math.round(dx), 0, Math.round(rightSidebar.width), 1); + + let leftTotalWidth = leftRect.width; + let leftVisibility = visibility(leftRect, visibleRect); + + let rightTotalWidth = rightRect.width; + let rightVisibility = visibility(rightRect, visibleRect); + + return [leftVisibility, rightVisibility, leftTotalWidth, rightTotalWidth]; + }, + + /** + * Compute the horizontal distance needed to scroll in order to snap the + * sidebars into place. + * + * Visibility is computed by creating dummy rectangles for the sidebar and the + * visible rect. Sidebar rectangles come from getBoundingClientRect(), so + * they are in absolute client coordinates (and since we're in a scrollbox, + * this means they are positioned relative to the window, which is anchored at + * (0, 0) regardless of the scrollbox's scroll position. The rectangles are + * made to have a top of 0 and a height of 1, since these do not affect how we + * compute visibility (we care only about width), and using rectangles allows + * us to use restrictTo(), which comes in handy. + * + * @return scrollBy dx needed to make snap happen + */ + snapSidebars: function snapSidebars() { + let [leftvis, ritevis, leftw, ritew] = Browser.computeSidebarVisibility(); + + let snappedX = 0; + + if (leftvis != 0 && leftvis != 1) { + if (leftvis >= 0.6666) { + snappedX = -((1 - leftvis) * leftw); + } else { + snappedX = leftvis * leftw; + } + } + else if (ritevis != 0 && ritevis != 1) { + if (ritevis >= 0.6666) { + snappedX = (1 - ritevis) * ritew; + } else { + snappedX = -ritevis * ritew; + } + } + + return Math.round(snappedX); + }, + + tryFloatToolbar: function tryFloatToolbar(dx, dy) { + if (this.floatedWhileDragging) + return; + + let [leftvis, ritevis, leftw, ritew] = Browser.computeSidebarVisibility(dx, dy); + if (leftvis > 0 || ritevis > 0) { + BrowserUI.lockToolbar(); + this.floatedWhileDragging = true; + } + }, + + tryUnfloatToolbar: function tryUnfloatToolbar(dx, dy) { + if (!this.floatedWhileDragging) + return true; + + let [leftvis, ritevis, leftw, ritew] = Browser.computeSidebarVisibility(dx, dy); + if (leftvis == 0 && ritevis == 0) { + BrowserUI.unlockToolbar(); + this.floatedWhileDragging = false; + return true; + } + return false; + }, + + /** Zoom one step in (negative) or out (positive). */ + zoom: function zoom(aDirection) { + let tab = this.selectedTab; + if (!tab.allowZoom) + return; + + let browser = tab.browser; + let oldZoomLevel = browser.scale; + let zoomLevel = oldZoomLevel; + + let zoomValues = ZoomManager.zoomValues; + let i = zoomValues.indexOf(ZoomManager.snap(zoomLevel)) + (aDirection < 0 ? 1 : -1); + if (i >= 0 && i < zoomValues.length) + zoomLevel = zoomValues[i]; + + zoomLevel = tab.clampZoomLevel(zoomLevel); + + let browserRect = browser.getBoundingClientRect(); + let center = browser.transformClientToBrowser(browserRect.width / 2, + browserRect.height / 2); + let rect = this._getZoomRectForPoint(center.x, center.y, zoomLevel); + AnimatedZoom.animateTo(rect); + }, + + /** Rect should be in browser coordinates. */ + _getZoomLevelForRect: function _getZoomLevelForRect(rect) { + const margin = 15; + return this.selectedTab.clampZoomLevel(window.innerWidth / (rect.width + margin * 2)); + }, + + /** + * Find an appropriate zoom rect for an element bounding rect, if it exists. + * @return Rect in viewport coordinates, or null + */ + _getZoomRectForRect: function _getZoomRectForRect(rect, y) { + let zoomLevel = this._getZoomLevelForRect(rect); + return this._getZoomRectForPoint(rect.center().x, y, zoomLevel); + }, + + /** + * Find a good zoom rectangle for point that is specified in browser coordinates. + * @return Rect in viewport coordinates + */ + _getZoomRectForPoint: function _getZoomRectForPoint(x, y, zoomLevel) { + let browser = getBrowser(); + x = x * browser.scale; + y = y * browser.scale; + + zoomLevel = Math.min(ZoomManager.MAX, zoomLevel); + let oldScale = browser.scale; + let zoomRatio = zoomLevel / oldScale; + let browserRect = browser.getBoundingClientRect(); + let newVisW = browserRect.width / zoomRatio, newVisH = browserRect.height / zoomRatio; + let result = new Rect(x - newVisW / 2, y - newVisH / 2, newVisW, newVisH); + + // Make sure rectangle doesn't poke out of viewport + return result.translateInside(new Rect(0, 0, browser.contentDocumentWidth * oldScale, + browser.contentDocumentHeight * oldScale)); + }, + + zoomToPoint: function zoomToPoint(cX, cY, aRect) { + let tab = this.selectedTab; + if (!tab.allowZoom) + return null; + + let zoomRect = null; + if (aRect) + zoomRect = this._getZoomRectForRect(aRect, cY); + + if (!zoomRect && tab.isDefaultZoomLevel()) { + let scale = tab.clampZoomLevel(tab.browser.scale * 2); + zoomRect = this._getZoomRectForPoint(cX, cY, scale); + } + + if (zoomRect) + AnimatedZoom.animateTo(zoomRect); + + return zoomRect; + }, + + zoomFromPoint: function zoomFromPoint(cX, cY) { + let tab = this.selectedTab; + if (tab.allowZoom && !tab.isDefaultZoomLevel()) { + let zoomLevel = tab.getDefaultZoomLevel(); + let zoomRect = this._getZoomRectForPoint(cX, cY, zoomLevel); + AnimatedZoom.animateTo(zoomRect); + } + }, + + // The device-pixel-to-CSS-px ratio used to adjust meta viewport values. + // This is higher on higher-dpi displays, so pages stay about the same physical size. + getScaleRatio: function getScaleRatio() { + let prefValue = Services.prefs.getIntPref("browser.viewport.scaleRatio"); + if (prefValue > 0) + return prefValue / 100; + + let dpi = this.windowUtils.displayDPI; + if (dpi < 200) // Includes desktop displays, and LDPI and MDPI Android devices + return 1; + else if (dpi < 300) // Includes Nokia N900, and HDPI Android devices + return 1.5; + + // For very high-density displays like the iPhone 4, calculate an integer ratio. + return Math.floor(dpi / 150); + }, + + /** + * Convenience function for getting the scrollbox position off of a + * scrollBoxObject interface. Returns the actual values instead of the + * wrapping objects. + * + * @param scroller a scrollBoxObject on which to call scroller.getPosition() + */ + getScrollboxPosition: function getScrollboxPosition(scroller) { + let x = {}; + let y = {}; + scroller.getPosition(x, y); + return new Point(x.value, y.value); + }, + + forceChromeReflow: function forceChromeReflow() { + let dummy = getComputedStyle(document.documentElement, "").width; + }, + + receiveMessage: function receiveMessage(aMessage) { + let json = aMessage.json; + let browser = aMessage.target; + + switch (aMessage.name) { + case "Browser:ViewportMetadata": + let tab = this.getTabForBrowser(browser); + // Some browser such as iframes loaded dynamically into the chrome UI + // does not have any assigned tab + if (tab) + tab.updateViewportMetadata(json); + break; + + case "Browser:FormSubmit": + browser.lastLocation = null; + break; + + case "Browser:CanUnload:Return": { + if (!json.permit) + return; + + // Allow a little delay to not close the target tab while processing + // a message for this particular tab + setTimeout(function(self) { + let tab = self.getTabForBrowser(browser); + self._doCloseTab(tab); + }, 0, this); + break; + } + + case "Browser:KeyPress": + let event = document.createEvent("KeyEvents"); + event.initKeyEvent("keypress", true, true, null, + json.ctrlKey, json.altKey, json.shiftKey, json.metaKey, + json.keyCode, json.charCode); + document.getElementById("mainKeyset").dispatchEvent(event); + break; + + case "Browser:ZoomToPoint:Return": + if (json.zoomTo) { + let rect = Rect.fromRect(json.zoomTo); + this.zoomToPoint(json.x, json.y, rect); + } else { + this.zoomFromPoint(json.x, json.y); + } + break; + + case "scroll": + if (browser == this.selectedBrowser) { + let view = browser.getRootView(); + let position = view.getPosition(); + if (position.x != 0) + this.hideSidebars(); + + if (position.y != 0) + this.hideTitlebar(); + } + break; + case "Browser:CertException": + this._handleCertException(aMessage); + break; + } + } +}; + + +Browser.MainDragger = function MainDragger() { + this._horizontalScrollbar = document.getElementById("horizontal-scroller"); + this._verticalScrollbar = document.getElementById("vertical-scroller"); + this._scrollScales = { x: 0, y: 0 }; + + Elements.browsers.addEventListener("PanBegin", this, false); + Elements.browsers.addEventListener("PanFinished", this, false); +}; + +Browser.MainDragger.prototype = { + isDraggable: function isDraggable(target, scroller) { + return { x: true, y: true }; + }, + + dragStart: function dragStart(clientX, clientY, target, scroller) { + let browser = getBrowser(); + let bcr = browser.getBoundingClientRect(); + this._contentView = browser.getViewAt(clientX - bcr.left, clientY - bcr.top); + this._stopAtSidebar = 0; + this._hitSidebar = false; + if (this._sidebarTimeout) { + clearTimeout(this._sidebarTimeout); + this._sidebarTimeout = null; + } + }, + + dragStop: function dragStop(dx, dy, scroller) { + if (this._contentView && this._contentView._updateCacheViewport) + this._contentView._updateCacheViewport(); + this._contentView = null; + this.dragMove(Browser.snapSidebars(), 0, scroller); + Browser.tryUnfloatToolbar(); + }, + + dragMove: function dragMove(dx, dy, scroller, aIsKinetic) { + let doffset = new Point(dx, dy); + + // First calculate any panning to take sidebars out of view + let panOffset = this._panControlsAwayOffset(doffset); + + // If we started at one sidebar, stop when we get to the other. + if (panOffset.x != 0 && !this._stopAtSidebar) { + this._stopAtSidebar = panOffset.x; // negative: stop at left; positive: stop at right + this._sidebarTimeout = setTimeout(function(self) { + self._stopAtSidebar = 0; + self._sidebarTimeout = null; + }, 350, this); + } + + if (this._contentView && !this._contentView.isRoot()) { + this._panContentView(this._contentView, doffset); + // XXX we may need to have "escape borders" for iframe panning + // XXX does not deal with scrollables within scrollables + } + + // Do content panning + this._panContentView(getBrowser().getRootView(), doffset); + + if (this._hitSidebar && aIsKinetic) + return; // No kinetic panning after we've stopped at the sidebar. + + // Any leftover panning in doffset would bring controls into view. Add to sidebar + // away panning for the total scroll offset. + let offsetX = doffset.x; + if ((this._stopAtSidebar > 0 && offsetX > 0) || + (this._stopAtSidebar < 0 && offsetX < 0)) { + if (offsetX != panOffset.x) + this._hitSidebar = true; + doffset.x = panOffset.x; + } else { + doffset.add(panOffset); + } + + Browser.tryFloatToolbar(doffset.x, 0); + this._panScroller(Browser.controlsScrollboxScroller, doffset); + this._panScroller(Browser.pageScrollboxScroller, doffset); + this._updateScrollbars(); + + return !doffset.equals(dx, dy); + }, + + handleEvent: function handleEvent(aEvent) { + let browser = getBrowser(); + switch (aEvent.type) { + case "PanBegin": { + let width = ViewableAreaObserver.width, height = ViewableAreaObserver.height; + let contentWidth = browser.contentDocumentWidth * browser.scale; + let contentHeight = browser.contentDocumentHeight * browser.scale; + + // Allow a small margin on both sides to prevent adding scrollbars + // on small viewport approximation + const ALLOWED_MARGIN = 5; + const SCROLL_CORNER_SIZE = 8; + this._scrollScales = { + x: (width + ALLOWED_MARGIN) < contentWidth ? (width - SCROLL_CORNER_SIZE) / contentWidth : 0, + y: (height + ALLOWED_MARGIN) < contentHeight ? (height - SCROLL_CORNER_SIZE) / contentHeight : 0 + } + this._showScrollbars(); + break; + } + case "PanFinished": + this._hideScrollbars(); + + // Update the scroll position of the content + browser._updateCSSViewport(); + break; + } + }, + + /** Return offset that pans controls away from screen. Updates doffset with leftovers. */ + _panControlsAwayOffset: function(doffset) { + let x = 0, y = 0, rect; + + rect = Rect.fromRect(Browser.pageScrollbox.getBoundingClientRect()).map(Math.round); + if (doffset.x < 0 && rect.right < window.innerWidth) + x = Math.max(doffset.x, rect.right - window.innerWidth); + if (doffset.x > 0 && rect.left > 0) + x = Math.min(doffset.x, rect.left); + + // XXX could we use getBrowser().getBoundingClientRect().height here? + let height = Elements.contentViewport.getBoundingClientRect().height; + height -= Elements.contentNavigator.getBoundingClientRect().height; + + rect = Rect.fromRect(Browser.contentScrollbox.getBoundingClientRect()).map(Math.round); + if (doffset.y < 0 && rect.bottom < height) + y = Math.max(doffset.y, rect.bottom - height); + if (doffset.y > 0 && rect.top > 0) + y = Math.min(doffset.y, rect.top); + + doffset.subtract(x, y); + return new Point(x, y); + }, + + /** Pan scroller by the given amount. Updates doffset with leftovers. */ + _panContentView: function _panContentView(contentView, doffset) { + let pos0 = contentView.getPosition(); + contentView.scrollBy(doffset.x, doffset.y); + let pos1 = contentView.getPosition(); + doffset.subtract(pos1.x - pos0.x, pos1.y - pos0.y); + }, + + /** Pan scroller by the given amount. Updates doffset with leftovers. */ + _panScroller: function _panScroller(scroller, doffset) { + let scroll = Browser.getScrollboxPosition(scroller); + scroller.scrollBy(doffset.x, doffset.y); + let scroll1 = Browser.getScrollboxPosition(scroller); + doffset.subtract(scroll1.x - scroll.x, scroll1.y - scroll.y); + }, + + _updateScrollbars: function _updateScrollbars() { + let scaleX = this._scrollScales.x, scaleY = this._scrollScales.y; + let contentScroll = Browser.getScrollboxPosition(Browser.contentScrollboxScroller); + if (scaleX) + this._horizontalScrollbar.style.MozTransform = "translateX(" + Math.round(contentScroll.x * scaleX) + "px)"; + + if (scaleY) { + let y = Math.round(contentScroll.y * scaleY); + + // Vertical scrollbar is out of view when showing the tabs sidebar, + // the 'solution' for now is to reposition it if needed + let x = 0; + if (Browser.floatedWhileDragging) { + // Check if the sidebars are inverted (rtl) + let [leftVis, rightVis, leftW, rightW] = Browser.computeSidebarVisibility(); + let [leftSidebar, rightSidebar] = [Elements.tabs.getBoundingClientRect(), Elements.controls.getBoundingClientRect()]; + if (leftSidebar.left > rightSidebar.left) + x = Math.round(Math.max(0, rightW * rightVis)); + else + x = Math.round(Math.max(0, leftW * leftVis)) * -1.0; + } + + this._verticalScrollbar.style.MozTransform = "translate(" + x + "px," + y + "px)"; + } + }, + + _showScrollbars: function _showScrollbars() { + this._updateScrollbars(); + let scaleX = this._scrollScales.x, scaleY = this._scrollScales.y; + if (scaleX) { + this._horizontalScrollbar.width = ViewableAreaObserver.width * scaleX; + this._horizontalScrollbar.setAttribute("panning", "true"); + } + + if (scaleY) { + this._verticalScrollbar.height = ViewableAreaObserver.height * scaleY; + this._verticalScrollbar.setAttribute("panning", "true"); + } + }, + + _hideScrollbars: function _hideScrollbars() { + this._scrollScales.x = 0, this._scrollScales.y = 0; + this._horizontalScrollbar.removeAttribute("panning"); + this._verticalScrollbar.removeAttribute("panning"); + this._horizontalScrollbar.style.MozTransform = ""; + this._verticalScrollbar.style.MozTransform = ""; + } +}; + + +Browser.WebProgress = function WebProgress() { + messageManager.addMessageListener("Content:StateChange", this); + messageManager.addMessageListener("Content:LocationChange", this); + messageManager.addMessageListener("Content:SecurityChange", this); +}; + +Browser.WebProgress.prototype = { + receiveMessage: function receiveMessage(aMessage) { + let json = aMessage.json; + let tab = Browser.getTabForBrowser(aMessage.target); + + switch (aMessage.name) { + case "Content:StateChange": { + if (json.stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { + if (json.stateFlags & Ci.nsIWebProgressListener.STATE_START) + this._networkStart(tab); + else if (json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP) + this._networkStop(tab); + } + + if (json.stateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) { + if (json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP) + this._documentStop(tab); + } + break; + } + + case "Content:LocationChange": { + let spec = json.location; + let location = spec.split("#")[0]; // Ignore fragment identifier changes. + + if (tab == Browser.selectedTab) + BrowserUI.updateURI(); + + let locationHasChanged = (location != tab.browser.lastLocation); + if (locationHasChanged) { + Browser.getNotificationBox(tab.browser).removeTransientNotifications(); + tab.resetZoomLevel(); + tab.hostChanged = true; + tab.browser.lastLocation = location; + tab.browser.userTypedValue = ""; + +#ifdef MOZ_CRASH_REPORTER + if (CrashReporter.enabled) + CrashReporter.annotateCrashReport("URL", spec); +#endif + this._waitForLoad(tab); + tab.useFallbackWidth = false; + } + + let event = document.createEvent("UIEvents"); + event.initUIEvent("URLChanged", true, false, window, locationHasChanged); + tab.browser.dispatchEvent(event); + break; + } + + case "Content:SecurityChange": { + // Don't need to do anything if the data we use to update the UI hasn't changed + if (tab.state == json.state && !tab.hostChanged) + return; + + tab.hostChanged = false; + tab.state = json.state; + + if (tab == Browser.selectedTab) + getIdentityHandler().checkIdentity(); + break; + } + } + }, + + _networkStart: function _networkStart(aTab) { + aTab.startLoading(); + + if (aTab == Browser.selectedTab) { + BrowserUI.update(TOOLBARSTATE_LOADING); + + // We should at least show something in the URLBar until + // the load has progressed further along + if (aTab.browser.currentURI.spec == "about:blank") + BrowserUI.updateURI({ captionOnly: true }); + } + }, + + _networkStop: function _networkStop(aTab) { + aTab.endLoading(); + + if (aTab == Browser.selectedTab) + BrowserUI.update(TOOLBARSTATE_LOADED); + }, + + _documentStop: function _documentStop(aTab) { + // Make sure the URLbar is in view. If this were the selected tab, + // _waitForLoad would scroll to top. + aTab.pageScrollOffset = { x: 0, y: 0 }; + }, + + _waitForLoad: function _waitForLoad(aTab) { + let browser = aTab.browser; + browser.messageManager.removeMessageListener("MozScrolledAreaChanged", aTab.scrolledAreaChanged); + + browser.messageManager.addMessageListener("Browser:FirstPaint", function firstPaintListener(aMessage) { + browser.messageManager.removeMessageListener(aMessage.name, arguments.callee); + + // We're about to have new page content, so scroll the content area + // so the new paints will draw correctly. + // Background tabs are delayed scrolled to top in _documentStop + if (getBrowser() == browser) { + let json = aMessage.json; + browser.getRootView().scrollTo(Math.floor(json.x * browser.scale), + Math.floor(json.y * browser.scale)); + Browser.pageScrollboxScroller.scrollTo(0, 0); + } + + aTab.scrolledAreaChanged(); + if (browser.currentURI.spec != "about:blank") + aTab.updateThumbnail(); + browser.messageManager.addMessageListener("MozScrolledAreaChanged", aTab.scrolledAreaChanged); + }); + } +}; + + +function nsBrowserAccess() { } + +nsBrowserAccess.prototype = { + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports)) + return this; + throw Cr.NS_NOINTERFACE; + }, + + _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) { + let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); + if (isExternal && aURI && aURI.schemeIs("chrome")) + return null; + + let loadflags = isExternal ? + Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL : + Ci.nsIWebNavigation.LOAD_FLAGS_NONE; + let location; + if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { + switch (aContext) { + case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL : + aWhere = Services.prefs.getIntPref("browser.link.open_external"); + break; + default : // OPEN_NEW or an illegal value + aWhere = Services.prefs.getIntPref("browser.link.open_newwindow"); + } + } + + let browser; + if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW) { + let url = aURI ? aURI.spec : "about:blank"; + let newWindow = openDialog("chrome://browser/content/browser.xul", "_blank", + "all,dialog=no", url, null, null, null); + // since newWindow.Browser doesn't exist yet, just return null + return null; + } else if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) { + let owner = isExternal ? null : Browser.selectedTab; + let tab = Browser.addTab("about:blank", true, owner, { getAttention: true }); + if (isExternal) + tab.closeOnExit = true; + browser = tab.browser; + BrowserUI.hidePanel(); + } else { // OPEN_CURRENTWINDOW and illegal values + browser = Browser.selectedBrowser; + } + + try { + let referrer; + if (aURI) { + if (aOpener) { + location = aOpener.location; + referrer = Services.io.newURI(location, null, null); + } + browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null); + } + browser.focus(); + } catch(e) { } + + BrowserUI.closeAutoComplete(); + return browser; + }, + + openURI: function browser_openURI(aURI, aOpener, aWhere, aContext) { + let browser = this._getBrowser(aURI, aOpener, aWhere, aContext); + return browser ? browser.contentWindow : null; + }, + + openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) { + let browser = this._getBrowser(aURI, aOpener, aWhere, aContext); + return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null; + }, + + isTabContentWindow: function(aWindow) { + return Browser.browsers.some(function (browser) browser.contentWindow == aWindow); + } +}; + + +/** Watches for mouse events in chrome and sends them to content. */ +const ContentTouchHandler = { + // Use lightweight transactions so that old context menus and tap + // highlights don't ever see the light of day. + _messageId: 0, + + init: function init() { + document.addEventListener("TapDown", this, true); + document.addEventListener("TapOver", this, false); + document.addEventListener("TapUp", this, false); + document.addEventListener("TapSingle", this, false); + document.addEventListener("TapDouble", this, false); + document.addEventListener("TapLong", this, false); + + document.addEventListener("PanBegin", this, false); + document.addEventListener("PopupChanged", this, false); + document.addEventListener("CancelTouchSequence", this, false); + + // Context menus have the following flow: + // [parent] mousedown -> TapLong -> Browser:MouseLong + // [child] Browser:MouseLong -> contextmenu -> Browser:ContextMenu + // [parent] Browser:ContextMenu -> ...* + // + // * = Here some time will elapse. Although we get the context menu we need + // ASAP, we do not act on the context menu until we receive a LongTap. + // This is so we can show the context menu as soon as we know it is + // a long tap, without waiting for child process. + // + messageManager.addMessageListener("Browser:ContextMenu", this); + + messageManager.addMessageListener("Browser:Highlight", this); + }, + + handleEvent: function handleEvent(aEvent) { + // ignore content events we generate + if (aEvent.target.localName == "browser") + return; + + switch (aEvent.type) { + case "PanBegin": + case "PopupChanged": + case "CancelTouchSequence": + this._clearPendingMessages(); + break; + + default: { + if (ContextHelper.popupState) { + // Don't send content events when there's a popup + aEvent.preventDefault(); + break; + } + + if (!this._targetIsContent(aEvent)) { + // Received tap event on something that isn't for content. + this._clearPendingMessages(); + break; + } + + switch (aEvent.type) { + case "TapDown": + this.tapDown(aEvent.clientX, aEvent.clientY); + break; + case "TapOver": + this.tapOver(aEvent.clientX, aEvent.clientY); + break; + case "TapUp": + if (aEvent.isClick) { + if (!Browser.selectedTab.allowZoom) { + this.tapSingle(aEvent.clientX, aEvent.clientY, aEvent.modifiers); + aEvent.preventDefault(); + } + } else { + this.tapUp(aEvent.clientX, aEvent.clientY); + } + break; + case "TapSingle": + this.tapSingle(aEvent.clientX, aEvent.clientY, aEvent.modifiers); + break; + case "TapDouble": + this.tapDouble(aEvent.clientX, aEvent.clientY, aEvent.modifiers); + break; + case "TapLong": + this.tapLong(aEvent.clientX, aEvent.clientY); + break; + } + } + } + }, + + receiveMessage: function receiveMessage(aMessage) { + if (aMessage.json.messageId != this._messageId) + return; + + switch (aMessage.name) { + case "Browser:ContextMenu": + // Long tap + let contextMenu = { name: aMessage.name, json: aMessage.json, target: aMessage.target }; + if (ContextHelper.showPopup(contextMenu)) { + // Stop all input sequences + let event = document.createEvent("Events"); + event.initEvent("CancelTouchSequence", true, false); + document.dispatchEvent(event); + } + break; + } + }, + + /** Invalidates any messages received from content that are sensitive to time. */ + _clearPendingMessages: function _clearPendingMessages() { + this._messageId++; + let browser = getBrowser(); + browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {}); + }, + + /** + * Check if the event concern the browser content + */ + _targetIsContent: function _targetIsContent(aEvent) { + // TapUp event with XULDocument as a target occurs on desktop when the + // mouse is released outside of the Fennec window, and because XULDocument + // does not have a classList properties, just check it exists first to + // prevent a warning + let target = aEvent.target; + return target && ("classList" in target && target.classList.contains("inputHandler")); + }, + + _dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY, aOptions) { + let browser = getBrowser(); + let pos = browser.transformClientToBrowser(aX || 0, aY || 0); + + let json = aOptions || {}; + json.x = pos.x; + json.y = pos.y; + json.messageId = this._messageId; + browser.messageManager.sendAsyncMessage(aName, json); + }, + + tapDown: function tapDown(aX, aY) { + // Ensure that the content process has gets an activate event + let browser = getBrowser(); + let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; + browser.focus(); + try { + fl.activateRemoteFrame(); + } catch (e) {} + this._dispatchMouseEvent("Browser:MouseDown", aX, aY); + }, + + tapOver: function tapOver(aX, aY) { + this._dispatchMouseEvent("Browser:MouseOver", aX, aY); + }, + + tapUp: function tapUp(aX, aY) { + let browser = getBrowser(); + browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {}); + }, + + tapSingle: function tapSingle(aX, aY, aModifiers) { + // Cancel the mouse click if we are showing a context menu + if (!ContextHelper.popupState) + this._dispatchMouseEvent("Browser:MouseUp", aX, aY, { modifiers: aModifiers }); + }, + + tapDouble: function tapDouble(aX, aY, aModifiers) { + this._clearPendingMessages(); + + let tab = Browser.selectedTab; + if (!tab.allowZoom) + return; + + let width = window.innerWidth / Browser.getScaleRatio(); + this._dispatchMouseEvent("Browser:ZoomToPoint", aX, aY, { width: width }); + }, + + tapLong: function tapLong(aX, aY) { + this._dispatchMouseEvent("Browser:MouseLong", aX, aY); + }, + + toString: function toString() { + return "[ContentTouchHandler] { }"; + } +}; + + +/** Watches for mouse events in chrome and sends them to content. */ +function ContentCustomKeySender(container) { + container.addEventListener("keypress", this, false); + container.addEventListener("keyup", this, false); + container.addEventListener("keydown", this, false); +} + +ContentCustomKeySender.prototype = { + handleEvent: function handleEvent(aEvent) { + if (Elements.contentShowing.getAttribute("disabled") == "true") + return; + + let browser = getBrowser(); + if (browser && browser.active && browser.getAttribute("remote") == "true") { + aEvent.stopPropagation(); + aEvent.preventDefault(); + + let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; + fl.sendCrossProcessKeyEvent(aEvent.type, + aEvent.keyCode, + (aEvent.type != "keydown") ? aEvent.charCode : null, + this._parseModifiers(aEvent)); + } + }, + + _parseModifiers: function _parseModifiers(aEvent) { + const masks = Ci.nsIDOMNSEvent; + let mval = 0; + if (aEvent.shiftKey) + mval |= masks.SHIFT_MASK; + if (aEvent.ctrlKey) + mval |= masks.CONTROL_MASK; + if (aEvent.altKey) + mval |= masks.ALT_MASK; + if (aEvent.metaKey) + mval |= masks.META_MASK; + return mval; + }, + + toString: function toString() { + return "[ContentCustomKeySender] { }"; + } +}; + + +/** + * Utility class to handle manipulations of the identity indicators in the UI + */ +function IdentityHandler() { + this._staticStrings = {}; + this._staticStrings[this.IDENTITY_MODE_DOMAIN_VERIFIED] = { + encryption_label: Strings.browser.GetStringFromName("identity.encrypted2") + }; + this._staticStrings[this.IDENTITY_MODE_IDENTIFIED] = { + encryption_label: Strings.browser.GetStringFromName("identity.encrypted2") + }; + this._staticStrings[this.IDENTITY_MODE_UNKNOWN] = { + encryption_label: Strings.browser.GetStringFromName("identity.unencrypted2") + }; + + // Close the popup when reloading the page + Elements.browsers.addEventListener("URLChanged", this, true); + + this._cacheElements(); +} + +IdentityHandler.prototype = { + // Mode strings used to control CSS display + IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information + IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification + IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information + + // Cache the most recent SSLStatus and Location seen in checkIdentity + _lastStatus : null, + _lastLocation : null, + + /** + * Build out a cache of the elements that we need frequently. + */ + _cacheElements: function() { + this._identityBox = document.getElementById("identity-box"); + this._identityPopup = document.getElementById("identity-container"); + this._identityPopupContentBox = document.getElementById("identity-popup-content-box"); + this._identityPopupContentHost = document.getElementById("identity-popup-content-host"); + this._identityPopupContentOwner = document.getElementById("identity-popup-content-owner"); + this._identityPopupContentSupp = document.getElementById("identity-popup-content-supplemental"); + this._identityPopupContentVerif = document.getElementById("identity-popup-content-verifier"); + this._identityPopupEncLabel = document.getElementById("identity-popup-encryption-label"); + }, + + getIdentityData: function() { + return this._lastStatus.serverCert; + }, + + /** + * Determine the identity of the page being displayed by examining its SSL cert + * (if available) and, if necessary, update the UI to reflect this. + */ + checkIdentity: function() { + let browser = getBrowser(); + let state = browser.securityUI.state; + let location = browser.currentURI; + let currentStatus = browser.securityUI.SSLStatus; + + this._lastStatus = currentStatus; + this._lastLocation = {}; + + try { + // make a copy of the passed in location to avoid cycles + this._lastLocation = { host: location.hostPort, hostname: location.host, port: location.port == -1 ? "" : location.port}; + } catch(e) { } + + if (state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) + this.setMode(this.IDENTITY_MODE_IDENTIFIED); + else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH) + this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED); + else + this.setMode(this.IDENTITY_MODE_UNKNOWN); + }, + + /** + * Return the eTLD+1 version of the current hostname + */ + getEffectiveHost: function() { + // Cache the eTLDService if this is our first time through + if (!this._eTLDService) + this._eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"] + .getService(Ci.nsIEffectiveTLDService); + try { + return this._eTLDService.getBaseDomainFromHost(this._lastLocation.hostname); + } catch (e) { + // If something goes wrong (e.g. hostname is an IP address) just fail back + // to the full domain. + return this._lastLocation.hostname; + } + }, + + /** + * Update the UI to reflect the specified mode, which should be one of the + * IDENTITY_MODE_* constants. + */ + setMode: function(newMode) { + this._identityBox.setAttribute("mode", newMode); + this.setIdentityMessages(newMode); + + // Update the popup too, if it's open + if (!this._identityPopup.hidden) + this.setPopupMessages(newMode); + }, + + /** + * Set up the messages for the primary identity UI based on the specified mode, + * and the details of the SSL cert, where applicable + * + * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants. + */ + setIdentityMessages: function(newMode) { + let strings = Strings.browser; + + if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) { + var iData = this.getIdentityData(); + + // We need a port number for all lookups. If one hasn't been specified, use + // the https default + var lookupHost = this._lastLocation.host; + if (lookupHost.indexOf(':') < 0) + lookupHost += ":443"; + + // Cache the override service the first time we need to check it + if (!this._overrideService) + this._overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(Ci.nsICertOverrideService); + + // Verifier is either the CA Org, for a normal cert, or a special string + // for certs that are trusted because of a security exception. + var tooltip = strings.formatStringFromName("identity.identified.verifier", [iData.caOrg], 1); + + // Check whether this site is a security exception. + if (iData.isException) + tooltip = strings.GetStringFromName("identity.identified.verified_by_you"); + } + else if (newMode == this.IDENTITY_MODE_IDENTIFIED) { + // If it's identified, then we can populate the dialog with credentials + iData = this.getIdentityData(); + tooltip = strings.formatStringFromName("identity.identified.verifier", [iData.caOrg], 1); + } + else { + tooltip = strings.GetStringFromName("identity.unknown.tooltip"); + } + + // Push the appropriate strings out to the UI + this._identityBox.tooltipText = tooltip; + }, + + /** + * Set up the title and content messages for the identity message popup, + * based on the specified mode, and the details of the SSL cert, where + * applicable + * + * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants. + */ + setPopupMessages: function(newMode) { + this._identityPopup.setAttribute("mode", newMode); + this._identityPopupContentBox.className = newMode; + + // Set the static strings up front + this._identityPopupEncLabel.textContent = this._staticStrings[newMode].encryption_label; + + // Initialize the optional strings to empty values + var supplemental = ""; + var verifier = ""; + + if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) { + var iData = this.getIdentityData(); + var host = this.getEffectiveHost(); + var owner = Strings.browser.GetStringFromName("identity.ownerUnknown2"); + verifier = this._identityBox.tooltipText; + supplemental = ""; + } + else if (newMode == this.IDENTITY_MODE_IDENTIFIED) { + // If it's identified, then we can populate the dialog with credentials + iData = this.getIdentityData(); + host = this.getEffectiveHost(); + owner = iData.subjectOrg; + verifier = this._identityBox.tooltipText; + + // Build an appropriate supplemental block out of whatever location data we have + if (iData.city) + supplemental += iData.city + " "; + if (iData.state && iData.country) + supplemental += Strings.browser.formatStringFromName("identity.identified.state_and_country", [iData.state, iData.country], 2); + else if (iData.state) // State only + supplemental += iData.state; + else if (iData.country) // Country only + supplemental += iData.country; + } else { + // These strings will be hidden in CSS anyhow + host = ""; + owner = ""; + } + + // Push the appropriate strings out to the UI + this._identityPopupContentHost.textContent = host; + this._identityPopupContentOwner.textContent = owner; + this._identityPopupContentSupp.textContent = supplemental; + this._identityPopupContentVerif.textContent = verifier; + + PageActions.updateSiteMenu(); + }, + + show: function ih_show() { + Elements.contentShowing.setAttribute("disabled", "true"); + + // dismiss any dialog which hide the identity popup + BrowserUI.activePanel = null; + while (BrowserUI.activeDialog) + BrowserUI.activeDialog.close(); + + // Update the popup strings + this.setPopupMessages(this._identityBox.getAttribute("mode") || this.IDENTITY_MODE_UNKNOWN); + + this._identityPopup.hidden = false; + this._identityPopup.top = BrowserUI.toolbarH - this._identityPopup.offset; + this._identityPopup.anchorTo(this._identityBox); + + this._identityBox.setAttribute("open", "true"); + + BrowserUI.pushPopup(this, [this._identityPopup, this._identityBox, Elements.toolbarContainer]); + BrowserUI.lockToolbar(); + }, + + hide: function ih_hide() { + Elements.contentShowing.setAttribute("disabled", "false"); + + this._identityPopup.hidden = true; + this._identityBox.removeAttribute("open"); + + BrowserUI.popPopup(this); + BrowserUI.unlockToolbar(); + }, + + toggle: function ih_toggle() { + if (this._identityPopup.hidden) + this.show(); + else + this.hide(); + }, + + /** + * Click handler for the identity-box element in primary chrome. + */ + handleIdentityButtonEvent: function(aEvent) { + aEvent.stopPropagation(); + + if ((aEvent.type == "click" && aEvent.button != 0) || + (aEvent.type == "keypress" && aEvent.charCode != KeyEvent.DOM_VK_SPACE && + aEvent.keyCode != KeyEvent.DOM_VK_RETURN)) + return; // Left click, space or enter only + + this.toggle(); + }, + + handleEvent: function(aEvent) { + if (aEvent.type == "URLChanged" && aEvent.target == Browser.selectedBrowser && !this._identityPopup.hidden) + this.hide(); + } +}; + +var gIdentityHandler; + +/** + * Returns the singleton instance of the identity handler class. Should always be + * used instead of referencing the global variable directly or creating new instances + */ +function getIdentityHandler() { + if (!gIdentityHandler) + gIdentityHandler = new IdentityHandler(); + return gIdentityHandler; +} + + +/** + * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml + */ +var PopupBlockerObserver = { + onUpdatePageReport: function onUpdatePageReport(aEvent) + { + var cBrowser = Browser.selectedBrowser; + if (aEvent.originalTarget != cBrowser) + return; + + if (!cBrowser.pageReport) + return; + + let result = Services.perms.testExactPermission(Browser.selectedBrowser.currentURI, "popup"); + if (result == Ci.nsIPermissionManager.DENY_ACTION) + return; + + // Only show the notification again if we've not already shown it. Since + // notifications are per-browser, we don't need to worry about re-adding + // it. + if (!cBrowser.pageReport.reported) { + if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) { + var brandShortName = Strings.brand.GetStringFromName("brandShortName"); + var message; + var popupCount = cBrowser.pageReport.length; + + let strings = Strings.browser; + if (popupCount > 1) + message = strings.formatStringFromName("popupWarningMultiple", [brandShortName, popupCount], 2); + else + message = strings.formatStringFromName("popupWarning", [brandShortName], 1); + + var notificationBox = Browser.getNotificationBox(); + var notification = notificationBox.getNotificationWithValue("popup-blocked"); + if (notification) { + notification.label = message; + } + else { + var buttons = [ + { + label: strings.GetStringFromName("popupButtonAllowOnce"), + accessKey: null, + callback: function() { PopupBlockerObserver.showPopupsForSite(); } + }, + { + label: strings.GetStringFromName("popupButtonAlwaysAllow2"), + accessKey: null, + callback: function() { PopupBlockerObserver.allowPopupsForSite(true); } + }, + { + label: strings.GetStringFromName("popupButtonNeverWarn2"), + accessKey: null, + callback: function() { PopupBlockerObserver.allowPopupsForSite(false); } + } + ]; + + const priority = notificationBox.PRIORITY_WARNING_MEDIUM; + notificationBox.appendNotification(message, "popup-blocked", + "", + priority, buttons); + } + } + // Record the fact that we've reported this blocked popup, so we don't + // show it again. + cBrowser.pageReport.reported = true; + } + }, + + allowPopupsForSite: function allowPopupsForSite(aAllow) { + var currentURI = Browser.selectedBrowser.currentURI; + Services.perms.add(currentURI, "popup", aAllow + ? Ci.nsIPermissionManager.ALLOW_ACTION + : Ci.nsIPermissionManager.DENY_ACTION); + + Browser.getNotificationBox().removeCurrentNotification(); + }, + + showPopupsForSite: function showPopupsForSite() { + let uri = Browser.selectedBrowser.currentURI; + let pageReport = Browser.selectedBrowser.pageReport; + if (pageReport) { + for (let i = 0; i < pageReport.length; ++i) { + var popupURIspec = pageReport[i].popupWindowURI.spec; + + // Sometimes the popup URI that we get back from the pageReport + // isn't useful (for instance, netscape.com's popup URI ends up + // being "http://www.netscape.com", which isn't really the URI of + // the popup they're trying to show). This isn't going to be + // useful to the user, so we won't create a menu item for it. + if (popupURIspec == "" || popupURIspec == "about:blank" || popupURIspec == uri.spec) + continue; + + let popupFeatures = pageReport[i].popupWindowFeatures; + let popupName = pageReport[i].popupWindowName; + + Browser.addTab(popupURIspec, false, Browser.selectedTab); + } + } + } +}; + +var XPInstallObserver = { + observe: function xpi_observer(aSubject, aTopic, aData) + { + switch (aTopic) { + case "addon-install-started": + var messageString = Strings.browser.GetStringFromName("alertAddonsDownloading"); + ExtensionsView.showAlert(messageString); + break; + case "addon-install-blocked": + var installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo); + var host = installInfo.originatingURI.host; + var brandShortName = Strings.brand.GetStringFromName("brandShortName"); + var notificationName, messageString, buttons; + var strings = Strings.browser; + var enabled = true; + try { + enabled = Services.prefs.getBoolPref("xpinstall.enabled"); + } + catch (e) {} + if (!enabled) { + notificationName = "xpinstall-disabled"; + if (Services.prefs.prefIsLocked("xpinstall.enabled")) { + messageString = strings.GetStringFromName("xpinstallDisabledMessageLocked"); + buttons = []; + } + else { + messageString = strings.formatStringFromName("xpinstallDisabledMessage", [brandShortName, host], 2); + buttons = [{ + label: strings.GetStringFromName("xpinstallDisabledButton"), + accessKey: null, + popup: null, + callback: function editPrefs() { + Services.prefs.setBoolPref("xpinstall.enabled", true); + return false; + } + }]; + } + } + else { + notificationName = "xpinstall"; + messageString = strings.formatStringFromName("xpinstallPromptWarning", [brandShortName, host], 2); + + buttons = [{ + label: strings.GetStringFromName("xpinstallPromptAllowButton"), + accessKey: null, + popup: null, + callback: function() { + // Kick off the install + installInfo.install(); + return false; + } + }]; + } + + var nBox = Browser.getNotificationBox(); + if (!nBox.getNotificationWithValue(notificationName)) { + const priority = nBox.PRIORITY_WARNING_MEDIUM; + const iconURL = "chrome://mozapps/skin/update/update.png"; + nBox.appendNotification(messageString, notificationName, iconURL, priority, buttons); + } + break; + } + } +}; + +var SessionHistoryObserver = { + observe: function sho_observe(aSubject, aTopic, aData) { + if (aTopic != "browser:purge-session-history") + return; + + let back = document.getElementById("cmd_back"); + back.setAttribute("disabled", "true"); + let forward = document.getElementById("cmd_forward"); + forward.setAttribute("disabled", "true"); + + let urlbar = document.getElementById("urlbar-edit"); + if (urlbar) { + // Clear undo history of the URL bar + urlbar.editor.transactionManager.clear(); + } + } +}; + +var ContentCrashObserver = { + get CrashSubmit() { + delete this.CrashSubmit; + Cu.import("resource://gre/modules/CrashSubmit.jsm", this); + return this.CrashSubmit; + }, + + observe: function cco_observe(aSubject, aTopic, aData) { + if (aTopic != "ipc:content-shutdown") { + Cu.reportError("ContentCrashObserver unexpected topic: " + aTopic); + return; + } + + if (!aSubject.QueryInterface(Ci.nsIPropertyBag2).hasKey("abnormal")) + return; + + // See if we should hide the UI or auto close the app based on env vars + let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); + let shutdown = env.get("MOZ_CRASHREPORTER_SHUTDOWN"); + if (shutdown) { + let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); + appStartup.quit(Ci.nsIAppStartup.eForceQuit); + return; + } + + let hideUI = env.get("MOZ_CRASHREPORTER_NO_REPORT"); + if (hideUI) + return; + + // Spin through the open tabs and resurrect the out-of-process tabs. Resurrection + // does not auto-reload the content. We delay load the content as needed. + Browser.tabs.forEach(function(aTab) { + if (aTab.browser.getAttribute("remote") == "true") + aTab.resurrect(); + }) + + let dumpID = aSubject.hasKey("dumpID") ? aSubject.getProperty("dumpID") : null; + + // Execute the UI prompt after the notification has had a chance to return and close the child process + setTimeout(function(self) { + // Ask the user if we should reload or close the current tab. Other tabs + // will be reloaded when selected. + let title = Strings.browser.GetStringFromName("tabs.crashWarningTitle"); + let message = Strings.browser.GetStringFromName("tabs.crashWarningMsg"); + let submitText = Strings.browser.GetStringFromName("tabs.crashSubmitReport"); + let reloadText = Strings.browser.GetStringFromName("tabs.crashReload"); + let closeText = Strings.browser.GetStringFromName("tabs.crashClose"); + let buttons = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT + + (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) + + (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1); + + // Only show the submit checkbox if we have a crash report we can submit + if (!dumpID) + submitText = null; + + let submit = { value: true }; + let reload = Services.prompt.confirmEx(window, title, message, buttons, closeText, reloadText, null, submitText, submit); + if (reload) { + // Fire a TabSelect event to kick start the restore process + let event = document.createEvent("Events"); + event.initEvent("TabSelect", true, false); + event.lastTab = null; + Browser.selectedTab.chromeTab.dispatchEvent(event); + } else { + // If this is the only tab, we need to pre-fab a new tab. We should never + // have zero open tabs + if (Browser.tabs.length == 1) { + // Get the start page from the *default* pref branch, not the user's + let fallbackURL = Browser.getHomePage({ useDefault: true }); + Browser.addTab(fallbackURL, false, null, { getAttention: false }); + } + + // Close this tab, it could be the reason we crashed. The undo-close-tab + // system will pick it up. + Browser.closeTab(Browser.selectedTab, { forceClose: true }); + } + + // Submit the report, if we have one and the user wants to submit it + if (submit.value && dumpID) + self.CrashSubmit.submit(dumpID, Elements.stack, null, null); + }, 0, this); + } +}; + +var MemoryObserver = { + observe: function mo_observe(aSubject, aTopic, aData) { + if (aData == "heap-minimize") { + // do non-destructive stuff here. + return; + } + + for (let i = Browser.tabs.length - 1; i >= 0; i--) { + let tab = Browser.tabs[i]; + if (tab == Browser.selectedTab) + continue; + tab.resurrect(); + } + + window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils).garbageCollect(); + Cu.forceGC(); + // Bug 637582 - The low memory condition throws out some stuff that we still + // need, re-selecting the active tab gets us back to where we need to be. + let sTab = Browser.selectedTab; + Browser._selectedTab = null; + Browser.selectedTab = sTab; + } +}; + +function getNotificationBox(aBrowser) { + return Browser.getNotificationBox(aBrowser); +} + +function importDialog(aParent, aSrc, aArguments) { + // load the dialog with a synchronous XHR + let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(); + xhr.open("GET", aSrc, false); + xhr.overrideMimeType("text/xml"); + xhr.send(null); + if (!xhr.responseXML) + return null; + + let currentNode; + let nodeIterator = xhr.responseXML.createNodeIterator(xhr.responseXML, NodeFilter.SHOW_TEXT, null, false); + while (currentNode = nodeIterator.nextNode()) { + let trimmed = currentNode.nodeValue.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); + if (!trimmed.length) + currentNode.parentNode.removeChild(currentNode); + } + + let doc = xhr.responseXML.documentElement; + + let dialog = null; + + // we need to insert before menulist-container if we want it to show correctly + // for prompt.select for instance + let menulistContainer = document.getElementById("menulist-container"); + let parentNode = menulistContainer.parentNode; + + // emit DOMWillOpenModalDialog event + let event = document.createEvent("Events"); + event.initEvent("DOMWillOpenModalDialog", true, false); + let dispatcher = aParent || getBrowser(); + dispatcher.dispatchEvent(event); + + // create a full-screen semi-opaque box as a background + let back = document.createElement("box"); + back.setAttribute("class", "modal-block"); + dialog = back.appendChild(document.importNode(doc, true)); + parentNode.insertBefore(back, menulistContainer); + + dialog.arguments = aArguments; + dialog.parent = aParent; + return dialog; +} + +function showDownloadManager(aWindowContext, aID, aReason) { + BrowserUI.showPanel("downloads-container"); + // TODO: select the download with aID +} + +function Tab(aURI, aParams) { + this._id = null; + this._browser = null; + this._notification = null; + this._loading = false; + this._chromeTab = null; + this._metadata = null; + + this.useFallbackWidth = false; + this.owner = null; + + this.hostChanged = false; + this.state = null; + + // Set to 0 since new tabs that have not been viewed yet are good tabs to + // toss if app needs more memory. + this.lastSelected = 0; + + // aParams is an object that contains some properties for the initial tab + // loading like flags, a referrerURI, a charset or even a postData. + this.create(aURI, aParams || {}); + + // default tabs to inactive (i.e. no display port) + this.active = false; + + this.scrolledAreaChanged = this.scrolledAreaChanged.bind(this); +} + +Tab.prototype = { + get browser() { + return this._browser; + }, + + get notification() { + return this._notification; + }, + + get chromeTab() { + return this._chromeTab; + }, + + get metadata() { + return this._metadata || kDefaultMetadata; + }, + + get inputHandler() { + if (!this._notification) + return null; + return this._notification.inputHandler; + }, + + /** Update browser styles when the viewport metadata changes. */ + updateViewportMetadata: function updateViewportMetadata(aMetadata) { + if (aMetadata && aMetadata.autoScale) { + let scaleRatio = aMetadata.scaleRatio = Browser.getScaleRatio(); + + if ("defaultZoom" in aMetadata && aMetadata.defaultZoom > 0) + aMetadata.defaultZoom *= scaleRatio; + if ("minZoom" in aMetadata && aMetadata.minZoom > 0) + aMetadata.minZoom *= scaleRatio; + if ("maxZoom" in aMetadata && aMetadata.maxZoom > 0) + aMetadata.maxZoom *= scaleRatio; + } + this._metadata = aMetadata; + this.updateViewportSize(); + }, + + /** + * Update browser size when the metadata or the window size changes. + */ + updateViewportSize: function updateViewportSize() { + let browser = this._browser; + if (!browser) + return; + + let screenW = ViewableAreaObserver.width; + let screenH = ViewableAreaObserver.height; + let viewportW, viewportH; + + let metadata = this.metadata; + if (metadata.autoSize) { + if ("scaleRatio" in metadata) { + viewportW = screenW / metadata.scaleRatio; + viewportH = screenH / metadata.scaleRatio; + } else { + viewportW = screenW; + viewportH = screenH; + } + } else { + viewportW = metadata.width; + viewportH = metadata.height; + + // If (scale * width) < device-width, increase the width (bug 561413). + let maxInitialZoom = metadata.defaultZoom || metadata.maxZoom; + if (maxInitialZoom && viewportW) + viewportW = Math.max(viewportW, screenW / maxInitialZoom); + + let validW = viewportW > 0; + let validH = viewportH > 0; + + if (validW && !validH) { + viewportH = viewportW * (screenH / screenW); + } else if (!validW && validH) { + viewportW = viewportH * (screenW / screenH); + } else if (!validW && !validH) { + viewportW = this.useFallbackWidth ? kFallbackBrowserWidth : kDefaultBrowserWidth; + viewportH = kDefaultBrowserWidth * (screenH / screenW); + } + } + + // Make sure the viewport height is not shorter than the window when + // the page is zoomed out to show its full width. + viewportH = Math.max(viewportH, screenH * (browser.contentDocumentWidth / screenW)); + + if (browser.contentWindowWidth != viewportW || browser.contentWindowHeight != viewportH) + browser.setWindowSize(viewportW, viewportH); + }, + + restoreViewportPosition: function restoreViewportPosition(aOldWidth, aNewWidth) { + let browser = this._browser; + + // zoom to keep the same portion of the document visible + let oldScale = browser.scale; + let newScale = this.clampZoomLevel(oldScale * aNewWidth / aOldWidth); + let scaleRatio = newScale / oldScale; + + let view = browser.getRootView(); + let pos = view.getPosition(); + browser.fuzzyZoom(newScale, pos.x * scaleRatio, pos.y * scaleRatio); + browser.finishFuzzyZoom(); + }, + + startLoading: function startLoading() { + if (this._loading) throw "Already Loading!"; + this._loading = true; + }, + + endLoading: function endLoading() { + if (!this._loading) throw "Not Loading!"; + this._loading = false; + if (this._drawThumb) { + this._drawThumb = false; + this.updateThumbnail(); + } + }, + + isLoading: function isLoading() { + return this._loading; + }, + + create: function create(aURI, aParams) { + this._chromeTab = document.getElementById("tabs").addTab(); + let browser = this._createBrowser(aURI, null); + + // Should we fully load the new browser, or wait until later + if ("delayLoad" in aParams && aParams.delayLoad) + return; + + try { + let flags = aParams.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE; + browser.loadURIWithFlags(aURI, flags, aParams.referrerURI, aParams.charset, aParams.postData); + } catch(e) { + dump("Error: " + e + "\n"); + } + }, + + destroy: function destroy() { + document.getElementById("tabs").removeTab(this._chromeTab); + this._chromeTab = null; + this._destroyBrowser(); + }, + + resurrect: function resurrect() { + let dead = this._browser; + let active = this.active; + + // Hold onto the session store data + let session = { data: dead.__SS_data, extra: dead.__SS_extdata }; + + // We need this data to correctly create and position the new browser + // If this browser is already a zombie, fallback to the session data + let currentURL = dead.__SS_restore ? session.data.entries[0].url : dead.currentURI.spec; + let sibling = dead.nextSibling; + + // Destory and re-create the browser + this._destroyBrowser(); + let browser = this._createBrowser(currentURL, sibling); + if (active) + this.active = true; + + // Reattach session store data and flag this browser so it is restored on select + browser.__SS_data = session.data; + browser.__SS_extdata = session.extra; + browser.__SS_restore = true; + }, + + _createBrowser: function _createBrowser(aURI, aInsertBefore) { + if (this._browser) + throw "Browser already exists"; + + // Create a notification box around the browser + let notification = this._notification = document.createElement("notificationbox"); + notification.classList.add("inputHandler"); + + // Create the browser using the current width the dynamically size the height + let browser = this._browser = document.createElement("browser"); + browser.setAttribute("class", "viewable-width viewable-height"); + this._chromeTab.linkedBrowser = browser; + + browser.setAttribute("type", "content"); + + let useRemote = Services.prefs.getBoolPref("browser.tabs.remote"); + let useLocal = Util.isLocalScheme(aURI); + browser.setAttribute("remote", (!useLocal && useRemote) ? "true" : "false"); + + // Append the browser to the document, which should start the page load + notification.appendChild(browser); + Elements.browsers.insertBefore(notification, aInsertBefore); + + // stop about:blank from loading + browser.stop(); + + let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; + fl.renderMode = Ci.nsIFrameLoader.RENDER_MODE_ASYNC_SCROLL; + + return browser; + }, + + _destroyBrowser: function _destroyBrowser() { + if (this._browser) { + let notification = this._notification; + let browser = this._browser; + browser.active = false; + + this._notification = null; + this._browser = null; + this._loading = false; + + Elements.browsers.removeChild(notification); + } + }, + + clampZoomLevel: function clampZoomLevel(aScale) { + let browser = this._browser; + let bounded = Util.clamp(aScale, ZoomManager.MIN, ZoomManager.MAX); + + let md = this.metadata; + if (md && md.minZoom) + bounded = Math.max(bounded, md.minZoom); + if (md && md.maxZoom) + bounded = Math.min(bounded, md.maxZoom); + + bounded = Math.max(bounded, this.getPageZoomLevel()); + + let rounded = Math.round(bounded * kBrowserViewZoomLevelPrecision) / kBrowserViewZoomLevelPrecision; + return rounded || 1.0; + }, + + /** Record the initial zoom level when a page first loads. */ + resetZoomLevel: function resetZoomLevel() { + this._defaultZoomLevel = this._browser.scale; + }, + + scrolledAreaChanged: function scrolledAreaChanged() { + if (!this._browser) + return; + + this.updateDefaultZoomLevel(); + + if (!this.useFallbackWidth && this._browser.contentDocumentWidth > kDefaultBrowserWidth) + this.useFallbackWidth = true; + + this.updateViewportSize(); + }, + + /** + * Recalculate default zoom level when page size changes, and update zoom + * level if we are at default. + */ + updateDefaultZoomLevel: function updateDefaultZoomLevel() { + let browser = this._browser; + if (!browser) + return; + + let isDefault = this.isDefaultZoomLevel(); + this._defaultZoomLevel = this.getDefaultZoomLevel(); + if (isDefault) { + if (browser.scale != this._defaultZoomLevel) { + browser.scale = this._defaultZoomLevel; + } + else { + // If the scale level has not changed we want to be sure the content + // render correctly since the page refresh process could have been + // stalled during page load. In this case if the page has the exact + // same width (like the same page, so by doing 'refresh') and the + // page was scrolled the content is just checkerboard at this point + // and this call ensure we render it correctly. + browser.getRootView()._updateCacheViewport(); + } + } else { + // if we are reloading, the page will retain its scale. if it is zoomed + // we need to refresh the viewport so that we do not show checkerboard + browser.getRootView()._updateCacheViewport(); + } + }, + + isDefaultZoomLevel: function isDefaultZoomLevel() { + return this._browser.scale == this._defaultZoomLevel; + }, + + getDefaultZoomLevel: function getDefaultZoomLevel() { + let md = this.metadata; + if (md && md.defaultZoom) + return this.clampZoomLevel(md.defaultZoom); + + let pageZoom = this.getPageZoomLevel(); + + // If pageZoom is "almost" 100%, zoom in to exactly 100% (bug 454456). + let granularity = Services.prefs.getIntPref("browser.ui.zoom.pageFitGranularity"); + let threshold = 1 - 1 / granularity; + if (threshold < pageZoom && pageZoom < 1) + pageZoom = 1; + + return this.clampZoomLevel(pageZoom); + }, + + getPageZoomLevel: function getPageZoomLevel() { + let browserW = this._browser.contentDocumentWidth; + if (browserW == 0) + return 1.0; + + return this._browser.getBoundingClientRect().width / browserW; + }, + + get allowZoom() { + return this.metadata.allowZoom && !Util.isURLEmpty(this.browser.currentURI.spec); + }, + + updateThumbnail: function updateThumbnail() { + let browser = this._browser; + + if (this._loading) { + this._drawThumb = true; + return; + } + + // Do not repaint thumbnail if we already painted for this load. Bad things + // happen when we do async canvas draws in quick succession. + if (!browser || this._thumbnailWindowId == browser.contentWindowId) + return; + + // Do not try to paint thumbnails if contentWindowWidth/Height have not been + // set yet. This also blows up for async canvas draws. + if (!browser.contentWindowWidth || !browser.contentWindowHeight) + return; + + this._thumbnailWindowId = browser.contentWindowId; + this._chromeTab.updateThumbnail(browser, browser.contentWindowWidth, browser.contentWindowHeight); + }, + + set active(aActive) { + if (!this._browser) + return; + + let notification = this._notification; + let browser = this._browser; + + if (aActive) { + browser.setAttribute("type", "content-primary"); + Elements.browsers.selectedPanel = notification; + browser.active = true; + document.getElementById("tabs").selectedTab = this._chromeTab; + + // Ensure that the content process has gets an activate event + try { + let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; + fl.activateRemoteFrame(); + } catch (e) {} + } else { + browser.messageManager.sendAsyncMessage("Browser:Blur", { }); + browser.setAttribute("type", "content"); + browser.active = false; + } + }, + + get active() { + if (!this._browser) + return false; + return this._browser.getAttribute("type") == "content-primary"; + }, + + toString: function() { + return "[Tab " + (this._browser ? this._browser.currentURI.spec : "(no browser)") + "]"; + } +}; + +// Helper used to hide IPC / non-IPC differences for rendering to a canvas +function rendererFactory(aBrowser, aCanvas) { + let wrapper = {}; + + if (aBrowser.contentWindow) { + let ctx = aCanvas.getContext("2d"); + let draw = function(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags) { + ctx.drawWindow(browser.contentWindow, aLeft, aTop, aWidth, aHeight, aColor, aFlags); + let e = document.createEvent("HTMLEvents"); + e.initEvent("MozAsyncCanvasRender", true, true); + aCanvas.dispatchEvent(e); + }; + wrapper.checkBrowser = function(browser) { + return browser.contentWindow; + }; + wrapper.drawContent = function(callback) { + callback(ctx, draw); + }; + } + else { + let ctx = aCanvas.MozGetIPCContext("2d"); + let draw = function(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags) { + ctx.asyncDrawXULElement(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags); + }; + wrapper.checkBrowser = function(browser) { + return !browser.contentWindow; + }; + wrapper.drawContent = function(callback) { + callback(ctx, draw); + }; + } + + return wrapper; +}; + +/* ViewableAreaObserver is an helper object where width/height represents the + * size of the currently viewable area in pixels. This is use instead of + * window.innerHeight/innerWidth because some keyboards does not resize the + * window but floats over it. + */ +var ViewableAreaObserver = { + get width() { + return this._width || window.innerWidth; + }, + + get height() { + return (this._height || window.innerHeight); + }, + + observe: function va_observe(aSubject, aTopic, aData) { +#if MOZ_PLATFORM_MAEMO == 6 + let rect = Rect.fromRect(JSON.parse(aData)); + let height = rect.bottom - rect.top; + let width = rect.right - rect.left; + if (height == window.innerHeight && width == window.innerWidth) { + this._height = null; + this._width = null; + } + else { + this._height = height; + this._width = width; + } + this.update(); +#endif + }, + + update: function va_update() { + let oldHeight = parseInt(Browser.styles["viewable-height"].height); + let oldWidth = parseInt(Browser.styles["viewable-width"].width); + + let newWidth = this.width; + let newHeight = this.height; + if (newHeight == oldHeight && newWidth == oldWidth) + return; + + Browser.styles["viewable-height"].height = newHeight + "px"; + Browser.styles["viewable-height"].maxHeight = newHeight + "px"; + + Browser.styles["viewable-width"].width = newWidth + "px"; + Browser.styles["viewable-width"].maxWidth = newWidth + "px"; + + let startup = !oldHeight && !oldWidth; + for (let i = Browser.tabs.length - 1; i >= 0; i--) { + let tab = Browser.tabs[i]; + let oldContentWindowWidth = tab.browser.contentWindowWidth; + tab.updateViewportSize(); // contentWindowWidth may change here. + + // Don't bother updating the zoom level on startup + if (!startup) { + // If the viewport width is still the same, the page layout has not + // changed, so we can keep keep the same content on-screen. + if (tab.browser.contentWindowWidth == oldContentWindowWidth) + tab.restoreViewportPosition(oldWidth, newWidth); + + tab.updateDefaultZoomLevel(); + } + } + + // setTimeout(callback, 0) to ensure the resize event handler dispatch is finished + setTimeout(function() { + let event = document.createEvent("Events"); + event.initEvent("SizeChanged", true, false); + Elements.browsers.dispatchEvent(event); + }, 0); + } +}; diff --git a/mobile/chrome/content/browser.xul b/mobile/chrome/content/browser.xul new file mode 100644 index 000000000000..8372762fcfd7 --- /dev/null +++ b/mobile/chrome/content/browser.xul @@ -0,0 +1,696 @@ + + + + + + + + + + +%globalDTD; + +%browserDTD; + +%brandDTD; + +%prefsDTD; +#ifdef MOZ_SERVICES_SYNC + +%syncDTD; +#endif +]> + + + + + + + + + + diff --git a/mobile/chrome/content/firstrun/mozilla.png b/mobile/chrome/content/firstrun/mozilla.png new file mode 100644 index 000000000000..a6de797c1384 Binary files /dev/null and b/mobile/chrome/content/firstrun/mozilla.png differ diff --git a/mobile/chrome/content/firstrun/nav-arrow.png b/mobile/chrome/content/firstrun/nav-arrow.png new file mode 100644 index 000000000000..75d313549349 Binary files /dev/null and b/mobile/chrome/content/firstrun/nav-arrow.png differ diff --git a/mobile/chrome/content/firstrun/twitter.png b/mobile/chrome/content/firstrun/twitter.png new file mode 100644 index 000000000000..369450985a1a Binary files /dev/null and b/mobile/chrome/content/firstrun/twitter.png differ diff --git a/mobile/chrome/content/forms.js b/mobile/chrome/content/forms.js new file mode 100644 index 000000000000..83adacdddf5a --- /dev/null +++ b/mobile/chrome/content/forms.js @@ -0,0 +1,934 @@ +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- +/* + * ***** 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 Mobile Browser. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle + * Matt Brubeck + * Vivien Nicolas + * + * 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 ***** */ + +let Ci = Components.interfaces; +let Cc = Components.classes; + +dump("###################################### forms.js loaded\n"); + +let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement; +let HTMLInputElement = Ci.nsIDOMHTMLInputElement; +let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement; +let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement; +let HTMLDocument = Ci.nsIDOMHTMLDocument; +let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement; +let HTMLBodyElement = Ci.nsIDOMHTMLBodyElement; +let HTMLLabelElement = Ci.nsIDOMHTMLLabelElement; +let HTMLButtonElement = Ci.nsIDOMHTMLButtonElement; +let HTMLOptGroupElement = Ci.nsIDOMHTMLOptGroupElement; +let HTMLOptionElement = Ci.nsIDOMHTMLOptionElement; +let XULMenuListElement = Ci.nsIDOMXULMenuListElement; + +/** + * Responsible of navigation between forms fields and of the opening of the assistant + */ +function FormAssistant() { + addMessageListener("FormAssist:Closed", this); + addMessageListener("FormAssist:Previous", this); + addMessageListener("FormAssist:Next", this); + addMessageListener("FormAssist:ChoiceSelect", this); + addMessageListener("FormAssist:ChoiceChange", this); + addMessageListener("FormAssist:AutoComplete", this); + addMessageListener("Content:SetWindowSize", this); + + /* Listen text events in order to update the autocomplete suggestions as soon + * a key is entered on device + */ + addEventListener("text", this, false); + + addEventListener("keypress", this, true); + addEventListener("keyup", this, false); + addEventListener("focus", this, true); + addEventListener("pageshow", this, false); + addEventListener("pagehide", this, false); + + this._enabled = Services.prefs.getBoolPref("formhelper.enabled"); +}; + +FormAssistant.prototype = { + _selectWrapper: null, + _currentIndex: -1, + _elements: [], + + get currentElement() { + return this._elements[this._currentIndex]; + }, + + get currentIndex() { + return this._currentIndex; + }, + + set currentIndex(aIndex) { + let element = this._elements[aIndex]; + if (!element) + return -1; + + if (this._isVisibleElement(element)) { + this._currentIndex = aIndex; + gFocusManager.setFocus(element, Ci.nsIFocusManager.FLAG_NOSCROLL); + + // To ensure we get the current caret positionning of the focused + // element we need to delayed a bit the event + this._executeDelayed(function(self) { + // Bug 640870 + // Sometimes the element inner frame get destroyed while the element + // receive the focus because the display is turned to 'none' for + // example, in this "fun" case just do nothing if the element is hidden + if (self._isVisibleElement(gFocusManager.focusedElement)) + sendAsyncMessage("FormAssist:Show", self._getJSON()); + }); + } else { + // Repopulate the list of elements in the page, some could have gone + // because of AJAX changes for example + this._elements = []; + let currentIndex = this._getAllElements(gFocusManager.focusedElement) + + if (aIndex < this._currentIndex) + this.currentIndex = currentIndex - 1; + else if (aIndex > this._currentIndex) + this.currentIndex = currentIndex + 1; + else if (this._currentIndex != currentIndex) + this.currentIndex = currentIndex; + } + return element; + }, + + _open: false, + open: function formHelperOpen(aElement) { + // if the click is on an option element we want to check if the parent is a valid target + if (aElement instanceof HTMLOptionElement && aElement.parentNode instanceof HTMLSelectElement && !aElement.disabled) { + aElement = aElement.parentNode; + } + + // bug 526045 - the form assistant will close if a click happen: + // * outside of the scope of the form helper + // * hover a button of type=[image|submit] + // * hover a disabled element + if (!this._isValidElement(aElement)) { + let passiveButtons = { button: true, checkbox: true, file: true, radio: true, reset: true }; + if ((aElement instanceof HTMLInputElement || aElement instanceof HTMLButtonElement) && + passiveButtons[aElement.type] && !aElement.disabled) + return false; + + return this.close(); + } + + // Look for a top editable element + if (this._isEditable(aElement)) + aElement = this._getTopLevelEditable(aElement); + + // Checking if the element is the current focused one while the form assistant is open + // allow the user to reposition the caret into an input element + if (this._open && aElement == this.currentElement) { + //hack bug 604351 + // if the element is the same editable element and the VKB is closed, reopen it + let utils = Util.getWindowUtils(content); + if (utils.IMEStatus == utils.IME_STATUS_DISABLED && aElement instanceof HTMLInputElement && aElement.mozIsTextField(false)) { + aElement.blur(); + aElement.focus(); + } + + // If the element is a + +

+ + + + + + + + + + + + + + + diff --git a/mobile/chrome/tests/browser_autocomplete.js b/mobile/chrome/tests/browser_autocomplete.js new file mode 100644 index 000000000000..965da8d1d69c --- /dev/null +++ b/mobile/chrome/tests/browser_autocomplete.js @@ -0,0 +1,140 @@ +let testURL = chromeRoot + "browser_autocomplete.html"; +messageManager.loadFrameScript(chromeRoot + "remote_autocomplete.js", true); + +let newTab = null; + +// A queue to order the tests and a handle for each test +var gTests = []; +var gCurrentTest = null; + +function test() { + // This test is async + waitForExplicitFinish(); + + // Need to wait until the page is loaded + messageManager.addMessageListener("pageshow", function(aMessage) { + if (newTab && newTab.browser.currentURI.spec != "about:blank") { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + BrowserUI.closeAutoComplete(true); + setTimeout(runNextTest, 0); + } + }); + + newTab = Browser.addTab(testURL, true); +} + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Cleanup. All tests are completed at this point + try { + // Add any cleanup code here + } + finally { + // We must finialize the tests + finish(); + } + } +} + +function waitForAutocomplete(aCallback) { + messageManager.addMessageListener("FormAssist:AutoComplete", function(aMessage) { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + setTimeout(function() { + aCallback(aMessage.json.current.list); + }, 0); + }); +}; + +let data = [ + { label: "foo", value: "foo" }, + { label: "Somewhat bar", value: "bar" }, + { label: "foobar", value: "_" } +]; + +//------------------------------------------------------------------------------ +// Case: Click on a datalist element and show suggestions +gTests.push({ + desc: "Click on a datalist element and show suggestions", + + run: function() { + waitForAutocomplete(gCurrentTest.checkData); + AsyncTests.waitFor("TestRemoteAutocomplete:Click", + { id: "input-datalist-1" }, function(json) {}); + }, + + // Check that the data returned by the autocomplete handler on the content + // side is correct + checkData: function(aOptions) { + for (let i = 0; i < aOptions.length; i++) { + let option = aOptions[i]; + let valid = data[i]; + + is(option.label, valid.label, "Label should be equal (" + option.label + ", " + valid.label +")"); + is(option.value, valid.value, "Value should be equal (" + option.value + ", " + valid.value +")"); + } + + // Wait until suggestions box has been popupated + waitFor(gCurrentTest.checkUI, function() { + let suggestionsBox = document.getElementById("form-helper-suggestions"); + return suggestionsBox.childNodes.length; + }); + }, + + // Check that the UI reflect the specificity of the data + checkUI: function() { + let suggestionsBox = document.getElementById("form-helper-suggestions"); + let suggestions = suggestionsBox.childNodes; + + for (let i = 0; i < suggestions.length; i++) { + let suggestion = suggestions[i]; + let valid = data[i]; + let label = suggestion.getAttribute("value"); + let value = suggestion.getAttribute("data"); + + is(label, valid.label, "Label should be equal (" + label + ", " + valid.label +")"); + is(value, valid.value, "Value should be equal (" + value + ", " + valid.value +")"); + } + + gCurrentTest.checkUIClick(0); + }, + + // Ensure that clicking on a given datalist element set the right value in + // the input box + checkUIClick: function(aIndex) { + let suggestionsBox = document.getElementById("form-helper-suggestions"); + + let suggestion = suggestionsBox.childNodes[aIndex]; + if (!suggestion) { + gCurrentTest.finish(); + return; + } + + // Use the form helper autocompletion helper + FormHelperUI.doAutoComplete(suggestion); + + AsyncTests.waitFor("TestRemoteAutocomplete:Check", { id: "input-datalist-1" }, function(json) { + is(json.result, suggestion.getAttribute("data"), "The target input value should be set to " + data); + gCurrentTest.checkUIClick(aIndex + 1); + }); + }, + + finish: function() { + // Close the form assistant + FormHelperUI.hide(); + + // Close our tab when finished + Browser.closeTab(newTab); + + // We must finalize the tests + finish(); + } +}); + diff --git a/mobile/chrome/tests/browser_autocompletesearch.js b/mobile/chrome/tests/browser_autocompletesearch.js new file mode 100644 index 000000000000..0c1e31d37ef5 --- /dev/null +++ b/mobile/chrome/tests/browser_autocompletesearch.js @@ -0,0 +1,85 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +let match= [ + ["http://example.com/a", "A", "favicon", "http://example.com/a/favicon.png"], + ["http://example.com/b", "B", "favicon", "http://example.com/b/favicon.png"], + ["http://example.com/c", "C", "favicon", "http://example.com/c/favicon.png"], + ["http://example.com/d", "D", "bookmark", "http://example.com/d/favicon.png"], + ["http://example.com/e", "E", "boolmark", "http://example.com/e/favicon.png"] +]; + +var gAutocomplete = null; +var gProfileDir = null; + +function test() { + waitForExplicitFinish(); + + gProfileDir = Services.dirsvc.get("ProfD", Ci.nsIFile); + + // First we need to remove the existing cache file so we can control the state of the service + let oldCacheFile = gProfileDir.clone(); + oldCacheFile.append("autocomplete.json"); + if (oldCacheFile.exists()) + oldCacheFile.remove(true); + + // Since we removed the cache file, we know the service will need to write out a new + // file. We use that as a trigger to move forward. + Services.obs.addObserver(function (aSubject, aTopic, aData) { + Services.obs.removeObserver(arguments.callee, aTopic, false); + saveMockCache(); + }, "browser:cache-session-history-write-complete", false); + + // This might trigger an init or it may have already happened. That's why we need + // to do some work to control the state. + gAutocomplete = Cc["@mozilla.org/autocomplete/search;1?name=history"].getService(Ci.nsIAutoCompleteSearch); + + // Trigger the new cache to be written out, since the existing was removed + Services.obs.notifyObservers(null, "browser:cache-session-history-reload", ""); +} + +function saveMockCache() { + // Now we write our own mock data cache into the profile + let oldCacheFile = gProfileDir.clone(); + oldCacheFile.append("autocomplete.json"); + if (oldCacheFile.exists()) + oldCacheFile.remove(true); + + let mockCachePath = gTestPath; + let mockCacheFile = getChromeDir(getResolvedURI(mockCachePath)); + mockCacheFile.append("mock_autocomplete.json"); + mockCacheFile.copyToFollowingLinks(gProfileDir, "autocomplete.json"); + + // Listen for when the mock cache has been loaded + Services.obs.addObserver(function (aSubject, aTopic, aData) { + Services.obs.removeObserver(arguments.callee, aTopic, false); + runTest(); + }, "browser:cache-session-history-read-complete", false); + + // Trigger the new mock cache to be loaded + Services.obs.notifyObservers(null, "browser:cache-session-history-reload", ""); +} + +function runTest() { + let cacheFile = gProfileDir.clone(); + cacheFile.append("autocomplete.json"); + ok(cacheFile.exists(), "Mock autocomplete JSON cache file exists"); + + // Compare the mock data, which should be used now that we loaded it into the service + gAutocomplete.startSearch("", "", null, { + onSearchResult: function(search, result) { + is(result.matchCount, 5, "matchCount is correct"); + + for (let i=0; i<5; i++) { + is(result.getValueAt(i), match[i][0], "value matches"); + is(result.getCommentAt(i), match[i][1], "comment matches"); + is(result.getStyleAt(i), match[i][2], "style matches"); + is(result.getImageAt(i), match[i][3], "image matches"); + } + + if (cacheFile.exists()) + cacheFile.remove(true); + + finish(); + } + }); +} diff --git a/mobile/chrome/tests/browser_awesomescreen.js b/mobile/chrome/tests/browser_awesomescreen.js new file mode 100644 index 000000000000..46d1b8da2157 --- /dev/null +++ b/mobile/chrome/tests/browser_awesomescreen.js @@ -0,0 +1,432 @@ +/* + * Bug 436069 - Fennec browser-chrome tests to verify correct navigation into the + * differents part of the awesome panel + */ + +let testURL_01 = chromeRoot + "browser_blank_01.html"; + +let gTests = []; +let gCurrentTest = null; +let Panels = [AllPagesList, HistoryList, BookmarkList]; + +function test() { + // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()" + // We call "finish()" when the tests are finished + waitForExplicitFinish(); + + // Start the tests + setTimeout(runNextTest, 200); +} + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Close the awesome panel just in case + BrowserUI.activePanel = null; + finish(); + } +} + +function waitForNavigationPanel(aCallback, aWaitForHide) { + let evt = aWaitForHide ? "NavigationPanelHidden" : "NavigationPanelShown"; + info("waitFor " + evt + "(" + Components.stack.caller + ")"); + window.addEventListener(evt, function(aEvent) { + info("receive " + evt); + window.removeEventListener(aEvent.type, arguments.callee, false); + setTimeout(aCallback, 0); + }, false); +} + +//------------------------------------------------------------------------------ +// Case: Test awesome bar collapsed state +gTests.push({ + desc: "Test awesome bar collapsed state", + + run: function() { + waitForNavigationPanel(gCurrentTest.onPopupShown); + AllPagesList.doCommand(); + }, + + onPopupShown: function() { + is(BrowserUI.activePanel, AllPagesList, "AllPagesList should be visible"); + ok(!BrowserUI._edit.collapsed, "The urlbar edit element is visible"); + ok(BrowserUI._title.collapsed, "The urlbar title element is not visible"); + + waitForNavigationPanel(gCurrentTest.onPopupHidden, true); + EventUtils.synthesizeKey("VK_ESCAPE", {}, window); + }, + + onPopupHidden: function() { + is(BrowserUI.activePanel, null, "AllPagesList should be dismissed"); + ok(BrowserUI._edit.collapsed, "The urlbar edit element is not visible"); + ok(!BrowserUI._title.collapsed, "The urlbar title element is visible"); + + runNextTest(); + } +}); + + +//------------------------------------------------------------------------------ +// Case: Test typing a character should dismiss the awesome header +gTests.push({ + desc: "Test typing a character should dismiss the awesome header", + + run: function() { + waitForNavigationPanel(gCurrentTest.onPopupReady); + AllPagesList.doCommand(); + }, + + onPopupReady: function() { + is(BrowserUI.activePanel == AllPagesList, true, "AllPagesList should be visible"); + + let awesomeHeader = document.getElementById("awesome-header"); + is(awesomeHeader.hidden, false, "Awesome header should be visible"); + + BrowserUI._edit.addEventListener("onsearchbegin", function(aEvent) { + if (BrowserUI._edit.value == "") + return; + + BrowserUI._edit.removeEventListener(aEvent.type, arguments.callee, true); + let awesomeHeader = document.getElementById("awesome-header"); + is(awesomeHeader.hidden, true, "Awesome header should be hidden"); + gCurrentTest.onKeyPress(); + }, true); + EventUtils.synthesizeKey("A", {}, window); + }, + + onKeyPress: function(aKey, aHidden) { + waitForNavigationPanel(function() { + let awesomeHeader = document.getElementById("awesome-header"); + is(awesomeHeader.hidden, false, "Awesome header should be visible"); + runNextTest(); + }, true); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, window); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test typing a character should open the awesome bar +gTests.push({ + desc: "Test typing a character should open the All Pages List", + + run: function() { + waitForNavigationPanel(gCurrentTest.onPopupReady); + BookmarkList.doCommand(); + }, + + onPopupReady: function() { + BrowserUI._edit.addEventListener("onsearchbegin", function(aEvent) { + BrowserUI._edit.removeEventListener(aEvent.type, arguments.callee, false); + gCurrentTest.onSearchBegin(); + }, false); + EventUtils.synthesizeKey("I", {}, window); + }, + + onSearchBegin: function() { + let awesomeHeader = document.getElementById("awesome-header"); + is(awesomeHeader.hidden, true, "Awesome header should be hidden"); + is(BrowserUI.activePanel == AllPagesList, true, "AllPagesList should be opened on a keydown"); + is(BrowserUI._edit.readOnly, false, "urlbar should not be readonly after an input"); + + waitForNavigationPanel(gCurrentTest.onPopupHidden, true); + EventUtils.synthesizeKey("VK_ESCAPE", {}, window); + }, + + onPopupHidden: function() { + is(BrowserUI.activePanel == null, true, "VK_ESCAPE should have dismissed the awesome panel"); + runNextTest(); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test opening the awesome panel and checking the urlbar readonly state +gTests.push({ + desc: "Test opening the awesome panel and checking the urlbar readonly state", + + run: function() { + is(BrowserUI._edit.readOnly, true, "urlbar input textbox should be readonly"); + + waitForNavigationPanel(gCurrentTest.onPopupReady); + AllPagesList.doCommand(); + }, + + onPopupReady: function() { + is(Elements.urlbarState.getAttribute("mode"), "edit", "bcast_urlbarState mode attribute should be equal to 'edit'"); + + let edit = BrowserUI._edit; + is(edit.readOnly, !Util.isPortrait(), "urlbar input textbox be readonly once it is open in landscape, editable if portrait"); + + let urlString = BrowserUI.getDisplayURI(Browser.selectedBrowser); + if (Util.isURLEmpty(urlString)) + urlString = ""; + + let firstPanel = true; + Panels.forEach(function(aPanel) { + aPanel.doCommand(); + is(BrowserUI.activePanel, aPanel, "The panel " + aPanel.panel.id + " should be selected"); + if (firstPanel) { + // First panel will have selected text, if we are in portrait + is(edit.readOnly, !Util.isPortrait(), "urlbar input textbox be readonly once it is open in landscape, editable if portrait"); + } else { + is(edit.readOnly, true, "urlbar input textbox be readonly if not the first panel"); + } + edit.click(); + is(edit.readOnly, false, "urlbar input textbox should not be readonly after a click, in both landscape and portrait"); + is(edit.value, urlString, "urlbar value should be equal to the page uri"); + + firstPanel = false; + }); + + setTimeout(function() { + BrowserUI.activePanel = null; + runNextTest(); + }, 0); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test opening the awesome panel and checking the urlbar selection +gTests.push({ + desc: "Test opening the awesome panel and checking the urlbar selection", + + run: function() { + BrowserUI.closeAutoComplete(true); + this._currentTab = BrowserUI.newTab(testURL_01); + + // Need to wait until the page is loaded + messageManager.addMessageListener("pageshow", + function(aMessage) { + if (gCurrentTest._currentTab.browser.currentURI.spec != "about:blank") { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + setTimeout(gCurrentTest.onPageReady, 0); + } + }); + }, + + onPageReady: function() { + waitForNavigationPanel(gCurrentTest.onPopupReady); + + AllPagesList.doCommand(); + }, + + onPopupReady: function() { + let edit = BrowserUI._edit; + + let firstPanel = true; + Panels.forEach(function(aPanel) { + aPanel.doCommand(); + if (firstPanel && Util.isPortrait()) { + // First panel will have selected text, if we are in portrait + ok(edit.selectionStart == 0 && edit.selectionEnd == edit.textLength, "[case 1] urlbar text should be selected on a simple show"); + edit.click(); + // The click is not sync enough for this to work + todo(edit.selectionStart == edit.selectionEnd, "[case 1] urlbar text should not be selected on a click"); + } else { + ok(edit.selectionStart == edit.selectionEnd, "[case 2] urlbar text should not be selected on a simple show"); + edit.click(); + ok(edit.selectionStart == 0 && edit.selectionEnd == edit.textLength, "[case 2] urlbar text should be selected on a click"); + } + firstPanel = false; + }); + + // We are disabling it early, otherwise calling edit.click(); quickly made input.js though this is a double click (sigh) + let oldDoubleClickSelectsAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll"); + Services.prefs.setBoolPref("browser.urlbar.doubleClickSelectsAll", false); + + let oldClickSelectsAll = edit.clickSelectsAll; + edit.clickSelectsAll = false; + firstPanel = true; + Panels.forEach(function(aPanel) { + aPanel.doCommand(); + if (firstPanel && Util.isPortrait()) { + // First panel will have selected text, if we are in portrait + ok(edit.selectionStart == 0 && edit.selectionEnd == edit.textLength, "[case 1] urlbar text should be selected on a simple show"); + edit.click(); + // The click is not sync enough for this to work + todo(edit.selectionStart == edit.selectionEnd, "[case 1] urlbar text should not be selected on a click"); + } else { + ok(edit.selectionStart == edit.selectionEnd, "[case 2] urlbar text should not be selected on a simple show"); + edit.click(); + ok(edit.selectionStart == edit.selectionEnd, "[case 2] urlbar text should not be selected on a click"); + } + + firstPanel = false; + }); + + Panels.forEach(function(aPanel) { + aPanel.doCommand(); + ok(edit.selectionStart == edit.selectionEnd, "urlbar text should not be selected on a simple show"); + edit.click(); + edit.click(); + ok(edit.selectionStart == edit.selectionEnd, "urlbar text should not be selected on a double click"); + }); + + Services.prefs.setBoolPref("browser.urlbar.doubleClickSelectsAll", oldDoubleClickSelectsAll); + + Panels.forEach(function(aPanel) { + aPanel.doCommand(); + ok(edit.selectionStart == edit.selectionEnd, "urlbar text should not be selected on a simple show"); + edit.click(); + edit.click(); + ok(edit.selectionStart == 0 && edit.selectionEnd == edit.textLength, "urlbar text should be selected on a double click"); + }); + + edit.clickSelectsAll = oldClickSelectsAll; + + BrowserUI.closeTab(this._currentTab); + + BrowserUI.activePanel = null; + + // Ensure the tab is well closed before doing the rest of the code, otherwise + // this cause some bugs with the composition events + waitFor(runNextTest, function() { return Browser.tabs.length == 1 }); + } +}); + +// Case: Test context clicks on awesome panel +gTests.push({ + desc: "Test context clicks on awesome panel", + + _panelIndex : 0, + _contextOpts : [ + ["link-openable", "link-shareable"], + ["link-openable", "link-shareable"], + ["edit-bookmark", "link-shareable", "link-openable"], + ], + + clearContextTypes: function clearContextTypes() { + if (ContextHelper.popupState) + ContextHelper.hide(); + }, + + checkContextTypes: function checkContextTypes(aTypes) { + let commandlist = document.getElementById("context-commands"); + + for (let i=0; i -1) { + // command should be visible + if(command.hidden == true) + return false; + } else { + if(command.hidden == false) + return false; + } + } + return true; + }, + + run: function() { + waitForNavigationPanel(gCurrentTest.onPopupReady); + AllPagesList.doCommand(); + }, + + onPopupReady: function() { + let self = this; + if(self._panelIndex < Panels.length) { + let panel = Panels[self._panelIndex]; + panel.doCommand(); + + self.clearContextTypes(); + + EventUtils.synthesizeMouse(panel.panel, panel.panel.width / 2, panel.panel.height / 2, { type: "mousedown" }); + setTimeout(function() { + EventUtils.synthesizeMouse(panel.panel, panel.panel.width / 2, panel.panel.height / 2, { type: "mouseup" }); + ok(self.checkContextTypes(self._contextOpts[self._panelIndex]), "Correct context menu shown for panel"); + self.clearContextTypes(); + + self._panelIndex++; + self.onPopupReady(); + }, 500); + } else { + BrowserUI.activePanel = null; + runNextTest(); + } + } +}); + + +// Case: Test compositionevent +gTests.push({ + desc: "Test sending composition events", + _textValue: null, + get popup() { + delete this.popup; + return this.popup = document.getElementById("popup_autocomplete"); + }, + + get popupHeader() { + delete this.popupHeader; + return this.popupHeader = document.getElementById("awesome-header"); + }, + + get inputField() { + delete this.inputField; + return this.inputField = document.getElementById("urlbar-edit"); + }, + + run: function() { + // Saving value to compare the result before and after the composition event + gCurrentTest._textValue = gCurrentTest.inputField.value; + + window.addEventListener("popupshown", function() { + window.removeEventListener("popupshown", arguments.callee, false); + if (!Util.isPortrait()) + gCurrentTest.inputField.readOnly = false; + setTimeout(gCurrentTest.onPopupReady, 0); + }, false); + AllPagesList.doCommand(); + }, + + _checkState: function() { + ok(gCurrentTest.popup._popupOpen, "AutoComplete popup should be opened"); + is(gCurrentTest.popupHeader.hidden, false, "AutoComplete popup header should be visible"); + is(gCurrentTest.inputField.value, gCurrentTest._textValue, "Value should not have changed"); + }, + + onPopupReady: function() { + gCurrentTest._checkState(); + + window.addEventListener("compositionstart", function() { + window.removeEventListener("compositionstart", arguments.callee, false); + setTimeout(gCurrentTest.onCompositionStart, 0) + }, false); + Browser.windowUtils.sendCompositionEvent("compositionstart"); + }, + + onCompositionStart: function() { + gCurrentTest._checkState(); + + window.addEventListener("compositionend", function() { + window.removeEventListener("compositionend", arguments.callee, false); + setTimeout(gCurrentTest.onCompositionEnd, 0) + }, false); + Browser.windowUtils.sendCompositionEvent("compositionend"); + }, + + onCompositionEnd: function() { + gCurrentTest._checkState(); + + let isHiddenHeader = function() { + return gCurrentTest.popupHeader.hidden; + } + + // Wait to be sure there the header won't dissapear + // XXX this sucks because it means we'll be stuck 500ms if the test succeed + // but I don't have a better idea about how to do it for now since we don't + // that to happen! + waitForAndContinue(function() { + gCurrentTest._checkState(); + runNextTest(); + }, isHiddenHeader, Date.now() + 500); + } +}); + diff --git a/mobile/chrome/tests/browser_blank_01.html b/mobile/chrome/tests/browser_blank_01.html new file mode 100644 index 000000000000..8fd14cc1bebd --- /dev/null +++ b/mobile/chrome/tests/browser_blank_01.html @@ -0,0 +1,6 @@ + +Browser Blank Page 01 + +

Browser Blank Page 01

+ + diff --git a/mobile/chrome/tests/browser_blank_02.html b/mobile/chrome/tests/browser_blank_02.html new file mode 100644 index 000000000000..61d6b1f61acc --- /dev/null +++ b/mobile/chrome/tests/browser_blank_02.html @@ -0,0 +1,7 @@ + +Browser Blank Page 02 + + +

Browser Blank Page 02

+ + diff --git a/mobile/chrome/tests/browser_bookmarks.js b/mobile/chrome/tests/browser_bookmarks.js new file mode 100644 index 000000000000..1574cc04bdfc --- /dev/null +++ b/mobile/chrome/tests/browser_bookmarks.js @@ -0,0 +1,300 @@ +/* + * Bug 486490 - Fennec browser-chrome tests to verify correct implementation of chrome + * code in mobile/chrome/content in terms of integration with Places + * component, specifically for bookmark management. + */ + +var testURL_01 = chromeRoot + "browser_blank_01.html"; +var testURL_02 = chromeRoot + "browser_blank_02.html"; + +// A queue to order the tests and a handle for each test +var gTests = []; +var gCurrentTest = null; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()" + // We call "finish()" when the tests are finished + waitForExplicitFinish(); + + // Start the tests + runNextTest(); +} + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Cleanup. All tests are completed at this point + try { + PlacesUtils.bookmarks.removeFolderChildren(BookmarkList.panel.mobileRoot); + } + finally { + // We must finialize the tests + finish(); + } + } +} + +function waitForPageShow(aCallback) { + messageManager.addMessageListener("pageshow", function(aMessage) { + if (gCurrentTest._currentTab.browser.currentURI.spec != "about:blank") { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + setTimeout(aCallback, 0); + } + }); +} + +function waitForNavigationPanel(aCallback, aWaitForHide) { + let evt = aWaitForHide ? "NavigationPanelHidden" : "NavigationPanelShown"; + info("waitFor " + evt + "(" + Components.stack.caller + ")"); + window.addEventListener(evt, function(aEvent) { + info("receive " + evt); + window.removeEventListener(aEvent.type, arguments.callee, false); + setTimeout(aCallback, 0); + }, false); +} + +//------------------------------------------------------------------------------ +// Case: Test adding a bookmark with the Star button +gTests.push({ + desc: "Test adding a bookmark with the Star button", + _currentTab: null, + + run: function() { + this._currentTab = Browser.addTab(testURL_01, true); + + // Need to wait until the page is loaded + waitForPageShow(gCurrentTest.onPageReady); + }, + + onPageReady: function() { + let starbutton = document.getElementById("tool-star"); + starbutton.click(); + window.addEventListener("BookmarkCreated", function(aEvent) { + window.removeEventListener(aEvent.type, arguments.callee, false); + let bookmark = PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_01)); + ok(bookmark != -1, testURL_01 + " should be added."); + + Browser.closeTab(gCurrentTest._currentTab); + + runNextTest(); + }, false); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test clicking on a bookmark loads the web page +gTests.push({ + desc: "Test clicking on a bookmark loads the web page", + _currentTab: null, + + run: function() { + BrowserUI.closeAutoComplete(true); + this._currentTab = Browser.addTab(testURL_02, true); + + // Need to wait until the page is loaded + waitForPageShow(gCurrentTest.onPageReady); + }, + + onPageReady: function() { + // Wait for the bookmarks to load, then do the test + waitForNavigationPanel(gCurrentTest.onBookmarksReady); + BrowserUI.doCommand("cmd_bookmarks"); + }, + + onBookmarksReady: function() { + let bookmarkitem = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_01); + bookmarkitem.control.scrollBoxObject.ensureElementIsVisible(bookmarkitem); + + isnot(bookmarkitem, null, "Found the bookmark"); + is(bookmarkitem.getAttribute("uri"), testURL_01, "Bookmark has the right URL via attribute"); + is(bookmarkitem.spec, testURL_01, "Bookmark has the right URL via property"); + + // Create a listener for the opening bookmark + waitForPageShow(function() { + is(gCurrentTest._currentTab.browser.currentURI.spec, testURL_01, "Opened the right bookmark"); + Browser.closeTab(gCurrentTest._currentTab); + + runNextTest(); + }); + + EventUtils.synthesizeMouse(bookmarkitem, bookmarkitem.width / 2, bookmarkitem.height / 2, {}); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test editing URI of existing bookmark +gTests.push({ + desc: "Test editing URI of existing bookmark", + + run: function() { + // Wait for the bookmarks to load, then do the test + waitForNavigationPanel(gCurrentTest.onBookmarksReady); + BrowserUI.doCommand("cmd_bookmarks"); + }, + + onBookmarksReady: function() { + // Go into edit mode + let bookmark = BookmarkList.panel.items[0]; + bookmark.startEditing(); + + waitFor(gCurrentTest.onEditorReady, function() { return bookmark.isEditing == true; }); + }, + + onEditorReady: function() { + let bookmarkitem = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_01); + EventUtils.synthesizeMouse(bookmarkitem, bookmarkitem.width / 2, bookmarkitem.height / 2, {}); + + let uritextbox = document.getAnonymousElementByAttribute(bookmarkitem, "anonid", "uri"); + uritextbox.value = testURL_02; + + let donebutton = document.getAnonymousElementByAttribute(bookmarkitem, "anonid", "done-button"); + donebutton.click(); + + let bookmark = PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_01)); + is(bookmark, -1, testURL_01 + " should no longer in bookmark"); + bookmark = PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_02)); + isnot(bookmark, -1, testURL_02 + " is in bookmark"); + + BrowserUI.activePanel = null; + + runNextTest(); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test editing title of existing bookmark +gTests.push({ + desc: "Test editing title of existing bookmark", + + run: function() { + // Wait for the bookmarks to load, then do the test + waitForNavigationPanel(gCurrentTest.onBookmarksReady); + BrowserUI.doCommand("cmd_bookmarks"); + }, + + onBookmarksReady: function() { + // Go into edit mode + let bookmark = BookmarkList.panel.items[0]; + bookmark.startEditing(); + + waitFor(gCurrentTest.onEditorReady, function() { return bookmark.isEditing == true; }); + }, + + onEditorReady: function() { + let bookmark = PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_02)); + is(PlacesUtils.bookmarks.getItemTitle(bookmark), "Browser Blank Page 01", "Title remains the same."); + + let bookmarkitem = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_02); + EventUtils.synthesizeMouse(bookmarkitem, bookmarkitem.width / 2, bookmarkitem.height / 2, {}); + + let titletextbox = document.getAnonymousElementByAttribute(bookmarkitem, "anonid", "name"); + let newtitle = "Changed Title"; + titletextbox.value = newtitle; + + let donebutton = document.getAnonymousElementByAttribute(bookmarkitem, "anonid", "done-button"); + donebutton.click(); + + isnot(PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_02)), -1, testURL_02 + " is still in bookmark."); + is(PlacesUtils.bookmarks.getItemTitle(bookmark), newtitle, "Title is changed."); + + BrowserUI.activePanel = null; + + runNextTest(); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test removing existing bookmark +gTests.push({ + desc: "Test removing existing bookmark", + bookmarkitem: null, + + run: function() { + // Wait for the bookmarks to load, then do the test + waitForNavigationPanel(gCurrentTest.onBookmarksReady); + BrowserUI.doCommand("cmd_bookmarks"); + }, + + onBookmarksReady: function() { + // Go into edit mode + let bookmark = BookmarkList.panel.items[0]; + bookmark.startEditing(); + + waitFor(gCurrentTest.onEditorReady, function() { return bookmark.isEditing == true; }); + }, + + onEditorReady: function() { + let bookmark = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_02); + bookmark.remove(); + + let bookmark = PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_02)); + ok(bookmark == -1, testURL_02 + " should no longer in bookmark"); + bookmark = PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_01)); + ok(bookmark == -1, testURL_01 + " should no longer in bookmark"); + + BrowserUI.activePanel = null; + + runNextTest(); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test editing title of desktop folder +gTests.push({ + desc: "Test editing title of desktop folder", + bmId: null, + + run: function() { + // Add a bookmark to the desktop area so the desktop folder is displayed + gCurrentTest.bmId = PlacesUtils.bookmarks + .insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + makeURI(testURL_02), + Ci.nsINavBookmarksService.DEFAULT_INDEX, + testURL_02); + + // Wait for the bookmarks to load, then do the test + waitForNavigationPanel(gCurrentTest.onBookmarksReady); + BrowserUI.doCommand("cmd_bookmarks"); + }, + + onBookmarksReady: function() { + // Go into edit mode + let bookmarksPanel = BookmarkList.panel; + let bookmark = bookmarksPanel.items[0]; + bookmark.startEditing(); + + // Is the "desktop" folder showing? + let first = bookmarksPanel._children.firstChild; + is(first.itemId, bookmarksPanel._desktopFolderId, "Desktop folder is showing"); + + // Is the "desktop" folder in edit mode? + is(first.isEditing, false, "Desktop folder is not in edit mode"); + + // Do not allow the "desktop" folder to be editable by tap + EventUtils.synthesizeMouse(first, first.width / 2, first.height / 2, {}); + + // A tap on the "desktop" folder _should_ open the folder, not put it into edit mode. + // So we need to get the first item again. + first = bookmarksPanel._children.firstChild; + + // It should not be the "desktop" folder + isnot(first.itemId, bookmarksPanel._desktopFolderId, "Desktop folder is not showing after mouse click"); + + // But it should be one of the other readonly bookmark roots + isnot(bookmarksPanel._readOnlyFolders.indexOf(parseInt(first.itemId)), -1, "Desktop subfolder is showing after mouse click"); + + PlacesUtils.bookmarks.removeItem(gCurrentTest.bmId); + + BrowserUI.activePanel = null; + runNextTest(); + } +}); diff --git a/mobile/chrome/tests/browser_bookmarks_star.js b/mobile/chrome/tests/browser_bookmarks_star.js new file mode 100644 index 000000000000..441e0b861286 --- /dev/null +++ b/mobile/chrome/tests/browser_bookmarks_star.js @@ -0,0 +1,268 @@ +/* + * Bug 486490 - Fennec browser-chrome tests to verify correct implementation of chrome + * code in mobile/chrome/content in terms of integration with Places + * component, specifically for bookmark management. + */ + +let testURL_01 = chromeRoot + "browser_blank_01.html"; +let testURL_02 = chromeRoot + "browser_blank_02.html"; + +// A queue to order the tests and a handle for each test +let gTests = []; +let gCurrentTest = null; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()" + // We call "finish()" when the tests are finished + waitForExplicitFinish(); + + // Start the tests + runNextTest(); +} + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Cleanup. All tests are completed at this point + try { + PlacesUtils.bookmarks.removeFolderChildren(BookmarkList.panel.mobileRoot); + } + finally { + // We must finialize the tests + finish(); + } + } +} + +//------------------------------------------------------------------------------ +// Case: Test appearance and behavior of the bookmark popup +gTests.push({ + desc: "Test appearance and behavior of the bookmark popup", + _currentTab: null, + + run: function() { + this._currentTab = Browser.addTab(testURL_02, true); + + messageManager.addMessageListener("pageshow", + function(aMessage) { + if (gCurrentTest._currentTab.browser.currentURI.spec != "about:blank") { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + + // Wait a bit until Places is initialized + waitFor(gCurrentTest.onPageLoad, function() { + let mobileRoot = PlacesUtils.annotations.getItemsWithAnnotation("mobile/bookmarksRoot", {})[0]; + return mobileRoot; + }); + } + }); + }, + + onPageLoad: function() { + let starbutton = document.getElementById("tool-star"); + starbutton.click(); + + waitFor(gCurrentTest.onPopupReady, function() { return BookmarkPopup.box.hidden == false; }); + }, + + onPopupReady: function() { + // Let's make it disappear again by clicking the star again + let starbutton = document.getElementById("tool-star"); + starbutton.click(); + + waitFor(gCurrentTest.onPopupGone, function() { return BookmarkPopup.box.hidden == true; }); + }, + + onPopupGone: function() { + // Make sure it's hidden again + is(BookmarkPopup.box.hidden, true, "Bookmark popup should be hidden by clicking star"); + + // Let's make it appear again and continue the test + let starbutton = document.getElementById("tool-star"); + starbutton.click(); + + waitFor(gCurrentTest.onPopupReady2, function() { return BookmarkPopup.box.hidden == false; }); + }, + + onPopupReady2: function() { + // Let's make it disappear again by clicking somewhere + let contentarea = document.getElementById("browsers"); + EventUtils.synthesizeMouse(contentarea, contentarea.clientWidth / 2, contentarea.clientHeight / 2, {}); + + waitFor(gCurrentTest.onPopupGone2, function() { return BookmarkPopup.box.hidden == true; }); + }, + + onPopupGone2: function() { + // Make sure it's hidden again + is(BookmarkPopup.box.hidden, true, "Bookmark popup should be hidden by clicking in content"); + + BookmarkHelper.removeBookmarksForURI(getBrowser().currentURI); + BrowserUI.closeTab(this._currentTab); + + runNextTest(); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test adding tags via star icon +gTests.push({ + desc: "Test adding tags via star icon", + _currentTab: null, + + run: function() { + this._currentTab = Browser.addTab(testURL_02, true); + + messageManager.addMessageListener("pageshow", + function(aMessage) { + if (gCurrentTest._currentTab.browser.currentURI.spec != "about:blank") { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + gCurrentTest.onPageLoad(); + } + }); + }, + + onPageLoad: function() { + let starbutton = document.getElementById("tool-star"); + starbutton.click(); + + waitFor(gCurrentTest.onPopupReady, function() { return BookmarkPopup.box.hidden == false }); + }, + + onPopupReady: function() { + let editbutton = document.getElementById("bookmark-popup-edit"); + editbutton.click(); + + waitFor(gCurrentTest.onEditorReady, function() { + let item = document.getElementById("bookmark-item"); + return item && item.isEditing == true; + }); + }, + + onEditorReady: function() { + let bookmarkitem = document.getElementById("bookmark-item"); + bookmarkitem.tags = "tagone, tag two, tag-three, tag4"; + + let donebutton = document.getAnonymousElementByAttribute(bookmarkitem, "anonid", "done-button"); + donebutton.click(); + + waitFor(gCurrentTest.onEditorDone, function() { return document.getElementById("bookmark-container").hidden == true; }); + }, + + onEditorDone: function() { + let uri = makeURI(testURL_02); + let tagsarray = PlacesUtils.tagging.getTagsForURI(uri, {}); + is(tagsarray.length, 4, "All tags are added."); + + BookmarkHelper.removeBookmarksForURI(uri); + BrowserUI.closeTab(this._currentTab); + + runNextTest(); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test editing uri via star icon +gTests.push({ + desc: "Test editing uri via star icon", + _currentTab: null, + + run: function() { + this._currentTab = Browser.addTab(testURL_02, true); + + messageManager.addMessageListener("pageshow", + function(aMessage) { + if (gCurrentTest._currentTab.browser.currentURI.spec != "about:blank") { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + gCurrentTest.onPageLoad(); + } + }); + }, + + onPageLoad: function() { + let starbutton = document.getElementById("tool-star"); + starbutton.click(); + + waitFor(gCurrentTest.onPopupReady, function() { + return BookmarkPopup.box.hidden == false && + PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_02)) != -1; + }); + }, + + onPopupReady: function() { + let editbutton = document.getElementById("bookmark-popup-edit"); + editbutton.click(); + + waitFor(gCurrentTest.onEditorReady, function() { + let item = document.getElementById("bookmark-item"); + return item && item.isEditing == true; + }); + }, + + onEditorReady: function() { + let bookmarkitem = document.getElementById("bookmark-item"); + bookmarkitem.spec = testURL_01; + + let donebutton = document.getAnonymousElementByAttribute(bookmarkitem, "anonid", "done-button"); + donebutton.click(); + + waitFor(gCurrentTest.onEditorDone, function() { return document.getElementById("bookmark-container").hidden == true; }); + }, + + onEditorDone: function() { + isnot(PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_01)), -1, testURL_01 + " is now bookmarked"); + is(PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_02)), -1, testURL_02 + " is no longer bookmarked"); + + BookmarkHelper.removeBookmarksForURI(makeURI(testURL_02)); + BrowserUI.closeTab(this._currentTab); + + runNextTest(); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test removing existing bookmark via popup +gTests.push({ + desc: "Test removing existing bookmark via popup", + _currentTab: null, + run: function() { + this._currentTab = Browser.addTab(testURL_01, true); + + messageManager.addMessageListener("pageshow", + function(aMessage) { + if (gCurrentTest._currentTab.browser.currentURI.spec != "about:blank") { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + gCurrentTest.onPageLoad(); + } + }); + }, + + onPageLoad: function() { + let starbutton = document.getElementById("tool-star"); + starbutton.click(); + + waitFor(gCurrentTest.onPopupReady, function() { + return BookmarkPopup.box.hidden == false && + PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_01)) != -1; + }); + }, + + onPopupReady: function() { + let removebutton = document.getElementById("bookmark-popup-remove"); + removebutton.click(); + + let bookmark = PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_01)); + ok(bookmark == -1, testURL_01 + " should no longer in bookmark"); + + BrowserUI.closeTab(this._currentTab); + + runNextTest(); + } +}); diff --git a/mobile/chrome/tests/browser_bookmarks_tags.js b/mobile/chrome/tests/browser_bookmarks_tags.js new file mode 100644 index 000000000000..4d4541abf6a9 --- /dev/null +++ b/mobile/chrome/tests/browser_bookmarks_tags.js @@ -0,0 +1,192 @@ +/* + * Bug 486490 - Fennec browser-chrome tests to verify correct implementation of chrome + * code in mobile/chrome/content in terms of integration with Places + * component, specifically for bookmark management. + */ + +var testURL_01 = chromeRoot + "browser_blank_01.html"; +var testURL_02 = chromeRoot + "browser_blank_02.html"; + +// A queue to order the tests and a handle for each test +var gTests = []; +var gCurrentTest = null; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()" + // We call "finish()" when the tests are finished + waitForExplicitFinish(); + + // Start the tests + runNextTest(); +} + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Cleanup. All tests are completed at this point + try { + PlacesUtils.bookmarks.removeFolderChildren(BookmarkList.panel.mobileRoot); + } + finally { + // We must finialize the tests + finish(); + } + } +} + +//------------------------------------------------------------------------------ +// Case: Test adding tags to bookmark +gTests.push({ + desc: "Test adding tags to a bookmark", + _currentTab: null, + + run: function() { + this._currentTab = Browser.addTab(testURL_02, true); + function handleEvent() { + gCurrentTest._currentTab.browser.removeEventListener("load", handleEvent, true); + gCurrentTest.onPageLoad(); + }; + this._currentTab.browser.addEventListener("load", handleEvent , true); + }, + + onPageLoad: function() { + let starbutton = document.getElementById("tool-star"); + starbutton.click(); + + window.addEventListener("BookmarkCreated", function(aEvent) { + window.removeEventListener(aEvent.type, arguments.callee, false); + let bookmarkItem = PlacesUtils.getMostRecentBookmarkForURI(makeURI(testURL_02)); + ok(bookmarkItem != -1, testURL_02 + " should be added."); + + // Wait for the bookmarks to load, then do the test + window.addEventListener("NavigationPanelShown", gCurrentTest.onBookmarksReady, false); + BrowserUI.doCommand("cmd_bookmarks"); + }, false); + }, + + onBookmarksReady: function() { + window.removeEventListener("NavigationPanelShown", gCurrentTest.onBookmarksReady, false); + + // Go into edit mode + let bookmark = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_02); + bookmark.startEditing(); + + waitFor(gCurrentTest.onEditorReady, function() { return bookmark.isEditing == true; }); + }, + + onEditorReady: function() { + let bookmark = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_02); + let tagstextbox = document.getAnonymousElementByAttribute(bookmark, "anonid", "tags"); + tagstextbox.value = "tagone, tag two, tag-three, tag4"; + + let donebutton = document.getAnonymousElementByAttribute(bookmark, "anonid", "done-button"); + donebutton.click(); + + let tagsarray = PlacesUtils.tagging.getTagsForURI(makeURI(testURL_02), {}); + is(tagsarray.length, 4, "All tags are associated with specified bookmark"); + + BrowserUI.activePanel = null; + + Browser.closeTab(gCurrentTest._currentTab); + + runNextTest(); + } +}); + +//------------------------------------------------------------------------------ +// Case: Test editing tags to bookmark +gTests.push({ + desc: "Test editing tags to bookmark", + + run: function() { + // Wait for the bookmarks to load, then do the test + window.addEventListener("NavigationPanelShown", gCurrentTest.onBookmarksReady, false); + BrowserUI.doCommand("cmd_bookmarks"); + }, + + onBookmarksReady: function() { + window.removeEventListener("NavigationPanelShown", gCurrentTest.onBookmarksReady, false); + + + // Go into edit mode + let bookmark = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_02); + bookmark.startEditing(); + + waitFor(gCurrentTest.onEditorReady, function() { return bookmark.isEditing == true; }); + }, + + onEditorReady: function() { + let bookmark = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_02); + + let taggeduri = PlacesUtils.tagging.getURIsForTag("tag-three"); + is(taggeduri[0].spec, testURL_02, "Old tag still associated with bookmark"); + + let tagstextbox = document.getAnonymousElementByAttribute(bookmark, "anonid", "tags"); + tagstextbox.value = "tagone, tag two, edited-tag-three, tag4"; + + let donebutton = document.getAnonymousElementByAttribute(bookmark, "anonid", "done-button"); + donebutton.click(); + + let untaggeduri = PlacesUtils.tagging.getURIsForTag("tag-three"); + is(untaggeduri, "", "Old tag is not associated with any bookmark"); + taggeduri = PlacesUtils.tagging.getURIsForTag("edited-tag-three"); + is(taggeduri[0].spec, testURL_02, "New tag is added to bookmark"); + let tagsarray = PlacesUtils.tagging.getTagsForURI(makeURI(testURL_02), {}); + is(tagsarray.length, 4, "Bookmark still has same number of tags"); + + BrowserUI.activePanel = null; + + runNextTest(); + } +}); + + +//------------------------------------------------------------------------------ +// Case: Test removing tags from bookmark +gTests.push({ + desc: "Test removing tags from a bookmark", + _currentTab: null, + + run: function() { + // Wait for the bookmarks to load, then do the test + window.addEventListener("NavigationPanelShown", gCurrentTest.onBookmarksReady, false); + BrowserUI.doCommand("cmd_bookmarks"); + }, + + onBookmarksReady: function() { + window.removeEventListener("NavigationPanelShown", gCurrentTest.onBookmarksReady, false); + + // Go into edit mode + let bookmark = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_02); + bookmark.startEditing(); + + waitFor(gCurrentTest.onEditorReady, function() { return bookmark.isEditing == true; }); + }, + + onEditorReady: function() { + let bookmark = document.getAnonymousElementByAttribute(BookmarkList.panel, "uri", testURL_02); + + let tagstextbox = document.getAnonymousElementByAttribute(bookmark, "anonid", "tags"); + tagstextbox.value = "tagone, tag two, tag4"; + + let donebutton = document.getAnonymousElementByAttribute(bookmark, "anonid", "done-button"); + donebutton.click(); + + let untaggeduri = PlacesUtils.tagging.getURIsForTag("edited-tag-three"); + is(untaggeduri, "", "Old tag is not associated with any bookmark"); + let tagsarray = PlacesUtils.tagging.getTagsForURI(makeURI(testURL_02), {}); + is(tagsarray.length, 3, "Tag is successfully deleted"); + + BrowserUI.activePanel = null; + runNextTest(); + } +}); diff --git a/mobile/chrome/tests/browser_click_content.html b/mobile/chrome/tests/browser_click_content.html new file mode 100644 index 000000000000..071ce21c24ba --- /dev/null +++ b/mobile/chrome/tests/browser_click_content.html @@ -0,0 +1,7 @@ + +Browser Click Page 01 + + + + + diff --git a/mobile/chrome/tests/browser_click_content.js b/mobile/chrome/tests/browser_click_content.js new file mode 100644 index 000000000000..b77856ed9525 --- /dev/null +++ b/mobile/chrome/tests/browser_click_content.js @@ -0,0 +1,129 @@ +let testURL_click = chromeRoot + "browser_click_content.html"; + +let currentTab; +let element; +let isClickFired = false; +let clickPosition = { x: null, y: null}; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // This test is async + waitForExplicitFinish(); + + // Add new tab + currentTab = Browser.addTab(testURL_click, true); + ok(currentTab, "Tab Opened"); + + // Wait for the tab to load, then do the test + messageManager.addMessageListener("pageshow", function() { + if (currentTab.browser.currentURI.spec == testURL_click) { + messageManager.removeMessageListener("pageshow", arguments.callee); + testClickAndPosition(); + }}); +} + +function clickFired(aEvent) { + isClickFired = true; + let [x, y] = browserViewToClient(aEvent.clientX, aEvent.clientY); + clickPosition.x = x; + clickPosition.y = y; +} + +function testClickAndPosition() { + // Do sanity tests + let uri = currentTab.browser.currentURI.spec; + is(uri, testURL_click, "URL Matches newly created Tab"); + + // Check click + element = currentTab.browser.contentDocument.getElementById("iframe-1"); + element.addEventListener("click", clickFired, true); + + EventUtils.synthesizeMouseForContent(element, 1, 1, {}, window); + waitFor(checkClick, function() { return isClickFired }); +} + +function checkClick() { + ok(isClickFired, "Click handler fired"); + element.removeEventListener("click", clickFired, true); + + // Check position + isClickFired = false; + element = currentTab.browser.contentDocument.documentElement; + element.addEventListener("click", clickFired, true); + + let rect = getBoundingContentRect(element); + EventUtils.synthesizeMouse(element, 1, rect.height + 10, {}, window); + waitFor(checkPosition, function() { return isClickFired }); +} + +function checkPosition() { + element.removeEventListener("click", clickFired, true); + + let rect = getBoundingContentRect(element); + is(clickPosition.x, 1, "X position is correct"); + is(clickPosition.y, rect.height + 10, "Y position is correct"); + + checkThickBorder(); +} + +function checkThickBorder() { + let frame = currentTab.browser.contentDocument.getElementById("iframe-2"); + let element = frame.contentDocument.getElementsByTagName("input")[0]; + + let frameRect = getBoundingContentRect(frame); + let frameLeftBorder = window.getComputedStyle(frame, "").borderLeftWidth; + let frameTopBorder = window.getComputedStyle(frame, "").borderTopWidth; + + let elementRect = getBoundingContentRect(element); + ok((frameRect.left + parseInt(frameLeftBorder)) < elementRect.left, "X position of nested element ok"); + ok((frameRect.top + parseInt(frameTopBorder)) < elementRect.top, "Y position of nested element ok"); + + close(); +} + +function close() { + // Close the tab + Browser.closeTab(currentTab); + + // We must finialize the tests + finish(); +} + +// XXX copied from chrome/content/content.js +function getBoundingContentRect(aElement) { + if (!aElement) + return new Rect(0, 0, 0, 0); + + let document = aElement.ownerDocument; + while(document.defaultView.frameElement) + document = document.defaultView.frameElement.ownerDocument; + + let content = document.defaultView; + let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + let scrollX = {}, scrollY = {}; + cwu.getScrollXY(false, scrollX, scrollY); + let offset = new Point(scrollX.value, scrollY.value); + let r = aElement.getBoundingClientRect(); + + // step out of iframes and frames, offsetting scroll values + for (let frame = aElement.ownerDocument.defaultView; frame != content; frame = frame.parent) { + // adjust client coordinates' origin to be top left of iframe viewport + let rect = frame.frameElement.getBoundingClientRect(); + let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth; + let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth; + offset.add(rect.left + parseInt(left), rect.top + parseInt(top)); + } + + return new Rect(r.left + offset.x, r.top + offset.y, r.width, r.height); +} + +function browserViewToClient(x, y) { + let container = document.getElementById("browsers"); + let containerBCR = container.getBoundingClientRect(); + + let x0 = Math.round(-containerBCR.left); + let y0 = Math.round(-containerBCR.top); + + return [x - x0, y - y0]; +} diff --git a/mobile/chrome/tests/browser_contacts.js b/mobile/chrome/tests/browser_contacts.js new file mode 100644 index 000000000000..47d61ac93cf7 --- /dev/null +++ b/mobile/chrome/tests/browser_contacts.js @@ -0,0 +1,88 @@ + +// pull in the Contacts service +Components.utils.import("resource:///modules/contacts.jsm"); + +let fac = Cc["@mozilla.org/satchel/form-autocomplete;1"].getService(Ci.nsIFormAutoComplete); +let fh = Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2); + +function test() { + ok(Contacts, "Contacts class exists"); + for (var fname in tests) { + tests[fname](); + } +} + +let MockContactsProvider = { + getContacts: function() { + let contacts = [ + { + fullName: "-Billy One", + emails: [], + phoneNumbers: ["999-888-7777"] + }, + { + fullName: "-Billy Two", + emails: ["billy.two@fake.com", "btwo@work.com"], + phoneNumbers: ["111-222-3333", "123-123-1234"] + }, + { + fullName: "-Joe Schmo", + emails: ["joeschmo@foo.com"], + phoneNumbers: ["555-555-5555"] + } + ]; + + return contacts; + } +}; + +// In case there are real contacts that could mess up our test counts +let preEmailCount = fac.autoCompleteSearch("email", "", null, null).matchCount; +let prePhoneCount = fac.autoCompleteSearch("tel", "", null, null).matchCount; + +Contacts.addProvider(MockContactsProvider); + +let tests = { + testBasicMatch: function() { + // Search for any emails + let results = fac.autoCompleteSearch("email", "", null, null); + is(results.matchCount, 3 + preEmailCount, "Found 3 emails for un-filtered search"); + + // Do some filtered searches + results = fac.autoCompleteSearch("email", "-Billy", null, null); + is(results.matchCount, 2, "Found 2 emails '-Billy'"); + + results = fac.autoCompleteSearch("tel", "-Billy", null, null); + is(results.matchCount, 3, "Found 3 phone numbers '-Billy'"); + + results = fac.autoCompleteSearch("skip", "-Billy", null, null); + is(results.matchCount, 0, "Found nothing for a non-contact field"); + + results = fac.autoCompleteSearch("phone", "-Jo", null, null); + is(results.matchCount, 1, "Found 1 phone number '-Jo'"); + }, + + testMixingData: function() { + // Add a simple value to the non-contact system + fh.addEntry("email", "super.cool@place.com"); + + let results = fac.autoCompleteSearch("email", "", null, null); + is(results.matchCount, 4 + preEmailCount, "Found 4 emails for un-filtered search"); + + let firstEmail = results.getValueAt(0); + is(firstEmail, "super.cool@place.com", "The non-contact entry is first"); + + fh.removeAllEntries(); + }, + + testFakeInputField: function() { + let attributes = ["type", "id", "class"]; + for (let i = 0; i < attributes.length; i++) { + let field = document.createElementNS("http://www.w3.org/1999/xhtml", "html:input"); + field.setAttribute(attributes[i], "tel"); + + let results = fac.autoCompleteSearch("", "-Jo", field, null); + is(results.matchCount, 1 + prePhoneCount, "Found 1 phone number -Jo"); + } + } +}; diff --git a/mobile/chrome/tests/browser_find.js b/mobile/chrome/tests/browser_find.js new file mode 100644 index 000000000000..f0a5517efd28 --- /dev/null +++ b/mobile/chrome/tests/browser_find.js @@ -0,0 +1,26 @@ +// Tests for the Find In Page UI + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + let menu = document.getElementById("identity-container"); + let item = document.getElementById("pageaction-findinpage"); + let navigator = document.getElementById("content-navigator"); + + // Open and close the find toolbar + + getIdentityHandler().show(); + ok(!menu.hidden, "Site menu is open"); + ok(!navigator.isActive, "Toolbar is closed"); + + EventUtils.sendMouseEvent({ type: "click" }, item); + ok(menu.hidden, "Site menu is closed"); + ok(navigator.isActive, "Toolbar is open"); + + is(navigator._previousButton.disabled, true, "Previous button should be disabled"); + is(navigator._nextButton.disabled, true, "Previous button should be disabled"); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, window); + ok(menu.hidden, "Site menu is closed"); + ok(!navigator.isActive, "Toolbar is closed"); +} diff --git a/mobile/chrome/tests/browser_forms.html b/mobile/chrome/tests/browser_forms.html new file mode 100644 index 000000000000..5566bbe5bb52 --- /dev/null +++ b/mobile/chrome/tests/browser_forms.html @@ -0,0 +1,51 @@ + + + Browser Form Assistant + + + + + + + + + + text + +
click here
+ + +
+ + + dumb type + + +
div
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/chrome/tests/browser_forms.js b/mobile/chrome/tests/browser_forms.js new file mode 100644 index 000000000000..a956cb825e03 --- /dev/null +++ b/mobile/chrome/tests/browser_forms.js @@ -0,0 +1,242 @@ +let testURL = chromeRoot + "browser_forms.html"; +messageManager.loadFrameScript(chromeRoot + "remote_forms.js", true); + +let newTab = null; + +function test() { + // This test is async + waitForExplicitFinish(); + + // Need to wait until the page is loaded + messageManager.addMessageListener("pageshow", function(aMessage) { + if (newTab && newTab.browser.currentURI.spec != "about:blank") { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + setTimeout(onTabLoaded, 0); + } + }); + + // Add new tab to hold the page + newTab = Browser.addTab(testURL, true); +} + +function onTabLoaded() { + BrowserUI.closeAutoComplete(true); + testMouseEvents(); +} + +function testMouseEvents() { + // Sending a synthesized event directly on content should not work - we + // don't want web content to be able to open the form helper without the + // user consent, so we have to pass throught the canvas tile-container + AsyncTests.waitFor("Test:Click", {}, function(json) { + is(json.result, false, "Form Assistant should stay closed"); + }); + + AsyncTests.waitFor("Test:Focus", { value: "#root" }, function(json) { + is(json.result, false, "Form Assistant should stay closed"); + }); + + AsyncTests.waitFor("Test:FocusRedirect", { value: "*[tabindex='0']" }, function(json) { + is(json.result, false, "Form Assistant should stay closed"); + testOpenUIWithSyncFocus(); + }); +}; + +function waitForFormAssist(aCallback) { + messageManager.addMessageListener("FormAssist:Show", function(aMessage) { + messageManager.removeMessageListener(aMessage.name, arguments.callee); + setTimeout(function() { + ok(FormHelperUI._open, "Form Assistant should be open"); + setTimeout(aCallback, 0); + }); + }); +}; + +function testOpenUIWithSyncFocus() { + AsyncTests.waitFor("Test:Open", { value: "*[tabindex='0']" }, function(json) {}); + waitForFormAssist(testOpenUI); +}; + +function testOpenUI() { + AsyncTests.waitFor("Test:Open", { value: "*[tabindex='0']" }, function(json) {}); + waitForFormAssist(testOpenUIWithFocusRedirect); +}; + +function testOpenUIWithFocusRedirect() { + AsyncTests.waitFor("Test:OpenWithFocusRedirect", { value: "*[tabindex='0']" }, function(json) {}); + waitForFormAssist(testShowUIForSelect); +}; + +function testShowUIForSelect() { + AsyncTests.waitFor("Test:CanShowUI", { value: "#select"}, function(json) { + ok(json.result, "canShowUI for select element'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "#select", disabled: true }, function(json) { + is(json.result, false, "!canShowUI for disabled select element'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "#option"}, function(json) { + ok(json.result, "canShowUI for option element'"); + }); + + AsyncTests.waitFor("Test:CanShowUISelect", { value: "#option", disabled: true }, function(json) { + is(json.result, false, "!canShowUI for option element with a disabled parent select element'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "#option", disabled: true }, function(json) { + is(json.result, false, "!canShowUI for disabled option element'"); + testShowUIForElements(); + }); +} + +function testShowUIForElements() { + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='1']" }, function(json) { + ok(json.result, "canShowUI for input type='text'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='1']", disabled: true }, function(json) { + is(json.result, false, "!canShowUI for disabled input type='text'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='2']" }, function(json) { + ok(json.result, "canShowUI for input type='password'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='2']", disabled: true }, function(json) { + is(json.result, false, "!canShowUI for disabled input type='password'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='8']" }, function(json) { + ok(json.result, "canShowUI for contenteditable div"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='8']", disabled: true }, function(json) { + is(json.result, false, "!canShowUI for disabled contenteditable div"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='3']" }, function(json) { + is(json.result, false, "!canShowUI for input type='submit'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='4']" }, function(json) { + is(json.result, false, "!canShowUI for input type='file'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='5']" }, function(json) { + is(json.result, false, "!canShowUI for input button type='submit'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='6']" }, function(json) { + is(json.result, false, "!canShowUI for input div@role='button'"); + }); + + AsyncTests.waitFor("Test:CanShowUI", { value: "*[tabindex='6']" }, function(json) { + is(json.result, false, "!canShowUI for input type='image'"); + }); + + // Open the Form Helper + AsyncTests.waitFor("Test:Open", { value: "*[tabindex='1']" }, function(json) { + ok(json.result, "Form Assistant should be open"); + testTabIndexNavigation(); + }); +}; + +function testTabIndexNavigation() { + AsyncTests.waitFor("Test:Previous", { value: "*[tabindex='0']" }, function(json) { + is(json.result, false, "Focus should not have changed"); + }); + + AsyncTests.waitFor("Test:Next", { value: "*[tabindex='2']" }, function(json) { + is(json.result, true, "Focus should be on element with tab-index : 2"); + }); + + AsyncTests.waitFor("Test:Previous", { value: "*[tabindex='1']" }, function(json) { + is(json.result, true, "Focus should be on element with tab-index : 1"); + }); + + AsyncTests.waitFor("Test:Next"); + AsyncTests.waitFor("Test:Next"); + AsyncTests.waitFor("Test:Next"); + AsyncTests.waitFor("Test:Next"); + AsyncTests.waitFor("Test:Next"); + + AsyncTests.waitFor("Test:Next", { value: "*[tabindex='7']" }, function(json) { + is(json.result, true, "Focus should be on element with tab-index : 7"); + }); + + AsyncTests.waitFor("Test:Next", { value: "*[tabindex='8']" }, function(json) { + is(json.result, true, "Focus should be on element with tab-index : 8"); + }); + + AsyncTests.waitFor("Test:Next", { value: "*[tabindex='0']" }, function(json) { + is(json.result, true, "Focus should be on element with tab-index : 0"); + }); + + let ids = ["next", "select", "dumb", "reset", "checkbox", "radio0", "radio4", "last", "last"]; + for (let i = 0; i < ids.length; i++) { + let id = ids[i]; + AsyncTests.waitFor("Test:Next", { value: "#" + id }, function(json) { + is(json.result, true, "Focus should be on element with #id: " + id + ""); + }); + }; + + FormHelperUI.hide(); + let container = document.getElementById("content-navigator"); + is(container.hidden, true, "Form Assistant should be close"); + + AsyncTests.waitFor("Test:Open", { value: "*[tabindex='0']" }, function(json) { + ok(FormHelperUI._open, "Form Assistant should be open"); + testFocusChanges(); + }); +}; + +function testFocusChanges() { + AsyncTests.waitFor("Test:Focus", { value: "*[tabindex='1']" }, function(json) { + ok(json.result, "Form Assistant should be open"); + }); + + AsyncTests.waitFor("Test:Focus", { value: "#select" }, function(json) { + ok(json.result, "Form Assistant should stay open"); + }); + + AsyncTests.waitFor("Test:Focus", { value: "*[type='hidden']" }, function(json) { + ok(json.result, "Form Assistant should stay open"); + loadNestedIFrames(); + }); +} + +function loadNestedIFrames() { + AsyncTests.waitFor("Test:Iframe", { }, function(json) { + is(json.result, true, "Iframe should have loaded"); + navigateIntoNestedIFrames(); + }); +} + +function navigateIntoNestedIFrames() { + AsyncTests.waitFor("Test:IframeOpen", { }, function(json) { + is(json.result, true, "Form Assistant should have been opened"); + }); + + AsyncTests.waitFor("Test:IframePrevious", { value: 0 }, function(json) { + is(json.result, true, "Focus should not have move"); + }); + + AsyncTests.waitFor("Test:IframeNext", { value: 1 }, function(json) { + is(json.result, true, "Focus should have move"); + }); + + AsyncTests.waitFor("Test:IframeNext", { value: 1 }, function(json) { + is(json.result, true, "Focus should not have move"); + + // Close the form assistant + FormHelperUI.hide(); + + // Close our tab when finished + Browser.closeTab(newTab); + + // We must finalize the tests + finish(); + }); +}; + diff --git a/mobile/chrome/tests/browser_formsZoom.html b/mobile/chrome/tests/browser_formsZoom.html new file mode 100644 index 000000000000..36f641d0d0dd --- /dev/null +++ b/mobile/chrome/tests/browser_formsZoom.html @@ -0,0 +1,66 @@ + + + Browser Zoom Test + + +















+
+
+
+ +
+ + diff --git a/mobile/chrome/tests/browser_formsZoom.js b/mobile/chrome/tests/browser_formsZoom.js new file mode 100644 index 000000000000..145e918993bb --- /dev/null +++ b/mobile/chrome/tests/browser_formsZoom.js @@ -0,0 +1,216 @@ +let testURL_01 = chromeRoot + "browser_formsZoom.html"; + +let baseURI = "http://mochi.test:8888/browser/mobile/chrome/"; +let testURL_02 = baseURI + "browser_formsZoom.html"; +messageManager.loadFrameScript(baseURI + "remote_formsZoom.js", true); + +// A queue to order the tests and a handle for each test +var gTests = []; +var gCurrentTest = null; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()" + // We call "finish()" when the tests are finished + waitForExplicitFinish(); + // Start the tests + runNextTest(); +} + +function waitForPageShow(aPageURL, aCallback) { + messageManager.addMessageListener("pageshow", function(aMessage) { + if (aMessage.target.currentURI.spec == aPageURL) { + messageManager.removeMessageListener("pageshow", arguments.callee); + setTimeout(function() { aCallback(); }, 0); + } + }); +}; + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + setTimeout(gCurrentTest.run, 0); + } + else { + // Cleanup. All tests are completed at this point + try { + // Add any cleanup code here + } + finally { + // We must finialize the tests + finish(); + } + } +} + +function waitForZoom(aCallback) { + if (AnimatedZoom.isZooming()) { + let self = this; + window.addEventListener("AnimatedZoomEnd", function() { + window.removeEventListener("AnimatedZoomEnd", arguments.callee, false); + setTimeout(aCallback, 0); + }, false); + } + else setTimeout(aCallback, 0); +} + +function isElementVisible(aElement) { + let elementRect = Rect.fromRect(aElement.rect); + let caretRect = Rect.fromRect(aElement.caretRect); + + let browser = getBrowser(); + let zoomRect = Rect.fromRect(browser.getBoundingClientRect()); + let scroll = browser.getRootView().getPosition(); + let browserRect = new Rect(scroll.x, scroll.y, zoomRect.width, zoomRect.height); + + info("CanZoom: " +Browser.selectedTab.allowZoom); + + info("Browser rect: " + browserRect + " - scale: " + browser.scale); + info("Element rect: " + elementRect + " - caret rect: " + caretRect); + info("Scale element rect: " + elementRect.clone().scale(browser.scale, browser.scale) + " - scale caretRect: " + caretRect.clone().scale(browser.scale, browser.scale)); + info("Resulting zoom rect: " + Browser._getZoomRectForPoint(elementRect.center().x, elementRect.y, browser.scale)); + + let isCaretSyncEnabled = Services.prefs.getBoolPref("formhelper.autozoom.caret"); + if (isCaretSyncEnabled) { + ok(browserRect.contains(caretRect.clone().scale(browser.scale, browser.scale)), "Caret rect should be completely visible"); + } + else { + elementRect = elementRect.clone().scale(browser.scale, browser.scale); + let resultRect = browserRect.intersect(elementRect); + ok(!resultRect.isEmpty() && elementRect.x > browserRect.x && elementRect.y > browserRect.y, "Element should be partially visible"); + } +} + + +//------------------------------------------------------------------------------ +// Case: Loading a page and Zoom into textarea field with caret sync disabled +gTests.push({ + desc: "Loading a page and Zoom into textarea field with caret sync enabled", + elements: ["textarea-1", "textarea-2", "textarea-3", "textarea-4"], + _currentTab: null, + + run: function() { + Services.prefs.setBoolPref("formhelper.autozoom.caret", false); + gCurrentTest._currentTab = BrowserUI.newTab(testURL_01); + + waitForPageShow(testURL_01, function() { gCurrentTest.zoomNext(); }); + }, + + zoomNext: function() { + let id = this.elements.shift(); + if (!id) { + BrowserUI.closeTab(); + runNextTest(); + return; + } + + info("Zooming to " + id); + AsyncTests.waitFor("FormAssist:Show", { id: id }, function(json) { + waitForZoom(function() { + isElementVisible(json.current); + gCurrentTest.zoomNext(); + }); + }); + } +}); + +//------------------------------------------------------------------------------ +// Case: Loading a page and Zoom into textarea field with caret sync enabled +gTests.push({ + desc: "Loading a page and Zoom into textarea field with caret sync enabled", + elements: ["textarea-1", "textarea-2", "textarea-3", "textarea-4"], + _currentTab: null, + + run: function() { + Services.prefs.setBoolPref("formhelper.autozoom.caret", true); + gCurrentTest._currentTab = BrowserUI.newTab(testURL_01); + + waitForPageShow(testURL_01, function() { gCurrentTest.zoomNext(); }); + }, + + zoomNext: function() { + let id = this.elements.shift(); + if (!id) { + BrowserUI.closeTab(); + runNextTest(); + return; + } + + AsyncTests.waitFor("FormAssist:Show", { id: id }, function(json) { + waitForZoom(function() { + isElementVisible(json.current); + gCurrentTest.zoomNext(); + }); + }); + } +}); + +//------------------------------------------------------------------------------ +// Case: Loading a remote page and Zoom into textarea field with caret sync disabled +gTests.push({ + desc: "Loading a remote page and Zoom into textarea field with caret sync disabled", + elements: ["textarea-1", "textarea-2", "textarea-3", "textarea-4"], + _currentTab: null, + + run: function() { + Services.prefs.setBoolPref("formhelper.autozoom.caret", false); + gCurrentTest._currentTab = BrowserUI.newTab(testURL_02); + + waitForPageShow(testURL_02, function() { gCurrentTest.zoomNext(); }); + }, + + zoomNext: function() { + let id = this.elements.shift(); + if (!id) { + BrowserUI.closeTab(); + runNextTest(); + return; + } + + AsyncTests.waitFor("FormAssist:Show", { id: id }, function(json) { + waitForZoom(function() { + isElementVisible(json.current); + gCurrentTest.zoomNext(); + }); + }); + } +}); + +//------------------------------------------------------------------------------ +// Case: Loading a remote page and Zoom into textarea field with caret sync enabled +gTests.push({ + desc: "Loading a remote page and Zoom into textarea field with caret sync enabled", + elements: ["textarea-1", "textarea-2"], + _currentTab: null, + + run: function() { + Services.prefs.setBoolPref("formhelper.autozoom.caret", true); + gCurrentTest._currentTab = BrowserUI.newTab(testURL_02); + + waitForPageShow(testURL_02, function() { gCurrentTest.zoomNext(); }); + }, + + zoomNext: function() { + let id = this.elements.shift(); + if (!id) { + todo(false, "textarea-3 caret should be synced, but for some reason it is not"); + todo(false, "textarea-4 caret should be synced, but for some reason it is not"); + BrowserUI.closeTab(); + runNextTest(); + return; + } + + AsyncTests.waitFor("FormAssist:Show", { id: id }, function(json) { + waitForZoom(function() { + isElementVisible(json.current); + gCurrentTest.zoomNext(); + }); + }); + } +}); + diff --git a/mobile/chrome/tests/browser_history.js b/mobile/chrome/tests/browser_history.js new file mode 100644 index 000000000000..87a3ee03d1b8 --- /dev/null +++ b/mobile/chrome/tests/browser_history.js @@ -0,0 +1,75 @@ +/* + * Make sure history is being recorded when we visit websites. + */ + +var testURL_01 = "http://mochi.test:8888/browser/mobile/chrome/browser_blank_01.html"; + +// A queue to order the tests and a handle for each test +var gTests = []; +var gCurrentTest = null; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()" + // We call "finish()" when the tests are finished + waitForExplicitFinish(); + + // Start the tests + runNextTest(); +} + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Cleanup. All tests are completed at this point + try { + } + finally { + // We must finialize the tests + finish(); + } + } +} + +/** + * One-time observer callback. + */ +function waitForObserve(name, callback) { + var observerService = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + var observer = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), + observe: function(subject, topic, data) { + observerService.removeObserver(observer, name); + observer = null; + callback(subject, topic, data); + } + }; + + observerService.addObserver(observer, name, false); +} + +//------------------------------------------------------------------------------ + +gTests.push({ + desc: "Test history being added with page visit", + _currentTab: null, + + run: function() { + this._currentTab = Browser.addTab(testURL_01, true); + waitForObserve("uri-visit-saved", function(subject, topic, data) { + let uri = subject.QueryInterface(Ci.nsIURI); + ok(uri.spec == testURL_01, "URI was saved to history"); + Browser.closeTab(gCurrentTest._currentTab); + runNextTest(); + }); + } +}); diff --git a/mobile/chrome/tests/browser_install.xml b/mobile/chrome/tests/browser_install.xml new file mode 100644 index 000000000000..0623420e4e2e --- /dev/null +++ b/mobile/chrome/tests/browser_install.xml @@ -0,0 +1,58 @@ + + + + Install Tests + Extension + addon1@tests.mozilla.org + 1.0 + http://example.com/icon.png + http://example.com/ + + + Test Creator + http://example.com/creator.html + + + Public + Test add-on + Test add-on + + + Fennec + {a23983c0-fd0e-11dc-95ff-0800200c9a66} + 0 + 5 + + + ALL + http://example.com/browser/mobile/chrome/addons/browser_install1_1.xpi + + + + Install Tests 2 + Extension + addon2@tests.mozilla.org + 1.0 + http://example.com/icon.png + http://example.com/ + + + Test Creator + http://example.com/creator.html + + + Public + Test add-on 2 + Test add-on 2 + + + Fennec + {a23983c0-fd0e-11dc-95ff-0800200c9a66} + 0 + 5 + + + ALL + http://example.com/browser/mobile/chrome/addons/browser_install1_2.xpi + + diff --git a/mobile/chrome/tests/browser_mainui.js b/mobile/chrome/tests/browser_mainui.js new file mode 100644 index 000000000000..386858ade07d --- /dev/null +++ b/mobile/chrome/tests/browser_mainui.js @@ -0,0 +1,27 @@ +// Very basic tests for the main window UI + +const Cc = Components.classes; +const Ci = Components.interfaces; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + is(window.location.href, "chrome://browser/content/browser.xul", "Main window should be browser.xul"); + + window.focus(); + + let browser = Browser.selectedBrowser; + isnot(browser, null, "Should have a browser"); + + is(browser.currentURI.spec, Browser.selectedTab.browser.currentURI.spec, "selectedBrowser == selectedTab.browser"); + + testContentContainerSize(); +} + +function testContentContainerSize() { + let container = document.getElementById("content-viewport"); + + let rect = container.getBoundingClientRect(); + is(rect.width, window.innerWidth, "Content container is same width as window"); + is(rect.height, window.innerHeight, "Content container is same height as window"); +} diff --git a/mobile/chrome/tests/browser_navigation.js b/mobile/chrome/tests/browser_navigation.js new file mode 100644 index 000000000000..ad5f4177c7df --- /dev/null +++ b/mobile/chrome/tests/browser_navigation.js @@ -0,0 +1,404 @@ +var testURL_01 = chromeRoot + "browser_blank_01.html"; +var testURL_02 = chromeRoot + "browser_blank_02.html"; + +let baseURI = "http://mochi.test:8888/browser/mobile/chrome/"; +var titleURL = baseURI + "browser_title.sjs?"; +var pngURL = "data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw=="; + +// A queue to order the tests and a handle for each test +var gTests = []; +var gCurrentTest = null; + +var back = document.getElementById("tool-back"); +var forward = document.getElementById("tool-forward"); + +function pageLoaded(aURL) { + return function() { + let tab = gCurrentTest._currentTab; + return !tab.isLoading() && tab.browser.currentURI.spec == aURL; + } +} + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()" + // We call "finish()" when the tests are finished + waitForExplicitFinish(); + // Start the tests + runNextTest(); +} + +function waitForPageShow(aPageURL, aCallback) { + messageManager.addMessageListener("pageshow", function(aMessage) { + if (aMessage.target.currentURI.spec == aPageURL) { + messageManager.removeMessageListener("pageshow", arguments.callee); + setTimeout(function() { aCallback(); }, 0); + } + }); +}; + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Cleanup. All tests are completed at this point + try { + // Add any cleanup code here + } + finally { + // We must finialize the tests + finish(); + } + } +} + +//------------------------------------------------------------------------------ +// Case: Loading a page into the URLBar with VK_RETURN +gTests.push({ + desc: "Loading a page into the URLBar with VK_RETURN", + _currentTab: null, + + run: function() { + gCurrentTest._currentTab = BrowserUI.newTab(testURL_01); + + // Wait for the tab to load, then do the test + waitFor(gCurrentTest.onPageReady, pageLoaded(testURL_01)); + }, + + onPageReady: function() { + // Test the mode + let urlIcons = document.getElementById("urlbar-icons"); + is(urlIcons.getAttribute("mode"), "view", "URL Mode is set to 'view'"); + + // Test back button state + is(back.disabled, !gCurrentTest._currentTab.browser.canGoBack, "Back button check"); + + // Test forward button state + is(forward.disabled, !gCurrentTest._currentTab.browser.canGoForward, "Forward button check"); + + // Focus the url edit + let urlbarTitle = document.getElementById("urlbar-title"); + + // Wait for the awesomebar to load, then do the test + window.addEventListener("NavigationPanelShown", function() { + window.removeEventListener("NavigationPanelShown", arguments.callee, false); + setTimeout(gCurrentTest.onFocusReady, 0); + }, false); + EventUtils.synthesizeMouse(urlbarTitle, urlbarTitle.width / 2, urlbarTitle.height / 2, {}); + }, + + onFocusReady: function() { + // Test mode + let urlIcons = document.getElementById("urlbar-icons"); + is(urlIcons.getAttribute("mode"), "edit", "URL Mode is set to 'edit'"); + + // Test back button state + is(back.disabled, !gCurrentTest._currentTab.browser.canGoBack, "Back button check"); + + // Test forward button state + is(forward.disabled, !gCurrentTest._currentTab.browser.canGoForward, "Forward button check"); + + // Check button states (url edit is focused) + let search = document.getElementById("tool-search"); + let searchStyle = window.getComputedStyle(search, null); + is(searchStyle.visibility, "visible", "SEARCH is visible"); + + let stop = document.getElementById("tool-stop"); + let stopStyle = window.getComputedStyle(stop, null); + is(stopStyle.visibility, "collapse", "STOP is hidden"); + + let reload = document.getElementById("tool-reload"); + let reloadStyle = window.getComputedStyle(reload, null); + is(reloadStyle.visibility, "collapse", "RELOAD is hidden"); + + // Send the string and press return + EventUtils.synthesizeString(testURL_02, window); + + // It looks like there is a race condition somewhere that result having + // testURL_01 concatenate with testURL_02 as a urlbar value, so to + // workaround that we're waiting for the readonly state to be fully updated + function URLIsReadWrite() { + return BrowserUI._edit.readOnly == false; + } + + waitFor(function() { + // Wait for the tab to load, then do the test + waitFor(gCurrentTest.onPageFinish, pageLoaded(testURL_02)); + + setTimeout(function() { + is(BrowserUI._edit.value, testURL_02, "URL value should be equal to the string sent via synthesizeString"); + EventUtils.synthesizeKey("VK_RETURN", {}, window); + }, 0); + }, URLIsReadWrite); + }, + + onPageFinish: function() { + let urlIcons = document.getElementById("urlbar-icons"); + is(urlIcons.getAttribute("mode"), "view", "URL Mode is set to 'view'"); + + // Check button states (url edit is not focused) + let search = document.getElementById("tool-search"); + let searchStyle = window.getComputedStyle(search, null); + is(searchStyle.visibility, "collapse", "SEARCH is hidden"); + + let stop = document.getElementById("tool-stop"); + let stopStyle = window.getComputedStyle(stop, null); + is(stopStyle.visibility, "collapse", "STOP is hidden"); + + let reload = document.getElementById("tool-reload"); + let reloadStyle = window.getComputedStyle(reload, null); + is(reloadStyle.visibility, "visible", "RELOAD is visible"); + + let uri = gCurrentTest._currentTab.browser.currentURI.spec; + is(uri, testURL_02, "URL Matches newly created Tab"); + + // Go back in session + gCurrentTest._currentTab.browser.goBack(); + + // Wait for the tab to load, then do the test + waitFor(gCurrentTest.onPageBack, pageLoaded(testURL_01)); + }, + + onPageBack: function() { + // Test back button state + is(back.disabled, !gCurrentTest._currentTab.browser.canGoBack, "Back button check"); + + // Test forward button state + is(forward.disabled, !gCurrentTest._currentTab.browser.canGoForward, "Forward button check"); + + BrowserUI.closeTab(gCurrentTest._currentTab); + runNextTest(); + } +}); + +// Bug 611327 ----------------------------------------------------------------- +// Check for urlbar label value +gTests.push({ + desc: "Check for urlbar label value on different cases", + _currentTab: null, + + run: function() { + gCurrentTest._currentTab = BrowserUI.newTab(titleURL + "no_title"); + waitForPageShow(titleURL + "no_title", gCurrentTest.onPageLoadWithoutTitle); + }, + + onPageLoadWithoutTitle: function() { + let urlbarTitle = document.getElementById("urlbar-title"); + is(urlbarTitle.value, titleURL + "no_title", "The title should be equal to the URL"); + + BrowserUI.closeTab(gCurrentTest._currentTab); + + gCurrentTest._currentTab = BrowserUI.newTab(titleURL + "english_title"); + waitForPageShow(titleURL + "english_title", gCurrentTest.onPageLoadWithTitle); + }, + + onPageLoadWithTitle: function() { + let urlbarTitle = document.getElementById("urlbar-title"); + is(urlbarTitle.value, "English Title Page", "The title should be equal to the page title"); + + BrowserUI.closeTab(gCurrentTest._currentTab); + + gCurrentTest._currentTab = BrowserUI.newTab(titleURL + "dynamic_title"); + messageManager.addMessageListener("pageshow", function(aMessage) { + messageManager.removeMessageListener("pageshow", arguments.callee); + gCurrentTest.onBeforePageTitleChanged(); + }); + + messageManager.addMessageListener("DOMTitleChanged", function(aMessage) { + messageManager.removeMessageListener("DOMTitleChanged", arguments.callee); + urlbarTitle.addEventListener("DOMAttrModified", function(aEvent) { + if (aEvent.attrName == "value") { + urlbarTitle.removeEventListener("DOMAttrModified", arguments.callee, false); + setTimeout(gCurrentTest.onPageTitleChanged, 0); + } + }, false); + }); + }, + + onBeforePageTitleChanged: function() { + let urlbarTitle = document.getElementById("urlbar-title"); + isnot(urlbarTitle.value, "This is not a french title", "The title should not be equal to the new page title yet"); + }, + + onPageTitleChanged: function() { + let urlbarTitle = document.getElementById("urlbar-title"); + is(urlbarTitle.value, "This is not a french title", "The title should be equal to the new page title"); + + BrowserUI.closeTab(gCurrentTest._currentTab); + + gCurrentTest._currentTab = BrowserUI.newTab(titleURL + "redirect"); + waitForPageShow(titleURL + "no_title", gCurrentTest.onPageRedirect); + }, + + onPageRedirect: function() { + let urlbarTitle = document.getElementById("urlbar-title"); + is(urlbarTitle.value, gCurrentTest._currentTab.browser.currentURI.spec, "The title should be equal to the redirected page url"); + + BrowserUI.closeTab(gCurrentTest._currentTab); + + gCurrentTest._currentTab = BrowserUI.newTab(titleURL + "location"); + waitForPageShow(titleURL + "no_title", gCurrentTest.onPageLocation); + }, + + onPageLocation: function() { + let urlbarTitle = document.getElementById("urlbar-title"); + is(urlbarTitle.value, gCurrentTest._currentTab.browser.currentURI.spec, "The title should be equal to the relocate page url"); + + BrowserUI.closeTab(gCurrentTest._currentTab); + + // Wait for the awesomebar to load, then do the test + window.addEventListener("NavigationPanelShown", function() { + window.removeEventListener("NavigationPanelShown", arguments.callee, false); + + setTimeout(function() { + EventUtils.synthesizeString(testURL_02, window); + EventUtils.synthesizeKey("VK_RETURN", {}, window); + + waitForPageShow(testURL_02, gCurrentTest.onUserTypedValue); + }, 0); + + }, false); + + gCurrentTest._currentTab = BrowserUI.newTab("about:blank"); + }, + + onUserTypedValue: function() { + let urlbarTitle = document.getElementById("urlbar-title"); + is(urlbarTitle.value, "Browser Blank Page 02", "The title should be equal to the typed page url title"); + + // about:blank has been closed, so we need to close the last selected one + BrowserUI.closeTab(Browser.selectedTab); + + // Wait for the awesomebar to load, then do the test + window.addEventListener("NavigationPanelShown", function() { + window.removeEventListener("NavigationPanelShown", arguments.callee, false); + + EventUtils.synthesizeString("no_title", window); + + // Wait until the no_title result row is here + let popup = document.getElementById("popup_autocomplete"); + let result = null; + function hasResults() { + result = popup._items.childNodes[0]; + if (result) + return result.getAttribute("value") == (titleURL + "no_title"); + + return false; + }; + waitFor(function() { EventUtils.synthesizeMouse(result, result.width / 2, result.height / 2, {}); }, hasResults); + + urlbarTitle.addEventListener("DOMAttrModified", function(aEvent) { + if (aEvent.attrName == "value") { + urlbarTitle.removeEventListener("DOMAttrModified", arguments.callee, false); + is(urlbarTitle.value, titleURL + "no_title", "The title should be equal to the url of the clicked row"); + } + }, false); + + waitForPageShow(titleURL + "no_title", gCurrentTest.onUserSelectValue); + }, false); + + gCurrentTest._currentTab = BrowserUI.newTab("about:blank"); + }, + + onUserSelectValue: function() { + let urlbarTitle = document.getElementById("urlbar-title"); + is(urlbarTitle.value, Browser.selectedTab.browser.currentURI.spec, "The title should be equal to the clicked page url"); + + // about:blank has been closed, so we need to close the last selected one + BrowserUI.closeTab(Browser.selectedTab); + + //is(urlbarTitle.value, "Browser Blank Page 02", "The title of the second page must be displayed"); + runNextTest(); + } +}); + +// Case: Check for appearance of the favicon +gTests.push({ + desc: "Check for appearance of the favicon", + _currentTab: null, + + run: function() { + gCurrentTest._currentTab = BrowserUI.newTab(testURL_01); + waitForPageShow(testURL_01, gCurrentTest.onPageReady); + }, + + onPageReady: function() { + let favicon = document.getElementById("urlbar-favicon"); + is(favicon.src, "", "The default favicon must be loaded"); + BrowserUI.closeTab(gCurrentTest._currentTab); + + gCurrentTest._currentTab = BrowserUI.newTab(testURL_02); + waitForPageShow(testURL_02, gCurrentTest.onPageFinish); + }, + + onPageFinish: function(){ + let favicon = document.getElementById("urlbar-favicon"); + is(favicon.src, pngURL, "The page favicon must be loaded"); + BrowserUI.closeTab(gCurrentTest._currentTab); + runNextTest(); + } +}); + +// Bug 600707 - Back and forward buttons are updated when navigating within a page +// +// These tests use setTimeout instead of waitFor or addEventListener, because +// in-page navigation does not fire any loading events or progress +// notifications, and happens more or less instantly. +gTests.push({ + desc: "Navigating within a page via URI fragments", + _currentTab: null, + + run: function() { + gCurrentTest._currentTab = BrowserUI.newTab(testURL_01); + waitFor(gCurrentTest.onPageReady, pageLoaded(testURL_01)); + }, + + onPageReady: function() { + ok(back.disabled, "Can't go back"); + ok(forward.disabled, "Can't go forward"); + + messageManager.addMessageListener("Content:LocationChange", gCurrentTest.onFragmentLoaded); + Browser.loadURI(testURL_01 + "#fragment"); + }, + + onFragmentLoaded: function() { + messageManager.removeMessageListener("Content:LocationChange", arguments.callee); + + ok(!back.disabled, "Can go back"); + ok(forward.disabled, "Can't go forward"); + + messageManager.addMessageListener("Content:LocationChange", gCurrentTest.onBack); + CommandUpdater.doCommand("cmd_back"); + }, + + onBack: function() { + messageManager.removeMessageListener("Content:LocationChange", arguments.callee); + + ok(back.disabled, "Can't go back"); + ok(!forward.disabled, "Can go forward"); + + messageManager.addMessageListener("Content:LocationChange", gCurrentTest.onForward); + CommandUpdater.doCommand("cmd_forward"); + }, + + onForward: function() { + messageManager.removeMessageListener("Content:LocationChange", arguments.callee); + + ok(!back.disabled, "Can go back"); + ok(forward.disabled, "Can't go forward"); + + gCurrentTest.finish(); + }, + + finish: function() { + BrowserUI.closeTab(gCurrentTest._currentTab); + runNextTest(); + } +}); diff --git a/mobile/chrome/tests/browser_preferences_text.js b/mobile/chrome/tests/browser_preferences_text.js new file mode 100644 index 000000000000..439abc68ddb0 --- /dev/null +++ b/mobile/chrome/tests/browser_preferences_text.js @@ -0,0 +1,132 @@ +// Bug 571866 - --browser-chrome test for fennec preferences and text values + +var gTests = []; +var gCurrentTest = null; +var expected = { + "aboutButton": {"tagName": "button", "element_id": "prefs-about-button"}, + "homepage": {"element_id": "prefs-homepage", + "home_page": "prefs-homepage-default", + "blank_page": "prefs-homepage-none", + "current_page": "prefs-homepage-currentpage"}, + "doneButton": {"tagName": "button"}, + "contentRegion": {"element_id": "prefs-content"}, + "imageRegion": {"pref": "permissions.default.image", "anonid": "input", "localName": "checkbox"}, + "jsRegion": {"pref": "javascript.enabled", "anonid": "input", "localName": "checkbox"}, + "privacyRegion": {"element_id": "prefs-privacy" }, + "cookiesRegion": {"pref": "network.cookie.cookieBehavior", "anonid": "input", "localName": "checkbox"}, + "passwordsRegion": {"pref": "signon.rememberSignons", "anonid": "input", "localName": "checkbox"}, + "clearDataButton": {"element_id": "prefs-clear-data", "tagName": "button"} +}; + +function getPreferencesElements() { + let prefElements = {}; + prefElements.panelOpen = document.getElementById("tool-panel-open"); + prefElements.panelClose = document.getElementById("tool-panel-close"); + prefElements.panelContainer = document.getElementById("panel-container"); + prefElements.homeButton = document.getElementById("prefs-homepage-options"); + prefElements.doneButton = document.getElementById("select-buttons-done"); + prefElements.homePageControl = document.getElementById("prefs-homepage"); + prefElements.selectContainer = document.getElementById("menulist-container"); + return prefElements; +} + +function test() { + // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()" + // We call "finish()" when the tests are finished + waitForExplicitFinish(); + + // Start the tests + runNextTest(); +} +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Cleanup. All tests are completed at this point + finish(); + } +} + +// ----------------------------------------------------------------------------------------- +// Verify preferences and text +gTests.push({ + desc: "Verify Preferences and Text", + + run: function(){ + var prefs = getPreferencesElements(); + // 1.Click preferences to view prefs + prefs.panelOpen.click(); + + // 2. For each prefs *verify text *the button/option type *verify height of each field to be the same + is(prefs.panelContainer.hidden, false, "Preferences should be visible"); + + var prefsList = document.getElementById("prefs-messages"); + + // Check for *About page* + let about = expected.aboutButton; + var aboutRegion = document.getAnonymousElementByAttribute(prefsList, "title", "About Fennec"); + var aboutButton = document.getElementById(about.element_id); + is(aboutButton.tagName, about.tagName, "The About Fennec input must be a button"); + + // Check for *Startpage* + let homepage = expected.homepage; + var homepageRegion = document.getElementById(homepage.element_id); + prefs.homeButton.click(); + + is(prefs.selectContainer.hidden, false, "Homepage select dialog must be visible"); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, window); + is(prefs.selectContainer.hidden, true, "Homepage select dialog must be closed"); + + let content = expected.contentRegion; + var contentRegion = document.getElementById(content.element_id); + + // Check for *Show images* + var images = expected.imageRegion; + var imageRegion = document.getAnonymousElementByAttribute(contentRegion, "pref", images.pref); + var imageButton = document.getAnonymousElementByAttribute(imageRegion, "anonid", images.anonid); + is(imageButton.localName, images.localName, "Show images checkbox check"); + // Checkbox or radiobutton? + + // Check for *Enable javascript* + let js = expected.jsRegion; + var jsRegion = document.getAnonymousElementByAttribute(contentRegion, "pref", js.pref); + var jsButton = document.getAnonymousElementByAttribute(jsRegion, "anonid", js.anonid); + is(jsButton.localName, js.localName, "Enable JavaScript checkbox check"); + // Checkbox or radiobutton? + + let privacyRegion = expected.privacyRegion; + var prefsPrivacy = document.getElementById(privacyRegion.element_id); + + // Check for *Allow cookies* + let cookies = expected.cookiesRegion; + var cookiesRegion = document.getAnonymousElementByAttribute(prefsPrivacy, "pref", cookies.pref); + var cookiesButton = document.getAnonymousElementByAttribute(cookiesRegion, "anonid", cookies.anonid); + is(cookiesButton.localName, cookies.localName, "Allow cookies checkbox check"); + // Checkbox or radiobutton? + + // Check for *Remember password* + let passwords = expected.passwordsRegion; + var passwordsRegion = document.getAnonymousElementByAttribute(prefsPrivacy, "pref", passwords.pref); + var passwordsButton = document.getAnonymousElementByAttribute(passwordsRegion, "anonid", passwords.anonid); + is(passwordsButton.localName, passwords.localName, "Allow cookies checkbox check"); + // Checkbox or radiobutton? + + // Check for *Clear Private Data* + let clearData = expected.clearDataButton; + var clearDataRegion = prefsPrivacy.lastChild; + var clearDataButton = document.getElementById(clearData.element_id); + is(clearDataButton.tagName, clearData.tagName, "Check for Clear Private Data button type"); + + prefs.panelClose.click() + is(document.getElementById("panel-container").hidden, true, "Preferences panel should be closed"); + runNextTest(); + } +}); + diff --git a/mobile/chrome/tests/browser_rect.js b/mobile/chrome/tests/browser_rect.js new file mode 100644 index 000000000000..fb8be8cc4d25 --- /dev/null +++ b/mobile/chrome/tests/browser_rect.js @@ -0,0 +1,107 @@ +function test() { + waitForExplicitFinish(); + + ok(Rect, "Rect class exists"); + + for (let test in tests) { + tests[test](); + } + + finish(); +} + +let tests = { + testGetDimensions: function() { + let r = new Rect(5, 10, 100, 50); + ok(r.left == 5, "rect has correct left value"); + ok(r.top == 10, "rect has correct top value"); + ok(r.right == 105, "rect has correct right value"); + ok(r.bottom == 60, "rect has correct bottom value"); + ok(r.width == 100, "rect has correct width value"); + ok(r.height == 50, "rect has correct height value"); + ok(r.x == 5, "rect has correct x value"); + ok(r.y == 10, "rect has correct y value"); + }, + + testIsEmpty: function() { + let r = new Rect(0, 0, 0, 10); + ok(r.isEmpty(), "rect with nonpositive width is empty"); + let r = new Rect(0, 0, 10, 0); + ok(r.isEmpty(), "rect with nonpositive height is empty"); + let r = new Rect(0, 0, 10, 10); + ok(!r.isEmpty(), "rect with positive dimensions is not empty"); + }, + + testRestrictTo: function() { + let r1 = new Rect(10, 10, 100, 100); + let r2 = new Rect(50, 50, 100, 100); + r1.restrictTo(r2); + ok(r1.equals(new Rect(50, 50, 60, 60)), "intersection is non-empty"); + + let r1 = new Rect(10, 10, 100, 100); + let r2 = new Rect(120, 120, 100, 100); + r1.restrictTo(r2); + ok(r1.isEmpty(), "intersection is empty"); + + let r1 = new Rect(10, 10, 100, 100); + let r2 = new Rect(0, 0, 0, 0); + r1.restrictTo(r2); + ok(r1.isEmpty(), "intersection of rect and empty is empty"); + + let r1 = new Rect(0, 0, 0, 0); + let r2 = new Rect(0, 0, 0, 0); + r1.restrictTo(r2); + ok(r1.isEmpty(), "intersection of empty and empty is empty"); + }, + + testExpandToContain: function() { + let r1 = new Rect(10, 10, 100, 100); + let r2 = new Rect(50, 50, 100, 100); + r1.expandToContain(r2); + ok(r1.equals(new Rect(10, 10, 140, 140)), "correct expandToContain on intersecting rectangles"); + + let r1 = new Rect(10, 10, 100, 100); + let r2 = new Rect(120, 120, 100, 100); + r1.expandToContain(r2); + ok(r1.equals(new Rect(10, 10, 210, 210)), "correct expandToContain on non-intersecting rectangles"); + + let r1 = new Rect(10, 10, 100, 100); + let r2 = new Rect(0, 0, 0, 0); + r1.expandToContain(r2); + ok(r1.equals(new Rect(10, 10, 100, 100)), "expandToContain of rect and empty is rect"); + + let r1 = new Rect(10, 10, 0, 0); + let r2 = new Rect(0, 0, 0, 0); + r1.expandToContain(r2); + ok(r1.isEmpty(), "expandToContain of empty and empty is empty"); + }, + + testSubtract: function testSubtract() { + function equals(rects1, rects2) { + return rects1.length == rects2.length && rects1.every(function(r, i) { + return r.equals(rects2[i]); + }); + } + + let r1 = new Rect(0, 0, 100, 100); + let r2 = new Rect(500, 500, 100, 100); + ok(equals(r1.subtract(r2), [r1]), "subtract area outside of region yields same region"); + + let r1 = new Rect(0, 0, 100, 100); + let r2 = new Rect(-10, -10, 50, 120); + ok(equals(r1.subtract(r2), [new Rect(40, 0, 60, 100)]), "subtracting vertical bar from edge leaves one rect"); + + let r1 = new Rect(0, 0, 100, 100); + let r2 = new Rect(-10, -10, 120, 50); + ok(equals(r1.subtract(r2), [new Rect(0, 40, 100, 60)]), "subtracting horizontal bar from edge leaves one rect"); + + let r1 = new Rect(0, 0, 100, 100); + let r2 = new Rect(40, 40, 20, 20); + ok(equals(r1.subtract(r2), [ + new Rect(0, 0, 40, 100), + new Rect(40, 0, 20, 40), + new Rect(40, 60, 20, 40), + new Rect(60, 0, 40, 100)]), + "subtracting rect in middle leaves union of rects"); + }, +}; diff --git a/mobile/chrome/tests/browser_rememberPassword.js b/mobile/chrome/tests/browser_rememberPassword.js new file mode 100644 index 000000000000..584a04cbc9fd --- /dev/null +++ b/mobile/chrome/tests/browser_rememberPassword.js @@ -0,0 +1,59 @@ +var testURL_01 = chromeRoot + "browser_blank_01.html"; + +// Tests for the Remember Password UI + +let gCurrentTab = null; +function test() { + waitForExplicitFinish(); + + gCurrentTab = Browser.addTab(testURL_01, true); + messageManager.addMessageListener("pageshow", function() { + if (gCurrentTab.browser.currentURI.spec == testURL_01) { + messageManager.removeMessageListener("pageshow", arguments.callee); + pageLoaded(); + } + }); +} + +function pageLoaded() { + let iHandler = getIdentityHandler(); + let iPassword = document.getElementById("pageaction-password"); + let lm = getLoginManager(); + let host = gCurrentTab.browser.currentURI.prePath; + let nullSubmit = createLogin(host, host, null); + let nullForm = createLogin(host, null, "realm"); + + lm.removeAllLogins(); + + iHandler.show(); + is(iPassword.hidden, true, "Remember password hidden for no logins"); + iHandler.hide(); + + lm.addLogin(nullSubmit); + iHandler.show(); + is(iPassword.hidden, false, "Remember password shown for form logins"); + iPassword.click(); + is(iPassword.hidden, true, "Remember password hidden after click"); + is(lm.countLogins(host, "", null), 0, "Logins deleted when clicked"); + iHandler.hide(); + + lm.addLogin(nullForm); + iHandler.show(); + is(iPassword.hidden, false, "Remember password shown for protocol logins"); + iPassword.click(); + is(iPassword.hidden, true, "Remember password hidden after click"); + is(lm.countLogins(host, null, ""), 0, "Logins deleted when clicked"); + iHandler.hide(); + + Browser.closeTab(gCurrentTab); + finish(); +} + +function getLoginManager() { + return Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager); +} + +function createLogin(aHostname, aFormSubmitURL, aRealm) { + let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init"); + return new nsLoginInfo(aHostname, aFormSubmitURL, aRealm, "username", "password", "uname", "pword"); +} diff --git a/mobile/chrome/tests/browser_scrollbar.js b/mobile/chrome/tests/browser_scrollbar.js new file mode 100644 index 000000000000..3efbb7d9a7e8 --- /dev/null +++ b/mobile/chrome/tests/browser_scrollbar.js @@ -0,0 +1,134 @@ +let baseURI = "http://mochi.test:8888/browser/mobile/chrome/"; +let testURL_01 = baseURI + "browser_scrollbar.sjs?"; + +let gCurrentTest = null; +let gTests = []; +let gOpenedTabs = []; // for cleanup + +//------------------------------------------------------------------------------ +// Iterating tests by shifting test out one by one as runNextTest is called. +function runNextTest() { + // Run the next test until all tests completed + if (gTests.length > 0) { + gCurrentTest = gTests.shift(); + info(gCurrentTest.desc); + gCurrentTest.run(); + } + else { + // Close the awesome panel just in case + BrowserUI.activePanel = null; + finish(); + } +} + +function waitForPageShow(aPageURL, aCallback) { + messageManager.addMessageListener("pageshow", function(aMessage) { + if (aMessage.target.currentURI.spec == aPageURL) { + messageManager.removeMessageListener("pageshow", arguments.callee); + + setTimeout(aCallback, 0); + } + }); +}; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // This test is async + waitForExplicitFinish(); + runNextTest(); +} + +let horizontalScrollbar = document.getElementById("horizontal-scroller"); +let verticalScrollbar = document.getElementById("vertical-scroller"); + +function checkScrollbars(aHorizontalVisible, aVerticalVisible, aHorizontalPosition, aVerticalPosition) { + let browser = getBrowser(); + let width = browser.getBoundingClientRect().width; + let height = browser.getBoundingClientRect().height; + EventUtils.synthesizeMouse(browser, width / 2, height / 4, { type: "mousedown" }); + EventUtils.synthesizeMouse(browser, width / 2, height * 3 / 4, { type: "mousemove" }); + + let horizontalVisible = horizontalScrollbar.hasAttribute("panning"), + verticalVisible = verticalScrollbar.hasAttribute("panning"); + is(horizontalVisible, aHorizontalVisible, "The horizontal scrollbar should be " + (aHorizontalVisible ? "visible" : "hidden")); + is(verticalVisible, aVerticalVisible, "The vertical scrollbar should be " + (aVerticalVisible ? "visible" : "hidden")); + + EventUtils.synthesizeMouse(browser, width / 2, height * 3 / 4, { type: "mouseup" }); +} + +gTests.push({ + desc: "Testing visibility of scrollbars", + + run: function() { + waitForPageShow(testURL_01 + "blank", gCurrentTest.checkNotScrollable); + gOpenedTabs.push(Browser.addTab(testURL_01 + "blank", true)); + }, + + checkNotScrollable: function() { + checkScrollbars(false, false); + + waitForPageShow(testURL_01 + "horizontal", gCurrentTest.checkHorizontalScrollable); + gOpenedTabs.push(Browser.addTab(testURL_01 + "horizontal", true)); + }, + + checkHorizontalScrollable: function() { + checkScrollbars(true, true); + // TODO: current code forces the height to grow so we always have visible document when + // zooming out to see the wide document + //checkScrollbars(true, false); + todo(false, "Don't cause the height to grow beyond the window height if it doesn't need to"); + + waitForPageShow(testURL_01 + "vertical", gCurrentTest.checkVerticalScrollable); + gOpenedTabs.push(Browser.addTab(testURL_01 + "vertical", true)); + }, + + checkVerticalScrollable: function() { + checkScrollbars(false, true); + + waitForPageShow(testURL_01 + "both", gCurrentTest.checkBothScrollable); + gOpenedTabs.push(Browser.addTab(testURL_01 + "both", true)); + }, + + checkBothScrollable: function() { + checkScrollbars(true, true); + Elements.browsers.addEventListener("PanFinished", function(aEvent) { + Elements.browsers.removeEventListener("PanFinished", arguments.callee, false); + setTimeout(function() { + Browser.hideSidebars(); + }, 0); + runNextTest(); + }, false); + } +}); + + +gTests.push({ + desc: "Check scrollbar visibility when the touch sequence is cancelled", + + run: function() { + waitForPageShow(testURL_01 + "both", gCurrentTest.checkVisibility); + gOpenedTabs.push(Browser.addTab(testURL_01 + "both", true)); + }, + + checkVisibility: function() { + let browser = getBrowser(); + let width = browser.getBoundingClientRect().width; + let height = browser.getBoundingClientRect().height; + EventUtils.synthesizeMouse(browser, width / 2, height / 4, { type: "mousedown" }); + EventUtils.synthesizeMouse(browser, width / 2, height * 3 / 4, { type: "mousemove" }); + + let event = document.createEvent("Events"); + event.initEvent("CancelTouchSequence", true, false); + document.dispatchEvent(event); + + let horizontalVisible = horizontalScrollbar.hasAttribute("panning"), + verticalVisible = verticalScrollbar.hasAttribute("panning"); + is(horizontalVisible, false, "The horizontal scrollbar should be hidden when a canceltouchsequence is fired"); + is(verticalVisible, false, "The vertical scrollbar should be hidden should be hidden when a canceltouchsequence is called"); + + for (let iTab=0; iTab"); + response.write(""); + + let body = "" + query + ""; + switch (query) { + case "blank": + response.write("This is a not a scrollable page"); + break; + case "horizontal": + response.write("This is a horizontally scrollable page"); + body += "\n
Browser scrollbar test
"; + break; + case "vertical": + response.write("This is a vertically scrollable page"); + body += "\n
Browser scrollbar test
"; + break; + case "both": + response.write("This is a scrollable page in both directions"); + body += "\n
Browser scrollbar test
"; + break; + default: + break; + } + response.write("" + body + ""); +} diff --git a/mobile/chrome/tests/browser_select.html b/mobile/chrome/tests/browser_select.html new file mode 100644 index 000000000000..70f8a7cd312d --- /dev/null +++ b/mobile/chrome/tests/browser_select.html @@ -0,0 +1,84 @@ + +Browser Blank Page 01 + + + +
+ onchange: (selectedIndex) [value] +
+ - +
+ + + +
+
+ + +
+ onchange: (selectedIndex) [value] +
+ - +
+
+ + +
+
+ +
OptGroup Combobox:
+ +
+
+ + +
+ + + diff --git a/mobile/chrome/tests/browser_select.js b/mobile/chrome/tests/browser_select.js new file mode 100644 index 000000000000..895229851008 --- /dev/null +++ b/mobile/chrome/tests/browser_select.js @@ -0,0 +1,53 @@ +let testURL = chromeRoot + "browser_select.html"; +let new_tab = null; + +//------------------------------------------------------------------------------ +// Entry point (must be named "test") +function test() { + // This test is async + waitForExplicitFinish(); + + // Add new tab to hold the