diff --git a/mozilla/xpfe/browser/resources/content/nsContextMenu.js b/mozilla/xpfe/browser/resources/content/nsContextMenu.js new file mode 100644 index 00000000000..82a08e48145 --- /dev/null +++ b/mozilla/xpfe/browser/resources/content/nsContextMenu.js @@ -0,0 +1,452 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (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/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, + * released March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All Rights + * Reserved. + * + * Contributors: + * William A. ("PowerGUI") Law + */ + +/*------------------------------ nsContextMenu --------------------------------- +| This JavaScript "class" is used to implement the browser's content-area | +| context menu. | +| | +| For usage, see references to this class in navigator.xul. | +| | +| Currently, this code is relatively useless for any other purpose. In the | +| longer term, this code will be restructured to make it more reusable. | +------------------------------------------------------------------------------*/ +function nsContextMenu( xulMenu ) { + this.target = null; + this.menu = null; + this.onImage = false; + this.onLink = false; + this.link = false; + this.inFrame = false; + this.hasBGImage = false; + + // Initialize new menu. + this.initMenu( xulMenu ); +} + +// Prototype for nsContextMenu "class." +nsContextMenu.prototype = { + // Remove all the children which we added at oncreate. + onDestroy : function () { + this.removeAllItems(); + }, + // Initialize context menu. + initMenu : function ( popup, event ) { + // Save menu. + this.menu = popup; + + // Get contextual info. + this.setTarget( document.popupNode ); + + // Populate menu from template. + var template = document.getElementById( "context-template" ); + var items = template.childNodes; + for ( var i = 0; i < items.length; i++ ) { + // Replicate item. + //var item = items.item(i).cloneNode( false ); + + // cloneNode not implemented, fake it. + var item = this.cloneNode( items.item(i) ); + + // Change id. + item.setAttribute( "id", item.getAttribute( "id" ).replace( "template-", "context-" ) ); + + // Add it to popup menu. + this.menu.appendChild( item ); + } + + // Initialize (disable/remove) menu items. + this.initItems(); + }, + initItems : function () { + this.initOpenItems(); + this.initNavigationItems(); + this.initViewItems(); + this.initMiscItems(); + this.initSaveItems(); + this.initClipboardItems(); + }, + initOpenItems : function () { + var needSep = false; + + // Remove open/edit link if not applicable. + if ( !this.onLink ) { + this.removeItem( "context-openlink" ); + this.removeItem( "context-editlink" ); + } else { + needSep = true; + } + + // Remove open frame if not applicable. + if ( !this.inFrame ) { + this.removeItem( "context-openframe" ); + } else { + needSep = true; + } + + if ( !needSep ) { + // Remove separator after open items. + this.removeItem( "context-sep-open" ); + } + }, + initNavigationItems : function () { + // Back determined by canGoBack broadcaster. + this.setItemAttrFromNode( "context-back", "disabled", "canGoBack" ); + + // Forward determined by canGoForward broadcaster. + this.setItemAttrFromNode( "context-forward", "disabled", "canGoForward" ); + + // Reload is always OK. + + // Stop determined by canStop broadcaster. + this.setItemAttrFromNode( "context-stop", "disabled", "canStop" ); + }, + initSaveItems : function () { + // Save page is always OK. + + // Save frame as depends on whether we're in a frame. + if ( !this.inFrame ) { + this.removeItem( "context-saveframe" ); + } + + // Save link depends on whether we're in a link. + if ( !this.onLink ) { + this.removeItem( "context-savelink" ); + } + + // Save background image depends on whether there is one. + if ( !this.hasBGImage ) { + this.removeItem( "context-savebgimage" ); + } + + // Save image depends on whether there is one. + if ( !this.onImage ) { + this.removeItem( "context-saveimage" ); + } + }, + initViewItems : function () { + // View source is always OK. + + // View frame source depends on whether we're in a frame. + if ( !this.inFrame ) { + this.removeItem( "context-viewframesource" ); + } + + // View Info don't work no way no how. + this.removeItem( "context-viewinfo" ); + + // View Frame Info isn't working, either. + this.removeItem( "context-viewframeinfo" ); + + // View Image depends on whether an image was clicked on. + if ( !this.onImage ) { + this.removeItem( "context-viewimage" ); + } + }, + initMiscItems : function () { + // Add bookmark always OK. + + // Send Page not working yet. + this.removeItem( "context-sendpage" ); + }, + initClipboardItems : function () { + // Select All is always OK. + + // Copy depends on whether there is selected text. + this.setItemAttr( "context-copy", "disabled", this.isNoTextSelected() ); + + // Copy link location depends on whether we're on a link. + if ( !this.onLink ) { + this.removeItem( "context-copylink" ); + } + + // Copy image location depends on whether we're on an image. + if ( !this.onImage ) { + this.removeItem( "context-copyimage" ); + } + }, + // Set various context menu attributes based on the state of the world. + setTarget : function ( node ) { + // Initialize contextual info. + this.onImage = false; + this.onLink = false; + this.inFrame = false; + this.hasBGImage = false; + + // Remember the node that was clicked. + this.target = node; + + // See if the user clicked on an image. + if ( this.target.nodeType == 1 ) { + if ( this.target.tagName.toUpperCase() == "IMG" ) { + this.onImage = true; + // Look for image map. + var mapName = this.target.getAttribute( "usemap" ); + if ( mapName ) { + // Find map. + var map = this.target.ownerDocument.getElementById( mapName.substr(1) ); + if ( map ) { + // Search child s for a match. + var areas = map.childNodes; + //XXX Client side image maps are too hard for now! + dump( "Client side image maps not supported yet, sorry!\n" ); + areas.length = 0; + for ( var i = 0; i < areas.length && !this.onLink; i++ ) { + var area = areas[i]; + if ( area.nodeType == 1 + && + area.tagName.toUpperCase() == "AREA" ) { + // Get type (rect/circle/polygon/default). + var type = area.getAttribute( "type" ); + var coords = this.parseCoords( area ); + switch ( type.toUpperCase() ) { + case "RECT": + case "RECTANGLE": + break; + case "CIRC": + case "CIRCLE": + break; + case "POLY": + case "POLYGON": + break; + case "DEFAULT": + // Default matches entire image. + this.onLink = true; + this.link = area; + break; + } + } + } + } + } + } + } + + // See if the user clicked in a frame. + if ( this.target.ownerDocument != window.content.document ) { + this.inFrame = true; + } + + // Bubble out, looking for link. + var elem = this.target; + while ( elem && !this.onLink ) { + // Test for element types of interest. + if ( elem.nodeType == 1 && + ( elem.tagName.toUpperCase() == "A" + || + elem.tagName.toUpperCase() == "AREA" ) ) { + // Clicked on a link. + this.onLink = true; + // Remember corresponding element. + this.link = elem; + } + elem = elem.parentNode; + } + }, + // Open linked-to URL in a new window. + openLink : function () { + // Determine linked-to URL. + openNewWindowWith( this.linkURL() ); + }, + // Edit linked-to URL in a new window. + editLink : function () { + BrowserEditPage( this.linkURL() ); + }, + // Open clicked-in frame in its own window. + openFrame : function () { + openNewWindowWith( this.target.ownerDocument.location.href ); + }, + // Open new "view source" window with the frame's URL. + viewFrameSource : function () { + window.openDialog( "chrome://navigator/content/", + "_blank", + "chrome,menubar,status,dialog=no", + this.target.ownerDocument.location.href, + "view-source" ); + }, + viewInfo : function () { + dump( "nsContextMenu.viewInfo not implemented yet\n" ); + }, + viewFrameInfo : function () { + dump( "nsContextMenu.viewFrameInfo not implemented yet\n" ); + }, + // Open new window with the URL of the image. + viewImage : function () { + openNewWindowWith( this.target.src ); + }, + // Save URL of clicked-on frame. + saveFrame : function () { + this.savePage( this.target.ownerDocument.location.href ); + }, + // Save URL of clicked-on link. + saveLink : function () { + this.savePage( this.linkURL() ); + }, + // Save URL of clicked-on image. + saveImage : function () { + this.savePage( this.imageURL() ); + }, + // Save URL of background image. + saveBGImage : function () { + this.savePage( this.bgImageURL() ); + }, + // Generate link URL and put it on clibboard. + copyLink : function () { + this.copyToClipboard( this.linkURL() ); + }, + // Generate image URL and put it on the clipboard. + copyImage : function () { + this.copyToClipboard( this.imageURL() ); + }, + // Utilities + // Remove one item (specified via name or the item element itself). + removeItem : function ( itemOrId ) { + var item = null; + if ( itemOrId.constructor == String ) { + // Argument specifies item id. + item = document.getElementById( itemOrId ); + } else { + // Argument is the item itself. + item = itemOrId; + } + if ( item ) { + // Change id so it doesn't interfere with this menu + // the next time it is displayed. + item.setAttribute( "id", "_dead" ); + this.menu.removeChild( item ); + } + }, + // Remove all menu items. + removeAllItems : function () { + if ( this.menu ) { + var items = this.menu.childNodes; + for ( var i = 0; i < items.length; i++ ) { + this.removeItem( items[i] ); + } + } + }, + // Set given attribute of specified context-menu item. If the + // value is null, then it removes the attribute (which works + // nicely for the disabled attribute). + setItemAttr : function ( id, attr, val ) { + var elem = document.getElementById( id ); + if ( elem ) { + if ( val == null ) { + // null indicates attr should be removed. + elem.removeAttribute( attr ); + } else { + // Set attr=val. + elem.setAttribute( attr, val ); + } + } + }, + // Set context menu attribute according to like attribute of another node + // (such as a broadcaster). + setItemAttrFromNode : function ( item_id, attr, other_id ) { + var elem = document.getElementById( other_id ); + if ( elem && elem.getAttribute( attr ) == "true" ) { + this.setItemAttr( item_id, attr, "true" ); + } else { + this.setItemAttr( item_id, attr, null ); + } + }, + // Temporary workaround for DOM api not yet implemented by XUL nodes. + cloneNode : function ( item ) { + // Create another element like the one we're cloning. + var node = document.createElement( item.tagName ); + + // Copy attributes from argument item to the new one. + var attrs = item.attributes; + for ( var i = 0; i < attrs.length; i++ ) { + var attr = attrs.item( i ); + node.setAttribute( attr.nodeName, attr.nodeValue ); + } + + // Voila! + return node; + }, + // Generate fully-qualified URL for clicked-on link. + linkURL : function () { + return this.link.href; + }, + // Generate fully-qualified URL for clicked-on image. + imageURL : function () { + return this.target.src; + }, + // Returns "true" if there's no text selected, null otherwise. + isNoTextSelected : function ( event ) { + // Not implemented so all text-selected-based options are disabled. + return "true"; + }, + // Copy link/image url to clipboard. + copyToClipboard : function ( text ) { + // Get clipboard. + var clipboard = getServiceById( "{8B5314BA-DB01-11d2-96CE-0060B0FB9956}", + "nsIClipboard" ); + // Create tranferable that will transfer the text. + var transferable = createInstanceById( "{8B5314BC-DB01-11d2-96CE-0060B0FB9956}", + "nsITransferable" ); + transferable.addDataFlavor( "text/plain" ); + // Create wrapper for text. + var data = createInstance( "component://netscape/supports-string", + "nsISupportsString" ); + data.data = text ; + transferable.setTransferData( "text/plain", data, text.length * 2 ); + // Put on clipboard. + clipboard.setData( transferable, null ); + }, + // Save specified URL in user-selected file. + savePage : function ( url ) { + // Default is to save current page. + if ( !url ) { + url = window.content.location.href; + } + // Use stream xfer component to prompt for destination and save. + var xfer = Components + .classes[ "component://netscape/appshell/component/xfer" ] + .getService( Components.interfaces.nsIStreamTransfer ); + try { + // When Necko lands, we need to receive the real nsIChannel and + // do SelectFileAndTransferLocation! + + // Use this for now... + xfer.SelectFileAndTransferLocationSpec( url, window ); + } catch( exception ) { + // Failed (or cancelled), give them another chance. + dump( "SelectFileAndTransferLocationSpec failed, rv=" + exception + "\n" ); + } + return; + }, + // Parse coords= attribute and return array. + parseCoords : function ( area ) { + return []; + }, + toString : function () { + return "contextMenu.target = " + this.target + "\n" + + "contextMenu.onImage = " + this.onImage + "\n" + + "contextMenu.onLink = " + this.onLink + "\n" + + "contextMenu.link = " + this.link + "\n" + + "contextMenu.inFrame = " + this.inFrame + "\n" + + "contextMenu.hasBGImage = " + this.hasBGImage + "\n"; + } +};