1 /*
   2  * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text.html;
  26 
  27 import java.awt.*;
  28 import java.util.*;
  29 import java.net.*;
  30 import java.io.*;
  31 import javax.swing.*;
  32 import javax.swing.text.*;
  33 import javax.swing.event.*;
  34 
  35 import sun.swing.text.html.FrameEditorPaneTag;
  36 
  37 /**
  38  * Implements a FrameView, intended to support the HTML
  39  * <FRAME> tag.  Supports the frameborder, scrolling,
  40  * marginwidth and marginheight attributes.
  41  *
  42  * @author    Sunita Mani
  43  */
  44 
  45 class FrameView extends ComponentView implements HyperlinkListener {
  46 
  47 
  48     JEditorPane htmlPane;
  49     JScrollPane scroller;
  50     boolean editable;
  51     float width;
  52     float height;
  53     URL src;
  54     /** Set to true when the component has been created. */
  55     private boolean createdComponent;
  56 
  57     /**
  58      * Creates a new Frame.
  59      *
  60      * @param elem the element to represent.
  61      */
  62     public FrameView(Element elem) {
  63         super(elem);
  64     }
  65 
  66     protected Component createComponent() {
  67 
  68         Element elem = getElement();
  69         AttributeSet attributes = elem.getAttributes();
  70         String srcAtt = (String)attributes.getAttribute(HTML.Attribute.SRC);
  71 
  72         if ((srcAtt != null) && (!srcAtt.equals(""))) {
  73             try {
  74                 URL base = ((HTMLDocument)elem.getDocument()).getBase();
  75                 src = new URL(base, srcAtt);
  76                 htmlPane = new FrameEditorPane();
  77                 htmlPane.addHyperlinkListener(this);
  78                 JEditorPane host = getHostPane();
  79                 boolean isAutoFormSubmission = true;
  80                 if (host != null) {
  81                     htmlPane.setEditable(host.isEditable());
  82                     String charset = (String) host.getClientProperty("charset");
  83                     if (charset != null) {
  84                         htmlPane.putClientProperty("charset", charset);
  85                     }
  86                     HTMLEditorKit hostKit = (HTMLEditorKit)host.getEditorKit();
  87                     if (hostKit != null) {
  88                         isAutoFormSubmission = hostKit.isAutoFormSubmission();
  89                     }
  90                 }
  91                 htmlPane.setPage(src);
  92                 HTMLEditorKit kit = (HTMLEditorKit)htmlPane.getEditorKit();
  93                 if (kit != null) {
  94                     kit.setAutoFormSubmission(isAutoFormSubmission);
  95                 }
  96 
  97                 Document doc = htmlPane.getDocument();
  98                 if (doc instanceof HTMLDocument) {
  99                     ((HTMLDocument)doc).setFrameDocumentState(true);
 100                 }
 101                 setMargin();
 102                 createScrollPane();
 103                 setBorder();
 104             } catch (MalformedURLException e) {
 105                 e.printStackTrace();
 106             } catch (IOException e1) {
 107                 e1.printStackTrace();
 108             }
 109         }
 110         createdComponent = true;
 111         return scroller;
 112     }
 113 
 114     JEditorPane getHostPane() {
 115         Container c = getContainer();
 116         while ((c != null) && ! (c instanceof JEditorPane)) {
 117             c = c.getParent();
 118         }
 119         return (JEditorPane) c;
 120     }
 121 
 122 
 123     /**
 124      * Sets the parent view for the FrameView.
 125      * Also determines if the FrameView should be editable
 126      * or not based on whether the JTextComponent that
 127      * contains it is editable.
 128      *
 129      * @param parent View
 130      */
 131     public void setParent(View parent) {
 132         if (parent != null) {
 133             JTextComponent t = (JTextComponent)parent.getContainer();
 134             editable = t.isEditable();
 135         }
 136         super.setParent(parent);
 137     }
 138 
 139 
 140     /**
 141      * Also determines if the FrameView should be editable
 142      * or not based on whether the JTextComponent that
 143      * contains it is editable. And then proceeds to call
 144      * the superclass to do the paint().
 145      *
 146      * @param parent View
 147      * @see text.ComponentView#paint
 148      */
 149     public void paint(Graphics g, Shape allocation) {
 150 
 151         Container host = getContainer();
 152         if (host != null && htmlPane != null &&
 153             htmlPane.isEditable() != ((JTextComponent)host).isEditable()) {
 154             editable = ((JTextComponent)host).isEditable();
 155             htmlPane.setEditable(editable);
 156         }
 157         super.paint(g, allocation);
 158     }
 159 
 160 
 161     /**
 162      * If the marginwidth or marginheight attributes have been specified,
 163      * then the JEditorPane's margin's are set to the new values.
 164      */
 165     private void setMargin() {
 166         int margin = 0;
 167         Insets in = htmlPane.getMargin();
 168         Insets newInsets;
 169         boolean modified = false;
 170         AttributeSet attributes = getElement().getAttributes();
 171         String marginStr = (String)attributes.getAttribute(HTML.Attribute.MARGINWIDTH);
 172         if ( in != null) {
 173             newInsets = new Insets(in.top, in.left, in.right, in.bottom);
 174         } else {
 175             newInsets = new Insets(0,0,0,0);
 176         }
 177         if (marginStr != null) {
 178             margin = Integer.parseInt(marginStr);
 179             if (margin > 0) {
 180                 newInsets.left = margin;
 181                 newInsets.right = margin;
 182                 modified = true;
 183             }
 184         }
 185         marginStr = (String)attributes.getAttribute(HTML.Attribute.MARGINHEIGHT);
 186         if (marginStr != null) {
 187             margin = Integer.parseInt(marginStr);
 188             if (margin > 0) {
 189                 newInsets.top = margin;
 190                 newInsets.bottom = margin;
 191                 modified = true;
 192             }
 193         }
 194         if (modified) {
 195             htmlPane.setMargin(newInsets);
 196         }
 197     }
 198 
 199     /**
 200      * If the frameborder attribute has been specified, either in the frame,
 201      * or by the frames enclosing frameset, the JScrollPane's setBorder()
 202      * method is invoked to achieve the desired look.
 203      */
 204     private void setBorder() {
 205 
 206         AttributeSet attributes = getElement().getAttributes();
 207         String frameBorder = (String)attributes.getAttribute(HTML.Attribute.FRAMEBORDER);
 208         if ((frameBorder != null) &&
 209             (frameBorder.equals("no") || frameBorder.equals("0"))) {
 210             // make invisible borders.
 211             scroller.setBorder(null);
 212         }
 213     }
 214 
 215 
 216     /**
 217      * This method creates the JScrollPane.  The scrollbar policy is determined by
 218      * the scrolling attribute.  If not defined, the default is "auto" which
 219      * maps to the scrollbar's being displayed as needed.
 220      */
 221     @SuppressWarnings("deprecation")
 222     private void createScrollPane() {
 223         AttributeSet attributes = getElement().getAttributes();
 224         String scrolling = (String)attributes.getAttribute(HTML.Attribute.SCROLLING);
 225         if (scrolling == null) {
 226             scrolling = "auto";
 227         }
 228 
 229         if (!scrolling.equals("no")) {
 230             if (scrolling.equals("yes")) {
 231                 scroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
 232                                            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
 233             } else {
 234                 // scrollbars will be displayed if needed
 235                 //
 236                 scroller = new JScrollPane();
 237             }
 238         } else {
 239             scroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_NEVER,
 240                                        JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 241         }
 242 
 243         JViewport vp = scroller.getViewport();
 244         vp.add(htmlPane);
 245         vp.setBackingStoreEnabled(true);
 246         scroller.setMinimumSize(new Dimension(5,5));
 247         scroller.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
 248     }
 249 
 250 
 251     /**
 252      * Finds the outermost FrameSetView.  It then
 253      * returns that FrameSetView's container.
 254      */
 255     JEditorPane getOutermostJEditorPane() {
 256 
 257         View parent = getParent();
 258         FrameSetView frameSetView = null;
 259         while (parent != null) {
 260             if (parent instanceof FrameSetView) {
 261                 frameSetView = (FrameSetView)parent;
 262             }
 263             parent = parent.getParent();
 264         }
 265         if (frameSetView != null) {
 266             return (JEditorPane)frameSetView.getContainer();
 267         }
 268         return null;
 269     }
 270 
 271 
 272     /**
 273      * Returns true if this frame is contained within
 274      * a nested frameset.
 275      */
 276     private boolean inNestedFrameSet() {
 277         FrameSetView parent = (FrameSetView)getParent();
 278         return (parent.getParent() instanceof FrameSetView);
 279     }
 280 
 281 
 282     /**
 283      * Notification of a change relative to a
 284      * hyperlink. This method searches for the outermost
 285      * JEditorPane, and then fires an HTMLFrameHyperlinkEvent
 286      * to that frame.  In addition, if the target is _parent,
 287      * and there is not nested framesets then the target is
 288      * reset to _top.  If the target is _top, in addition to
 289      * firing the event to the outermost JEditorPane, this
 290      * method also invokes the setPage() method and explicitly
 291      * replaces the current document with the destination url.
 292      *
 293      * @param HyperlinkEvent
 294      */
 295     public void hyperlinkUpdate(HyperlinkEvent evt) {
 296 
 297         JEditorPane c = getOutermostJEditorPane();
 298         if (c == null) {
 299             return;
 300         }
 301 
 302         if (!(evt instanceof HTMLFrameHyperlinkEvent)) {
 303             c.fireHyperlinkUpdate(evt);
 304             return;
 305         }
 306 
 307         HTMLFrameHyperlinkEvent e = (HTMLFrameHyperlinkEvent)evt;
 308 
 309         if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
 310             String target = e.getTarget();
 311             String postTarget = target;
 312 
 313             if (target.equals("_parent") && !inNestedFrameSet()){
 314                 target = "_top";
 315             }
 316 
 317             if (evt instanceof FormSubmitEvent) {
 318                 HTMLEditorKit kit = (HTMLEditorKit)c.getEditorKit();
 319                 if (kit != null && kit.isAutoFormSubmission()) {
 320                     if (target.equals("_top")) {
 321                         try {
 322                             movePostData(c, postTarget);
 323                             c.setPage(e.getURL());
 324                         } catch (IOException ex) {
 325                             // Need a way to handle exceptions
 326                         }
 327                     } else {
 328                         HTMLDocument doc = (HTMLDocument)c.getDocument();
 329                         doc.processHTMLFrameHyperlinkEvent(e);
 330                     }
 331                 } else {
 332                     c.fireHyperlinkUpdate(evt);
 333                 }
 334                 return;
 335             }
 336 
 337             if (target.equals("_top")) {
 338                 try {
 339                     c.setPage(e.getURL());
 340                 } catch (IOException ex) {
 341                     // Need a way to handle exceptions
 342                     // ex.printStackTrace();
 343                 }
 344             }
 345             if (!c.isEditable()) {
 346                 c.fireHyperlinkUpdate(new HTMLFrameHyperlinkEvent(c,
 347                                                                   e.getEventType(),
 348                                                                   e.getURL(),
 349                                                                   e.getDescription(),
 350                                                                   getElement(),
 351                                                                   e.getInputEvent(),
 352                                                                   target));
 353             }
 354         }
 355     }
 356 
 357     /**
 358      * Gives notification from the document that attributes were changed
 359      * in a location that this view is responsible for.  Currently this view
 360      * handles changes to its SRC attribute.
 361      *
 362      * @param e the change information from the associated document
 363      * @param a the current allocation of the view
 364      * @param f the factory to use to rebuild if the view has children
 365      *
 366      */
 367     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
 368 
 369         Element elem = getElement();
 370         AttributeSet attributes = elem.getAttributes();
 371 
 372         URL oldPage = src;
 373 
 374         String srcAtt = (String)attributes.getAttribute(HTML.Attribute.SRC);
 375         URL base = ((HTMLDocument)elem.getDocument()).getBase();
 376         try {
 377             if (!createdComponent) {
 378                 return;
 379             }
 380 
 381             Object postData = movePostData(htmlPane, null);
 382             src = new URL(base, srcAtt);
 383             if (oldPage.equals(src) && (src.getRef() == null) && (postData == null)) {
 384                 return;
 385             }
 386 
 387             htmlPane.setPage(src);
 388             Document newDoc = htmlPane.getDocument();
 389             if (newDoc instanceof HTMLDocument) {
 390                 ((HTMLDocument)newDoc).setFrameDocumentState(true);
 391             }
 392         } catch (MalformedURLException e1) {
 393             // Need a way to handle exceptions
 394             //e1.printStackTrace();
 395         } catch (IOException e2) {
 396             // Need a way to handle exceptions
 397             //e2.printStackTrace();
 398         }
 399     }
 400 
 401     /**
 402      * Move POST data from temporary storage into the target document property.
 403      *
 404      * @return the POST data or null if no data found
 405      */
 406     private Object movePostData(JEditorPane targetPane, String frameName) {
 407         Object postData = null;
 408         JEditorPane p = getOutermostJEditorPane();
 409         if (p != null) {
 410             if (frameName == null) {
 411                 frameName = (String) getElement().getAttributes().getAttribute(
 412                         HTML.Attribute.NAME);
 413             }
 414             if (frameName != null) {
 415                 String propName = FormView.PostDataProperty + "." + frameName;
 416                 Document d = p.getDocument();
 417                 postData = d.getProperty(propName);
 418                 if (postData != null) {
 419                     targetPane.getDocument().putProperty(
 420                             FormView.PostDataProperty, postData);
 421                     d.putProperty(propName, null);
 422                 }
 423             }
 424         }
 425 
 426         return postData;
 427     }
 428 
 429     /**
 430      * Determines the minimum span for this view along an
 431      * axis.
 432      *
 433      * @param axis may be either <code>View.X_AXIS</code> or
 434      *  <code>View.Y_AXIS</code>
 435      * @return the preferred span; given that we do not
 436      * support resizing of frames, the minimum span returned
 437      * is the same as the preferred span
 438      *
 439      */
 440     public float getMinimumSpan(int axis) {
 441       return 5;
 442     }
 443 
 444     /**
 445      * Determines the maximum span for this view along an
 446      * axis.
 447      *
 448      * @param axis may be either <code>View.X_AXIS</code> or
 449      *  <code>View.Y_AXIS</code>
 450      * @return the preferred span; given that we do not
 451      * support resizing of frames, the maximum span returned
 452      * is the same as the preferred span
 453      *
 454      */
 455     public float getMaximumSpan(int axis) {
 456         return Integer.MAX_VALUE;
 457     }
 458 
 459     /** Editor pane rendering frame of HTML document
 460      *  It uses the same editor kits classes as outermost JEditorPane
 461      */
 462     @SuppressWarnings("serial") // Superclass is not serializable across versions
 463     class FrameEditorPane extends JEditorPane implements FrameEditorPaneTag {
 464         public EditorKit getEditorKitForContentType(String type) {
 465             EditorKit editorKit = super.getEditorKitForContentType(type);
 466             JEditorPane outerMostJEditorPane = null;
 467             if ((outerMostJEditorPane = getOutermostJEditorPane()) != null) {
 468                 EditorKit inheritedEditorKit = outerMostJEditorPane.getEditorKitForContentType(type);
 469                 if (! editorKit.getClass().equals(inheritedEditorKit.getClass())) {
 470                     editorKit = (EditorKit) inheritedEditorKit.clone();
 471                     setEditorKitForContentType(type, editorKit);
 472                 }
 473             }
 474             return editorKit;
 475         }
 476 
 477         FrameView getFrameView() {
 478             return FrameView.this;
 479         }
 480     }
 481 }