mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-11-24 15:29:45 +03:00
b4bf069099
svn path=/trunk/netsurf/; revision=13375
2687 lines
67 KiB
C
2687 lines
67 KiB
C
/*
|
|
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
|
|
* Copyright 2006 James Bursa <bursa@users.sourceforge.net>
|
|
* Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk>
|
|
* Copyright 2004 John Tytgat <joty@netsurf-browser.org>
|
|
* Copyright 2006 Richard Wilson <info@tinct.net>
|
|
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
|
|
* Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
|
|
*
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
*
|
|
* NetSurf 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; version 2 of the License.
|
|
*
|
|
* NetSurf 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** \file
|
|
* Browser window creation and manipulation (implementation).
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <math.h>
|
|
|
|
#include "curl/curl.h"
|
|
#include "utils/config.h"
|
|
#include "content/content.h"
|
|
#include "content/fetch.h"
|
|
#include "content/hlcache.h"
|
|
#include "content/urldb.h"
|
|
#include "desktop/401login.h"
|
|
#include "desktop/browser.h"
|
|
#include "desktop/download.h"
|
|
#include "desktop/frames.h"
|
|
#include "desktop/history_core.h"
|
|
#include "desktop/hotlist.h"
|
|
#include "desktop/gui.h"
|
|
#include "desktop/knockout.h"
|
|
#include "desktop/options.h"
|
|
#include "desktop/scrollbar.h"
|
|
#include "desktop/selection.h"
|
|
#include "desktop/textinput.h"
|
|
#include "desktop/plotters.h"
|
|
#include "desktop/js.h"
|
|
|
|
#include "render/form.h"
|
|
#include "render/html.h"
|
|
#include "render/textplain.h"
|
|
#include "utils/log.h"
|
|
#include "utils/messages.h"
|
|
#include "utils/nsurl.h"
|
|
#include "utils/schedule.h"
|
|
#include "utils/url.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/utf8.h"
|
|
|
|
/** one or more windows require a reformat */
|
|
bool browser_reformat_pending;
|
|
|
|
/** maximum frame depth */
|
|
#define FRAME_DEPTH 8
|
|
|
|
static nserror browser_window_callback(hlcache_handle *c,
|
|
const hlcache_event *event, void *pw);
|
|
static void browser_window_refresh(void *p);
|
|
static bool browser_window_check_throbber(struct browser_window *bw);
|
|
static void browser_window_convert_to_download(struct browser_window *bw,
|
|
llcache_handle *stream);
|
|
static void browser_window_start_throbber(struct browser_window *bw);
|
|
static void browser_window_stop_throbber(struct browser_window *bw);
|
|
static void browser_window_destroy_children(struct browser_window *bw);
|
|
static void browser_window_destroy_internal(struct browser_window *bw);
|
|
static void browser_window_set_scale_internal(struct browser_window *bw,
|
|
float scale);
|
|
static void browser_window_find_target_internal(struct browser_window *bw,
|
|
const char *target, int depth, struct browser_window *page,
|
|
int *rdepth, struct browser_window **bw_target);
|
|
static void browser_window_mouse_drag_end(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y);
|
|
|
|
|
|
/**
|
|
* Get position of scrollbar widget within browser window.
|
|
*
|
|
* \param bw The browser window
|
|
* \param horizontal Whether to get position of horizontal scrollbar
|
|
* \param x Updated to x-coord of top left of scrollbar widget
|
|
* \param y Updated to y-coord of top left of scrollbar widget
|
|
*/
|
|
|
|
static inline void browser_window_get_scrollbar_pos(struct browser_window *bw,
|
|
bool horizontal, int *x, int *y)
|
|
{
|
|
if (horizontal) {
|
|
*x = 0;
|
|
*y = bw->height - SCROLLBAR_WIDTH;
|
|
} else {
|
|
*x = bw->width - SCROLLBAR_WIDTH;
|
|
*y = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get browser window scrollbar widget length
|
|
*
|
|
* \param bw The browser window
|
|
* \param horizontal Whether to get length of horizontal scrollbar
|
|
* \return the scrollbar's length
|
|
*/
|
|
|
|
static inline int browser_window_get_scrollbar_len(struct browser_window *bw,
|
|
bool horizontal)
|
|
{
|
|
if (horizontal)
|
|
return bw->width - (bw->scroll_y != NULL ? SCROLLBAR_WIDTH : 0);
|
|
else
|
|
return bw->height;
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
bool browser_window_redraw(struct browser_window *bw, int x, int y,
|
|
const struct rect *clip, const struct redraw_context *ctx)
|
|
{
|
|
struct redraw_context new_ctx = *ctx;
|
|
int width = 0;
|
|
int height = 0;
|
|
bool plot_ok = true;
|
|
content_type content_type;
|
|
struct content_redraw_data data;
|
|
struct rect content_clip;
|
|
|
|
if (bw == NULL) {
|
|
LOG(("NULL browser window"));
|
|
return false;
|
|
}
|
|
|
|
if (bw->current_content == NULL && bw->children == NULL) {
|
|
/* Browser window has no content, render blank fill */
|
|
ctx->plot->clip(clip);
|
|
return ctx->plot->rectangle(clip->x0, clip->y0,
|
|
clip->x1, clip->y1,
|
|
plot_style_fill_white);
|
|
}
|
|
|
|
/* Browser window has content OR children (frames) */
|
|
|
|
if (bw->window != NULL && ctx->plot->option_knockout) {
|
|
/* Root browser window: start knockout */
|
|
knockout_plot_start(ctx, &new_ctx);
|
|
}
|
|
|
|
new_ctx.plot->clip(clip);
|
|
|
|
/* Handle redraw of any browser window children */
|
|
if (bw->children) {
|
|
struct browser_window *child;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
if (bw->window != NULL)
|
|
/* Root browser window; start with blank fill */
|
|
plot_ok &= new_ctx.plot->rectangle(clip->x0, clip->y0,
|
|
clip->x1, clip->y1,
|
|
plot_style_fill_white);
|
|
|
|
/* Loop through all children of bw */
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
/* Set current child */
|
|
child = &bw->children[cur_child];
|
|
|
|
/* Get frame edge box in global coordinates */
|
|
content_clip.x0 = (x + child->x) * child->scale;
|
|
content_clip.y0 = (y + child->y) * child->scale;
|
|
content_clip.x1 = content_clip.x0 +
|
|
child->width * child->scale;
|
|
content_clip.y1 = content_clip.y0 +
|
|
child->height * child->scale;
|
|
|
|
/* Intersect it with clip rectangle */
|
|
if (content_clip.x0 < clip->x0)
|
|
content_clip.x0 = clip->x0;
|
|
if (content_clip.y0 < clip->y0)
|
|
content_clip.y0 = clip->y0;
|
|
if (clip->x1 < content_clip.x1)
|
|
content_clip.x1 = clip->x1;
|
|
if (clip->y1 < content_clip.y1)
|
|
content_clip.y1 = clip->y1;
|
|
|
|
/* Skip this frame if it lies outside clip rectangle */
|
|
if (content_clip.x0 >= content_clip.x1 ||
|
|
content_clip.y0 >= content_clip.y1)
|
|
continue;
|
|
|
|
/* Redraw frame */
|
|
plot_ok &= browser_window_redraw(child,
|
|
x + child->x, y + child->y,
|
|
&content_clip, &new_ctx);
|
|
}
|
|
|
|
/* Nothing else to redraw for browser windows with children;
|
|
* cleanup and return */
|
|
if (bw->window != NULL && ctx->plot->option_knockout) {
|
|
/* Root browser window: knockout end */
|
|
knockout_plot_end();
|
|
}
|
|
|
|
return plot_ok;
|
|
}
|
|
|
|
/* Handle browser windows with content to redraw */
|
|
|
|
content_type = content_get_type(bw->current_content);
|
|
if (content_type != CONTENT_HTML && content_type != CONTENT_TEXTPLAIN) {
|
|
/* Set render area according to scale */
|
|
width = content_get_width(bw->current_content) * bw->scale;
|
|
height = content_get_height(bw->current_content) * bw->scale;
|
|
|
|
/* Non-HTML may not fill viewport to extents, so plot white
|
|
* background fill */
|
|
plot_ok &= new_ctx.plot->rectangle(clip->x0, clip->y0,
|
|
clip->x1, clip->y1, plot_style_fill_white);
|
|
}
|
|
|
|
/* Set up content redraw data */
|
|
data.x = x - scrollbar_get_offset(bw->scroll_x);
|
|
data.y = y - scrollbar_get_offset(bw->scroll_y);
|
|
data.width = width;
|
|
data.height = height;
|
|
|
|
data.background_colour = 0xFFFFFF;
|
|
data.scale = bw->scale;
|
|
data.repeat_x = false;
|
|
data.repeat_y = false;
|
|
|
|
content_clip = *clip;
|
|
|
|
if (!bw->window) {
|
|
int x0 = x * bw->scale;
|
|
int y0 = y * bw->scale;
|
|
int x1 = (x + bw->width - ((bw->scroll_y != NULL) ?
|
|
SCROLLBAR_WIDTH : 0)) * bw->scale;
|
|
int y1 = (y + bw->height - ((bw->scroll_x != NULL) ?
|
|
SCROLLBAR_WIDTH : 0)) * bw->scale;
|
|
|
|
if (content_clip.x0 < x0) content_clip.x0 = x0;
|
|
if (content_clip.y0 < y0) content_clip.y0 = y0;
|
|
if (x1 < content_clip.x1) content_clip.x1 = x1;
|
|
if (y1 < content_clip.y1) content_clip.y1 = y1;
|
|
}
|
|
|
|
/* Render the content */
|
|
plot_ok &= content_redraw(bw->current_content, &data,
|
|
&content_clip, &new_ctx);
|
|
|
|
/* Back to full clip rect */
|
|
new_ctx.plot->clip(clip);
|
|
|
|
if (!bw->window) {
|
|
/* Render scrollbars */
|
|
int off_x, off_y;
|
|
if (bw->scroll_x != NULL) {
|
|
browser_window_get_scrollbar_pos(bw, true,
|
|
&off_x, &off_y);
|
|
plot_ok &= scrollbar_redraw(bw->scroll_x,
|
|
x + off_x, y + off_y, clip,
|
|
bw->scale, &new_ctx);
|
|
}
|
|
if (bw->scroll_y != NULL) {
|
|
browser_window_get_scrollbar_pos(bw, false,
|
|
&off_x, &off_y);
|
|
plot_ok &= scrollbar_redraw(bw->scroll_y,
|
|
x + off_x, y + off_y, clip,
|
|
bw->scale, &new_ctx);
|
|
}
|
|
}
|
|
|
|
if (bw->window != NULL && ctx->plot->option_knockout) {
|
|
/* Root browser window: end knockout */
|
|
knockout_plot_end();
|
|
}
|
|
|
|
return plot_ok;
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
bool browser_window_redraw_ready(struct browser_window *bw)
|
|
{
|
|
if (bw == NULL) {
|
|
LOG(("NULL browser window"));
|
|
return false;
|
|
} else if (bw->current_content != NULL) {
|
|
/* Can't render locked contents */
|
|
return !content_is_locked(bw->current_content);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
void browser_window_update_extent(struct browser_window *bw)
|
|
{
|
|
if (bw->window != NULL)
|
|
/* Front end window */
|
|
gui_window_update_extent(bw->window);
|
|
else
|
|
/* Core-managed browser window */
|
|
browser_window_handle_scrollbars(bw);
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
void browser_window_get_position(struct browser_window *bw, bool root,
|
|
int *pos_x, int *pos_y)
|
|
{
|
|
*pos_x = 0;
|
|
*pos_y = 0;
|
|
|
|
assert(bw != NULL);
|
|
|
|
while (bw) {
|
|
switch (bw->browser_window_type) {
|
|
|
|
case BROWSER_WINDOW_FRAMESET:
|
|
*pos_x += bw->x * bw->scale;
|
|
*pos_y += bw->y * bw->scale;
|
|
break;
|
|
|
|
case BROWSER_WINDOW_NORMAL:
|
|
/* There is no offset to the root browser window */
|
|
break;
|
|
|
|
case BROWSER_WINDOW_FRAME:
|
|
/* Iframe and Frame handling is identical;
|
|
* fall though */
|
|
case BROWSER_WINDOW_IFRAME:
|
|
*pos_x += (bw->x - scrollbar_get_offset(bw->scroll_x)) *
|
|
bw->scale;
|
|
*pos_y += (bw->y - scrollbar_get_offset(bw->scroll_y)) *
|
|
bw->scale;
|
|
break;
|
|
}
|
|
|
|
bw = bw->parent;
|
|
|
|
if (!root) {
|
|
/* return if we just wanted the position in the parent
|
|
* browser window. */
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
void browser_window_set_position(struct browser_window *bw, int x, int y)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window */
|
|
bw->x = x;
|
|
bw->y = y;
|
|
} else {
|
|
LOG(("Asked to set position of front end window."));
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
void browser_window_set_drag_type(struct browser_window *bw,
|
|
browser_drag_type type)
|
|
{
|
|
struct browser_window *top_bw = browser_window_get_root(bw);
|
|
|
|
if (type == DRAGGING_NONE)
|
|
top_bw->drag_window = NULL;
|
|
else
|
|
top_bw->drag_window = bw;
|
|
|
|
bw->drag_type = type;
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
struct browser_window * browser_window_get_root(struct browser_window *bw)
|
|
{
|
|
while (bw && !bw->window && bw->parent) {
|
|
bw = bw->parent;
|
|
}
|
|
return bw;
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
bool browser_window_has_selection(struct browser_window *bw)
|
|
{
|
|
assert(bw->window);
|
|
|
|
if (bw->cur_sel != NULL && selection_defined(bw->cur_sel)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
void browser_window_set_selection(struct browser_window *bw,
|
|
struct selection *s)
|
|
{
|
|
assert(bw->window);
|
|
|
|
if (bw->cur_sel != s && bw->cur_sel != NULL) {
|
|
/* Clear any existing selection */
|
|
selection_clear(bw->cur_sel, true);
|
|
}
|
|
|
|
/* Replace current selection pointer */
|
|
if (s == NULL && bw->current_content != NULL) {
|
|
bw->cur_sel = content_get_selection(bw->current_content);
|
|
} else {
|
|
bw->cur_sel = s;
|
|
}
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
struct selection *browser_window_get_selection(struct browser_window *bw)
|
|
{
|
|
assert(bw->window);
|
|
|
|
return bw->cur_sel;
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
void browser_window_scroll_visible(struct browser_window *bw,
|
|
const struct rect *rect)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
if (bw->window != NULL) {
|
|
/* Front end window */
|
|
gui_window_scroll_visible(bw->window,
|
|
rect->x0, rect->y0, rect->x1, rect->y1);
|
|
} else {
|
|
/* Core managed browser window */
|
|
if (bw->scroll_x != NULL)
|
|
scrollbar_set(bw->scroll_x, rect->x0, false);
|
|
if (bw->scroll_y != NULL)
|
|
scrollbar_set(bw->scroll_y, rect->y0, false);
|
|
}
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
void browser_window_set_scroll(struct browser_window *bw, int x, int y)
|
|
{
|
|
if (bw->window != NULL) {
|
|
gui_window_set_scroll(bw->window, x, y);
|
|
} else {
|
|
if (bw->scroll_x != NULL)
|
|
scrollbar_set(bw->scroll_x, x, false);
|
|
if (bw->scroll_y != NULL)
|
|
scrollbar_set(bw->scroll_y, y, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal helper for browser_window_get_contextual_content
|
|
*/
|
|
static void browser_window__get_contextual_content(struct browser_window *bw,
|
|
int x, int y, struct contextual_content *data)
|
|
{
|
|
/* Handle (i)frame scroll offset (core-managed browser windows only) */
|
|
x += scrollbar_get_offset(bw->scroll_x);
|
|
y += scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children, so pass request on to
|
|
* appropriate child */
|
|
struct browser_window *bwc;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
/* Loop through all children of bw */
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
/* Set current child */
|
|
bwc = &bw->children[cur_child];
|
|
|
|
/* Skip this frame if (x, y) coord lies outside */
|
|
if (x < bwc->x || bwc->x + bwc->width < x ||
|
|
y < bwc->y || bwc->y + bwc->height < y)
|
|
continue;
|
|
|
|
/* Pass request into this child */
|
|
browser_window__get_contextual_content(bwc,
|
|
(x - bwc->x), (y - bwc->y), data);
|
|
return;
|
|
}
|
|
|
|
/* Coordinate not contained by any frame */
|
|
return;
|
|
}
|
|
|
|
if (bw->current_content == NULL)
|
|
/* No content; nothing to set */
|
|
return;
|
|
|
|
/* Pass request to content */
|
|
content_get_contextual_content(bw->current_content, x, y, data);
|
|
data->main = bw->current_content;
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
void browser_window_get_contextual_content(struct browser_window *bw,
|
|
int x, int y, struct contextual_content *data)
|
|
{
|
|
data->link_url = NULL;
|
|
data->object = NULL;
|
|
data->main = NULL;
|
|
|
|
browser_window__get_contextual_content(bw, x, y, data);
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
bool browser_window_scroll_at_point(struct browser_window *bw,
|
|
int x, int y, int scrx, int scry)
|
|
{
|
|
bool handled_scroll = false;
|
|
assert(bw != NULL);
|
|
|
|
/* Handle (i)frame scroll offset (core-managed browser windows only) */
|
|
x += scrollbar_get_offset(bw->scroll_x);
|
|
y += scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children, so pass request on to
|
|
* appropriate child */
|
|
struct browser_window *bwc;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
/* Loop through all children of bw */
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
/* Set current child */
|
|
bwc = &bw->children[cur_child];
|
|
|
|
/* Skip this frame if (x, y) coord lies outside */
|
|
if (x < bwc->x || bwc->x + bwc->width < x ||
|
|
y < bwc->y || bwc->y + bwc->height < y)
|
|
continue;
|
|
|
|
/* Pass request into this child */
|
|
return browser_window_scroll_at_point(bwc,
|
|
(x - bwc->x), (y - bwc->y),
|
|
scrx, scry);
|
|
}
|
|
}
|
|
|
|
/* Try to scroll any current content */
|
|
if (bw->current_content != NULL && content_scroll_at_point(
|
|
bw->current_content, x, y, scrx, scry) == true)
|
|
/* Scroll handled by current content */
|
|
return true;
|
|
|
|
/* Try to scroll this window, if scroll not already handled */
|
|
if (handled_scroll == false) {
|
|
if (bw->scroll_y && scrollbar_scroll(bw->scroll_y, scry))
|
|
handled_scroll = true;
|
|
|
|
if (bw->scroll_x && scrollbar_scroll(bw->scroll_x, scrx))
|
|
handled_scroll = true;
|
|
}
|
|
|
|
return handled_scroll;
|
|
}
|
|
|
|
/* exported interface, documented in browser.h */
|
|
bool browser_window_drop_file_at_point(struct browser_window *bw,
|
|
int x, int y, char *file)
|
|
{
|
|
assert(bw != NULL);
|
|
|
|
/* Handle (i)frame scroll offset (core-managed browser windows only) */
|
|
x += scrollbar_get_offset(bw->scroll_x);
|
|
y += scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children, so pass request on to
|
|
* appropriate child */
|
|
struct browser_window *bwc;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
/* Loop through all children of bw */
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
/* Set current child */
|
|
bwc = &bw->children[cur_child];
|
|
|
|
/* Skip this frame if (x, y) coord lies outside */
|
|
if (x < bwc->x || bwc->x + bwc->width < x ||
|
|
y < bwc->y || bwc->y + bwc->height < y)
|
|
continue;
|
|
|
|
/* Pass request into this child */
|
|
return browser_window_drop_file_at_point(bwc,
|
|
(x - bwc->x), (y - bwc->y),
|
|
file);
|
|
}
|
|
}
|
|
|
|
/* Pass file drop on to any content */
|
|
if (bw->current_content != NULL)
|
|
return content_drop_file_at_point(bw->current_content,
|
|
x, y, file);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create and open a new root browser window with the given page.
|
|
*
|
|
* \param url URL to start fetching in the new window (copied)
|
|
* \param clone The browser window to clone
|
|
* \param referer The referring uri (copied), or 0 if none
|
|
*/
|
|
|
|
struct browser_window *browser_window_create(const char *url,
|
|
struct browser_window *clone,
|
|
const char *referer, bool history_add, bool new_tab)
|
|
{
|
|
struct browser_window *bw;
|
|
struct browser_window *top;
|
|
|
|
assert(clone || history_add);
|
|
|
|
if ((bw = calloc(1, sizeof *bw)) == NULL) {
|
|
warn_user("NoMemory", 0);
|
|
return NULL;
|
|
}
|
|
|
|
bw->jsctx = js_newcontext();
|
|
|
|
/* Initialise common parts */
|
|
browser_window_initialise_common(bw, clone);
|
|
|
|
/* window characteristics */
|
|
bw->browser_window_type = BROWSER_WINDOW_NORMAL;
|
|
bw->scrolling = SCROLLING_YES;
|
|
bw->border = true;
|
|
bw->no_resize = true;
|
|
bw->last_action = wallclock();
|
|
bw->focus = bw;
|
|
|
|
/* gui window */
|
|
/* from the front end's pov, it clones the top level browser window,
|
|
* so find that. */
|
|
top = browser_window_get_root(clone);
|
|
|
|
bw->window = gui_create_browser_window(bw, top, new_tab);
|
|
|
|
if (bw->window == NULL) {
|
|
browser_window_destroy(bw);
|
|
return NULL;
|
|
}
|
|
|
|
if (url) {
|
|
browser_window_go(bw, url, referer, history_add);
|
|
}
|
|
|
|
|
|
return bw;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialise common parts of a browser window
|
|
*
|
|
* \param bw The window to initialise
|
|
* \param clone The window to clone, or NULL if none
|
|
*/
|
|
void browser_window_initialise_common(struct browser_window *bw,
|
|
struct browser_window *clone)
|
|
{
|
|
assert(bw);
|
|
|
|
if (!clone)
|
|
bw->history = history_create();
|
|
else
|
|
bw->history = history_clone(clone->history);
|
|
|
|
/* window characteristics */
|
|
bw->cur_sel = NULL;
|
|
bw->cur_search = NULL;
|
|
bw->refresh_interval = -1;
|
|
|
|
bw->reformat_pending = false;
|
|
bw->drag_type = DRAGGING_NONE;
|
|
bw->scale = (float) option_scale / 100.0;
|
|
|
|
bw->scroll_x = NULL;
|
|
bw->scroll_y = NULL;
|
|
|
|
bw->focus = NULL;
|
|
|
|
/* initialise status text cache */
|
|
bw->status_text = NULL;
|
|
bw->status_text_len = 0;
|
|
bw->status_match = 0;
|
|
bw->status_miss = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Start fetching a page in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param url URL to start fetching (copied)
|
|
* \param referer the referring uri (copied), or 0 if none
|
|
*
|
|
* Any existing fetches in the window are aborted.
|
|
*/
|
|
|
|
void browser_window_go(struct browser_window *bw, const char *url,
|
|
const char *referer, bool history_add)
|
|
{
|
|
/* All fetches passing through here are verifiable
|
|
* (i.e are the result of user action) */
|
|
browser_window_go_post(bw, url, 0, 0, history_add, referer,
|
|
false, true, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* Start a download of the given URL from a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param url URL to start downloading (copied)
|
|
* \param referer the referring uri (copied), or 0 if none
|
|
*/
|
|
|
|
void browser_window_download(struct browser_window *bw, const char *url,
|
|
const char *referer)
|
|
{
|
|
browser_window_go_post(bw, url, 0, 0, false, referer,
|
|
true, true, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* Start fetching a page in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param url URL to start fetching (copied)
|
|
* \param referer the referring uri (copied), or 0 if none
|
|
*
|
|
* Any existing fetches in the window are aborted.
|
|
*/
|
|
|
|
void browser_window_go_unverifiable(struct browser_window *bw,
|
|
const char *url, const char *referer, bool history_add,
|
|
hlcache_handle *parent)
|
|
{
|
|
/* All fetches passing through here are unverifiable
|
|
* (i.e are not the result of user action) */
|
|
browser_window_go_post(bw, url, 0, 0, history_add, referer,
|
|
false, false, parent);
|
|
}
|
|
|
|
/**
|
|
* Start fetching a page in a browser window, POSTing form data.
|
|
*
|
|
* \param bw browser window
|
|
* \param url URL to start fetching (copied)
|
|
* \param post_urlenc url encoded post data, or 0 if none
|
|
* \param post_multipart multipart post data, or 0 if none
|
|
* \param add_to_history add to window history
|
|
* \param referer the referring uri (copied), or 0 if none
|
|
* \param download download, rather than render the uri
|
|
* \param verifiable this transaction is verifiable
|
|
* \param parent Parent content, or NULL
|
|
*
|
|
* Any existing fetches in the window are aborted.
|
|
*
|
|
* If post_urlenc and post_multipart are 0 the url is fetched using GET.
|
|
*
|
|
* The page is not added to the window history if add_to_history is false.
|
|
* This should be used when returning to a page in the window history.
|
|
*/
|
|
|
|
void browser_window_go_post(struct browser_window *bw, const char *url,
|
|
char *post_urlenc,
|
|
struct fetch_multipart_data *post_multipart,
|
|
bool add_to_history, const char *referer, bool download,
|
|
bool verifiable, hlcache_handle *parent)
|
|
{
|
|
hlcache_handle *c;
|
|
int depth = 0;
|
|
struct browser_window *cur;
|
|
uint32_t fetch_flags = 0;
|
|
bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL);
|
|
llcache_post_data post;
|
|
hlcache_child_context child;
|
|
nserror error;
|
|
|
|
nsurl *nsref = NULL;
|
|
nsurl *nsurl;
|
|
|
|
LOG(("bw %p, url %s", bw, url));
|
|
assert(bw);
|
|
assert(url);
|
|
|
|
/* don't allow massively nested framesets */
|
|
for (cur = bw; cur->parent; cur = cur->parent)
|
|
depth++;
|
|
if (depth > FRAME_DEPTH) {
|
|
LOG(("frame depth too high."));
|
|
return;
|
|
}
|
|
|
|
/* Set up retrieval parameters */
|
|
if (verifiable)
|
|
fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE;
|
|
|
|
if (post_multipart != NULL) {
|
|
post.type = LLCACHE_POST_MULTIPART;
|
|
post.data.multipart = post_multipart;
|
|
} else if (post_urlenc != NULL) {
|
|
post.type = LLCACHE_POST_URL_ENCODED;
|
|
post.data.urlenc = post_urlenc;
|
|
}
|
|
|
|
if (parent != NULL && content_get_type(parent) == CONTENT_HTML) {
|
|
child.charset = html_get_encoding(parent);
|
|
child.quirks = content_get_quirks(parent);
|
|
}
|
|
|
|
error = nsurl_create(url, &nsurl);
|
|
if (error != NSERROR_OK) {
|
|
return;
|
|
}
|
|
|
|
if (referer != NULL) {
|
|
error = nsurl_create(referer, &nsref);
|
|
if (error != NSERROR_OK) {
|
|
nsurl_unref(nsurl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Get download out of the way */
|
|
if (download) {
|
|
llcache_handle *l;
|
|
struct browser_window *root;
|
|
|
|
root = browser_window_get_root(bw);
|
|
assert(root != NULL);
|
|
|
|
fetch_flags |= LLCACHE_RETRIEVE_FORCE_FETCH;
|
|
fetch_flags |= LLCACHE_RETRIEVE_STREAM_DATA;
|
|
|
|
error = llcache_handle_retrieve(nsurl, fetch_flags, nsref,
|
|
fetch_is_post ? &post : NULL,
|
|
NULL, NULL, &l);
|
|
if (error == NSERROR_NO_FETCH_HANDLER) {
|
|
gui_launch_url(nsurl_access(nsurl));
|
|
} else if (error != NSERROR_OK) {
|
|
LOG(("Failed to fetch download: %d", error));
|
|
} else {
|
|
error = download_context_create(l, root->window);
|
|
if (error != NSERROR_OK) {
|
|
LOG(("Failed creating download context: %d",
|
|
error));
|
|
llcache_handle_abort(l);
|
|
llcache_handle_release(l);
|
|
}
|
|
}
|
|
|
|
nsurl_unref(nsurl);
|
|
if (nsref != NULL)
|
|
nsurl_unref(nsref);
|
|
|
|
return;
|
|
}
|
|
|
|
if (bw->frag_id != NULL)
|
|
lwc_string_unref(bw->frag_id);
|
|
bw->frag_id = NULL;
|
|
|
|
if (nsurl_has_component(nsurl, NSURL_FRAGMENT)) {
|
|
bool same_url = false;
|
|
|
|
bw->frag_id = nsurl_get_component(nsurl, NSURL_FRAGMENT);
|
|
|
|
/* Compare new URL with existing one (ignoring fragments) */
|
|
if (bw->current_content != NULL &&
|
|
hlcache_handle_get_url(bw->current_content) != NULL) {
|
|
same_url = nsurl_compare(nsurl,
|
|
hlcache_handle_get_url(bw->current_content),
|
|
NSURL_COMPLETE);
|
|
}
|
|
|
|
/* if we're simply moving to another ID on the same page,
|
|
* don't bother to fetch, just update the window.
|
|
*/
|
|
if (same_url && fetch_is_post == false &&
|
|
nsurl_has_component(nsurl, NSURL_QUERY) ==
|
|
false) {
|
|
nsurl_unref(nsurl);
|
|
if (nsref != NULL)
|
|
nsurl_unref(nsref);
|
|
if (add_to_history)
|
|
history_add(bw->history, bw->current_content,
|
|
bw->frag_id == NULL ? NULL :
|
|
lwc_string_data(bw->frag_id));
|
|
browser_window_update(bw, false);
|
|
if (bw->current_content != NULL) {
|
|
browser_window_refresh_url_bar(bw,
|
|
hlcache_handle_get_url(bw->current_content),
|
|
bw->frag_id);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
browser_window_stop(bw);
|
|
browser_window_remove_caret(bw);
|
|
browser_window_destroy_children(bw);
|
|
|
|
LOG(("Loading '%s'", nsurl_access(nsurl)));
|
|
|
|
browser_window_set_status(bw, messages_get("Loading"));
|
|
bw->history_add = add_to_history;
|
|
|
|
/* fresh javascript compartment */
|
|
bw->jsglobal = js_newcompartment(bw->jsctx);
|
|
|
|
/* Verifiable fetches may trigger a download */
|
|
if (verifiable)
|
|
fetch_flags |= HLCACHE_RETRIEVE_MAY_DOWNLOAD;
|
|
|
|
error = hlcache_handle_retrieve(nsurl,
|
|
fetch_flags | HLCACHE_RETRIEVE_SNIFF_TYPE,
|
|
nsref,
|
|
fetch_is_post ? &post : NULL,
|
|
browser_window_callback, bw,
|
|
parent != NULL ? &child : NULL,
|
|
CONTENT_ANY, &c);
|
|
if (error == NSERROR_NO_FETCH_HANDLER) {
|
|
gui_launch_url(nsurl_access(nsurl));
|
|
nsurl_unref(nsurl);
|
|
if (nsref != NULL)
|
|
nsurl_unref(nsref);
|
|
return;
|
|
} else if (error != NSERROR_OK) {
|
|
nsurl_unref(nsurl);
|
|
if (nsref != NULL)
|
|
nsurl_unref(nsref);
|
|
browser_window_set_status(bw, messages_get("NoMemory"));
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
}
|
|
|
|
bw->loading_content = c;
|
|
browser_window_start_throbber(bw);
|
|
browser_window_refresh_url_bar(bw, nsurl, NULL);
|
|
|
|
nsurl_unref(nsurl);
|
|
if (nsref != NULL)
|
|
nsurl_unref(nsref);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Callback for fetchcache() for browser window favicon fetches.
|
|
*/
|
|
|
|
static nserror browser_window_favicon_callback(hlcache_handle *c,
|
|
const hlcache_event *event, void *pw)
|
|
{
|
|
struct browser_window *bw = pw;
|
|
|
|
switch (event->type) {
|
|
case CONTENT_MSG_DONE:
|
|
if (bw->current_favicon != NULL) {
|
|
content_status status =
|
|
content_get_status(bw->current_favicon);
|
|
|
|
if ((status == CONTENT_STATUS_READY) ||
|
|
(status == CONTENT_STATUS_DONE))
|
|
content_close(bw->current_favicon);
|
|
|
|
hlcache_handle_release(bw->current_favicon);
|
|
}
|
|
|
|
bw->current_favicon = c;
|
|
bw->loading_favicon = NULL;
|
|
|
|
/* content_get_bitmap on the hlcache_handle should give
|
|
* us the favicon bitmap at this point
|
|
*/
|
|
gui_window_set_icon(bw->window, c);
|
|
break;
|
|
|
|
case CONTENT_MSG_ERROR:
|
|
|
|
/* clean up after ourselves */
|
|
if (c == bw->loading_favicon)
|
|
bw->loading_favicon = NULL;
|
|
else if (c == bw->current_favicon) {
|
|
bw->current_favicon = NULL;
|
|
}
|
|
|
|
hlcache_handle_release(c);
|
|
|
|
if (bw->failed_favicon == false) {
|
|
nsurl *nsref = NULL;
|
|
nsurl *nsurl;
|
|
nserror error;
|
|
|
|
bw->failed_favicon = true;
|
|
|
|
error = nsurl_create("resource:favicon.ico", &nsurl);
|
|
if (error != NSERROR_OK) {
|
|
LOG(("Unable to create default location url"));
|
|
} else {
|
|
|
|
hlcache_handle_retrieve(nsurl,
|
|
HLCACHE_RETRIEVE_SNIFF_TYPE,
|
|
nsref, NULL,
|
|
browser_window_favicon_callback,
|
|
bw, NULL, CONTENT_IMAGE,
|
|
&bw->loading_favicon);
|
|
|
|
nsurl_unref(nsurl);
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
static void browser_window_update_favicon(hlcache_handle *c,
|
|
struct browser_window *bw, struct content_rfc5988_link *link)
|
|
{
|
|
lwc_string *icon_str;
|
|
nsurl *nsref = NULL;
|
|
nsurl *nsurl;
|
|
nserror error;
|
|
|
|
assert(c != NULL);
|
|
assert(bw !=NULL);
|
|
|
|
if (bw->window == NULL)
|
|
/* Not top-level browser window; not interested */
|
|
return;
|
|
|
|
/* already fetching the favicon - use that */
|
|
if (bw->loading_favicon != NULL)
|
|
return;
|
|
|
|
bw->failed_favicon = false;
|
|
|
|
if (link == NULL) {
|
|
/* look for favicon metadata link */
|
|
if (lwc_intern_string("icon", SLEN("icon"),
|
|
&icon_str) == lwc_error_ok) {
|
|
link = content_find_rfc5988_link(c, icon_str);
|
|
lwc_string_unref(icon_str);
|
|
}
|
|
}
|
|
|
|
if (link == NULL) {
|
|
if (lwc_intern_string("shortcut icon", SLEN("shortcut icon"),
|
|
&icon_str) == lwc_error_ok) {
|
|
link = content_find_rfc5988_link(c, icon_str);
|
|
lwc_string_unref(icon_str);
|
|
}
|
|
}
|
|
|
|
if (link == NULL) {
|
|
lwc_string *scheme;
|
|
bool speculative_default = false;
|
|
|
|
nsurl = hlcache_handle_get_url(c);
|
|
|
|
scheme = nsurl_get_component(nsurl, NSURL_SCHEME);
|
|
|
|
/* If the document was fetched over http(s), then speculate
|
|
* that there's a favicon living at /favicon.ico */
|
|
if ((lwc_string_length(scheme) == SLEN("HTTP") &&
|
|
strcasecmp(lwc_string_data(scheme),
|
|
"http") == 0) ||
|
|
(lwc_string_length(scheme) == SLEN("HTTPS") &&
|
|
strcasecmp(lwc_string_data(scheme),
|
|
"https") == 0)) {
|
|
speculative_default = true;
|
|
}
|
|
|
|
lwc_string_unref(scheme);
|
|
|
|
if (speculative_default) {
|
|
/* no favicon via link, try for the default location */
|
|
error = nsurl_join(nsurl, "/favicon.ico", &nsurl);
|
|
} else {
|
|
bw->failed_favicon = true;
|
|
error = nsurl_create("resource:favicon.ico", &nsurl);
|
|
}
|
|
if (error != NSERROR_OK) {
|
|
LOG(("Unable to create default location url"));
|
|
return;
|
|
}
|
|
} else {
|
|
nsurl = nsurl_ref(link->href);
|
|
}
|
|
|
|
if (link == NULL) {
|
|
LOG(("fetching general favicon from '%s'",
|
|
nsurl_access(nsurl)));
|
|
} else {
|
|
LOG(("fetching favicon rel:%s '%s'",
|
|
lwc_string_data(link->rel),
|
|
nsurl_access(nsurl)));
|
|
}
|
|
|
|
hlcache_handle_retrieve(nsurl, HLCACHE_RETRIEVE_SNIFF_TYPE,
|
|
nsref, NULL, browser_window_favicon_callback,
|
|
bw, NULL, CONTENT_IMAGE, &bw->loading_favicon);
|
|
|
|
nsurl_unref(nsurl);
|
|
}
|
|
|
|
/**
|
|
* Callback for fetchcache() for browser window fetches.
|
|
*/
|
|
|
|
nserror browser_window_callback(hlcache_handle *c,
|
|
const hlcache_event *event, void *pw)
|
|
{
|
|
struct browser_window *bw = pw;
|
|
|
|
switch (event->type) {
|
|
case CONTENT_MSG_DOWNLOAD:
|
|
assert(bw->loading_content == c);
|
|
|
|
browser_window_convert_to_download(bw, event->data.download);
|
|
|
|
if (bw->current_content != NULL) {
|
|
browser_window_refresh_url_bar(bw,
|
|
hlcache_handle_get_url(bw->current_content),
|
|
bw->frag_id);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_LOADING:
|
|
assert(bw->loading_content == c);
|
|
|
|
#ifdef WITH_THEME_INSTALL
|
|
if (content_get_type(c) == CONTENT_THEME) {
|
|
theme_install_start(c);
|
|
bw->loading_content = NULL;
|
|
browser_window_stop_throbber(bw);
|
|
} else
|
|
#endif
|
|
{
|
|
bw->refresh_interval = -1;
|
|
browser_window_set_status(bw,
|
|
content_get_status_message(c));
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_READY:
|
|
{
|
|
int width, height;
|
|
|
|
assert(bw->loading_content == c);
|
|
|
|
if (bw->current_content != NULL) {
|
|
content_status status =
|
|
content_get_status(bw->current_content);
|
|
|
|
if (status == CONTENT_STATUS_READY ||
|
|
status == CONTENT_STATUS_DONE)
|
|
content_close(bw->current_content);
|
|
|
|
hlcache_handle_release(bw->current_content);
|
|
}
|
|
|
|
bw->current_content = c;
|
|
bw->loading_content = NULL;
|
|
|
|
/* Format the new content to the correct dimensions */
|
|
browser_window_get_dimensions(bw, &width, &height, true);
|
|
content_reformat(c, false, width, height);
|
|
|
|
browser_window_remove_caret(bw);
|
|
|
|
if (bw->window)
|
|
gui_window_new_content(bw->window);
|
|
|
|
browser_window_refresh_url_bar(bw,
|
|
hlcache_handle_get_url(bw->current_content),
|
|
bw->frag_id);
|
|
|
|
/* new content; set scroll_to_top */
|
|
browser_window_update(bw, true);
|
|
content_open(c, bw, 0, 0, 0);
|
|
browser_window_set_status(bw, content_get_status_message(c));
|
|
|
|
/* history */
|
|
if (bw->history_add && bw->history) {
|
|
const char *url = nsurl_access(hlcache_handle_get_url(c));
|
|
|
|
history_add(bw->history, c, bw->frag_id == NULL ? NULL :
|
|
lwc_string_data(bw->frag_id));
|
|
if (urldb_add_url(url)) {
|
|
urldb_set_url_title(url, content_get_title(c));
|
|
urldb_update_url_visit_data(url);
|
|
urldb_set_url_content_type(url,
|
|
content_get_type(c));
|
|
/* This is safe as we've just added the URL */
|
|
global_history_add(urldb_get_url(url));
|
|
}
|
|
}
|
|
|
|
if (bw->window != NULL) {
|
|
browser_window_set_selection(bw,
|
|
content_get_selection(c));
|
|
}
|
|
|
|
/* frames */
|
|
if (content_get_type(c) == CONTENT_HTML &&
|
|
html_get_frameset(c) != NULL)
|
|
browser_window_create_frameset(bw,
|
|
html_get_frameset(c));
|
|
if (content_get_type(c) == CONTENT_HTML &&
|
|
html_get_iframe(c) != NULL)
|
|
browser_window_create_iframes(bw, html_get_iframe(c));
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_DONE:
|
|
assert(bw->current_content == c);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Updated browser window's scrollbars.
|
|
* TODO: do this before CONTENT_MSG_DONE */
|
|
browser_window_reformat(bw, true,
|
|
bw->width, bw->height);
|
|
browser_window_handle_scrollbars(bw);
|
|
}
|
|
|
|
browser_window_update(bw, false);
|
|
browser_window_set_status(bw, content_get_status_message(c));
|
|
browser_window_stop_throbber(bw);
|
|
browser_window_update_favicon(c, bw, NULL);
|
|
|
|
history_update(bw->history, c);
|
|
hotlist_visited(c);
|
|
|
|
if (bw->refresh_interval != -1)
|
|
schedule(bw->refresh_interval,
|
|
browser_window_refresh, bw);
|
|
break;
|
|
|
|
case CONTENT_MSG_ERROR:
|
|
browser_window_set_status(bw, event->data.error);
|
|
|
|
/* Only warn the user about errors in top-level windows */
|
|
if (bw->browser_window_type == BROWSER_WINDOW_NORMAL)
|
|
warn_user(event->data.error, 0);
|
|
|
|
if (c == bw->loading_content)
|
|
bw->loading_content = NULL;
|
|
else if (c == bw->current_content) {
|
|
bw->current_content = NULL;
|
|
browser_window_remove_caret(bw);
|
|
}
|
|
|
|
hlcache_handle_release(c);
|
|
|
|
browser_window_stop_throbber(bw);
|
|
break;
|
|
|
|
case CONTENT_MSG_STATUS:
|
|
browser_window_set_status(bw, content_get_status_message(c));
|
|
break;
|
|
|
|
case CONTENT_MSG_REFORMAT:
|
|
if (c == bw->current_content &&
|
|
content_get_type(c) == CONTENT_HTML) {
|
|
/* reposition frames */
|
|
if (html_get_frameset(c) != NULL)
|
|
browser_window_recalculate_frameset(bw);
|
|
/* reflow iframe positions */
|
|
if (html_get_iframe(c) != NULL)
|
|
browser_window_recalculate_iframes(bw);
|
|
}
|
|
|
|
if (bw->move_callback)
|
|
bw->move_callback(bw, bw->caret_p1, bw->caret_p2);
|
|
|
|
if (!(event->data.background)) {
|
|
/* Reformatted content should be redrawn */
|
|
browser_window_update(bw, false);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_REDRAW:
|
|
{
|
|
struct rect rect = {
|
|
.x0 = event->data.redraw.x,
|
|
.y0 = event->data.redraw.y,
|
|
.x1 = event->data.redraw.x + event->data.redraw.width,
|
|
.y1 = event->data.redraw.y + event->data.redraw.height
|
|
};
|
|
|
|
browser_window_update_box(bw, &rect);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_REFRESH:
|
|
bw->refresh_interval = event->data.delay * 100;
|
|
break;
|
|
|
|
case CONTENT_MSG_LINK: /* content has an rfc5988 link element */
|
|
{
|
|
lwc_string *icon_str;
|
|
lwc_string *shortcut_icon_str;
|
|
bool icon_match = false;
|
|
bool shortcut_icon_match = false;
|
|
|
|
if (lwc_intern_string("icon", SLEN("icon"),
|
|
&icon_str) == lwc_error_ok) {
|
|
lwc_string_caseless_isequal(
|
|
event->data.rfc5988_link->rel,
|
|
icon_str, &icon_match);
|
|
lwc_string_unref(icon_str);
|
|
}
|
|
|
|
if (lwc_intern_string("shortcut icon", SLEN("shortcut icon"),
|
|
&shortcut_icon_str) == lwc_error_ok) {
|
|
lwc_string_caseless_isequal(
|
|
event->data.rfc5988_link->rel,
|
|
shortcut_icon_str,
|
|
&shortcut_icon_match);
|
|
lwc_string_unref(shortcut_icon_str);
|
|
}
|
|
|
|
if (icon_match || shortcut_icon_match) {
|
|
/* it's a favicon perhaps start a fetch for it */
|
|
browser_window_update_favicon(c, bw,
|
|
event->data.rfc5988_link);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the dimensions of the area a browser window occupies
|
|
*
|
|
* \param bw The browser window to get dimensions of
|
|
* \param width Updated to the browser window viewport width
|
|
* \param height Updated to the browser window viewport height
|
|
* \param scaled Whether we want the height with scale applied
|
|
*/
|
|
|
|
void browser_window_get_dimensions(struct browser_window *bw,
|
|
int *width, int *height, bool scaled)
|
|
{
|
|
assert(bw);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window */
|
|
*width = bw->width;
|
|
*height = bw->height;
|
|
} else {
|
|
/* Front end window */
|
|
gui_window_get_dimensions(bw->window, width, height, scaled);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Set the dimensions of the area a browser window occupies
|
|
*
|
|
* \param bw The browser window to set dimensions of
|
|
* \param width Width in pixels
|
|
* \param height Height in pixels
|
|
*/
|
|
|
|
void browser_window_set_dimensions(struct browser_window *bw,
|
|
int width, int height)
|
|
{
|
|
assert(bw);
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window */
|
|
bw->width = width;
|
|
bw->height = height;
|
|
} else {
|
|
LOG(("Asked to set dimensions of front end window."));
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Transfer the loading_content to a new download window.
|
|
*/
|
|
|
|
void browser_window_convert_to_download(struct browser_window *bw,
|
|
llcache_handle *stream)
|
|
{
|
|
struct browser_window *root = browser_window_get_root(bw);
|
|
nserror error;
|
|
|
|
assert(root != NULL);
|
|
|
|
error = download_context_create(stream, root->window);
|
|
if (error != NSERROR_OK) {
|
|
llcache_handle_abort(stream);
|
|
llcache_handle_release(stream);
|
|
}
|
|
|
|
/* remove content from browser window */
|
|
hlcache_handle_release(bw->loading_content);
|
|
bw->loading_content = NULL;
|
|
|
|
browser_window_stop_throbber(bw);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle meta http-equiv refresh time elapsing by loading a new page.
|
|
*
|
|
* \param p browser window to refresh with new page
|
|
*/
|
|
|
|
void browser_window_refresh(void *p)
|
|
{
|
|
struct browser_window *bw = p;
|
|
bool history_add = true;
|
|
const char *url;
|
|
const char *refresh;
|
|
|
|
assert(bw->current_content != NULL &&
|
|
(content_get_status(bw->current_content) ==
|
|
CONTENT_STATUS_READY ||
|
|
content_get_status(bw->current_content) ==
|
|
CONTENT_STATUS_DONE));
|
|
|
|
/* Ignore if the refresh URL has gone
|
|
* (may happen if a fetch error occurred) */
|
|
refresh = nsurl_access(content_get_refresh_url(bw->current_content));
|
|
if (refresh == NULL)
|
|
return;
|
|
|
|
/* mark this content as invalid so it gets flushed from the cache */
|
|
content_invalidate_reuse_data(bw->current_content);
|
|
|
|
url = nsurl_access(hlcache_handle_get_url(bw->current_content));
|
|
if (url != NULL && strcmp(url, refresh) == 0)
|
|
history_add = false;
|
|
|
|
/* Treat an (almost) immediate refresh in a top-level browser window as
|
|
* if it were an HTTP redirect, and thus make the resulting fetch
|
|
* verifiable.
|
|
*
|
|
* See fetchcache.c for why redirected fetches should be verifiable at
|
|
* all.
|
|
*/
|
|
if (bw->refresh_interval <= 100 && bw->parent == NULL) {
|
|
browser_window_go(bw, refresh, url, history_add);
|
|
} else {
|
|
browser_window_go_unverifiable(bw, refresh, url, history_add,
|
|
bw->current_content);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Start the busy indicator.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_start_throbber(struct browser_window *bw)
|
|
{
|
|
bw->throbbing = true;
|
|
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
|
|
gui_window_start_throbber(bw->window);
|
|
}
|
|
|
|
|
|
/**
|
|
* Stop the busy indicator.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_stop_throbber(struct browser_window *bw)
|
|
{
|
|
bw->throbbing = false;
|
|
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
|
|
if (!browser_window_check_throbber(bw))
|
|
gui_window_stop_throbber(bw->window);
|
|
}
|
|
|
|
bool browser_window_check_throbber(struct browser_window *bw)
|
|
{
|
|
int children, index;
|
|
|
|
if (bw->throbbing)
|
|
return true;
|
|
|
|
if (bw->children) {
|
|
children = bw->rows * bw->cols;
|
|
for (index = 0; index < children; index++) {
|
|
if (browser_window_check_throbber(&bw->children[index]))
|
|
return true;
|
|
}
|
|
}
|
|
if (bw->iframes) {
|
|
for (index = 0; index < bw->iframe_count; index++) {
|
|
if (browser_window_check_throbber(&bw->iframes[index]))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Redraw browser window, set extent to content, and update title.
|
|
*
|
|
* \param bw browser_window
|
|
* \param scroll_to_top move view to top of page
|
|
*/
|
|
|
|
void browser_window_update(struct browser_window *bw, bool scroll_to_top)
|
|
{
|
|
int x, y;
|
|
|
|
if (bw->current_content == NULL)
|
|
return;
|
|
|
|
switch (bw->browser_window_type) {
|
|
|
|
case BROWSER_WINDOW_NORMAL:
|
|
/* Root browser window, constituting a front end window/tab */
|
|
gui_window_set_title(bw->window,
|
|
content_get_title(bw->current_content));
|
|
|
|
browser_window_update_extent(bw);
|
|
|
|
if (scroll_to_top)
|
|
browser_window_set_scroll(bw, 0, 0);
|
|
|
|
/* if frag_id exists, then try to scroll to it */
|
|
/** \TODO don't do this if the user has scrolled */
|
|
if (bw->frag_id && html_get_id_offset(bw->current_content,
|
|
bw->frag_id, &x, &y)) {
|
|
browser_window_set_scroll(bw, x, y);
|
|
}
|
|
|
|
gui_window_redraw_window(bw->window);
|
|
|
|
break;
|
|
|
|
case BROWSER_WINDOW_IFRAME:
|
|
/* Internal iframe browser window */
|
|
|
|
browser_window_update_extent(bw);
|
|
|
|
if (scroll_to_top)
|
|
browser_window_set_scroll(bw, 0, 0);
|
|
|
|
/* if frag_id exists, then try to scroll to it */
|
|
/** \TODO don't do this if the user has scrolled */
|
|
if (bw->frag_id && html_get_id_offset(bw->current_content,
|
|
bw->frag_id, &x, &y)) {
|
|
browser_window_set_scroll(bw, x, y);
|
|
}
|
|
|
|
html_redraw_a_box(bw->parent->current_content, bw->box);
|
|
break;
|
|
|
|
case BROWSER_WINDOW_FRAME:
|
|
{
|
|
struct rect rect;
|
|
browser_window_update_extent(bw);
|
|
|
|
if (scroll_to_top)
|
|
browser_window_set_scroll(bw, 0, 0);
|
|
|
|
/* if frag_id exists, then try to scroll to it */
|
|
/** \TODO don't do this if the user has scrolled */
|
|
if (bw->frag_id && html_get_id_offset(bw->current_content,
|
|
bw->frag_id, &x, &y)) {
|
|
browser_window_set_scroll(bw, x, y);
|
|
}
|
|
|
|
rect.x0 = scrollbar_get_offset(bw->scroll_x);
|
|
rect.y0 = scrollbar_get_offset(bw->scroll_y);
|
|
rect.x1 = rect.x0 + bw->width;
|
|
rect.y1 = rect.y0 + bw->height;
|
|
|
|
browser_window_update_box(bw, &rect);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
case BROWSER_WINDOW_FRAMESET:
|
|
/* Nothing to do */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void browser_window_update_box(struct browser_window *bw, struct rect *rect)
|
|
{
|
|
int pos_x;
|
|
int pos_y;
|
|
struct browser_window *top;
|
|
|
|
assert(bw);
|
|
|
|
if (bw->window != NULL) {
|
|
/* Front end window */
|
|
gui_window_update_box(bw->window, rect);
|
|
} else {
|
|
/* Core managed browser window */
|
|
browser_window_get_position(bw, true, &pos_x, &pos_y);
|
|
|
|
top = browser_window_get_root(bw);
|
|
|
|
rect->x0 += pos_x / bw->scale;
|
|
rect->y0 += pos_y / bw->scale;
|
|
rect->x1 += pos_x / bw->scale;
|
|
rect->y1 += pos_y / bw->scale;
|
|
|
|
gui_window_update_box(top->window, rect);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Stop all fetching activity in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_stop(struct browser_window *bw)
|
|
{
|
|
int children, index;
|
|
|
|
if (bw->loading_content != NULL) {
|
|
hlcache_handle_abort(bw->loading_content);
|
|
hlcache_handle_release(bw->loading_content);
|
|
bw->loading_content = NULL;
|
|
}
|
|
|
|
if (bw->current_content != NULL && content_get_status(
|
|
bw->current_content) != CONTENT_STATUS_DONE) {
|
|
nserror error;
|
|
assert(content_get_status(bw->current_content) ==
|
|
CONTENT_STATUS_READY);
|
|
error = hlcache_handle_abort(bw->current_content);
|
|
assert(error == NSERROR_OK);
|
|
}
|
|
|
|
schedule_remove(browser_window_refresh, bw);
|
|
|
|
if (bw->children) {
|
|
children = bw->rows * bw->cols;
|
|
for (index = 0; index < children; index++)
|
|
browser_window_stop(&bw->children[index]);
|
|
}
|
|
if (bw->iframes) {
|
|
children = bw->iframe_count;
|
|
for (index = 0; index < children; index++)
|
|
browser_window_stop(&bw->iframes[index]);
|
|
}
|
|
|
|
if (bw->current_content != NULL) {
|
|
browser_window_refresh_url_bar(bw,
|
|
hlcache_handle_get_url(bw->current_content),
|
|
bw->frag_id);
|
|
}
|
|
|
|
browser_window_stop_throbber(bw);
|
|
}
|
|
|
|
|
|
/**
|
|
* Reload the page in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param all whether to reload all objects associated with the page
|
|
*/
|
|
|
|
void browser_window_reload(struct browser_window *bw, bool all)
|
|
{
|
|
hlcache_handle *c;
|
|
unsigned int i;
|
|
|
|
if (bw->current_content == NULL || bw->loading_content != NULL)
|
|
return;
|
|
|
|
if (all && content_get_type(bw->current_content) == CONTENT_HTML) {
|
|
struct html_stylesheet *sheets;
|
|
struct content_html_object *object;
|
|
unsigned int count;
|
|
|
|
c = bw->current_content;
|
|
|
|
/* invalidate objects */
|
|
object = html_get_objects(c, &count);
|
|
|
|
for (; object != NULL; object = object->next) {
|
|
if (object->content != NULL)
|
|
content_invalidate_reuse_data(object->content);
|
|
}
|
|
|
|
/* invalidate stylesheets */
|
|
sheets = html_get_stylesheets(c, &count);
|
|
|
|
for (i = STYLESHEET_START; i != count; i++) {
|
|
if (sheets[i].type == HTML_STYLESHEET_EXTERNAL &&
|
|
sheets[i].data.external != NULL) {
|
|
content_invalidate_reuse_data(
|
|
sheets[i].data.external);
|
|
}
|
|
}
|
|
}
|
|
|
|
content_invalidate_reuse_data(bw->current_content);
|
|
|
|
browser_window_go(bw, nsurl_access(
|
|
hlcache_handle_get_url(bw->current_content)), 0, false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Change the status bar of a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param text new status text (copied)
|
|
*/
|
|
|
|
void browser_window_set_status(struct browser_window *bw, const char *text)
|
|
{
|
|
int text_len;
|
|
/* find topmost window */
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
|
|
if ((bw->status_text != NULL) &&
|
|
(strcmp(text, bw->status_text) == 0)) {
|
|
/* status text is unchanged */
|
|
bw->status_match++;
|
|
return;
|
|
}
|
|
|
|
/* status text is changed */
|
|
|
|
text_len = strlen(text);
|
|
|
|
if ((bw->status_text == NULL) || (bw->status_text_len < text_len)) {
|
|
/* no current string allocation or it is not long enough */
|
|
free(bw->status_text);
|
|
bw->status_text = strdup(text);
|
|
bw->status_text_len = text_len;
|
|
} else {
|
|
/* current allocation has enough space */
|
|
memcpy(bw->status_text, text, text_len + 1);
|
|
}
|
|
|
|
bw->status_miss++;
|
|
gui_window_set_status(bw->window, bw->status_text);
|
|
}
|
|
|
|
|
|
/**
|
|
* Change the shape of the mouse pointer
|
|
*
|
|
* \param shape shape to use
|
|
*/
|
|
|
|
void browser_window_set_pointer(struct browser_window *bw,
|
|
gui_pointer_shape shape)
|
|
{
|
|
struct browser_window *root = browser_window_get_root(bw);
|
|
|
|
assert(root);
|
|
assert(root->window);
|
|
|
|
gui_window_set_pointer(root->window, shape);
|
|
}
|
|
|
|
|
|
/**
|
|
* Close and destroy a browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_destroy(struct browser_window *bw)
|
|
{
|
|
/* can't destoy child windows on their own */
|
|
assert(!bw->parent);
|
|
|
|
/* destroy */
|
|
browser_window_destroy_internal(bw);
|
|
free(bw);
|
|
}
|
|
|
|
|
|
/**
|
|
* Close and destroy all child browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_destroy_children(struct browser_window *bw)
|
|
{
|
|
int i;
|
|
|
|
if (bw->children) {
|
|
for (i = 0; i < (bw->rows * bw->cols); i++)
|
|
browser_window_destroy_internal(&bw->children[i]);
|
|
free(bw->children);
|
|
bw->children = NULL;
|
|
bw->rows = 0;
|
|
bw->cols = 0;
|
|
}
|
|
if (bw->iframes) {
|
|
for (i = 0; i < bw->iframe_count; i++)
|
|
browser_window_destroy_internal(&bw->iframes[i]);
|
|
free(bw->iframes);
|
|
bw->iframes = NULL;
|
|
bw->iframe_count = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Release all memory associated with a browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_destroy_internal(struct browser_window *bw)
|
|
{
|
|
assert(bw);
|
|
|
|
LOG(("Destroying window"));
|
|
|
|
if (bw->children != NULL || bw->iframes != NULL)
|
|
browser_window_destroy_children(bw);
|
|
|
|
schedule_remove(browser_window_refresh, bw);
|
|
|
|
/* If this brower window is not the root window, and has focus, unset
|
|
* the root browser window's focus pointer. */
|
|
if (!bw->window) {
|
|
struct browser_window *top = browser_window_get_root(bw);
|
|
|
|
if (top->focus == bw)
|
|
top->focus = top;
|
|
|
|
if (bw->current_content != NULL &&
|
|
top->cur_sel == content_get_selection(
|
|
bw->current_content)) {
|
|
browser_window_set_selection(top, NULL);
|
|
}
|
|
}
|
|
|
|
/* Destruction order is important: we must ensure that the frontend
|
|
* destroys any window(s) associated with this browser window before
|
|
* we attempt any destructive cleanup.
|
|
*/
|
|
|
|
if (bw->window) {
|
|
/* Only the root window has a GUI window */
|
|
gui_window_destroy(bw->window);
|
|
}
|
|
|
|
if (bw->loading_content != NULL) {
|
|
hlcache_handle_abort(bw->loading_content);
|
|
hlcache_handle_release(bw->loading_content);
|
|
bw->loading_content = NULL;
|
|
}
|
|
|
|
if (bw->current_content != NULL) {
|
|
content_status status = content_get_status(bw->current_content);
|
|
if (status == CONTENT_STATUS_READY ||
|
|
status == CONTENT_STATUS_DONE)
|
|
content_close(bw->current_content);
|
|
|
|
hlcache_handle_release(bw->current_content);
|
|
bw->current_content = NULL;
|
|
}
|
|
|
|
if (bw->loading_favicon != NULL) {
|
|
hlcache_handle_abort(bw->loading_favicon);
|
|
hlcache_handle_release(bw->loading_favicon);
|
|
bw->loading_favicon = NULL;
|
|
}
|
|
|
|
if (bw->current_favicon != NULL) {
|
|
content_status status = content_get_status(bw->current_favicon);
|
|
|
|
if (status == CONTENT_STATUS_READY ||
|
|
status == CONTENT_STATUS_DONE)
|
|
content_close(bw->current_favicon);
|
|
|
|
hlcache_handle_release(bw->current_favicon);
|
|
bw->current_favicon = NULL;
|
|
}
|
|
|
|
if (bw->box != NULL) {
|
|
bw->box->iframe = NULL;
|
|
bw->box = NULL;
|
|
}
|
|
|
|
if (bw->jsctx != NULL) {
|
|
js_destroycontext(bw->jsctx);
|
|
}
|
|
|
|
/* These simply free memory, so are safe here */
|
|
|
|
if (bw->frag_id != NULL)
|
|
lwc_string_unref(bw->frag_id);
|
|
|
|
history_destroy(bw->history);
|
|
|
|
free(bw->name);
|
|
free(bw->status_text);
|
|
bw->status_text = NULL;
|
|
LOG(("Status text cache match:miss %d:%d",
|
|
bw->status_match, bw->status_miss));
|
|
}
|
|
|
|
|
|
/**
|
|
* Reformat a browser window contents to a new width or height.
|
|
*
|
|
* \param bw the browser window to reformat
|
|
* \param width new width
|
|
* \param height new height
|
|
*/
|
|
|
|
void browser_window_reformat(struct browser_window *bw, bool background,
|
|
int width, int height)
|
|
{
|
|
hlcache_handle *c = bw->current_content;
|
|
|
|
if (c == NULL)
|
|
return;
|
|
|
|
if (bw->browser_window_type != BROWSER_WINDOW_IFRAME) {
|
|
/* Iframe dimensions are already scaled in parent's layout */
|
|
width /= bw->scale;
|
|
height /= bw->scale;
|
|
}
|
|
|
|
if (bw->window == NULL) {
|
|
/* Core managed browser window; subtract scrollbar width */
|
|
width -= bw->scroll_y ? SCROLLBAR_WIDTH : 0;
|
|
height -= bw->scroll_x ? SCROLLBAR_WIDTH : 0;
|
|
|
|
width = width > 0 ? width : 0;
|
|
height = height > 0 ? height : 0;
|
|
}
|
|
|
|
content_reformat(c, background, width, height);
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the scale of a browser window
|
|
*
|
|
* \param bw The browser window to scale
|
|
* \param scale The new scale
|
|
* \param all Scale all windows in the tree (ie work up aswell as down)
|
|
*/
|
|
|
|
void browser_window_set_scale(struct browser_window *bw, float scale, bool all)
|
|
{
|
|
while (bw->parent && all)
|
|
bw = bw->parent;
|
|
|
|
browser_window_set_scale_internal(bw, scale);
|
|
|
|
if (bw->parent)
|
|
bw = bw->parent;
|
|
|
|
browser_window_recalculate_frameset(bw);
|
|
}
|
|
|
|
void browser_window_set_scale_internal(struct browser_window *bw, float scale)
|
|
{
|
|
int i;
|
|
hlcache_handle *c;
|
|
|
|
if (fabs(bw->scale-scale) < 0.0001)
|
|
return;
|
|
|
|
bw->scale = scale;
|
|
c = bw->current_content;
|
|
|
|
if (c != NULL) {
|
|
if (content_can_reformat(c) == false) {
|
|
browser_window_update(bw, false);
|
|
} else {
|
|
bw->reformat_pending = true;
|
|
browser_reformat_pending = true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < (bw->cols * bw->rows); i++)
|
|
browser_window_set_scale_internal(&bw->children[i], scale);
|
|
for (i = 0; i < bw->iframe_count; i++)
|
|
browser_window_set_scale_internal(&bw->iframes[i], scale);
|
|
}
|
|
|
|
|
|
/**
|
|
* Update URL bar for a given browser window to given URL
|
|
*
|
|
* \param bw Browser window to update URL bar for.
|
|
* \param url URL for content displayed by bw, excluding any fragment.
|
|
* \param frag Additional fragment. May be NULL if none.
|
|
*/
|
|
|
|
void browser_window_refresh_url_bar(struct browser_window *bw, nsurl *url,
|
|
lwc_string *frag)
|
|
{
|
|
assert(bw);
|
|
assert(url);
|
|
|
|
if (bw->parent != NULL) {
|
|
/* Not root window; don't set a URL in GUI URL bar */
|
|
return;
|
|
}
|
|
|
|
if (frag == NULL) {
|
|
/* With no fragment, we may as well pass url straight through
|
|
* saving a malloc, copy, free cycle.
|
|
*/
|
|
gui_window_set_url(bw->window, nsurl_access(url));
|
|
} else {
|
|
nsurl *display_url;
|
|
nserror error;
|
|
|
|
error = nsurl_refragment(url, frag, &display_url);
|
|
if (error != NSERROR_OK) {
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
}
|
|
|
|
gui_window_set_url(bw->window, nsurl_access(display_url));
|
|
nsurl_unref(display_url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Locate a browser window in the specified stack according.
|
|
*
|
|
* \param bw the browser_window to search all relatives of
|
|
* \param target the target to locate
|
|
* \param new_window always return a new window (ie 'Open Link in New Window')
|
|
*/
|
|
|
|
struct browser_window *browser_window_find_target(struct browser_window *bw,
|
|
const char *target, browser_mouse_state mouse)
|
|
{
|
|
struct browser_window *bw_target;
|
|
struct browser_window *top;
|
|
hlcache_handle *c;
|
|
int rdepth;
|
|
|
|
/* use the base target if we don't have one */
|
|
c = bw->current_content;
|
|
if (target == NULL && c != NULL && content_get_type(c) == CONTENT_HTML)
|
|
target = html_get_base_target(c);
|
|
if (target == NULL)
|
|
target = TARGET_SELF;
|
|
|
|
/* allow the simple case of target="_blank" to be ignored if requested
|
|
*/
|
|
if ((!(mouse & BROWSER_MOUSE_CLICK_2)) &&
|
|
(!((mouse & BROWSER_MOUSE_CLICK_2) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) &&
|
|
(!option_target_blank)) {
|
|
/* not a mouse button 2 click
|
|
* not a mouse button 1 click with ctrl pressed
|
|
* configured to ignore target="_blank" */
|
|
if ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank")))
|
|
return bw;
|
|
}
|
|
|
|
/* handle reserved keywords */
|
|
if (((option_button_2_tab) && (mouse & BROWSER_MOUSE_CLICK_2)) ||
|
|
((!option_button_2_tab) &&
|
|
((mouse & BROWSER_MOUSE_CLICK_1) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) ||
|
|
((option_button_2_tab) && ((target == TARGET_BLANK) ||
|
|
(!strcasecmp(target, "_blank"))))) {
|
|
/* open in new tab if:
|
|
* - button_2 opens in new tab and button_2 was pressed
|
|
* OR
|
|
* - button_2 doesn't open in new tabs and button_1 was
|
|
* pressed with ctrl held
|
|
* OR
|
|
* - button_2 opens in new tab and the link target is "_blank"
|
|
*/
|
|
bw_target = browser_window_create(NULL, bw, NULL, false, true);
|
|
if (!bw_target)
|
|
return bw;
|
|
return bw_target;
|
|
} else if (((!option_button_2_tab) &&
|
|
(mouse & BROWSER_MOUSE_CLICK_2)) ||
|
|
((option_button_2_tab) &&
|
|
((mouse & BROWSER_MOUSE_CLICK_1) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) ||
|
|
((!option_button_2_tab) && ((target == TARGET_BLANK) ||
|
|
(!strcasecmp(target, "_blank"))))) {
|
|
/* open in new window if:
|
|
* - button_2 doesn't open in new tabs and button_2 was pressed
|
|
* OR
|
|
* - button_2 opens in new tab and button_1 was pressed with
|
|
* ctrl held
|
|
* OR
|
|
* - button_2 doesn't open in new tabs and the link target is
|
|
* "_blank"
|
|
*/
|
|
bw_target = browser_window_create(NULL, bw, NULL, false, false);
|
|
if (!bw_target)
|
|
return bw;
|
|
return bw_target;
|
|
} else if ((target == TARGET_SELF) || (!strcasecmp(target, "_self"))) {
|
|
return bw;
|
|
} else if ((target == TARGET_PARENT) ||
|
|
(!strcasecmp(target, "_parent"))) {
|
|
if (bw->parent)
|
|
return bw->parent;
|
|
return bw;
|
|
} else if ((target == TARGET_TOP) || (!strcasecmp(target, "_top"))) {
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
return bw;
|
|
}
|
|
|
|
/* find frame according to B.8, ie using the following priorities:
|
|
*
|
|
* 1) current frame
|
|
* 2) closest to front
|
|
*/
|
|
rdepth = -1;
|
|
bw_target = NULL;
|
|
for (top = bw; top->parent; top = top->parent);
|
|
browser_window_find_target_internal(top, target, 0, bw, &rdepth,
|
|
&bw_target);
|
|
if (bw_target)
|
|
return bw_target;
|
|
|
|
/* we require a new window using the target name */
|
|
if (!option_target_blank)
|
|
return bw;
|
|
bw_target = browser_window_create(NULL, bw, NULL, false, false);
|
|
if (!bw_target)
|
|
return bw;
|
|
|
|
/* frame names should begin with an alphabetic character (a-z,A-Z),
|
|
* however in practice you get things such as '_new' and '2left'. The
|
|
* only real effect this has is when giving out names as it can be
|
|
* assumed that an author intended '_new' to create a new nameless
|
|
* window (ie '_blank') whereas in the case of '2left' the intention
|
|
* was for a new named window. As such we merely special case windows
|
|
* that begin with an underscore. */
|
|
if (target[0] != '_') {
|
|
bw_target->name = strdup(target);
|
|
if (!bw_target->name)
|
|
warn_user("NoMemory", 0);
|
|
}
|
|
return bw_target;
|
|
}
|
|
|
|
void browser_window_find_target_internal(struct browser_window *bw,
|
|
const char *target, int depth, struct browser_window *page,
|
|
int *rdepth, struct browser_window **bw_target)
|
|
{
|
|
int i;
|
|
|
|
if ((bw->name) && (!strcasecmp(bw->name, target))) {
|
|
if ((bw == page) || (depth > *rdepth)) {
|
|
*rdepth = depth;
|
|
*bw_target = bw;
|
|
}
|
|
}
|
|
|
|
if ((!bw->children) && (!bw->iframes))
|
|
return;
|
|
|
|
depth++;
|
|
|
|
if (bw->children != NULL) {
|
|
for (i = 0; i < (bw->cols * bw->rows); i++) {
|
|
if ((bw->children[i].name) &&
|
|
(!strcasecmp(bw->children[i].name,
|
|
target))) {
|
|
if ((page == &bw->children[i]) ||
|
|
(depth > *rdepth)) {
|
|
*rdepth = depth;
|
|
*bw_target = &bw->children[i];
|
|
}
|
|
}
|
|
if (bw->children[i].children)
|
|
browser_window_find_target_internal(
|
|
&bw->children[i],
|
|
target, depth, page,
|
|
rdepth, bw_target);
|
|
}
|
|
}
|
|
|
|
if (bw->iframes != NULL) {
|
|
for (i = 0; i < bw->iframe_count; i++)
|
|
browser_window_find_target_internal(&bw->iframes[i],
|
|
target, depth, page, rdepth, bw_target);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle non-click mouse action in a browser window. (drag ends, movements)
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
|
|
void browser_window_mouse_track(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
hlcache_handle *c = bw->current_content;
|
|
const char *status = NULL;
|
|
gui_pointer_shape pointer = GUI_POINTER_DEFAULT;
|
|
|
|
if (bw->window != NULL && bw->drag_window && bw != bw->drag_window) {
|
|
/* This is the root browser window and there's an active drag
|
|
* in a sub window.
|
|
* Pass the mouse action straight on to that bw. */
|
|
struct browser_window *drag_bw = bw->drag_window;
|
|
int off_x = 0;
|
|
int off_y = 0;
|
|
|
|
browser_window_get_position(drag_bw, true, &off_x, &off_y);
|
|
|
|
if (drag_bw->browser_window_type == BROWSER_WINDOW_FRAME) {
|
|
browser_window_mouse_track(drag_bw, mouse,
|
|
x - off_x, y - off_y);
|
|
|
|
} else if (drag_bw->browser_window_type ==
|
|
BROWSER_WINDOW_IFRAME) {
|
|
browser_window_mouse_track(drag_bw, mouse,
|
|
x - off_x / bw->scale,
|
|
y - off_y / bw->scale);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children (frames) */
|
|
struct browser_window *child;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
|
|
child = &bw->children[cur_child];
|
|
|
|
if (x < child->x || y < child->y ||
|
|
child->x + child->width < x ||
|
|
child->y + child->height < y) {
|
|
/* Click not in this child */
|
|
continue;
|
|
}
|
|
|
|
/* It's this child that contains the mouse; pass
|
|
* mouse action on to child */
|
|
browser_window_mouse_track(child, mouse,
|
|
x - child->x + scrollbar_get_offset(
|
|
child->scroll_x),
|
|
y - child->y + scrollbar_get_offset(
|
|
child->scroll_y));
|
|
|
|
/* Mouse action was for this child, we're done */
|
|
return;
|
|
}
|
|
|
|
/* Odd if we reached here, but nothing else can use the click
|
|
* when there are children. */
|
|
return;
|
|
}
|
|
|
|
if (c == NULL && bw->drag_type != DRAGGING_FRAME)
|
|
return;
|
|
|
|
if (bw->drag_type != DRAGGING_NONE && !mouse) {
|
|
browser_window_mouse_drag_end(bw, mouse, x, y);
|
|
}
|
|
|
|
/* Browser window's horizontal scrollbar */
|
|
if (bw->scroll_x != NULL && bw->drag_type != DRAGGING_SCR_Y) {
|
|
int scr_x, scr_y;
|
|
browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y);
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if ((scr_x > 0 && scr_x < browser_window_get_scrollbar_len(bw,
|
|
true) &&
|
|
scr_y > 0 && scr_y < SCROLLBAR_WIDTH &&
|
|
bw->drag_type == DRAGGING_NONE) ||
|
|
bw->drag_type == DRAGGING_SCR_X) {
|
|
/* Start a scrollbar drag, or continue existing drag */
|
|
status = scrollbar_mouse_action(bw->scroll_x, mouse,
|
|
scr_x, scr_y);
|
|
pointer = GUI_POINTER_DEFAULT;
|
|
|
|
if (status != NULL)
|
|
browser_window_set_status(bw, status);
|
|
|
|
browser_window_set_pointer(bw, pointer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Browser window's vertical scrollbar */
|
|
if (bw->scroll_y != NULL) {
|
|
int scr_x, scr_y;
|
|
browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y);
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if ((scr_y > 0 && scr_y < browser_window_get_scrollbar_len(bw,
|
|
false) &&
|
|
scr_x > 0 && scr_x < SCROLLBAR_WIDTH &&
|
|
bw->drag_type == DRAGGING_NONE) ||
|
|
bw->drag_type == DRAGGING_SCR_Y) {
|
|
/* Start a scrollbar drag, or continue existing drag */
|
|
status = scrollbar_mouse_action(bw->scroll_y, mouse,
|
|
scr_x, scr_y);
|
|
pointer = GUI_POINTER_DEFAULT;
|
|
|
|
if (status != NULL)
|
|
browser_window_set_status(bw, status);
|
|
|
|
browser_window_set_pointer(bw, pointer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (bw->drag_type == DRAGGING_FRAME) {
|
|
browser_window_resize_frame(bw, bw->x + x, bw->y + y);
|
|
} else if (bw->drag_type == DRAGGING_PAGE_SCROLL) {
|
|
/* mouse movement since drag started */
|
|
int scrollx = bw->drag_start_x - x;
|
|
int scrolly = bw->drag_start_y - y;
|
|
|
|
/* new scroll offsets */
|
|
scrollx += bw->drag_start_scroll_x;
|
|
scrolly += bw->drag_start_scroll_y;
|
|
|
|
bw->drag_start_scroll_x = scrollx;
|
|
bw->drag_start_scroll_y = scrolly;
|
|
|
|
browser_window_set_scroll(bw, scrollx, scrolly);
|
|
} else {
|
|
assert(c != NULL);
|
|
content_mouse_track(c, bw, mouse, x, y);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse clicks in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
|
|
void browser_window_mouse_click(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
hlcache_handle *c = bw->current_content;
|
|
const char *status = NULL;
|
|
gui_pointer_shape pointer = GUI_POINTER_DEFAULT;
|
|
|
|
if (bw->children) {
|
|
/* Browser window has children (frames) */
|
|
struct browser_window *child;
|
|
int cur_child;
|
|
int children = bw->rows * bw->cols;
|
|
|
|
for (cur_child = 0; cur_child < children; cur_child++) {
|
|
|
|
child = &bw->children[cur_child];
|
|
|
|
if (x < child->x || y < child->y ||
|
|
child->x + child->width < x ||
|
|
child->y + child->height < y) {
|
|
/* Click not in this child */
|
|
continue;
|
|
}
|
|
|
|
/* It's this child that contains the click; pass it
|
|
* on to child. */
|
|
browser_window_mouse_click(child, mouse,
|
|
x - child->x + scrollbar_get_offset(
|
|
child->scroll_x),
|
|
y - child->y + scrollbar_get_offset(
|
|
child->scroll_y));
|
|
|
|
/* Mouse action was for this child, we're done */
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
if (bw->scroll_x != NULL) {
|
|
int scr_x, scr_y;
|
|
browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y);
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (scr_x > 0 && scr_x < browser_window_get_scrollbar_len(bw,
|
|
true) &&
|
|
scr_y > 0 && scr_y < SCROLLBAR_WIDTH) {
|
|
status = scrollbar_mouse_action(bw->scroll_x, mouse,
|
|
scr_x, scr_y);
|
|
pointer = GUI_POINTER_DEFAULT;
|
|
|
|
if (status != NULL)
|
|
browser_window_set_status(bw, status);
|
|
|
|
browser_window_set_pointer(bw, pointer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (bw->scroll_y != NULL) {
|
|
int scr_x, scr_y;
|
|
browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y);
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
if (scr_y > 0 && scr_y < browser_window_get_scrollbar_len(bw,
|
|
false) &&
|
|
scr_x > 0 && scr_x < SCROLLBAR_WIDTH) {
|
|
status = scrollbar_mouse_action(bw->scroll_y, mouse,
|
|
scr_x, scr_y);
|
|
pointer = GUI_POINTER_DEFAULT;
|
|
|
|
if (status != NULL)
|
|
browser_window_set_status(bw, status);
|
|
|
|
browser_window_set_pointer(bw, pointer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (content_get_type(c)) {
|
|
case CONTENT_HTML:
|
|
case CONTENT_TEXTPLAIN:
|
|
content_mouse_action(c, bw, mouse, x, y);
|
|
break;
|
|
default:
|
|
if (mouse & BROWSER_MOUSE_MOD_2) {
|
|
if (mouse & BROWSER_MOUSE_DRAG_2)
|
|
gui_drag_save_object(GUI_SAVE_OBJECT_NATIVE, c,
|
|
bw->window);
|
|
else if (mouse & BROWSER_MOUSE_DRAG_1)
|
|
gui_drag_save_object(GUI_SAVE_OBJECT_ORIG, c,
|
|
bw->window);
|
|
}
|
|
else if (mouse & (BROWSER_MOUSE_DRAG_1 |
|
|
BROWSER_MOUSE_DRAG_2)) {
|
|
browser_window_page_drag_start(bw, x, y);
|
|
browser_window_set_pointer(bw, GUI_POINTER_MOVE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles the end of a drag operation in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*
|
|
* TODO: Remove this function, once these things are associated with content,
|
|
* rather than bw.
|
|
*/
|
|
|
|
void browser_window_mouse_drag_end(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
int scr_x, scr_y;
|
|
|
|
switch (bw->drag_type) {
|
|
case DRAGGING_SELECTION:
|
|
/* Drag handled by content handler */
|
|
break;
|
|
|
|
case DRAGGING_OTHER:
|
|
/* Drag handled by content handler */
|
|
break;
|
|
|
|
case DRAGGING_SCR_X:
|
|
|
|
browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y);
|
|
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
scrollbar_mouse_drag_end(bw->scroll_x, mouse, scr_x, scr_y);
|
|
|
|
bw->drag_type = DRAGGING_NONE;
|
|
break;
|
|
|
|
case DRAGGING_SCR_Y:
|
|
|
|
browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y);
|
|
|
|
scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x);
|
|
scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y);
|
|
|
|
scrollbar_mouse_drag_end(bw->scroll_y, mouse, scr_x, scr_y);
|
|
|
|
bw->drag_type = DRAGGING_NONE;
|
|
break;
|
|
|
|
default:
|
|
browser_window_set_drag_type(bw, DRAGGING_NONE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Redraw a rectangular region of a browser window
|
|
*
|
|
* \param bw browser window to be redrawn
|
|
* \param x x co-ord of top-left
|
|
* \param y y co-ord of top-left
|
|
* \param width width of rectangle
|
|
* \param height height of rectangle
|
|
*/
|
|
|
|
void browser_window_redraw_rect(struct browser_window *bw, int x, int y,
|
|
int width, int height)
|
|
{
|
|
content_request_redraw(bw->current_content, x, y, width, height);
|
|
}
|
|
|
|
|
|
/**
|
|
* Start drag scrolling the contents of the browser window
|
|
*
|
|
* \param bw browser window
|
|
* \param x x ordinate of initial mouse position
|
|
* \param y y ordinate
|
|
*/
|
|
|
|
void browser_window_page_drag_start(struct browser_window *bw, int x, int y)
|
|
{
|
|
browser_window_set_drag_type(bw, DRAGGING_PAGE_SCROLL);
|
|
|
|
bw->drag_start_x = x;
|
|
bw->drag_start_y = y;
|
|
|
|
if (bw->window != NULL) {
|
|
/* Front end window */
|
|
gui_window_get_scroll(bw->window, &bw->drag_start_scroll_x,
|
|
&bw->drag_start_scroll_y);
|
|
|
|
gui_window_scroll_start(bw->window);
|
|
} else {
|
|
/* Core managed browser window */
|
|
bw->drag_start_scroll_x = scrollbar_get_offset(bw->scroll_x);
|
|
bw->drag_start_scroll_y = scrollbar_get_offset(bw->scroll_y);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Check availability of Back action for a given browser window
|
|
*
|
|
* \param bw browser window
|
|
* \return true if Back action is available
|
|
*/
|
|
|
|
bool browser_window_back_available(struct browser_window *bw)
|
|
{
|
|
return (bw && bw->history && history_back_available(bw->history));
|
|
}
|
|
|
|
|
|
/**
|
|
* Check availability of Forward action for a given browser window
|
|
*
|
|
* \param bw browser window
|
|
* \return true if Forward action is available
|
|
*/
|
|
|
|
bool browser_window_forward_available(struct browser_window *bw)
|
|
{
|
|
return (bw && bw->history && history_forward_available(bw->history));
|
|
}
|
|
|
|
|
|
/**
|
|
* Check availability of Reload action for a given browser window
|
|
*
|
|
* \param bw browser window
|
|
* \return true if Reload action is available
|
|
*/
|
|
|
|
bool browser_window_reload_available(struct browser_window *bw)
|
|
{
|
|
return (bw && bw->current_content && !bw->loading_content);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check availability of Stop action for a given browser window
|
|
*
|
|
* \param bw browser window
|
|
* \return true if Stop action is available
|
|
*/
|
|
|
|
bool browser_window_stop_available(struct browser_window *bw)
|
|
{
|
|
return (bw && (bw->loading_content ||
|
|
(bw->current_content &&
|
|
(content_get_status(bw->current_content) !=
|
|
CONTENT_STATUS_DONE))));
|
|
}
|